├── .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 │ ├── acceptance-tests.yaml │ ├── build_plugin_binaries.yml │ ├── go-test-darwin.yml │ ├── go-test-linux.yml │ ├── go-test-windows.yml │ ├── go-validate.yml │ ├── jira.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 │ │ ├── arm │ │ │ └── README.md │ │ ├── chroot │ │ │ └── README.md │ │ └── dtl │ │ │ └── README.md │ └── provisioner │ │ └── dtlartifact │ │ └── README.md ├── metadata.hcl └── scripts │ └── compile-to-webdocs.sh ├── CHANGELOG.md ├── CODEOWNERS ├── GNUmakefile ├── LICENSE ├── README.md ├── builder └── azure │ ├── LICENSE │ ├── arm │ ├── artifact.go │ ├── artifact_test.go │ ├── azure_client.go │ ├── azure_error_response.go │ ├── azure_error_response_test.TestAzureErrorNestedShouldFormat.approved.txt │ ├── azure_error_response_test.TestAzureErrorSimpleShouldFormat.approved.txt │ ├── azure_error_response_test.go │ ├── builder.go │ ├── builder_acc_test.go │ ├── builder_test.go │ ├── config.go │ ├── config.hcl2spec.go │ ├── config_test.go │ ├── openssh_key_pair.go │ ├── openssh_key_pair_test.go │ ├── resource_resolver.go │ ├── resource_resolver_test.go │ ├── step.go │ ├── step_capture_image.go │ ├── step_capture_image_test.go │ ├── step_certificate_in_keyvault.go │ ├── step_certificate_in_keyvault_test.go │ ├── step_create_resource_group.go │ ├── step_create_resource_group_test.go │ ├── step_deploy_template.go │ ├── step_deploy_template_test.go │ ├── step_get_additional_disks.go │ ├── step_get_additional_disks_test.go │ ├── step_get_certificate.go │ ├── step_get_certificate_test.go │ ├── step_get_ip_address.go │ ├── step_get_ip_address_test.go │ ├── step_get_os_disk.go │ ├── step_get_os_disk_test.go │ ├── step_get_source_image_name.go │ ├── step_get_source_image_name_test.go │ ├── step_power_off_compute.go │ ├── step_power_off_compute_test.go │ ├── step_publish_to_shared_image_gallery.go │ ├── step_publish_to_shared_image_gallery_test.go │ ├── step_set_certificate.go │ ├── step_set_certificate_test.go │ ├── step_set_generated_data.go │ ├── step_snapshot_data_disks.go │ ├── step_snapshot_data_disks_test.go │ ├── step_snapshot_os_disk.go │ ├── step_snapshot_os_disk_test.go │ ├── step_test.go │ ├── step_validate_template.go │ ├── step_validate_template_test.go │ ├── template_factory.go │ ├── template_factory_test.TestBasicSkuPublicIPVMDeployment.approved.json │ ├── template_factory_test.TestConfidentialVM01.approved.json │ ├── template_factory_test.TestConfidentialVM02.approved.json │ ├── template_factory_test.TestConfidentialVM03.approved.json │ ├── template_factory_test.TestEncryptionAtHost01.approved.json │ ├── template_factory_test.TestEncryptionAtHost02.approved.json │ ├── template_factory_test.TestKeyVaultDeployment03.approved.json │ ├── template_factory_test.TestKeyVaultDeployment04.approved.json │ ├── template_factory_test.TestPlanInfo01.approved.json │ ├── template_factory_test.TestPlanInfo02.approved.json │ ├── template_factory_test.TestSigSourcedWithDiskEncryptionSet.approved.json │ ├── template_factory_test.TestStandardSkuPublicIPVMDeployment.approved.json │ ├── template_factory_test.TestTrustedLaunch01.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment03.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment04.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment05.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment06.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment07.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment08.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment09.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment10.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment11.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment12.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment13.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment14.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment15.approved.json │ ├── template_factory_test.TestVirtualMachineDeployment16.approved.json │ ├── template_factory_test.TestVirtualMachineDeploymentLicenseType01.approved.json │ ├── template_factory_test.TestVirtualMachineDeploymentLicenseType02.approved.json │ ├── template_factory_test.go │ ├── tempname.go │ ├── tempname_test.go │ └── testdata │ │ ├── arm_linux_specialized.pkr.hcl │ │ ├── child_from_specialized_parent.pkr.hcl │ │ ├── rsa_sha2_only_server.pkr.hcl │ │ └── windows_sig.pkr.hcl │ ├── chroot │ ├── builder.go │ ├── builder.hcl2spec.go │ ├── builder_test.go │ ├── const.go │ ├── diskattacher.go │ ├── diskattacher_freebsd.go │ ├── diskattacher_linux.go │ ├── diskattacher_other.go │ ├── diskset.go │ ├── diskset_test.go │ ├── metadatastub_test.go │ ├── packerui_test.go │ ├── shared_image_gallery_destination.go │ ├── shared_image_gallery_destination.hcl2spec.go │ ├── shared_image_gallery_destination_test.go │ ├── step_attach_disk.go │ ├── step_attach_disk_test.go │ ├── step_create_image.go │ ├── step_create_image_test.go │ ├── step_create_new_diskset.go │ ├── step_create_new_diskset_test.go │ ├── step_create_shared_image_version.go │ ├── step_create_shared_image_version_test.go │ ├── step_create_snapshotset.go │ ├── step_create_snapshotset_test.go │ ├── step_get_source_image_name.go │ ├── step_get_source_image_name_test.go │ ├── step_mount_device.go │ ├── step_mount_device_test.go │ ├── step_resolve_plaform_image_version.go │ ├── step_resolve_plaform_image_version_test.go │ ├── step_verify_shared_image_destination.go │ ├── step_verify_shared_image_destination_test.go │ ├── step_verify_shared_image_source.go │ ├── step_verify_shared_image_source_test.go │ ├── step_verify_source_disk.go │ ├── step_verify_source_disk_test.go │ └── template_funcs.go │ ├── common │ ├── acceptance_helper.go │ ├── artifact.go │ ├── artifact_test.go │ ├── cli │ │ └── cli.go │ ├── client │ │ ├── azure_authorizer.go │ │ ├── azure_client_set.go │ │ ├── azure_client_set_mock.go │ │ ├── config.go │ │ ├── config_retriever.go │ │ ├── config_retriever_test.go │ │ ├── config_test.go │ │ ├── detect_azure.go │ │ ├── detect_azure_linux.go │ │ ├── detect_azure_linux_test.go │ │ ├── metadata.go │ │ ├── normalize_location.go │ │ ├── normalize_location_test.go │ │ ├── resource.go │ │ └── resource_test.go │ ├── config.go │ ├── config_test.go │ ├── constants │ │ ├── licenseTypes.go │ │ ├── securityTypes.go │ │ ├── stateBag.go │ │ └── targetplatforms.go │ ├── dump_config.go │ ├── dump_config_test.go │ ├── env.go │ ├── gluestrings.go │ ├── gluestrings_test.go │ ├── inspector.go │ ├── log │ │ └── log.go │ ├── logutil │ │ └── logfields.go │ ├── map.go │ ├── pointer.go │ ├── state_bag.go │ ├── step_notify.go │ ├── step_notify_test.go │ ├── strings_contains.go │ ├── strings_contains_test.go │ ├── template │ │ ├── template.go │ │ ├── template_builder.go │ │ ├── template_builder_test.TestBuildEncryptedWindows.approved.json │ │ ├── template_builder_test.TestBuildLinux00.approved.json │ │ ├── template_builder_test.TestBuildLinux01.approved.json │ │ ├── template_builder_test.TestBuildLinux02.approved.json │ │ ├── template_builder_test.TestBuildWindows00.approved.json │ │ ├── template_builder_test.TestBuildWindows01.approved.json │ │ ├── template_builder_test.TestBuildWindows02.approved.json │ │ ├── template_builder_test.TestBuildWindows03.approved.json │ │ ├── template_builder_test.TestBuildWindows04.approved.json │ │ ├── template_builder_test.TestBuildWindows05.approved.json │ │ ├── template_builder_test.TestCommunitySharedImageGallery00.approved.json │ │ ├── template_builder_test.TestDirectSharedImageGallery00.approved.json │ │ ├── template_builder_test.TestLicenseType00.approved.json │ │ ├── template_builder_test.TestLicenseType01.approved.json │ │ ├── template_builder_test.TestNetworkSecurityGroup00.approved.json │ │ ├── template_builder_test.TestSetIdentity00.approved.json │ │ ├── template_builder_test.TestSharedImageGallery00.approved.json │ │ ├── template_builder_test.go │ │ ├── template_parameters.go │ │ └── template_parameters_test.go │ ├── template_funcs.go │ └── template_funcs_test.go │ ├── dtl │ ├── TestVirtualMachineDeployment05.approved.txt │ ├── WindowsMixAndMatch.json │ ├── WindowsSimple.json │ ├── acceptancetest.json │ ├── artifact.go │ ├── azure_client.go │ ├── azure_error_response.go │ ├── azure_error_response_test.TestAzureErrorNestedShouldFormat.approved.txt │ ├── azure_error_response_test.TestAzureErrorSimpleShouldFormat.approved.txt │ ├── azure_error_response_test.go │ ├── builder.go │ ├── builder_acc_test.go │ ├── builder_test.go │ ├── capture_template.go │ ├── capture_template_test.go │ ├── config.go │ ├── config.hcl2spec.go │ ├── config_test.go │ ├── openssh_key_pair.go │ ├── openssh_key_pair_test.go │ ├── resource_resolver.go │ ├── step.go │ ├── step_capture_image.go │ ├── step_delete_virtual_machine.go │ ├── step_deploy_template.go │ ├── step_power_off_compute.go │ ├── step_publish_to_shared_image_gallery.go │ ├── step_save_winrm_password.go │ ├── step_test.go │ ├── template_factory.go │ ├── template_funcs.go │ ├── template_funcs_test.go │ ├── tempname.go │ └── testdata │ │ ├── linux.pkr.hcl │ │ └── windows.pkr.hcl │ └── pkcs12 │ ├── LICENSE │ ├── bmp-string.go │ ├── bmp-string_test.go │ ├── crypto.go │ ├── crypto_test.go │ ├── errors.go │ ├── mac.go │ ├── mac_test.go │ ├── pbkdf.go │ ├── pbkdf_test.go │ ├── pkcs12.go │ ├── pkcs12_test.go │ ├── pkcs8.go │ ├── pkcs8_test.go │ ├── rc2 │ ├── bench_test.go │ ├── rc2.go │ └── rc2_test.go │ ├── safebags.go │ └── safebags_test.go ├── contrib └── azure-setup.sh ├── docs-partials ├── builder │ └── azure │ │ ├── arm │ │ ├── Config-not-required.mdx │ │ ├── Config-required.mdx │ │ ├── PlanInformation-not-required.mdx │ │ ├── SharedImageGallery-not-required.mdx │ │ ├── SharedImageGalleryDestination-not-required.mdx │ │ ├── Spot-not-required.mdx │ │ ├── TargetRegion-not-required.mdx │ │ ├── TargetRegion-required.mdx │ │ └── TargetRegion.mdx │ │ ├── chroot │ │ ├── Config-not-required.mdx │ │ ├── Config-required.mdx │ │ ├── Config.mdx │ │ ├── SharedImageGalleryDestination-not-required.mdx │ │ ├── SharedImageGalleryDestination-required.mdx │ │ ├── SharedImageGalleryDestination.mdx │ │ ├── TargetRegion-not-required.mdx │ │ ├── TargetRegion-required.mdx │ │ └── TargetRegion.mdx │ │ ├── common │ │ ├── Config-not-required.mdx │ │ └── client │ │ │ ├── Config-not-required.mdx │ │ │ └── Config.mdx │ │ └── dtl │ │ ├── ArtifactParameter-not-required.mdx │ │ ├── Config-not-required.mdx │ │ ├── Config-required.mdx │ │ ├── DtlArtifact-not-required.mdx │ │ ├── DtlArtifact.mdx │ │ ├── SharedImageGallery-not-required.mdx │ │ └── SharedImageGalleryDestination-not-required.mdx └── provisioner │ └── azure-dtlartifact │ ├── ArtifactParameter-not-required.mdx │ ├── Config-not-required.mdx │ ├── Config-required.mdx │ └── DtlArtifact-not-required.mdx ├── docs ├── README.md ├── builders │ ├── arm.mdx │ ├── chroot.mdx │ ├── dtl.mdx │ └── index.mdx └── provisioners │ └── dtlartifact.mdx ├── example ├── centos.json ├── centos.json.pkr.hcl ├── debian-chroot.json ├── debian.json ├── freebsd-chroot.json ├── freebsd.json ├── github-oidc-example.pkr.hcl ├── linux_custom_image.json ├── linux_custom_managed_image.json ├── marketplace_plan_info.json ├── oidc-example.pkr.hcl ├── rhel.json ├── suse.json ├── ubuntu-chroot.json ├── ubuntu.json ├── ubuntu_managed_image_sig.json ├── ubuntu_quickstart.json ├── variables.pkr.hcl ├── windows.json ├── windows_custom_image.json ├── windows_custom_image │ ├── packer_config.pkr.hcl │ ├── variables.auto.pkrvars.hcl │ ├── variables.pkr.hcl │ └── winserver2022dcg2.pkr.hcl ├── windows_quickstart.json └── windows_skip_key_vault │ ├── 11 │ ├── userdata.ps1 │ └── windows_skip_key_vault.pkr.hcl │ └── avd │ ├── userdata.ps1 │ └── windows_skip_key_vault.pkr.hcl ├── go.mod ├── go.sum ├── main.go ├── provisioner └── azure-dtlartifact │ ├── provisioner.go │ └── provisioner.hcl2spec.go ├── terraform ├── README.md ├── main.tf ├── providers.tf ├── run_terraform_apply_with_expected_env_vars.sh ├── run_terraform_destroy_with_expected_env_vars.sh └── variables.tf └── version ├── version.go └── version_test.go /.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: https://developer.hashicorp.com/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: https://discuss.hashicorp.com/c/packer 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-azure/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_azure_${{ inputs.GOOS }}_${{ inputs.GOARCH }} ." 15 | shell: bash 16 | - run: zip ./pkg/packer_plugin_azure_${{ inputs.GOOS }}_${{ inputs.GOARCH }}.zip ./pkg/packer_plugin_azure_${{ inputs.GOOS }}_${{ inputs.GOARCH }} 17 | shell: bash 18 | - run: rm ./pkg/packer_plugin_azure_${{ inputs.GOOS }}_${{ inputs.GOARCH }} 19 | shell: bash 20 | - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 21 | with: 22 | name: "packer_plugin_azure_${{ inputs.GOOS }}_${{ inputs.GOARCH }}.zip" 23 | path: "pkg/packer_plugin_azure_${{ 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/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/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/azure" 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/azure" 53 | release_version: ${{ needs.strip-version.outputs.packer-version }} 54 | release_sha: ${{ github.ref }} 55 | github_token: ${{ secrets.GITHUB_TOKEN }} 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | dist/* 3 | packer-plugin-azure 4 | .docs 5 | crash.log 6 | terraform/.terraform* 7 | terraform/terraform.tfstate 8 | terraform/terraform.tfstate.backup 9 | -------------------------------------------------------------------------------- /.go-version: -------------------------------------------------------------------------------- 1 | 1.22.10 2 | 3 | -------------------------------------------------------------------------------- /.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 = "Azure" 8 | description = "Packer can create Azure virtual machine images through variety of ways depending on the strategy that you want to use for building the images." 9 | identifier = "packer/hashicorp/azure" 10 | flags = ["hcp-ready"] 11 | component { 12 | type = "builder" 13 | name = "ARM" 14 | slug = "arm" 15 | } 16 | component { 17 | type = "builder" 18 | name = "chroot" 19 | slug = "chroot" 20 | } 21 | component { 22 | type = "builder" 23 | name = "DTL" 24 | slug = "dtl" 25 | } 26 | component { 27 | type = "provisioner" 28 | name = "DTL Artifact" 29 | slug = "dtlartifact" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Latest Release 2 | 3 | Please refer to [releases](https://github.com/hashicorp/packer-plugin-azure/releases) for the latest CHANGELOG information. 4 | 5 | --- 6 | ## 1.0.4 (October 18, 2021) 7 | 8 | ### NOTES: 9 | Support for the HCP Packer registry is currently in beta and requires 10 | Packer v1.7.7 [GH-160] 11 | 12 | ### IMPROVEMENTS: 13 | * Add `SourceImageName` as shared builder information variable. [GH-160] 14 | * Add `SourceImageName` to HCP Packer registry image metadata. [GH-160] 15 | * Update packer-plugin-sdk to v0.2.7 [GH-159] 16 | 17 | ### BUG FIXES: 18 | * builder/arm: Fix panic when running the cleanup step on a failed deployment. [GH-155] 19 | 20 | ## 1.0.3 (September 13, 2021) 21 | 22 | ### NOTES: 23 | HCP Packer private beta support requires Packer version 1.7.5 or 1.7.6 [GH-150] 24 | 25 | ### FEATURES: 26 | * Add HCP Packer registry image metadata to builder artifacts. [GH-138] [GH-150] 27 | 28 | ### IMPROVEMENTS: 29 | * Allow Premium_LRS as SIG storage account type. [GH-124] 30 | 31 | ### BUG FIXES: 32 | * Update VaultClientDelete to pass correct Azure cloud environment endpoint. [GH-137] 33 | 34 | ## 1.0.2 (August 19, 2021) 35 | 36 | ### IMPROVEMENTS: 37 | * Add user_data_file to arm builder. [GH-123] 38 | 39 | ### BUG FIXES: 40 | * Bump github.com/Azure/azure-sdk-for-go to fix vulnerability in plugin dependency. [GH-117] 41 | 42 | ## 1.0.0 (June 15, 2021) 43 | 44 | * Update packer-plugin-sdk to v0.2.3 [GH-96] 45 | * Add Go module retraction for v0.0.1 46 | 47 | ## 0.0.3 (May 14, 2021) 48 | * Update packer-plugin-sdk to enable use of ntlm with WinRM. 49 | 50 | ## 0.0.2 (May 13, 2021) 51 | 52 | ### IMPROVEMENTS: 53 | 54 | * builder/dtl: Add `disallow_public_ip` configuration to support private DevTest Lab VMs. [GH-85] 55 | 56 | ### BUG FIXES: 57 | 58 | * Fixes a version string issue to support plugin vendoring from within Packer [hashicorp/packer#10979](https://github.com/hashicorp/packer/pull/10979). 59 | [GH-84] 60 | 61 | ## 0.0.1 (May 7, 2021) 62 | 63 | * Azure Plugin break out from Packer core. Changes prior to break out can be found in [Packer's CHANGELOG](https://github.com/hashicorp/packer/blob/master/CHANGELOG.md) 64 | 65 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @hashicorp/packer 2 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | NAME=azure 2 | BINARY=packer-plugin-${NAME} 3 | PLUGIN_FQN="$(shell grep -E '^module' %s : %s\n", indent, error.Code, error.Message)) 54 | for _, x := range error.Details { 55 | newIndent := fmt.Sprintf("%s ", indent) 56 | 57 | var aer azureErrorResponse 58 | err := json.Unmarshal([]byte(x.Message), &aer) 59 | if err == nil { 60 | buf.WriteString(fmt.Sprintf("ERROR: %s-> %s\n", newIndent, x.Code)) 61 | formatAzureErrorResponse(aer.ErrorDetails, buf, newIndent) 62 | } else { 63 | buf.WriteString(fmt.Sprintf("ERROR: %s-> %s : %s\n", newIndent, x.Code, x.Message)) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /builder/azure/arm/azure_error_response_test.TestAzureErrorNestedShouldFormat.approved.txt: -------------------------------------------------------------------------------- 1 | ERROR: -> DeploymentFailed : At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details. 2 | ERROR: -> BadRequest 3 | ERROR: -> InvalidRequestFormat : Cannot parse the request. 4 | ERROR: -> InvalidJson : Error converting value "playground" to type 'Microsoft.WindowsAzure.Networking.Nrp.Frontend.Contract.Csm.Public.IpAllocationMethod'. Path 'properties.publicIPAllocationMethod', line 1, position 130. 5 | -------------------------------------------------------------------------------- /builder/azure/arm/azure_error_response_test.TestAzureErrorSimpleShouldFormat.approved.txt: -------------------------------------------------------------------------------- 1 | ERROR: -> ResourceNotFound : The Resource 'Microsoft.Compute/images/PackerUbuntuImage' under resource group 'packer-test00' was not found. 2 | -------------------------------------------------------------------------------- /builder/azure/arm/openssh_key_pair.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package arm 5 | 6 | import ( 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "crypto/x509" 10 | "encoding/base64" 11 | "encoding/pem" 12 | "fmt" 13 | "time" 14 | 15 | "golang.org/x/crypto/ssh" 16 | ) 17 | 18 | const ( 19 | KeySize = 2048 20 | ) 21 | 22 | type OpenSshKeyPair struct { 23 | privateKey *rsa.PrivateKey 24 | publicKey ssh.PublicKey 25 | } 26 | 27 | func NewOpenSshKeyPair() (*OpenSshKeyPair, error) { 28 | return NewOpenSshKeyPairWithSize(KeySize) 29 | } 30 | 31 | func NewOpenSshKeyPairWithSize(keySize int) (*OpenSshKeyPair, error) { 32 | privateKey, err := rsa.GenerateKey(rand.Reader, keySize) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | return &OpenSshKeyPair{ 43 | privateKey: privateKey, 44 | publicKey: publicKey, 45 | }, nil 46 | } 47 | 48 | func (s *OpenSshKeyPair) AuthorizedKey() string { 49 | return fmt.Sprintf("%s %s packer Azure Deployment%s", 50 | s.publicKey.Type(), 51 | base64.StdEncoding.EncodeToString(s.publicKey.Marshal()), 52 | time.Now().Format(time.RFC3339)) 53 | } 54 | 55 | func (s *OpenSshKeyPair) PrivateKey() []byte { 56 | privateKey := pem.EncodeToMemory(&pem.Block{ 57 | Type: "RSA PRIVATE KEY", 58 | Bytes: x509.MarshalPKCS1PrivateKey(s.privateKey), 59 | }) 60 | 61 | return privateKey 62 | } 63 | -------------------------------------------------------------------------------- /builder/azure/arm/openssh_key_pair_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package arm 5 | 6 | import ( 7 | "testing" 8 | 9 | "golang.org/x/crypto/ssh" 10 | ) 11 | 12 | func TestFart(t *testing.T) { 13 | 14 | } 15 | 16 | func TestAuthorizedKeyShouldParse(t *testing.T) { 17 | testSubject, err := NewOpenSshKeyPairWithSize(512) 18 | if err != nil { 19 | t.Fatalf("Failed to create a new OpenSSH key pair, err=%s.", err) 20 | } 21 | 22 | authorizedKey := testSubject.AuthorizedKey() 23 | 24 | _, _, _, _, err = ssh.ParseAuthorizedKey([]byte(authorizedKey)) 25 | if err != nil { 26 | t.Fatalf("Failed to parse the authorized key, err=%s", err) 27 | } 28 | } 29 | 30 | func TestPrivateKeyShouldParse(t *testing.T) { 31 | testSubject, err := NewOpenSshKeyPairWithSize(512) 32 | if err != nil { 33 | t.Fatalf("Failed to create a new OpenSSH key pair, err=%s.", err) 34 | } 35 | 36 | _, err = ssh.ParsePrivateKey(testSubject.PrivateKey()) 37 | if err != nil { 38 | t.Fatalf("Failed to parse the private key, err=%s\n", err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /builder/azure/arm/step.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package arm 5 | 6 | import ( 7 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/constants" 8 | "github.com/hashicorp/packer-plugin-sdk/multistep" 9 | ) 10 | 11 | func processStepResult( 12 | err error, sayError func(error), state multistep.StateBag) multistep.StepAction { 13 | 14 | if err != nil { 15 | state.Put(constants.Error, err) 16 | sayError(err) 17 | 18 | return multistep.ActionHalt 19 | } 20 | 21 | return multistep.ActionContinue 22 | 23 | } 24 | -------------------------------------------------------------------------------- /builder/azure/arm/step_certificate_in_keyvault_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package arm 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "testing" 10 | 11 | "github.com/hashicorp/go-azure-sdk/resource-manager/keyvault/2023-07-01/secrets" 12 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/constants" 13 | "github.com/hashicorp/packer-plugin-sdk/multistep" 14 | ) 15 | 16 | func TestNewStepCertificateInKeyVault(t *testing.T) { 17 | 18 | state := new(multistep.BasicStateBag) 19 | state.Put(constants.ArmKeyVaultName, "testKeyVaultName") 20 | state.Put(constants.ArmSubscription, "testSubscription") 21 | state.Put(constants.ArmResourceGroupName, "testResourceGroupName") 22 | state.Put(constants.ArmKeyVaultSecretName, "testKeyVaultSecretName") 23 | 24 | config := &Config{ 25 | winrmCertificate: "testCertificateString", 26 | } 27 | 28 | certKVStep := &StepCertificateInKeyVault{ 29 | say: func(message string) {}, 30 | error: func(e error) {}, 31 | set: func(ctx context.Context, id secrets.SecretId) error { return nil }, 32 | config: config, 33 | certificate: config.winrmCertificate} 34 | 35 | stepAction := certKVStep.Run(context.TODO(), state) 36 | 37 | if stepAction == multistep.ActionHalt { 38 | t.Fatalf("step should have succeeded.") 39 | } 40 | 41 | } 42 | 43 | func TestNewStepCertificateInKeyVault_error(t *testing.T) { 44 | state := new(multistep.BasicStateBag) 45 | state.Put(constants.ArmKeyVaultName, "testKeyVaultName") 46 | state.Put(constants.ArmSubscription, "testSubscription") 47 | state.Put(constants.ArmResourceGroupName, "testResourceGroupName") 48 | state.Put(constants.ArmKeyVaultSecretName, "testKeyVaultSecretName") 49 | 50 | config := &Config{ 51 | winrmCertificate: "testCertificateString", 52 | } 53 | 54 | certKVStep := &StepCertificateInKeyVault{ 55 | say: func(message string) {}, 56 | error: func(e error) {}, 57 | set: func(ctx context.Context, id secrets.SecretId) error { return fmt.Errorf("Unit test fail") }, 58 | config: config, 59 | certificate: config.winrmCertificate} 60 | 61 | stepAction := certKVStep.Run(context.TODO(), state) 62 | 63 | if stepAction != multistep.ActionHalt { 64 | t.Fatalf("step should have failed.") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /builder/azure/arm/step_power_off_compute.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package arm 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/go-azure-sdk/resource-manager/compute/2022-03-01/virtualmachines" 11 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/constants" 12 | "github.com/hashicorp/packer-plugin-sdk/multistep" 13 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 14 | ) 15 | 16 | type StepPowerOffCompute struct { 17 | client *AzureClient 18 | powerOff func(ctx context.Context, subscriptionId string, resourceGroupName string, computeName string) error 19 | say func(message string) 20 | error func(e error) 21 | } 22 | 23 | func NewStepPowerOffCompute(client *AzureClient, ui packersdk.Ui) *StepPowerOffCompute { 24 | var step = &StepPowerOffCompute{ 25 | client: client, 26 | say: func(message string) { ui.Say(message) }, 27 | error: func(e error) { ui.Error(e.Error()) }, 28 | } 29 | 30 | step.powerOff = step.powerOffCompute 31 | return step 32 | } 33 | 34 | func (s *StepPowerOffCompute) powerOffCompute(ctx context.Context, subscriptionId string, resourceGroupName string, computeName string) error { 35 | hibernate := false 36 | pollingContext, cancel := context.WithTimeout(ctx, s.client.PollingDuration) 37 | defer cancel() 38 | vmId := virtualmachines.NewVirtualMachineID(subscriptionId, resourceGroupName, computeName) 39 | err := s.client.VirtualMachinesClient.DeallocateThenPoll(pollingContext, vmId, virtualmachines.DeallocateOperationOptions{Hibernate: &hibernate}) 40 | if err != nil { 41 | s.say(s.client.LastError.Error()) 42 | } 43 | return err 44 | } 45 | 46 | func (s *StepPowerOffCompute) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 47 | s.say("Powering off machine ...") 48 | 49 | var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string) 50 | var computeName = state.Get(constants.ArmComputeName).(string) 51 | var subscriptionId = state.Get(constants.ArmSubscription).(string) 52 | 53 | s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName)) 54 | s.say(fmt.Sprintf(" -> ComputeName : '%s'", computeName)) 55 | 56 | err := s.powerOff(ctx, subscriptionId, resourceGroupName, computeName) 57 | 58 | return processStepResult(err, s.error, state) 59 | } 60 | 61 | func (*StepPowerOffCompute) Cleanup(multistep.StateBag) { 62 | } 63 | -------------------------------------------------------------------------------- /builder/azure/arm/step_set_certificate.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package arm 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/constants" 10 | "github.com/hashicorp/packer-plugin-sdk/multistep" 11 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 12 | ) 13 | 14 | type StepSetCertificate struct { 15 | config *Config 16 | say func(message string) 17 | error func(e error) 18 | } 19 | 20 | func NewStepSetCertificate(config *Config, ui packersdk.Ui) *StepSetCertificate { 21 | var step = &StepSetCertificate{ 22 | config: config, 23 | say: func(message string) { ui.Say(message) }, 24 | error: func(e error) { ui.Error(e.Error()) }, 25 | } 26 | 27 | return step 28 | } 29 | 30 | func (s *StepSetCertificate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 31 | s.say("Setting the certificate's URL ...") 32 | 33 | var winRMCertificateUrl = state.Get(constants.ArmCertificateUrl).(string) 34 | s.config.tmpWinRMCertificateUrl = winRMCertificateUrl 35 | 36 | return multistep.ActionContinue 37 | } 38 | 39 | func (*StepSetCertificate) Cleanup(multistep.StateBag) { 40 | } 41 | -------------------------------------------------------------------------------- /builder/azure/arm/step_set_certificate_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package arm 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/constants" 11 | "github.com/hashicorp/packer-plugin-sdk/multistep" 12 | ) 13 | 14 | func TestStepSetCertificateShouldPassIfGetPasses(t *testing.T) { 15 | var testSubject = &StepSetCertificate{ 16 | config: new(Config), 17 | say: func(message string) {}, 18 | error: func(e error) {}, 19 | } 20 | 21 | stateBag := createTestStateBagStepSetCertificate() 22 | 23 | var result = testSubject.Run(context.Background(), stateBag) 24 | if result != multistep.ActionContinue { 25 | t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result) 26 | } 27 | 28 | if _, ok := stateBag.GetOk(constants.Error); ok == true { 29 | t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error) 30 | } 31 | } 32 | 33 | func TestStepSetCertificateShouldTakeStepArgumentsFromStateBag(t *testing.T) { 34 | config := new(Config) 35 | var testSubject = &StepSetCertificate{ 36 | config: config, 37 | say: func(message string) {}, 38 | error: func(e error) {}, 39 | } 40 | 41 | stateBag := createTestStateBagStepSetCertificate() 42 | var result = testSubject.Run(context.Background(), stateBag) 43 | 44 | if result != multistep.ActionContinue { 45 | t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result) 46 | } 47 | 48 | if config.tmpWinRMCertificateUrl != stateBag.Get(constants.ArmCertificateUrl) { 49 | t.Fatalf("Expected config.tmpWinRMCertificateUrl to be %s, but got %s'", stateBag.Get(constants.ArmCertificateUrl), config.tmpWinRMCertificateUrl) 50 | } 51 | } 52 | 53 | func createTestStateBagStepSetCertificate() multistep.StateBag { 54 | stateBag := new(multistep.BasicStateBag) 55 | stateBag.Put(constants.ArmCertificateUrl, "Unit Test: Certificate URL") 56 | return stateBag 57 | } 58 | -------------------------------------------------------------------------------- /builder/azure/arm/step_set_generated_data.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package arm 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/packer-plugin-sdk/multistep" 10 | "github.com/hashicorp/packer-plugin-sdk/packerbuilderdata" 11 | ) 12 | 13 | type StepSetGeneratedData struct { 14 | GeneratedData *packerbuilderdata.GeneratedData 15 | Config *Config 16 | } 17 | 18 | func (s *StepSetGeneratedData) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 19 | 20 | s.GeneratedData.Put("TenantID", s.Config.ClientConfig.TenantID) 21 | s.GeneratedData.Put("SubscriptionID", s.Config.ClientConfig.SubscriptionID) 22 | s.GeneratedData.Put("TempComputeName", s.Config.tmpComputeName) 23 | s.GeneratedData.Put("TempNicName", s.Config.tmpNicName) 24 | s.GeneratedData.Put("TempOSDiskName", s.Config.tmpOSDiskName) 25 | s.GeneratedData.Put("TempDataDiskName", s.Config.tmpDataDiskName) 26 | s.GeneratedData.Put("TempDeploymentName", s.Config.tmpDeploymentName) 27 | s.GeneratedData.Put("TempVirtualNetworkName", s.Config.tmpVirtualNetworkName) 28 | s.GeneratedData.Put("TempKeyVaultName", s.Config.tmpKeyVaultName) 29 | s.GeneratedData.Put("TempResourceGroupName", s.Config.tmpResourceGroupName) 30 | s.GeneratedData.Put("TempNsgName", s.Config.tmpNsgName) 31 | s.GeneratedData.Put("TempSubnetName", s.Config.tmpSubnetName) 32 | s.GeneratedData.Put("TempPublicIPAddressName", s.Config.tmpPublicIPAddressName) 33 | return multistep.ActionContinue 34 | } 35 | 36 | func (s *StepSetGeneratedData) Cleanup(state multistep.StateBag) { 37 | // No cleanup... 38 | } 39 | -------------------------------------------------------------------------------- /builder/azure/arm/step_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package arm 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/constants" 11 | "github.com/hashicorp/packer-plugin-sdk/multistep" 12 | ) 13 | 14 | func TestProcessStepResultShouldContinueForNonErrors(t *testing.T) { 15 | stateBag := new(multistep.BasicStateBag) 16 | 17 | code := processStepResult(nil, func(error) { t.Fatal("Should not be called!") }, stateBag) 18 | if _, ok := stateBag.GetOk(constants.Error); ok { 19 | t.Errorf("Error was nil, but was still in the state bag.") 20 | } 21 | 22 | if code != multistep.ActionContinue { 23 | t.Errorf("Expected ActionContinue(%d), but got=%d", multistep.ActionContinue, code) 24 | } 25 | } 26 | 27 | func TestProcessStepResultShouldHaltOnError(t *testing.T) { 28 | stateBag := new(multistep.BasicStateBag) 29 | isSaidError := false 30 | 31 | code := processStepResult(fmt.Errorf("boom"), func(error) { isSaidError = true }, stateBag) 32 | if _, ok := stateBag.GetOk(constants.Error); !ok { 33 | t.Errorf("Error was non nil, but was not in the state bag.") 34 | } 35 | 36 | if !isSaidError { 37 | t.Errorf("Expected error to be said, but it was not.") 38 | } 39 | 40 | if code != multistep.ActionHalt { 41 | t.Errorf("Expected ActionHalt(%d), but got=%d", multistep.ActionHalt, code) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /builder/azure/arm/step_validate_template.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package arm 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/go-azure-sdk/resource-manager/resources/2022-09-01/deployments" 11 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/constants" 12 | "github.com/hashicorp/packer-plugin-sdk/multistep" 13 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 14 | ) 15 | 16 | type StepValidateTemplate struct { 17 | client *AzureClient 18 | validate func(ctx context.Context, subscriptionId string, resourceGroupName string, deploymentName string) error 19 | say func(message string) 20 | error func(e error) 21 | config *Config 22 | factory templateFactoryFunc 23 | name string 24 | } 25 | 26 | func NewStepValidateTemplate(client *AzureClient, ui packersdk.Ui, config *Config, deploymentName string, factory templateFactoryFunc) *StepValidateTemplate { 27 | var step = &StepValidateTemplate{ 28 | client: client, 29 | say: func(message string) { ui.Say(message) }, 30 | error: func(e error) { ui.Error(e.Error()) }, 31 | config: config, 32 | factory: factory, 33 | name: deploymentName, 34 | } 35 | 36 | step.validate = step.validateTemplate 37 | return step 38 | } 39 | 40 | func (s *StepValidateTemplate) validateTemplate(ctx context.Context, subscriptionId string, resourceGroupName string, deploymentName string) error { 41 | deployment, err := s.factory(s.config) 42 | if err != nil { 43 | return err 44 | } 45 | pollingContext, cancel := context.WithTimeout(ctx, s.client.PollingDuration) 46 | defer cancel() 47 | 48 | id := deployments.NewResourceGroupProviderDeploymentID(subscriptionId, resourceGroupName, deploymentName) 49 | _, err = s.client.DeploymentsClient.Validate(pollingContext, id, *deployment) 50 | if err != nil { 51 | s.say(s.client.LastError.Error()) 52 | } 53 | return err 54 | } 55 | 56 | func (s *StepValidateTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 57 | s.say("Validating deployment template ...") 58 | 59 | var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string) 60 | var subscriptionId = state.Get(constants.ArmSubscription).(string) 61 | 62 | s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName)) 63 | s.say(fmt.Sprintf(" -> DeploymentName : '%s'", s.name)) 64 | 65 | err := s.validate(ctx, subscriptionId, resourceGroupName, s.name) 66 | return processStepResult(err, s.error, state) 67 | } 68 | 69 | func (*StepValidateTemplate) Cleanup(multistep.StateBag) { 70 | } 71 | -------------------------------------------------------------------------------- /builder/azure/arm/template_factory_test.TestKeyVaultDeployment03.approved.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "keyVaultName": { 6 | "type": "string" 7 | }, 8 | "keyVaultSKU": { 9 | "type": "string" 10 | }, 11 | "keyVaultSecretName": { 12 | "type": "string" 13 | }, 14 | "keyVaultSecretValue": { 15 | "type": "securestring" 16 | }, 17 | "objectId": { 18 | "type": "string" 19 | }, 20 | "tenantId": { 21 | "type": "string" 22 | } 23 | }, 24 | "resources": [ 25 | { 26 | "apiVersion": "[variables('apiVersion')]", 27 | "location": "[variables('location')]", 28 | "name": "[parameters('keyVaultName')]", 29 | "properties": { 30 | "accessPolicies": [ 31 | { 32 | "objectId": "[parameters('objectId')]", 33 | "permissions": { 34 | "keys": [ 35 | "all" 36 | ], 37 | "secrets": [ 38 | "all" 39 | ] 40 | }, 41 | "tenantId": "[parameters('tenantId')]" 42 | } 43 | ], 44 | "enableSoftDelete": "true", 45 | "enabledForDeployment": "true", 46 | "enabledForTemplateDeployment": "true", 47 | "sku": { 48 | "family": "A", 49 | "name": "[parameters('keyVaultSKU')]" 50 | }, 51 | "tenantId": "[parameters('tenantId')]" 52 | }, 53 | "tags": { 54 | "tag01": "value01", 55 | "tag02": "value02", 56 | "tag03": "value03" 57 | }, 58 | "type": "Microsoft.KeyVault/vaults" 59 | }, 60 | { 61 | "apiVersion": "[variables('apiVersion')]", 62 | "dependsOn": [ 63 | "[resourceId('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]" 64 | ], 65 | "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('keyVaultSecretName'))]", 66 | "properties": { 67 | "value": "[parameters('keyVaultSecretValue')]" 68 | }, 69 | "tags": { 70 | "tag01": "value01", 71 | "tag02": "value02", 72 | "tag03": "value03" 73 | }, 74 | "type": "Microsoft.KeyVault/vaults/secrets" 75 | } 76 | ], 77 | "variables": { 78 | "apiVersion": "2022-07-01", 79 | "location": "[resourceGroup().location]" 80 | } 81 | } -------------------------------------------------------------------------------- /builder/azure/arm/template_factory_test.TestKeyVaultDeployment04.approved.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "keyVaultName": { 6 | "type": "string" 7 | }, 8 | "keyVaultSKU": { 9 | "type": "string" 10 | }, 11 | "keyVaultSecretName": { 12 | "type": "string" 13 | }, 14 | "keyVaultSecretValue": { 15 | "type": "securestring" 16 | }, 17 | "objectId": { 18 | "type": "string" 19 | }, 20 | "tenantId": { 21 | "type": "string" 22 | } 23 | }, 24 | "resources": [ 25 | { 26 | "apiVersion": "[variables('apiVersion')]", 27 | "location": "[variables('location')]", 28 | "name": "[parameters('keyVaultName')]", 29 | "properties": { 30 | "accessPolicies": [ 31 | { 32 | "objectId": "[parameters('objectId')]", 33 | "permissions": { 34 | "keys": [ 35 | "all" 36 | ], 37 | "secrets": [ 38 | "all" 39 | ] 40 | }, 41 | "tenantId": "[parameters('tenantId')]" 42 | } 43 | ], 44 | "enableSoftDelete": "true", 45 | "enabledForDeployment": "true", 46 | "enabledForTemplateDeployment": "true", 47 | "sku": { 48 | "family": "A", 49 | "name": "[parameters('keyVaultSKU')]" 50 | }, 51 | "tenantId": "[parameters('tenantId')]" 52 | }, 53 | "type": "Microsoft.KeyVault/vaults" 54 | }, 55 | { 56 | "apiVersion": "[variables('apiVersion')]", 57 | "dependsOn": [ 58 | "[resourceId('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]" 59 | ], 60 | "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('keyVaultSecretName'))]", 61 | "properties": { 62 | "attributes": { 63 | "exp": 4102444800 64 | }, 65 | "value": "[parameters('keyVaultSecretValue')]" 66 | }, 67 | "type": "Microsoft.KeyVault/vaults/secrets" 68 | } 69 | ], 70 | "variables": { 71 | "apiVersion": "2022-07-01", 72 | "location": "[resourceGroup().location]" 73 | } 74 | } -------------------------------------------------------------------------------- /builder/azure/arm/tempname.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package arm 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | 10 | "github.com/hashicorp/packer-plugin-sdk/random" 11 | ) 12 | 13 | type TempName struct { 14 | AdminPassword string 15 | CertificatePassword string 16 | ComputeName string 17 | DeploymentName string 18 | KeyVaultName string 19 | ResourceGroupName string 20 | OSDiskName string 21 | DataDiskName string 22 | NicName string 23 | SubnetName string 24 | PublicIPAddressName string 25 | VirtualNetworkName string 26 | NsgName string 27 | } 28 | 29 | func NewTempName(p string) *TempName { 30 | tempName := &TempName{} 31 | 32 | suffix := random.AlphaNumLower(5) 33 | if p == "" { 34 | p = "pkr" 35 | suffix = random.AlphaNumLower(10) 36 | } 37 | 38 | tempName.ComputeName = fmt.Sprintf("%svm%s", p, suffix) 39 | tempName.DeploymentName = fmt.Sprintf("%sdp%s", p, suffix) 40 | tempName.KeyVaultName = fmt.Sprintf("%skv%s", p, suffix) 41 | tempName.OSDiskName = fmt.Sprintf("%sos%s", p, suffix) 42 | tempName.DataDiskName = fmt.Sprintf("%sdd%s", p, suffix) 43 | tempName.NicName = fmt.Sprintf("%sni%s", p, suffix) 44 | tempName.PublicIPAddressName = fmt.Sprintf("%sip%s", p, suffix) 45 | tempName.SubnetName = fmt.Sprintf("%ssn%s", p, suffix) 46 | tempName.VirtualNetworkName = fmt.Sprintf("%svn%s", p, suffix) 47 | tempName.NsgName = fmt.Sprintf("%ssg%s", p, suffix) 48 | tempName.ResourceGroupName = fmt.Sprintf("%s-Resource-Group-%s", p, suffix) 49 | 50 | tempName.AdminPassword = generatePassword() 51 | tempName.CertificatePassword = random.AlphaNum(32) 52 | 53 | return tempName 54 | } 55 | 56 | // generate a password that is acceptable to Azure 57 | // Three of the four items must be met. 58 | // 1. Contains an uppercase character 59 | // 2. Contains a lowercase character 60 | // 3. Contains a numeric digit 61 | // 4. Contains a special character 62 | func generatePassword() string { 63 | var s string 64 | for i := 0; i < 100; i++ { 65 | s := random.AlphaNum(32) 66 | if !strings.ContainsAny(s, random.PossibleNumbers) { 67 | continue 68 | } 69 | 70 | if !strings.ContainsAny(s, random.PossibleLowerCase) { 71 | continue 72 | } 73 | 74 | if !strings.ContainsAny(s, random.PossibleUpperCase) { 75 | continue 76 | } 77 | 78 | return s 79 | } 80 | 81 | // if an acceptable password cannot be generated in 100 tries, give up 82 | return s 83 | } 84 | -------------------------------------------------------------------------------- /builder/azure/arm/testdata/arm_linux_specialized.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") } 4 | 5 | variable "ssh_private_key_location" { 6 | default = "${env("ARM_SSH_PRIVATE_KEY_FILE")}" 7 | type = string 8 | } 9 | variable "resource_group_name" { 10 | default = "${env("ARM_RESOURCE_GROUP_NAME")}" 11 | type = string 12 | } 13 | variable "resource_prefix" { 14 | default = "${env("ARM_RESOURCE_PREFIX")}" 15 | type = string 16 | } 17 | source "azure-arm" "linux-sig" { 18 | image_offer = "0001-com-ubuntu-server-jammy" 19 | image_publisher = "canonical" 20 | image_sku = "22_04-lts-arm64" 21 | use_azure_cli_auth = true 22 | location = "South Central US" 23 | vm_size = "Standard_D4ps_v5" 24 | ssh_username = "packer" 25 | ssh_private_key_file = var.ssh_private_key_location 26 | communicator = "ssh" 27 | shared_image_gallery_destination { 28 | image_name = "${var.resource_prefix}-arm-linux-specialized-sig" 29 | gallery_name = "${var.resource_prefix}_acctestgallery" 30 | image_version = "1.0.0" 31 | resource_group = var.resource_group_name 32 | specialized = true 33 | use_shallow_replication = true 34 | } 35 | 36 | os_type = "Linux" 37 | } 38 | 39 | build { 40 | sources = ["source.azure-arm.linux-sig"] 41 | } 42 | 43 | -------------------------------------------------------------------------------- /builder/azure/arm/testdata/child_from_specialized_parent.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") } 4 | 5 | variable "subscription" { 6 | default = "${env("ARM_SUBSCRIPTION_ID")}" 7 | type = string 8 | sensitive = true 9 | } 10 | 11 | variable "ssh_private_key_location" { 12 | default = "${env("ARM_SSH_PRIVATE_KEY_FILE")}" 13 | type = string 14 | } 15 | 16 | variable "resource_group_name" { 17 | default = "${env("ARM_RESOURCE_GROUP_NAME")}" 18 | type = string 19 | } 20 | 21 | variable "resource_prefix" { 22 | default = "${env("ARM_RESOURCE_PREFIX")}" 23 | type = string 24 | } 25 | 26 | source "azure-arm" "linux-sig" { 27 | use_azure_cli_auth = true 28 | location = "South Central US" 29 | vm_size = "Standard_D4ps_v5" 30 | ssh_username = "packer" 31 | ssh_private_key_file = var.ssh_private_key_location 32 | communicator = "ssh" 33 | shared_image_gallery { 34 | subscription = var.subscription 35 | image_name = "${var.resource_prefix}-arm-linux-specialized-sig" 36 | gallery_name = "${var.resource_prefix}_acctestgallery" 37 | image_version = "1.0.0" 38 | resource_group = var.resource_group_name 39 | } 40 | shared_image_gallery_destination { 41 | image_version = "1.0.1" 42 | image_name = "${var.resource_prefix}-arm-linux-specialized-sig" 43 | gallery_name = "${var.resource_prefix}_acctestgallery" 44 | resource_group = var.resource_group_name 45 | specialized = true 46 | use_shallow_replication = true 47 | } 48 | 49 | os_type = "Linux" 50 | } 51 | 52 | build { 53 | sources = ["source.azure-arm.linux-sig"] 54 | } 55 | 56 | -------------------------------------------------------------------------------- /builder/azure/arm/testdata/rsa_sha2_only_server.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | /* 5 | OpenSSH migrated the ssh-rsa key type, which historically used the ssh-rsa 6 | signature algorithm based on SHA-1, to the new rsa-sha2-256 and rsa-sha2-512 signature algorithms. 7 | Golang issues: https://github.com/golang/go/issues/49952 8 | See plugin issue: https://github.com/hashicorp/packer-plugin-azure/issues/191 9 | */ 10 | 11 | variables { 12 | subscription_id = env("ARM_SUBSCRIPTION_ID") 13 | client_id = env("ARM_CLIENT_ID") 14 | client_secret = env("ARM_CLIENT_SECRET") 15 | resource_group = env("ARM_RESOURCE_GROUP_NAME") 16 | } 17 | locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") } 18 | 19 | source "azure-arm" "ubuntu2204" { 20 | subscription_id = var.subscription_id 21 | client_id = var.client_id 22 | 23 | client_secret = var.client_secret 24 | 25 | managed_image_resource_group_name = var.resource_group 26 | managed_image_name = "ubuntu-jammay-server-test-${local.timestamp}" 27 | 28 | os_type = "Linux" 29 | image_publisher = "canonical" 30 | image_offer = "0001-com-ubuntu-server-jammy-daily" 31 | image_sku = "22_04-daily-lts" 32 | 33 | location = "West US2" 34 | vm_size = "Standard_DS2_v2" 35 | } 36 | 37 | build { 38 | sources = ["source.azure-arm.ubuntu2204"] 39 | provisioner "shell" { 40 | inline = ["uname -a"] 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /builder/azure/arm/testdata/windows_sig.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") } 5 | 6 | variable "resource_group_name" { 7 | default = "${env("ARM_RESOURCE_GROUP_NAME")}" 8 | type = string 9 | } 10 | variable "resource_prefix" { 11 | default = "${env("ARM_RESOURCE_PREFIX")}" 12 | type = string 13 | } 14 | source "azure-arm" "windows-sig" { 15 | communicator = "winrm" 16 | winrm_timeout = "5m" 17 | winrm_use_ssl = true 18 | winrm_insecure = true 19 | winrm_username = "packer" 20 | use_azure_cli_auth = true 21 | public_ip_sku = "Standard" 22 | shared_image_gallery_destination { 23 | image_name = "${var.resource_prefix}-windows-sig" 24 | gallery_name = "${var.resource_prefix}_acctestgallery" 25 | image_version = "1.0.0" 26 | resource_group = var.resource_group_name 27 | } 28 | managed_image_name = "packer-test-windows-sig-${local.timestamp}" 29 | managed_image_resource_group_name = var.resource_group_name 30 | 31 | os_type = "Windows" 32 | image_publisher = "MicrosoftWindowsServer" 33 | image_offer = "WindowsServer" 34 | image_sku = "2022-datacenter" 35 | 36 | location = "South Central US" 37 | vm_size = "Standard_DS2_v2" 38 | } 39 | 40 | build { 41 | sources = ["source.azure-arm.windows-sig"] 42 | } 43 | 44 | -------------------------------------------------------------------------------- /builder/azure/chroot/const.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package chroot 5 | 6 | const ( 7 | stateBagKey_Diskset = "diskset" 8 | stateBagKey_Snapshotset = "snapshotset" 9 | ) 10 | -------------------------------------------------------------------------------- /builder/azure/chroot/diskattacher_freebsd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package chroot 5 | 6 | import ( 7 | "bufio" 8 | "bytes" 9 | "context" 10 | "fmt" 11 | "os/exec" 12 | "regexp" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | func (da diskAttacher) WaitForDevice(ctx context.Context, lun int64) (device string, err error) { 18 | // This builder will always be running in Azure, where data disks show up 19 | // on scbus5 target 0. The camcontrol command always outputs LUNs in 20 | // unpadded hexadecimal format. 21 | regexStr := fmt.Sprintf(`at scbus5 target 0 lun %x \(.*?da([\d]+)`, lun) 22 | devRegex, err := regexp.Compile(regexStr) 23 | if err != nil { 24 | return "", err 25 | } 26 | for { 27 | cmd := exec.Command("camcontrol", "devlist") 28 | var out bytes.Buffer 29 | cmd.Stdout = &out 30 | err = cmd.Run() 31 | if err != nil { 32 | return "", err 33 | } 34 | outString := out.String() 35 | scanner := bufio.NewScanner(strings.NewReader(outString)) 36 | for scanner.Scan() { 37 | line := scanner.Text() 38 | // Check if this is the correct bus, target, and LUN. 39 | if matches := devRegex.FindStringSubmatch(line); matches != nil { 40 | // If this function immediately returns, devfs won't have 41 | // created the device yet. 42 | time.Sleep(1000 * time.Millisecond) 43 | return fmt.Sprintf("/dev/da%s", matches[1]), nil 44 | } 45 | } 46 | if err = scanner.Err(); err != nil { 47 | return "", err 48 | } 49 | 50 | select { 51 | case <-time.After(100 * time.Millisecond): 52 | // continue 53 | case <-ctx.Done(): 54 | return "", ctx.Err() 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /builder/azure/chroot/diskattacher_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package chroot 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | "syscall" 12 | "time" 13 | ) 14 | 15 | func diskPathForLun(lun int64) string { 16 | return fmt.Sprintf("/dev/disk/azure/scsi1/lun%d", lun) 17 | } 18 | 19 | func (da diskAttacher) WaitForDevice(ctx context.Context, lun int64) (device string, err error) { 20 | path := diskPathForLun(lun) 21 | 22 | for { 23 | link, err := os.Readlink(path) 24 | if err == nil { 25 | return filepath.Abs("/dev/disk/azure/scsi1/" + link) 26 | } else if err != os.ErrNotExist { 27 | if pe, ok := err.(*os.PathError); ok && pe.Err != syscall.ENOENT { 28 | return "", err 29 | } 30 | } 31 | 32 | select { 33 | case <-time.After(100 * time.Millisecond): 34 | // continue 35 | case <-ctx.Done(): 36 | return "", ctx.Err() 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /builder/azure/chroot/diskattacher_other.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | //go:build !linux && !freebsd 5 | // +build !linux,!freebsd 6 | 7 | package chroot 8 | 9 | import ( 10 | "context" 11 | ) 12 | 13 | func (da diskAttacher) WaitForDevice(ctx context.Context, lun int64) (device string, err error) { 14 | panic("The azure-chroot builder does not work on this platform.") 15 | } 16 | -------------------------------------------------------------------------------- /builder/azure/chroot/diskset.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package chroot 5 | 6 | import "github.com/hashicorp/packer-plugin-azure/builder/azure/common/client" 7 | 8 | // Diskset represents all of the disks or snapshots associated with an image. 9 | // It maps lun to resource ids. The OS disk is stored with lun=-1. 10 | type Diskset map[int64]client.Resource 11 | 12 | // OS return the OS disk resource ID or nil if it is not assigned 13 | func (ds Diskset) OS() *client.Resource { 14 | if r, ok := ds[-1]; ok { 15 | return &r 16 | } 17 | return nil 18 | } 19 | 20 | // Data return the data disk resource ID or nil if it is not assigned 21 | func (ds Diskset) Data(lun int64) *client.Resource { 22 | if r, ok := ds[lun]; ok { 23 | return &r 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /builder/azure/chroot/diskset_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package chroot 5 | 6 | import "github.com/hashicorp/packer-plugin-azure/builder/azure/common/client" 7 | 8 | // diskset easily creates a diskset for testing 9 | func diskset(ids ...string) Diskset { 10 | diskset := make(Diskset) 11 | for i, id := range ids { 12 | r, err := client.ParseResourceID(id) 13 | if err != nil { 14 | panic(err) 15 | } 16 | diskset[int64(i-1)] = r 17 | } 18 | return diskset 19 | } 20 | -------------------------------------------------------------------------------- /builder/azure/chroot/metadatastub_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package chroot 5 | 6 | import "github.com/hashicorp/packer-plugin-azure/builder/azure/common/client" 7 | 8 | func withMetadataStub(f func()) { 9 | mdc := client.DefaultMetadataClient 10 | defer func() { client.DefaultMetadataClient = mdc }() 11 | client.DefaultMetadataClient = client.MetadataClientStub{ 12 | ComputeInfo: client.ComputeInfo{ 13 | SubscriptionID: "testSubscriptionID", 14 | ResourceGroupName: "testResourceGroup", 15 | Name: "testVM", 16 | Location: "testLocation", 17 | }, 18 | } 19 | 20 | f() 21 | } 22 | -------------------------------------------------------------------------------- /builder/azure/chroot/packerui_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package chroot 5 | 6 | import ( 7 | "io" 8 | "strings" 9 | 10 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 11 | ) 12 | 13 | // testUI returns a test ui plus a function to retrieve the errors written to the ui 14 | func testUI() (packersdk.Ui, func() string) { 15 | errorBuffer := &strings.Builder{} 16 | ui := &packersdk.BasicUi{ 17 | Reader: strings.NewReader(""), 18 | Writer: io.Discard, 19 | ErrorWriter: errorBuffer, 20 | } 21 | return ui, errorBuffer.String 22 | } 23 | -------------------------------------------------------------------------------- /builder/azure/chroot/step_mount_device_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package chroot 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "os" 10 | "runtime" 11 | "testing" 12 | "time" 13 | 14 | "github.com/hashicorp/packer-plugin-sdk/common" 15 | "github.com/hashicorp/packer-plugin-sdk/multistep" 16 | ) 17 | 18 | func TestStepMountDevice_Run(t *testing.T) { 19 | switch runtime.GOOS { 20 | case "linux", "freebsd": 21 | break 22 | default: 23 | t.Skip("Unsupported operating system") 24 | } 25 | mountPath, err := os.MkdirTemp("", "stepmountdevicetest") 26 | if err != nil { 27 | t.Errorf("Unable to create a temporary directory: %q", err) 28 | } 29 | step := &StepMountDevice{ 30 | MountOptions: []string{"foo"}, 31 | MountPartition: "42", 32 | MountPath: mountPath, 33 | } 34 | 35 | var gotCommand string 36 | var wrapper common.CommandWrapper = func(ran string) (string, error) { 37 | gotCommand = ran 38 | return "", nil 39 | } 40 | 41 | state := new(multistep.BasicStateBag) 42 | state.Put("wrappedCommand", wrapper) 43 | state.Put("device", "/dev/quux") 44 | 45 | ui, getErrs := testUI() 46 | state.Put("ui", ui) 47 | 48 | var config Config 49 | state.Put("config", &config) 50 | 51 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 52 | defer cancel() 53 | 54 | got := step.Run(ctx, state) 55 | if got != multistep.ActionContinue { 56 | t.Errorf("Expected 'continue', but got '%v'", got) 57 | } 58 | 59 | var expectedMountDevice string 60 | switch runtime.GOOS { 61 | case "freebsd": 62 | expectedMountDevice = "/dev/quuxp42" 63 | default: // currently just Linux 64 | expectedMountDevice = "/dev/quux42" 65 | } 66 | expectedCommand := fmt.Sprintf("mount -o foo %s %s", expectedMountDevice, mountPath) 67 | if gotCommand != expectedCommand { 68 | t.Errorf("Expected '%v', but got '%v'", expectedCommand, gotCommand) 69 | } 70 | 71 | os.Remove(mountPath) 72 | _ = getErrs 73 | } 74 | -------------------------------------------------------------------------------- /builder/azure/chroot/step_resolve_plaform_image_version_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package chroot 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | "time" 10 | 11 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/client" 12 | "github.com/hashicorp/packer-plugin-sdk/multistep" 13 | 14 | "github.com/hashicorp/go-azure-sdk/resource-manager/compute/2022-03-01/virtualmachineimages" 15 | ) 16 | 17 | func TestStepResolvePlatformImageVersion_Run(t *testing.T) { 18 | 19 | var expectedSkuId, actualSkuId virtualmachineimages.SkuId 20 | expectedSku := "Linux" 21 | expectedOffer := "Offer" 22 | expectedPublisher := "Arch" 23 | subscriptionID := "1234" 24 | expectedLocation := "linuxland" 25 | expectedSkuId = virtualmachineimages.NewSkuID(subscriptionID, expectedLocation, expectedPublisher, expectedOffer, expectedSku) 26 | var actualListOperations virtualmachineimages.ListOperationOptions 27 | returnedVMImages := []virtualmachineimages.VirtualMachineImageResource{ 28 | { 29 | Name: "1.2.3", 30 | }, 31 | { 32 | Name: "0.2.1", 33 | }, 34 | } 35 | pi := &StepResolvePlatformImageVersion{ 36 | PlatformImage: &client.PlatformImage{ 37 | Version: "latest", 38 | Sku: expectedSku, 39 | Offer: expectedOffer, 40 | Publisher: expectedPublisher, 41 | }, 42 | Location: expectedLocation, 43 | list: func(ctx context.Context, azcli client.AzureClientSet, skuID virtualmachineimages.SkuId, operations virtualmachineimages.ListOperationOptions) (*[]virtualmachineimages.VirtualMachineImageResource, error) { 44 | 45 | actualSkuId = skuID 46 | actualListOperations = operations 47 | return &returnedVMImages, nil 48 | }, 49 | } 50 | 51 | state := new(multistep.BasicStateBag) 52 | 53 | ui, _ := testUI() 54 | state.Put("azureclient", &client.AzureClientSetMock{ 55 | SubscriptionIDMock: subscriptionID, 56 | }) 57 | state.Put("ui", ui) 58 | 59 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 60 | defer cancel() 61 | 62 | got := pi.Run(ctx, state) 63 | if got != multistep.ActionContinue { 64 | t.Errorf("Expected 'continue', but got %q", got) 65 | } 66 | 67 | if pi.PlatformImage.Version != "1.2.3" { 68 | t.Errorf("Expected version '1.2.3', but got %q", pi.PlatformImage.Version) 69 | } 70 | if actualSkuId != expectedSkuId { 71 | t.Fatalf("Expected sku ID %+v got sku ID %+v", expectedSkuId, actualSkuId) 72 | } 73 | if *actualListOperations.Orderby != "name desc" { 74 | t.Fatalf("Expected name desc order by list operation, got %s", *actualListOperations.Orderby) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /builder/azure/chroot/template_funcs.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package chroot 5 | 6 | import ( 7 | "fmt" 8 | "sync" 9 | 10 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/client" 11 | ) 12 | 13 | // CreateVMMetadataTemplateFunc returns a template function that retrieves VM metadata. VM metadata is retrieved only once and reused for all executions of the function. 14 | func CreateVMMetadataTemplateFunc() func(string) (string, error) { 15 | var data *client.ComputeInfo 16 | var dataErr error 17 | once := sync.Once{} 18 | return func(key string) (string, error) { 19 | once.Do(func() { 20 | data, dataErr = client.DefaultMetadataClient.GetComputeInfo() 21 | }) 22 | if dataErr != nil { 23 | return "", dataErr 24 | } 25 | switch key { 26 | case "name": 27 | return data.Name, nil 28 | case "subscription_id": 29 | return data.SubscriptionID, nil 30 | case "resource_group": 31 | return data.ResourceGroupName, nil 32 | case "location": 33 | return data.Location, nil 34 | case "resource_id": 35 | return data.GetResourceID(), nil 36 | default: 37 | return "", fmt.Errorf("unknown metadata key: %s (supported: name, subscription_id, resource_group, location, resource_id)", key) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /builder/azure/common/acceptance_helper.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | import ( 7 | "os" 8 | "os/exec" 9 | "testing" 10 | ) 11 | 12 | type CheckAcceptanceTestEnvVarsParams struct { 13 | CheckAzureCLI bool 14 | CheckSSHPrivateKeyFile bool 15 | } 16 | 17 | func CheckAcceptanceTestEnvVars(t *testing.T, params CheckAcceptanceTestEnvVarsParams) { 18 | if os.Getenv("PACKER_ACC") == "" { 19 | t.Skipf("Skipping acceptance test %s has environment variable `PACKER_ACC` is not set", t.Name()) 20 | } 21 | if os.Getenv("ARM_RESOURCE_GROUP_NAME") == "" { 22 | t.Fatalf("Test %s requires environment variable ARM_RESOURCE_GROUP_NAME is set", t.Name()) 23 | } 24 | if os.Getenv("ARM_RESOURCE_PREFIX") == "" { 25 | t.Fatalf("Test %s requires environment variable ARM_RESOURCE_PREFIX is set", t.Name()) 26 | } 27 | if os.Getenv("ARM_CLIENT_ID") == "" { 28 | t.Fatalf("Test %s requires environment variable ARM_CLIENT_ID is set", t.Name()) 29 | } 30 | if os.Getenv("ARM_CLIENT_SECRET") == "" { 31 | t.Fatalf("Test %s requires environment variable ARM_CLIENT_SECRET is set", t.Name()) 32 | } 33 | if os.Getenv("ARM_SUBSCRIPTION_ID") == "" { 34 | t.Fatalf("Test %s requires environment variable ARM_SUBSCRIPTION_ID is set", t.Name()) 35 | } 36 | 37 | if params.CheckAzureCLI && !loggedIntoAzureCLI(t) { 38 | t.Fatalf("Test %s requires CLI authentication, install the Azure CLI and log in", t.Name()) 39 | } 40 | if params.CheckSSHPrivateKeyFile && os.Getenv("ARM_SSH_PRIVATE_KEY_FILE") == "" { 41 | t.Fatalf("Test %s requires environment variable ARM_SSH_PRIVATE_KEY_FILE is set", t.Name()) 42 | } 43 | } 44 | 45 | func loggedIntoAzureCLI(t *testing.T) bool { 46 | command := exec.Command("az", "account", "show") 47 | commandStdout, err := command.CombinedOutput() 48 | if err != nil { 49 | t.Logf("`az account show` failed\n"+ 50 | "error: %v\n"+ 51 | "output: \n%s", err, string(commandStdout)) 52 | } 53 | 54 | return err == nil 55 | } 56 | -------------------------------------------------------------------------------- /builder/azure/common/artifact_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestArtifact_String(t *testing.T) { 11 | a := &Artifact{ 12 | Resources: []string{ 13 | "/subscriptions/4674464f-6024-43ae-903c-f6eed761be04/resourceGroups/rg/providers/Microsoft.Compute/disks/PackerTemp-osdisk-1586461959", 14 | "/subscriptions/4674464f-6024-43ae-903c-f6eed761be04/resourceGroups/images/providers/Microsoft.Compute/galleries/testgallery/images/myUbuntu/versions/1.0.10", 15 | }, 16 | } 17 | want := `Azure resources created: 18 | /subscriptions/4674464f-6024-43ae-903c-f6eed761be04/resourcegroups/images/providers/microsoft.compute/galleries/testgallery/images/myubuntu/versions/1.0.10 19 | /subscriptions/4674464f-6024-43ae-903c-f6eed761be04/resourcegroups/rg/providers/microsoft.compute/disks/packertemp-osdisk-1586461959 20 | ` 21 | if got := a.String(); got != want { 22 | t.Errorf("Artifact.String() = %v, want %v", got, want) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /builder/azure/common/cli/cli.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // spdx-license-identifier: mpl-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "fmt" 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | 14 | "github.com/dimchansky/utfbom" 15 | "github.com/mitchellh/go-homedir" 16 | ) 17 | 18 | // Code copy pasted from https://github.com/Azure/go-autorest/blob/main/autorest/azure/cli/profile.go 19 | 20 | // Profile represents a Profile from the Azure CLI 21 | type Profile struct { 22 | InstallationID string `json:"installationId"` 23 | Subscriptions []Subscription `json:"subscriptions"` 24 | } 25 | 26 | // Subscription represents a Subscription from the Azure CLI 27 | type Subscription struct { 28 | EnvironmentName string `json:"environmentName"` 29 | ID string `json:"id"` 30 | IsDefault bool `json:"isDefault"` 31 | Name string `json:"name"` 32 | State string `json:"state"` 33 | TenantID string `json:"tenantId"` 34 | User *User `json:"user"` 35 | } 36 | 37 | // User represents a User from the Azure CLI 38 | type User struct { 39 | Name string `json:"name"` 40 | Type string `json:"type"` 41 | } 42 | 43 | const azureProfileJSON = "azureProfile.json" 44 | 45 | func configDir() string { 46 | return os.Getenv("AZURE_CONFIG_DIR") 47 | } 48 | 49 | // ProfilePath returns the path where the Azure Profile is stored from the Azure CLI 50 | func ProfilePath() (string, error) { 51 | if cfgDir := configDir(); cfgDir != "" { 52 | return filepath.Join(cfgDir, azureProfileJSON), nil 53 | } 54 | return homedir.Expand("~/.azure/" + azureProfileJSON) 55 | } 56 | 57 | // LoadProfile restores a Profile object from a file located at 'path'. 58 | func LoadProfile(path string) (result Profile, err error) { 59 | var contents []byte 60 | contents, err = ioutil.ReadFile(path) 61 | if err != nil { 62 | err = fmt.Errorf("failed to open file (%s) while loading token: %v", path, err) 63 | return 64 | } 65 | reader := utfbom.SkipOnly(bytes.NewReader(contents)) 66 | 67 | dec := json.NewDecoder(reader) 68 | if err = dec.Decode(&result); err != nil { 69 | err = fmt.Errorf("failed to decode contents of file (%s) into a Profile representation: %v", path, err) 70 | return 71 | } 72 | 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /builder/azure/common/client/config_retriever.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package client 5 | 6 | import ( 7 | "encoding/json" 8 | "io" 9 | "net/http" 10 | ) 11 | 12 | // allow override for unit tests 13 | var getSubscriptionFromIMDS = _getSubscriptionFromIMDS 14 | 15 | func _getSubscriptionFromIMDS() (string, error) { 16 | client := &http.Client{} 17 | 18 | req, _ := http.NewRequest("GET", "http://169.254.169.254/metadata/instance/compute", nil) 19 | req.Header.Add("Metadata", "True") 20 | 21 | q := req.URL.Query() 22 | q.Add("format", "json") 23 | q.Add("api-version", "2017-08-01") 24 | 25 | req.URL.RawQuery = q.Encode() 26 | resp, err := client.Do(req) 27 | if err != nil { 28 | return "", err 29 | } 30 | 31 | defer resp.Body.Close() 32 | resp_body, _ := io.ReadAll(resp.Body) 33 | result := map[string]string{} 34 | err = json.Unmarshal(resp_body, &result) 35 | if err != nil { 36 | return "", err 37 | } 38 | 39 | return result["subscriptionId"], nil 40 | } 41 | -------------------------------------------------------------------------------- /builder/azure/common/client/config_retriever_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package client 5 | 6 | import ( 7 | "errors" 8 | "testing" 9 | 10 | "github.com/hashicorp/go-azure-sdk/sdk/environments" 11 | ) 12 | 13 | func TestConfigRetrieverLeavesTenantIDWhenNotEmpty(t *testing.T) { 14 | c := Config{CloudEnvironmentName: "AzurePublicCloud"} 15 | userSpecifiedTid := "not-empty" 16 | c.TenantID = userSpecifiedTid 17 | findTenantID = nil // assert that this not even called 18 | getSubscriptionFromIMDS = func() (string, error) { return "unittest", nil } 19 | if err := c.FillParameters(); err != nil { 20 | t.Errorf("Unexpected error when calling c.FillParameters: %v", err) 21 | } 22 | 23 | if expected := userSpecifiedTid; c.TenantID != expected { 24 | t.Errorf("Expected TenantID to be %q but got %q", expected, c.TenantID) 25 | } 26 | } 27 | 28 | func TestConfigRetrieverFillsTenantIDWhenEmpty(t *testing.T) { 29 | c := Config{CloudEnvironmentName: "AzurePublicCloud"} 30 | if expected := ""; c.TenantID != expected { 31 | t.Errorf("Expected TenantID to be %q but got %q", expected, c.TenantID) 32 | } 33 | 34 | retrievedTid := "my-tenant-id" 35 | findTenantID = func(environments.Environment, string) (string, error) { return retrievedTid, nil } 36 | getSubscriptionFromIMDS = func() (string, error) { return "unittest", nil } 37 | if err := c.FillParameters(); err != nil { 38 | t.Errorf("Unexpected error when calling c.FillParameters: %v", err) 39 | } 40 | 41 | if expected := retrievedTid; c.TenantID != expected { 42 | t.Errorf("Expected TenantID to be %q but got %q", expected, c.TenantID) 43 | } 44 | } 45 | 46 | func TestConfigRetrieverReturnsErrorWhenTenantIDEmptyAndRetrievalFails(t *testing.T) { 47 | c := Config{CloudEnvironmentName: "AzurePublicCloud"} 48 | if expected := ""; c.TenantID != expected { 49 | t.Errorf("Expected TenantID to be %q but got %q", expected, c.TenantID) 50 | } 51 | errorString := "sorry, I failed" 52 | findTenantID = func(environments.Environment, string) (string, error) { return "", errors.New(errorString) } 53 | getSubscriptionFromIMDS = func() (string, error) { return "unittest", nil } 54 | if err := c.FillParameters(); err != nil && err.Error() != errorString { 55 | t.Errorf("Unexpected error when calling c.FillParameters: %v", err) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /builder/azure/common/client/detect_azure.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | //go:build !linux 5 | // +build !linux 6 | 7 | package client 8 | 9 | // IsAzure returns true if Packer is running on Azure (currently only works on Linux) 10 | func IsAzure() bool { 11 | return false 12 | } 13 | -------------------------------------------------------------------------------- /builder/azure/common/client/detect_azure_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package client 5 | 6 | import ( 7 | "bytes" 8 | "io/ioutil" 9 | ) 10 | 11 | var ( 12 | smbiosAssetTagFile = "/sys/class/dmi/id/chassis_asset_tag" 13 | azureAssetTag = []byte("7783-7084-3265-9085-8269-3286-77\n") 14 | ) 15 | 16 | // IsAzure returns true if Packer is running on Azure 17 | func IsAzure() bool { 18 | return isAzureAssetTag(smbiosAssetTagFile) 19 | } 20 | 21 | func isAzureAssetTag(filename string) bool { 22 | if d, err := ioutil.ReadFile(filename); err == nil { 23 | return bytes.Equal(d, azureAssetTag) 24 | } 25 | return false 26 | } 27 | -------------------------------------------------------------------------------- /builder/azure/common/client/detect_azure_linux_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package client 5 | 6 | import ( 7 | "io/ioutil" 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestIsAzure(t *testing.T) { 15 | f, err := ioutil.TempFile("", "TestIsAzure*") 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | defer os.Remove(f.Name()) 20 | 21 | _, err = f.Seek(0, 0) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | err = f.Truncate(0) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | 31 | _, err = f.Write([]byte("not the azure assettag")) 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | 36 | assert.False(t, isAzureAssetTag(f.Name()), "asset tag is not Azure's") 37 | 38 | _, err = f.Seek(0, 0) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | err = f.Truncate(0) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | 48 | _, err = f.Write(azureAssetTag) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | assert.True(t, isAzureAssetTag(f.Name()), "asset tag is Azure's") 54 | } 55 | -------------------------------------------------------------------------------- /builder/azure/common/client/metadata.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package client 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | ) 12 | 13 | // DefaultMetadataClient is the default instance metadata client for Azure. Replace this variable for testing purposes only 14 | var DefaultMetadataClient = NewMetadataClient() 15 | 16 | // MetadataClientAPI holds methods that Packer uses to get information about the current VM 17 | type MetadataClientAPI interface { 18 | GetComputeInfo() (*ComputeInfo, error) 19 | } 20 | 21 | // MetadataClientStub is an easy way to put a test hook in DefaultMetadataClient 22 | type MetadataClientStub struct { 23 | ComputeInfo 24 | } 25 | 26 | // GetComputeInfo implements MetadataClientAPI 27 | func (s MetadataClientStub) GetComputeInfo() (*ComputeInfo, error) { 28 | return &s.ComputeInfo, nil 29 | } 30 | 31 | // ComputeInfo defines the Azure VM metadata that is used in Packer 32 | type ComputeInfo struct { 33 | Name string 34 | ResourceID string 35 | ResourceGroupName string 36 | SubscriptionID string 37 | Location string 38 | VmScaleSetName string 39 | } 40 | 41 | // metadataClient implements MetadataClient 42 | type metadataClient struct { 43 | } 44 | 45 | var _ MetadataClientAPI = metadataClient{} 46 | 47 | const imdsURL = "http://169.254.169.254/metadata/instance?api-version=2021-02-01" 48 | 49 | // VMResourceID returns the resource ID of the current VM 50 | func (client metadataClient) GetComputeInfo() (*ComputeInfo, error) { 51 | httpClient := &http.Client{} 52 | req, err := http.NewRequest("GET", imdsURL, nil) 53 | if err != nil { 54 | return nil, err 55 | } 56 | req.Header.Add("Metadata", "true") 57 | resp, err := httpClient.Do(req) 58 | if err != nil { 59 | return nil, err 60 | } 61 | defer resp.Body.Close() 62 | body, err := io.ReadAll(resp.Body) 63 | if err != nil { 64 | return nil, err 65 | } 66 | var vminfo struct { 67 | ComputeInfo `json:"compute"` 68 | } 69 | err = json.Unmarshal(body, &vminfo) 70 | if err != nil { 71 | return nil, err 72 | } 73 | return &vminfo.ComputeInfo, nil 74 | } 75 | 76 | func (ci ComputeInfo) GetResourceID() string { 77 | return fmt.Sprintf("/%s", ci.ResourceID) 78 | } 79 | 80 | // NewMetadataClient creates a new instance metadata client 81 | func NewMetadataClient() MetadataClientAPI { 82 | return metadataClient{} 83 | } 84 | -------------------------------------------------------------------------------- /builder/azure/common/client/normalize_location.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package client 5 | 6 | import "strings" 7 | 8 | // NormalizeLocation returns a normalized location string. 9 | // Strings are converted to lower case and spaces are removed. 10 | func NormalizeLocation(loc string) string { 11 | return strings.ReplaceAll(strings.ToLower(loc), " ", "") 12 | } 13 | -------------------------------------------------------------------------------- /builder/azure/common/client/normalize_location_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package client 5 | 6 | import "testing" 7 | 8 | func TestNormalizeLocation(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | loc string 12 | want string 13 | }{ 14 | {"removes spaces", " with spaces ", "withspaces"}, 15 | {"makes lowercase", "MiXed Case", "mixedcase"}, 16 | {"North East US", "North East US", "northeastus"}, 17 | } 18 | for _, tt := range tests { 19 | t.Run(tt.name, func(t *testing.T) { 20 | if got := NormalizeLocation(tt.loc); got != tt.want { 21 | t.Errorf("NormalizeLocation() = %v, want %v", got, tt.want) 22 | } 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /builder/azure/common/config.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | //go:generate packer-sdc struct-markdown 5 | 6 | package common 7 | 8 | import ( 9 | "github.com/hashicorp/packer-plugin-sdk/multistep" 10 | ) 11 | 12 | const ( 13 | SkippingImageCreation = "Skipping image creation..." 14 | ) 15 | 16 | type Config struct { 17 | // Skip creating the image. 18 | // Useful for setting to `true` during a build test stage. 19 | // Defaults to `false`. 20 | SkipCreateImage bool `mapstructure:"skip_create_image" required:"false"` 21 | } 22 | 23 | // CaptureSteps returns the steps unless `SkipCreateImage` is `true`. In that case it returns 24 | // a step that to inform the user that image capture is being skipped. 25 | func (config Config) CaptureSteps(say func(string), steps ...multistep.Step) []multistep.Step { 26 | if !config.SkipCreateImage { 27 | return steps 28 | } 29 | 30 | return []multistep.Step{ 31 | &StepNotify{ 32 | message: SkippingImageCreation, 33 | say: say, 34 | }, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /builder/azure/common/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/packer-plugin-sdk/multistep" 11 | "github.com/stretchr/testify/assert" 12 | 13 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common" 14 | ) 15 | 16 | func TestSkipCreateImageFalse(t *testing.T) { 17 | var said []string 18 | 19 | say := func(what string) { 20 | said = append(said, what) 21 | } 22 | 23 | config := common.Config{} 24 | message := "Capture Image" 25 | 26 | steps := config.CaptureSteps(say, common.NewStepNotify(message, say)) 27 | state := &multistep.BasicStateBag{} 28 | 29 | ctx := context.Background() 30 | 31 | for _, step := range steps { 32 | step.Run(ctx, state) 33 | } 34 | 35 | assert.Equal(t, said, []string{message}) 36 | } 37 | 38 | func TestSkipCreateImageTrue(t *testing.T) { 39 | var said []string 40 | 41 | say := func(what string) { 42 | said = append(said, what) 43 | } 44 | 45 | config := common.Config{ 46 | SkipCreateImage: true, 47 | } 48 | 49 | message := "Capture Image" 50 | 51 | steps := config.CaptureSteps(say, common.NewStepNotify(message, say)) 52 | state := &multistep.BasicStateBag{} 53 | 54 | ctx := context.Background() 55 | 56 | for _, step := range steps { 57 | step.Run(ctx, state) 58 | } 59 | 60 | assert.Equal(t, said, []string{common.SkippingImageCreation}) 61 | } 62 | -------------------------------------------------------------------------------- /builder/azure/common/constants/licenseTypes.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package constants 5 | 6 | // License types 7 | const ( 8 | License_RHEL string = "RHEL_BYOS" 9 | License_SUSE string = "SLES_BYOS" 10 | License_Windows_Client string = "Windows_Client" 11 | License_Windows_Server string = "Windows_Server" 12 | ) 13 | -------------------------------------------------------------------------------- /builder/azure/common/constants/securityTypes.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package constants 5 | 6 | // Security types 7 | const ( 8 | TrustedLaunch string = "TrustedLaunch" 9 | ConfidentialVM string = "ConfidentialVM" 10 | ) 11 | -------------------------------------------------------------------------------- /builder/azure/common/constants/targetplatforms.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package constants 5 | 6 | // Target types 7 | const ( 8 | Target_Linux string = "Linux" 9 | Target_Windows string = "Windows" 10 | ) 11 | -------------------------------------------------------------------------------- /builder/azure/common/dump_config.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | import ( 7 | "fmt" 8 | "reflect" 9 | "strings" 10 | 11 | "github.com/mitchellh/reflectwalk" 12 | ) 13 | 14 | type walker struct { 15 | depth int 16 | say func(string) 17 | } 18 | 19 | func newDumpConfig(say func(string)) *walker { 20 | return &walker{ 21 | depth: 0, 22 | say: say, 23 | } 24 | } 25 | 26 | func (s *walker) Enter(l reflectwalk.Location) error { 27 | s.depth += 1 28 | return nil 29 | } 30 | 31 | func (s *walker) Exit(l reflectwalk.Location) error { 32 | s.depth -= 1 33 | return nil 34 | } 35 | 36 | func (s *walker) Struct(v reflect.Value) error { 37 | return nil 38 | } 39 | 40 | func (s *walker) StructField(f reflect.StructField, v reflect.Value) error { 41 | if !s.shouldDump(v) { 42 | return nil 43 | } 44 | 45 | switch v.Kind() { 46 | case reflect.String: 47 | s.say(fmt.Sprintf("%s=%s", f.Name, s.formatValue(f.Name, v.String()))) 48 | } 49 | 50 | return nil 51 | } 52 | 53 | func (s *walker) shouldDump(v reflect.Value) bool { 54 | return s.depth == 2 && v.IsValid() && v.CanInterface() 55 | } 56 | 57 | func (s *walker) formatValue(name, value string) string { 58 | if s.isMaskable(name) { 59 | return strings.Repeat("*", len(value)) 60 | } 61 | 62 | return value 63 | } 64 | 65 | func (s *walker) isMaskable(name string) bool { 66 | up := strings.ToUpper(name) 67 | return strings.Contains(up, "SECRET") || strings.Contains(up, "PASSWORD") 68 | } 69 | 70 | func DumpConfig(config interface{}, say func(string)) { 71 | walker := newDumpConfig(say) 72 | _ = reflectwalk.Walk(config, walker) 73 | } 74 | -------------------------------------------------------------------------------- /builder/azure/common/env.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | import "os" 7 | 8 | const AzureDebugLogsEnvVar string = "PACKER_AZURE_DEBUG_LOG" 9 | 10 | func IsDebugEnabled() bool { 11 | debug, defined := os.LookupEnv(AzureDebugLogsEnvVar) 12 | if !defined { 13 | return false 14 | } 15 | 16 | return debug != "" 17 | } 18 | -------------------------------------------------------------------------------- /builder/azure/common/gluestrings.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | // removes overlap between the end of a and the start of b and 7 | // glues them together 8 | func GlueStrings(a, b string) string { 9 | shift := 0 10 | for shift < len(a) { 11 | i := 0 12 | for (i+shift < len(a)) && (i < len(b)) && (a[i+shift] == b[i]) { 13 | i++ 14 | } 15 | if i+shift == len(a) { 16 | break 17 | } 18 | shift++ 19 | } 20 | 21 | return a[:shift] + b 22 | } 23 | -------------------------------------------------------------------------------- /builder/azure/common/gluestrings_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestGlueStrings(t *testing.T) { 11 | cases := []struct{ a, b, expected string }{ 12 | { 13 | "Some log that starts in a", 14 | "starts in a, but continues in b", 15 | "Some log that starts in a, but continues in b", 16 | }, 17 | { 18 | "", 19 | "starts in b", 20 | "starts in b", 21 | }, 22 | } 23 | for _, testcase := range cases { 24 | t.Logf("testcase: %+v\n", testcase) 25 | 26 | result := GlueStrings(testcase.a, testcase.b) 27 | t.Logf("result: '%s'", result) 28 | 29 | if result != testcase.expected { 30 | t.Errorf("expected %q, got %q", testcase.expected, result) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /builder/azure/common/inspector.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | import ( 7 | "bytes" 8 | "net/http" 9 | 10 | "io" 11 | 12 | "github.com/hashicorp/go-azure-sdk/sdk/client" 13 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/log" 14 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/logutil" 15 | ) 16 | 17 | func chop(data []byte, maxlen int64) string { 18 | s := string(data) 19 | if int64(len(s)) > maxlen { 20 | s = s[:maxlen] + "..." 21 | } 22 | return s 23 | } 24 | 25 | func HandleBody(body io.ReadCloser, maxlen int64) (io.ReadCloser, string) { 26 | if body == nil { 27 | return nil, "" 28 | } 29 | 30 | defer body.Close() 31 | 32 | b, err := io.ReadAll(body) 33 | if err != nil { 34 | return nil, "" 35 | } 36 | 37 | return io.NopCloser(bytes.NewReader(b)), chop(b, maxlen) 38 | } 39 | 40 | func WithInspection(maxlen int64) client.RequestMiddleware { 41 | return func(r *http.Request) (*http.Request, error) { 42 | if IsDebugEnabled() { 43 | body, bodyString := HandleBody(r.Body, maxlen) 44 | r.Body = body 45 | 46 | log.Print("Azure request", logutil.Fields{ 47 | "method": r.Method, 48 | "request": r.URL.String(), 49 | "body": bodyString, 50 | }) 51 | } 52 | return r, nil 53 | } 54 | } 55 | 56 | func ByInspecting(maxlen int64) client.ResponseMiddleware { 57 | return func(req *http.Request, resp *http.Response) (*http.Response, error) { 58 | if IsDebugEnabled() { 59 | body, bodyString := HandleBody(resp.Body, maxlen) 60 | resp.Body = body 61 | 62 | log.Print("Azure response", logutil.Fields{ 63 | "status": resp.Status, 64 | "method": resp.Request.Method, 65 | "request": resp.Request.URL.String(), 66 | "x-ms-request-id": ExtractRequestID(resp), 67 | "body": bodyString, 68 | }) 69 | } 70 | 71 | return resp, nil 72 | } 73 | } 74 | 75 | // ExtractRequestID extracts the Azure server generated request identifier from the 76 | // x-ms-request-id header. 77 | func ExtractRequestID(resp *http.Response) string { 78 | if resp != nil && resp.Header != nil { 79 | header := resp.Header[http.CanonicalHeaderKey("x-ms-request-id")] 80 | if len(header) > 0 { 81 | return header[0] 82 | } 83 | } 84 | return "" 85 | } 86 | -------------------------------------------------------------------------------- /builder/azure/common/log/log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | // log allows users to replicate Packer's behaviour with logging, i.e. mask 5 | // potential secrets by replacing them if they occur with ``. 6 | // 7 | // This is intended as a drop-in replacement for the standard `log` package, 8 | // and relies on it for final printing. 9 | package log 10 | 11 | import ( 12 | "fmt" 13 | "log" 14 | 15 | "github.com/hashicorp/packer-plugin-sdk/packer" 16 | ) 17 | 18 | func Print(v ...any) { 19 | raw := string(fmt.Append(nil, v...)) 20 | log.Print(packer.LogSecretFilter.FilterString(raw)) 21 | } 22 | 23 | func Printf(format string, v ...any) { 24 | raw := string(fmt.Appendf(nil, format, v...)) 25 | log.Print(packer.LogSecretFilter.FilterString(raw)) 26 | } 27 | 28 | func Println(v ...any) { 29 | raw := string(fmt.Appendln(nil, v...)) 30 | log.Print(packer.LogSecretFilter.FilterString(raw)) 31 | } 32 | -------------------------------------------------------------------------------- /builder/azure/common/logutil/logfields.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package logutil 5 | 6 | import "fmt" 7 | 8 | type Fields map[string]interface{} 9 | 10 | func (f Fields) String() string { 11 | var s string 12 | for k, v := range f { 13 | if sv, ok := v.(string); ok { 14 | v = fmt.Sprintf("%q", sv) 15 | } 16 | s += fmt.Sprintf(" %s=%v", k, v) 17 | } 18 | return s 19 | } 20 | -------------------------------------------------------------------------------- /builder/azure/common/map.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | func MapToAzureTags(in map[string]string) map[string]*string { 7 | res := map[string]*string{} 8 | for k := range in { 9 | v := in[k] 10 | res[k] = &v 11 | } 12 | return res 13 | } 14 | -------------------------------------------------------------------------------- /builder/azure/common/pointer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | //go:generate packer-sdc struct-markdown 5 | 6 | package common 7 | 8 | // StringPtr returns a pointer to the passed string. 9 | func StringPtr(s string) *string { 10 | return &s 11 | } 12 | 13 | // BoolPtr returns a pointer to the passed bool. 14 | func BoolPtr(b bool) *bool { 15 | return &b 16 | } 17 | 18 | // IntPtr returns a pointer to the passed int. 19 | func IntPtr(i int) *int { 20 | return &i 21 | } 22 | 23 | // Int32Ptr returns a pointer to the passed int32. 24 | func Int32Ptr(i int32) *int32 { 25 | return &i 26 | } 27 | 28 | // Int64Ptr returns a pointer to the passed int64. 29 | func Int64Ptr(i int64) *int64 { 30 | return &i 31 | } 32 | 33 | // Float64Ptr returns a pointer to the passed float64. 34 | func Float64Ptr(i float64) *float64 { 35 | return &i 36 | } 37 | -------------------------------------------------------------------------------- /builder/azure/common/state_bag.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | import "github.com/hashicorp/packer-plugin-sdk/multistep" 7 | 8 | func IsStateCancelled(stateBag multistep.StateBag) bool { 9 | _, ok := stateBag.GetOk(multistep.StateCancelled) 10 | return ok 11 | } 12 | -------------------------------------------------------------------------------- /builder/azure/common/step_notify.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/packer-plugin-sdk/multistep" 10 | ) 11 | 12 | type StepNotify struct { 13 | message string 14 | say func(string) 15 | } 16 | 17 | func NewStepNotify(message string, say func(string)) *StepNotify { 18 | return &StepNotify{ 19 | message: message, 20 | say: say, 21 | } 22 | } 23 | 24 | func (step *StepNotify) Run( 25 | ctx context.Context, 26 | state multistep.StateBag, 27 | ) multistep.StepAction { 28 | step.say(step.message) 29 | return multistep.ActionContinue 30 | } 31 | 32 | func (step *StepNotify) Cleanup(state multistep.StateBag) {} 33 | 34 | var _ multistep.Step = (*StepNotify)(nil) 35 | -------------------------------------------------------------------------------- /builder/azure/common/step_notify_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/packer-plugin-sdk/multistep" 11 | "github.com/stretchr/testify/assert" 12 | 13 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common" 14 | ) 15 | 16 | func TestStepNotify(t *testing.T) { 17 | var said []string 18 | 19 | say := func(what string) { 20 | said = append(said, what) 21 | } 22 | 23 | message := "Notify Step" 24 | 25 | step := common.NewStepNotify(message, say) 26 | state := &multistep.BasicStateBag{} 27 | 28 | ctx := context.Background() 29 | 30 | step.Run(ctx, state) 31 | 32 | assert.Equal(t, said, []string{message}) 33 | } 34 | -------------------------------------------------------------------------------- /builder/azure/common/strings_contains.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | import "strings" 7 | 8 | // StringsContains returns true if the `haystack` contains the `needle`. Search is case insensitive. 9 | func StringsContains(haystack []string, needle string) bool { 10 | for _, s := range haystack { 11 | if strings.EqualFold(s, needle) { 12 | return true 13 | } 14 | } 15 | return false 16 | } 17 | -------------------------------------------------------------------------------- /builder/azure/common/strings_contains_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | import "testing" 7 | 8 | func TestStringsContains(t *testing.T) { 9 | 10 | tests := []struct { 11 | name string 12 | haystack []string 13 | needle string 14 | want bool 15 | }{ 16 | { 17 | name: "found", 18 | haystack: []string{"a", "b", "c"}, 19 | needle: "b", 20 | want: true, 21 | }, 22 | { 23 | name: "missing", 24 | haystack: []string{"a", "b", "c"}, 25 | needle: "D", 26 | want: false, 27 | }, 28 | { 29 | name: "case insensitive", 30 | haystack: []string{"a", "b", "c"}, 31 | needle: "B", 32 | want: true, 33 | }, 34 | } 35 | for _, tt := range tests { 36 | t.Run(tt.name, func(t *testing.T) { 37 | if got := StringsContains(tt.haystack, tt.needle); got != tt.want { 38 | t.Errorf("StringsContains() = %v, want %v", got, tt.want) 39 | } 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /builder/azure/common/template/template_parameters.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package template 5 | 6 | // The intent of these types to facilitate interchange with Azure in the 7 | // appropriate JSON format. A sample format is below. Each parameter listed 8 | // below corresponds to a parameter defined in the template. 9 | // 10 | // { 11 | // "storageAccountName": { 12 | // "value" : "my_storage_account_name" 13 | // }, 14 | // "adminUserName" : { 15 | // "value": "admin" 16 | // } 17 | // } 18 | 19 | type TemplateParameter struct { 20 | Value string `json:"value"` 21 | } 22 | 23 | type TemplateParameters struct { 24 | AdminUsername *TemplateParameter `json:"adminUsername,omitempty"` 25 | AdminPassword *TemplateParameter `json:"adminPassword,omitempty"` 26 | CommandToExecute *TemplateParameter `json:"commandToExecute,omitempty"` 27 | DnsNameForPublicIP *TemplateParameter `json:"dnsNameForPublicIP,omitempty"` 28 | KeyVaultName *TemplateParameter `json:"keyVaultName,omitempty"` 29 | KeyVaultSKU *TemplateParameter `json:"keyVaultSKU,omitempty"` 30 | KeyVaultSecretName *TemplateParameter `json:"keyVaultSecretName,omitempty"` 31 | KeyVaultSecretValue *TemplateParameter `json:"keyVaultSecretValue,omitempty"` 32 | ObjectId *TemplateParameter `json:"objectId,omitempty"` 33 | NicName *TemplateParameter `json:"nicName,omitempty"` 34 | OSDiskName *TemplateParameter `json:"osDiskName,omitempty"` 35 | DataDiskName *TemplateParameter `json:"dataDiskName,omitempty"` 36 | PublicIPAddressName *TemplateParameter `json:"publicIPAddressName,omitempty"` 37 | StorageAccountBlobEndpoint *TemplateParameter `json:"storageAccountBlobEndpoint,omitempty"` 38 | SubnetName *TemplateParameter `json:"subnetName,omitempty"` 39 | TenantId *TemplateParameter `json:"tenantId,omitempty"` 40 | VirtualNetworkName *TemplateParameter `json:"virtualNetworkName,omitempty"` 41 | NsgName *TemplateParameter `json:"nsgName,omitempty"` 42 | VMSize *TemplateParameter `json:"vmSize,omitempty"` 43 | VMName *TemplateParameter `json:"vmName,omitempty"` 44 | } 45 | -------------------------------------------------------------------------------- /builder/azure/common/template_funcs.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | import ( 7 | "bytes" 8 | "text/template" 9 | ) 10 | 11 | func isValidByteValue(b byte) bool { 12 | if '0' <= b && b <= '9' { 13 | return true 14 | } 15 | if 'a' <= b && b <= 'z' { 16 | return true 17 | } 18 | if 'A' <= b && b <= 'Z' { 19 | return true 20 | } 21 | return b == '.' || b == '_' || b == '-' 22 | } 23 | 24 | // Clean up image name by replacing invalid characters with "-" 25 | // Names are not allowed to end in '.', '-', or '_' and are trimmed. 26 | func templateCleanImageName(s string) string { 27 | b := []byte(s) 28 | newb := make([]byte, len(b)) 29 | for i := range newb { 30 | if isValidByteValue(b[i]) { 31 | newb[i] = b[i] 32 | } else { 33 | newb[i] = '-' 34 | } 35 | } 36 | 37 | newb = bytes.TrimRight(newb, "-_.") 38 | return string(newb) 39 | } 40 | 41 | var TemplateFuncs = template.FuncMap{ 42 | "clean_resource_name": templateCleanImageName, 43 | } 44 | -------------------------------------------------------------------------------- /builder/azure/common/template_funcs_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package common 5 | 6 | import "testing" 7 | 8 | func TestTemplateCleanImageName(t *testing.T) { 9 | vals := []struct { 10 | origName string 11 | expected string 12 | }{ 13 | // test that valid name is unchanged 14 | { 15 | origName: "abcde-012345xyz", 16 | expected: "abcde-012345xyz", 17 | }, 18 | // test that colons are converted to hyphens 19 | { 20 | origName: "abcde-012345v1.0:0", 21 | expected: "abcde-012345v1.0-0", 22 | }, 23 | // Name starting with number is not valid, but not in scope of this 24 | // function to correct 25 | { 26 | origName: "012345v1.0:0", 27 | expected: "012345v1.0-0", 28 | }, 29 | // Name over 80 chars is not valid, but not corrected by this function. 30 | { 31 | origName: "l012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", 32 | expected: "l012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", 33 | }, 34 | // Name cannot end in a -Name over 80 chars is not valid, but not corrected by this function. 35 | { 36 | origName: "abcde-:_", 37 | expected: "abcde", 38 | }, 39 | // Lost of special characters 40 | { 41 | origName: "My()./-_:&^ $%[]#'@name", 42 | expected: "My--.--_-----------name", 43 | }, 44 | } 45 | 46 | for _, v := range vals { 47 | name := templateCleanImageName(v.origName) 48 | if name != v.expected { 49 | t.Fatalf("template names do not match: expected %s got %s\n", v.expected, name) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /builder/azure/dtl/WindowsMixAndMatch.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `client_id`}}", 4 | "client_secret": "{{env `client_secret`}}", 5 | "tenant_id": "{{env `tenant_id`}}", 6 | "subscription_id": "{{env `subscription_id`}}", 7 | "lab_name": "NewpackerRG", 8 | "resource_group_name": "NewPackerRG", 9 | "vm_name": "win21" 10 | }, 11 | "builders": [ 12 | { 13 | "type": "azure-dtl", 14 | "client_id": "{{user `client_id`}}", 15 | "client_secret": "{{user `client_secret`}}", 16 | "tenant_id": "{{user `tenant_id`}}", 17 | "subscription_id": "{{user `subscription_id`}}", 18 | "managed_image_resource_group_name": "NewPackerRG", 19 | "managed_image_name": "PackerImage2", 20 | "os_type": "Windows", 21 | "image_publisher": "MicrosoftWindowsDesktop", 22 | "image_offer": "Windows-10", 23 | "image_sku": "19h1-ent", 24 | "azure_tags": { 25 | "dept": "Engineering", 26 | "task": "Image deployment" 27 | }, 28 | "dtl_artifacts": [ 29 | { 30 | "artifact_name": "windows-7zip" 31 | }, 32 | { 33 | "artifact_name": "windows-mongodb" 34 | } 35 | ], 36 | "lab_name": "{{user `lab_name`}}", 37 | "vm_name": "{{user `vm_name`}}", 38 | "lab_virtual_network_name": "dtlnewpackerrg", 39 | "lab_resource_group_name": "{{user `resource_group_name`}}", 40 | "lab_subnet_name": "dtlnewpackerrgSubnet", 41 | "location": "Central US", 42 | "vm_size": "Standard_DS2_v2" 43 | } 44 | ], 45 | "provisioners": [ 46 | { 47 | "type": "azure-dtlartifact", 48 | "client_id": "{{user `client_id`}}", 49 | "client_secret": "{{user `client_secret`}}", 50 | "tenant_id": "{{user `tenant_id`}}", 51 | "subscription_id": "{{user `subscription_id`}}", 52 | "lab_name": "{{user `lab_name`}}", 53 | "resource_group_name": "{{user `resource_group_name`}}", 54 | "vm_name": "{{user `vm_name`}}", 55 | "dtl_artifacts": [ 56 | { 57 | "artifact_name": "windows-chrome" 58 | }, 59 | { 60 | "artifact_name": "windows-azurepowershell" 61 | } 62 | ] 63 | } 64 | ] 65 | } -------------------------------------------------------------------------------- /builder/azure/dtl/WindowsSimple.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `client_id`}}", 4 | "client_secret": "{{env `client_secret`}}", 5 | "tenant_id": "{{env `tenant_id`}}", 6 | "subscription_id": "{{env `subscription_id`}}", 7 | "lab_name": "NewpackerRG", 8 | "resource_group_name": "NewPackerRG", 9 | "vm_name": "win18" 10 | }, 11 | "builders": [ 12 | { 13 | "type": "azure-dtl", 14 | "client_id": "{{user `client_id`}}", 15 | "client_secret": "{{user `client_secret`}}", 16 | "tenant_id": "{{user `tenant_id`}}", 17 | "subscription_id": "{{user `subscription_id`}}", 18 | "managed_image_resource_group_name": "NewPackerRG", 19 | "managed_image_name": "PackerImage2", 20 | "os_type": "Windows", 21 | "image_publisher": "MicrosoftWindowsDesktop", 22 | "image_offer": "Windows-10", 23 | "image_sku": "19h1-ent", 24 | "azure_tags": { 25 | "dept": "Engineering", 26 | "task": "Image deployment" 27 | }, 28 | "lab_name": "{{user `lab_name`}}", 29 | "vm_name": "{{user `vm_name`}}", 30 | "lab_virtual_network_name": "dtlnewpackerrg", 31 | "lab_resource_group_name": "{{user `resource_group_name`}}", 32 | "lab_subnet_name": "dtlnewpackerrgSubnet", 33 | "location": "Central US", 34 | "vm_size": "Standard_DS2_v2" 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /builder/azure/dtl/azure_error_response.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package dtl 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "fmt" 10 | ) 11 | 12 | type azureErrorDetails struct { 13 | Code string `json:"code"` 14 | Message string `json:"message"` 15 | Details []azureErrorDetails `json:"details"` 16 | } 17 | 18 | type azureErrorResponse struct { 19 | ErrorDetails azureErrorDetails `json:"error"` 20 | } 21 | 22 | func newAzureErrorResponse(s string) *azureErrorResponse { 23 | var errorResponse azureErrorResponse 24 | err := json.Unmarshal([]byte(s), &errorResponse) 25 | if err == nil { 26 | return &errorResponse 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func (e *azureErrorDetails) isEmpty() bool { 33 | return e.Code == "" 34 | } 35 | 36 | func (e *azureErrorResponse) Error() string { 37 | var buf bytes.Buffer 38 | //buf.WriteString("-=-=- ERROR -=-=-") 39 | formatAzureErrorResponse(e.ErrorDetails, &buf, "") 40 | //buf.WriteString("-=-=- ERROR -=-=-") 41 | return buf.String() 42 | } 43 | 44 | // format a Azure Error Response by recursing through the JSON structure. 45 | // 46 | // Errors may contain nested errors, which are JSON documents that have been 47 | // serialized and escaped. Keep following this nesting all the way down... 48 | func formatAzureErrorResponse(error azureErrorDetails, buf *bytes.Buffer, indent string) { 49 | if error.isEmpty() { 50 | return 51 | } 52 | 53 | buf.WriteString(fmt.Sprintf("ERROR: %s-> %s : %s\n", indent, error.Code, error.Message)) 54 | for _, x := range error.Details { 55 | newIndent := fmt.Sprintf("%s ", indent) 56 | 57 | var aer azureErrorResponse 58 | err := json.Unmarshal([]byte(x.Message), &aer) 59 | if err == nil { 60 | buf.WriteString(fmt.Sprintf("ERROR: %s-> %s\n", newIndent, x.Code)) 61 | formatAzureErrorResponse(aer.ErrorDetails, buf, newIndent) 62 | } else { 63 | buf.WriteString(fmt.Sprintf("ERROR: %s-> %s : %s\n", newIndent, x.Code, x.Message)) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /builder/azure/dtl/azure_error_response_test.TestAzureErrorNestedShouldFormat.approved.txt: -------------------------------------------------------------------------------- 1 | ERROR: -> DeploymentFailed : At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details. 2 | ERROR: -> BadRequest 3 | ERROR: -> InvalidRequestFormat : Cannot parse the request. 4 | ERROR: -> InvalidJson : Error converting value "playground" to type 'Microsoft.WindowsAzure.Networking.Nrp.Frontend.Contract.Csm.Public.IpAllocationMethod'. Path 'properties.publicIPAllocationMethod', line 1, position 130. 5 | -------------------------------------------------------------------------------- /builder/azure/dtl/azure_error_response_test.TestAzureErrorSimpleShouldFormat.approved.txt: -------------------------------------------------------------------------------- 1 | ERROR: -> ResourceNotFound : The Resource 'Microsoft.Compute/images/PackerUbuntuImage' under resource group 'packer-test00' was not found. 2 | -------------------------------------------------------------------------------- /builder/azure/dtl/builder_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package dtl 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/constants" 10 | ) 11 | 12 | func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) { 13 | var testSubject = &Builder{} 14 | _, _, err := testSubject.Prepare(getDtlBuilderConfiguration(), getPackerConfiguration()) 15 | if err != nil { 16 | t.Fatalf("failed to prepare: %s", err) 17 | } 18 | 19 | var expectedStateBagKeys = []string{ 20 | constants.AuthorizedKey, 21 | 22 | constants.ArmTags, 23 | constants.ArmComputeName, 24 | constants.ArmDeploymentName, 25 | constants.ArmNicName, 26 | constants.ArmResourceGroupName, 27 | constants.ArmVirtualMachineCaptureParameters, 28 | constants.ArmPublicIPAddressName, 29 | } 30 | 31 | for _, v := range expectedStateBagKeys { 32 | if _, ok := testSubject.stateBag.GetOk(v); ok == false { 33 | t.Errorf("Expected the builder's state bag to contain '%s', but it did not.", v) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /builder/azure/dtl/openssh_key_pair.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package dtl 5 | 6 | import ( 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "crypto/x509" 10 | "encoding/base64" 11 | "encoding/pem" 12 | "fmt" 13 | "time" 14 | 15 | "golang.org/x/crypto/ssh" 16 | ) 17 | 18 | const ( 19 | KeySize = 2048 20 | ) 21 | 22 | type OpenSshKeyPair struct { 23 | privateKey *rsa.PrivateKey 24 | publicKey ssh.PublicKey 25 | } 26 | 27 | func NewOpenSshKeyPair() (*OpenSshKeyPair, error) { 28 | return NewOpenSshKeyPairWithSize(KeySize) 29 | } 30 | 31 | func NewOpenSshKeyPairWithSize(keySize int) (*OpenSshKeyPair, error) { 32 | privateKey, err := rsa.GenerateKey(rand.Reader, keySize) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | return &OpenSshKeyPair{ 43 | privateKey: privateKey, 44 | publicKey: publicKey, 45 | }, nil 46 | } 47 | 48 | func (s *OpenSshKeyPair) AuthorizedKey() string { 49 | return fmt.Sprintf("%s %s packer Azure Deployment%s", 50 | s.publicKey.Type(), 51 | base64.StdEncoding.EncodeToString(s.publicKey.Marshal()), 52 | time.Now().Format(time.RFC3339)) 53 | } 54 | 55 | func (s *OpenSshKeyPair) PrivateKey() []byte { 56 | privateKey := pem.EncodeToMemory(&pem.Block{ 57 | Type: "RSA PRIVATE KEY", 58 | Bytes: x509.MarshalPKCS1PrivateKey(s.privateKey), 59 | }) 60 | 61 | return privateKey 62 | } 63 | -------------------------------------------------------------------------------- /builder/azure/dtl/openssh_key_pair_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package dtl 5 | 6 | import ( 7 | "testing" 8 | 9 | "golang.org/x/crypto/ssh" 10 | ) 11 | 12 | func TestFart(t *testing.T) { 13 | 14 | } 15 | 16 | func TestAuthorizedKeyShouldParse(t *testing.T) { 17 | testSubject, err := NewOpenSshKeyPairWithSize(512) 18 | if err != nil { 19 | t.Fatalf("Failed to create a new OpenSSH key pair, err=%s.", err) 20 | } 21 | 22 | authorizedKey := testSubject.AuthorizedKey() 23 | 24 | _, _, _, _, err = ssh.ParseAuthorizedKey([]byte(authorizedKey)) 25 | if err != nil { 26 | t.Fatalf("Failed to parse the authorized key, err=%s", err) 27 | } 28 | } 29 | 30 | func TestPrivateKeyShouldParse(t *testing.T) { 31 | testSubject, err := NewOpenSshKeyPairWithSize(512) 32 | if err != nil { 33 | t.Fatalf("Failed to create a new OpenSSH key pair, err=%s.", err) 34 | } 35 | 36 | _, err = ssh.ParsePrivateKey(testSubject.PrivateKey()) 37 | if err != nil { 38 | t.Fatalf("Failed to parse the private key, err=%s\n", err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /builder/azure/dtl/resource_resolver.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package dtl 5 | 6 | // Code to resolve resources that are required by the API. These resources 7 | // can most likely be resolved without asking the user, thereby reducing the 8 | // amount of configuration they need to provide. 9 | // 10 | // Resource resolver differs from config retriever because resource resolver 11 | // requires a client to communicate with the Azure API. A config retriever is 12 | // used to determine values without use of a client. 13 | 14 | import ( 15 | "context" 16 | "fmt" 17 | "strings" 18 | 19 | "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" 20 | "github.com/hashicorp/go-azure-sdk/resource-manager/compute/2022-03-01/images" 21 | ) 22 | 23 | type resourceResolver struct { 24 | client *AzureClient 25 | } 26 | 27 | func newResourceResolver(client *AzureClient) *resourceResolver { 28 | return &resourceResolver{ 29 | client: client, 30 | } 31 | } 32 | 33 | func (s *resourceResolver) Resolve(c *Config) error { 34 | if s.shouldResolveManagedImageName(c) { 35 | image, err := findManagedImageByName(s.client, c.ClientConfig.SubscriptionID, c.CustomManagedImageName, c.CustomManagedImageResourceGroupName) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | c.customManagedImageID = *image.Id 41 | } 42 | 43 | return nil 44 | } 45 | 46 | func (s *resourceResolver) shouldResolveManagedImageName(c *Config) bool { 47 | return c.CustomManagedImageName != "" 48 | } 49 | 50 | func findManagedImageByName(client *AzureClient, name, subscriptionId, resourceGroupName string) (*images.Image, error) { 51 | managedImageContext, cancel := context.WithTimeout(context.TODO(), client.PollingDuration) 52 | defer cancel() 53 | id := commonids.NewResourceGroupID(subscriptionId, resourceGroupName) 54 | images, err := client.ImagesClient.ListByResourceGroupComplete(managedImageContext, id) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | for _, image := range images.Items { 60 | if strings.EqualFold(name, *image.Name) { 61 | return &image, nil 62 | } 63 | } 64 | return nil, fmt.Errorf("Cannot find an image named '%s' in the resource group '%s'", name, resourceGroupName) 65 | } 66 | -------------------------------------------------------------------------------- /builder/azure/dtl/step.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package dtl 5 | 6 | import ( 7 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/constants" 8 | "github.com/hashicorp/packer-plugin-sdk/multistep" 9 | ) 10 | 11 | func processStepResult( 12 | err error, sayError func(error), state multistep.StateBag) multistep.StepAction { 13 | 14 | if err != nil { 15 | state.Put(constants.Error, err) 16 | sayError(err) 17 | 18 | return multistep.ActionHalt 19 | } 20 | 21 | return multistep.ActionContinue 22 | 23 | } 24 | -------------------------------------------------------------------------------- /builder/azure/dtl/step_power_off_compute.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package dtl 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/go-azure-sdk/resource-manager/devtestlab/2018-09-15/virtualmachines" 11 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/constants" 12 | "github.com/hashicorp/packer-plugin-sdk/multistep" 13 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 14 | ) 15 | 16 | type StepPowerOffCompute struct { 17 | client *AzureClient 18 | config *Config 19 | powerOff func(ctx context.Context, resourceGroupName string, labName, computeName string) error 20 | say func(message string) 21 | error func(e error) 22 | } 23 | 24 | func NewStepPowerOffCompute(client *AzureClient, ui packersdk.Ui, config *Config) *StepPowerOffCompute { 25 | 26 | var step = &StepPowerOffCompute{ 27 | client: client, 28 | config: config, 29 | say: func(message string) { ui.Say(message) }, 30 | error: func(e error) { ui.Error(e.Error()) }, 31 | } 32 | 33 | step.powerOff = step.powerOffCompute 34 | return step 35 | } 36 | 37 | func (s *StepPowerOffCompute) powerOffCompute(ctx context.Context, resourceGroupName string, labName, computeName string) error { 38 | pollingContext, cancel := context.WithTimeout(ctx, s.client.PollingDuration) 39 | defer cancel() 40 | vmResourceId := virtualmachines.NewVirtualMachineID(s.config.ClientConfig.SubscriptionID, s.config.tmpResourceGroupName, labName, computeName) 41 | err := s.client.DtlMetaClient.VirtualMachines.StopThenPoll(pollingContext, vmResourceId) 42 | if err != nil { 43 | s.say(s.client.LastError.Error()) 44 | } 45 | return err 46 | } 47 | 48 | func (s *StepPowerOffCompute) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 49 | s.say("Powering off machine ...") 50 | 51 | var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string) 52 | var computeName = state.Get(constants.ArmComputeName).(string) 53 | 54 | s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName)) 55 | s.say(fmt.Sprintf(" -> ComputeName : '%s'", computeName)) 56 | 57 | err := s.powerOff(ctx, s.config.LabResourceGroupName, s.config.LabName, computeName) 58 | 59 | s.say("Powering off machine ...Complete") 60 | return processStepResult(err, s.error, state) 61 | } 62 | 63 | func (*StepPowerOffCompute) Cleanup(multistep.StateBag) { 64 | } 65 | -------------------------------------------------------------------------------- /builder/azure/dtl/step_save_winrm_password.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package dtl 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/packer-plugin-sdk/multistep" 10 | packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 11 | ) 12 | 13 | type StepSaveWinRMPassword struct { 14 | Password string 15 | BuildName string 16 | } 17 | 18 | func (s *StepSaveWinRMPassword) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 19 | // store so that we can access this later during provisioning 20 | state.Put("winrm_password", s.Password) 21 | packersdk.LogSecretFilter.Set(s.Password) 22 | return multistep.ActionContinue 23 | } 24 | 25 | func (s *StepSaveWinRMPassword) Cleanup(multistep.StateBag) {} 26 | -------------------------------------------------------------------------------- /builder/azure/dtl/step_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package dtl 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/hashicorp/packer-plugin-azure/builder/azure/common/constants" 11 | "github.com/hashicorp/packer-plugin-sdk/multistep" 12 | ) 13 | 14 | func TestProcessStepResultShouldContinueForNonErrors(t *testing.T) { 15 | stateBag := new(multistep.BasicStateBag) 16 | 17 | code := processStepResult(nil, func(error) { t.Fatal("Should not be called!") }, stateBag) 18 | if _, ok := stateBag.GetOk(constants.Error); ok { 19 | t.Errorf("Error was nil, but was still in the state bag.") 20 | } 21 | 22 | if code != multistep.ActionContinue { 23 | t.Errorf("Expected ActionContinue(%d), but got=%d", multistep.ActionContinue, code) 24 | } 25 | } 26 | 27 | func TestProcessStepResultShouldHaltOnError(t *testing.T) { 28 | stateBag := new(multistep.BasicStateBag) 29 | isSaidError := false 30 | 31 | code := processStepResult(fmt.Errorf("boom"), func(error) { isSaidError = true }, stateBag) 32 | if _, ok := stateBag.GetOk(constants.Error); !ok { 33 | t.Errorf("Error was non nil, but was not in the state bag.") 34 | } 35 | 36 | if !isSaidError { 37 | t.Errorf("Expected error to be said, but it was not.") 38 | } 39 | 40 | if code != multistep.ActionHalt { 41 | t.Errorf("Expected ActionHalt(%d), but got=%d", multistep.ActionHalt, code) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /builder/azure/dtl/template_funcs.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package dtl 5 | 6 | import ( 7 | "bytes" 8 | "text/template" 9 | 10 | packertpl "github.com/hashicorp/packer-plugin-sdk/template" 11 | ) 12 | 13 | func isValidByteValue(b byte) bool { 14 | if '0' <= b && b <= '9' { 15 | return true 16 | } 17 | if 'a' <= b && b <= 'z' { 18 | return true 19 | } 20 | if 'A' <= b && b <= 'Z' { 21 | return true 22 | } 23 | return b == '.' || b == '_' || b == '-' 24 | } 25 | 26 | // Clean up image name by replacing invalid characters with "-" 27 | // Names are not allowed to end in '.', '-', or '_' and are trimmed. 28 | func templateCleanImageName(s string) string { 29 | if ok, _ := assertManagedImageName(s, ""); ok { 30 | return s 31 | } 32 | b := []byte(s) 33 | newb := make([]byte, len(b)) 34 | for i := range newb { 35 | if isValidByteValue(b[i]) { 36 | newb[i] = b[i] 37 | } else { 38 | newb[i] = '-' 39 | } 40 | } 41 | 42 | newb = bytes.TrimRight(newb, "-_.") 43 | return string(newb) 44 | } 45 | 46 | var TemplateFuncs = template.FuncMap{ 47 | "clean_resource_name": templateCleanImageName, 48 | "clean_image_name": packertpl.DeprecatedTemplateFunc("clean_image_name", "clean_resource_name", templateCleanImageName), 49 | } 50 | -------------------------------------------------------------------------------- /builder/azure/dtl/template_funcs_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package dtl 5 | 6 | import "testing" 7 | 8 | func TestTemplateCleanImageName(t *testing.T) { 9 | vals := []struct { 10 | origName string 11 | expected string 12 | }{ 13 | // test that valid name is unchanged 14 | { 15 | origName: "abcde-012345xyz", 16 | expected: "abcde-012345xyz", 17 | }, 18 | // test that colons are converted to hyphens 19 | { 20 | origName: "abcde-012345v1.0:0", 21 | expected: "abcde-012345v1.0-0", 22 | }, 23 | // Name starting with number is not valid, but not in scope of this 24 | // function to correct 25 | { 26 | origName: "012345v1.0:0", 27 | expected: "012345v1.0-0", 28 | }, 29 | // Name over 80 chars is not valid, but not corrected by this function. 30 | { 31 | origName: "l012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", 32 | expected: "l012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", 33 | }, 34 | // Name cannot end in a -Name over 80 chars is not valid, but not corrected by this function. 35 | { 36 | origName: "abcde-:_", 37 | expected: "abcde", 38 | }, 39 | // Lost of special characters 40 | { 41 | origName: "My()./-_:&^ $%[]#'@name", 42 | expected: "My--.--_-----------name", 43 | }, 44 | } 45 | 46 | for _, v := range vals { 47 | name := templateCleanImageName(v.origName) 48 | if name != v.expected { 49 | t.Fatalf("template names do not match: expected %s got %s\n", v.expected, name) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /builder/azure/dtl/tempname.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package dtl 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | 10 | "github.com/hashicorp/packer-plugin-sdk/random" 11 | ) 12 | 13 | type TempName struct { 14 | AdminPassword string 15 | CertificatePassword string 16 | ComputeName string 17 | DeploymentName string 18 | KeyVaultName string 19 | ResourceGroupName string 20 | OSDiskName string 21 | NicName string 22 | SubnetName string 23 | PublicIPAddressName string 24 | VirtualNetworkName string 25 | } 26 | 27 | func NewTempName(c *Config) *TempName { 28 | tempName := &TempName{} 29 | suffix := random.AlphaNumLower(10) 30 | 31 | if c.VMName != "" { 32 | suffix = c.VMName 33 | } 34 | 35 | tempName.ComputeName = suffix 36 | tempName.DeploymentName = fmt.Sprintf("pkrdp%s", suffix) 37 | tempName.KeyVaultName = fmt.Sprintf("pkrkv%s", suffix) 38 | tempName.OSDiskName = fmt.Sprintf("pkros%s", suffix) 39 | tempName.NicName = tempName.ComputeName 40 | tempName.PublicIPAddressName = tempName.ComputeName 41 | tempName.SubnetName = fmt.Sprintf("pkrsn%s", suffix) 42 | tempName.VirtualNetworkName = fmt.Sprintf("pkrvn%s", suffix) 43 | tempName.ResourceGroupName = fmt.Sprintf("packer-Resource-Group-%s", suffix) 44 | 45 | tempName.AdminPassword = generatePassword() 46 | tempName.CertificatePassword = random.AlphaNum(32) 47 | return tempName 48 | } 49 | 50 | // generate a password that is acceptable to Azure 51 | // Three of the four items must be met. 52 | // 1. Contains an uppercase character 53 | // 2. Contains a lowercase character 54 | // 3. Contains a numeric digit 55 | // 4. Contains a special character 56 | func generatePassword() string { 57 | var s string 58 | for i := 0; i < 100; i++ { 59 | s := random.AlphaNum(32) 60 | if !strings.ContainsAny(s, random.PossibleNumbers) { 61 | continue 62 | } 63 | 64 | if !strings.ContainsAny(s, random.PossibleLowerCase) { 65 | continue 66 | } 67 | 68 | if !strings.ContainsAny(s, random.PossibleUpperCase) { 69 | continue 70 | } 71 | 72 | return s 73 | } 74 | 75 | // if an acceptable password cannot be generated in 100 tries, give up 76 | return s 77 | } 78 | -------------------------------------------------------------------------------- /builder/azure/dtl/testdata/linux.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | variable "client_id" { 5 | type = string 6 | default = "${env("ARM_CLIENT_ID")}" 7 | } 8 | 9 | variable "client_secret" { 10 | type = string 11 | default = "${env("ARM_CLIENT_SECRET")}" 12 | } 13 | 14 | variable "resource_group_name" { 15 | type = string 16 | default = "${env("ARM_RESOURCE_GROUP_NAME")}" 17 | } 18 | 19 | variable "subscription_id" { 20 | type = string 21 | default = "${env("ARM_SUBSCRIPTION_ID")}" 22 | } 23 | 24 | variable "resource_prefix" { 25 | type = string 26 | default = "${env("ARM_RESOURCE_PREFIX")}" 27 | } 28 | 29 | 30 | locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") } 31 | 32 | source "azure-dtl" "autogenerated_1" { 33 | client_id = "${var.client_id}" 34 | client_secret = "${var.client_secret}" 35 | dtl_artifacts { 36 | artifact_name = "linux-apt-package" 37 | parameters { 38 | name = "packages" 39 | value = "vim" 40 | } 41 | parameters { 42 | name = "update" 43 | value = "true" 44 | } 45 | parameters { 46 | name = "options" 47 | value = "--fix-broken" 48 | } 49 | } 50 | image_offer = "UbuntuServer" 51 | image_publisher = "Canonical" 52 | image_sku = "16.04-LTS" 53 | lab_name = "${var.resource_prefix}-packer-acceptance-test" 54 | lab_resource_group_name = "${var.resource_group_name}" 55 | lab_virtual_network_name = "dtlpacker-acceptance-test" 56 | location = "South Central US" 57 | managed_image_name = "testBuilderAccManagedDiskLinux-${local.timestamp}" 58 | managed_image_resource_group_name = "${var.resource_group_name}" 59 | os_type = "Linux" 60 | polling_duration_timeout = "25m" 61 | subscription_id = "${var.subscription_id}" 62 | vm_size = "Standard_DS2_v2" 63 | } 64 | 65 | build { 66 | sources = ["source.azure-dtl.autogenerated_1"] 67 | 68 | } 69 | -------------------------------------------------------------------------------- /builder/azure/dtl/testdata/windows.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | variable "client_id" { 5 | type = string 6 | default = "${env("ARM_CLIENT_ID")}" 7 | } 8 | 9 | variable "client_secret" { 10 | type = string 11 | default = "${env("ARM_CLIENT_SECRET")}" 12 | } 13 | 14 | variable "resource_group_name" { 15 | type = string 16 | default = "${env("ARM_RESOURCE_GROUP_NAME")}" 17 | } 18 | 19 | variable "subscription_id" { 20 | type = string 21 | default = "${env("ARM_SUBSCRIPTION_ID")}" 22 | } 23 | 24 | variable "resource_prefix" { 25 | type = string 26 | default = "${env("ARM_RESOURCE_PREFIX")}" 27 | } 28 | locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") } 29 | 30 | source "azure-dtl" "autogenerated_1" { 31 | client_id = "${var.client_id}" 32 | client_secret = "${var.client_secret}" 33 | communicator = "winrm" 34 | image_offer = "WindowsServer" 35 | image_publisher = "MicrosoftWindowsServer" 36 | image_sku = "2022-datacenter" 37 | lab_name = "${var.resource_prefix}-packer-acceptance-test" 38 | lab_resource_group_name = "${var.resource_group_name}" 39 | lab_virtual_network_name = "dtlpacker-acceptance-test" 40 | location = "South Central US" 41 | managed_image_name = "testBuilderAccManagedDiskWindows-${local.timestamp}" 42 | managed_image_resource_group_name = "${var.resource_group_name}" 43 | os_type = "Windows" 44 | polling_duration_timeout = "25m" 45 | subscription_id = "${var.subscription_id}" 46 | vm_size = "Standard_DS2_v2" 47 | winrm_insecure = "true" 48 | winrm_timeout = "10m" 49 | winrm_use_ssl = "true" 50 | winrm_username = "packer" 51 | } 52 | 53 | build { 54 | sources = ["source.azure-dtl.autogenerated_1"] 55 | 56 | } 57 | -------------------------------------------------------------------------------- /builder/azure/pkcs12/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /builder/azure/pkcs12/bmp-string.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pkcs12 6 | 7 | import ( 8 | "errors" 9 | "unicode/utf16" 10 | ) 11 | 12 | // bmpString returns s encoded in UCS-2 with a zero terminator. 13 | func bmpString(s string) ([]byte, error) { 14 | // References: 15 | // https://tools.ietf.org/html/rfc7292#appendix-B.1 16 | // http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane 17 | // - non-BMP characters are encoded in UTF 16 by using a surrogate pair of 16-bit codes 18 | // EncodeRune returns 0xfffd if the rune does not need special encoding 19 | // - the above RFC provides the info that BMPStrings are NULL terminated. 20 | 21 | ret := make([]byte, 0, 2*len(s)+2) 22 | 23 | for _, r := range s { 24 | if t, _ := utf16.EncodeRune(r); t != 0xfffd { 25 | return nil, errors.New("pkcs12: string contains characters that cannot be encoded in UCS-2") 26 | } 27 | ret = append(ret, byte(r/256), byte(r%256)) 28 | } 29 | 30 | return append(ret, 0, 0), nil 31 | } 32 | 33 | func decodeBMPString(bmpString []byte) (string, error) { 34 | if len(bmpString)%2 != 0 { 35 | return "", errors.New("pkcs12: odd-length BMP string") 36 | } 37 | 38 | // strip terminator if present 39 | if l := len(bmpString); l >= 2 && bmpString[l-1] == 0 && bmpString[l-2] == 0 { 40 | bmpString = bmpString[:l-2] 41 | } 42 | 43 | s := make([]uint16, 0, len(bmpString)/2) 44 | for len(bmpString) > 0 { 45 | s = append(s, uint16(bmpString[0])<<8+uint16(bmpString[1])) 46 | bmpString = bmpString[2:] 47 | } 48 | 49 | return string(utf16.Decode(s)), nil 50 | } 51 | -------------------------------------------------------------------------------- /builder/azure/pkcs12/bmp-string_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pkcs12 6 | 7 | import ( 8 | "bytes" 9 | "encoding/hex" 10 | "testing" 11 | ) 12 | 13 | var bmpStringTests = []struct { 14 | in string 15 | expectedHex string 16 | shouldFail bool 17 | }{ 18 | {"", "0000", false}, 19 | // Example from https://tools.ietf.org/html/rfc7292#appendix-B. 20 | {"Beavis", "0042006500610076006900730000", false}, 21 | // Some characters from the "Letterlike Symbols Unicode block". 22 | {"\u2115 - Double-struck N", "21150020002d00200044006f00750062006c0065002d00730074007200750063006b0020004e0000", false}, 23 | // any character outside the BMP should trigger an error. 24 | {"\U0001f000 East wind (Mahjong)", "", true}, 25 | } 26 | 27 | func TestBMPStringDecode(t *testing.T) { 28 | if _, err := decodeBMPString([]byte("a")); err == nil { 29 | t.Fatalf("expected decode to fail, but it succeeded") 30 | } 31 | } 32 | 33 | func TestBMPString(t *testing.T) { 34 | for i, test := range bmpStringTests { 35 | expected, err := hex.DecodeString(test.expectedHex) 36 | if err != nil { 37 | t.Fatalf("#%d: failed to decode expectation", i) 38 | } 39 | 40 | out, err := bmpString(test.in) 41 | if err == nil && test.shouldFail { 42 | t.Errorf("#%d: expected to fail, but produced %x", i, out) 43 | continue 44 | } 45 | 46 | if err != nil && !test.shouldFail { 47 | t.Errorf("#%d: failed unexpectedly: %s", i, err) 48 | continue 49 | } 50 | 51 | if !test.shouldFail { 52 | if !bytes.Equal(out, expected) { 53 | t.Errorf("#%d: expected %s, got %x", i, test.expectedHex, out) 54 | continue 55 | } 56 | 57 | roundTrip, err := decodeBMPString(out) 58 | if err != nil { 59 | t.Errorf("#%d: decoding output gave an error: %s", i, err) 60 | continue 61 | } 62 | 63 | if roundTrip != test.in { 64 | t.Errorf("#%d: decoding output resulted in %q, but it should have been %q", i, roundTrip, test.in) 65 | continue 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /builder/azure/pkcs12/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pkcs12 6 | 7 | import "errors" 8 | 9 | var ( 10 | // ErrDecryption represents a failure to decrypt the input. 11 | ErrDecryption = errors.New("pkcs12: decryption error, incorrect padding") 12 | 13 | // ErrIncorrectPassword is returned when an incorrect password is detected. 14 | // Usually, P12/PFX data is signed to be able to verify the password. 15 | ErrIncorrectPassword = errors.New("pkcs12: decryption password incorrect") 16 | ) 17 | 18 | // NotImplementedError indicates that the input is not currently supported. 19 | type NotImplementedError string 20 | type EncodeError string 21 | 22 | func (e NotImplementedError) Error() string { 23 | return "pkcs12: " + string(e) 24 | } 25 | 26 | func (e EncodeError) Error() string { 27 | return "pkcs12: encode error: " + string(e) 28 | } 29 | -------------------------------------------------------------------------------- /builder/azure/pkcs12/mac.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pkcs12 6 | 7 | import ( 8 | "crypto/hmac" 9 | "crypto/sha1" 10 | "crypto/x509/pkix" 11 | "encoding/asn1" 12 | ) 13 | 14 | type macData struct { 15 | Mac digestInfo 16 | MacSalt []byte 17 | Iterations int `asn1:"optional,default:1"` 18 | } 19 | 20 | // from PKCS#7: 21 | type digestInfo struct { 22 | Algorithm pkix.AlgorithmIdentifier 23 | Digest []byte 24 | } 25 | 26 | var ( 27 | oidSHA1 = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}) 28 | ) 29 | 30 | func verifyMac(macData *macData, message, password []byte) error { 31 | if !macData.Mac.Algorithm.Algorithm.Equal(oidSHA1) { 32 | return NotImplementedError("unknown digest algorithm: " + macData.Mac.Algorithm.Algorithm.String()) 33 | } 34 | 35 | expectedMAC := computeMac(message, macData.Iterations, macData.MacSalt, password) 36 | 37 | if !hmac.Equal(macData.Mac.Digest, expectedMAC) { 38 | return ErrIncorrectPassword 39 | } 40 | return nil 41 | } 42 | 43 | func computeMac(message []byte, iterations int, salt, password []byte) []byte { 44 | key := pbkdf(sha1Sum, 20, 64, salt, password, iterations, 3, 20) 45 | 46 | mac := hmac.New(sha1.New, key) 47 | _, _ = mac.Write(message) 48 | 49 | return mac.Sum(nil) 50 | } 51 | -------------------------------------------------------------------------------- /builder/azure/pkcs12/mac_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pkcs12 6 | 7 | import ( 8 | "encoding/asn1" 9 | "testing" 10 | ) 11 | 12 | func TestVerifyMac(t *testing.T) { 13 | td := macData{ 14 | Mac: digestInfo{ 15 | Digest: []byte{0x18, 0x20, 0x3d, 0xff, 0x1e, 0x16, 0xf4, 0x92, 0xf2, 0xaf, 0xc8, 0x91, 0xa9, 0xba, 0xd6, 0xca, 0x9d, 0xee, 0x51, 0x93}, 16 | }, 17 | MacSalt: []byte{1, 2, 3, 4, 5, 6, 7, 8}, 18 | Iterations: 2048, 19 | } 20 | 21 | message := []byte{11, 12, 13, 14, 15} 22 | password, _ := bmpString("") 23 | 24 | td.Mac.Algorithm.Algorithm = asn1.ObjectIdentifier([]int{1, 2, 3}) 25 | err := verifyMac(&td, message, password) 26 | if _, ok := err.(NotImplementedError); !ok { 27 | t.Errorf("err: %v", err) 28 | } 29 | 30 | td.Mac.Algorithm.Algorithm = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}) 31 | err = verifyMac(&td, message, password) 32 | if err != ErrIncorrectPassword { 33 | t.Errorf("Expected incorrect password, got err: %v", err) 34 | } 35 | 36 | password, _ = bmpString("Sesame open") 37 | err = verifyMac(&td, message, password) 38 | if err != nil { 39 | t.Errorf("err: %v", err) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /builder/azure/pkcs12/pbkdf_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pkcs12 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | ) 11 | 12 | func TestThatPBKDFWorksCorrectlyForLongKeys(t *testing.T) { 13 | cipherInfo := shaWithTripleDESCBC{} 14 | 15 | salt := []byte("\xff\xff\xff\xff\xff\xff\xff\xff") 16 | password, _ := bmpString("sesame") 17 | key := cipherInfo.deriveKey(salt, password, 2048) 18 | 19 | if expected := []byte("\x7c\xd9\xfd\x3e\x2b\x3b\xe7\x69\x1a\x44\xe3\xbe\xf0\xf9\xea\x0f\xb9\xb8\x97\xd4\xe3\x25\xd9\xd1"); !bytes.Equal(key, expected) { 20 | t.Fatalf("expected key '%x', but found '%x'", expected, key) 21 | } 22 | } 23 | 24 | func TestThatPBKDFHandlesLeadingZeros(t *testing.T) { 25 | // This test triggers a case where I_j (in step 6C) ends up with leading zero 26 | // byte, meaning that len(Ijb) < v (leading zeros get stripped by big.Int). 27 | // This was previously causing bug whereby certain inputs would break the 28 | // derivation and produce the wrong output. 29 | key := pbkdf(sha1Sum, 20, 64, []byte("\xf3\x7e\x05\xb5\x18\x32\x4b\x4b"), []byte("\x00\x00"), 2048, 1, 24) 30 | expected := []byte("\x00\xf7\x59\xff\x47\xd1\x4d\xd0\x36\x65\xd5\x94\x3c\xb3\xc4\xa3\x9a\x25\x55\xc0\x2a\xed\x66\xe1") 31 | if !bytes.Equal(key, expected) { 32 | t.Fatalf("expected key '%x', but found '%x'", expected, key) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /builder/azure/pkcs12/pkcs8.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package pkcs12 5 | 6 | import ( 7 | "crypto/ecdsa" 8 | "crypto/rsa" 9 | "crypto/x509" 10 | "crypto/x509/pkix" 11 | "encoding/asn1" 12 | "errors" 13 | ) 14 | 15 | // pkcs8 reflects an ASN.1, PKCS#8 PrivateKey. See 16 | // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-8/pkcs-8v1_2.asn 17 | // and RFC5208. 18 | type pkcs8 struct { 19 | Version int 20 | Algo pkix.AlgorithmIdentifier 21 | PrivateKey []byte 22 | // optional attributes omitted. 23 | } 24 | 25 | var ( 26 | oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} 27 | oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} 28 | 29 | nullAsn = asn1.RawValue{Tag: 5} 30 | ) 31 | 32 | // marshalPKCS8PrivateKey converts a private key to PKCS#8 encoded form. 33 | // See http://www.rsa.com/rsalabs/node.asp?id=2130 and RFC5208. 34 | func marshalPKCS8PrivateKey(key interface{}) (der []byte, err error) { 35 | pkcs := pkcs8{ 36 | Version: 0, 37 | } 38 | 39 | switch key := key.(type) { 40 | case *rsa.PrivateKey: 41 | pkcs.Algo = pkix.AlgorithmIdentifier{ 42 | Algorithm: oidPublicKeyRSA, 43 | Parameters: nullAsn, 44 | } 45 | pkcs.PrivateKey = x509.MarshalPKCS1PrivateKey(key) 46 | case *ecdsa.PrivateKey: 47 | bytes, err := x509.MarshalECPrivateKey(key) 48 | if err != nil { 49 | return nil, errors.New("x509: failed to marshal to PKCS#8: " + err.Error()) 50 | } 51 | 52 | pkcs.Algo = pkix.AlgorithmIdentifier{ 53 | Algorithm: oidPublicKeyECDSA, 54 | Parameters: nullAsn, 55 | } 56 | pkcs.PrivateKey = bytes 57 | default: 58 | return nil, errors.New("x509: PKCS#8 only RSA and ECDSA private keys supported") 59 | } 60 | 61 | bytes, err := asn1.Marshal(pkcs) 62 | if err != nil { 63 | return nil, errors.New("x509: failed to marshal to PKCS#8: " + err.Error()) 64 | } 65 | 66 | return bytes, nil 67 | } 68 | -------------------------------------------------------------------------------- /builder/azure/pkcs12/rc2/bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package rc2 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func BenchmarkEncrypt(b *testing.B) { 12 | r, _ := New([]byte{0, 0, 0, 0, 0, 0, 0, 0}, 64) 13 | b.ResetTimer() 14 | var src [8]byte 15 | for i := 0; i < b.N; i++ { 16 | r.Encrypt(src[:], src[:]) 17 | } 18 | } 19 | 20 | func BenchmarkDecrypt(b *testing.B) { 21 | r, _ := New([]byte{0, 0, 0, 0, 0, 0, 0, 0}, 64) 22 | b.ResetTimer() 23 | var src [8]byte 24 | for i := 0; i < b.N; i++ { 25 | r.Decrypt(src[:], src[:]) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /builder/azure/pkcs12/rc2/rc2_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package rc2 6 | 7 | import ( 8 | "bytes" 9 | "encoding/hex" 10 | "testing" 11 | ) 12 | 13 | func TestEncryptDecrypt(t *testing.T) { 14 | 15 | // TODO(dgryski): add the rest of the test vectors from the RFC 16 | var tests = []struct { 17 | key string 18 | plain string 19 | cipher string 20 | t1 int 21 | }{ 22 | { 23 | "0000000000000000", 24 | "0000000000000000", 25 | "ebb773f993278eff", 26 | 63, 27 | }, 28 | { 29 | "ffffffffffffffff", 30 | "ffffffffffffffff", 31 | "278b27e42e2f0d49", 32 | 64, 33 | }, 34 | { 35 | "3000000000000000", 36 | "1000000000000001", 37 | "30649edf9be7d2c2", 38 | 64, 39 | }, 40 | { 41 | "88", 42 | "0000000000000000", 43 | "61a8a244adacccf0", 44 | 64, 45 | }, 46 | { 47 | "88bca90e90875a", 48 | "0000000000000000", 49 | "6ccf4308974c267f", 50 | 64, 51 | }, 52 | { 53 | "88bca90e90875a7f0f79c384627bafb2", 54 | "0000000000000000", 55 | "1a807d272bbe5db1", 56 | 64, 57 | }, 58 | { 59 | "88bca90e90875a7f0f79c384627bafb2", 60 | "0000000000000000", 61 | "2269552ab0f85ca6", 62 | 128, 63 | }, 64 | { 65 | "88bca90e90875a7f0f79c384627bafb216f80a6f85920584c42fceb0be255daf1e", 66 | "0000000000000000", 67 | "5b78d3a43dfff1f1", 68 | 129, 69 | }, 70 | } 71 | 72 | for _, tt := range tests { 73 | k, _ := hex.DecodeString(tt.key) 74 | p, _ := hex.DecodeString(tt.plain) 75 | c, _ := hex.DecodeString(tt.cipher) 76 | 77 | b, _ := New(k, tt.t1) 78 | 79 | var dst [8]byte 80 | 81 | b.Encrypt(dst[:], p) 82 | 83 | if !bytes.Equal(dst[:], c) { 84 | t.Errorf("encrypt failed: got % 2x wanted % 2x\n", dst, c) 85 | } 86 | 87 | b.Decrypt(dst[:], c) 88 | 89 | if !bytes.Equal(dst[:], p) { 90 | t.Errorf("decrypt failed: got % 2x wanted % 2x\n", dst, p) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/arm/Config-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `image_publisher` (string) - Name of the publisher to use for your base image (Azure Marketplace Images only). See 4 | [documentation](https://docs.microsoft.com/en-us/cli/azure/vm/image) 5 | for details. 6 | 7 | CLI example `az vm image list-publishers --location westus` 8 | 9 | - `image_offer` (string) - Name of the publisher's offer to use for your base image (Azure Marketplace Images only). See 10 | [documentation](https://docs.microsoft.com/en-us/cli/azure/vm/image) 11 | for details. 12 | 13 | CLI example 14 | `az vm image list-offers --location westus --publisher Canonical` 15 | 16 | - `image_sku` (string) - SKU of the image offer to use for your base image (Azure Marketplace Images only). See 17 | [documentation](https://docs.microsoft.com/en-us/cli/azure/vm/image) 18 | for details. 19 | 20 | CLI example 21 | `az vm image list-skus --location westus --publisher Canonical --offer UbuntuServer` 22 | 23 | - `image_url` (string) - URL to a custom VHD to use for your base image. If this value is set, 24 | image_publisher, image_offer, image_sku, or image_version should not be set. 25 | 26 | - `custom_managed_image_name` (string) - Name of a custom managed image to use for your base image. If this value is set, do 27 | not set image_publisher, image_offer, image_sku, or image_version. 28 | If this value is set, the option 29 | `custom_managed_image_resource_group_name` must also be set. See 30 | [documentation](https://docs.microsoft.com/en-us/azure/storage/storage-managed-disks-overview#images) 31 | to learn more about managed images. 32 | 33 | - `custom_managed_image_resource_group_name` (string) - Name of a custom managed image's resource group to use for your base image. If this 34 | value is set, image_publisher, image_offer, image_sku, or image_version should not be set. 35 | If this value is set, the option 36 | `custom_managed_image_name` must also be set. See 37 | [documentation](https://docs.microsoft.com/en-us/azure/storage/storage-managed-disks-overview#images) 38 | to learn more about managed images. 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/arm/PlanInformation-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `plan_name` (string) - Plan Name 4 | 5 | - `plan_product` (string) - Plan Product 6 | 7 | - `plan_publisher` (string) - Plan Publisher 8 | 9 | - `plan_promotion_code` (string) - Plan Promotion Code 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/arm/SharedImageGallery-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `subscription` (string) - Subscription 4 | 5 | - `resource_group` (string) - Resource Group 6 | 7 | - `gallery_name` (string) - Gallery Name 8 | 9 | - `image_name` (string) - Image Name 10 | 11 | - `image_version` (string) - Specify a specific version of an OS to boot from. 12 | Defaults to latest. There may be a difference in versions available 13 | across regions due to image synchronization latency. To ensure a consistent 14 | version across regions set this value to one that is available in all 15 | regions where you are deploying. 16 | 17 | - `community_gallery_image_id` (string) - Id of the community gallery image : /CommunityGalleries/{galleryUniqueName}/Images/{img}[/Versions/{}] (Versions part is optional) 18 | 19 | - `direct_shared_gallery_image_id` (string) - Id of the direct shared gallery image : /sharedGalleries/{galleryUniqueName}/Images/{img}[/Versions/{}] (Versions part is optional) 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/arm/Spot-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `eviction_policy` (virtualmachines.VirtualMachineEvictionPolicyTypes) - Specify eviction policy for spot instance: "Deallocate" or "Delete". If this is set, a spot instance will be used. 4 | 5 | - `max_price` (float32) - How much should the VM cost maximally per hour. Specify -1 (or do not specify) to not evict based on price. 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/arm/TargetRegion-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `disk_encryption_set_id` (string) - DiskEncryptionSetId for Disk Encryption Set in Region. Needed for supporting 4 | the replication of encrypted disks across regions. CMKs must 5 | already exist within the target regions. 6 | 7 | - `replicas` (int64) - The number of replicas of the Image Version to be created within the region. Defaults to 1. 8 | Replica count must be between 1 and 100, but 50 replicas should be sufficient for most use cases. 9 | When using shallow replication `use_shallow_replication=true` the value can only be 1 for the primary build region. 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/arm/TargetRegion-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `name` (string) - Name of the Azure region 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/arm/TargetRegion.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | TargetRegion describes a destination region for storing the image version of a Shard Image Gallery. 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/chroot/Config-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `source` (string) - One of the following can be used as a source for an image: 4 | - a shared image version resource ID 5 | - a managed disk resource ID 6 | - a publisher:offer:sku:version specifier for platform image sources. 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/chroot/Config.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | Config is the configuration that is chained through the steps and settable 4 | from the template. 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/chroot/SharedImageGalleryDestination-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `target_regions` ([]TargetRegion) - Target Regions 4 | 5 | - `exclude_from_latest` (bool) - Exclude From Latest 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/chroot/SharedImageGalleryDestination-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `resource_group` (string) - Resource Group 4 | 5 | - `gallery_name` (string) - Gallery Name 6 | 7 | - `image_name` (string) - Image Name 8 | 9 | - `image_version` (string) - Image Version 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/chroot/SharedImageGalleryDestination.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | SharedImageGalleryDestination models an image version in a Shared 4 | Image Gallery that can be used as a destination. 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/chroot/TargetRegion-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `replicas` (int64) - Number of replicas in this region. Default: 1 4 | 5 | - `storage_account_type` (string) - Storage account type: Standard_LRS or Standard_ZRS. Default: Standard_ZRS 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/chroot/TargetRegion-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `name` (string) - Name of the Azure region 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/chroot/TargetRegion.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | TargetRegion describes a region where the shared image should be replicated 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/common/Config-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `skip_create_image` (bool) - Skip creating the image. 4 | Useful for setting to `true` during a build test stage. 5 | Defaults to `false`. 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/common/client/Config.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | Config allows for various ways to authenticate Azure clients. When 4 | `client_id` and `subscription_id` are specified in addition to one of the following 5 | * `client_secret` 6 | * `client_jwt` 7 | * `client_cert_path` 8 | * `oidc_request_url` combined with `oidc_request_token` 9 | 10 | Packer will use the specified Azure Active Directory (AAD) Service Principal (SP). 11 | If none of these options are specified, Packer will attempt to use the Managed Identity 12 | and subscription of the VM that Packer is running on. This will only work if 13 | Packer is running on an Azure VM with either a System Assigned Managed 14 | Identity or User Assigned Managed Identity. 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/dtl/ArtifactParameter-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `name` (string) - Name 4 | 5 | - `value` (string) - Value 6 | 7 | - `type` (string) - Type 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/dtl/Config-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `managed_image_resource_group_name` (string) - Specify the managed image resource group name where the result of the 4 | Packer build will be saved. The resource group must already exist. If 5 | this value is set, the value managed_image_name must also be set. See 6 | documentation to learn more about managed images. 7 | 8 | - `managed_image_name` (string) - Specify the managed image name where the result of the Packer build will 9 | be saved. The image name must not exist ahead of time, and will not be 10 | overwritten. If this value is set, the value 11 | managed_image_resource_group_name must also be set. See documentation to 12 | learn more about managed images. 13 | 14 | - `lab_name` (string) - Name of the existing lab where the virtual machine will be created. 15 | 16 | - `lab_subnet_name` (string) - Name of the subnet being used in the lab, if not the default. 17 | 18 | - `lab_resource_group_name` (string) - Name of the resource group where the lab exist. 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/dtl/DtlArtifact-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `artifact_name` (string) - Artifact Name 4 | 5 | - `repository_name` (string) - Repository Name 6 | 7 | - `artifact_id` (string) - Artifact Id 8 | 9 | - `parameters` ([]ArtifactParameter) - Parameters 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/dtl/DtlArtifact.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | An existing artifact that can be added to the virtual machine being provisioned. 4 | At a minimum the `artifact_name` attribute must be set. 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/dtl/SharedImageGallery-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `subscription` (string) - Subscription 4 | 5 | - `resource_group` (string) - Resource Group 6 | 7 | - `gallery_name` (string) - Gallery Name 8 | 9 | - `image_name` (string) - Image Name 10 | 11 | - `image_version` (string) - Image Version 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs-partials/builder/azure/dtl/SharedImageGalleryDestination-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `resource_group` (string) - Sig Destination Resource Group 4 | 5 | - `gallery_name` (string) - Sig Destination Gallery Name 6 | 7 | - `image_name` (string) - Sig Destination Image Name 8 | 9 | - `image_version` (string) - Sig Destination Image Version 10 | 11 | - `replication_regions` ([]string) - Sig Destination Replication Regions 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs-partials/provisioner/azure-dtlartifact/ArtifactParameter-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `name` (string) - Name 4 | 5 | - `value` (string) - Value 6 | 7 | - `type` (string) - Type 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs-partials/provisioner/azure-dtlartifact/Config-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `polling_duration_timeout` (duration string | ex: "1h5m2s") - The default PollingDuration for azure is 15mins, this property will override 4 | that value. 5 | If your Packer build is failing on the 6 | ARM deployment step with the error `Original Error: 7 | context deadline exceeded`, then you probably need to increase this timeout from 8 | its default of "15m" (valid time units include `s` for seconds, `m` for 9 | minutes, and `h` for hours.) 10 | 11 | - `azure_tags` (map[string]\*string) - Azure Tags 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs-partials/provisioner/azure-dtlartifact/Config-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `dtl_artifacts` ([]DtlArtifact) - Dtl Artifacts 4 | 5 | - `lab_name` (string) - Name of the existing lab where the virtual machine exist. 6 | 7 | - `lab_resource_group_name` (string) - Name of the resource group where the lab exist. 8 | 9 | - `vm_name` (string) - Name of the virtual machine within the DevTest lab. 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs-partials/provisioner/azure-dtlartifact/DtlArtifact-not-required.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | - `artifact_name` (string) - Artifact Name 4 | 5 | - `artifact_id` (string) - Artifact Id 6 | 7 | - `parameters` ([]ArtifactParameter) - Parameters 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/provisioners/dtlartifact.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: Packer supports the ability to apply artifacts to a running vm within an Azure DevTest Lab. 3 | page_title: Azure DevTest Lab - Provisioner 4 | nav_title: Azure DTL Artifact 5 | --- 6 | 7 | # Azure DevTest Lab Artifact Provisioner 8 | 9 | Type: `azure-dtlartifact` 10 | 11 | The Azure DevTest Labs provisioner can be used to apply an artifact to a VM - See [Add an artifact to a VM](https://docs.microsoft.com/en-us/azure/devtest-labs/add-artifact-vm) 12 | 13 | ## Azure DevTest Labs provisioner specific options 14 | 15 | ### Required: 16 | 17 | @include 'provisioner/azure-dtlartifact/Config-required.mdx' 18 | 19 | ### Optional: 20 | 21 | @include 'provisioner/azure-dtlartifact/Config-not-required.mdx' 22 | 23 | #### DtlArtifact 24 | @include 'provisioner/azure-dtlartifact/DtlArtifact-not-required.mdx' 25 | 26 | #### ArtifactParmater 27 | @include 'provisioner/azure-dtlartifact/ArtifactParameter-not-required.mdx' 28 | 29 | ## Basic Example 30 | 31 | ```hcl 32 | source "null" "example" { 33 | communicator = "none" 34 | } 35 | 36 | build { 37 | sources = ["source.null.example"] 38 | 39 | provisioner "azure-dtlartifact" { 40 | lab_name = "packer-test" 41 | lab_resource_group_name = "packer-test" 42 | vm_name = "packer-test-vm" 43 | dtl_artifacts { 44 | artifact_name = "linux-apt-package" 45 | parameters { 46 | name = "packages" 47 | value = "vim" 48 | } 49 | parameters { 50 | name = "update" 51 | value = "true" 52 | } 53 | parameters { 54 | name = "options" 55 | value = "--fix-broken" 56 | } 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /example/centos.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}", 6 | "tenant_id": "{{env `ARM_TENANT_ID`}}", 7 | "ssh_user": "centos", 8 | "ssh_pass": "{{env `ARM_SSH_PASS`}}", 9 | "resource_group": "{{env `ARM_RESOURCE_GROUP_NAME`}}" 10 | }, 11 | "builders": [{ 12 | "type": "azure-arm", 13 | 14 | "client_id": "{{user `client_id`}}", 15 | "client_secret": "{{user `client_secret`}}", 16 | "subscription_id": "{{user `subscription_id`}}", 17 | "tenant_id": "{{user `tenant_id`}}", 18 | 19 | "managed_image_resource_group_name": "{{user `resource_group`}}", 20 | "managed_image_name": "MyCentOSImage", 21 | 22 | "ssh_username": "{{user `ssh_user`}}", 23 | "ssh_password": "{{user `ssh_pass`}}", 24 | 25 | "os_type": "Linux", 26 | "image_publisher": "OpenLogic", 27 | "image_offer": "CentOS", 28 | "image_sku": "7.3", 29 | "image_version": "latest", 30 | "ssh_pty": "true", 31 | 32 | "location": "South Central US", 33 | "vm_size": "Standard_DS2_v2" 34 | }], 35 | "provisioners": [{ 36 | "execute_command": "echo '{{user `ssh_pass`}}' | {{ .Vars }} sudo -S -E sh '{{ .Path }}'", 37 | "inline": [ 38 | "yum update -y", 39 | "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync" 40 | ], 41 | "inline_shebang": "/bin/sh -x", 42 | "type": "shell", 43 | "skip_clean": true 44 | }] 45 | } 46 | -------------------------------------------------------------------------------- /example/centos.json.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | source "azure-arm" "centos" { 5 | client_id = "${var.client_id}" 6 | client_secret = "${var.client_secret}" 7 | image_offer = "CentOS" 8 | image_publisher = "OpenLogic" 9 | image_sku = "7.3" 10 | image_version = "latest" 11 | location = "South Central US" 12 | managed_image_name = "MyCentOSImage" 13 | managed_image_resource_group_name = "${var.resource_group}" 14 | os_type = "Linux" 15 | ssh_password = "${var.ssh_pass}" 16 | ssh_pty = "true" 17 | ssh_username = "${var.ssh_user}" 18 | subscription_id = "${var.subscription_id}" 19 | tenant_id = "${var.tenant_id}" 20 | vm_size = "Standard_DS2_v2" 21 | } 22 | 23 | build { 24 | sources = ["source.azure-arm.centos"] 25 | 26 | provisioner "shell" { 27 | execute_command = "echo '${var.ssh_pass}' | {{ .Vars }} sudo -S -E sh '{{ .Path }}'" 28 | inline = ["yum update -y", "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"] 29 | inline_shebang = "/bin/sh -x" 30 | skip_clean = true 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /example/debian-chroot.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}", 6 | "resource_group": "{{env `ARM_IMAGE_RESOURCEGROUP_ID`}}" 7 | }, 8 | "builders": [{ 9 | "type": "azure-chroot", 10 | 11 | "client_id": "{{user `client_id`}}", 12 | "client_secret": "{{user `client_secret`}}", 13 | "subscription_id": "{{user `subscription_id`}}", 14 | 15 | "image_resource_id": "/subscriptions/{{user `subscription_id`}}/resourceGroups/{{user `resource_group`}}/providers/Microsoft.Compute/images/MyDebianOSImage-{{timestamp}}", 16 | 17 | "source": "credativ:Debian:9:latest" 18 | }], 19 | "provisioners": [{ 20 | "inline": [ 21 | "apt-get update", 22 | "apt-get upgrade -y" 23 | ], 24 | "inline_shebang": "/bin/sh -x", 25 | "type": "shell" 26 | }] 27 | } -------------------------------------------------------------------------------- /example/debian.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}", 6 | "ssh_user": "packer", 7 | "ssh_pass": "{{env `ARM_SSH_PASS`}}" 8 | }, 9 | "builders": [{ 10 | "type": "azure-arm", 11 | 12 | "client_id": "{{user `client_id`}}", 13 | "client_secret": "{{user `client_secret`}}", 14 | "resource_group_name": "{{user `resource_group`}}", 15 | "storage_account": "{{user `storage_account`}}", 16 | "subscription_id": "{{user `subscription_id`}}", 17 | 18 | "managed_image_resource_group_name": "packertest", 19 | "managed_image_name": "MyDebianOSImage", 20 | 21 | "ssh_username": "{{user `ssh_user`}}", 22 | "ssh_password": "{{user `ssh_pass`}}", 23 | 24 | "os_type": "Linux", 25 | "image_publisher": "credativ", 26 | "image_offer": "Debian", 27 | "image_sku": "9", 28 | "ssh_pty": "true", 29 | 30 | "location": "South Central US", 31 | "vm_size": "Standard_DS2_v2" 32 | }], 33 | "provisioners": [{ 34 | "execute_command": "echo '{{user `ssh_pass`}}' | {{ .Vars }} sudo -S -E sh '{{ .Path }}'", 35 | "inline": [ 36 | "apt-get update", 37 | "apt-get upgrade -y", 38 | 39 | "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync" 40 | ], 41 | "inline_shebang": "/bin/sh -x", 42 | "skip_clean": true, 43 | "type": "shell" 44 | }] 45 | } 46 | -------------------------------------------------------------------------------- /example/freebsd-chroot.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": {}, 3 | "builders": [ 4 | { 5 | "type": "azure-chroot", 6 | "source": "thefreebsdfoundation:freebsd-12_1:12_1-release:latest", 7 | "image_resource_id": "/subscriptions/{{vm `subscription_id`}}/resourceGroups/{{vm `resource_group`}}/providers/Microsoft.Compute/images/freebsd-{{timestamp}}", 8 | "os_disk_size_gb": 64, 9 | "os_disk_storage_account_type": "Premium_LRS", 10 | "mount_partition": 2, 11 | "chroot_mounts": [ 12 | ["devfs", "devfs", "/dev"], 13 | ["procfs", "procfs", "/proc"] 14 | ] 15 | } 16 | ], 17 | "provisioners": [ 18 | { 19 | "inline": [ 20 | "env ASSUME_ALWAYS_YES=YES pkg bootstrap" 21 | ], 22 | "inline_shebang": "/bin/sh -x", 23 | "type": "shell" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /example/freebsd.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}", 6 | "ssh_user": "packer", 7 | "ssh_pass": "{{env `ARM_SSH_PASS`}}" 8 | }, 9 | "builders": [{ 10 | "type": "azure-arm", 11 | 12 | "client_id": "{{user `client_id`}}", 13 | "client_secret": "{{user `client_secret`}}", 14 | "subscription_id": "{{user `subscription_id`}}", 15 | 16 | "managed_image_resource_group_name": "packertest", 17 | "managed_image_name": "MyFreeBsdOSImage", 18 | 19 | "ssh_username": "{{user `ssh_user`}}", 20 | "ssh_password": "{{user `ssh_pass`}}", 21 | 22 | "os_type": "Linux", 23 | "image_publisher": "MicrosoftOSTC", 24 | "image_offer": "FreeBSD", 25 | "image_sku": "11.1", 26 | "image_version": "latest", 27 | 28 | "location": "West US 2", 29 | "vm_size": "Standard_DS2_v2" 30 | }], 31 | "provisioners": [{ 32 | "execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'", 33 | "inline": [ 34 | "env ASSUME_ALWAYS_YES=YES pkg bootstrap", 35 | "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync" 36 | ], 37 | "inline_shebang": "/bin/sh -x", 38 | "type": "shell", 39 | "skip_clean": "true", 40 | "expect_disconnect": "true" 41 | }] 42 | 43 | } 44 | -------------------------------------------------------------------------------- /example/github-oidc-example.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | 5 | variable "arm_client_id" { 6 | type = string 7 | default = "${env("ARM_CLIENT_ID")}" 8 | } 9 | 10 | variable "oidc_request_token" { 11 | type = string 12 | default = "${env("ACTIONS_ID_TOKEN_REQUEST_TOKEN")}" 13 | } 14 | 15 | variable "oidc_request_url" { 16 | type = string 17 | default = "${env("ACTIONS_ID_TOKEN_REQUEST_URL")}" 18 | } 19 | 20 | variable "subscription_id" { 21 | type = string 22 | default = "${env("ARM_SUBSCRIPTION_ID")}" 23 | } 24 | 25 | source "azure-arm" "autogenerated_1" { 26 | client_id = "${var.arm_client_id}" 27 | oidc_request_token = "${var.oidc_request_token}" 28 | oidc_request_url = "${var.oidc_request_url}" 29 | communicator = "winrm" 30 | image_offer = "WindowsServer" 31 | image_publisher = "MicrosoftWindowsServer" 32 | image_sku = "2022-datacenter" 33 | location = "South Central US" 34 | managed_image_name = "oidc-example" 35 | managed_image_resource_group_name = "packer-acceptance-test" 36 | polling_duration_timeout = "3h" 37 | os_type = "Windows" 38 | subscription_id = "${var.subscription_id}" 39 | vm_size = "Standard_DS2_v2" 40 | winrm_insecure = "true" 41 | winrm_timeout = "3m" 42 | winrm_use_ssl = "true" 43 | winrm_username = "packer" 44 | } 45 | 46 | build { 47 | sources = ["source.azure-arm.autogenerated_1"] 48 | } 49 | -------------------------------------------------------------------------------- /example/linux_custom_image.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "resource_group": "{{env `ARM_RESOURCE_GROUP`}}", 6 | "storage_account": "{{env `ARM_STORAGE_ACCOUNT`}}", 7 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}" 8 | }, 9 | "builders": [ 10 | { 11 | "type": "azure-arm", 12 | 13 | "client_id": "{{user `client_id`}}", 14 | "client_secret": "{{user `client_secret`}}", 15 | "resource_group_name": "{{user `resource_group`}}", 16 | "storage_account": "{{user `storage_account`}}", 17 | "subscription_id": "{{user `subscription_id`}}", 18 | 19 | "capture_container_name": "images", 20 | "capture_name_prefix": "packer", 21 | 22 | "os_type": "Linux", 23 | "image_url": "https://my-storage-account.blob.core.windows.net/path/to/your/custom/image.vhd", 24 | 25 | "azure_tags": { 26 | "dept": "engineering", 27 | "task": "image deployment" 28 | }, 29 | 30 | "location": "West US", 31 | "vm_size": "Standard_DS2_v2" 32 | } 33 | ], 34 | "provisioners": [{ 35 | "execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'", 36 | "inline": [ 37 | "apt-get update", 38 | "apt-get upgrade -y", 39 | "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync" 40 | ], 41 | "inline_shebang": "/bin/sh -x", 42 | "type": "shell" 43 | }] 44 | } 45 | -------------------------------------------------------------------------------- /example/linux_custom_managed_image.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}" 6 | }, 7 | "builders": [ 8 | { 9 | "type": "azure-arm", 10 | 11 | "client_id": "{{user `client_id`}}", 12 | "client_secret": "{{user `client_secret`}}", 13 | "subscription_id": "{{user `subscription_id`}}", 14 | 15 | "os_type": "Linux", 16 | "custom_managed_image_resource_group_name": "MyResourceGroup", 17 | "custom_managed_image_name": "MyImage", 18 | "managed_image_resource_group_name": "PackerImages", 19 | "managed_image_name": "MyImage", 20 | 21 | "azure_tags": { 22 | "dept": "engineering", 23 | "task": "image deployment" 24 | }, 25 | 26 | "location": "West US", 27 | "vm_size": "Standard_DS2_v2" 28 | } 29 | ], 30 | "provisioners": [{ 31 | "execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'", 32 | "inline": [ 33 | "apt-get update", 34 | "apt-get upgrade -y", 35 | "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync" 36 | ], 37 | "inline_shebang": "/bin/sh -x", 38 | "type": "shell" 39 | }] 40 | } 41 | -------------------------------------------------------------------------------- /example/marketplace_plan_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}" 6 | }, 7 | "builders": [{ 8 | "type": "azure-arm", 9 | 10 | "client_id": "{{user `client_id`}}", 11 | "client_secret": "{{user `client_secret`}}", 12 | "subscription_id": "{{user `subscription_id`}}", 13 | 14 | "managed_image_resource_group_name": "packertest", 15 | "managed_image_name": "MyMarketplaceOSImage", 16 | 17 | "os_type": "Linux", 18 | "image_publisher": "bitnami", 19 | "image_offer": "rabbitmq", 20 | "image_sku": "rabbitmq", 21 | 22 | "azure_tags": { 23 | "dept": "engineering", 24 | "task": "image deployment" 25 | }, 26 | 27 | "plan_info": { 28 | "plan_name": "rabbitmq", 29 | "plan_product": "rabbitmq", 30 | "plan_publisher": "bitnami" 31 | }, 32 | 33 | "location": "West US", 34 | "vm_size": "Standard_DS2_v2" 35 | }], 36 | "provisioners": [{ 37 | "execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'", 38 | "inline": [ 39 | "apt-get update", 40 | "apt-get upgrade -y", 41 | 42 | "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync" 43 | ], 44 | "inline_shebang": "/bin/sh -x", 45 | "type": "shell" 46 | }] 47 | } 48 | -------------------------------------------------------------------------------- /example/oidc-example.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | 5 | variable "arm_client_id" { 6 | type = string 7 | default = "${env("ARM_CLIENT_ID")}" 8 | } 9 | 10 | variable "arm_oidc_token" { 11 | type = string 12 | default = "${env("ARM_OIDC_TOKEN")}" 13 | } 14 | 15 | variable "subscription_id" { 16 | type = string 17 | default = "${env("ARM_SUBSCRIPTION_ID")}" 18 | } 19 | 20 | source "azure-arm" "autogenerated_1" { 21 | client_id = "${var.arm_client_id}" 22 | client_jwt = "${var.arm_oidc_token}" 23 | communicator = "winrm" 24 | image_offer = "WindowsServer" 25 | image_publisher = "MicrosoftWindowsServer" 26 | image_sku = "2022-datacenter" 27 | location = "South Central US" 28 | managed_image_name = "oidc-example" 29 | managed_image_resource_group_name = "packer-acceptance-test" 30 | os_type = "Windows" 31 | subscription_id = "${var.subscription_id}" 32 | vm_size = "Standard_DS2_v2" 33 | winrm_insecure = "true" 34 | winrm_timeout = "3m" 35 | winrm_use_ssl = "true" 36 | winrm_username = "packer" 37 | } 38 | 39 | build { 40 | sources = ["source.azure-arm.autogenerated_1"] 41 | 42 | } 43 | -------------------------------------------------------------------------------- /example/rhel.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}", 6 | "tenant_id": "{{env `ARM_TENANT_ID`}}", 7 | "ssh_user": "centos", 8 | "ssh_pass": "{{env `ARM_SSH_PASS`}}" 9 | }, 10 | "builders": [{ 11 | "type": "azure-arm", 12 | 13 | "client_id": "{{user `client_id`}}", 14 | "client_secret": "{{user `client_secret`}}", 15 | "subscription_id": "{{user `subscription_id`}}", 16 | "tenant_id": "{{user `tenant_id`}}", 17 | 18 | "managed_image_resource_group_name": "packertest", 19 | "managed_image_name": "MyRedHatOSImage", 20 | 21 | 22 | "ssh_username": "{{user `ssh_user`}}", 23 | "ssh_password": "{{user `ssh_pass`}}", 24 | 25 | "os_type": "Linux", 26 | "image_publisher": "RedHat", 27 | "image_offer": "RHEL", 28 | "image_sku": "7.3", 29 | "image_version": "latest", 30 | "ssh_pty": "true", 31 | 32 | "location": "South Central US", 33 | "vm_size": "Standard_DS2_v2" 34 | }], 35 | "provisioners": [{ 36 | "execute_command": "echo '{{user `ssh_pass`}}' | {{ .Vars }} sudo -S -E sh '{{ .Path }}'", 37 | "inline": [ 38 | "yum update -y", 39 | "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync" 40 | ], 41 | "inline_shebang": "/bin/sh -x", 42 | "type": "shell", 43 | "skip_clean": true 44 | }] 45 | } 46 | -------------------------------------------------------------------------------- /example/suse.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}", 6 | "ssh_user": "packer", 7 | "ssh_pass": "{{env `ARM_SSH_PASS`}}" 8 | }, 9 | "builders": [{ 10 | "type": "azure-arm", 11 | 12 | "client_id": "{{user `client_id`}}", 13 | "client_secret": "{{user `client_secret`}}", 14 | "subscription_id": "{{user `subscription_id`}}", 15 | 16 | "managed_image_resource_group_name": "packertest", 17 | "managed_image_name": "MySuseOSImage", 18 | 19 | "ssh_username": "{{user `ssh_user`}}", 20 | "ssh_password": "{{user `ssh_pass`}}", 21 | 22 | "os_type": "Linux", 23 | "image_publisher": "SUSE", 24 | "image_offer": "SLES", 25 | "image_sku": "12-SP3", 26 | "ssh_pty": "true", 27 | 28 | "location": "South Central US", 29 | "vm_size": "Standard_DS2_v2" 30 | }], 31 | "provisioners": [{ 32 | "execute_command": "echo '{{user `ssh_pass`}}' | {{ .Vars }} sudo -S -E sh '{{ .Path }}'", 33 | "inline": [ 34 | "zypper update -y", 35 | 36 | "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync" 37 | ], 38 | "inline_shebang": "/bin/sh -x", 39 | "skip_clean": true, 40 | "type": "shell" 41 | }] 42 | } 43 | -------------------------------------------------------------------------------- /example/ubuntu-chroot.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}", 6 | "resource_group": "{{env `ARM_IMAGE_RESOURCEGROUP_ID`}}", 7 | "gallery_name": "{{env `ARM_GALLERY_NAME`}}" 8 | }, 9 | "builders": [{ 10 | "type": "azure-chroot", 11 | 12 | "client_id": "{{user `client_id`}}", 13 | "client_secret": "{{user `client_secret`}}", 14 | "subscription_id": "{{user `subscription_id`}}", 15 | 16 | "source": "Canonical:UbuntuServer:20.04-LTS:latest", 17 | 18 | "shared_image_destination": { 19 | "resource_group": "{{user `resource_group`}}", 20 | "gallery_name": "{{user `gallery_name`}}", 21 | "image_name": "MyUbuntuOSImage", 22 | "image_version": "1.0.0", 23 | "exclude_from_latest": false, 24 | "target_regions": [ 25 | { 26 | "name": "eastus", 27 | "replicas": "1", 28 | "storage_account_type": "standard_zrs" 29 | } 30 | ] 31 | } 32 | }], 33 | "provisioners": [{ 34 | "inline": [ 35 | "apt update", 36 | "apt upgrade -y" 37 | ], 38 | "inline_shebang": "/bin/sh -x", 39 | "type": "shell" 40 | }] 41 | } 42 | -------------------------------------------------------------------------------- /example/ubuntu.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}" 6 | }, 7 | "builders": [{ 8 | "type": "azure-arm", 9 | 10 | "client_id": "{{user `client_id`}}", 11 | "client_secret": "{{user `client_secret`}}", 12 | "subscription_id": "{{user `subscription_id`}}", 13 | 14 | "os_type": "Linux", 15 | "image_publisher": "Canonical", 16 | "image_offer": "UbuntuServer", 17 | "image_sku": "16.04-LTS", 18 | 19 | "managed_image_resource_group_name": "packertest", 20 | "managed_image_name": "MyUbuntuImage", 21 | 22 | "azure_tags": { 23 | "dept": "engineering", 24 | "task": "image deployment" 25 | }, 26 | 27 | "location": "South Central US", 28 | "vm_size": "Standard_DS2_v2" 29 | }], 30 | "provisioners": [{ 31 | "execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'", 32 | "inline": [ 33 | "apt-get update", 34 | "apt-get upgrade -y", 35 | 36 | "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync" 37 | ], 38 | "inline_shebang": "/bin/sh -x", 39 | "type": "shell" 40 | }] 41 | } 42 | -------------------------------------------------------------------------------- /example/ubuntu_managed_image_sig.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}", 6 | "tenant_id": "{{env `ARM_TENANT_ID`}}" 7 | }, 8 | "builders": [{ 9 | "type": "azure-arm", 10 | 11 | "client_id": "{{user `client_id`}}", 12 | "client_secret": "{{user `client_secret`}}", 13 | "tenant_id": "{{user `tenant_id`}}", 14 | "subscription_id": "{{user `subscription_id`}}", 15 | 16 | "os_type": "Linux", 17 | "image_publisher": "Canonical", 18 | "image_offer": "UbuntuServer", 19 | "image_sku": "16.04-LTS", 20 | 21 | "location": "West Central US", 22 | "vm_size": "Standard_DS2_v2", 23 | 24 | "managed_image_resource_group_name": "PackerSigRGManagedImageRG", 25 | "managed_image_name": "demo-image-sig-packer", 26 | "shared_image_gallery_destination": { 27 | "resource_group": "PackerSigPublishRG", 28 | "gallery_name": "PackerSigGallery", 29 | "image_name": "PackerSigImageDefinition", 30 | "image_version": "1.0.0", 31 | "replication_regions": ["South Central US"] 32 | } 33 | }], 34 | "provisioners": [{ 35 | "execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'", 36 | "inline": [ 37 | "apt-get update", 38 | "apt-get upgrade -y", 39 | 40 | "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync" 41 | ], 42 | "inline_shebang": "/bin/sh -x", 43 | "type": "shell" 44 | }] 45 | } 46 | 47 | -------------------------------------------------------------------------------- /example/ubuntu_quickstart.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "resource_group": "{{env `ARM_RESOURCE_GROUP`}}", 4 | "storage_account": "{{env `ARM_STORAGE_ACCOUNT`}}", 5 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}" 6 | }, 7 | "builders": [{ 8 | "type": "azure-arm", 9 | 10 | "resource_group_name": "{{user `resource_group`}}", 11 | "storage_account": "{{user `storage_account`}}", 12 | "subscription_id": "{{user `subscription_id`}}", 13 | 14 | "capture_container_name": "images", 15 | "capture_name_prefix": "packer", 16 | 17 | "os_type": "Linux", 18 | "image_publisher": "Canonical", 19 | "image_offer": "UbuntuServer", 20 | "image_sku": "16.04-LTS", 21 | 22 | "location": "West US", 23 | "vm_size": "Standard_DS2_v2" 24 | }], 25 | "provisioners": [{ 26 | "execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'", 27 | "inline": [ 28 | "apt-get update", 29 | "apt-get upgrade -y", 30 | "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync" 31 | ], 32 | "inline_shebang": "/bin/sh -x", 33 | "type": "shell" 34 | }] 35 | } 36 | -------------------------------------------------------------------------------- /example/variables.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | variable "client_id" { 5 | type = string 6 | default = "${env("ARM_CLIENT_ID")}" 7 | } 8 | 9 | variable "client_secret" { 10 | type = string 11 | default = "${env("ARM_CLIENT_SECRET")}" 12 | } 13 | 14 | variable "resource_group" { 15 | type = string 16 | default = "${env("ARM_RESOURCE_GROUP_NAME")}" 17 | } 18 | 19 | variable "ssh_pass" { 20 | type = string 21 | default = "${env("ARM_SSH_PASS")}" 22 | } 23 | 24 | variable "ssh_user" { 25 | type = string 26 | default = "centos" 27 | } 28 | 29 | variable "subscription_id" { 30 | type = string 31 | default = "${env("ARM_SUBSCRIPTION_ID")}" 32 | } 33 | 34 | variable "tenant_id" { 35 | type = string 36 | default = "${env("ARM_TENANT_ID")}" 37 | } 38 | 39 | -------------------------------------------------------------------------------- /example/windows.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}" 6 | }, 7 | "builders": [{ 8 | "type": "azure-arm", 9 | 10 | "client_id": "{{user `client_id`}}", 11 | "client_secret": "{{user `client_secret`}}", 12 | "subscription_id": "{{user `subscription_id`}}", 13 | 14 | "managed_image_resource_group_name": "packertest", 15 | "managed_image_name": "MyWindowsOSImage", 16 | 17 | "os_type": "Windows", 18 | "image_publisher": "MicrosoftWindowsServer", 19 | "image_offer": "WindowsServer", 20 | "image_sku": "2022-datacenter", 21 | 22 | "communicator": "winrm", 23 | "winrm_use_ssl": "true", 24 | "winrm_insecure": "true", 25 | "winrm_timeout": "3m", 26 | "winrm_username": "packer", 27 | 28 | "location": "South Central US", 29 | "vm_size": "Standard_DS2_v2" 30 | }], 31 | "provisioners": [{ 32 | "type": "powershell", 33 | "inline": [ 34 | "# If Guest Agent services are installed, make sure that they have started.", 35 | "foreach ($service in Get-Service -Name RdAgent, WindowsAzureTelemetryService, WindowsAzureGuestAgent -ErrorAction SilentlyContinue) { while ((Get-Service $service.Name).Status -ne 'Running') { Start-Sleep -s 5 } }", 36 | 37 | "if( Test-Path $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml ){ rm $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml -Force}", 38 | "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit /mode:vm", 39 | "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; Write-Output $imageState.ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Start-Sleep -s 10 } else { break } }" 40 | ] 41 | }] 42 | } 43 | 44 | -------------------------------------------------------------------------------- /example/windows_custom_image.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "client_id": "{{env `ARM_CLIENT_ID`}}", 4 | "client_secret": "{{env `ARM_CLIENT_SECRET`}}", 5 | "resource_group": "{{env `ARM_RESOURCE_GROUP`}}", 6 | "storage_account": "{{env `ARM_STORAGE_ACCOUNT`}}", 7 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}", 8 | "object_id": "{{env `ARM_OBJECT_ID`}}" 9 | }, 10 | "builders": [ 11 | { 12 | "type": "azure-arm", 13 | 14 | "client_id": "{{user `client_id`}}", 15 | "client_secret": "{{user `client_secret`}}", 16 | "resource_group_name": "{{user `resource_group`}}", 17 | "storage_account": "{{user `storage_account`}}", 18 | "subscription_id": "{{user `subscription_id`}}", 19 | "object_id": "{{user `object_id`}}", 20 | 21 | "capture_container_name": "images", 22 | "capture_name_prefix": "packer", 23 | 24 | "os_type": "Windows", 25 | "image_url": "https://my-storage-account.blob.core.windows.net/path/to/your/custom/image.vhd", 26 | 27 | "azure_tags": { 28 | "dept": "engineering", 29 | "task": "image deployment" 30 | }, 31 | 32 | "location": "West US", 33 | "vm_size": "Standard_DS2_v2" 34 | } 35 | ], 36 | "provisioners": [{ 37 | "type": "powershell", 38 | "inline": [ 39 | "# If Guest Agent services are installed, make sure that they have started.", 40 | "foreach ($service in Get-Service -Name RdAgent, WindowsAzureTelemetryService, WindowsAzureGuestAgent -ErrorAction SilentlyContinue) { while ((Get-Service $service.Name).Status -ne 'Running') { Start-Sleep -s 5 } }", 41 | 42 | "if( Test-Path $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml ){ rm $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml -Force}", 43 | "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit /mode:vm", 44 | "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Write-Output $imageState.ImageState; Start-Sleep -s 10 } else { break } }" 45 | ] 46 | }] 47 | } 48 | -------------------------------------------------------------------------------- /example/windows_custom_image/packer_config.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | packer { 5 | required_plugins { 6 | azure = { 7 | version = ">= 1.4.2" 8 | source = "github.com/hashicorp/azure" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example/windows_custom_image/variables.auto.pkrvars.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | acgName = "acgDemo" 5 | temp_os_disk_name = "osDisk001" 6 | destination_image_version = "1.0.0" 7 | Release = "AWESOME" -------------------------------------------------------------------------------- /example/windows_custom_image/variables.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | variable "rgName" { 5 | type = string 6 | default = "rg-acg-test" 7 | } 8 | 9 | variable "acgName" { 10 | type = string 11 | default = "acgDemo" 12 | } 13 | 14 | variable "image_name" { 15 | type = string 16 | default = "WindowsImage" 17 | } 18 | 19 | variable "build_key_vault_name" { 20 | type = string 21 | default = "kv-demo" 22 | } 23 | 24 | variable "build_revision" { 25 | type = string 26 | default = "001" 27 | } 28 | 29 | variable "disk_encryption_set_id" { 30 | type = string 31 | default = "/subscriptions//resourceGroups//providers/Microsoft.Compute/diskEncryptionSets/" 32 | } 33 | 34 | variable "image_offer" { 35 | type = string 36 | default = "WindowsServer" 37 | } 38 | 39 | variable "image_publisher" { 40 | type = string 41 | default = "MicrosoftWindowsServer" 42 | } 43 | 44 | variable "image_sku" { 45 | type = string 46 | default = "2022-datacenter-g2" 47 | } 48 | 49 | variable "temp_os_disk_name" { 50 | type = string 51 | default = "osDisk001" 52 | } 53 | 54 | variable "destination_image_version" { 55 | type = string 56 | default = "1.0.0" 57 | } 58 | 59 | variable "location" { 60 | type = string 61 | default = "westeurope" 62 | } 63 | 64 | variable "vmSize" { 65 | type = string 66 | default = "Standard_DS3_V2" 67 | } 68 | 69 | variable "subscription_id" { 70 | type = string 71 | default = "" 72 | } 73 | 74 | variable "tenant_id" { 75 | type = string 76 | default = "" 77 | } 78 | 79 | variable "client_id" { 80 | type = string 81 | default = "" 82 | } 83 | 84 | variable "client_secret" { 85 | type = string 86 | default = "" 87 | } 88 | 89 | variable "Release" { 90 | type = string 91 | default = "COOL" 92 | } 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /example/windows_quickstart.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}" 4 | }, 5 | "builders": [{ 6 | "type": "azure-arm", 7 | 8 | "subscription_id": "{{user `subscription_id`}}", 9 | 10 | "managed_image_resource_group_name": "packertest", 11 | "managed_image_name": "MyWindowsOSImage", 12 | 13 | "os_type": "Windows", 14 | "image_publisher": "MicrosoftWindowsServer", 15 | "image_offer": "WindowsServer", 16 | "image_sku": "2022-datacenter", 17 | 18 | "communicator": "winrm", 19 | "winrm_use_ssl": "true", 20 | "winrm_insecure": "true", 21 | "winrm_timeout": "3m", 22 | "winrm_username": "packer", 23 | 24 | "location": "South Central US", 25 | "vm_size": "Standard_DS2_v2" 26 | }], 27 | "provisioners": [{ 28 | "type": "powershell", 29 | "inline": [ 30 | "# If Guest Agent services are installed, make sure that they have started.", 31 | "foreach ($service in Get-Service -Name RdAgent, WindowsAzureTelemetryService, WindowsAzureGuestAgent -ErrorAction SilentlyContinue) { while ((Get-Service $service.Name).Status -ne 'Running') { Start-Sleep -s 5 } }", 32 | 33 | "if( Test-Path $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml ){ rm $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml -Force}", 34 | "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit /mode:vm", 35 | "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Write-Output $imageState.ImageState; Start-Sleep -s 10 } else { break } }" 36 | ] 37 | }] 38 | } 39 | 40 | -------------------------------------------------------------------------------- /example/windows_skip_key_vault/11/userdata.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | # Startup the WinRM Service 5 | Enable-PSRemoting -Force -SkipNetworkProfileCheck 6 | 7 | # Create a Firewall rule to allow build computer to connect to the Azure VM 8 | New-NetFirewallRule -Name "Allow WinRM HTTPS" -DisplayName "WinRM HTTPS" -Enabled True -Profile Any -Action Allow -Direction Inbound -LocalPort 5986 -Protocol TCP 9 | 10 | # Used for creating the WinRM certificate for authentication 11 | $thumbprint = (New-SelfSignedCertificate -DnsName $env:COMPUTERNAME -CertStoreLocation Cert:\LocalMachine\My -NotAfter $(Get-Date).AddDays(1)).Thumbprint 12 | 13 | # Create a new WinRM listener using this certificate 14 | $command = "winrm create winrm/config/Listener?Address=*+Transport=HTTPS @{Hostname=""$env:computername""; CertificateThumbprint=""$thumbprint""}" 15 | cmd.exe /C $command 16 | -------------------------------------------------------------------------------- /example/windows_skip_key_vault/11/windows_skip_key_vault.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | packer { 5 | required_plugins { 6 | azure = { 7 | source = "github.com/hashicorp/azure" 8 | version = "~> 1" 9 | } 10 | } 11 | } 12 | 13 | variable "resource_group" { 14 | type = string 15 | } 16 | 17 | source "azure-arm" "autogenerated_1" { 18 | # Authentication and build settings 19 | 20 | polling_duration_timeout = "30m" 21 | location = "East US" 22 | use_azure_cli_auth = "true" 23 | vm_size = "Standard_D2ds_v4" 24 | 25 | # Powershell script which adds a WinRM certificate to the Virtual Machine 26 | custom_script = "powershell -ExecutionPolicy Unrestricted -NoProfile -NonInteractive -Command \"$userData = (Invoke-RestMethod -H @{'Metadata'='True'} -Method GET -Uri 'http://169.254.169.254/metadata/instance/compute/userData?api-version=2021-01-01&format=text'); $contents = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($userData)); set-content -path c:\\Windows\\Temp\\userdata.ps1 -value $contents; . c:\\Windows\\Temp\\userdata.ps1;\"" 27 | user_data_file = "./userdata.ps1" 28 | 29 | # Base Image 30 | os_type = "Windows" 31 | image_offer = "windows-11" 32 | image_publisher = "MicrosoftWindowsDesktop" 33 | image_sku = "win11-22h2-pro" 34 | image_version = "latest" 35 | 36 | # Output Image 37 | managed_image_name = "windows-skip-kv-{{timestamp}}" 38 | managed_image_resource_group_name = var.resource_group 39 | 40 | # WinRM configuration 41 | communicator = "winrm" 42 | skip_create_build_key_vault = true 43 | winrm_username = "packer" 44 | winrm_use_ssl = true 45 | 46 | # This example provides a self-signed certificate on the VM, so it not signed by a valid certificate authority. With a user provided certificate authority one should be able to make this more secure if that's required as a policy 47 | winrm_insecure = true 48 | 49 | } 50 | 51 | build { 52 | sources = ["source.azure-arm.autogenerated_1"] 53 | 54 | # Sysprep 55 | provisioner "powershell" { 56 | inline = [ 57 | "if( Test-Path $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml ){ rm $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml -Force}", 58 | "& $Env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /shutdown /quiet" 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /example/windows_skip_key_vault/avd/userdata.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | # Startup the WinRM service 5 | Enable-PSRemoting -Force 6 | 7 | # Create a Firewall rule to allow build computer to connect to the Azure VM 8 | New-NetFirewallRule -Name "Allow WinRM HTTPS" -DisplayName "WinRM HTTPS" -Enabled True -Profile Any -Action Allow -Direction Inbound -LocalPort 5986 -Protocol TCP 9 | 10 | # Used for creating the WinRM certificate for authentication 11 | $thumbprint = (New-SelfSignedCertificate -DnsName $env:COMPUTERNAME -CertStoreLocation Cert:\LocalMachine\My -NotAfter $(Get-Date).AddDays(1)).Thumbprint 12 | 13 | # Create a new WinRM listener using this certificate 14 | $command = "winrm create winrm/config/Listener?Address=*+Transport=HTTPS @{Hostname=""$env:computername""; CertificateThumbprint=""$thumbprint""}" 15 | cmd.exe /C $command 16 | -------------------------------------------------------------------------------- /example/windows_skip_key_vault/avd/windows_skip_key_vault.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | packer { 5 | required_plugins { 6 | azure = { 7 | source = "github.com/hashicorp/azure" 8 | version = "~> 1" 9 | } 10 | } 11 | } 12 | 13 | variable "resource_group" { 14 | type = string 15 | } 16 | 17 | source "azure-arm" "autogenerated_1" { 18 | # Authentication and build settings 19 | 20 | polling_duration_timeout = "30m" 21 | location = "East US" 22 | use_azure_cli_auth = "true" 23 | vm_size = "Standard_D2ds_v4" 24 | 25 | # Powershell script which adds a WinRM certificate to the Virtual Machine 26 | custom_script = "powershell -ExecutionPolicy Unrestricted -NoProfile -NonInteractive -Command \"$userData = (Invoke-RestMethod -H @{'Metadata'='True'} -Method GET -Uri 'http://169.254.169.254/metadata/instance/compute/userData?api-version=2021-01-01&format=text'); $contents = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($userData)); set-content -path c:\\Windows\\Temp\\userdata.ps1 -value $contents; . c:\\Windows\\Temp\\userdata.ps1;\"" 27 | user_data_file = "./userdata.ps1" 28 | 29 | # Base Image 30 | os_type = "Windows" 31 | image_offer = "Windows-10" 32 | image_publisher = "MicrosoftWindowsDesktop" 33 | image_sku = "win10-22h2-avd" 34 | image_version = "latest" 35 | 36 | # Output Image 37 | managed_image_name = "windows-skip-kv-{{timestamp}}" 38 | managed_image_resource_group_name = var.resource_group 39 | 40 | # WinRM configuration 41 | communicator = "winrm" 42 | skip_create_build_key_vault = true 43 | winrm_username = "packer" 44 | winrm_use_ssl = true 45 | 46 | # This example provides a self-signed certificate on the VM, so it not signed by a valid certificate authority. With a user provided certificate authority one should be able to make this more secure if that's required as a policy 47 | winrm_insecure = true 48 | 49 | } 50 | 51 | build { 52 | sources = ["source.azure-arm.autogenerated_1"] 53 | 54 | # Sysprep 55 | provisioner "powershell" { 56 | inline = [ 57 | "if( Test-Path $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml ){ rm $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml -Force}", 58 | "& $Env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /shutdown /quiet" 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /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 | azurearm "github.com/hashicorp/packer-plugin-azure/builder/azure/arm" 11 | azurechroot "github.com/hashicorp/packer-plugin-azure/builder/azure/chroot" 12 | azuredtl "github.com/hashicorp/packer-plugin-azure/builder/azure/dtl" 13 | azuredtlartifact "github.com/hashicorp/packer-plugin-azure/provisioner/azure-dtlartifact" 14 | "github.com/hashicorp/packer-plugin-azure/version" 15 | 16 | "github.com/hashicorp/packer-plugin-sdk/plugin" 17 | ) 18 | 19 | func main() { 20 | pps := plugin.NewSet() 21 | pps.RegisterBuilder("arm", new(azurearm.Builder)) 22 | pps.RegisterBuilder("chroot", new(azurechroot.Builder)) 23 | pps.RegisterBuilder("dtl", new(azuredtl.Builder)) 24 | pps.RegisterProvisioner("dtlartifact", new(azuredtlartifact.Provisioner)) 25 | pps.SetVersion(version.AzurePluginVersion) 26 | err := pps.Run() 27 | if err != nil { 28 | fmt.Fprintln(os.Stderr, err.Error()) 29 | os.Exit(1) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /terraform/README.md: -------------------------------------------------------------------------------- 1 | This repo's acceptance tests require a bit of setup to run, a subscription, and app registration, and several resources must be created. To make this process easier to manage in CI and easier for developers to quickly test changes, this directory contains terraform configuration to create the required resources. 2 | 3 | ## Creating Azure Resources 4 | 5 | First you need an Azure Subscription, it is also recommended to also have an app registration created with client/secret authentication setup, as this is required for the acceptance tests themselves. 6 | 7 | Authenticate to Azure using the Azure CLI for a service principal 8 | 9 | The default resource group is named `packer-acceptance-test` with a storage account named `packeracctest`, however you can use variables TF `resource_group_name` and `storage_account_name` to change that to anything 10 | 11 | For example 12 | ``` 13 | terraform apply -var "resource_group_name=cool-group" -var "storage_account_name=coolblobstore" 14 | ``` 15 | 16 | Note that Azure storage account names must not contain special characters 17 | 18 | -------------------------------------------------------------------------------- /terraform/providers.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | terraform { 5 | required_providers { 6 | azurerm = { 7 | source = "hashicorp/azurerm" 8 | version = "~>4.14" 9 | } 10 | } 11 | } 12 | 13 | provider "azurerm" { 14 | features { 15 | resource_group { 16 | prevent_deletion_if_contains_resources = false 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /terraform/run_terraform_apply_with_expected_env_vars.sh: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | if [ -z "${AZURE_CLI_AUTH}" ]; then 5 | echo "AZURE_CLI_AUTH is unset or set to the empty string" 6 | exit 1 7 | fi 8 | 9 | if [ -z "${ARM_RESOURCE_GROUP_NAME}" ]; then 10 | echo "ARM_RESOURCE_GROUP_NAME is unset or set to the empty string" 11 | exit 1 12 | fi 13 | 14 | if [ -z "${ARM_STORAGE_ACCOUNT}" ]; then 15 | echo "ARM_STORAGE_ACCOUNT is unset or set to the empty string" 16 | exit 1 17 | fi 18 | 19 | if [ -z "${ARM_RESOURCE_PREFIX}" ]; then 20 | echo "ARM_RESOURCE_PREFIX is unset or set to the empty string" 21 | exit 1 22 | fi 23 | 24 | if ! command -v terraform &> /dev/null 25 | then 26 | echo "terraform is not installed" 27 | exit 1 28 | fi 29 | terraform apply -var "resource_prefix=${ARM_RESOURCE_PREFIX}" -var "resource_group_name=${ARM_RESOURCE_GROUP_NAME}" -var "storage_account_name=${ARM_STORAGE_ACCOUNT}" -auto-approve 30 | 31 | -------------------------------------------------------------------------------- /terraform/run_terraform_destroy_with_expected_env_vars.sh: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | if [ -z "${AZURE_CLI_AUTH}" ]; then 5 | echo "AZURE_CLI_AUTH is unset or set to the empty string" 6 | exit 1 7 | fi 8 | 9 | if [ -z "${ARM_RESOURCE_GROUP_NAME}" ]; then 10 | echo "ARM_RESOURCE_GROUP_NAME is unset or set to the empty string" 11 | exit 1 12 | fi 13 | 14 | if [ -z "${ARM_STORAGE_ACCOUNT}" ]; then 15 | echo "ARM_STORAGE_ACCOUNT is unset or set to the empty string" 16 | exit 1 17 | fi 18 | 19 | if [ -z "${ARM_RESOURCE_PREFIX}" ]; then 20 | echo "ARM_RESOURCE_PREFIX is unset or set to the empty string" 21 | exit 1 22 | fi 23 | 24 | if ! command -v terraform &> /dev/null 25 | then 26 | echo "terraform is not installed" 27 | exit 1 28 | fi 29 | terraform destroy -var "resource_prefix=${ARM_RESOURCE_PREFIX}" -var "resource_group_name=${ARM_RESOURCE_GROUP_NAME}" -var "storage_account_name=${ARM_STORAGE_ACCOUNT}" -auto-approve 30 | 31 | -------------------------------------------------------------------------------- /terraform/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | variable "resource_group_location" { 5 | type = string 6 | default = "southcentralus" 7 | } 8 | 9 | variable "resource_group_name" { 10 | type = string 11 | } 12 | 13 | variable "storage_account_name" { 14 | type = string 15 | } 16 | 17 | // Variable applied to resources that have uniqueness constraints at a subscription level 18 | // For example you can't have two shared image galleries named `linux` in the same Subscription in different resource group 19 | variable "resource_prefix" { 20 | type = string 21 | } 22 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package version 5 | 6 | import ( 7 | "github.com/hashicorp/packer-plugin-sdk/version" 8 | ) 9 | 10 | var ( 11 | Version = "2.3.4" 12 | VersionPrerelease = "dev" 13 | VersionMetadata = "" 14 | AzurePluginVersion = version.NewPluginVersion(Version, VersionPrerelease, VersionMetadata) 15 | ) 16 | -------------------------------------------------------------------------------- /version/version_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package version 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | ) 10 | 11 | func TestAzurePluginVersion_FormattedVersion(t *testing.T) { 12 | if AzurePluginVersion == nil { 13 | t.Fatal("Unable to continue with nil version") 14 | } 15 | 16 | expected := Version 17 | if VersionPrerelease != "" { 18 | expected = fmt.Sprintf("%s-%s", Version, VersionPrerelease) 19 | } 20 | got := AzurePluginVersion.FormattedVersion() 21 | if got != expected { 22 | t.Errorf("calling FormattedVersion on AzurePluginVersion failed: expected %s, but got %s", expected, got) 23 | } 24 | 25 | } 26 | --------------------------------------------------------------------------------