├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_requests.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md ├── actions │ └── build-and-persist-plugin-binary │ │ └── action.yml ├── dependabot.yml ├── release.yml └── workflows │ ├── build_plugin_binaries.yml │ ├── go-test-darwin.yml │ ├── go-test-linux.yml │ ├── go-test-windows.yml │ ├── go-validate.yml │ ├── notify-integration-release-via-manual.yaml │ ├── notify-integration-release-via-tag.yaml │ └── release.yml ├── .gitignore ├── .go-version ├── .golangci.yml ├── .goreleaser.yml ├── .web-docs ├── README.md ├── components │ ├── builder │ │ └── vagrant │ │ │ └── README.md │ └── post-processor │ │ ├── vagrant-cloud │ │ └── README.md │ │ ├── vagrant-registry │ │ └── README.md │ │ └── vagrant │ │ └── README.md ├── metadata.hcl └── scripts │ └── compile-to-webdocs.sh ├── CHANGELOG.md ├── CODEOWNERS ├── GNUmakefile ├── LICENSE ├── README.md ├── builder └── vagrant │ ├── artifact.go │ ├── artifact_test.go │ ├── builder.go │ ├── builder.hcl2spec.go │ ├── builder_test.go │ ├── driver.go │ ├── driver_2_2.go │ ├── driver_mock.go │ ├── ssh.go │ ├── step_add_box.go │ ├── step_add_box_test.go │ ├── step_create_vagrantfile.go │ ├── step_create_vagrantfile_test.go │ ├── step_package.go │ ├── step_ssh_config.go │ ├── step_ssh_config_test.go │ ├── step_up.go │ └── step_up_test.go ├── docs-partials └── builder │ └── vagrant │ ├── Config-not-required.mdx │ └── Config-required.mdx ├── docs ├── README.md ├── builders │ └── vagrant.mdx └── post-processors │ ├── vagrant-cloud.mdx │ ├── vagrant-registry.mdx │ └── vagrant.mdx ├── example ├── vagrant_builder.pkr.hcl ├── vagrant_cloud_postprocessor.pkr.hcl └── vagrant_postprocessor.pkr.hcl ├── go.mod ├── go.sum ├── main.go ├── post-processor ├── hcp-vagrant-registry │ ├── artifact.go │ ├── artifact_test.go │ ├── post-processor.go │ ├── post-processor.hcl2spec.go │ ├── post-processor_test.go │ ├── step_confirm_upload.go │ ├── step_create_architecture.go │ ├── step_create_box.go │ ├── step_create_provider.go │ ├── step_create_version.go │ ├── step_prepare_upload.go │ ├── step_release_version.go │ ├── step_upload.go │ └── util.go ├── vagrant-cloud │ ├── artifact.go │ ├── artifact_test.go │ ├── client.go │ ├── client_test.go │ ├── post-processor.go │ ├── post-processor.hcl2spec.go │ ├── post-processor_test.go │ ├── step_confirm_upload.go │ ├── step_create_provider.go │ ├── step_create_version.go │ ├── step_prepare_upload.go │ ├── step_release_version.go │ ├── step_upload.go │ └── step_verify_box.go └── vagrant │ ├── artifact.go │ ├── artifact_test.go │ ├── aws.go │ ├── aws_test.go │ ├── azure.go │ ├── azure_test.go │ ├── digitalocean.go │ ├── digitalocean_test.go │ ├── docker.go │ ├── docker_test.go │ ├── file.go │ ├── file_test.go │ ├── google.go │ ├── google_test.go │ ├── hyperv.go │ ├── libvirt.go │ ├── libvirt_test.go │ ├── lxc.go │ ├── lxc_test.go │ ├── parallels.go │ ├── parallels_test.go │ ├── post-processor.go │ ├── post-processor.hcl2spec.go │ ├── post-processor_test.go │ ├── provider.go │ ├── scaleway.go │ ├── tar_fix.go │ ├── tar_fix_go110.go │ ├── test-fixtures │ └── decompress-tar │ │ └── outside_parent.tar │ ├── util.go │ ├── virtualbox.go │ ├── virtualbox_test.go │ ├── vmware.go │ └── vmware_test.go └── version └── version.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: You're experiencing an issue with this Packer plugin that is different than the documented behavior. 4 | labels: bug 5 | --- 6 | 7 | When filing a bug, please include the following headings if possible. Any 8 | example text in this template can be deleted. 9 | 10 | #### Overview of the Issue 11 | 12 | A paragraph or two about the issue you're experiencing. 13 | 14 | #### Reproduction Steps 15 | 16 | Steps to reproduce this issue 17 | 18 | ### Plugin and Packer version 19 | 20 | From `packer version` 21 | 22 | ### Simplified Packer Buildfile 23 | 24 | If the file is longer than a few dozen lines, please include the URL to the 25 | [gist](https://gist.github.com/) of the log or use the [Github detailed 26 | format](https://gist.github.com/ericclemmons/b146fe5da72ca1f706b2ef72a20ac39d) 27 | instead of posting it directly in the issue. 28 | 29 | ### Operating system and Environment details 30 | 31 | OS, Architecture, and any other information you can provide about the 32 | environment. 33 | 34 | ### Log Fragments and crash.log files 35 | 36 | Include appropriate log fragments. If the log is longer than a few dozen lines, 37 | please include the URL to the [gist](https://gist.github.com/) of the log or 38 | use the [Github detailed format](https://gist.github.com/ericclemmons/b146fe5da72ca1f706b2ef72a20ac39d) instead of posting it directly in the issue. 39 | 40 | Set the env var `PACKER_LOG=1` for maximum log detail. 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_requests.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: If you have something you think this Packer plugin could improve or add support for. 4 | labels: enhancement 5 | --- 6 | 7 | Please search the existing issues for relevant feature requests, and use the 8 | reaction feature 9 | (https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) 10 | to add upvotes to pre-existing requests. 11 | 12 | #### Community Note 13 | 14 | Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request. 15 | Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request. 16 | If you are interested in working on this issue or have submitted a pull request, please leave a comment. 17 | 18 | #### Description 19 | 20 | A written overview of the feature. 21 | 22 | #### Use Case(s) 23 | 24 | Any relevant use-cases that you see. 25 | 26 | #### Potential configuration 27 | 28 | ``` 29 | ``` 30 | 31 | #### Potential References 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: If you have a question, please check out our other community resources instead of opening an issue. 4 | labels: question 5 | --- 6 | 7 | Issues on GitHub are intended to be related to bugs or feature requests, so we 8 | recommend using our other community resources instead of asking here if you 9 | have a question. 10 | 11 | - Packer Guides: 12 | - Packer Community Tools: https://developer.hashicorp.com/packer/docs/community-tools enumerates 13 | vetted community resources like examples and useful tools 14 | - Any other questions can be sent to the Packer section of the HashiCorp 15 | forum: 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **DELETE THIS PART BEFORE SUBMITTING** 2 | 3 | In order to have a good experience with our community, we recommend that you 4 | read the contributing guidelines for making a PR, and understand the lifecycle 5 | of a Packer Plugin PR: 6 | 7 | https://github.com/hashicorp/packer-plugin-vagrant/blob/main/.github/CONTRIBUTING.md#opening-an-pull-request 8 | 9 | ---- 10 | 11 | ### Description 12 | What code changed, and why? 13 | 14 | 15 | ### Resolved Issues 16 | If your PR resolves any open issue(s), please indicate them like this so they will be closed when your PR is merged: 17 | 18 | Closes #xxx 19 | Closes #xxx 20 | 21 | 22 | ### Rollback Plan 23 | 24 | If a change needs to be reverted, we will roll out an update to the code within 7 days. 25 | 26 | ### Changes to Security Controls 27 | 28 | Are there any changes to security controls (access controls, encryption, logging) in this pull request? If so, explain. 29 | 30 | -------------------------------------------------------------------------------- /.github/actions/build-and-persist-plugin-binary/action.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | name: build-and-persist-plugin-binary 5 | inputs: 6 | GOOS: 7 | required: true 8 | GOARCH: 9 | required: true 10 | runs: 11 | using: composite 12 | steps: 13 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 14 | - run: "GOOS=${{ inputs.GOOS }} GOARCH=${{ inputs.GOARCH }} go build -o ./pkg/packer_plugin_vagrant_${{ inputs.GOOS }}_${{ inputs.GOARCH }} ." 15 | shell: bash 16 | - run: zip ./pkg/packer_plugin_vagrant_${{ inputs.GOOS }}_${{ inputs.GOARCH }}.zip ./pkg/packer_plugin_vagrant_${{ inputs.GOOS }}_${{ inputs.GOARCH }} 17 | shell: bash 18 | - run: rm ./pkg/packer_plugin_vagrant_${{ inputs.GOOS }}_${{ inputs.GOARCH }} 19 | shell: bash 20 | - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 21 | with: 22 | name: "packer_plugin_vagrant_${{ inputs.GOOS }}_${{ inputs.GOARCH }}.zip" 23 | path: "pkg/packer_plugin_vagrant_${{ inputs.GOOS }}_${{ inputs.GOARCH }}.zip" 24 | retention-days: 30 25 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "gomod" # See documentation for possible values 7 | directory: "/" # Location of package manifests 8 | schedule: 9 | interval: "daily" 10 | allow: 11 | - dependency-name: "github.com/hashicorp/packer-plugin-sdk" 12 | 13 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | changelog: 5 | exclude: 6 | labels: 7 | - ignore-for-release 8 | categories: 9 | - title: Breaking Changes 🛠 10 | labels: 11 | - breaking-change 12 | - title: Exciting New Features 🎉 13 | labels: 14 | - enhancement 15 | - title: Bug fixes🧑‍🔧 🐞 16 | labels: 17 | - bug 18 | - title: Doc improvements 📚 19 | labels: 20 | - docs 21 | - documentation 22 | - title: Other Changes 23 | labels: 24 | - "*" 25 | 26 | -------------------------------------------------------------------------------- /.github/workflows/build_plugin_binaries.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | name: hashicorp/packer-plugin-vagrant/build_plugin_binaries 5 | permissions: 6 | contents: read 7 | on: 8 | push: 9 | branches: 10 | - main 11 | jobs: 12 | build_darwin: 13 | defaults: 14 | run: 15 | working-directory: ~/go/src/github.com/hashicorp/packer-plugin-vagrant 16 | runs-on: ubuntu-latest 17 | container: 18 | image: docker.mirror.hashicorp.services/cimg/go:1.21 19 | steps: 20 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 21 | - uses: "./.github/actions/build-and-persist-plugin-binary" 22 | with: 23 | GOOS: darwin 24 | GOARCH: amd64 25 | - uses: "./.github/actions/build-and-persist-plugin-binary" 26 | with: 27 | GOOS: darwin 28 | GOARCH: arm64 29 | build_freebsd: 30 | defaults: 31 | run: 32 | working-directory: ~/go/src/github.com/hashicorp/packer-plugin-vagrant 33 | runs-on: ubuntu-latest 34 | container: 35 | image: docker.mirror.hashicorp.services/cimg/go:1.21 36 | steps: 37 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 38 | - uses: "./.github/actions/build-and-persist-plugin-binary" 39 | with: 40 | GOOS: freebsd 41 | GOARCH: 386 42 | - uses: "./.github/actions/build-and-persist-plugin-binary" 43 | with: 44 | GOOS: freebsd 45 | GOARCH: amd64 46 | - uses: "./.github/actions/build-and-persist-plugin-binary" 47 | with: 48 | GOOS: freebsd 49 | GOARCH: arm 50 | build_linux: 51 | defaults: 52 | run: 53 | working-directory: ~/go/src/github.com/hashicorp/packer-plugin-vagrant 54 | runs-on: ubuntu-latest 55 | container: 56 | image: docker.mirror.hashicorp.services/cimg/go:1.21 57 | steps: 58 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 59 | - uses: "./.github/actions/build-and-persist-plugin-binary" 60 | with: 61 | GOOS: linux 62 | GOARCH: 386 63 | - uses: "./.github/actions/build-and-persist-plugin-binary" 64 | with: 65 | GOOS: linux 66 | GOARCH: amd64 67 | - uses: "./.github/actions/build-and-persist-plugin-binary" 68 | with: 69 | GOOS: linux 70 | GOARCH: arm 71 | - uses: "./.github/actions/build-and-persist-plugin-binary" 72 | with: 73 | GOOS: linux 74 | GOARCH: arm64 75 | build_netbsd: 76 | defaults: 77 | run: 78 | working-directory: ~/go/src/github.com/hashicorp/packer-plugin-vagrant 79 | runs-on: ubuntu-latest 80 | container: 81 | image: docker.mirror.hashicorp.services/cimg/go:1.21 82 | steps: 83 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 84 | - uses: "./.github/actions/build-and-persist-plugin-binary" 85 | with: 86 | GOOS: netbsd 87 | GOARCH: 386 88 | - uses: "./.github/actions/build-and-persist-plugin-binary" 89 | with: 90 | GOOS: netbsd 91 | GOARCH: amd64 92 | - uses: "./.github/actions/build-and-persist-plugin-binary" 93 | with: 94 | GOOS: netbsd 95 | GOARCH: arm 96 | build_openbsd: 97 | defaults: 98 | run: 99 | working-directory: ~/go/src/github.com/hashicorp/packer-plugin-vagrant 100 | runs-on: ubuntu-latest 101 | container: 102 | image: docker.mirror.hashicorp.services/cimg/go:1.21 103 | steps: 104 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 105 | - uses: "./.github/actions/build-and-persist-plugin-binary" 106 | with: 107 | GOOS: openbsd 108 | GOARCH: 386 109 | - uses: "./.github/actions/build-and-persist-plugin-binary" 110 | with: 111 | GOOS: openbsd 112 | GOARCH: amd64 113 | - uses: "./.github/actions/build-and-persist-plugin-binary" 114 | with: 115 | GOOS: openbsd 116 | GOARCH: arm 117 | build_solaris: 118 | defaults: 119 | run: 120 | working-directory: ~/go/src/github.com/hashicorp/packer-plugin-vagrant 121 | runs-on: ubuntu-latest 122 | container: 123 | image: docker.mirror.hashicorp.services/cimg/go:1.21 124 | steps: 125 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 126 | - uses: "./.github/actions/build-and-persist-plugin-binary" 127 | with: 128 | GOOS: solaris 129 | GOARCH: amd64 130 | build_windows: 131 | defaults: 132 | run: 133 | working-directory: ~/go/src/github.com/hashicorp/packer-plugin-vagrant 134 | runs-on: ubuntu-latest 135 | container: 136 | image: docker.mirror.hashicorp.services/cimg/go:1.21 137 | steps: 138 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 139 | - uses: "./.github/actions/build-and-persist-plugin-binary" 140 | with: 141 | GOOS: windows 142 | GOARCH: 386 143 | - uses: "./.github/actions/build-and-persist-plugin-binary" 144 | with: 145 | GOOS: windows 146 | GOARCH: amd64 147 | -------------------------------------------------------------------------------- /.github/workflows/go-test-darwin.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) HashiCorp, Inc. 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | # This GitHub action runs Packer go tests across 6 | # MacOS runners. 7 | # 8 | 9 | name: "Go Test MacOS" 10 | 11 | on: 12 | push: 13 | branches: 14 | - 'main' 15 | pull_request: 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | get-go-version: 22 | runs-on: ubuntu-latest 23 | outputs: 24 | go-version: ${{ steps.get-go-version.outputs.go-version }} 25 | steps: 26 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 27 | - name: 'Determine Go version' 28 | id: get-go-version 29 | run: | 30 | echo "Found Go $(cat .go-version)" 31 | echo "go-version=$(cat .go-version)" >> $GITHUB_OUTPUT 32 | darwin-go-tests: 33 | needs: 34 | - get-go-version 35 | runs-on: macos-latest 36 | name: Darwin Go tests 37 | steps: 38 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 39 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 40 | with: 41 | go-version: ${{ needs.get-go-version.outputs.go-version }} 42 | - run: | 43 | echo "Testing with Go ${{ needs.get-go-version.outputs.go-version }}" 44 | go test -race -count 1 ./... -timeout=3m 45 | 46 | 47 | -------------------------------------------------------------------------------- /.github/workflows/go-test-linux.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | # 5 | # This GitHub action runs Packer go tests across 6 | # Linux runners. 7 | # 8 | 9 | name: "Go Test Linux" 10 | 11 | on: 12 | push: 13 | branches: 14 | - 'main' 15 | pull_request: 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | get-go-version: 22 | runs-on: ubuntu-latest 23 | outputs: 24 | go-version: ${{ steps.get-go-version.outputs.go-version }} 25 | steps: 26 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 27 | - name: 'Determine Go version' 28 | id: get-go-version 29 | run: | 30 | echo "Found Go $(cat .go-version)" 31 | echo "go-version=$(cat .go-version)" >> $GITHUB_OUTPUT 32 | linux-go-tests: 33 | needs: 34 | - get-go-version 35 | runs-on: ubuntu-latest 36 | name: Linux Go tests 37 | steps: 38 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 39 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 40 | with: 41 | go-version: ${{ needs.get-go-version.outputs.go-version }} 42 | - run: | 43 | echo "Testing with Go ${{ needs.get-go-version.outputs.go-version }}" 44 | go test -race -count 1 ./... -timeout=3m 45 | -------------------------------------------------------------------------------- /.github/workflows/go-test-windows.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | # 5 | # This GitHub action runs Packer go tests across 6 | # Windows runners. 7 | # 8 | 9 | name: "Go Test Windows" 10 | 11 | on: 12 | push: 13 | branches: 14 | - 'main' 15 | pull_request: 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | get-go-version: 22 | runs-on: ubuntu-latest 23 | outputs: 24 | go-version: ${{ steps.get-go-version.outputs.go-version }} 25 | steps: 26 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 27 | - name: 'Determine Go version' 28 | id: get-go-version 29 | run: | 30 | echo "Found Go $(cat .go-version)" 31 | echo "go-version=$(cat .go-version)" >> $GITHUB_OUTPUT 32 | windows-go-tests: 33 | needs: 34 | - get-go-version 35 | runs-on: windows-latest 36 | name: Windows Go tests 37 | steps: 38 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 39 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 40 | with: 41 | go-version: ${{ needs.get-go-version.outputs.go-version }} 42 | - run: | 43 | echo "Testing with Go ${{ needs.get-go-version.outputs.go-version }}" 44 | go test -race -count 1 ./... -timeout=3m 45 | 46 | -------------------------------------------------------------------------------- /.github/workflows/go-validate.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | # 5 | # This GitHub action runs basic linting checks for Packer. 6 | # 7 | 8 | name: "Go Validate" 9 | 10 | on: 11 | push: 12 | branches: 13 | - 'main' 14 | pull_request: 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | get-go-version: 21 | runs-on: ubuntu-latest 22 | outputs: 23 | go-version: ${{ steps.get-go-version.outputs.go-version }} 24 | steps: 25 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 26 | - name: 'Determine Go version' 27 | id: get-go-version 28 | run: | 29 | echo "Found Go $(cat .go-version)" 30 | echo "go-version=$(cat .go-version)" >> $GITHUB_OUTPUT 31 | check-mod-tidy: 32 | needs: 33 | - get-go-version 34 | runs-on: ubuntu-latest 35 | name: Go Mod Tidy 36 | steps: 37 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 38 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 39 | with: 40 | go-version: ${{ needs.get-go-version.outputs.go-version }} 41 | - run: go mod tidy 42 | check-lint: 43 | needs: 44 | - get-go-version 45 | runs-on: ubuntu-latest 46 | name: Lint check 47 | steps: 48 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 49 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 50 | with: 51 | go-version: ${{ needs.get-go-version.outputs.go-version }} 52 | - uses: golangci/golangci-lint-action@82d40c283aeb1f2b6595839195e95c2d6a49081b # v5.0.0 53 | with: 54 | version: v1.54.2 55 | only-new-issues: true 56 | check-fmt: 57 | needs: 58 | - get-go-version 59 | runs-on: ubuntu-latest 60 | name: Gofmt check 61 | steps: 62 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 63 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 64 | with: 65 | go-version: ${{ needs.get-go-version.outputs.go-version }} 66 | - run: | 67 | go fmt ./... 68 | echo "==> Checking that code complies with go fmt requirements..." 69 | git diff --exit-code; if [ $$? -eq 1 ]; then \ 70 | echo "Found files that are not fmt'ed."; \ 71 | echo "You can use the command: \`go fmt ./...\` to reformat code."; \ 72 | exit 1; \ 73 | fi 74 | check-generate: 75 | needs: 76 | - get-go-version 77 | runs-on: ubuntu-latest 78 | name: Generate check 79 | steps: 80 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 81 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 82 | with: 83 | go-version: ${{ needs.get-go-version.outputs.go-version }} 84 | - run: | 85 | export PATH=$PATH:$(go env GOPATH)/bin 86 | make generate 87 | uncommitted="$(git status -s)" 88 | if [[ -z "$uncommitted" ]]; then 89 | echo "OK" 90 | else 91 | echo "Docs have been updated, but the compiled docs have not been committed." 92 | echo "Run 'make generate', and commit the result to resolve this error." 93 | echo "Generated but uncommitted files:" 94 | echo "$uncommitted" 95 | exit 1 96 | fi 97 | -------------------------------------------------------------------------------- /.github/workflows/notify-integration-release-via-manual.yaml: -------------------------------------------------------------------------------- 1 | name: Notify Integration Release (Manual) 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: "The release version (semver)" 7 | default: 0.0.1 8 | required: false 9 | branch: 10 | description: "A branch or SHA" 11 | default: 'main' 12 | required: false 13 | jobs: 14 | strip-version: 15 | runs-on: ubuntu-latest 16 | outputs: 17 | packer-version: ${{ steps.strip.outputs.packer-version }} 18 | steps: 19 | - name: Strip leading v from version tag 20 | id: strip 21 | env: 22 | REF: ${{ github.event.inputs.version }} 23 | run: | 24 | echo "packer-version=$(echo "$REF" | sed -E 's/v?([0-9]+\.[0-9]+\.[0-9]+)/\1/')" >> "$GITHUB_OUTPUT" 25 | notify-release: 26 | needs: 27 | - strip-version 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout this repo 31 | uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 32 | with: 33 | ref: ${{ github.event.inputs.branch }} 34 | # Ensure that Docs are Compiled 35 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 36 | - shell: bash 37 | run: make generate 38 | - shell: bash 39 | run: | 40 | uncommitted="$(git status -s)" 41 | if [[ -z "$uncommitted" ]]; then 42 | echo "OK" 43 | else 44 | echo "Docs have been updated, but the compiled docs have not been committed." 45 | echo "Run 'make generate', and commit the result to resolve this error." 46 | echo "Generated but uncommitted files:" 47 | echo "$uncommitted" 48 | exit 1 49 | fi 50 | # Perform the Release 51 | - name: Checkout integration-release-action 52 | uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 53 | with: 54 | repository: hashicorp/integration-release-action 55 | path: ./integration-release-action 56 | - name: Notify Release 57 | uses: ./integration-release-action 58 | with: 59 | integration_identifier: "packer/hashicorp/vagrant" 60 | release_version: ${{ needs.strip-version.outputs.packer-version }} 61 | release_sha: ${{ github.event.inputs.branch }} 62 | github_token: ${{ secrets.GITHUB_TOKEN }} 63 | -------------------------------------------------------------------------------- /.github/workflows/notify-integration-release-via-tag.yaml: -------------------------------------------------------------------------------- 1 | name: Notify Integration Release (Tag) 2 | on: 3 | push: 4 | tags: 5 | - '*.*.*' # Proper releases 6 | jobs: 7 | strip-version: 8 | runs-on: ubuntu-latest 9 | outputs: 10 | packer-version: ${{ steps.strip.outputs.packer-version }} 11 | steps: 12 | - name: Strip leading v from version tag 13 | id: strip 14 | env: 15 | REF: ${{ github.ref_name }} 16 | run: | 17 | echo "packer-version=$(echo "$REF" | sed -E 's/v?([0-9]+\.[0-9]+\.[0-9]+)/\1/')" >> "$GITHUB_OUTPUT" 18 | notify-release: 19 | needs: 20 | - strip-version 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout this repo 24 | uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 25 | with: 26 | ref: ${{ github.ref }} 27 | # Ensure that Docs are Compiled 28 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 29 | - shell: bash 30 | run: make generate 31 | - shell: bash 32 | run: | 33 | uncommitted="$(git status -s)" 34 | if [[ -z "$uncommitted" ]]; then 35 | echo "OK" 36 | else 37 | echo "Docs have been updated, but the compiled docs have not been committed." 38 | echo "Run 'make generate', and commit the result to resolve this error." 39 | echo "Generated but uncommitted files:" 40 | echo "$uncommitted" 41 | exit 1 42 | fi 43 | # Perform the Release 44 | - name: Checkout integration-release-action 45 | uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 46 | with: 47 | repository: hashicorp/integration-release-action 48 | path: ./integration-release-action 49 | - name: Notify Release 50 | uses: ./integration-release-action 51 | with: 52 | integration_identifier: "packer/hashicorp/vagrant" 53 | release_version: ${{ needs.strip-version.outputs.packer-version }} 54 | release_sha: ${{ github.ref }} 55 | github_token: ${{ secrets.GITHUB_TOKEN }} 56 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | # This GitHub action can publish assets for release when a tag is created. 5 | # Currently its setup to run on any tag that matches the pattern "v*" (ie. v0.1.0). 6 | # 7 | # This uses an action (hashicorp/ghaction-import-gpg) that assumes you set your 8 | # private key in the `GPG_PRIVATE_KEY` secret and passphrase in the `GPG_PASSPHRASE` 9 | # secret. If you would rather own your own GPG handling, please fork this action 10 | # or use an alternative one for key handling. 11 | # 12 | # You will need to pass the `--batch` flag to `gpg` in your signing step 13 | # in `goreleaser` to indicate this is being used in a non-interactive mode. 14 | # 15 | name: release 16 | on: 17 | push: 18 | tags: 19 | - 'v*' 20 | permissions: 21 | contents: write 22 | packages: read 23 | jobs: 24 | get-go-version: 25 | runs-on: ubuntu-latest 26 | outputs: 27 | go-version: ${{ steps.get-go-version.outputs.go-version }} 28 | steps: 29 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 30 | - name: 'Determine Go version' 31 | id: get-go-version 32 | run: | 33 | echo "Found Go $(cat .go-version)" 34 | echo "go-version=$(cat .go-version)" >> $GITHUB_OUTPUT 35 | goreleaser: 36 | needs: 37 | - get-go-version 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 42 | - name: Unshallow 43 | run: git fetch --prune --unshallow 44 | - name: Set up Go 45 | uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 46 | with: 47 | go-version: ${{ needs.get-go-version.outputs.go-version }} 48 | - name: Describe plugin 49 | id: plugin_describe 50 | run: echo "api_version=$(go run . describe | jq -r '.api_version')" >> "$GITHUB_OUTPUT" 51 | - name: Install signore 52 | uses: hashicorp/setup-signore-package@v1 53 | - name: Run GoReleaser 54 | uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 55 | with: 56 | version: latest 57 | args: release --clean --timeout 120m 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | API_VERSION: ${{ steps.plugin_describe.outputs.api_version }} 61 | SIGNORE_CLIENT_ID: ${{ secrets.SIGNORE_CLIENT_ID }} 62 | SIGNORE_CLIENT_SECRET: ${{ secrets.SIGNORE_CLIENT_SECRET }} 63 | SIGNORE_SIGNER: ${{ secrets.SIGNORE_SIGNER }} 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | dist/* 3 | packer-plugin-vagrant 4 | packer-plugin-vagrant.exe 5 | .docs 6 | -------------------------------------------------------------------------------- /.go-version: -------------------------------------------------------------------------------- 1 | 1.23.0 2 | 3 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | issues: 5 | # List of regexps of issue texts to exclude, empty list by default. 6 | # But independently from this option we use default exclude patterns, 7 | # it can be disabled by `exclude-use-default: false`. To list all 8 | # excluded by default patterns execute `golangci-lint run --help` 9 | 10 | exclude-rules: 11 | # Exclude gosimple bool check 12 | - linters: 13 | - gosimple 14 | text: "S(1002|1008|1021)" 15 | # Exclude failing staticchecks for now 16 | - linters: 17 | - staticcheck 18 | text: "SA(1006|1019|4006|4010|4017|5007|6005|9004):" 19 | # Exclude lll issues for long lines with go:generate 20 | - linters: 21 | - lll 22 | source: "^//go:generate " 23 | 24 | # Maximum issues count per one linter. Set to 0 to disable. Default is 50. 25 | max-issues-per-linter: 0 26 | 27 | # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. 28 | max-same-issues: 0 29 | 30 | linters: 31 | disable-all: true 32 | enable: 33 | - deadcode 34 | - errcheck 35 | - goimports 36 | - gosimple 37 | - govet 38 | - ineffassign 39 | - staticcheck 40 | - unconvert 41 | - unused 42 | - varcheck 43 | fast: true 44 | 45 | # options for analysis running 46 | run: 47 | # default concurrency is a available CPU number 48 | concurrency: 4 49 | 50 | # timeout for analysis, e.g. 30s, 5m, default is 1m 51 | timeout: 10m 52 | 53 | # exit code when at least one issue was found, default is 1 54 | issues-exit-code: 1 55 | 56 | # include test files or not, default is true 57 | tests: true 58 | 59 | # list of build tags, all linters use it. Default is empty list. 60 | #build-tags: 61 | # - mytag 62 | 63 | # which dirs to skip: issues from them won't be reported; 64 | # can use regexp here: generated.*, regexp is applied on full path; 65 | # default value is empty list, but default dirs are skipped independently 66 | # from this option's value (see skip-dirs-use-default). 67 | #skip-dirs: 68 | # - src/external_libs 69 | # - autogenerated_by_my_lib 70 | 71 | # default is true. Enables skipping of directories: 72 | # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ 73 | skip-dirs-use-default: true 74 | 75 | # which files to skip: they will be analyzed, but issues from them 76 | # won't be reported. Default value is empty list, but there is 77 | # no need to include all autogenerated files, we confidently recognize 78 | # autogenerated files. If it's not please let us know. 79 | skip-files: 80 | - ".*\\.hcl2spec\\.go$" 81 | # - lib/bad.go 82 | 83 | # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": 84 | # If invoked with -mod=readonly, the go command is disallowed from the implicit 85 | # automatic updating of go.mod described above. Instead, it fails when any changes 86 | # to go.mod are needed. This setting is most useful to check that go.mod does 87 | # not need updates, such as in a continuous integration and testing system. 88 | # If invoked with -mod=vendor, the go command assumes that the vendor 89 | # directory holds the correct copies of dependencies and ignores 90 | # the dependency descriptions in go.mod. 91 | # modules-download-mode: vendor 92 | 93 | 94 | # output configuration options 95 | output: 96 | # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" 97 | format: colored-line-number 98 | 99 | # print lines of code with issue, default is true 100 | print-issued-lines: true 101 | 102 | # print linter name in the end of issue text, default is true 103 | print-linter-name: true 104 | 105 | # make issues output unique by line, default is true 106 | uniq-by-line: true 107 | 108 | 109 | # all available settings of specific linters 110 | linters-settings: 111 | errcheck: 112 | # report about not checking of errors in type assetions: `a := b.(MyStruct)`; 113 | # default is false: such cases aren't reported by default. 114 | check-type-assertions: false 115 | 116 | # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; 117 | # default is false: such cases aren't reported by default. 118 | check-blank: false 119 | 120 | # [deprecated] comma-separated list of pairs of the form pkg:regex 121 | # the regex is used to ignore names within pkg. (default "fmt:.*"). 122 | # see https://github.com/kisielk/errcheck#the-deprecated-method for details 123 | ignore: fmt:.*,io/ioutil:^Read.*,io:Close 124 | 125 | # path to a file containing a list of functions to exclude from checking 126 | # see https://github.com/kisielk/errcheck#excluding-functions for details 127 | #exclude: /path/to/file.txt 128 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | # This is an example goreleaser.yaml file with some defaults. 5 | # Make sure to check the documentation at http://goreleaser.com 6 | version: 2 7 | env: 8 | - CGO_ENABLED=0 9 | before: 10 | hooks: 11 | # We strongly recommend running tests to catch any regression before release. 12 | # Even though, this an optional step. 13 | - go test ./... 14 | # Check plugin compatibility with required version of the Packer SDK 15 | - make plugin-check 16 | # Copy LICENSE file for inclusion in zip archive 17 | - cp LICENSE LICENSE.txt 18 | builds: 19 | # A separated build to run the packer-plugins-check only once for a linux_amd64 binary 20 | - 21 | id: plugin-check 22 | mod_timestamp: '{{ .CommitTimestamp }}' 23 | flags: 24 | - -trimpath #removes all file system paths from the compiled executable 25 | ldflags: 26 | - '-s -w -X {{ .ModulePath }}/version.Version={{.Version}} -X {{ .ModulePath }}/version.VersionPrerelease= ' 27 | goos: 28 | - linux 29 | goarch: 30 | - amd64 31 | binary: '{{ .ProjectName }}_v{{ .Version }}_{{ .Env.API_VERSION }}_{{ .Os }}_{{ .Arch }}' 32 | - 33 | id: linux-builds 34 | mod_timestamp: '{{ .CommitTimestamp }}' 35 | flags: 36 | - -trimpath #removes all file system paths from the compiled executable 37 | ldflags: 38 | - '-s -w -X {{ .ModulePath }}/version.Version={{.Version}} -X {{ .ModulePath }}/version.VersionPrerelease= ' 39 | goos: 40 | - linux 41 | goarch: 42 | - amd64 43 | - '386' 44 | - arm 45 | - arm64 46 | ignore: 47 | - goos: linux 48 | goarch: amd64 49 | binary: '{{ .ProjectName }}_v{{ .Version }}_{{ .Env.API_VERSION }}_{{ .Os }}_{{ .Arch }}' 50 | - 51 | id: darwin-builds 52 | mod_timestamp: '{{ .CommitTimestamp }}' 53 | flags: 54 | - -trimpath #removes all file system paths from the compiled executable 55 | ldflags: 56 | - '-s -w -X {{ .ModulePath }}/version.Version={{.Version}} -X {{ .ModulePath }}/version.VersionPrerelease= ' 57 | goos: 58 | - darwin 59 | goarch: 60 | - amd64 61 | - arm64 62 | binary: '{{ .ProjectName }}_v{{ .Version }}_{{ .Env.API_VERSION }}_{{ .Os }}_{{ .Arch }}' 63 | - 64 | id: other-builds 65 | mod_timestamp: '{{ .CommitTimestamp }}' 66 | flags: 67 | - -trimpath #removes all file system paths from the compiled executable 68 | ldflags: 69 | - '-s -w -X {{ .ModulePath }}/version.Version={{.Version}} -X {{ .ModulePath }}/version.VersionPrerelease= ' 70 | goos: 71 | - netbsd 72 | - openbsd 73 | - freebsd 74 | - windows 75 | - solaris 76 | goarch: 77 | - amd64 78 | - '386' 79 | - arm 80 | ignore: 81 | - goos: windows 82 | goarch: arm 83 | - goos: solaris 84 | goarch: arm 85 | - goos: solaris 86 | goarch: '386' 87 | binary: '{{ .ProjectName }}_v{{ .Version }}_{{ .Env.API_VERSION }}_{{ .Os }}_{{ .Arch }}' 88 | archives: 89 | - format: zip 90 | files: 91 | - "LICENSE.txt" 92 | 93 | name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Env.API_VERSION }}_{{ .Os }}_{{ .Arch }}' 94 | checksum: 95 | name_template: '{{ .ProjectName }}_v{{ .Version }}_SHA256SUMS' 96 | algorithm: sha256 97 | signs: 98 | - cmd: signore 99 | args: ["sign", "--dearmor", "--file", "${artifact}", "--out", "${signature}"] 100 | artifacts: checksum 101 | signature: ${artifact}.sig 102 | 103 | changelog: 104 | use: github-native 105 | -------------------------------------------------------------------------------- /.web-docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | The Vagrant plugin integrates Packer with HashiCorp [Vagrant](https://www.vagrantup.com/), allowing you to use Packer to create development boxes. 3 | 4 | ### Installation 5 | To install this plugin add this code into your Packer configuration and run [packer init](/packer/docs/commands/init) 6 | 7 | ```hcl 8 | packer { 9 | required_plugins { 10 | vagrant = { 11 | version = "~> 1" 12 | source = "github.com/hashicorp/vagrant" 13 | } 14 | } 15 | } 16 | ``` 17 | 18 | Alternatively, you can use `packer plugins install` to manage installation of this plugin. 19 | 20 | ```sh 21 | packer plugins install github.com/hashicorp/vagrant 22 | ``` 23 | 24 | ### Components 25 | 26 | #### Builders 27 | - [vagrant](/packer/integrations/hashicorp/vagrant/latest/components/builder/vagrant) - The Vagrant builder is intended for building new boxes from already-existing boxes. 28 | 29 | #### Post-Processor 30 | - [vagrant](/packer/integrations/hashicorp/vagrant/latest/components/post-processor/vagrant) - The Packer Vagrant post-processor takes a build and converts the artifact into a valid Vagrant box. 31 | - [vagrant-cloud](/packer/integrations/hashicorp/vagrant/latest/components/post-processor/vagrant-cloud) - The Vagrant Cloud post-processor enables the upload of Vagrant boxes to Vagrant Cloud. 32 | - [vagrant-registry](/packer/integrations/hashicorp/vagrant/latest/components/post-processor/vagrant-registry) - The Vagrant Registry post-processor enables the upload of Vagrant boxes to HCP Vagrant Box Registry. 33 | -------------------------------------------------------------------------------- /.web-docs/metadata.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | # For full specification on the configuration of this file visit: 5 | # https://github.com/hashicorp/integration-template#metadata-configuration 6 | integration { 7 | name = "Vagrant" 8 | description = "The Vagrant multi-component plugin can be used with HashiCorp Packer to create custom images." 9 | identifier = "packer/hashicorp/vagrant" 10 | component { 11 | type = "builder" 12 | name = "Vagrant" 13 | slug = "vagrant" 14 | } 15 | component { 16 | type = "post-processor" 17 | name = "Vagrant" 18 | slug = "vagrant" 19 | } 20 | component { 21 | type = "post-processor" 22 | name = "Vagrant Cloud" 23 | slug = "vagrant-cloud" 24 | } 25 | component { 26 | type = "post-processor" 27 | name = "Vagrant Registry" 28 | slug = "vagrant-registry" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.web-docs/scripts/compile-to-webdocs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) HashiCorp, Inc. 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | # Converts the folder name that the component documentation file 7 | # is stored in into the integration slug of the component. 8 | componentTypeFromFolderName() { 9 | if [[ "$1" = "builders" ]]; then 10 | echo "builder" 11 | elif [[ "$1" = "provisioners" ]]; then 12 | echo "provisioner" 13 | elif [[ "$1" = "post-processors" ]]; then 14 | echo "post-processor" 15 | elif [[ "$1" = "datasources" ]]; then 16 | echo "data-source" 17 | else 18 | echo "" 19 | fi 20 | } 21 | 22 | # $1: The content to adjust links 23 | # $2: The organization of the integration 24 | rewriteLinks() { 25 | local result="$1" 26 | local organization="$2" 27 | 28 | urlSegment="([^/]+)" 29 | urlAnchor="(#[^/]+)" 30 | 31 | # Rewrite Component Index Page links to the Integration root page. 32 | # 33 | # (\1) (\2) (\3) 34 | # /packer/plugins/datasources/amazon#anchor-tag--> 35 | # /packer/integrations/hashicorp/amazon#anchor-tag 36 | local find="\(\/packer\/plugins\/$urlSegment\/$urlSegment$urlAnchor?\)" 37 | local replace="\(\/packer\/integrations\/$organization\/\2\3\)" 38 | result="$(echo "$result" | sed -E "s/$find/$replace/g")" 39 | 40 | 41 | # Rewrite Component links to the Integration component page 42 | # 43 | # (\1) (\2) (\3) (\4) 44 | # /packer/plugins/datasources/amazon/parameterstore#anchor-tag --> 45 | # /packer/integrations/{organization}/amazon/latest/components/datasources/parameterstore 46 | local find="\(\/packer\/plugins\/$urlSegment\/$urlSegment\/$urlSegment$urlAnchor?\)" 47 | local replace="\(\/packer\/integrations\/$organization\/\2\/latest\/components\/\1\/\3\4\)" 48 | result="$(echo "$result" | sed -E "s/$find/$replace/g")" 49 | 50 | # Rewrite the Component URL segment from the Packer Plugin format 51 | # to the Integrations format 52 | result="$(echo "$result" \ 53 | | sed "s/\/datasources\//\/data-source\//g" \ 54 | | sed "s/\/builders\//\/builder\//g" \ 55 | | sed "s/\/post-processors\//\/post-processor\//g" \ 56 | | sed "s/\/provisioners\//\/provisioner\//g" \ 57 | )" 58 | 59 | echo "$result" 60 | } 61 | 62 | # $1: Docs Dir 63 | # $2: Web Docs Dir 64 | # $3: Component File 65 | # $4: The org of the integration 66 | processComponentFile() { 67 | local docsDir="$1" 68 | local webDocsDir="$2" 69 | local componentFile="$3" 70 | 71 | local escapedDocsDir="$(echo "$docsDir" | sed 's/\//\\\//g' | sed 's/\./\\\./g')" 72 | local componentTypeAndSlug="$(echo "$componentFile" | sed "s/$escapedDocsDir\///g" | sed 's/\.mdx//g')" 73 | 74 | # Parse out the Component Slug & Component Type 75 | local componentSlug="$(echo "$componentTypeAndSlug" | cut -d'/' -f 2)" 76 | local componentType="$(componentTypeFromFolderName "$(echo "$componentTypeAndSlug" | cut -d'/' -f 1)")" 77 | if [[ "$componentType" = "" ]]; then 78 | echo "Failed to process '$componentFile', unexpected folder name." 79 | echo "Documentation for components must be stored in one of:" 80 | echo "builders, provisioners, post-processors, datasources" 81 | exit 1 82 | fi 83 | 84 | 85 | # Calculate the location of where this file will ultimately go 86 | local webDocsFolder="$webDocsDir/components/$componentType/$componentSlug" 87 | mkdir -p "$webDocsFolder" 88 | local webDocsFile="$webDocsFolder/README.md" 89 | local webDocsFileTmp="$webDocsFolder/README.md.tmp" 90 | 91 | # Copy over the file to its webDocsFile location 92 | cp "$componentFile" "$webDocsFile" 93 | 94 | # Remove the Header 95 | local lastMetadataLine="$(grep -n -m 2 '^\-\-\-' "$componentFile" | tail -n1 | cut -d':' -f1)" 96 | cat "$webDocsFile" | tail -n +"$(($lastMetadataLine+2))" > "$webDocsFileTmp" 97 | mv "$webDocsFileTmp" "$webDocsFile" 98 | 99 | # Remove the top H1, as this will be added automatically on the web 100 | cat "$webDocsFile" | tail -n +3 > "$webDocsFileTmp" 101 | mv "$webDocsFileTmp" "$webDocsFile" 102 | 103 | # Rewrite Links 104 | rewriteLinks "$(cat "$webDocsFile")" "$4" > "$webDocsFileTmp" 105 | mv "$webDocsFileTmp" "$webDocsFile" 106 | } 107 | 108 | # Compiles the Packer SDC compiled docs folder down 109 | # to a integrations-compliant folder (web docs) 110 | # 111 | # $1: The directory of the plugin 112 | # $2: The directory of the SDC compiled docs files 113 | # $3: The output directory to place the web-docs files 114 | # $4: The org of the integration 115 | compileWebDocs() { 116 | local docsDir="$1/$2" 117 | local webDocsDir="$1/$3" 118 | 119 | echo "Compiling MDX docs in '$2' to Markdown in '$3'..." 120 | # Create the web-docs directory if it hasn't already been created 121 | mkdir -p "$webDocsDir" 122 | 123 | # Copy the README over 124 | cp "$docsDir/README.md" "$webDocsDir/README.md" 125 | 126 | # Process all MDX component files (exclude index files, which are unsupported) 127 | for file in $(find "$docsDir" | grep "$docsDir/.*/.*\.mdx" | grep --invert-match "index.mdx"); do 128 | processComponentFile "$docsDir" "$webDocsDir" "$file" "$4" 129 | done 130 | } 131 | 132 | compileWebDocs "$1" "$2" "$3" "$4" 133 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.1 (Upcoming) 2 | 3 | * post-procesor/vagrant-cloud: Add `box_checksum` argument to allow for setting 4 | checksums on uploaded box. [GH-32] 5 | 6 | ## 1.0.0 (June 15, 2021) 7 | 8 | * Update to v0.2.3 of packer-plugin-sdk. [GH-18] 9 | 10 | ## 0.0.3 (April 21, 2021) 11 | 12 | * Refactor docs and fix goreleaser configuration. 13 | 14 | ## 0.0.2 (April 13, 2021) 15 | 16 | * Update docs generation with the packer-sdc command 17 | * No-op tag; debugging release workflow. 18 | 19 | ## 0.0.1 (April 13, 2021) 20 | 21 | * Extract Vagrant builders and post-processors to packer-plugin-vagrant 22 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @hashicorp/packer 2 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | NAME=vagrant 2 | BINARY=packer-plugin-${NAME} 3 | PLUGIN_FQN="$(shell grep -E '^module' 0 { 39 | packageArgs = append(packageArgs, "--include", strings.Join(s.Include, ",")) 40 | } 41 | if s.Vagrantfile != "" { 42 | packageArgs = append(packageArgs, "--vagrantfile", s.Vagrantfile) 43 | } 44 | 45 | err := driver.Package(packageArgs) 46 | if err != nil { 47 | state.Put("error", err) 48 | return multistep.ActionHalt 49 | } 50 | 51 | return multistep.ActionContinue 52 | } 53 | 54 | func (s *StepPackage) Cleanup(state multistep.StateBag) { 55 | } 56 | -------------------------------------------------------------------------------- /builder/vagrant/step_ssh_config.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "context" 8 | "log" 9 | "strconv" 10 | 11 | "github.com/hashicorp/packer-plugin-sdk/multistep" 12 | ) 13 | 14 | // Vagrant already sets up ssh on the guests; our job is to find out what 15 | // it did. We can do that with the ssh-config command. Example output: 16 | 17 | // $ vagrant ssh-config 18 | // Host default 19 | // HostName 172.16.41.194 20 | // User vagrant 21 | // Port 22 22 | // UserKnownHostsFile /dev/null 23 | // StrictHostKeyChecking no 24 | // PasswordAuthentication no 25 | // IdentityFile /Users/mmarsh/Projects/vagrant-boxes/ubuntu/.vagrant/machines/default/vmware_fusion/private_key 26 | // IdentitiesOnly yes 27 | // LogLevel FATAL 28 | 29 | type StepSSHConfig struct { 30 | GlobalID string 31 | } 32 | 33 | func (s *StepSSHConfig) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 34 | driver := state.Get("driver").(VagrantDriver) 35 | config := state.Get("config").(*Config) 36 | 37 | box := "source" 38 | if s.GlobalID != "" { 39 | box = s.GlobalID 40 | } 41 | sshConfig, err := driver.SSHConfig(box) 42 | if err != nil { 43 | state.Put("error", err) 44 | return multistep.ActionHalt 45 | } 46 | 47 | if config.Comm.SSHHost == "" { 48 | config.Comm.SSHHost = sshConfig.Hostname 49 | } 50 | if config.Comm.SSHPort == 0 { 51 | port, err := strconv.Atoi(sshConfig.Port) 52 | if err != nil { 53 | state.Put("error", err) 54 | return multistep.ActionHalt 55 | } 56 | config.Comm.SSHPort = port 57 | } 58 | 59 | if config.Comm.SSHUsername != "" { 60 | // If user has set the username within the communicator, use the 61 | // username, password, and/or keyfile auth provided there. 62 | log.Printf("Overriding SSH config from Vagrant with the username, " + 63 | "password, and private key information provided to the Packer template.") 64 | return multistep.ActionContinue 65 | } 66 | log.Printf("identity file is %s", sshConfig.IdentityFile) 67 | log.Printf("Removing quotes from identity file") 68 | unquoted, err := strconv.Unquote(sshConfig.IdentityFile) 69 | if err == nil { 70 | sshConfig.IdentityFile = unquoted 71 | } 72 | config.Comm.SSHPrivateKeyFile = sshConfig.IdentityFile 73 | config.Comm.SSHUsername = sshConfig.User 74 | 75 | return multistep.ActionContinue 76 | } 77 | 78 | func (s *StepSSHConfig) Cleanup(state multistep.StateBag) { 79 | } 80 | -------------------------------------------------------------------------------- /builder/vagrant/step_ssh_config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/packer-plugin-sdk/communicator" 11 | "github.com/hashicorp/packer-plugin-sdk/multistep" 12 | ) 13 | 14 | func TestStepSSHConfig_Impl(t *testing.T) { 15 | var raw interface{} 16 | raw = new(StepSSHConfig) 17 | if _, ok := raw.(multistep.Step); !ok { 18 | t.Fatalf("initialize should be a step") 19 | } 20 | } 21 | 22 | func TestPrepStepSSHConfig_sshOverrides(t *testing.T) { 23 | type testcase struct { 24 | name string 25 | inputSSHConfig communicator.SSH 26 | expectedSSHConfig communicator.SSH 27 | } 28 | tcs := []testcase{ 29 | { 30 | // defaults to overriding with the ssh config from vagrant\ 31 | name: "default", 32 | inputSSHConfig: communicator.SSH{}, 33 | expectedSSHConfig: communicator.SSH{ 34 | SSHHost: "127.0.0.1", 35 | SSHPort: 2222, 36 | SSHUsername: "vagrant", 37 | SSHPassword: "", 38 | }, 39 | }, 40 | { 41 | // respects SSH host and port overrides independent of credential 42 | // overrides 43 | name: "host_override", 44 | inputSSHConfig: communicator.SSH{ 45 | SSHHost: "123.45.67.8", 46 | SSHPort: 1234, 47 | }, 48 | expectedSSHConfig: communicator.SSH{ 49 | SSHHost: "123.45.67.8", 50 | SSHPort: 1234, 51 | SSHUsername: "vagrant", 52 | SSHPassword: "", 53 | }, 54 | }, 55 | { 56 | // respects credential overrides 57 | name: "credential_override", 58 | inputSSHConfig: communicator.SSH{ 59 | SSHUsername: "megan", 60 | SSHPassword: "SoSecure", 61 | }, 62 | expectedSSHConfig: communicator.SSH{ 63 | SSHHost: "127.0.0.1", 64 | SSHPort: 2222, 65 | SSHUsername: "megan", 66 | SSHPassword: "SoSecure", 67 | }, 68 | }, 69 | } 70 | for _, tc := range tcs { 71 | driver := &MockVagrantDriver{} 72 | config := &Config{ 73 | Comm: communicator.Config{ 74 | SSH: tc.inputSSHConfig, 75 | }, 76 | } 77 | state := new(multistep.BasicStateBag) 78 | state.Put("driver", driver) 79 | state.Put("config", config) 80 | 81 | step := StepSSHConfig{} 82 | _ = step.Run(context.Background(), state) 83 | 84 | if config.Comm.SSHHost != tc.expectedSSHConfig.SSHHost { 85 | t.Fatalf("unexpected sshconfig host: name: %s, recieved %s", tc.name, config.Comm.SSHHost) 86 | } 87 | if config.Comm.SSHPort != tc.expectedSSHConfig.SSHPort { 88 | t.Fatalf("unexpected sshconfig port: name: %s, recieved %d", tc.name, config.Comm.SSHPort) 89 | } 90 | if config.Comm.SSHUsername != tc.expectedSSHConfig.SSHUsername { 91 | t.Fatalf("unexpected sshconfig SSHUsername: name: %s, recieved %s", tc.name, config.Comm.SSHUsername) 92 | } 93 | if config.Comm.SSHPassword != tc.expectedSSHConfig.SSHPassword { 94 | t.Fatalf("unexpected sshconfig SSHUsername: name: %s, recieved %s", tc.name, config.Comm.SSHPassword) 95 | } 96 | } 97 | } 98 | 99 | func TestPrepStepSSHConfig_GlobalID(t *testing.T) { 100 | driver := &MockVagrantDriver{} 101 | config := &Config{} 102 | state := new(multistep.BasicStateBag) 103 | state.Put("driver", driver) 104 | state.Put("config", config) 105 | 106 | step := StepSSHConfig{ 107 | GlobalID: "adsfadf", 108 | } 109 | _ = step.Run(context.Background(), state) 110 | if driver.GlobalID != "adsfadf" { 111 | t.Fatalf("Should have called SSHConfig with GlobalID asdfasdf") 112 | } 113 | } 114 | 115 | func TestPrepStepSSHConfig_NoGlobalID(t *testing.T) { 116 | driver := &MockVagrantDriver{} 117 | config := &Config{} 118 | state := new(multistep.BasicStateBag) 119 | state.Put("driver", driver) 120 | state.Put("config", config) 121 | 122 | step := StepSSHConfig{} 123 | _ = step.Run(context.Background(), state) 124 | if driver.GlobalID != "source" { 125 | t.Fatalf("Should have called SSHConfig with GlobalID source") 126 | } 127 | } 128 | 129 | func TestPrepStepSSHConfig_SpacesInPath(t *testing.T) { 130 | driver := &MockVagrantDriver{} 131 | driver.ReturnSSHConfig = &VagrantSSHConfig{ 132 | Hostname: "127.0.0.1", 133 | User: "vagrant", 134 | Port: "2222", 135 | UserKnownHostsFile: "/dev/null", 136 | StrictHostKeyChecking: false, 137 | PasswordAuthentication: false, 138 | IdentityFile: "\"/path with spaces/insecure_private_key\"", 139 | IdentitiesOnly: true, 140 | LogLevel: "FATAL"} 141 | 142 | config := &Config{} 143 | state := new(multistep.BasicStateBag) 144 | state.Put("driver", driver) 145 | state.Put("config", config) 146 | 147 | step := StepSSHConfig{} 148 | _ = step.Run(context.Background(), state) 149 | expected := "/path with spaces/insecure_private_key" 150 | if config.Comm.SSHPrivateKeyFile != expected { 151 | t.Fatalf("Bad config private key. Recieved: %s; expected: %s.", config.Comm.SSHPrivateKeyFile, expected) 152 | } 153 | } 154 | 155 | func TestPrepStepSSHConfig_NoSpacesInPath(t *testing.T) { 156 | driver := &MockVagrantDriver{} 157 | driver.ReturnSSHConfig = &VagrantSSHConfig{ 158 | Hostname: "127.0.0.1", 159 | User: "vagrant", 160 | Port: "2222", 161 | UserKnownHostsFile: "/dev/null", 162 | StrictHostKeyChecking: false, 163 | PasswordAuthentication: false, 164 | IdentityFile: "/path/without/spaces/insecure_private_key", 165 | IdentitiesOnly: true, 166 | LogLevel: "FATAL"} 167 | 168 | config := &Config{} 169 | state := new(multistep.BasicStateBag) 170 | state.Put("driver", driver) 171 | state.Put("config", config) 172 | 173 | step := StepSSHConfig{} 174 | _ = step.Run(context.Background(), state) 175 | expected := "/path/without/spaces/insecure_private_key" 176 | if config.Comm.SSHPrivateKeyFile != expected { 177 | t.Fatalf("Bad config private key. Recieved: %s; expected: %s.", config.Comm.SSHPrivateKeyFile, expected) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /builder/vagrant/step_up.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/packer-plugin-sdk/multistep" 11 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 12 | ) 13 | 14 | type StepUp struct { 15 | TeardownMethod string 16 | Provider string 17 | GlobalID string 18 | } 19 | 20 | func (s *StepUp) generateArgs() []string { 21 | box := "source" 22 | if s.GlobalID != "" { 23 | box = s.GlobalID 24 | } 25 | 26 | // start only the source box 27 | args := []string{box} 28 | 29 | if s.Provider != "" { 30 | args = append(args, fmt.Sprintf("--provider=%s", s.Provider)) 31 | } 32 | return args 33 | } 34 | 35 | func (s *StepUp) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 36 | driver := state.Get("driver").(VagrantDriver) 37 | ui := state.Get("ui").(packersdk.Ui) 38 | 39 | ui.Say("Calling Vagrant Up (this can take some time)...") 40 | 41 | args := s.generateArgs() 42 | // instance_id is the generic term used so that users can have access to the 43 | // instance id inside of the provisioners, used in step_provision. 44 | state.Put("instance_id", args[0]) 45 | _, _, err := driver.Up(args) 46 | 47 | if err != nil { 48 | state.Put("error", err) 49 | return multistep.ActionHalt 50 | } 51 | 52 | return multistep.ActionContinue 53 | } 54 | 55 | func (s *StepUp) Cleanup(state multistep.StateBag) { 56 | driver := state.Get("driver").(VagrantDriver) 57 | ui := state.Get("ui").(packersdk.Ui) 58 | 59 | ui.Say(fmt.Sprintf("%sing Vagrant box...", s.TeardownMethod)) 60 | 61 | box := "source" 62 | if s.GlobalID != "" { 63 | box = s.GlobalID 64 | } 65 | 66 | var err error 67 | if s.TeardownMethod == "halt" { 68 | err = driver.Halt(box) 69 | } else if s.TeardownMethod == "suspend" { 70 | err = driver.Suspend(box) 71 | } else if s.TeardownMethod == "destroy" { 72 | err = driver.Destroy(box) 73 | } else { 74 | // Should never get here because of template validation 75 | state.Put("error", fmt.Errorf("Invalid teardown method selected; must be either halt, suspend, or destroy.")) 76 | } 77 | if err != nil { 78 | state.Put("error", fmt.Errorf("Error halting Vagrant machine; please try to do this manually")) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /builder/vagrant/step_up_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestPrepUpArgs(t *testing.T) { 12 | type testArgs struct { 13 | Step StepUp 14 | Expected []string 15 | } 16 | tests := []testArgs{ 17 | { 18 | Step: StepUp{ 19 | GlobalID: "foo", 20 | Provider: "bar", 21 | }, 22 | Expected: []string{"foo", "--provider=bar"}, 23 | }, 24 | { 25 | Step: StepUp{}, 26 | Expected: []string{"source"}, 27 | }, 28 | { 29 | Step: StepUp{ 30 | Provider: "pro", 31 | }, 32 | Expected: []string{"source", "--provider=pro"}, 33 | }, 34 | } 35 | for _, test := range tests { 36 | args := test.Step.generateArgs() 37 | for i, val := range test.Expected { 38 | if strings.Compare(args[i], val) != 0 { 39 | t.Fatalf("expected %#v but received %#v", test.Expected, args) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /docs-partials/builder/vagrant/Config-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `output_dir` (string) - The directory to create that will contain your output box. We always 4 | create this directory and run from inside of it to prevent Vagrant init 5 | collisions. If unset, it will be set to output- plus your buildname. 6 | 7 | - `checksum` (string) - The checksum for the .box file. The type of the checksum is specified 8 | within the checksum field as a prefix, ex: "md5:{$checksum}". The type 9 | of the checksum can also be omitted and Packer will try to infer it 10 | based on string length. Valid values are "none", "{$checksum}", 11 | "md5:{$checksum}", "sha1:{$checksum}", "sha256:{$checksum}", 12 | "sha512:{$checksum}" or "file:{$path}". Here is a list of valid checksum 13 | values: 14 | * md5:090992ba9fd140077b0661cb75f7ce13 15 | * 090992ba9fd140077b0661cb75f7ce13 16 | * sha1:ebfb681885ddf1234c18094a45bbeafd91467911 17 | * ebfb681885ddf1234c18094a45bbeafd91467911 18 | * sha256:ed363350696a726b7932db864dda019bd2017365c9e299627830f06954643f93 19 | * ed363350696a726b7932db864dda019bd2017365c9e299627830f06954643f93 20 | * file:http://releases.ubuntu.com/20.04/SHA256SUMS 21 | * file:file://./local/path/file.sum 22 | * file:./local/path/file.sum 23 | * none 24 | Although the checksum will not be verified when it is set to "none", 25 | this is not recommended since these files can be very large and 26 | corruption does happen from time to time. 27 | 28 | - `box_name` (string) - if your "source_path" is a boxfile that we need to add to Vagrant, this is 29 | the name to give it. If left blank, will default to "packer_" plus your 30 | buildname. 31 | 32 | - `insert_key` (bool) - If true, Vagrant will automatically insert a keypair to use for SSH, 33 | replacing Vagrant's default insecure key inside the machine if detected. 34 | By default, Packer sets this to false. 35 | 36 | - `provider` (string) - The vagrant provider. 37 | This parameter is required when source_path have more than one provider, 38 | or when using vagrant-cloud post-processor. Defaults to unset. 39 | 40 | - `teardown_method` (string) - Whether to halt, suspend, or destroy the box when the build has 41 | completed. Defaults to "halt" 42 | 43 | - `box_version` (string) - What box version to use when initializing Vagrant. 44 | 45 | - `template` (string) - a path to a golang template for a vagrantfile. Our default template can 46 | be found [here](https://github.com/hashicorp/packer-plugin-vagrant/blob/main/builder/vagrant/step_create_vagrantfile.go#L39-L54). The template variables available to you are 47 | `{{ .BoxName }}`, `{{ .SyncedFolder }}`, and `{{.InsertKey}}`, which 48 | correspond to the Packer options box_name, synced_folder, and insert_key. 49 | Alternatively, the template variable `{{.DefaultTemplate}}` is available for 50 | use if you wish to extend the default generated template. 51 | 52 | - `synced_folder` (string) - Path to the folder to be synced to the guest. The path can be absolute 53 | or relative to the directory Packer is being run from. 54 | 55 | - `skip_add` (bool) - Don't call "vagrant box add" to add the box to your local environment; this 56 | is necessary if you want to launch a box that is already added to your 57 | vagrant environment. 58 | 59 | - `add_cacert` (string) - Equivalent to setting the 60 | --cacert 61 | option in vagrant add; defaults to unset. 62 | 63 | - `add_capath` (string) - Equivalent to setting the 64 | --capath option 65 | in vagrant add; defaults to unset. 66 | 67 | - `add_cert` (string) - Equivalent to setting the 68 | --cert option in 69 | vagrant add; defaults to unset. 70 | 71 | - `add_clean` (bool) - Equivalent to setting the 72 | --clean flag in 73 | vagrant add; defaults to unset. 74 | 75 | - `add_force` (bool) - Equivalent to setting the 76 | --force flag in 77 | vagrant add; defaults to unset. 78 | 79 | - `add_insecure` (bool) - Equivalent to setting the 80 | --insecure flag in 81 | vagrant add; defaults to unset. 82 | 83 | - `skip_package` (bool) - if true, Packer will not call vagrant package to 84 | package your base box into its own standalone .box file. 85 | 86 | - `output_vagrantfile` (string) - Output Vagrantfile 87 | 88 | - `package_include` ([]string) - Equivalent to setting the 89 | [`--include`](https://developer.hashicorp.com/vagrant/docs/cli/package#include-x-y-z) option 90 | in `vagrant package`; defaults to unset 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs-partials/builder/vagrant/Config-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `source_path` (string) - URL of the vagrant box to use, or the name of the vagrant box. 4 | hashicorp/precise64, ./mylocalbox.box and 5 | are all valid source boxes. If your source is a .box file, whether 6 | locally or from a URL like the latter example above, you will also need 7 | to provide a box_name. This option is required, unless you set 8 | global_id. You may only set one or the other, not both. 9 | 10 | - `global_id` (string) - the global id of a Vagrant box already added to Vagrant on your system. 11 | You can find the global id of your Vagrant boxes using the command 12 | vagrant global-status; your global_id will be a 7-digit number and 13 | letter combination that you'll find in the leftmost column of the 14 | global-status output. If you choose to use global_id instead of 15 | source_path, Packer will skip the Vagrant initialize and add steps, and 16 | simply launch the box directly using the global id. 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | The Vagrant plugin integrates Packer with HashiCorp [Vagrant](https://www.vagrantup.com/), allowing you to use Packer to create development boxes. 3 | 4 | ### Installation 5 | To install this plugin add this code into your Packer configuration and run [packer init](/packer/docs/commands/init) 6 | 7 | ```hcl 8 | packer { 9 | required_plugins { 10 | vagrant = { 11 | version = "~> 1" 12 | source = "github.com/hashicorp/vagrant" 13 | } 14 | } 15 | } 16 | ``` 17 | 18 | Alternatively, you can use `packer plugins install` to manage installation of this plugin. 19 | 20 | ```sh 21 | packer plugins install github.com/hashicorp/vagrant 22 | ``` 23 | 24 | ### Components 25 | 26 | #### Builders 27 | - [vagrant](/packer/integrations/hashicorp/vagrant/latest/components/builder/vagrant) - The Vagrant builder is intended for building new boxes from already-existing boxes. 28 | 29 | #### Post-Processor 30 | - [vagrant](/packer/integrations/hashicorp/vagrant/latest/components/post-processor/vagrant) - The Packer Vagrant post-processor takes a build and converts the artifact into a valid Vagrant box. 31 | - [vagrant-cloud](/packer/integrations/hashicorp/vagrant/latest/components/post-processor/vagrant-cloud) - The Vagrant Cloud post-processor enables the upload of Vagrant boxes to Vagrant Cloud. 32 | - [vagrant-registry](/packer/integrations/hashicorp/vagrant/latest/components/post-processor/vagrant-registry) - The Vagrant Registry post-processor enables the upload of Vagrant boxes to HCP Vagrant Box Registry. 33 | -------------------------------------------------------------------------------- /docs/builders/vagrant.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: | 3 | The Vagrant Packer builder is able to launch Vagrant boxes and 4 | re-package them into .box files 5 | page_title: Vagrant - Builders 6 | nav_title: Vagrant 7 | --- 8 | 9 | # Vagrant Builder 10 | 11 | Type: `vagrant` 12 | Artifact BuilderId: `vagrant` 13 | 14 | The Vagrant builder is intended for building new boxes from already-existing 15 | boxes. Your source should be a URL or path to a .box file or a Vagrant Cloud 16 | box name such as `hashicorp/precise64`. 17 | 18 | Packer will not install vagrant, nor will it install the underlying 19 | virtualization platforms or extra providers; We expect when you run this 20 | builder that you have already installed what you need. 21 | 22 | By default, this builder will initialize a new Vagrant workspace, launch your 23 | box from that workspace, provision it, call `vagrant package` to package it 24 | into a new box, and then destroy the original box. Please note that vagrant 25 | will _not_ remove the box file from your system (we don't call 26 | `vagrant box remove`). 27 | 28 | You can change the behavior so that the builder doesn't destroy the box by 29 | setting the `teardown_method` option. You can change the behavior so the builder 30 | doesn't package it (not all provisioners support the `vagrant package` command) 31 | by setting the `skip package` option. You can also change the behavior so that 32 | rather than initializing a new Vagrant workspace, you use an already defined 33 | one, by using `global_id` instead of `source_path`. 34 | 35 | Please note that if you are using the Vagrant builder, then the Vagrant 36 | post-processor is unnecessary because the output of the Vagrant builder is 37 | already a Vagrant box; using that post-processor with the Vagrant builder will 38 | cause your build to fail. Similarly, since Vagrant boxes are already compressed, 39 | the Compress post-processor will not work with this builder. 40 | 41 | ## Configuration Reference 42 | 43 | ### Required 44 | 45 | - `source_path` (string) - URL of the vagrant box to use, or the name of the 46 | vagrant box. `hashicorp/precise64`, `./mylocalbox.box` and 47 | `https://example.com/my-box.box` are all valid source boxes. If your 48 | source is a .box file, whether locally or from a URL like the latter example 49 | above, you will also need to provide a `box_name`. This option is required, 50 | unless you set `global_id`. You may only set one or the other, not both. 51 | 52 | or 53 | 54 | - `global_id` (string) - the global id of a Vagrant box already added to Vagrant 55 | on your system. You can find the global id of your Vagrant boxes using the 56 | command `vagrant global-status`; your global_id will be a 7-digit number and 57 | letter combination that you'll find in the leftmost column of the 58 | global-status output. If you choose to use `global_id` instead of 59 | `source_path`, Packer will skip the Vagrant initialize and add steps, and 60 | simply launch the box directly using the global id. 61 | 62 | ### Optional 63 | 64 | @include 'builder/vagrant/Config-not-required.mdx' 65 | 66 | ## Example 67 | 68 | Sample for `hashicorp/precise64` with virtualbox provider. 69 | 70 | **JSON** 71 | 72 | ```json 73 | { 74 | "builders": [ 75 | { 76 | "communicator": "ssh", 77 | "source_path": "hashicorp/precise64", 78 | "provider": "virtualbox", 79 | "add_force": true, 80 | "type": "vagrant" 81 | } 82 | ] 83 | } 84 | ``` 85 | 86 | **HCL2** 87 | 88 | ```hcl 89 | source "vagrant" "example" { 90 | communicator = "ssh" 91 | source_path = "hashicorp/precise64" 92 | provider = "virtualbox" 93 | add_force = true 94 | } 95 | 96 | build { 97 | sources = ["source.vagrant.example"] 98 | } 99 | ``` 100 | 101 | 102 | ## Regarding output directory and new box 103 | 104 | After Packer completes building and provisioning a new Vagrant Box file, it is worth 105 | noting that the new box file will need to be added to Vagrant. For a beginner to Packer 106 | and Vagrant, it may seem as if a simple 'vagrant up' in the output directory will run the 107 | the newly created Box. This is not the case. 108 | 109 | Rather, create a new directory (to avoid Vagarant init collisions), add the new 110 | package.box to Vagrant and init. Then run vagrant up to bring up the new box created 111 | by Packer. You will now be able to connect to the new box with provisioned changes. 112 | 113 | ``` 114 | 'mkdir output2' 115 | 'cp package.box ./output2' 116 | 'vagrant box add new-box name-of-the-packer-box.box' 117 | 'vagrant init new-box' 118 | 'vagrant up' 119 | ``` 120 | 121 | ## A note on SSH connections 122 | 123 | Currently this builder only works for SSH connections, and automatically fills 124 | in all information needed for the SSH communicator using vagrant's ssh-config. 125 | 126 | If you would like to connect via a different username or authentication method 127 | than is produced when you call `vagrant ssh-config`, then you must provide the 128 | 129 | `ssh_username` and all other relevant authentication information (e.g. 130 | `ssh_password` or `ssh_private_key_file`) 131 | 132 | By providing the `ssh_username`, you're telling Packer not to use the vagrant 133 | ssh config, except for determining the host and port for the virtual machine to 134 | connect to. 135 | -------------------------------------------------------------------------------- /example/vagrant_builder.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | packer { 5 | required_plugins { 6 | vagrant = { 7 | version = ">= 1.0.0" 8 | source = "github.com/hashicorp/vagrant" 9 | } 10 | } 11 | } 12 | 13 | source "vagrant" "example" { 14 | communicator = "ssh" 15 | output_dir = "precisebox" 16 | package_include = ["./crash.log", "./base_aws.json"] 17 | provider = "virtualbox" 18 | skip_add = true 19 | source_path = "hashicorp/precise64" 20 | } 21 | 22 | source "vagrant" "frombox" { 23 | box_name = "test-packer" 24 | communicator = "ssh" 25 | output_dir = "test-output-dir" 26 | provider = "virtualbox" 27 | source_path = "./precisebox/package.box" 28 | teardown_method = "halt" 29 | } 30 | 31 | build { 32 | sources = ["source.vagrant.example", 33 | "source.vagrant.frombox"] 34 | 35 | provisioner "shell" { 36 | inline = [""] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/vagrant_cloud_postprocessor.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | packer { 5 | required_plugins { 6 | vagrant = { 7 | version = ">= 1.0.2" 8 | source = "github.com/hashicorp/vagrant" 9 | } 10 | } 11 | } 12 | 13 | variable "vagrant_token" { 14 | type = string 15 | default = env("VAGRANT_CLOUD_TOKEN") 16 | } 17 | 18 | build { 19 | sources = ["source.vagrant.example"] 20 | 21 | provisioner "shell" { 22 | inline = ["echo hi"] 23 | } 24 | 25 | post-processor "vagrant-cloud" { 26 | access_token = var.vagrant_token 27 | box_tag = "mmarsh/partybox" 28 | no_direct_upload = true 29 | version = "0.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/vagrant_postprocessor.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | packer { 5 | required_plugins { 6 | docker = { 7 | version = ">= 0.0.7" 8 | source = "github.com/hashicorp/docker" 9 | } 10 | vagrant = { 11 | version = ">= 1.0.2" 12 | source = "github.com/hashicorp/vagrant" 13 | } 14 | } 15 | } 16 | 17 | source "docker" "example" { 18 | image = "ubuntu:xenial" 19 | commit = true 20 | } 21 | 22 | build { 23 | sources = [ 24 | "source.docker.example", 25 | ] 26 | provisioner "shell" { 27 | environment_vars = [ 28 | "FOO=hello world", 29 | ] 30 | inline = [ 31 | "echo Adding file to Docker Container", 32 | "echo \"FOO is $FOO\" > example.txt" 33 | ] 34 | } 35 | 36 | post-processors { 37 | post-processor "vagrant" {} 38 | } 39 | } -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/hashicorp/packer-plugin-sdk/plugin" 11 | 12 | vagrantB "github.com/hashicorp/packer-plugin-vagrant/builder/vagrant" 13 | vagrantRegistryPP "github.com/hashicorp/packer-plugin-vagrant/post-processor/hcp-vagrant-registry" 14 | vagrantPP "github.com/hashicorp/packer-plugin-vagrant/post-processor/vagrant" 15 | vagrantCloudPP "github.com/hashicorp/packer-plugin-vagrant/post-processor/vagrant-cloud" 16 | "github.com/hashicorp/packer-plugin-vagrant/version" 17 | ) 18 | 19 | func main() { 20 | pps := plugin.NewSet() 21 | pps.RegisterBuilder(plugin.DEFAULT_NAME, new(vagrantB.Builder)) 22 | pps.RegisterPostProcessor(plugin.DEFAULT_NAME, new(vagrantPP.PostProcessor)) 23 | pps.RegisterPostProcessor("cloud", new(vagrantCloudPP.PostProcessor)) 24 | pps.RegisterPostProcessor("registry", new(vagrantRegistryPP.PostProcessor)) 25 | pps.SetVersion(version.PluginVersion) 26 | err := pps.Run() 27 | if err != nil { 28 | fmt.Fprintln(os.Stderr, err.Error()) 29 | os.Exit(1) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /post-processor/hcp-vagrant-registry/artifact.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package hcpvagrantregistry 5 | 6 | import ( 7 | "fmt" 8 | ) 9 | 10 | const BuilderId = "hashicorp.post-processor.vagrant-registry" 11 | 12 | type Artifact struct { 13 | Tag string 14 | Provider string 15 | } 16 | 17 | func NewArtifact(provider, tag string) *Artifact { 18 | return &Artifact{ 19 | Tag: tag, 20 | Provider: provider, 21 | } 22 | } 23 | 24 | func (*Artifact) BuilderId() string { 25 | return BuilderId 26 | } 27 | 28 | func (*Artifact) Files() []string { 29 | return nil 30 | } 31 | 32 | func (*Artifact) Id() string { 33 | return "" 34 | } 35 | 36 | func (a *Artifact) String() string { 37 | return fmt.Sprintf("'%s': %s", a.Provider, a.Tag) 38 | } 39 | 40 | func (*Artifact) State(name string) interface{} { 41 | return nil 42 | } 43 | 44 | func (*Artifact) Destroy() error { 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /post-processor/hcp-vagrant-registry/artifact_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package hcpvagrantregistry 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/hashicorp/packer-plugin-sdk/packer" 10 | ) 11 | 12 | func TestArtifact_ImplementsArtifact(t *testing.T) { 13 | var raw interface{} 14 | raw = &Artifact{} 15 | if _, ok := raw.(packer.Artifact); !ok { 16 | t.Fatalf("Artifact should be a Artifact") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /post-processor/hcp-vagrant-registry/step_confirm_upload.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package hcpvagrantregistry 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/client/registry_service" 11 | "github.com/hashicorp/packer-plugin-sdk/multistep" 12 | "github.com/hashicorp/packer-plugin-sdk/packer" 13 | ) 14 | 15 | type stepConfirmUpload struct{} 16 | 17 | func (s *stepConfirmUpload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 18 | client := state.Get("client").(*registry_service.Client) 19 | config := state.Get("config").(*Config) 20 | ui := state.Get("ui").(packer.Ui) 21 | 22 | if config.NoDirectUpload { 23 | return multistep.ActionContinue 24 | } 25 | 26 | providerName := state.Get("providerName").(string) 27 | archName := state.Get("architecture").(string) 28 | object := state.Get("upload-object").(string) 29 | 30 | ui.Say("Completing box upload...") 31 | 32 | _, err := client.CompleteDirectUploadBox( 33 | ®istry_service.CompleteDirectUploadBoxParams{ 34 | Context: ctx, 35 | Registry: config.registry, 36 | Box: config.box, 37 | Version: config.Version, 38 | Provider: providerName, 39 | Architecture: archName, 40 | Object: object, 41 | }, nil, 42 | ) 43 | 44 | if isErrorUnexpected(err, state) { 45 | return multistep.ActionHalt 46 | } 47 | 48 | if errMsg, ok := errorResponseMsg(err); ok { 49 | state.Put("error", fmt.Errorf("Failure confirming upload: %s", errMsg)) 50 | return multistep.ActionHalt 51 | } 52 | 53 | return multistep.ActionContinue 54 | } 55 | 56 | func (s *stepConfirmUpload) Cleanup(state multistep.StateBag) {} 57 | -------------------------------------------------------------------------------- /post-processor/hcp-vagrant-registry/step_create_architecture.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package hcpvagrantregistry 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/client/registry_service" 11 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/models" 12 | "github.com/hashicorp/packer-plugin-sdk/multistep" 13 | ) 14 | 15 | type stepCreateArchitecture struct{} 16 | 17 | func (s *stepCreateArchitecture) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 18 | client := state.Get("client").(*registry_service.Client) 19 | providerName := state.Get("providerName").(string) 20 | downloadUrl := state.Get("downloadUrl").(string) 21 | config := state.Get("config").(*Config) 22 | archName := state.Get("architecture").(string) 23 | 24 | resp, err := client.ReadArchitecture( 25 | ®istry_service.ReadArchitectureParams{ 26 | Context: ctx, 27 | Registry: config.registry, 28 | Box: config.box, 29 | Version: config.Version, 30 | Provider: providerName, 31 | Architecture: archName, 32 | }, 33 | ) 34 | 35 | if isErrorUnexpected(err, state) { 36 | return multistep.ActionHalt 37 | } 38 | 39 | if resp, ok := errorResponse(err); ok { 40 | // Any code outside of not found should be an error 41 | if !resp.IsCode(404) { 42 | state.Put("error", fmt.Errorf("Failure retrieving architecture: %s", resp.GetPayload().Message)) 43 | return multistep.ActionHalt 44 | } 45 | } 46 | 47 | data := &models.HashicorpCloudVagrant20220930BoxData{} 48 | 49 | if downloadUrl != "" { 50 | data.DownloadURL = downloadUrl 51 | } 52 | 53 | if config.BoxChecksum != "" { 54 | data.Checksum = config.checksum 55 | data.ChecksumType = models.NewHashicorpCloudVagrant20220930ChecksumType( 56 | models.HashicorpCloudVagrant20220930ChecksumType(config.checksumType), 57 | ) 58 | } else { 59 | data.ChecksumType = models.HashicorpCloudVagrant20220930ChecksumTypeNONE.Pointer() 60 | } 61 | 62 | // If the architecture already exists, update it 63 | if resp != nil && resp.IsSuccess() { 64 | _, err := client.UpdateArchitecture( 65 | ®istry_service.UpdateArchitectureParams{ 66 | Context: ctx, 67 | Registry: config.registry, 68 | Box: config.box, 69 | Version: config.Version, 70 | Provider: providerName, 71 | Architecture: archName, 72 | Data: &models.HashicorpCloudVagrant20220930Architecture{ 73 | BoxData: data, 74 | }, 75 | }, nil, 76 | ) 77 | 78 | if isErrorUnexpected(err, state) { 79 | return multistep.ActionHalt 80 | } 81 | 82 | if errMsg, ok := errorResponseMsg(err); ok { 83 | state.Put("error", fmt.Errorf("Failure updating existing architecture: %s", errMsg)) 84 | return multistep.ActionHalt 85 | } 86 | 87 | return multistep.ActionContinue 88 | } 89 | 90 | _, err = client.CreateArchitecture( 91 | ®istry_service.CreateArchitectureParams{ 92 | Context: ctx, 93 | Registry: config.registry, 94 | Box: config.box, 95 | Version: config.Version, 96 | Provider: providerName, 97 | Data: &models.HashicorpCloudVagrant20220930Architecture{ 98 | ArchitectureType: archName, 99 | Default: archName == config.DefaultArchitecture, 100 | BoxData: data, 101 | }, 102 | }, nil, 103 | ) 104 | 105 | if isErrorUnexpected(err, state) { 106 | return multistep.ActionHalt 107 | } 108 | 109 | if errMsg, ok := errorResponseMsg(err); ok { 110 | state.Put("error", fmt.Errorf("Failure creating new architecture: %s", errMsg)) 111 | return multistep.ActionHalt 112 | } 113 | 114 | return multistep.ActionContinue 115 | } 116 | 117 | func (s *stepCreateArchitecture) Cleanup(state multistep.StateBag) {} 118 | -------------------------------------------------------------------------------- /post-processor/hcp-vagrant-registry/step_create_box.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package hcpvagrantregistry 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-operation/stable/2020-05-05/client/operation_service" 11 | shared_models "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" 12 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/client/registry_service" 13 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/models" 14 | "github.com/hashicorp/packer-plugin-sdk/multistep" 15 | "github.com/hashicorp/packer-plugin-sdk/packer" 16 | ) 17 | 18 | type stepCreateBox struct{} 19 | 20 | var BOX_CREATE_TIMEOUT = "60s" 21 | 22 | func (s *stepCreateBox) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 23 | client := state.Get("client").(*registry_service.Client) 24 | ui := state.Get("ui").(packer.Ui) 25 | config := state.Get("config").(*Config) 26 | 27 | resp, err := client.ReadBox(®istry_service.ReadBoxParams{ 28 | Context: ctx, 29 | Box: config.box, 30 | Registry: config.registry, 31 | }) 32 | 33 | if isErrorUnexpected(err, state) { 34 | return multistep.ActionHalt 35 | } 36 | 37 | if resp, ok := errorResponse(err); ok { 38 | // Any code outside of not found should be an error 39 | if !resp.IsCode(404) { 40 | state.Put("error", fmt.Errorf("Failure retrieving box: %s", resp.GetPayload().Message)) 41 | return multistep.ActionHalt 42 | } 43 | } 44 | 45 | // If the request was successful, nothing to do 46 | if resp != nil && resp.IsSuccess() { 47 | ui.Say(fmt.Sprintf("Found box and verified accessible: %s", config.Tag)) 48 | return multistep.ActionContinue 49 | } 50 | 51 | // Create the box 52 | cresp, err := client.CreateBox( 53 | ®istry_service.CreateBoxParams{ 54 | Context: ctx, 55 | Registry: config.registry, 56 | Data: &models.HashicorpCloudVagrant20220930Box{ 57 | Name: config.box, 58 | Description: config.BoxDescription, 59 | IsPrivate: config.BoxPrivate, 60 | }, 61 | }, nil, 62 | ) 63 | 64 | if isErrorUnexpected(err, state) { 65 | return multistep.ActionHalt 66 | } 67 | 68 | if errMsg, ok := errorResponseMsg(err); ok { 69 | state.Put("error", fmt.Errorf("Failure creating new box: %s - Please try again.", errMsg)) 70 | return multistep.ActionHalt 71 | } 72 | 73 | ui.Say(fmt.Sprintf("Created new box: %s", config.Tag)) 74 | if cresp.Payload == nil || cresp.Payload.Operation == nil { 75 | state.Put("error", fmt.Errorf("Unable to wait for box to become available - Please check HCP Vagrant for box status, and try again.")) 76 | return multistep.ActionHalt 77 | } 78 | 79 | ui.Say("Waiting for box to become available...") 80 | op := cresp.Payload.Operation 81 | 82 | operationClient := state.Get("operation-client").(operation_service.ClientService) 83 | waitReq := &operation_service.WaitParams{ 84 | ID: op.ID, 85 | LocationOrganizationID: op.Location.OrganizationID, 86 | LocationProjectID: op.Location.ProjectID, 87 | Timeout: &BOX_CREATE_TIMEOUT, 88 | Context: ctx, 89 | } 90 | 91 | wresp, err := operationClient.Wait(waitReq, nil) 92 | if isErrorUnexpected(err, state) { 93 | return multistep.ActionHalt 94 | } 95 | 96 | if errMsg, ok := errorResponseMsg(err); ok { 97 | state.Put("error", fmt.Errorf("Unexpected failure waiting for box to become available: %s - Please try again.", errMsg)) 98 | return multistep.ActionHalt 99 | } 100 | 101 | if wresp.Payload == nil || wresp.Payload.Operation == nil { 102 | state.Put("error", fmt.Errorf("Unable to check box creation operation status - Please check HCP Vagrant for box status, and try again.")) 103 | return multistep.ActionHalt 104 | } 105 | 106 | operation := wresp.Payload.Operation 107 | if operation.Error != nil { 108 | state.Put("error", fmt.Errorf("Box creation operation reported a failure: %s - Please try again.", operation.Error.Message)) 109 | return multistep.ActionHalt 110 | } 111 | 112 | if operation.State == nil || *operation.State != shared_models.HashicorpCloudOperationOperationStateDONE { 113 | state.Put("error", fmt.Errorf("Timeout exceeded waiting for box to become available - Please verify box creation in HCP Vagrant and try again.")) 114 | return multistep.ActionHalt 115 | } 116 | 117 | return multistep.ActionContinue 118 | } 119 | 120 | func (s *stepCreateBox) Cleanup(state multistep.StateBag) {} 121 | -------------------------------------------------------------------------------- /post-processor/hcp-vagrant-registry/step_create_provider.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package hcpvagrantregistry 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/client/registry_service" 11 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/models" 12 | "github.com/hashicorp/packer-plugin-sdk/multistep" 13 | "github.com/hashicorp/packer-plugin-sdk/packer" 14 | ) 15 | 16 | type stepCreateProvider struct{} 17 | 18 | func (s *stepCreateProvider) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 19 | client := state.Get("client").(*registry_service.Client) 20 | ui := state.Get("ui").(packer.Ui) 21 | providerName := state.Get("providerName").(string) 22 | config := state.Get("config").(*Config) 23 | 24 | resp, err := client.ReadProvider( 25 | ®istry_service.ReadProviderParams{ 26 | Context: ctx, 27 | Registry: config.registry, 28 | Box: config.box, 29 | Version: config.Version, 30 | Provider: providerName, 31 | }, 32 | ) 33 | 34 | if isErrorUnexpected(err, state) { 35 | return multistep.ActionHalt 36 | } 37 | 38 | if resp, ok := errorResponse(err); ok { 39 | // Any code outside of not found should be an error 40 | if !resp.IsCode(404) { 41 | state.Put("error", fmt.Errorf("Failure retrieving provider: %s", resp.GetPayload().Message)) 42 | return multistep.ActionHalt 43 | } 44 | } 45 | 46 | if resp != nil && resp.IsSuccess() { 47 | ui.Message("Provider exists, skipping creation") 48 | return multistep.ActionContinue 49 | } 50 | 51 | _, err = client.CreateProvider( 52 | ®istry_service.CreateProviderParams{ 53 | Context: ctx, 54 | Registry: config.registry, 55 | Box: config.box, 56 | Version: config.Version, 57 | Data: &models.HashicorpCloudVagrant20220930Provider{ 58 | Name: providerName, 59 | }, 60 | }, nil, 61 | ) 62 | 63 | if isErrorUnexpected(err, state) { 64 | return multistep.ActionHalt 65 | } 66 | 67 | if errMsg, ok := errorResponseMsg(err); ok { 68 | state.Put("error", fmt.Errorf("Failure creating new provider: %s", errMsg)) 69 | return multistep.ActionHalt 70 | } 71 | 72 | ui.Say(fmt.Sprintf("Created new provider: %s", providerName)) 73 | 74 | return multistep.ActionContinue 75 | } 76 | 77 | func (s *stepCreateProvider) Cleanup(state multistep.StateBag) {} 78 | -------------------------------------------------------------------------------- /post-processor/hcp-vagrant-registry/step_create_version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package hcpvagrantregistry 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/client/registry_service" 11 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/models" 12 | "github.com/hashicorp/packer-plugin-sdk/multistep" 13 | "github.com/hashicorp/packer-plugin-sdk/packer" 14 | ) 15 | 16 | type stepCreateVersion struct{} 17 | 18 | func (s *stepCreateVersion) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 19 | client := state.Get("client").(*registry_service.Client) 20 | ui := state.Get("ui").(packer.Ui) 21 | config := state.Get("config").(*Config) 22 | 23 | resp, err := client.ReadVersion( 24 | ®istry_service.ReadVersionParams{ 25 | Context: ctx, 26 | Registry: config.registry, 27 | Box: config.box, 28 | Version: config.Version, 29 | }, 30 | ) 31 | 32 | if isErrorUnexpected(err, state) { 33 | return multistep.ActionHalt 34 | } 35 | 36 | if resp, ok := errorResponse(err); ok { 37 | // Any code outside of not found should error 38 | if !resp.IsCode(404) { 39 | state.Put("error", fmt.Errorf("Failure retrieving version: %s", resp.GetPayload().Message)) 40 | return multistep.ActionHalt 41 | } 42 | } 43 | 44 | // If the request was successful, nothing to do 45 | if resp != nil && resp.IsSuccess() { 46 | if resp.Payload == nil || resp.Payload.Version == nil { 47 | state.Put("error", fmt.Errorf("Invalid response body for version read")) 48 | return multistep.ActionHalt 49 | } 50 | 51 | state.Put("version", resp.Payload.Version) 52 | ui.Message("Version exists, skipping creation") 53 | return multistep.ActionContinue 54 | } 55 | 56 | vresp, err := client.CreateVersion( 57 | ®istry_service.CreateVersionParams{ 58 | Context: ctx, 59 | Registry: config.registry, 60 | Box: config.box, 61 | Data: &models.HashicorpCloudVagrant20220930Version{ 62 | Name: config.Version, 63 | Description: config.VersionDescription, 64 | }, 65 | }, nil, 66 | ) 67 | 68 | if isErrorUnexpected(err, state) { 69 | return multistep.ActionHalt 70 | } 71 | 72 | if errMsg, ok := errorResponseMsg(err); ok { 73 | state.Put("error", fmt.Errorf("Failure creating new version: %s", errMsg)) 74 | return multistep.ActionHalt 75 | } 76 | 77 | if vresp.Payload == nil || vresp.Payload.Version == nil { 78 | state.Put("error", fmt.Errorf("Invalid response body for version create")) 79 | return multistep.ActionHalt 80 | } 81 | 82 | state.Put("version", vresp.Payload.Version) 83 | ui.Say(fmt.Sprintf("Created new version: %s", config.Version)) 84 | 85 | return multistep.ActionContinue 86 | } 87 | 88 | func (s *stepCreateVersion) Cleanup(state multistep.StateBag) {} 89 | -------------------------------------------------------------------------------- /post-processor/hcp-vagrant-registry/step_prepare_upload.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package hcpvagrantregistry 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "os" 10 | 11 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/client/registry_service" 12 | "github.com/hashicorp/packer-plugin-sdk/multistep" 13 | "github.com/hashicorp/packer-plugin-sdk/packer" 14 | ) 15 | 16 | const HCP_VAGRANT_REGISTRY_DIRECT_UPLOAD_LIMIT = 5368709120 // Upload limit is 5G 17 | 18 | type stepPrepareUpload struct{} 19 | 20 | func (s *stepPrepareUpload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 21 | client := state.Get("client").(*registry_service.Client) 22 | config := state.Get("config").(*Config) 23 | ui := state.Get("ui").(packer.Ui) 24 | providerName := state.Get("providerName").(string) 25 | archName := state.Get("architecture").(string) 26 | artifactFilePath := state.Get("artifactFilePath").(string) 27 | 28 | // If direct upload is enabled, the asset size must be <= 5 GB 29 | if config.NoDirectUpload == false { 30 | f, err := os.Stat(artifactFilePath) 31 | if err != nil { 32 | ui.Error(fmt.Sprintf("failed determining size of upload artifact: %s", artifactFilePath)) 33 | } 34 | if f.Size() > HCP_VAGRANT_REGISTRY_DIRECT_UPLOAD_LIMIT { 35 | ui.Say(fmt.Sprintf("Asset %s is larger than the direct upload limit. Setting `NoDirectUpload` to true", artifactFilePath)) 36 | config.NoDirectUpload = true 37 | } 38 | } 39 | 40 | ui.Say(fmt.Sprintf("Preparing upload of box: %s", artifactFilePath)) 41 | 42 | if config.NoDirectUpload { 43 | resp, err := client.UploadBox( 44 | ®istry_service.UploadBoxParams{ 45 | Context: ctx, 46 | Registry: config.registry, 47 | Box: config.box, 48 | Version: config.Version, 49 | Provider: providerName, 50 | Architecture: archName, 51 | }, nil, 52 | ) 53 | 54 | if isErrorUnexpected(err, state) { 55 | return multistep.ActionHalt 56 | } 57 | 58 | if errMsg, ok := errorResponseMsg(err); ok { 59 | state.Put("error", fmt.Errorf("Failure preparing upload: %s", errMsg)) 60 | return multistep.ActionHalt 61 | } 62 | 63 | state.Put("upload-url", resp.Payload.URL) 64 | } else { 65 | resp, err := client.DirectUploadBox( 66 | ®istry_service.DirectUploadBoxParams{ 67 | Context: ctx, 68 | Registry: config.registry, 69 | Box: config.box, 70 | Version: config.Version, 71 | Provider: providerName, 72 | Architecture: archName, 73 | }, nil, 74 | ) 75 | 76 | if isErrorUnexpected(err, state) { 77 | return multistep.ActionHalt 78 | } 79 | 80 | if errMsg, ok := errorResponseMsg(err); ok { 81 | state.Put("error", fmt.Errorf("Failure preparing upload: %s", errMsg)) 82 | return multistep.ActionHalt 83 | } 84 | 85 | state.Put("upload-url", resp.Payload.URL) 86 | state.Put("upload-object", resp.Payload.Object) 87 | } 88 | 89 | return multistep.ActionContinue 90 | } 91 | 92 | func (s *stepPrepareUpload) Cleanup(state multistep.StateBag) {} 93 | -------------------------------------------------------------------------------- /post-processor/hcp-vagrant-registry/step_release_version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package hcpvagrantregistry 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/client/registry_service" 11 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/models" 12 | "github.com/hashicorp/packer-plugin-sdk/multistep" 13 | "github.com/hashicorp/packer-plugin-sdk/packer" 14 | ) 15 | 16 | type stepReleaseVersion struct{} 17 | 18 | func (s *stepReleaseVersion) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 19 | client := state.Get("client").(*registry_service.Client) 20 | ui := state.Get("ui").(packer.Ui) 21 | config := state.Get("config").(*Config) 22 | version := state.Get("version").(*models.HashicorpCloudVagrant20220930Version) 23 | 24 | if config.NoRelease { 25 | ui.Message("Not releasing version due to configuration") 26 | return multistep.ActionContinue 27 | } 28 | 29 | if version.State != nil && *version.State != models.HashicorpCloudVagrant20220930VersionStateUNRELEASED { 30 | ui.Message("Version not in unreleased state, skipping release") 31 | return multistep.ActionContinue 32 | } 33 | 34 | ui.Say(fmt.Sprintf("Releasing version: %s", config.Version)) 35 | 36 | _, err := client.ReleaseVersion( 37 | ®istry_service.ReleaseVersionParams{ 38 | Context: ctx, 39 | Registry: config.registry, 40 | Box: config.box, 41 | Version: config.Version, 42 | }, nil, 43 | ) 44 | 45 | if isErrorUnexpected(err, state) { 46 | return multistep.ActionHalt 47 | } 48 | 49 | if errMsg, ok := errorResponseMsg(err); ok { 50 | state.Put("error", fmt.Errorf("Failure releasing version: %s", errMsg)) 51 | return multistep.ActionHalt 52 | } 53 | 54 | ui.Message("Version successfully released and available") 55 | return multistep.ActionContinue 56 | } 57 | 58 | func (s *stepReleaseVersion) Cleanup(state multistep.StateBag) {} 59 | -------------------------------------------------------------------------------- /post-processor/hcp-vagrant-registry/step_upload.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package hcpvagrantregistry 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "net/http" 10 | "os" 11 | "time" 12 | 13 | "github.com/hashicorp/packer-plugin-sdk/multistep" 14 | "github.com/hashicorp/packer-plugin-sdk/net" 15 | "github.com/hashicorp/packer-plugin-sdk/packer" 16 | "github.com/hashicorp/packer-plugin-sdk/retry" 17 | ) 18 | 19 | type stepUpload struct{} 20 | 21 | func (s *stepUpload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 22 | ui := state.Get("ui").(packer.Ui) 23 | url := state.Get("upload-url").(string) 24 | artifactFilePath := state.Get("artifactFilePath").(string) 25 | 26 | client := net.HttpClientWithEnvironmentProxy() 27 | 28 | // Stash the http client we built so it can 29 | // be used in the confrim step if needed. 30 | state.Put("http-client", client) 31 | 32 | ui.Say(fmt.Sprintf("Uploading box: %s", artifactFilePath)) 33 | ui.Message( 34 | "Depending on your internet connection and the size of the box,\n" + 35 | "this may take some time") 36 | 37 | err := retry.Config{ 38 | Tries: 3, 39 | RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 10 * time.Second, Multiplier: 2}).Linear, 40 | }.Run(ctx, func(ctx context.Context) (err error) { 41 | ui.Message("Uploading box") 42 | 43 | defer func() { 44 | if err != nil { 45 | ui.Message(fmt.Sprintf( 46 | "Error uploading box! Will retry in 10 seconds. Error: %s", err)) 47 | } 48 | }() 49 | 50 | file, err := os.Open(artifactFilePath) 51 | if err != nil { 52 | return 53 | } 54 | defer file.Close() 55 | 56 | info, err := file.Stat() 57 | if err != nil { 58 | return 59 | } 60 | 61 | request, err := http.NewRequest("PUT", url, file) 62 | if err != nil { 63 | return 64 | } 65 | 66 | request.ContentLength = info.Size() 67 | resp, err := client.Do(request) 68 | if err != nil { 69 | return 70 | } 71 | 72 | if resp.StatusCode != 200 { 73 | err = fmt.Errorf("bad HTTP status: %d", resp.StatusCode) 74 | return 75 | } 76 | 77 | return 78 | }) 79 | 80 | if err != nil { 81 | state.Put("error", fmt.Errorf("Failed to upload box asset: %s", err)) 82 | return multistep.ActionHalt 83 | } 84 | 85 | ui.Message("Box successfully uploaded") 86 | 87 | return multistep.ActionContinue 88 | } 89 | 90 | func (s *stepUpload) Cleanup(state multistep.StateBag) {} 91 | -------------------------------------------------------------------------------- /post-processor/hcp-vagrant-registry/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package hcpvagrantregistry 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/go-openapi/runtime" 10 | "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" 11 | "github.com/hashicorp/packer-plugin-sdk/multistep" 12 | ) 13 | 14 | type hcpErrorResponse interface { 15 | IsSuccess() bool 16 | IsRedirect() bool 17 | IsClientError() bool 18 | IsServerError() bool 19 | IsCode(int) bool 20 | Code() int 21 | GetPayload() *models.GoogleRPCStatus 22 | } 23 | 24 | func isErrorUnexpected(err error, state multistep.StateBag) bool { 25 | if err == nil { 26 | return false 27 | } 28 | 29 | if _, ok := errorResponse(err); !ok { 30 | state.Put("error", fmt.Errorf("Unexpected client error: %s", err)) 31 | return true 32 | } 33 | 34 | return false 35 | } 36 | 37 | func errorResponse(err error) (hcpErrorResponse, bool) { 38 | if err == nil { 39 | return nil, false 40 | } 41 | 42 | if val, ok := err.(*runtime.APIError); ok { 43 | if resp, ok := val.Response.(hcpErrorResponse); ok { 44 | return resp, true 45 | } 46 | } 47 | 48 | if resp, ok := err.(hcpErrorResponse); ok { 49 | return resp, true 50 | } 51 | 52 | return nil, false 53 | } 54 | 55 | func errorStatus(err error) (*models.GoogleRPCStatus, bool) { 56 | if val, ok := errorResponse(err); ok { 57 | return val.GetPayload(), true 58 | } 59 | 60 | return nil, false 61 | } 62 | 63 | func errorResponseMsg(err error) (string, bool) { 64 | if val, ok := errorStatus(err); ok { 65 | msg := val.Message 66 | if msg == "" { 67 | msg = "Unexpected error encountered" 68 | } 69 | 70 | return msg, true 71 | } 72 | 73 | return "", false 74 | } 75 | -------------------------------------------------------------------------------- /post-processor/vagrant-cloud/artifact.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrantcloud 5 | 6 | import ( 7 | "fmt" 8 | ) 9 | 10 | const BuilderId = "pearkes.post-processor.vagrant-cloud" 11 | 12 | type Artifact struct { 13 | Tag string 14 | Provider string 15 | } 16 | 17 | func NewArtifact(provider, tag string) *Artifact { 18 | return &Artifact{ 19 | Tag: tag, 20 | Provider: provider, 21 | } 22 | } 23 | 24 | func (*Artifact) BuilderId() string { 25 | return BuilderId 26 | } 27 | 28 | func (a *Artifact) Files() []string { 29 | return nil 30 | } 31 | 32 | func (a *Artifact) Id() string { 33 | return "" 34 | } 35 | 36 | func (a *Artifact) String() string { 37 | return fmt.Sprintf("'%s': %s", a.Provider, a.Tag) 38 | } 39 | 40 | func (*Artifact) State(name string) interface{} { 41 | return nil 42 | } 43 | 44 | func (a *Artifact) Destroy() error { 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /post-processor/vagrant-cloud/artifact_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrantcloud 5 | 6 | import ( 7 | "testing" 8 | 9 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 10 | ) 11 | 12 | func TestArtifact_ImplementsArtifact(t *testing.T) { 13 | var raw interface{} 14 | raw = &Artifact{} 15 | if _, ok := raw.(packersdk.Artifact); !ok { 16 | t.Fatalf("Artifact should be a Artifact") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /post-processor/vagrant-cloud/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrantcloud 5 | 6 | import ( 7 | "encoding/json" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestVagranCloudErrors(t *testing.T) { 13 | testCases := []struct { 14 | resp string 15 | expected string 16 | }{ 17 | {`{"Status":"422 Unprocessable Entity", "StatusCode":422, "errors":[]}`, ""}, 18 | {`{"Status":"404 Artifact not found", "StatusCode":404, "errors":["error1", "error2"]}`, "error1. error2"}, 19 | {`{"StatusCode":403, "errors":[{"message":"Bad credentials"}]}`, "message Bad credentials"}, 20 | {`{"StatusCode":500, "errors":[["error in unexpected format"]]}`, "[error in unexpected format]"}, 21 | } 22 | 23 | for _, tc := range testCases { 24 | var cloudErrors VagrantCloudErrors 25 | err := json.NewDecoder(strings.NewReader(tc.resp)).Decode(&cloudErrors) 26 | if err != nil { 27 | t.Errorf("failed to decode error response: %s", err) 28 | } 29 | if got := cloudErrors.FormatErrors(); got != tc.expected { 30 | t.Errorf("failed to get expected response; expected %q, got %q", tc.expected, got) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /post-processor/vagrant-cloud/post-processor.hcl2spec.go: -------------------------------------------------------------------------------- 1 | // Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT. 2 | 3 | package vagrantcloud 4 | 5 | import ( 6 | "github.com/hashicorp/hcl/v2/hcldec" 7 | "github.com/zclconf/go-cty/cty" 8 | ) 9 | 10 | // FlatConfig is an auto-generated flat version of Config. 11 | // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. 12 | type FlatConfig struct { 13 | PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` 14 | PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` 15 | PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` 16 | PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` 17 | PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` 18 | PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` 19 | PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` 20 | PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` 21 | Tag *string `mapstructure:"box_tag" cty:"box_tag" hcl:"box_tag"` 22 | Version *string `mapstructure:"version" cty:"version" hcl:"version"` 23 | VersionDescription *string `mapstructure:"version_description" cty:"version_description" hcl:"version_description"` 24 | NoRelease *bool `mapstructure:"no_release" cty:"no_release" hcl:"no_release"` 25 | Architecture *string `mapstructure:"architecture" cty:"architecture" hcl:"architecture"` 26 | DefaultArchitecture *string `mapstructure:"default_architecture" cty:"default_architecture" hcl:"default_architecture"` 27 | AccessToken *string `mapstructure:"access_token" cty:"access_token" hcl:"access_token"` 28 | VagrantCloudUrl *string `mapstructure:"vagrant_cloud_url" cty:"vagrant_cloud_url" hcl:"vagrant_cloud_url"` 29 | InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"` 30 | BoxDownloadUrl *string `mapstructure:"box_download_url" cty:"box_download_url" hcl:"box_download_url"` 31 | NoDirectUpload *bool `mapstructure:"no_direct_upload" cty:"no_direct_upload" hcl:"no_direct_upload"` 32 | BoxChecksum *string `mapstructure:"box_checksum" cty:"box_checksum" hcl:"box_checksum"` 33 | } 34 | 35 | // FlatMapstructure returns a new FlatConfig. 36 | // FlatConfig is an auto-generated flat version of Config. 37 | // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. 38 | func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { 39 | return new(FlatConfig) 40 | } 41 | 42 | // HCL2Spec returns the hcl spec of a Config. 43 | // This spec is used by HCL to read the fields of Config. 44 | // The decoded values from this spec will then be applied to a FlatConfig. 45 | func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { 46 | s := map[string]hcldec.Spec{ 47 | "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, 48 | "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, 49 | "packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false}, 50 | "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, 51 | "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, 52 | "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, 53 | "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, 54 | "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, 55 | "box_tag": &hcldec.AttrSpec{Name: "box_tag", Type: cty.String, Required: false}, 56 | "version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false}, 57 | "version_description": &hcldec.AttrSpec{Name: "version_description", Type: cty.String, Required: false}, 58 | "no_release": &hcldec.AttrSpec{Name: "no_release", Type: cty.Bool, Required: false}, 59 | "architecture": &hcldec.AttrSpec{Name: "architecture", Type: cty.String, Required: false}, 60 | "default_architecture": &hcldec.AttrSpec{Name: "default_architecture", Type: cty.String, Required: false}, 61 | "access_token": &hcldec.AttrSpec{Name: "access_token", Type: cty.String, Required: false}, 62 | "vagrant_cloud_url": &hcldec.AttrSpec{Name: "vagrant_cloud_url", Type: cty.String, Required: false}, 63 | "insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false}, 64 | "box_download_url": &hcldec.AttrSpec{Name: "box_download_url", Type: cty.String, Required: false}, 65 | "no_direct_upload": &hcldec.AttrSpec{Name: "no_direct_upload", Type: cty.Bool, Required: false}, 66 | "box_checksum": &hcldec.AttrSpec{Name: "box_checksum", Type: cty.String, Required: false}, 67 | } 68 | return s 69 | } 70 | -------------------------------------------------------------------------------- /post-processor/vagrant-cloud/step_confirm_upload.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrantcloud 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/packer-plugin-sdk/multistep" 11 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 12 | ) 13 | 14 | type stepConfirmUpload struct { 15 | } 16 | 17 | func (s *stepConfirmUpload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 18 | client := state.Get("client").(*VagrantCloudClient) 19 | ui := state.Get("ui").(packersdk.Ui) 20 | upload := state.Get("upload").(*Upload) 21 | url := upload.CallbackPath 22 | config := state.Get("config").(*Config) 23 | 24 | if config.NoDirectUpload { 25 | return multistep.ActionContinue 26 | } 27 | 28 | ui.Say("Confirming direct box upload completion") 29 | 30 | resp, err := client.Callback(url) 31 | 32 | if err != nil || resp.StatusCode != 200 { 33 | if resp == nil || resp.Body == nil { 34 | state.Put("error", fmt.Errorf("No response from server.")) 35 | } else { 36 | cloudErrors := &VagrantCloudErrors{} 37 | err = decodeBody(resp, cloudErrors) 38 | if err != nil { 39 | ui.Error(fmt.Sprintf("error decoding error response: %s", err)) 40 | } 41 | state.Put("error", fmt.Errorf("Error preparing upload: %s", cloudErrors.FormatErrors())) 42 | } 43 | return multistep.ActionHalt 44 | } 45 | 46 | return multistep.ActionContinue 47 | } 48 | 49 | func (s *stepConfirmUpload) Cleanup(state multistep.StateBag) { 50 | // No cleanup 51 | } 52 | -------------------------------------------------------------------------------- /post-processor/vagrant-cloud/step_create_provider.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrantcloud 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "fmt" 10 | "strings" 11 | 12 | "github.com/hashicorp/packer-plugin-sdk/multistep" 13 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 14 | ) 15 | 16 | type Provider struct { 17 | Name string `json:"name"` 18 | Url string `json:"url,omitempty"` 19 | HostedToken string `json:"hosted_token,omitempty"` 20 | UploadUrl string `json:"upload_url,omitempty"` 21 | Checksum string `json:"checksum,omitempty"` 22 | ChecksumType string `json:"checksum_type,omitempty"` 23 | Architecture string `json:"architecture,omitempty"` 24 | DefaultArchitecture bool `json:"default_architecture,omitempty"` 25 | } 26 | 27 | type stepCreateProvider struct { 28 | name string // the name of the provider 29 | } 30 | 31 | func (s *stepCreateProvider) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 32 | client := state.Get("client").(*VagrantCloudClient) 33 | ui := state.Get("ui").(packersdk.Ui) 34 | box := state.Get("box").(*Box) 35 | version := state.Get("version").(*Version) 36 | providerName := state.Get("providerName").(string) 37 | downloadUrl := state.Get("boxDownloadUrl").(string) 38 | checksum := state.Get("boxChecksum").(string) 39 | architecture := state.Get("architecture").(string) 40 | defaultArchitecture := state.Get("defaultArchitecture").(bool) 41 | 42 | path := fmt.Sprintf("box/%s/version/%v/providers", box.Tag, version.Version) 43 | 44 | provider := &Provider{ 45 | Name: providerName, 46 | Architecture: architecture, 47 | DefaultArchitecture: defaultArchitecture, 48 | } 49 | 50 | if downloadUrl != "" { 51 | provider.Url = downloadUrl 52 | } 53 | 54 | if checksum != "" { 55 | checksumParts := strings.SplitN(checksum, ":", 2) 56 | if len(checksumParts) != 2 { 57 | state.Put("error", errors.New("Error parsing box_checksum: invalid format")) 58 | return multistep.ActionHalt 59 | } 60 | provider.ChecksumType = checksumParts[0] 61 | provider.Checksum = checksumParts[1] 62 | } 63 | 64 | // Wrap the provider in a provider object for the API 65 | wrapper := make(map[string]interface{}) 66 | wrapper["provider"] = provider 67 | 68 | ui.Say(fmt.Sprintf("Creating provider: %s", providerName)) 69 | 70 | resp, err := client.Post(path, wrapper) 71 | 72 | if err != nil || (resp.StatusCode != 200 && resp.StatusCode != 201) { 73 | cloudErrors := &VagrantCloudErrors{} 74 | err = decodeBody(resp, cloudErrors) 75 | if err != nil { 76 | ui.Error(fmt.Sprintf("error decoding error response: %s", err)) 77 | } 78 | state.Put("error", fmt.Errorf("Error creating provider: %s", cloudErrors.FormatErrors())) 79 | return multistep.ActionHalt 80 | } 81 | 82 | if err = decodeBody(resp, provider); err != nil { 83 | state.Put("error", fmt.Errorf("Error parsing provider response: %s", err)) 84 | return multistep.ActionHalt 85 | } 86 | 87 | // Save the name for cleanup 88 | s.name = provider.Name 89 | 90 | state.Put("provider", provider) 91 | 92 | return multistep.ActionContinue 93 | } 94 | 95 | func (s *stepCreateProvider) Cleanup(state multistep.StateBag) { 96 | client := state.Get("client").(*VagrantCloudClient) 97 | ui := state.Get("ui").(packersdk.Ui) 98 | box := state.Get("box").(*Box) 99 | version := state.Get("version").(*Version) 100 | 101 | // If we didn't save the provider name, it likely doesn't exist 102 | if s.name == "" { 103 | ui.Say("Cleaning up provider") 104 | ui.Message("Provider was not created, not deleting") 105 | return 106 | } 107 | 108 | _, cancelled := state.GetOk(multistep.StateCancelled) 109 | _, halted := state.GetOk(multistep.StateHalted) 110 | 111 | // Return if we didn't cancel or halt, and thus need 112 | // no cleanup 113 | if !cancelled && !halted { 114 | return 115 | } 116 | 117 | ui.Say("Cleaning up provider") 118 | ui.Message(fmt.Sprintf("Deleting provider: %s", s.name)) 119 | 120 | path := fmt.Sprintf("box/%s/version/%v/provider/%s", box.Tag, version.Version, s.name) 121 | 122 | // No need for resp from the cleanup DELETE 123 | _, err := client.Delete(path) 124 | 125 | if err != nil { 126 | ui.Error(fmt.Sprintf("Error destroying provider: %s", err)) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /post-processor/vagrant-cloud/step_create_version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrantcloud 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/packer-plugin-sdk/multistep" 11 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 12 | ) 13 | 14 | type Version struct { 15 | Version string `json:"version"` 16 | Description string `json:"description,omitempty"` 17 | } 18 | 19 | type stepCreateVersion struct { 20 | } 21 | 22 | func (s *stepCreateVersion) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 23 | client := state.Get("client").(*VagrantCloudClient) 24 | ui := state.Get("ui").(packersdk.Ui) 25 | config := state.Get("config").(*Config) 26 | box := state.Get("box").(*Box) 27 | 28 | ui.Say(fmt.Sprintf("Creating version: %s", config.Version)) 29 | 30 | if hasVersion, v := box.HasVersion(config.Version); hasVersion { 31 | ui.Message("Version exists, skipping creation") 32 | state.Put("version", v) 33 | return multistep.ActionContinue 34 | } 35 | 36 | path := fmt.Sprintf("box/%s/versions", box.Tag) 37 | 38 | version := &Version{Version: config.Version, Description: config.VersionDescription} 39 | 40 | // Wrap the version in a version object for the API 41 | wrapper := make(map[string]interface{}) 42 | wrapper["version"] = version 43 | 44 | resp, err := client.Post(path, wrapper) 45 | 46 | if err != nil || (resp.StatusCode != 200 && resp.StatusCode != 201) { 47 | cloudErrors := &VagrantCloudErrors{} 48 | err = decodeBody(resp, cloudErrors) 49 | if err != nil { 50 | ui.Error(fmt.Sprintf("error decoding error response: %s", err)) 51 | } 52 | state.Put("error", fmt.Errorf("Error creating version: %s", cloudErrors.FormatErrors())) 53 | return multistep.ActionHalt 54 | } 55 | 56 | if err = decodeBody(resp, version); err != nil { 57 | state.Put("error", fmt.Errorf("Error parsing version response: %s", err)) 58 | return multistep.ActionHalt 59 | } 60 | 61 | state.Put("version", version) 62 | 63 | return multistep.ActionContinue 64 | } 65 | 66 | func (s *stepCreateVersion) Cleanup(state multistep.StateBag) {} 67 | -------------------------------------------------------------------------------- /post-processor/vagrant-cloud/step_prepare_upload.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrantcloud 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "os" 10 | 11 | "github.com/hashicorp/packer-plugin-sdk/multistep" 12 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 13 | ) 14 | 15 | const VAGRANT_CLOUD_DIRECT_UPLOAD_LIMIT = 5368709120 // Upload limit is 5G 16 | 17 | type Upload struct { 18 | UploadPath string `json:"upload_path"` 19 | CallbackPath string `json:"callback"` 20 | } 21 | 22 | type stepPrepareUpload struct { 23 | } 24 | 25 | func (s *stepPrepareUpload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 26 | client := state.Get("client").(*VagrantCloudClient) 27 | config := state.Get("config").(*Config) 28 | ui := state.Get("ui").(packersdk.Ui) 29 | box := state.Get("box").(*Box) 30 | version := state.Get("version").(*Version) 31 | provider := state.Get("provider").(*Provider) 32 | artifactFilePath := state.Get("artifactFilePath").(string) 33 | 34 | // If direct upload is enabled, the asset size must be <= 5 GB 35 | if config.NoDirectUpload == false { 36 | f, err := os.Stat(artifactFilePath) 37 | if err != nil { 38 | ui.Error(fmt.Sprintf("error determining size of upload artifact: %s", artifactFilePath)) 39 | } 40 | if f.Size() > VAGRANT_CLOUD_DIRECT_UPLOAD_LIMIT { 41 | ui.Say(fmt.Sprintf("Asset %s is larger than the direct upload limit. Setting `NoDirectUpload` to true", artifactFilePath)) 42 | config.NoDirectUpload = true 43 | } 44 | } 45 | 46 | path := fmt.Sprintf("box/%s/version/%v/provider/%s/%s/upload", box.Tag, version.Version, provider.Name, provider.Architecture) 47 | if !config.NoDirectUpload { 48 | path = path + "/direct" 49 | } 50 | upload := &Upload{} 51 | 52 | ui.Say(fmt.Sprintf("Preparing upload of box: %s", artifactFilePath)) 53 | 54 | resp, err := client.Get(path) 55 | 56 | if err != nil || (resp.StatusCode != 200) { 57 | if resp == nil || resp.Body == nil { 58 | state.Put("error", fmt.Errorf("No response from server.")) 59 | } else { 60 | cloudErrors := &VagrantCloudErrors{} 61 | err = decodeBody(resp, cloudErrors) 62 | if err != nil { 63 | ui.Error(fmt.Sprintf("error decoding error response: %s", err)) 64 | } 65 | state.Put("error", fmt.Errorf("Error preparing upload: %s", cloudErrors.FormatErrors())) 66 | } 67 | return multistep.ActionHalt 68 | } 69 | 70 | if err = decodeBody(resp, upload); err != nil { 71 | state.Put("error", fmt.Errorf("Error parsing upload response: %s", err)) 72 | return multistep.ActionHalt 73 | } 74 | 75 | // Save the upload details to the state 76 | state.Put("upload", upload) 77 | 78 | return multistep.ActionContinue 79 | } 80 | 81 | func (s *stepPrepareUpload) Cleanup(state multistep.StateBag) { 82 | // No cleanup 83 | } 84 | -------------------------------------------------------------------------------- /post-processor/vagrant-cloud/step_release_version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrantcloud 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "strings" 10 | 11 | "github.com/hashicorp/packer-plugin-sdk/multistep" 12 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 13 | ) 14 | 15 | type stepReleaseVersion struct { 16 | } 17 | 18 | func (s *stepReleaseVersion) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 19 | client := state.Get("client").(*VagrantCloudClient) 20 | ui := state.Get("ui").(packersdk.Ui) 21 | box := state.Get("box").(*Box) 22 | version := state.Get("version").(*Version) 23 | config := state.Get("config").(*Config) 24 | 25 | ui.Say(fmt.Sprintf("Releasing version: %s", version.Version)) 26 | 27 | if config.NoRelease { 28 | ui.Message("Not releasing version due to configuration") 29 | return multistep.ActionContinue 30 | } 31 | 32 | path := fmt.Sprintf("box/%s/version/%v/release", box.Tag, version.Version) 33 | 34 | resp, err := client.Put(path) 35 | 36 | if err != nil || (resp.StatusCode != 200) { 37 | cloudErrors := &VagrantCloudErrors{} 38 | if err := decodeBody(resp, cloudErrors); err != nil { 39 | state.Put("error", fmt.Errorf("Error parsing provider response: %s", err)) 40 | return multistep.ActionHalt 41 | } 42 | if strings.Contains(cloudErrors.FormatErrors(), "already been released") { 43 | ui.Message("Not releasing version, already released") 44 | return multistep.ActionContinue 45 | } 46 | state.Put("error", fmt.Errorf("Error releasing version: %s", cloudErrors.FormatErrors())) 47 | return multistep.ActionHalt 48 | } 49 | 50 | ui.Message("Version successfully released and available") 51 | 52 | return multistep.ActionContinue 53 | } 54 | 55 | func (s *stepReleaseVersion) Cleanup(state multistep.StateBag) { 56 | // No cleanup 57 | } 58 | -------------------------------------------------------------------------------- /post-processor/vagrant-cloud/step_upload.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrantcloud 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "log" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/hashicorp/packer-plugin-sdk/multistep" 14 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 15 | "github.com/hashicorp/packer-plugin-sdk/retry" 16 | ) 17 | 18 | type stepUpload struct { 19 | } 20 | 21 | func (s *stepUpload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 22 | client := state.Get("client").(*VagrantCloudClient) 23 | config := state.Get("config").(*Config) 24 | ui := state.Get("ui").(packersdk.Ui) 25 | upload := state.Get("upload").(*Upload) 26 | artifactFilePath := state.Get("artifactFilePath").(string) 27 | url := upload.UploadPath 28 | 29 | ui.Say(fmt.Sprintf("Uploading box: %s", artifactFilePath)) 30 | ui.Message( 31 | "Depending on your internet connection and the size of the box,\n" + 32 | "this may take some time") 33 | 34 | err := retry.Config{ 35 | Tries: 3, 36 | RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 10 * time.Second, Multiplier: 2}).Linear, 37 | }.Run(ctx, func(ctx context.Context) error { 38 | ui.Message("Uploading box") 39 | 40 | var err error 41 | var resp *http.Response 42 | 43 | if config.NoDirectUpload { 44 | resp, err = client.Upload(artifactFilePath, url) 45 | } else { 46 | resp, err = client.DirectUpload(artifactFilePath, url) 47 | } 48 | if err != nil { 49 | ui.Message(fmt.Sprintf( 50 | "Error uploading box! Will retry in 10 seconds. Error: %s", err)) 51 | return err 52 | } 53 | if resp.StatusCode != 200 { 54 | err := fmt.Errorf("bad HTTP status: %d", resp.StatusCode) 55 | log.Print(err) 56 | ui.Message(fmt.Sprintf( 57 | "Error uploading box! Will retry in 10 seconds. Status: %d", 58 | resp.StatusCode)) 59 | return err 60 | } 61 | return err 62 | }) 63 | 64 | if err != nil { 65 | state.Put("error", err) 66 | return multistep.ActionHalt 67 | } 68 | 69 | ui.Message("Box successfully uploaded") 70 | 71 | return multistep.ActionContinue 72 | } 73 | 74 | func (s *stepUpload) Cleanup(state multistep.StateBag) { 75 | // No cleanup 76 | } 77 | -------------------------------------------------------------------------------- /post-processor/vagrant-cloud/step_verify_box.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrantcloud 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/packer-plugin-sdk/multistep" 11 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 12 | ) 13 | 14 | type Box struct { 15 | Tag string `json:"tag"` 16 | Versions []*Version `json:"versions"` 17 | } 18 | 19 | func (b *Box) HasVersion(version string) (bool, *Version) { 20 | for _, v := range b.Versions { 21 | if v.Version == version { 22 | return true, v 23 | } 24 | } 25 | return false, nil 26 | } 27 | 28 | type stepVerifyBox struct { 29 | } 30 | 31 | func (s *stepVerifyBox) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 32 | client := state.Get("client").(*VagrantCloudClient) 33 | ui := state.Get("ui").(packersdk.Ui) 34 | config := state.Get("config").(*Config) 35 | 36 | ui.Say(fmt.Sprintf("Verifying box is accessible: %s", config.Tag)) 37 | 38 | path := fmt.Sprintf("box/%s", config.Tag) 39 | resp, err := client.Get(path) 40 | 41 | if err != nil { 42 | state.Put("error", fmt.Errorf("Error retrieving box: %s", err)) 43 | return multistep.ActionHalt 44 | } 45 | 46 | if resp.StatusCode != 200 { 47 | cloudErrors := &VagrantCloudErrors{} 48 | err = decodeBody(resp, cloudErrors) 49 | if err != nil { 50 | ui.Error(fmt.Sprintf("error decoding error response: %s", err)) 51 | } 52 | state.Put("error", fmt.Errorf("Error retrieving box: %s", cloudErrors.FormatErrors())) 53 | return multistep.ActionHalt 54 | } 55 | 56 | box := &Box{} 57 | 58 | if err = decodeBody(resp, box); err != nil { 59 | state.Put("error", fmt.Errorf("Error parsing box response: %s", err)) 60 | return multistep.ActionHalt 61 | } 62 | 63 | if box.Tag != config.Tag { 64 | state.Put("error", fmt.Errorf("Could not verify box: %s", config.Tag)) 65 | return multistep.ActionHalt 66 | } 67 | 68 | ui.Message("Box accessible and matches tag") 69 | 70 | // Keep the box in state for later 71 | state.Put("box", box) 72 | 73 | // Box exists and is accessible 74 | return multistep.ActionContinue 75 | } 76 | 77 | func (s *stepVerifyBox) Cleanup(state multistep.StateBag) { 78 | // no cleanup needed 79 | } 80 | -------------------------------------------------------------------------------- /post-processor/vagrant/artifact.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | ) 10 | 11 | const BuilderId = "mitchellh.post-processor.vagrant" 12 | 13 | type Artifact struct { 14 | Path string 15 | Provider string 16 | } 17 | 18 | func NewArtifact(provider, path string) *Artifact { 19 | return &Artifact{ 20 | Path: path, 21 | Provider: provider, 22 | } 23 | } 24 | 25 | func (*Artifact) BuilderId() string { 26 | return BuilderId 27 | } 28 | 29 | func (a *Artifact) Files() []string { 30 | return []string{a.Path} 31 | } 32 | 33 | func (a *Artifact) Id() string { 34 | return a.Provider 35 | } 36 | 37 | func (a *Artifact) String() string { 38 | return fmt.Sprintf("'%s' provider box: %s", a.Provider, a.Path) 39 | } 40 | 41 | func (a *Artifact) State(name string) interface{} { 42 | return nil 43 | } 44 | 45 | func (a *Artifact) Destroy() error { 46 | return os.Remove(a.Path) 47 | } 48 | -------------------------------------------------------------------------------- /post-processor/vagrant/artifact_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "testing" 8 | 9 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 10 | ) 11 | 12 | func TestArtifact_ImplementsArtifact(t *testing.T) { 13 | var raw interface{} 14 | raw = &Artifact{} 15 | if _, ok := raw.(packersdk.Artifact); !ok { 16 | t.Fatalf("Artifact should be a Artifact") 17 | } 18 | } 19 | 20 | func TestArtifact_Id(t *testing.T) { 21 | artifact := NewArtifact("vmware", "./") 22 | if artifact.Id() != "vmware" { 23 | t.Fatalf("should return name as Id") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /post-processor/vagrant/aws.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "strings" 10 | "text/template" 11 | 12 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 13 | ) 14 | 15 | type AWSProvider struct{} 16 | 17 | func (p *AWSProvider) KeepInputArtifact() bool { 18 | return true 19 | } 20 | 21 | func (p *AWSProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { 22 | // Create the metadata 23 | metadata = map[string]interface{}{"provider": "aws"} 24 | 25 | // Build up the template data to build our Vagrantfile 26 | tplData := &awsVagrantfileTemplate{ 27 | Images: make(map[string]string), 28 | } 29 | 30 | for _, regions := range strings.Split(artifact.Id(), ",") { 31 | parts := strings.Split(regions, ":") 32 | if len(parts) != 2 { 33 | err = fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id()) 34 | return 35 | } 36 | 37 | tplData.Images[parts[0]] = parts[1] 38 | } 39 | 40 | // Build up the contents 41 | var contents bytes.Buffer 42 | t := template.Must(template.New("vf").Parse(defaultAWSVagrantfile)) 43 | err = t.Execute(&contents, tplData) 44 | vagrantfile = contents.String() 45 | return 46 | } 47 | 48 | type awsVagrantfileTemplate struct { 49 | Images map[string]string 50 | } 51 | 52 | var defaultAWSVagrantfile = ` 53 | Vagrant.configure("2") do |config| 54 | config.vm.provider "aws" do |aws| 55 | {{ range $region, $ami := .Images }} 56 | aws.region_config "{{ $region }}", ami: "{{ $ami }}" 57 | {{ end }} 58 | end 59 | end 60 | ` 61 | -------------------------------------------------------------------------------- /post-processor/vagrant/aws_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "strings" 8 | "testing" 9 | 10 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 11 | ) 12 | 13 | func TestAWSProvider_impl(t *testing.T) { 14 | var _ Provider = new(AWSProvider) 15 | } 16 | 17 | func TestAWSProvider_KeepInputArtifact(t *testing.T) { 18 | p := new(AWSProvider) 19 | 20 | if !p.KeepInputArtifact() { 21 | t.Fatal("should keep input artifact") 22 | } 23 | } 24 | 25 | func TestAWSProvider_ArtifactId(t *testing.T) { 26 | p := new(AWSProvider) 27 | ui := testUi() 28 | artifact := &packersdk.MockArtifact{ 29 | IdValue: "us-east-1:ami-1234", 30 | } 31 | 32 | vagrantfile, _, err := p.Process(ui, artifact, "foo") 33 | if err != nil { 34 | t.Fatalf("should not have error: %s", err) 35 | } 36 | result := `aws.region_config "us-east-1", ami: "ami-1234"` 37 | if !strings.Contains(vagrantfile, result) { 38 | t.Fatalf("wrong substitution: %s", vagrantfile) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /post-processor/vagrant/azure.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | 10 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 11 | ) 12 | 13 | type AzureProvider struct{} 14 | 15 | func (p *AzureProvider) KeepInputArtifact() bool { 16 | return true 17 | } 18 | 19 | func (p *AzureProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { 20 | // Create the metadata 21 | metadata = map[string]interface{}{"provider": "azure"} 22 | 23 | var AzureImageProps map[string]string 24 | AzureImageProps = make(map[string]string) 25 | 26 | // HACK(double16): It appears we can not access the Azure Artifact directly, so parse String() 27 | artifactString := artifact.String() 28 | ui.Message(fmt.Sprintf("artifact string: '%s'", artifactString)) 29 | lines := strings.Split(artifactString, "\n") 30 | for l := 0; l < len(lines); l++ { 31 | split := strings.Split(lines[l], ": ") 32 | if len(split) > 1 { 33 | AzureImageProps[strings.TrimSpace(split[0])] = strings.TrimSpace(split[1]) 34 | } 35 | } 36 | ui.Message(fmt.Sprintf("artifact string parsed: %+v", AzureImageProps)) 37 | 38 | if AzureImageProps["ManagedImageId"] != "" { 39 | vagrantfile = fmt.Sprintf(managedImageVagrantfile, AzureImageProps["ManagedImageLocation"], AzureImageProps["ManagedImageId"]) 40 | } else if AzureImageProps["OSDiskUri"] != "" { 41 | vagrantfile = fmt.Sprintf(vhdVagrantfile, AzureImageProps["StorageAccountLocation"], AzureImageProps["OSDiskUri"], AzureImageProps["OSType"]) 42 | } else { 43 | err = fmt.Errorf("No managed image nor VHD URI found in artifact: %s", artifactString) 44 | return 45 | } 46 | return 47 | } 48 | 49 | var managedImageVagrantfile = ` 50 | Vagrant.configure("2") do |config| 51 | config.vm.provider :azure do |azure, override| 52 | azure.location = "%s" 53 | azure.vm_managed_image_id = "%s" 54 | override.winrm.transport = :ssl 55 | override.winrm.port = 5986 56 | end 57 | end 58 | ` 59 | 60 | var vhdVagrantfile = ` 61 | Vagrant.configure("2") do |config| 62 | config.vm.provider :azure do |azure, override| 63 | azure.location = "%s" 64 | azure.vm_vhd_uri = "%s" 65 | azure.vm_operating_system = "%s" 66 | override.winrm.transport = :ssl 67 | override.winrm.port = 5986 68 | end 69 | end 70 | ` 71 | -------------------------------------------------------------------------------- /post-processor/vagrant/azure_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "strings" 8 | "testing" 9 | 10 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 11 | ) 12 | 13 | func TestAzureProvider_impl(t *testing.T) { 14 | var _ Provider = new(AzureProvider) 15 | } 16 | 17 | func TestAzureProvider_KeepInputArtifact(t *testing.T) { 18 | p := new(AzureProvider) 19 | 20 | if !p.KeepInputArtifact() { 21 | t.Fatal("should keep input artifact") 22 | } 23 | } 24 | 25 | func TestAzureProvider_ManagedImage(t *testing.T) { 26 | p := new(AzureProvider) 27 | ui := testUi() 28 | artifact := &packersdk.MockArtifact{ 29 | StringValue: `Azure.ResourceManagement.VMImage: 30 | 31 | OSType: Linux 32 | ManagedImageResourceGroupName: packerruns 33 | ManagedImageName: packer-1533651633 34 | ManagedImageId: /subscriptions/e6229913-d9c3-4ddd-99a4-9e1ef3beaa1b/resourceGroups/packerruns/providers/Microsoft.Compute/images/packer-1533675589 35 | ManagedImageLocation: westus`, 36 | } 37 | 38 | vagrantfile, _, err := p.Process(ui, artifact, "foo") 39 | if err != nil { 40 | t.Fatalf("should not have error: %s", err) 41 | } 42 | result := `azure.location = "westus"` 43 | if !strings.Contains(vagrantfile, result) { 44 | t.Fatalf("wrong substitution: %s", vagrantfile) 45 | } 46 | result = `azure.vm_managed_image_id = "/subscriptions/e6229913-d9c3-4ddd-99a4-9e1ef3beaa1b/resourceGroups/packerruns/providers/Microsoft.Compute/images/packer-1533675589"` 47 | if !strings.Contains(vagrantfile, result) { 48 | t.Fatalf("wrong substitution: %s", vagrantfile) 49 | } 50 | // DO NOT set resource group in Vagrantfile, it should be separate from the image 51 | result = `azure.resource_group_name` 52 | if strings.Contains(vagrantfile, result) { 53 | t.Fatalf("wrong substitution: %s", vagrantfile) 54 | } 55 | result = `azure.vm_operating_system` 56 | if strings.Contains(vagrantfile, result) { 57 | t.Fatalf("wrong substitution: %s", vagrantfile) 58 | } 59 | } 60 | 61 | func TestAzureProvider_VHD(t *testing.T) { 62 | p := new(AzureProvider) 63 | ui := testUi() 64 | artifact := &packersdk.MockArtifact{ 65 | IdValue: "https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd", 66 | StringValue: `Azure.ResourceManagement.VMImage: 67 | 68 | OSType: Linux 69 | StorageAccountLocation: westus 70 | OSDiskUri: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd 71 | OSDiskUriReadOnlySas: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd?se=2018-09-07T18%3A36%3A34Z&sig=xUiFvwAviPYoP%2Bc91vErqvwYR1eK4x%2BAx7YLMe84zzU%3D&sp=r&sr=b&sv=2016-05-31 72 | TemplateUri: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.96ed2120-591d-4900-95b0-ee8e985f2213.json 73 | TemplateUriReadOnlySas: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.96ed2120-591d-4900-95b0-ee8e985f2213.json?se=2018-09-07T18%3A36%3A34Z&sig=lDxePyAUCZbfkB5ddiofimXfwk5INn%2F9E2BsnqIKC9Q%3D&sp=r&sr=b&sv=2016-05-31`, 74 | } 75 | 76 | vagrantfile, _, err := p.Process(ui, artifact, "foo") 77 | if err != nil { 78 | t.Fatalf("should not have error: %s", err) 79 | } 80 | result := `azure.location = "westus"` 81 | if !strings.Contains(vagrantfile, result) { 82 | t.Fatalf("wrong substitution: %s", vagrantfile) 83 | } 84 | result = `azure.vm_vhd_uri = "https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd"` 85 | if !strings.Contains(vagrantfile, result) { 86 | t.Fatalf("wrong substitution: %s", vagrantfile) 87 | } 88 | result = `azure.vm_operating_system = "Linux"` 89 | if !strings.Contains(vagrantfile, result) { 90 | t.Fatalf("wrong substitution: %s", vagrantfile) 91 | } 92 | // DO NOT set resource group in Vagrantfile, it should be separate from the image 93 | result = `azure.resource_group_name` 94 | if strings.Contains(vagrantfile, result) { 95 | t.Fatalf("wrong substitution: %s", vagrantfile) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /post-processor/vagrant/digitalocean.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "strings" 10 | "text/template" 11 | 12 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 13 | ) 14 | 15 | type digitalOceanVagrantfileTemplate struct { 16 | Image string "" 17 | Region string "" 18 | } 19 | 20 | type DigitalOceanProvider struct{} 21 | 22 | func (p *DigitalOceanProvider) KeepInputArtifact() bool { 23 | return true 24 | } 25 | 26 | func (p *DigitalOceanProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { 27 | // Create the metadata 28 | metadata = map[string]interface{}{"provider": "digital_ocean"} 29 | 30 | // Determine the image and region... 31 | tplData := &digitalOceanVagrantfileTemplate{} 32 | 33 | parts := strings.Split(artifact.Id(), ":") 34 | if len(parts) != 2 { 35 | err = fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id()) 36 | return 37 | } 38 | tplData.Region = parts[0] 39 | tplData.Image = parts[1] 40 | 41 | // Build up the Vagrantfile 42 | var contents bytes.Buffer 43 | t := template.Must(template.New("vf").Parse(defaultDigitalOceanVagrantfile)) 44 | err = t.Execute(&contents, tplData) 45 | vagrantfile = contents.String() 46 | return 47 | } 48 | 49 | var defaultDigitalOceanVagrantfile = ` 50 | Vagrant.configure("2") do |config| 51 | config.vm.provider :digital_ocean do |digital_ocean| 52 | digital_ocean.image = "{{ .Image }}" 53 | digital_ocean.region = "{{ .Region }}" 54 | end 55 | end 56 | ` 57 | -------------------------------------------------------------------------------- /post-processor/vagrant/digitalocean_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "strings" 8 | "testing" 9 | 10 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 11 | ) 12 | 13 | func TestDigitalOceanProvider_impl(t *testing.T) { 14 | var _ Provider = new(DigitalOceanProvider) 15 | } 16 | 17 | func TestDigitalOceanProvider_KeepInputArtifact(t *testing.T) { 18 | p := new(DigitalOceanProvider) 19 | 20 | if !p.KeepInputArtifact() { 21 | t.Fatal("should keep input artifact") 22 | } 23 | } 24 | 25 | func TestDigitalOceanProvider_ArtifactId(t *testing.T) { 26 | p := new(DigitalOceanProvider) 27 | ui := testUi() 28 | artifact := &packersdk.MockArtifact{ 29 | IdValue: "San Francisco:42", 30 | } 31 | 32 | vagrantfile, _, err := p.Process(ui, artifact, "foo") 33 | if err != nil { 34 | t.Fatalf("should not have error: %s", err) 35 | } 36 | image := `digital_ocean.image = "42"` 37 | if !strings.Contains(vagrantfile, image) { 38 | t.Fatalf("wrong image substitution: %s", vagrantfile) 39 | } 40 | region := `digital_ocean.region = "San Francisco"` 41 | if !strings.Contains(vagrantfile, region) { 42 | t.Fatalf("wrong region substitution: %s", vagrantfile) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /post-processor/vagrant/docker.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "fmt" 8 | 9 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 10 | ) 11 | 12 | type DockerProvider struct{} 13 | 14 | func (p *DockerProvider) KeepInputArtifact() bool { 15 | return false 16 | } 17 | 18 | func (p *DockerProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { 19 | // Create the metadata 20 | metadata = map[string]interface{}{"provider": "docker"} 21 | 22 | vagrantfile = fmt.Sprintf(dockerVagrantfile, artifact.Id()) 23 | return 24 | } 25 | 26 | var dockerVagrantfile = ` 27 | Vagrant.configure("2") do |config| 28 | config.vm.provider :docker do |docker, override| 29 | docker.image = "%s" 30 | end 31 | end 32 | ` 33 | -------------------------------------------------------------------------------- /post-processor/vagrant/docker_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestDockerProvider_impl(t *testing.T) { 11 | var _ Provider = new(DockerProvider) 12 | } 13 | -------------------------------------------------------------------------------- /post-processor/vagrant/file.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "fmt" 8 | "path/filepath" 9 | 10 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 11 | ) 12 | 13 | type FileProvider struct{} 14 | 15 | func (p *FileProvider) KeepInputArtifact() bool { 16 | return false 17 | } 18 | 19 | func (p *FileProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { 20 | // Create the metadata 21 | metadata = map[string]interface{}{"provider": "file"} 22 | 23 | // Copy all of the original contents into the temporary directory 24 | for _, path := range artifact.Files() { 25 | ui.Message(fmt.Sprintf("Copying: %s", path)) 26 | 27 | dstPath := filepath.Join(dir, filepath.Base(path)) 28 | if err = CopyContents(dstPath, path); err != nil { 29 | return 30 | } 31 | } 32 | 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /post-processor/vagrant/file_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestFileProvider_impl(t *testing.T) { 11 | var _ Provider = new(FileProvider) 12 | } 13 | -------------------------------------------------------------------------------- /post-processor/vagrant/google.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "bytes" 8 | "text/template" 9 | 10 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 11 | ) 12 | 13 | type googleVagrantfileTemplate struct { 14 | Image string "" 15 | } 16 | 17 | type GoogleProvider struct{} 18 | 19 | func (p *GoogleProvider) KeepInputArtifact() bool { 20 | return true 21 | } 22 | 23 | func (p *GoogleProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { 24 | // Create the metadata 25 | metadata = map[string]interface{}{"provider": "google"} 26 | 27 | // Build up the template data to build our Vagrantfile 28 | tplData := &googleVagrantfileTemplate{} 29 | tplData.Image = artifact.Id() 30 | 31 | // Build up the Vagrantfile 32 | var contents bytes.Buffer 33 | t := template.Must(template.New("vf").Parse(defaultGoogleVagrantfile)) 34 | err = t.Execute(&contents, tplData) 35 | vagrantfile = contents.String() 36 | return 37 | } 38 | 39 | var defaultGoogleVagrantfile = ` 40 | Vagrant.configure("2") do |config| 41 | config.vm.provider :google do |google| 42 | google.image = "{{ .Image }}" 43 | end 44 | end 45 | ` 46 | -------------------------------------------------------------------------------- /post-processor/vagrant/google_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "strings" 8 | "testing" 9 | 10 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 11 | ) 12 | 13 | func TestGoogleProvider_impl(t *testing.T) { 14 | var _ Provider = new(GoogleProvider) 15 | } 16 | 17 | func TestGoogleProvider_KeepInputArtifact(t *testing.T) { 18 | p := new(GoogleProvider) 19 | 20 | if !p.KeepInputArtifact() { 21 | t.Fatal("should keep input artifact") 22 | } 23 | } 24 | 25 | func TestGoogleProvider_ArtifactId(t *testing.T) { 26 | p := new(GoogleProvider) 27 | ui := testUi() 28 | artifact := &packersdk.MockArtifact{ 29 | IdValue: "packer-1234", 30 | } 31 | 32 | vagrantfile, _, err := p.Process(ui, artifact, "foo") 33 | if err != nil { 34 | t.Fatalf("should not have error: %s", err) 35 | } 36 | result := `google.image = "packer-1234"` 37 | if !strings.Contains(vagrantfile, result) { 38 | t.Fatalf("wrong substitution: %s", vagrantfile) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /post-processor/vagrant/hyperv.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | 12 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 13 | ) 14 | 15 | type HypervProvider struct{} 16 | 17 | func (p *HypervProvider) KeepInputArtifact() bool { 18 | return false 19 | } 20 | 21 | func (p *HypervProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { 22 | // Create the metadata 23 | metadata = map[string]interface{}{"provider": "hyperv"} 24 | 25 | // ui.Message(fmt.Sprintf("artifacts all: %+v", artifact)) 26 | var outputDir string 27 | 28 | // Vargant requires specific dir structure for hyperv 29 | // hyperv builder creates the structure in the output dir 30 | // we have to keep the structure in a temp dir 31 | // hack little bit but string in artifact usually have output dir 32 | artifactString := artifact.String() 33 | d := strings.Split(artifactString, ": ") 34 | outputDir = d[1] 35 | // ui.Message(fmt.Sprintf("artifact dir from string: %s", outputDir)) 36 | 37 | // Copy all of the original contents into the temporary directory 38 | for _, path := range artifact.Files() { 39 | ui.Message(fmt.Sprintf("Copying: %s", path)) 40 | 41 | var rel string 42 | 43 | rel, err = filepath.Rel(outputDir, filepath.Dir(path)) 44 | // ui.Message(fmt.Sprintf("rel is: %s", rel)) 45 | 46 | if err != nil { 47 | ui.Message(fmt.Sprintf("err in: %s", rel)) 48 | return 49 | } 50 | 51 | dstDir := filepath.Join(dir, rel) 52 | // ui.Message(fmt.Sprintf("dstdir is: %s", dstDir)) 53 | if _, err = os.Stat(dstDir); err != nil { 54 | if err = os.MkdirAll(dstDir, 0755); err != nil { 55 | ui.Message(fmt.Sprintf("err in creating: %s", dstDir)) 56 | return 57 | } 58 | } 59 | 60 | dstPath := filepath.Join(dstDir, filepath.Base(path)) 61 | 62 | // We prefer to link the files where possible because they are often very huge. 63 | // Some filesystem configurations do not allow hardlinks. As the possibilities 64 | // of mounting different devices in different paths are flexible, we just try to 65 | // link the file and copy if the link fails, thereby automatically optimizing with a safe fallback. 66 | if err = LinkFile(dstPath, path); err != nil { 67 | // ui.Message(fmt.Sprintf("err in linking: %s to %s", path, dstPath)) 68 | if err = CopyContents(dstPath, path); err != nil { 69 | ui.Message(fmt.Sprintf("err in copying: %s to %s", path, dstPath)) 70 | return 71 | } 72 | } 73 | 74 | ui.Message(fmt.Sprintf("Copied %s to %s", path, dstPath)) 75 | } 76 | 77 | return 78 | } 79 | -------------------------------------------------------------------------------- /post-processor/vagrant/libvirt.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "fmt" 8 | "path/filepath" 9 | "strconv" 10 | "strings" 11 | 12 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 13 | ) 14 | 15 | // Lowercase a ascii letter. 16 | func lower(c byte) byte { 17 | return c | ('a' - 'A') 18 | } 19 | 20 | // Convert a string that represents a qemu disk image size to megabytes. 21 | // 22 | // Valid units (case-insensitive): 23 | // 24 | // B (byte) 1B 25 | // K (kilobyte) 1024B 26 | // M (megabyte) 1024K 27 | // G (gigabyte) 1024M 28 | // T (terabyte) 1024G 29 | // P (petabyte) 1024T 30 | // E (exabyte) 1024P 31 | // 32 | // The default is M. 33 | func sizeInMegabytes(size string) uint64 { 34 | unit := size[len(size)-1] 35 | 36 | if unit >= '0' && unit <= '9' { 37 | unit = 'm' 38 | } else { 39 | size = size[:len(size)-1] 40 | } 41 | 42 | value, _ := strconv.ParseUint(size, 10, 64) 43 | 44 | switch lower(unit) { 45 | case 'b': 46 | return value / 1024 / 1024 47 | case 'k': 48 | return value / 1024 49 | case 'm': 50 | return value 51 | case 'g': 52 | return value * 1024 53 | case 't': 54 | return value * 1024 * 1024 55 | case 'p': 56 | return value * 1024 * 1024 * 1024 57 | case 'e': 58 | return value * 1024 * 1024 * 1024 * 1024 59 | default: 60 | panic(fmt.Sprintf("Unknown size unit %c", unit)) 61 | } 62 | } 63 | 64 | type LibVirtProvider struct{} 65 | 66 | func (p *LibVirtProvider) KeepInputArtifact() bool { 67 | return false 68 | } 69 | func (p *LibVirtProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { 70 | disks := []map[string]string{} 71 | format := artifact.State("diskType").(string) 72 | diskName := artifact.State("diskName").(string) 73 | disk_index := 0 74 | for _, path := range artifact.Files() { 75 | // DiskName is [vmName, vmName-1, vmName-2] 76 | if !strings.HasPrefix(filepath.Base(path), diskName) { 77 | continue 78 | } 79 | ui.Message(fmt.Sprintf("Copying from artifact: %s", path)) 80 | dstDiskName := fmt.Sprintf("box_%d.img", disk_index) 81 | dstPath := filepath.Join(dir, dstDiskName) 82 | disks = append(disks, map[string]string{ 83 | "path": dstDiskName, 84 | "format": format, 85 | }) 86 | disk_index++ 87 | if err = CopyContents(dstPath, path); err != nil { 88 | return 89 | } 90 | } 91 | 92 | domainType := artifact.State("domainType").(string) 93 | 94 | // Convert domain type to libvirt driver 95 | var driver string 96 | switch domainType { 97 | case "none", "tcg", "hvf": 98 | driver = "qemu" 99 | case "kvm": 100 | driver = domainType 101 | default: 102 | return "", nil, fmt.Errorf("Unknown libvirt domain type: %s", domainType) 103 | } 104 | 105 | // Create the metadata 106 | metadata = map[string]interface{}{ 107 | "provider": "libvirt", 108 | "disks": disks, 109 | } 110 | 111 | vagrantfile = fmt.Sprintf(libvirtVagrantfile, driver) 112 | return 113 | } 114 | 115 | var libvirtVagrantfile = ` 116 | Vagrant.configure("2") do |config| 117 | config.vm.provider :libvirt do |libvirt| 118 | libvirt.driver = "%s" 119 | end 120 | end 121 | ` 122 | -------------------------------------------------------------------------------- /post-processor/vagrant/libvirt_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "testing" 10 | 11 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 12 | "github.com/hashicorp/packer-plugin-sdk/tmp" 13 | ) 14 | 15 | func assertSizeInMegabytes(t *testing.T, size string, expected uint64) { 16 | actual := sizeInMegabytes(size) 17 | if actual != expected { 18 | t.Fatalf("the size `%s` was converted to `%d` but expected `%d`", size, actual, expected) 19 | } 20 | } 21 | 22 | func Test_sizeInMegabytes_WithInvalidUnitMustPanic(t *testing.T) { 23 | defer func() { 24 | if r := recover(); r == nil { 25 | t.Fatalf("expected a panic but got none") 26 | } 27 | }() 28 | 29 | sizeInMegabytes("1234x") 30 | } 31 | 32 | func Test_sizeInMegabytes_WithoutUnitMustDefaultToMegabytes(t *testing.T) { 33 | assertSizeInMegabytes(t, "1234", 1234) 34 | } 35 | 36 | func Test_sizeInMegabytes_WithBytesUnit(t *testing.T) { 37 | assertSizeInMegabytes(t, fmt.Sprintf("%db", 1234*1024*1024), 1234) 38 | assertSizeInMegabytes(t, fmt.Sprintf("%dB", 1234*1024*1024), 1234) 39 | assertSizeInMegabytes(t, "1B", 0) 40 | } 41 | 42 | func Test_sizeInMegabytes_WithKiloBytesUnit(t *testing.T) { 43 | assertSizeInMegabytes(t, fmt.Sprintf("%dk", 1234*1024), 1234) 44 | assertSizeInMegabytes(t, fmt.Sprintf("%dK", 1234*1024), 1234) 45 | assertSizeInMegabytes(t, "1K", 0) 46 | } 47 | 48 | func Test_sizeInMegabytes_WithMegabytesUnit(t *testing.T) { 49 | assertSizeInMegabytes(t, "1234m", 1234) 50 | assertSizeInMegabytes(t, "1234M", 1234) 51 | assertSizeInMegabytes(t, "1M", 1) 52 | } 53 | 54 | func Test_sizeInMegabytes_WithGigabytesUnit(t *testing.T) { 55 | assertSizeInMegabytes(t, "1234g", 1234*1024) 56 | assertSizeInMegabytes(t, "1234G", 1234*1024) 57 | assertSizeInMegabytes(t, "1G", 1*1024) 58 | } 59 | 60 | func Test_sizeInMegabytes_WithTerabytesUnit(t *testing.T) { 61 | assertSizeInMegabytes(t, "1234t", 1234*1024*1024) 62 | assertSizeInMegabytes(t, "1234T", 1234*1024*1024) 63 | assertSizeInMegabytes(t, "1T", 1*1024*1024) 64 | } 65 | 66 | func Test_sizeInMegabytes_WithPetabytesUnit(t *testing.T) { 67 | assertSizeInMegabytes(t, "1234p", 1234*1024*1024*1024) 68 | assertSizeInMegabytes(t, "1234P", 1234*1024*1024*1024) 69 | assertSizeInMegabytes(t, "1P", 1*1024*1024*1024) 70 | } 71 | 72 | func Test_sizeInMegabytes_WithExabytesUnit(t *testing.T) { 73 | assertSizeInMegabytes(t, "1234e", 1234*1024*1024*1024*1024) 74 | assertSizeInMegabytes(t, "1234E", 1234*1024*1024*1024*1024) 75 | assertSizeInMegabytes(t, "1E", 1*1024*1024*1024*1024) 76 | } 77 | 78 | func Test_ManyFilesInArtifact(t *testing.T) { 79 | p := new(LibVirtProvider) 80 | ui := testUi() 81 | type testCases struct { 82 | Files []string 83 | Format string 84 | FilesExpected []string 85 | } 86 | testcases := []testCases{ 87 | { 88 | []string{}, 89 | "qcow2", 90 | []string{}, 91 | }, 92 | { 93 | []string{"test"}, 94 | "vmdk", 95 | []string{"box_0.img"}, 96 | }, 97 | { 98 | []string{"test", "test-1", "test-2"}, 99 | "qcow2", 100 | []string{"box_0.img", "box_1.img", "box_2.img"}, 101 | }, 102 | { 103 | []string{"test", "efivars.fd", "test-1", "test-2"}, 104 | "qcow2", 105 | []string{"box_0.img", "box_1.img", "box_2.img"}, 106 | }, 107 | } 108 | for _, tc := range testcases { 109 | dir, _ := tmp.Dir("pkr") 110 | defer os.RemoveAll(dir) 111 | 112 | artifactFiles := []string{} 113 | for _, file := range tc.Files { 114 | fullFilePath := fmt.Sprintf("%s/%s", dir, file) 115 | artifactFiles = append(artifactFiles, fullFilePath) 116 | _, err := os.Create(fullFilePath) 117 | if err != nil { 118 | t.Fatalf("Can't create %s : %s", fullFilePath, err) 119 | } 120 | } 121 | 122 | artifact := &packersdk.MockArtifact{ 123 | FilesValue: artifactFiles, 124 | StateValues: map[string]interface{}{ 125 | "diskType": tc.Format, 126 | "diskSize": "1234M", 127 | "diskName": "test", 128 | "domainType": "kvm", 129 | }, 130 | } 131 | 132 | dirProcess, _ := tmp.Dir("process") 133 | defer os.RemoveAll(dirProcess) 134 | _, metadata, err := p.Process(ui, artifact, dirProcess) 135 | 136 | if err != nil { 137 | t.Fatalf("should not have error: %s", err) 138 | } 139 | metaDisks := metadata["disks"].([]map[string]string) 140 | if len(tc.FilesExpected) != len(metaDisks) { 141 | t.Errorf("Expected %d disks, but test returned %d", len(tc.FilesExpected), len(metaDisks)) 142 | } 143 | 144 | for i, disk := range metaDisks { 145 | if tc.FilesExpected[i] != disk["path"] { 146 | t.Errorf("%s. Expected %#v", "Disk files order must be respected", tc.FilesExpected[i]) 147 | } 148 | if tc.Format != disk["format"] { 149 | t.Errorf("%s. Expected %#v", "Disk files format must be present", tc.Format) 150 | } 151 | } 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /post-processor/vagrant/lxc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "fmt" 8 | "path/filepath" 9 | 10 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 11 | ) 12 | 13 | type LXCProvider struct{} 14 | 15 | func (p *LXCProvider) KeepInputArtifact() bool { 16 | return false 17 | } 18 | 19 | func (p *LXCProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { 20 | // Create the metadata 21 | metadata = map[string]interface{}{ 22 | "provider": "lxc", 23 | "version": "1.0.0", 24 | } 25 | 26 | // Copy all of the original contents into the temporary directory 27 | for _, path := range artifact.Files() { 28 | ui.Message(fmt.Sprintf("Copying: %s", path)) 29 | 30 | dstPath := filepath.Join(dir, filepath.Base(path)) 31 | if err = CopyContents(dstPath, path); err != nil { 32 | return 33 | } 34 | } 35 | 36 | return 37 | } 38 | -------------------------------------------------------------------------------- /post-processor/vagrant/lxc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestLXCProvider_impl(t *testing.T) { 11 | var _ Provider = new(LXCProvider) 12 | } 13 | -------------------------------------------------------------------------------- /post-processor/vagrant/parallels.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "fmt" 8 | "path/filepath" 9 | "regexp" 10 | 11 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 12 | ) 13 | 14 | // These are the extensions of files and directories that are unnecessary for the function 15 | // of a Parallels virtual machine. 16 | var UnnecessaryFilesPatterns = []string{"\\.log$", "\\.backup$", "\\.Backup$", "\\.app/", "/Windows Disks/"} 17 | 18 | type ParallelsProvider struct{} 19 | 20 | func (p *ParallelsProvider) KeepInputArtifact() bool { 21 | return false 22 | } 23 | 24 | func (p *ParallelsProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { 25 | // Create the metadata 26 | metadata = map[string]interface{}{"provider": "parallels"} 27 | 28 | var copied int 29 | 30 | // Copy all of the original contents into the temporary directory 31 | for _, path := range artifact.Files() { 32 | // If the file isn't critical to the function of the 33 | // virtual machine, we get rid of it. 34 | unnecessary := false 35 | for _, unnecessaryPat := range UnnecessaryFilesPatterns { 36 | if matched, _ := regexp.MatchString(unnecessaryPat, path); matched { 37 | unnecessary = true 38 | break 39 | } 40 | } 41 | if unnecessary { 42 | continue 43 | } 44 | 45 | tmpPath := filepath.ToSlash(path) 46 | pathRe := regexp.MustCompile(`[^/]+\.(pvm|macvm)/.+$`) 47 | match := pathRe.FindString(tmpPath) 48 | var pvmPath string 49 | if match != "" { 50 | pvmPath = filepath.FromSlash(match) 51 | } else { 52 | continue // Just copy a pvm 53 | } 54 | dstPath := filepath.Join(dir, pvmPath) 55 | 56 | ui.Message(fmt.Sprintf("Copying: %s", path)) 57 | if err = CopyContents(dstPath, path); err != nil { 58 | return 59 | } 60 | copied++ 61 | } 62 | 63 | if copied == 0 { 64 | err = fmt.Errorf("No VM file found in source artifact") 65 | } 66 | 67 | return 68 | } 69 | -------------------------------------------------------------------------------- /post-processor/vagrant/parallels_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "io/ioutil" 10 | "math/rand" 11 | "os" 12 | "testing" 13 | 14 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 15 | ) 16 | 17 | func TestParallelsProvider_impl(t *testing.T) { 18 | var _ Provider = new(ParallelsProvider) 19 | } 20 | 21 | // mockParallelsVMDir creates a fake temp dir for parallels testing 22 | // 23 | // Note: the path to the pvm/macvm dir is returned, the responsibility to remove 24 | // it befalls the caller. 25 | func mockParallelsVMDir() ([]string, error) { 26 | tmpDir := fmt.Sprintf("%s/%d.pvm", os.TempDir(), rand.Uint32()) 27 | err := os.MkdirAll(tmpDir, 0755) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | file1, err := os.CreateTemp(tmpDir, "") 33 | if err != nil { 34 | os.RemoveAll(tmpDir) 35 | return nil, err 36 | } 37 | file1.Close() 38 | 39 | file2, err := os.CreateTemp(tmpDir, "") 40 | if err != nil { 41 | os.RemoveAll(tmpDir) 42 | return nil, err 43 | } 44 | file2.Close() 45 | 46 | return []string{ 47 | tmpDir, 48 | file1.Name(), 49 | file2.Name(), 50 | }, nil 51 | } 52 | 53 | func TestPostProcessorPostProcessParallels(t *testing.T) { 54 | var p PostProcessor 55 | 56 | inputVM, err := mockParallelsVMDir() 57 | if err != nil { 58 | t.Fatalf("failed to create parallels VM directory") 59 | } 60 | dir := inputVM[0] 61 | defer os.RemoveAll(dir) 62 | 63 | f, err := ioutil.TempFile("", "packer") 64 | if err != nil { 65 | t.Fatalf("err: %s", err) 66 | } 67 | defer os.Remove(f.Name()) 68 | 69 | c := map[string]interface{}{ 70 | "packer_user_variables": map[string]string{ 71 | "foo": f.Name(), 72 | }, 73 | 74 | "vagrantfile_template": "{{user `foo`}}", 75 | } 76 | err = p.Configure(c) 77 | if err != nil { 78 | t.Fatalf("err: %s", err) 79 | } 80 | 81 | a := &packersdk.MockArtifact{ 82 | BuilderIdValue: "packer.parallels", 83 | FilesValue: inputVM, 84 | } 85 | a2, _, _, err := p.PostProcess(context.Background(), testUi(), a) 86 | if a2 != nil { 87 | for _, fn := range a2.Files() { 88 | defer os.Remove(fn) 89 | } 90 | } 91 | if err != nil { 92 | t.Fatalf("err: %s", err) 93 | } 94 | } 95 | 96 | func TestPostProcessorPostProcessParallels_NoFileErrorOnCopy(t *testing.T) { 97 | var p PostProcessor 98 | 99 | c := map[string]interface{}{} 100 | err := p.Configure(c) 101 | if err != nil { 102 | t.Fatalf("err: %s", err) 103 | } 104 | 105 | a := &packersdk.MockArtifact{ 106 | BuilderIdValue: "packer.parallels", 107 | } 108 | a2, _, _, err := p.PostProcess(context.Background(), testUi(), a) 109 | if a2 != nil { 110 | for _, fn := range a2.Files() { 111 | defer os.Remove(fn) 112 | } 113 | } 114 | if err == nil { 115 | t.Fatalf("should have failed without a file to copy, succeeded instead") 116 | } 117 | t.Logf("failed as expected: %s", err) 118 | } 119 | -------------------------------------------------------------------------------- /post-processor/vagrant/post-processor.hcl2spec.go: -------------------------------------------------------------------------------- 1 | // Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT. 2 | 3 | package vagrant 4 | 5 | import ( 6 | "github.com/hashicorp/hcl/v2/hcldec" 7 | "github.com/zclconf/go-cty/cty" 8 | ) 9 | 10 | // FlatConfig is an auto-generated flat version of Config. 11 | // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. 12 | type FlatConfig struct { 13 | PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` 14 | PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` 15 | PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` 16 | PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` 17 | PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` 18 | PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` 19 | PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` 20 | PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` 21 | CompressionLevel *int `mapstructure:"compression_level" cty:"compression_level" hcl:"compression_level"` 22 | Include []string `mapstructure:"include" cty:"include" hcl:"include"` 23 | OutputPath *string `mapstructure:"output" cty:"output" hcl:"output"` 24 | Override map[string]interface{} `cty:"override" hcl:"override"` 25 | VagrantfileTemplate *string `mapstructure:"vagrantfile_template" cty:"vagrantfile_template" hcl:"vagrantfile_template"` 26 | VagrantfileTemplateGenerated *bool `mapstructure:"vagrantfile_template_generated" cty:"vagrantfile_template_generated" hcl:"vagrantfile_template_generated"` 27 | ProviderOverride *string `mapstructure:"provider_override" cty:"provider_override" hcl:"provider_override"` 28 | Architecture *string `mapstructure:"architecture" cty:"architecture" hcl:"architecture"` 29 | } 30 | 31 | // FlatMapstructure returns a new FlatConfig. 32 | // FlatConfig is an auto-generated flat version of Config. 33 | // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. 34 | func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { 35 | return new(FlatConfig) 36 | } 37 | 38 | // HCL2Spec returns the hcl spec of a Config. 39 | // This spec is used by HCL to read the fields of Config. 40 | // The decoded values from this spec will then be applied to a FlatConfig. 41 | func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { 42 | s := map[string]hcldec.Spec{ 43 | "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, 44 | "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, 45 | "packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false}, 46 | "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, 47 | "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, 48 | "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, 49 | "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, 50 | "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, 51 | "compression_level": &hcldec.AttrSpec{Name: "compression_level", Type: cty.Number, Required: false}, 52 | "include": &hcldec.AttrSpec{Name: "include", Type: cty.List(cty.String), Required: false}, 53 | "output": &hcldec.AttrSpec{Name: "output", Type: cty.String, Required: false}, 54 | "override": &hcldec.AttrSpec{Name: "override", Type: cty.Map(cty.String), Required: false}, 55 | "vagrantfile_template": &hcldec.AttrSpec{Name: "vagrantfile_template", Type: cty.String, Required: false}, 56 | "vagrantfile_template_generated": &hcldec.AttrSpec{Name: "vagrantfile_template_generated", Type: cty.Bool, Required: false}, 57 | "provider_override": &hcldec.AttrSpec{Name: "provider_override", Type: cty.String, Required: false}, 58 | "architecture": &hcldec.AttrSpec{Name: "architecture", Type: cty.String, Required: false}, 59 | } 60 | return s 61 | } 62 | -------------------------------------------------------------------------------- /post-processor/vagrant/post-processor_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "bytes" 8 | "compress/flate" 9 | "context" 10 | "io/ioutil" 11 | "os" 12 | "runtime" 13 | "strings" 14 | "testing" 15 | 16 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 17 | ) 18 | 19 | func testConfig() map[string]interface{} { 20 | return map[string]interface{}{} 21 | } 22 | 23 | func testPP(t *testing.T) *PostProcessor { 24 | var p PostProcessor 25 | if err := p.Configure(testConfig()); err != nil { 26 | t.Fatalf("err: %s", err) 27 | } 28 | 29 | return &p 30 | } 31 | 32 | func testUi() *packersdk.BasicUi { 33 | return &packersdk.BasicUi{ 34 | Reader: new(bytes.Buffer), 35 | Writer: new(bytes.Buffer), 36 | } 37 | } 38 | 39 | func TestPostProcessor_ImplementsPostProcessor(t *testing.T) { 40 | var _ packersdk.PostProcessor = new(PostProcessor) 41 | } 42 | 43 | func TestPostProcessorPrepare_compressionLevel(t *testing.T) { 44 | var p PostProcessor 45 | 46 | // Default 47 | c := testConfig() 48 | delete(c, "compression_level") 49 | if err := p.Configure(c); err != nil { 50 | t.Fatalf("err: %s", err) 51 | } 52 | 53 | config := p.config 54 | if config.CompressionLevel != flate.DefaultCompression { 55 | t.Fatalf("bad: %#v", config.CompressionLevel) 56 | } 57 | 58 | // Set 59 | c = testConfig() 60 | c["compression_level"] = 7 61 | if err := p.Configure(c); err != nil { 62 | t.Fatalf("err: %s", err) 63 | } 64 | 65 | config = p.config 66 | if config.CompressionLevel != 7 { 67 | t.Fatalf("bad: %#v", config.CompressionLevel) 68 | } 69 | } 70 | 71 | func TestPostProcessorPrepare_architecture(t *testing.T) { 72 | var p PostProcessor 73 | 74 | matchingArch := runtime.GOARCH 75 | if mappedArch, ok := vagrantArchMap[matchingArch]; ok { 76 | matchingArch = mappedArch 77 | } 78 | 79 | // Default 80 | c := testConfig() 81 | delete(c, "architecture") 82 | if err := p.Configure(c); err != nil { 83 | t.Fatalf("err: %s", err) 84 | } 85 | 86 | config := p.config 87 | if config.Architecture != matchingArch { 88 | t.Fatalf("bad: %#v", config.Architecture) 89 | } 90 | 91 | // Set 92 | c = testConfig() 93 | c["architecture"] = "s390x" 94 | if err := p.Configure(c); err != nil { 95 | t.Fatalf("err: %s", err) 96 | } 97 | 98 | config = p.config 99 | if config.Architecture != "s390x" { 100 | t.Fatalf("bad: %#v", config.Architecture) 101 | } 102 | 103 | } 104 | 105 | func TestPostProcessorPrepare_outputPath(t *testing.T) { 106 | var p PostProcessor 107 | 108 | // Default 109 | c := testConfig() 110 | delete(c, "output") 111 | err := p.Configure(c) 112 | if err != nil { 113 | t.Fatalf("err: %s", err) 114 | } 115 | 116 | // Bad template 117 | c["output"] = "bad {{{{.Template}}}}" 118 | err = p.Configure(c) 119 | if err == nil { 120 | t.Fatal("should have error") 121 | } 122 | } 123 | 124 | func TestSpecificConfig(t *testing.T) { 125 | var p PostProcessor 126 | 127 | // Default 128 | c := testConfig() 129 | c["compression_level"] = 1 130 | c["output"] = "folder" 131 | c["override"] = map[string]interface{}{ 132 | "aws": map[string]interface{}{ 133 | "compression_level": 7, 134 | }, 135 | } 136 | if err := p.Configure(c); err != nil { 137 | t.Fatalf("err: %s", err) 138 | } 139 | 140 | // overrides config 141 | config, err := p.specificConfig("aws") 142 | if err != nil { 143 | t.Fatalf("err: %s", err) 144 | } 145 | 146 | if config.CompressionLevel != 7 { 147 | t.Fatalf("bad: %#v", config.CompressionLevel) 148 | } 149 | 150 | if config.OutputPath != "folder" { 151 | t.Fatalf("bad: %#v", config.OutputPath) 152 | } 153 | 154 | // does NOT overrides config 155 | config, err = p.specificConfig("virtualbox") 156 | if err != nil { 157 | t.Fatalf("err: %s", err) 158 | } 159 | 160 | if config.CompressionLevel != 1 { 161 | t.Fatalf("bad: %#v", config.CompressionLevel) 162 | } 163 | 164 | if config.OutputPath != "folder" { 165 | t.Fatalf("bad: %#v", config.OutputPath) 166 | } 167 | } 168 | 169 | func TestPostProcessorPrepare_vagrantfileTemplateExists(t *testing.T) { 170 | f, err := ioutil.TempFile("", "packer") 171 | if err != nil { 172 | t.Fatalf("err: %s", err) 173 | } 174 | 175 | name := f.Name() 176 | c := testConfig() 177 | c["vagrantfile_template"] = name 178 | 179 | if err := f.Close(); err != nil { 180 | t.Fatalf("err: %s", err) 181 | } 182 | 183 | var p PostProcessor 184 | 185 | if err := p.Configure(c); err != nil { 186 | t.Fatal("no error expected as vagrantfile_template exists") 187 | } 188 | 189 | if err := os.Remove(name); err != nil { 190 | t.Fatalf("err: %s", err) 191 | } 192 | 193 | if err := p.Configure(c); err == nil { 194 | t.Fatal("expected error since vagrantfile_template does not exist and vagrantfile_template_generated is unset") 195 | } 196 | 197 | // The vagrantfile_template will be generated during the build process 198 | c["vagrantfile_template_generated"] = true 199 | 200 | if err := p.Configure(c); err != nil { 201 | t.Fatal("no error expected due to missing vagrantfile_template as vagrantfile_template_generated is set") 202 | } 203 | } 204 | 205 | func TestPostProcessorPrepare_ProviderOverrideExists(t *testing.T) { 206 | c := testConfig() 207 | c["provider_override"] = "foo" 208 | 209 | var p PostProcessor 210 | 211 | if err := p.Configure(c); err == nil { 212 | t.Fatal("Should have errored since foo is not a valid vagrant provider") 213 | } 214 | 215 | c = testConfig() 216 | c["provider_override"] = "aws" 217 | 218 | if err := p.Configure(c); err != nil { 219 | t.Fatal("Should not have errored since aws is a valid vagrant provider") 220 | } 221 | } 222 | 223 | func TestPostProcessorPostProcess_badId(t *testing.T) { 224 | artifact := &packersdk.MockArtifact{ 225 | BuilderIdValue: "invalid.packer", 226 | } 227 | 228 | _, _, _, err := testPP(t).PostProcess(context.Background(), testUi(), artifact) 229 | if !strings.Contains(err.Error(), "artifact type") { 230 | t.Fatalf("err: %s", err) 231 | } 232 | } 233 | 234 | func TestProviderForName(t *testing.T) { 235 | if v, ok := providerForName("virtualbox").(*VBoxProvider); !ok { 236 | t.Fatalf("bad: %#v", v) 237 | } 238 | 239 | if providerForName("nope") != nil { 240 | t.Fatal("should be nil if bad provider") 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /post-processor/vagrant/provider.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 8 | ) 9 | 10 | // Provider is the interface that each provider must implement in order 11 | // to package the artifacts into a Vagrant-compatible box. 12 | type Provider interface { 13 | // KeepInputArtifact should return true/false whether this provider 14 | // requires the input artifact to be kept by default. 15 | KeepInputArtifact() bool 16 | 17 | // Process is called to process an artifact into a Vagrant box. The 18 | // artifact is given as well as the temporary directory path to 19 | // put things. 20 | // 21 | // The Provider should return the contents for the Vagrantfile, 22 | // any metadata (including the provider type in that), and an error 23 | // if any. 24 | Process(packersdk.Ui, packersdk.Artifact, string) (vagrantfile string, metadata map[string]interface{}, err error) 25 | } 26 | -------------------------------------------------------------------------------- /post-processor/vagrant/scaleway.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "strings" 10 | "text/template" 11 | 12 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 13 | ) 14 | 15 | type scalewayVagrantfileTemplate struct { 16 | Image string "" 17 | Region string "" 18 | } 19 | 20 | type ScalewayProvider struct{} 21 | 22 | func (p *ScalewayProvider) KeepInputArtifact() bool { 23 | return true 24 | } 25 | 26 | func (p *ScalewayProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { 27 | // Create the metadata 28 | metadata = map[string]interface{}{"provider": "scaleway"} 29 | 30 | // Determine the image and region... 31 | tplData := &scalewayVagrantfileTemplate{} 32 | 33 | parts := strings.Split(artifact.Id(), ":") 34 | if len(parts) != 2 { 35 | err = fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id()) 36 | return 37 | } 38 | tplData.Region = parts[0] 39 | tplData.Image = parts[1] 40 | 41 | // Build up the Vagrantfile 42 | var contents bytes.Buffer 43 | t := template.Must(template.New("vf").Parse(defaultScalewayVagrantfile)) 44 | err = t.Execute(&contents, tplData) 45 | vagrantfile = contents.String() 46 | return 47 | } 48 | 49 | var defaultScalewayVagrantfile = ` 50 | Vagrant.configure("2") do |config| 51 | config.vm.provider :scaleway do |scaleway| 52 | scaleway.image = "{{ .Image }}" 53 | scaleway.region = "{{ .Region }}" 54 | end 55 | end 56 | ` 57 | -------------------------------------------------------------------------------- /post-processor/vagrant/tar_fix.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | //go:build !go1.10 5 | // +build !go1.10 6 | 7 | package vagrant 8 | 9 | import "archive/tar" 10 | 11 | func setHeaderFormat(header *tar.Header) { 12 | // no-op 13 | } 14 | -------------------------------------------------------------------------------- /post-processor/vagrant/tar_fix_go110.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | //go:build go1.10 5 | // +build go1.10 6 | 7 | package vagrant 8 | 9 | import "archive/tar" 10 | 11 | func setHeaderFormat(header *tar.Header) { 12 | // We have to set the Format explicitly because of a bug in 13 | // libarchive. This affects eg. the tar in macOS listing huge 14 | // files with zero byte length. 15 | header.Format = tar.FormatGNU 16 | } 17 | -------------------------------------------------------------------------------- /post-processor/vagrant/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "archive/tar" 8 | "compress/flate" 9 | "encoding/json" 10 | "fmt" 11 | "io" 12 | "log" 13 | "os" 14 | "path/filepath" 15 | "runtime" 16 | 17 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 18 | "github.com/hashicorp/packer-plugin-sdk/tmp" 19 | "github.com/klauspost/pgzip" 20 | ) 21 | 22 | var ( 23 | // ErrInvalidCompressionLevel is returned when the compression level passed 24 | // to gzip is not in the expected range. See compress/flate for details. 25 | ErrInvalidCompressionLevel = fmt.Errorf( 26 | "Invalid compression level. Expected an integer from -1 to 9.") 27 | ) 28 | 29 | // Copies a file by copying the contents of the file to another place. 30 | func CopyContents(dst, src string) error { 31 | srcF, err := os.Open(src) 32 | if err != nil { 33 | return err 34 | } 35 | defer srcF.Close() 36 | 37 | dstDir, _ := filepath.Split(dst) 38 | if dstDir != "" { 39 | err := os.MkdirAll(dstDir, 0755) 40 | if err != nil { 41 | return err 42 | } 43 | } 44 | 45 | dstF, err := os.Create(dst) 46 | if err != nil { 47 | return err 48 | } 49 | defer dstF.Close() 50 | 51 | if _, err := io.Copy(dstF, srcF); err != nil { 52 | return err 53 | } 54 | 55 | return nil 56 | } 57 | 58 | // Creates a (hard) link to a file, ensuring that all parent directories also exist. 59 | func LinkFile(dst, src string) error { 60 | dstDir, _ := filepath.Split(dst) 61 | if dstDir != "" { 62 | err := os.MkdirAll(dstDir, 0755) 63 | if err != nil { 64 | return err 65 | } 66 | } 67 | 68 | if err := os.Link(src, dst); err != nil { 69 | return err 70 | } 71 | 72 | return nil 73 | } 74 | 75 | // DirToBox takes the directory and compresses it into a Vagrant-compatible 76 | // box. This function does not perform checks to verify that dir is 77 | // actually a proper box. This is an expected precondition. 78 | func DirToBox(dst, dir string, ui packersdk.Ui, level int) error { 79 | log.Printf("Turning dir into box: %s => %s", dir, dst) 80 | 81 | // Make the containing directory, if it does not already exist 82 | err := os.MkdirAll(filepath.Dir(dst), 0755) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | dstF, err := os.Create(dst) 88 | if err != nil { 89 | return err 90 | } 91 | defer dstF.Close() 92 | 93 | var dstWriter io.WriteCloser = dstF 94 | if level != flate.NoCompression { 95 | log.Printf("Compressing with gzip compression level: %d", level) 96 | gzipWriter, err := makePgzipWriter(dstWriter, level) 97 | if err != nil { 98 | return err 99 | } 100 | defer gzipWriter.Close() 101 | 102 | dstWriter = gzipWriter 103 | } 104 | 105 | tarWriter := tar.NewWriter(dstWriter) 106 | defer tarWriter.Close() 107 | 108 | // This is the walk func that tars each of the files in the dir 109 | tarWalk := func(path string, info os.FileInfo, prevErr error) error { 110 | // If there was a prior error, return it 111 | if prevErr != nil { 112 | return prevErr 113 | } 114 | 115 | // Skip directories 116 | if info.IsDir() { 117 | log.Printf("Skipping directory '%s' for box '%s'", path, dst) 118 | return nil 119 | } 120 | 121 | log.Printf("Box add: '%s' to '%s'", path, dst) 122 | f, err := os.Open(path) 123 | if err != nil { 124 | return err 125 | } 126 | defer f.Close() 127 | 128 | header, err := tar.FileInfoHeader(info, "") 129 | if err != nil { 130 | return err 131 | } 132 | 133 | // go >=1.10 wants to use GNU tar format to workaround issues in 134 | // libarchive < 3.3.2 135 | setHeaderFormat(header) 136 | 137 | // We have to set the Name explicitly because it is supposed to 138 | // be a relative path to the root. Otherwise, the tar ends up 139 | // being a bunch of files in the root, even if they're actually 140 | // nested in a dir in the original "dir" param. 141 | header.Name, err = filepath.Rel(dir, path) 142 | if err != nil { 143 | return err 144 | } 145 | 146 | if ui != nil { 147 | ui.Message(fmt.Sprintf("Compressing: %s", header.Name)) 148 | } 149 | 150 | if err := tarWriter.WriteHeader(header); err != nil { 151 | return err 152 | } 153 | 154 | if _, err := io.Copy(tarWriter, f); err != nil { 155 | return err 156 | } 157 | 158 | return nil 159 | } 160 | 161 | // Tar.gz everything up 162 | return filepath.Walk(dir, tarWalk) 163 | } 164 | 165 | // CreateDummyBox create a dummy Vagrant-compatible box under temporary dir 166 | // This function is mainly used to check cases such as the host system having 167 | // a GNU tar incompatible uname that will cause the actual Vagrant box creation 168 | // to fail later 169 | func CreateDummyBox(ui packersdk.Ui, level int) error { 170 | ui.Say("Creating a dummy Vagrant box to ensure the host system can create one correctly") 171 | 172 | // Create a temporary dir to create dummy Vagrant box from 173 | tempDir, err := tmp.Dir("packer") 174 | if err != nil { 175 | return err 176 | } 177 | defer os.RemoveAll(tempDir) 178 | 179 | // Write some dummy metadata for the box 180 | if err := WriteMetadata(tempDir, make(map[string]string)); err != nil { 181 | return err 182 | } 183 | 184 | // Create the dummy Vagrant box 185 | tempBox, err := tmp.File("box-*.box") 186 | if err != nil { 187 | return err 188 | } 189 | defer tempBox.Close() 190 | defer os.Remove(tempBox.Name()) 191 | if err := DirToBox(tempBox.Name(), tempDir, nil, level); err != nil { 192 | return err 193 | } 194 | 195 | return nil 196 | } 197 | 198 | // WriteMetadata writes the "metadata.json" file for a Vagrant box. 199 | func WriteMetadata(dir string, contents interface{}) error { 200 | if _, err := os.Stat(filepath.Join(dir, "metadata.json")); os.IsNotExist(err) { 201 | f, err := os.Create(filepath.Join(dir, "metadata.json")) 202 | if err != nil { 203 | return err 204 | } 205 | defer f.Close() 206 | 207 | enc := json.NewEncoder(f) 208 | return enc.Encode(contents) 209 | } 210 | 211 | return nil 212 | } 213 | 214 | func makePgzipWriter(output io.WriteCloser, compressionLevel int) (io.WriteCloser, error) { 215 | gzipWriter, err := pgzip.NewWriterLevel(output, compressionLevel) 216 | if err != nil { 217 | return nil, ErrInvalidCompressionLevel 218 | } 219 | _ = gzipWriter.SetConcurrency(500000, runtime.GOMAXPROCS(-1)) 220 | return gzipWriter, nil 221 | } 222 | -------------------------------------------------------------------------------- /post-processor/vagrant/virtualbox.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "archive/tar" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "log" 13 | "os" 14 | "path/filepath" 15 | "regexp" 16 | 17 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 18 | ) 19 | 20 | type VBoxProvider struct{} 21 | 22 | func (p *VBoxProvider) KeepInputArtifact() bool { 23 | return false 24 | } 25 | 26 | func (p *VBoxProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { 27 | // Create the metadata 28 | metadata = map[string]interface{}{"provider": "virtualbox"} 29 | 30 | // Copy all of the original contents into the temporary directory 31 | for _, path := range artifact.Files() { 32 | // We treat OVA files specially, we unpack those into the temporary 33 | // directory so we can get the resulting disk and OVF. 34 | if extension := filepath.Ext(path); extension == ".ova" { 35 | ui.Message(fmt.Sprintf("Unpacking OVA: %s", path)) 36 | if err = DecompressOva(dir, path); err != nil { 37 | return 38 | } 39 | } else { 40 | ui.Message(fmt.Sprintf("Copying from artifact: %s", path)) 41 | dstPath := filepath.Join(dir, filepath.Base(path)) 42 | if err = CopyContents(dstPath, path); err != nil { 43 | return 44 | } 45 | } 46 | 47 | } 48 | 49 | // Rename the OVF file to box.ovf, as required by Vagrant 50 | ui.Message("Renaming the OVF to box.ovf...") 51 | if err = p.renameOVF(dir); err != nil { 52 | return 53 | } 54 | 55 | // Create the Vagrantfile from the template 56 | var baseMacAddress string 57 | baseMacAddress, err = p.findBaseMacAddress(dir) 58 | if err != nil { 59 | return 60 | } 61 | 62 | vagrantfile = fmt.Sprintf(vboxVagrantfile, baseMacAddress) 63 | return 64 | } 65 | 66 | func (p *VBoxProvider) findOvf(dir string) (string, error) { 67 | log.Println("Looking for OVF in artifact...") 68 | file_matches, err := filepath.Glob(filepath.Join(dir, "*.ovf")) 69 | if err != nil { 70 | return "", err 71 | } 72 | 73 | if len(file_matches) > 1 { 74 | return "", errors.New("More than one OVF file in VirtualBox artifact.") 75 | } 76 | 77 | if len(file_matches) < 1 { 78 | return "", errors.New("ovf file couldn't be found") 79 | } 80 | 81 | return file_matches[0], err 82 | } 83 | 84 | func (p *VBoxProvider) renameOVF(dir string) error { 85 | log.Println("Looking for OVF to rename...") 86 | ovf, err := p.findOvf(dir) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | log.Printf("Renaming: '%s' => box.ovf", ovf) 92 | return os.Rename(ovf, filepath.Join(dir, "box.ovf")) 93 | } 94 | 95 | func (p *VBoxProvider) findBaseMacAddress(dir string) (string, error) { 96 | log.Println("Looking for OVF for base mac address...") 97 | ovf, err := p.findOvf(dir) 98 | if err != nil { 99 | return "", err 100 | } 101 | 102 | f, err := os.Open(ovf) 103 | if err != nil { 104 | return "", err 105 | } 106 | defer f.Close() 107 | 108 | data, err := ioutil.ReadAll(f) 109 | if err != nil { 110 | return "", err 111 | } 112 | 113 | re := regexp.MustCompile(` %s", src, dir) 127 | srcF, err := os.Open(src) 128 | if err != nil { 129 | return err 130 | } 131 | defer srcF.Close() 132 | 133 | tarReader := tar.NewReader(srcF) 134 | for { 135 | hdr, err := tarReader.Next() 136 | if hdr == nil || err == io.EOF { 137 | break 138 | } 139 | if err != nil { 140 | return err 141 | } 142 | 143 | // We use the fileinfo to get the file name because we are not 144 | // expecting path information as from the tar header. It's important 145 | // that we not use the path name from the tar header without checking 146 | // for the presence of `..`. If we accidentally allow for that, we can 147 | // open ourselves up to a path traversal vulnerability. 148 | info := hdr.FileInfo() 149 | 150 | // Shouldn't be any directories, skip them 151 | if info.IsDir() { 152 | continue 153 | } 154 | 155 | // We wrap this in an anonymous function so that the defers 156 | // inside are handled more quickly so we can give up file handles. 157 | err = func() error { 158 | path := filepath.Join(dir, info.Name()) 159 | output, err := os.Create(path) 160 | if err != nil { 161 | return err 162 | } 163 | defer output.Close() 164 | 165 | _ = os.Chmod(path, info.Mode()) 166 | _ = os.Chtimes(path, hdr.AccessTime, hdr.ModTime) 167 | _, err = io.Copy(output, tarReader) 168 | return err 169 | }() 170 | if err != nil { 171 | return err 172 | } 173 | } 174 | 175 | return nil 176 | } 177 | 178 | var vboxVagrantfile = ` 179 | Vagrant.configure("2") do |config| 180 | config.vm.base_mac = "%s" 181 | end 182 | ` 183 | -------------------------------------------------------------------------------- /post-processor/vagrant/virtualbox_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "os" 8 | "path/filepath" 9 | "testing" 10 | 11 | "github.com/hashicorp/packer-plugin-sdk/tmp" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestVBoxProvider_impl(t *testing.T) { 16 | var _ Provider = new(VBoxProvider) 17 | } 18 | 19 | func TestDecomressOVA(t *testing.T) { 20 | td, err := tmp.Dir("pp-vagrant-virtualbox") 21 | assert.NoError(t, err) 22 | defer os.RemoveAll(td) 23 | 24 | fixture := "./test-fixtures/decompress-tar/outside_parent.tar" 25 | err = DecompressOva(td, fixture) 26 | assert.NoError(t, err) 27 | _, err = os.Stat(filepath.Join(filepath.Base(td), "demo.poc")) 28 | assert.Error(t, err) 29 | _, err = os.Stat(filepath.Join(td, "demo.poc")) 30 | assert.NoError(t, err) 31 | } 32 | -------------------------------------------------------------------------------- /post-processor/vagrant/vmware.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "fmt" 8 | "path/filepath" 9 | 10 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 11 | ) 12 | 13 | type VMwareProvider struct{} 14 | 15 | func (p *VMwareProvider) KeepInputArtifact() bool { 16 | return false 17 | } 18 | 19 | func (p *VMwareProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { 20 | // Create the metadata 21 | metadata = map[string]interface{}{"provider": "vmware_desktop"} 22 | 23 | // Copy all of the original contents into the temporary directory 24 | for _, path := range artifact.Files() { 25 | ui.Message(fmt.Sprintf("Copying: %s", path)) 26 | 27 | dstPath := filepath.Join(dir, filepath.Base(path)) 28 | if err = CopyContents(dstPath, path); err != nil { 29 | return 30 | } 31 | } 32 | 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /post-processor/vagrant/vmware_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package vagrant 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestVMwareProvider_impl(t *testing.T) { 11 | var _ Provider = new(VMwareProvider) 12 | } 13 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package version 5 | 6 | import "github.com/hashicorp/packer-plugin-sdk/version" 7 | 8 | var ( 9 | // Version is the main version number that is being run at the moment. 10 | Version = "1.1.6" 11 | 12 | // VersionPrerelease is A pre-release marker for the Version. If this is "" 13 | // (empty string) then it means that it is a final release. Otherwise, this 14 | // is a pre-release such as "dev" (in development), "beta", "rc1", etc. 15 | VersionPrerelease = "dev" 16 | 17 | // PluginVersion is used by the plugin set to allow Packer to recognize 18 | // what version this plugin is. 19 | PluginVersion = version.InitializePluginVersion(Version, VersionPrerelease) 20 | ) 21 | --------------------------------------------------------------------------------