Compare commits

..

1 Commits

Author SHA1 Message Date
Wilken Rivera 44eda339d3 docs/powershell: Add struct-markdown to provisioner config 2020-08-24 16:27:29 -04:00
2444 changed files with 198032 additions and 384279 deletions
+7 -7
View File
@@ -7,11 +7,11 @@ version: 2.1
executors:
golang:
docker:
- image: docker.mirror.hashicorp.services/circleci/golang:1.15
- image: circleci/golang:1.13
resource_class: medium+
darwin:
macos:
xcode: "12.0.0"
xcode: "9.0"
commands:
install-go-run-tests-unix:
@@ -67,7 +67,7 @@ jobs:
steps:
- install-go-run-tests-unix:
GOOS: darwin
GOVERSION: "1.15"
GOVERSION: "1.13"
- codecov/upload:
file: coverage.txt
test-windows:
@@ -76,7 +76,7 @@ jobs:
shell: bash.exe
steps:
- install-go-run-tests-windows:
GOVERSION: "1.15"
GOVERSION: "1.13"
- codecov/upload:
file: coverage.txt
check-lint:
@@ -153,7 +153,7 @@ jobs:
destination: /
build-website-docker-image:
docker:
- image: docker.mirror.hashicorp.services/circleci/buildpack-deps
- image: circleci/buildpack-deps
shell: /usr/bin/env bash -euo pipefail -c
steps:
- checkout
@@ -167,14 +167,14 @@ jobs:
echo "Dependencies have not changed, not building a new website docker image."
else
cd website/
docker login -u $WEBSITE_DOCKER_USER -p $WEBSITE_DOCKER_PASS
docker build -t hashicorp/packer-website:$IMAGE_TAG .
docker tag hashicorp/packer-website:$IMAGE_TAG hashicorp/packer-website:latest
docker login -u $WEBSITE_DOCKER_USER -p $WEBSITE_DOCKER_PASS
docker push hashicorp/packer-website
fi
algolia-index:
docker:
- image: docker.mirror.hashicorp.services/node:12
- image: node:12
steps:
- checkout
- run:
+2 -3
View File
@@ -13,6 +13,5 @@ coverage:
project: off
patch: off
ignore: # ignore hcl2spec generated code for coverage and mocks
- "**/*.hcl2spec.go"
- "**/*_mock.go"
ignore: # ignore hcl2spec generated code for coverage
- "**/*.hcl2spec.go"
-1
View File
@@ -6,7 +6,6 @@
*.mdx text eol=lf
*.ps1 text eol=lf
*.hcl text eol=lf
*.txt text eol=lf
go.mod text eol=lf
go.sum text eol=lf
common/test-fixtures/root/* eol=lf
-1
View File
@@ -27,4 +27,3 @@ Thumbs.db
/packer.exe
.project
cache
/.vscode/
+1 -227
View File
@@ -1,211 +1,4 @@
## 1.6.6 (Upcoming)
### FEATURES:
* **New command** `fmt` allows users to format existing HCL2 configuration
files into a canonical style. Please see [fmt command
docs](https://packer.io/docs/commands/fmt) for more details. [GH-10225]
* **New function** `env` allows users to set the default value of a variable to
the value of an environment variable. Please see [env function
docs](https://www.packer.io/docs/from-1.5/functions/contextual/env") for
more details. [GH-10240]
* builder/azure-arm: Create keyvaults with SoftDelete enabled [GH-10210]
* builder/outscale: Add x509 certificate support [GH-10161]
* post-processor/yandex-export: Verify the access to a specific bucket
[GH-10188]
### IMPROVEMENTS
* builder/amazon-ebs: Add tags to launch templates. [GH-10203]
* builder/azure-arm: Add Azure CLI authentication support to builder.
[GH-10157]
* core/hcl: Update to `hcl2_upgrade` command to support complex variable
values and packer version blocks. [GH-10221]
* post-processor/vagrant-cloud: Add support for uploading directly to storage
on Vagrant Cloud. [GH-10193]
### BUG FIXES
* builder/amazon: Fix single `tag` interpolation to allow for templating engine
usage. [GH-10224]
## 1.6.5 (October 30, 2020)
### FEATURES:
* New Builder(s): Proxmox builder has been split into two new builders
`proxmox-iso` and `promox-clone`. See [Proxmox
Builder](https://packer.io/docs/builders/proxmox) for more information on
the builder. For users of the previous `proxmox` builder please use `packer
fix` to migrate your templates to the new `promox-iso` builder. [GH-9262]
### BUG FIXES:
* builder/amazon: SSM connection now recovers from reboots. [GH-10003]
* builder/azure-arm: Fix build failures due to the deletion of additional
managed disks defined in "disk_additional_size". [GH-10163]
* builder/azure-chroot: Fix typo in option `exlude_from_latest` to
`exclude_from_latest`. Old name will still be respected. [GH-10034]
* builder/googlecompute: Fix HCL image_encryption_key fields and use the same
casing in JSON and HCL2 [GH-10173]
* builder/openstack: Fix source image validation regression when using filters.
[GH-10065]
* builder/proxmox: Fix unhandled buildvar type for HCL2 enabled build
templates. [GH-10154]
* builder/qemu: Fix a regression where Packer would not wait properly in
step_shutdown when a null communicator was used. [GH-10178]
* builder/qemu: Fix crash in step_run of qemu when loading commhostport form
the statebag in a situation where the communicator is none. [GH-10145]
* builder/vsphere-clone: Packer was not respecting the "destroy" flag set in
the content library config, and always keeping the source vm. This has been
fixed. [GH-10165]
* builder/vsphere: Ensure builds are able to continue when no communicator has
been specified `"communicator": "none"`. [GH-9964]
* builder/vsphere: Fix CD uploads so that Packer does not try to delete a CD
that was not successfully uploaded. [GH-10155]
* core/hcl: Hide sensitive variables from output. [GH-10031]
* core/hcl: Packer HCL's "Coalesce" function now behaves same way as
Terraform's. [GH-10016]
* core: Fix artifact handling so that input artifacts are properly preserved in
postprocessors that don't modify artifacts. [GH-9996]
* core: Fix pathing in cd_files to copy proper directory tree when user
provided absolute paths. [GH-10022]
* provisioner/ansible: Ansible galaxy no longer forces use of collections in v1
files. [GH-10010]
### IMPROVEMENTS:
* builder/amazon-ebssurrogate: Apply snapshot tags at snapshot creation time.
[GH-10150]
* builder/amazon: Add `io2` as a supported volume type. [GH-10102]
* builder/amazon: Add support for source instance tenancy [GH-10085]
* builder/google: Add service account impersonation. [GH-9968] [GH-10054]
* builder/googlecompute: Add `skip_create_image` option. [GH-10115]
* builder/googlecompute: Allow users to select the algorithm to use when
generating temporary SSH keypair [GH-10111]
* builder/linode: Add `state_timeout` attribute to Linode builder. [GH-10128]
* builder/oracle-oci: New option to specify image compartment separate from
build compartment. [GH-10040]
* builder/oracle-oci: New option to specify boot volume size. [GH-10017]
* builder/oracle: Add `base_image_filter` option as alternative to
`base_image_ocid` [GH-10116]
* builder/outscale: Migrate to new Outscale SDK. [GH-10056]
* builder/proxmox: split Proxmox into proxmox-iso and proxmox-clone. [GH-9626]
[GH-10166]
* builder/scaleway: Allow the user to use an image label (eg ubuntu_focal)
instead of a hardcoded UUID on the Scaleway builder. [GH-10061]
* builder/vsphere: Skip iso download if hashed file is already present on
remote datastore. [GH-10143]
* builder/yandex: Add support for IAM credentials in the token field and
YC_TOKEN environment variable. [GH-10158]
* core/hcl: Add ability to set version restrictions [GH-10149]
* core/hcl: Add build.name variable so users can access build name in addition
to source name. [GH-10114]
* core/hcl: Add consul_key function to HCL templates. [GH-10119]
* core/hcl: Add HCL2 aws_secretsmanager function [GH-10124]
* core/hcl: Add packer.version variable to hcl configs so users can access the
Packer release version. [GH-10117]
* core: Let user provide type of generated ssh key instead of always doing ssh-
rsa [GH-10101]
## 1.6.4 (September 30, 2020)
### BUG FIXES:
* builder/amazon: Fix authentication issue when using instance profiles or
assumed roles for loading session-derived credentials. [GH-10007]
* builder/azure: Fix crash when using `azure_tag` or `azure_tags` configuration
options. [GH-10014]
* builder/qemu: Ensure `qemu_img_args` are honored during the disk convert
step. [GH-10001]
## 1.6.3 (September 25, 2020)
### IMPROVEMENTS:
* builder/amazon: Add `pause_before_ssm` option to pause for some time before
establishing a Session Manager session; defaults to 10s. [GH-9988]
* builder/amazon: Implement assume_role option that matches Terraform behavior.
[GH-9981]
* builder/azure: Support publishing to a Shared Image Gallery with a different
subscription id [GH-9875]
* builder/openstack: Add `external_source_image_url` and
`external_source_image_format` to support building images from external
source URLs. [GH-9992]
* builder/openstack: Include API requests and responses as part of the debug
log output. [GH-9972]
* builder/oracle-oci: Add `create_vnic_details` option for launch details.
[GH-9856]
* builder/oracle-oci: Allow freeform and defined tags to be added to an instance.
[GH-9802]
* builder/proxmox: Add `io_thread` option for supporting io threads when using
a `virtio-scsi-single` controller with a `scsi` or `virtio` disk type.
[GH-9969]
* builder/proxmox: Add ability to specify interfaces for http_directory and VM.
[GH-9874]
* builder/proxmox: Allow the mounting of multiple ISOs via the `cd_drive`
option. [GH-9653]
* builder/proxmox: Fix boot command special keys. [GH-9885]
* builder/qemu: Add `qemu_img_args` option to set special cli flags for calls
to qemu-img [GH-9956]
* builder/qemu: Add `skip_resize_disk` option to skip the resizing of QCOW2
images. [GH-9896] [GH-9860]
* builder/qemu: Skip qemu-img convert on MacOS to prevent the creation of
corrupt images [QEMU
#1776920](https://bugs.launchpad.net/qemu/+bug/1776920) [GH-9949]
* builder/scaleway: Change default boottype to local. [GH-9853]
* builder/scaleway: Update scaleway to use non-deprecated sdk. [GH-9902]
* builder/vmware: Add `vnc_over_websocket` to allow the sending of a
`boot_command` to hosts running ESXi 6.7 and above. [GH-9938]
* builder/vmware: Allow user to set vmware tools source path. [GH-9983]
* builder/vsphere-clone: Add ability to set `mac_address` [GH-9930]
* builder/vsphere-clone: Add floppy_files, cd_files, and iso_paths options.
[GH-9963]
* builder/vsphere-iso: Add NVMe controller support. [GH-9880]
* builder/vsphere: Look for a default resource pool when root resource pool is
not found. [GH-9809]
* core: Add support for running cygwin/msys2 based cd/iso creation tool
[GH-9954]
* core: New `cd_files` option to mount iso for modern OSes which don't support
floppies. [GH-9796] [GH-9919] [GH-9928] [GH-9932] [GH-9941]
* HCL2: When the type of a variable is not known evaluate setting as a literal
string instead of a variable name. [GH-9863]
* post-processor/vagrant: Support the use of template variables within
Vagrantfile templates. [GH-9923]
* post-processor/yandex-import: Allow custom API endpoint. [GH-9850]
* provisioner/ansible: Add support for Ansible Galaxy Collections. [GH-9903]
### BUG FIXES:
* builder/amazon-ebs: Fix issue where retrying on invalid IAM instance profile
error was creating multiple spot instances. [GH-9946]
* builder/amazon-ebssurrogate: Fix issue where builder defaults to AWS managed
key even when custom `kms_key_id` is set. [GH-9959]
* builder/amazon: Update ssm_driver log polling logic to prevent infinite loops
when SSM driver is terminated outside of Packer. [GH-9991]
* builder/azure: Fix crash when using HCL2 configs. [GH-9984] [GH-9985]
* builder/qemu: Fix hardcoded lowerbound causing negative ports [GH-9905]
* builder/qemu: Skip compaction when backing file is used. [GH-9918]
* builder/scaleway: Add pre validate step to prevent the creation of multiple
images with the same name. [GH-9840]
* builder/vmware-iso: Prevent the use of reserved SCSI ID 0:7 when attaching
multiple disks. [GH-9940]
* builder/vsphere: Fix overly strict iso_path validation regex. [GH-9855]
* command/console: Prevent failure when there are unknown vars. [GH-9864]
* command/inspect: Allow unset variables in HCL2 and JSON. [GH-9832]
* core: Prevent the UI progressbar from hanging and crashing when there is no
TTY available. [GH-9974]
* core: Use $APPDATA over $HOME on Windows hosts when determining homedir.
[GH-9830]
* post-processor/digitalocean-import: Fix crash caused by empty artifact.Files
slice. [GH-9857]
* post-processor/yandex-export: Check for error after runner completes.
[GH-9925]
* post-processor/yandex-export: Set metadata key to expected value on error.
[GH-9849]
* post-processor/yandex-import: Fix S3 URL construct process. [GH-9931]
## 1.6.2 (August 28, 2020)
### FEATURES:
* **New command** `hcl2_upgrade` is a JSON to HCL2 transpiler that allows users
to transform an existing JSON configuration template into its HCL2 template
equivalent. Please see [hcl2_upgrade command
docs](https://packer.io/docs/commands/hcl2_upgrade) for more details.
[GH-9659]
## 1.6.2 (Upcoming)
### IMPROVEMENTS:
* builder/amazon: Add all of the custom AWS template engines to `build`
@@ -215,22 +8,14 @@
* builder/azure: Add FreeBSD support to azure/chroot builder. [GH-9697]
* builder/vmware-esx: Add `network_name` option to vmware so that users can set
a network without using vmx data. [GH-9718]
* builder/vmware-vmx: Add additional disk configuration option. Previously
only implemented for vmware-iso builder [GH-9815]
* builder/vmware: Add a `remote_output_directory option` so users can tell
Packer where on a datastore to create a vm. [GH-9784]
* builder/vmware: Add option to export to ovf or ova from a local vmware build
[GH-9825]
* builder/vmware: Add progress tracker to vmware-esx5 iso upload. [GH-9779]
* builder/vsphere-iso: Add support for building on a single ESXi host
[GH-9793]
* builder/vsphere: Add new `directory_permission` config export option.
[GH-9704]
* builder/vsphere: Add option to import OVF templates to the Content Library
[GH-9755]
* builder/vsphere: Add step and options to customize cloned VMs. [GH-9665]
* builder/vsphere: Update `iso_paths` to support reading ISOs from Content
Library paths [GH-9801]
* core/hcl: Add provisioner "override" option to HCL2 templates. [GH-9764]
* core/hcl: Add vault integration as an HCL2 function function. [GH-9746]
* core: Add colored prefix to progress bar so it's clearer what build each
@@ -242,8 +27,6 @@
[GH-9773]
* post-processor/vsphere: Improve UI to catch bad credentials and print errors.
[GH-9649]
* provisioner/ansible-remote: Add `ansible_ssh_extra_args` so users can specify
extra arguments to pass to ssh [GH-9821]
* provisioner/file: Clean up, bugfix, and document previously-hidden `sources`
option. [GH-9725] [GH-9735]
* provisioner/salt-masterless: Add option to option to download community
@@ -256,11 +39,6 @@
binaries. [GH-9706]
* builder/amazon-ebssurrogate: Make skip_save_build_region option work in the
ebssurrogate builder, not just the ebs builder. [GH-9666]
* builder/amazon: Add retry logic to the spot instance creation step to handle
"Invalid IAM Instance Profile name" errors [GH-9810]
* builder/amazon: Update the `aws_secretsmanager` function to read from the AWS
credentials file for obtaining default region information; fixes the
'MissingRegion' error when AWS_REGION is not set [GH-9781]
* builder/file: Make sure that UploadDir receives the interpolated destination.
[GH-9698]
* builder/googlecompute: Fix bug where startup script hang would cause export
@@ -283,10 +61,6 @@
interpolation. [GH-9673]
* post-processor/vsphere-template: Fix ReregisterVM to default to true instead
of false. [GH-9736]
* post-processor/yandex-export: Fix issue when validating region_name [GH-9814]
* provisioner/inspec: Fix the 'Unsupported argument; An argument named
"command"' error when using the inspec provisioner in an HCL2 configuration
[GH-9800]
## 1.6.1 (July 30, 2020)
+2 -2
View File
@@ -49,8 +49,8 @@
/builder/proxmox/ @carlpett
/website/pages/docs/builders/proxmox* @carlpett
/builder/scaleway/ @scaleway/devtools
/website/pages/docs/builders/scaleway* @scaleway/devtools
/builder/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
/website/pages/docs/builders/scaleway* @sieben @mvaude @jqueuniet @fflorens @brmzkw
/builder/hcloud/ @LKaemmerling
/website/pages/docs/builders/hcloud* @LKaemmerling
+1 -1
View File
@@ -1,4 +1,4 @@
FROM docker.mirror.hashicorp.services/ubuntu:16.04
FROM ubuntu:16.04
ENV DEBIAN_FRONTEND noninteractive
+5 -6
View File
@@ -1,5 +1,4 @@
TEST?=$(shell go list ./...)
COUNT?=1
VET?=$(shell go list ./...)
ACC_TEST_BUILDERS?=all
ACC_TEST_PROVISIONERS?=all
@@ -26,7 +25,7 @@ export GOLDFLAGS
.PHONY: bin checkversion ci ci-lint default install-build-deps install-gen-deps fmt fmt-docs fmt-examples generate install-lint-deps lint \
releasebin test testacc testrace
default: install-build-deps install-gen-deps generate dev
default: install-build-deps install-gen-deps generate bin
ci: testrace ## Test in continuous integration
@@ -137,7 +136,7 @@ generate-check: generate ## Check go code generation is on par
fi
test: mode-check vet ## Run unit tests
@go test -count $(COUNT) $(TEST) $(TESTARGS) -timeout=3m
@go test $(TEST) $(TESTARGS) -timeout=3m
# acctest runs provisioners acceptance tests
provisioners-acctest: install-build-deps generate
@@ -146,14 +145,14 @@ provisioners-acctest: install-build-deps generate
# testacc runs acceptance tests
testacc: install-build-deps generate ## Run acceptance tests
@echo "WARN: Acceptance tests will take a long time to run and may cost money. Ctrl-C if you want to cancel."
PACKER_ACC=1 go test -count $(COUNT) -v $(TEST) $(TESTARGS) -timeout=120m
PACKER_ACC=1 go test -v $(TEST) $(TESTARGS) -timeout=45m
testrace: mode-check vet ## Test with race detection enabled
@GO111MODULE=off go test -count $(COUNT) -race $(TEST) $(TESTARGS) -timeout=3m -p=8
@GO111MODULE=off go test -race $(TEST) $(TESTARGS) -timeout=3m -p=8
# Runs code coverage and open a html page with report
cover:
go test -count $(COUNT) $(TEST) $(TESTARGS) -timeout=3m -coverprofile=coverage.out
go test $(TEST) $(TESTARGS) -timeout=3m -coverprofile=coverage.out
go tool cover -html=coverage.out
rm coverage.out
-1
View File
@@ -46,7 +46,6 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
err := config.Decode(&b.config, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
-4
View File
@@ -107,8 +107,6 @@ type FlatConfig struct {
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
@@ -223,8 +221,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
+1
View File
@@ -422,6 +422,7 @@ func TestBuilderAcc_ECSImageSharing(t *testing.T) {
})
}
// share with catsby
const testBuilderAccSharing = `
{
"builders": [{
+81 -41
View File
@@ -12,8 +12,6 @@ import (
"github.com/hashicorp/packer/template/interpolate"
)
// The "AlicloudDiskDevice" object us used for the `ECSSystemDiskMapping` and
// `ECSImagesDiskMappings` options, and contains the following fields:
type AlicloudDiskDevice struct {
// The value of disk name is blank by default. [2,
// 128] English or Chinese characters, must begin with an
@@ -21,7 +19,8 @@ type AlicloudDiskDevice struct {
// ., _ and -. The disk name will appear on the console. It cannot
// begin with `http://` or `https://`.
DiskName string `mapstructure:"disk_name" required:"false"`
// Category of the system disk. Optional values are:
// Category of the system disk. Optional values
// are:
// - cloud - general cloud disk
// - cloud_efficiency - efficiency cloud disk
// - cloud_ssd - cloud SSD
@@ -33,8 +32,6 @@ type AlicloudDiskDevice struct {
// Snapshots are used to create the data
// disk After this parameter is specified, Size is ignored. The actual
// size of the created disk is the size of the specified snapshot.
// This field is only used in the ECSImagesDiskMappings option, not
// the ECSSystemDiskMapping option.
SnapshotId string `mapstructure:"disk_snapshot_id" required:"false"`
// The value of disk description is blank by
// default. [2, 256] characters. The disk description will appear on the
@@ -47,51 +44,94 @@ type AlicloudDiskDevice struct {
// such as /dev/xvdb It is null unless the Status is In_use.
Device string `mapstructure:"disk_device" required:"false"`
// Whether or not to encrypt the data disk.
// If this option is set to true, the data disk will be encryped and
// corresponding snapshot in the target image will also be encrypted. By
// If this option is set to true, the data disk will be encryped and corresponding snapshot in the target image will also be encrypted. By
// default, if this is an extra data disk, Packer will not encrypt the
// data disk. Otherwise, Packer will keep the encryption setting to what
// it was in the source image. Please refer to Introduction of ECS disk
// encryption for more details.
// it was in the source image. Please refer to Introduction of ECS disk encryption
// for more details.
Encrypted config.Trilean `mapstructure:"disk_encrypted" required:"false"`
}
// The "AlicloudDiskDevices" object is used to define disk mappings for your
// instance.
type AlicloudDiskDevices struct {
// Image disk mapping for the system disk.
// See the [disk device configuration](#disk-devices-configuration) section
// for more information on options.
// Usage example:
// Image disk mapping for system
// disk.
// - `disk_category` (string) - Category of the system disk. Optional values
// are:
// - `cloud` - general cloud disk
// - `cloud_efficiency` - efficiency cloud disk
// - `cloud_ssd` - cloud SSD
//
// For phased-out instance types and non-I/O optimized instances, the
// default value is cloud. Otherwise, the default value is
// cloud\_efficiency.
//
// - `disk_description` (string) - The value of disk description is blank by
// default. \[2, 256\] characters. The disk description will appear on the
// console. It cannot begin with `http://` or `https://`.
//
// - `disk_name` (string) - The value of disk name is blank by default. \[2,
// 128\] English or Chinese characters, must begin with an
// uppercase/lowercase letter or Chinese character. Can contain numbers,
// `.`, `_` and `-`. The disk name will appear on the console. It cannot
// begin with `http://` or `https://`.
//
// - `disk_size` (number) - Size of the system disk, measured in GiB. Value
// range: \[20, 500\]. The specified value must be equal to or greater
// than max{20, ImageSize}. Default value: max{40, ImageSize}.
//
// ```json
// "builders": [{
// "type":"alicloud-ecs",
// "system_disk_mapping": {
// "disk_size": 50,
// "disk_name": "mydisk"
// },
// ...
// }
// ```
ECSSystemDiskMapping AlicloudDiskDevice `mapstructure:"system_disk_mapping" required:"false"`
// Add one or more data disks to the image.
// See the [disk device configuration](#disk-devices-configuration) section
// for more information on options.
// Usage example:
// Add one or more data
// disks to the image.
//
// - `disk_category` (string) - Category of the data disk. Optional values
// are:
// - `cloud` - general cloud disk
// - `cloud_efficiency` - efficiency cloud disk
// - `cloud_ssd` - cloud SSD
//
// Default value: cloud.
//
// - `disk_delete_with_instance` (boolean) - Whether or not the disk is
// released along with the instance:
// - True indicates that when the instance is released, this disk will
// be released with it
// - False indicates that when the instance is released, this disk will
// be retained.
// - `disk_description` (string) - The value of disk description is blank by
// default. \[2, 256\] characters. The disk description will appear on the
// console. It cannot begin with `http://` or `https://`.
//
// - `disk_device` (string) - Device information of the related instance:
// such as `/dev/xvdb` It is null unless the Status is In\_use.
//
// - `disk_name` (string) - The value of disk name is blank by default. \[2,
// 128\] English or Chinese characters, must begin with an
// uppercase/lowercase letter or Chinese character. Can contain numbers,
// `.`, `_` and `-`. The disk name will appear on the console. It cannot
// begin with `http://` or `https://`.
//
// - `disk_size` (number) - Size of the data disk, in GB, values range:
// - `cloud` - 5 \~ 2000
// - `cloud_efficiency` - 20 \~ 2048
// - `cloud_ssd` - 20 \~ 2048
//
// The value should be equal to or greater than the size of the specific
// SnapshotId.
//
// - `disk_snapshot_id` (string) - Snapshots are used to create the data
// disk After this parameter is specified, Size is ignored. The actual
// size of the created disk is the size of the specified snapshot.
//
// Snapshots from on or before July 15, 2013 cannot be used to create a
// disk.
//
// - `disk_encrypted` (boolean) - Whether or not to encrypt the data disk.
// If this option is set to true, the data disk will be encryped and corresponding snapshot in the target image will also be encrypted. By
// default, if this is an extra data disk, Packer will not encrypt the
// data disk. Otherwise, Packer will keep the encryption setting to what
// it was in the source image. Please refer to Introduction of [ECS disk encryption](https://www.alibabacloud.com/help/doc-detail/59643.htm)
// for more details.
//
// ```json
// "builders": [{
// "type":"alicloud-ecs",
// "image_disk_mappings": [
// {
// "disk_snapshot_id": "someid",
// "disk_device": "dev/xvdb"
// }
// ],
// ...
// }
// ```
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings" required:"false"`
}
+2 -6
View File
@@ -14,10 +14,10 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/chroot"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/hcl2template"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
@@ -194,18 +194,14 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
b.config.ctx.Funcs = awscommon.TemplateFuncs
err := config.Decode(&b.config, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"ami_description",
"snapshot_tags",
"snapshot_tag",
"tags",
"tag",
"root_volume_tags",
"root_volume_tag",
"command_wrapper",
"post_mount_commands",
"pre_mount_commands",
@@ -372,7 +368,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("wrappedCommand", common.CommandWrapper(wrappedCommand))
generatedData := &packerbuilderdata.GeneratedData{State: state}
generatedData := &builder.GeneratedData{State: state}
// Build the steps
steps := []multistep.Step{
@@ -41,9 +41,7 @@ type FlatConfig struct {
SnapshotUsers []string `mapstructure:"snapshot_users" required:"false" cty:"snapshot_users" hcl:"snapshot_users"`
SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false" cty:"snapshot_groups" hcl:"snapshot_groups"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
AssumeRole *common.FlatAssumeRoleConfig `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"`
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2" hcl:"custom_endpoint_ec2"`
CredsFilename *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"`
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages" hcl:"decode_authorization_messages"`
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
MaxRetries *int `mapstructure:"max_retries" required:"false" cty:"max_retries" hcl:"max_retries"`
@@ -52,7 +50,6 @@ type FlatConfig struct {
RawRegion *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"`
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
@@ -120,9 +117,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"snapshot_users": &hcldec.AttrSpec{Name: "snapshot_users", Type: cty.List(cty.String), Required: false},
"snapshot_groups": &hcldec.AttrSpec{Name: "snapshot_groups", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@@ -131,7 +126,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
+2 -2
View File
@@ -10,8 +10,8 @@ import (
"strings"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/builder"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
@@ -31,7 +31,7 @@ type StepMountDevice struct {
MountPartition string
mountPath string
GeneratedData *packerbuilderdata.GeneratedData
GeneratedData *builder.GeneratedData
}
func (s *StepMountDevice) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
+2 -2
View File
@@ -6,14 +6,14 @@ import (
"log"
"os"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/builder"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepPrepareDevice finds an available device and sets it.
type StepPrepareDevice struct {
GeneratedData *packerbuilderdata.GeneratedData
GeneratedData *builder.GeneratedData
}
func (s *StepPrepareDevice) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
+11 -121
View File
@@ -1,5 +1,5 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type VaultAWSEngineOptions,AssumeRoleConfig
//go:generate mapstructure-to-hcl2 -type VaultAWSEngineOptions
package common
@@ -11,68 +11,15 @@ import (
"strings"
"github.com/aws/aws-sdk-go/aws"
awsCredentials "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
awsbase "github.com/hashicorp/aws-sdk-go-base"
cleanhttp "github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
"github.com/hashicorp/packer/template/interpolate"
vaultapi "github.com/hashicorp/vault/api"
)
// AssumeRoleConfig lets users set configuration options for assuming a special
// role when executing Packer.
//
// Usage example:
//
// HCL config example:
//
// ```HCL
// source "example" "amazon-ebs"{
// assume_role {
// role_arn = "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME"
// session_name = "SESSION_NAME"
// external_id = "EXTERNAL_ID"
// }
// }
// ```
//
// JSON config example:
//
// ```json
// builder{
// "type": "amazon-ebs",
// "assume_role": {
// "role_arn" : "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME",
// "session_name": "SESSION_NAME",
// "external_id" : "EXTERNAL_ID"
// }
// }
// ```
type AssumeRoleConfig struct {
// Amazon Resource Name (ARN) of the IAM Role to assume.
AssumeRoleARN string `mapstructure:"role_arn" required:"false"`
// Number of seconds to restrict the assume role session duration.
AssumeRoleDurationSeconds int `mapstructure:"duration_seconds" required:"false"`
// The external ID to use when assuming the role. If omitted, no external
// ID is passed to the AssumeRole call.
AssumeRoleExternalID string `mapstructure:"external_id" required:"false"`
// IAM Policy JSON describing further restricting permissions for the IAM
// Role being assumed.
AssumeRolePolicy string `mapstructure:"policy" required:"false"`
// Set of Amazon Resource Names (ARNs) of IAM Policies describing further
// restricting permissions for the IAM Role being
AssumeRolePolicyARNs []string `mapstructure:"policy_arns" required:"false"`
// Session name to use when assuming the role.
AssumeRoleSessionName string `mapstructure:"session_name" required:"false"`
// Map of assume role session tags.
AssumeRoleTags map[string]string `mapstructure:"tags" required:"false"`
// Set of assume role session tag keys to pass to any subsequent sessions.
AssumeRoleTransitiveTagKeys []string `mapstructure:"transitive_tag_keys" required:"false"`
}
type VaultAWSEngineOptions struct {
Name string `mapstructure:"name"`
RoleARN string `mapstructure:"role_arn"`
@@ -101,17 +48,10 @@ type AccessConfig struct {
// is not required if you are using `use_vault_aws_engine` for
// authentication instead.
AccessKey string `mapstructure:"access_key" required:"true"`
// If provided with a role ARN, Packer will attempt to assume this role
// using the supplied credentials. See
// [AssumeRoleConfig](#assume-role-configuration) below for more
// details on all of the options available, and for a usage example.
AssumeRole AssumeRoleConfig `mapstructure:"assume_role" required:"false"`
// This option is useful if you use a cloud
// provider whose API is compatible with aws EC2. Specify another endpoint
// like this https://ec2.custom.endpoint.com.
CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2" required:"false"`
// Path to a credentials file to load credentials from
CredsFilename string `mapstructure:"shared_credentials_file" required:"false"`
// Enable automatic decoding of any encoded authorization (error) messages
// using the `sts:DecodeAuthorizationMessage` API. Note: requires that the
// effective user/role have permissions to `sts:DecodeAuthorizationMessage`
@@ -146,8 +86,6 @@ type AccessConfig struct {
// validation of the ami_regions configuration option. Default false.
SkipValidation bool `mapstructure:"skip_region_validation" required:"false"`
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
// Set to true if you want to skip validating AWS credentials before runtime.
SkipCredsValidation bool `mapstructure:"skip_credential_validation"`
// The access token to use. This is different from the
// access key and secret key. If you're not sure what this is, then you
// probably don't need it. This will also be read from the AWS_SESSION_TOKEN
@@ -214,13 +152,16 @@ func (c *AccessConfig) Session() (*session.Session, error) {
return c.session, nil
}
// Create new AWS config
config := aws.NewConfig().WithCredentialsChainVerboseErrors(true)
if c.MaxRetries > 0 {
config = config.WithMaxRetries(c.MaxRetries)
}
// Set AWS config defaults.
staticCreds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
if _, err := staticCreds.Get(); err != credentials.ErrStaticCredentialsEmpty {
config.WithCredentials(staticCreds)
}
if c.RawRegion != "" {
config = config.WithRegion(c.RawRegion)
}
@@ -238,16 +179,6 @@ func (c *AccessConfig) Session() (*session.Session, error) {
}
transport.Proxy = http.ProxyFromEnvironment
// Figure out which possible credential providers are valid; test that we
// can get credentials via the selected providers, and set the providers in
// the config.
creds, err := c.GetCredentials(config)
if err != nil {
return nil, err
}
config.WithCredentials(creds)
// Create session options based on our AWS config
opts := session.Options{
SharedConfigState: session.SharedConfigEnable,
Config: *config,
@@ -272,8 +203,10 @@ func (c *AccessConfig) Session() (*session.Session, error) {
cp, err := c.session.Config.Credentials.Get()
if awserrors.Matches(err, "NoCredentialProviders", "") {
return nil, c.NewNoValidCredentialSourcesError(err)
if IsAWSErr(err, "NoCredentialProviders", "") {
return nil, fmt.Errorf("No valid credential sources found for AWS Builder. " +
"Please see https://www.packer.io/docs/builders/amazon#specifying-amazon-credentials " +
"for more information on providing credentials for the AWS Builder.")
}
if err != nil {
@@ -304,42 +237,6 @@ func (c *AccessConfig) IsChinaCloud() bool {
return strings.HasPrefix(c.SessionRegion(), "cn-")
}
// GetCredentials gets credentials from the environment, shared credentials,
// the session (which may include a credential process), or ECS/EC2 metadata
// endpoints. GetCredentials also validates the credentials and the ability to
// assume a role or will return an error if unsuccessful.
func (c *AccessConfig) GetCredentials(config *aws.Config) (*awsCredentials.Credentials, error) {
// Reload values into the config used by the Packer-Terraform shared SDK
awsbaseConfig := &awsbase.Config{
AccessKey: c.AccessKey,
AssumeRoleARN: c.AssumeRole.AssumeRoleARN,
AssumeRoleDurationSeconds: c.AssumeRole.AssumeRoleDurationSeconds,
AssumeRoleExternalID: c.AssumeRole.AssumeRoleExternalID,
AssumeRolePolicy: c.AssumeRole.AssumeRolePolicy,
AssumeRolePolicyARNs: c.AssumeRole.AssumeRolePolicyARNs,
AssumeRoleSessionName: c.AssumeRole.AssumeRoleSessionName,
AssumeRoleTags: c.AssumeRole.AssumeRoleTags,
AssumeRoleTransitiveTagKeys: c.AssumeRole.AssumeRoleTransitiveTagKeys,
CredsFilename: c.CredsFilename,
DebugLogging: false,
// TODO: implement for Packer
// IamEndpoint: c.Endpoints["iam"],
Insecure: c.InsecureSkipTLSVerify,
MaxRetries: c.MaxRetries,
Profile: c.ProfileName,
Region: c.RawRegion,
SecretKey: c.SecretKey,
SkipCredsValidation: c.SkipCredsValidation,
SkipMetadataApiCheck: c.SkipMetadataApiCheck,
// TODO: implement for Packer
// SkipRequestingAccountId: c.SkipRequestingAccountId,
// StsEndpoint: c.Endpoints["sts"],
Token: c.Token,
}
return awsbase.GetCredentials(awsbaseConfig)
}
func (c *AccessConfig) GetCredsFromVault() error {
// const EnvVaultAddress = "VAULT_ADDR"
// const EnvVaultToken = "VAULT_TOKEN"
@@ -409,13 +306,6 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
return errs
}
func (c *AccessConfig) NewNoValidCredentialSourcesError(err error) error {
return fmt.Errorf("No valid credential sources found for AWS Builder. "+
"Please see https://www.packer.io/docs/builders/amazon#authentication "+
"for more information on providing credentials for the AWS Builder. "+
"Error: %w", err)
}
func (c *AccessConfig) NewEC2Connection() (ec2iface.EC2API, error) {
if c.getEC2Connection != nil {
return c.getEC2Connection(), nil
@@ -1,4 +1,4 @@
// Code generated by "mapstructure-to-hcl2 -type VaultAWSEngineOptions,AssumeRoleConfig"; DO NOT EDIT.
// Code generated by "mapstructure-to-hcl2 -type VaultAWSEngineOptions"; DO NOT EDIT.
package common
import (
@@ -6,43 +6,6 @@ import (
"github.com/zclconf/go-cty/cty"
)
// FlatAssumeRoleConfig is an auto-generated flat version of AssumeRoleConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatAssumeRoleConfig struct {
AssumeRoleARN *string `mapstructure:"role_arn" required:"false" cty:"role_arn" hcl:"role_arn"`
AssumeRoleDurationSeconds *int `mapstructure:"duration_seconds" required:"false" cty:"duration_seconds" hcl:"duration_seconds"`
AssumeRoleExternalID *string `mapstructure:"external_id" required:"false" cty:"external_id" hcl:"external_id"`
AssumeRolePolicy *string `mapstructure:"policy" required:"false" cty:"policy" hcl:"policy"`
AssumeRolePolicyARNs []string `mapstructure:"policy_arns" required:"false" cty:"policy_arns" hcl:"policy_arns"`
AssumeRoleSessionName *string `mapstructure:"session_name" required:"false" cty:"session_name" hcl:"session_name"`
AssumeRoleTags map[string]string `mapstructure:"tags" required:"false" cty:"tags" hcl:"tags"`
AssumeRoleTransitiveTagKeys []string `mapstructure:"transitive_tag_keys" required:"false" cty:"transitive_tag_keys" hcl:"transitive_tag_keys"`
}
// FlatMapstructure returns a new FlatAssumeRoleConfig.
// FlatAssumeRoleConfig is an auto-generated flat version of AssumeRoleConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*AssumeRoleConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatAssumeRoleConfig)
}
// HCL2Spec returns the hcl spec of a AssumeRoleConfig.
// This spec is used by HCL to read the fields of AssumeRoleConfig.
// The decoded values from this spec will then be applied to a FlatAssumeRoleConfig.
func (*FlatAssumeRoleConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"role_arn": &hcldec.AttrSpec{Name: "role_arn", Type: cty.String, Required: false},
"duration_seconds": &hcldec.AttrSpec{Name: "duration_seconds", Type: cty.Number, Required: false},
"external_id": &hcldec.AttrSpec{Name: "external_id", Type: cty.String, Required: false},
"policy": &hcldec.AttrSpec{Name: "policy", Type: cty.String, Required: false},
"policy_arns": &hcldec.AttrSpec{Name: "policy_arns", Type: cty.List(cty.String), Required: false},
"session_name": &hcldec.AttrSpec{Name: "session_name", Type: cty.String, Required: false},
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.Map(cty.String), Required: false},
"transitive_tag_keys": &hcldec.AttrSpec{Name: "transitive_tag_keys", Type: cty.List(cty.String), Required: false},
}
return s
}
// FlatVaultAWSEngineOptions is an auto-generated flat version of VaultAWSEngineOptions.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatVaultAWSEngineOptions struct {
-18
View File
@@ -1,18 +0,0 @@
package awserrors
import (
"strings"
"github.com/aws/aws-sdk-go/aws/awserr"
)
// Returns true if the err matches all these conditions:
// * err is of type awserr.Error
// * Error.Code() matches code
// * Error.Message() contains message
func Matches(err error, code string, message string) bool {
if err, ok := err.(awserr.Error); ok {
return err.Code() == code && strings.Contains(err.Message(), message)
}
return false
}
+2 -2
View File
@@ -134,8 +134,8 @@ func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapp
ebsBlockDevice.VolumeSize = aws.Int64(blockDevice.VolumeSize)
}
// IOPS is only valid for io1 and io2 types
if blockDevice.VolumeType == "io1" || blockDevice.VolumeType == "io2" {
// IOPS is only valid for io1 type
if blockDevice.VolumeType == "io1" {
ebsBlockDevice.Iops = aws.Int64(blockDevice.IOPS)
}
@@ -66,25 +66,6 @@ func TestBlockDevice(t *testing.T) {
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 8,
DeleteOnTermination: true,
IOPS: 1000,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("io2"),
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(true),
Iops: aws.Int64(1000),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
+15 -3
View File
@@ -4,11 +4,12 @@ import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
"github.com/hashicorp/packer/common/retry"
)
@@ -30,7 +31,7 @@ func DestroyAMIs(imageids []*string, ec2conn *ec2.EC2) error {
err = retry.Config{
Tries: 11,
ShouldRetry: func(err error) bool {
return awserrors.Matches(err, "UnauthorizedOperation", "")
return IsAWSErr(err, "UnauthorizedOperation", "")
},
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
@@ -53,7 +54,7 @@ func DestroyAMIs(imageids []*string, ec2conn *ec2.EC2) error {
err = retry.Config{
Tries: 11,
ShouldRetry: func(err error) bool {
return awserrors.Matches(err, "UnauthorizedOperation", "")
return IsAWSErr(err, "UnauthorizedOperation", "")
},
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
@@ -73,3 +74,14 @@ func DestroyAMIs(imageids []*string, ec2conn *ec2.EC2) error {
}
return nil
}
// Returns true if the error matches all these conditions:
// * err is of type awserr.Error
// * Error.Code() matches code
// * Error.Message() contains message
func IsAWSErr(err error, code string, message string) bool {
if err, ok := err.(awserr.Error); ok {
return err.Code() == code && strings.Contains(err.Message(), message)
}
return false
}
@@ -3,7 +3,7 @@ package common
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/builder"
"github.com/hashicorp/packer/helper/multistep"
)
@@ -17,7 +17,7 @@ type BuildInfoTemplate struct {
SourceAMITags map[string]string
}
func extractBuildInfo(region string, state multistep.StateBag, generatedData *packerbuilderdata.GeneratedData) *BuildInfoTemplate {
func extractBuildInfo(region string, state multistep.StateBag, generatedData *builder.GeneratedData) *BuildInfoTemplate {
rawSourceAMI, hasSourceAMI := state.GetOk("source_image")
if !hasSourceAMI {
return &BuildInfoTemplate{
@@ -6,7 +6,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/builder"
"github.com/hashicorp/packer/helper/multistep"
)
@@ -17,7 +17,6 @@ func testImage() *ec2.Image {
Name: aws.String("ami_test_name"),
OwnerId: aws.String("ami_test_owner_id"),
ImageOwnerAlias: aws.String("ami_test_owner_alias"),
RootDeviceType: aws.String("ebs"),
Tags: []*ec2.Tag{
{
Key: aws.String("key-1"),
@@ -36,8 +35,8 @@ func testState() multistep.StateBag {
return state
}
func testGeneratedData(state multistep.StateBag) packerbuilderdata.GeneratedData {
generatedData := packerbuilderdata.GeneratedData{State: state}
func testGeneratedData(state multistep.StateBag) builder.GeneratedData {
generatedData := builder.GeneratedData{State: state}
return generatedData
}
+5 -25
View File
@@ -53,14 +53,14 @@ type VpcFilterOptions struct {
}
type Statement struct {
Effect string `mapstructure:"Effect" required:"false"`
Action []string `mapstructure:"Action" required:"false"`
Resource []string `mapstructure:"Resource" required:"false"`
Effect string
Action []string
Resource []string
}
type PolicyDocument struct {
Version string `mapstructure:"Version" required:"false"`
Statement []Statement `mapstructure:"Statement" required:"false"`
Version string
Statement []Statement
}
type SecurityGroupFilterOptions struct {
@@ -376,12 +376,6 @@ type RunConfig struct {
// subnet-12345def, where Packer will launch the EC2 instance. This field is
// required if you are using an non-default VPC.
SubnetId string `mapstructure:"subnet_id" required:"false"`
// [Tenancy](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/dedicated-instance.html) used
// when Packer launches the EC2 instance, allowing it to be launched on dedicated hardware.
//
// The default is "default", meaning shared tenancy. Allowed values are "default",
// "dedicated" and "host".
Tenancy string `mapstructure:"tenancy" required:"false"`
// The name of the temporary key pair to
// generate. By default, Packer generates a name that looks like
// `packer_<UUID>`, where &lt;UUID&gt; is a 36 character unique identifier.
@@ -479,13 +473,6 @@ type RunConfig struct {
// terminating the tunnel it will automatically terminate itself after 20 minutes of inactivity.
SSHInterface string `mapstructure:"ssh_interface"`
// The time to wait before establishing the Session Manager session.
// The value of this should be a duration. Examples are
// `5s` and `1m30s` which will cause Packer to wait five seconds and one
// minute 30 seconds, respectively. If no set, defaults to 10 seconds.
// This option is useful when the remote port takes longer to become available.
PauseBeforeSSM time.Duration `mapstructure:"pause_before_ssm"`
// Which port to connect the local end of the session tunnel to. If
// left blank, Packer will choose a port for you from available ports.
// This option is only used when `ssh_interface` is set `session_manager`.
@@ -633,13 +620,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
}
}
if c.Tenancy != "" &&
c.Tenancy != "default" &&
c.Tenancy != "dedicated" &&
c.Tenancy != "host" {
errs = append(errs, fmt.Errorf("Error: Unknown tenancy type %s", c.Tenancy))
}
return errs
}
+10 -10
View File
@@ -39,8 +39,8 @@ func (*FlatAmiFilterOptions) HCL2Spec() map[string]hcldec.Spec {
// FlatPolicyDocument is an auto-generated flat version of PolicyDocument.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatPolicyDocument struct {
Version *string `mapstructure:"Version" required:"false" cty:"Version" hcl:"Version"`
Statement []FlatStatement `mapstructure:"Statement" required:"false" cty:"Statement" hcl:"Statement"`
Version *string `cty:"version" hcl:"version"`
Statement []FlatStatement `cty:"statement" hcl:"statement"`
}
// FlatMapstructure returns a new FlatPolicyDocument.
@@ -55,8 +55,8 @@ func (*PolicyDocument) FlatMapstructure() interface{ HCL2Spec() map[string]hclde
// The decoded values from this spec will then be applied to a FlatPolicyDocument.
func (*FlatPolicyDocument) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"Version": &hcldec.AttrSpec{Name: "Version", Type: cty.String, Required: false},
"Statement": &hcldec.BlockListSpec{TypeName: "Statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())},
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
"statement": &hcldec.BlockListSpec{TypeName: "statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())},
}
return s
}
@@ -89,9 +89,9 @@ func (*FlatSecurityGroupFilterOptions) HCL2Spec() map[string]hcldec.Spec {
// FlatStatement is an auto-generated flat version of Statement.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatStatement struct {
Effect *string `mapstructure:"Effect" required:"false" cty:"Effect" hcl:"Effect"`
Action []string `mapstructure:"Action" required:"false" cty:"Action" hcl:"Action"`
Resource []string `mapstructure:"Resource" required:"false" cty:"Resource" hcl:"Resource"`
Effect *string `cty:"effect" hcl:"effect"`
Action []string `cty:"action" hcl:"action"`
Resource []string `cty:"resource" hcl:"resource"`
}
// FlatMapstructure returns a new FlatStatement.
@@ -106,9 +106,9 @@ func (*Statement) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spe
// The decoded values from this spec will then be applied to a FlatStatement.
func (*FlatStatement) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"Effect": &hcldec.AttrSpec{Name: "Effect", Type: cty.String, Required: false},
"Action": &hcldec.AttrSpec{Name: "Action", Type: cty.List(cty.String), Required: false},
"Resource": &hcldec.AttrSpec{Name: "Resource", Type: cty.List(cty.String), Required: false},
"effect": &hcldec.AttrSpec{Name: "effect", Type: cty.String, Required: false},
"action": &hcldec.AttrSpec{Name: "action", Type: cty.List(cty.String), Required: false},
"resource": &hcldec.AttrSpec{Name: "resource", Type: cty.List(cty.String), Required: false},
}
return s
}
-20
View File
@@ -232,23 +232,3 @@ func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
t.Fatal("keypair name does not match")
}
}
func TestRunConfigPrepare_TenancyBad(t *testing.T) {
c := testConfig()
c.Tenancy = "not_real"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatal("Should error if tenancy is set to an invalid type")
}
}
func TestRunConfigPrepare_TenancyGood(t *testing.T) {
validTenancy := []string{"", "default", "dedicated", "host"}
for _, vt := range validTenancy {
c := testConfig()
c.Tenancy = vt
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("Should not error if tenancy is set to %s", vt)
}
}
}
-117
View File
@@ -1,117 +0,0 @@
package ssm
import (
"context"
"encoding/json"
"fmt"
"log"
"os/exec"
"strconv"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
"github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/common/shell-local/localexec"
"github.com/hashicorp/packer/packer"
)
type Session struct {
SvcClient ssmiface.SSMAPI
Region string
InstanceID string
LocalPort, RemotePort int
}
func (s Session) buildTunnelInput() *ssm.StartSessionInput {
portNumber, localPortNumber := strconv.Itoa(s.RemotePort), strconv.Itoa(s.LocalPort)
params := map[string][]*string{
"portNumber": []*string{aws.String(portNumber)},
"localPortNumber": []*string{aws.String(localPortNumber)},
}
return &ssm.StartSessionInput{
DocumentName: aws.String("AWS-StartPortForwardingSession"),
Parameters: params,
Target: aws.String(s.InstanceID),
}
}
// getCommand return a valid ordered set of arguments to pass to the driver command.
func (s Session) getCommand(ctx context.Context) ([]string, string, error) {
input := s.buildTunnelInput()
var session *ssm.StartSessionOutput
err := retry.Config{
ShouldRetry: func(err error) bool { return awserrors.Matches(err, "TargetNotConnected", "") },
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 60 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) (err error) {
session, err = s.SvcClient.StartSessionWithContext(ctx, input)
return err
})
if err != nil {
return nil, "", err
}
if session == nil {
return nil, "", fmt.Errorf("an active Amazon SSM Session is required before trying to open a session tunnel")
}
// AWS session-manager-plugin requires a valid session be passed in JSON.
sessionDetails, err := json.Marshal(session)
if err != nil {
return nil, *session.SessionId, fmt.Errorf("error encountered in reading session details %s", err)
}
// AWS session-manager-plugin requires the parameters used in the session to be passed in JSON as well.
sessionParameters, err := json.Marshal(input)
if err != nil {
return nil, "", fmt.Errorf("error encountered in reading session parameter details %s", err)
}
// Args must be in this order
args := []string{
string(sessionDetails),
s.Region,
"StartSession",
"", // ProfileName
string(sessionParameters),
*session.StreamUrl,
}
return args, *session.SessionId, nil
}
// Start an interactive Systems Manager session with a remote instance via the
// AWS session-manager-plugin. To terminate the session you must cancell the
// context. If you do not wish to terminate the session manually: calling
// StopSession on a instance of this driver will terminate the active session
// created from calling StartSession.
func (s Session) Start(ctx context.Context, ui packer.Ui) error {
for ctx.Err() == nil {
log.Printf("ssm: Starting PortForwarding session to instance %s", s.InstanceID)
args, sessionID, err := s.getCommand(ctx)
if sessionID != "" {
defer func() {
_, err := s.SvcClient.TerminateSession(&ssm.TerminateSessionInput{SessionId: aws.String(sessionID)})
if err != nil {
ui.Error(fmt.Sprintf("Error terminating SSM Session %q. Please terminate the session manually: %s", sessionID, err))
}
}()
}
if err != nil {
return err
}
cmd := exec.CommandContext(ctx, "session-manager-plugin", args...)
ui.Message(fmt.Sprintf("Starting portForwarding session %q.", sessionID))
err = localexec.RunAndStream(cmd, ui, nil)
if err != nil {
ui.Error(err.Error())
}
}
return nil
}
+182
View File
@@ -0,0 +1,182 @@
package common
import (
"context"
"encoding/json"
"fmt"
"log"
"os/exec"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
"github.com/hashicorp/packer/common/retry"
"github.com/mitchellh/iochan"
)
const (
sessionManagerPluginName string = "session-manager-plugin"
//sessionCommand is the AWS-SDK equivalent to the command you would specify to `aws ssm ...`
sessionCommand string = "StartSession"
)
type SSMDriverConfig struct {
SvcClient ssmiface.SSMAPI
Region string
ProfileName string
SvcEndpoint string
}
type SSMDriver struct {
SSMDriverConfig
session *ssm.StartSessionOutput
sessionParams ssm.StartSessionInput
pluginCmdFunc func(context.Context) error
}
func NewSSMDriver(config SSMDriverConfig) *SSMDriver {
d := SSMDriver{SSMDriverConfig: config}
return &d
}
// StartSession starts an interactive Systems Manager session with a remote instance via the AWS session-manager-plugin
// This ssm.StartSessionOutput returned by this function can be used for terminating the session manually. If you do
// not wish to manage the session manually calling StopSession on a instance of this driver will terminate the active session
// created from calling StartSession.
func (d *SSMDriver) StartSession(ctx context.Context, input ssm.StartSessionInput) (*ssm.StartSessionOutput, error) {
log.Printf("Starting PortForwarding session to instance %q with following params %v", aws.StringValue(input.Target), input.Parameters)
var output *ssm.StartSessionOutput
err := retry.Config{
ShouldRetry: func(err error) bool { return IsAWSErr(err, "TargetNotConnected", "") },
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 60 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) (err error) {
output, err = d.SvcClient.StartSessionWithContext(ctx, &input)
return err
})
if err != nil {
return nil, fmt.Errorf("error encountered in starting session for instance %q: %s", aws.StringValue(input.Target), err)
}
d.session = output
d.sessionParams = input
if d.pluginCmdFunc == nil {
d.pluginCmdFunc = d.openTunnelForSession
}
if err := d.pluginCmdFunc(ctx); err != nil {
return nil, fmt.Errorf("error encountered in starting session for instance %q: %s", aws.StringValue(input.Target), err)
}
return d.session, nil
}
func (d *SSMDriver) openTunnelForSession(ctx context.Context) error {
args, err := d.Args()
if err != nil {
return fmt.Errorf("error encountered validating session details: %s", err)
}
cmd := exec.CommandContext(ctx, sessionManagerPluginName, args...)
// Let's build up our logging
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return err
}
// Create the channels we'll use for data
stdoutCh := iochan.DelimReader(stdout, '\n')
stderrCh := iochan.DelimReader(stderr, '\n')
/* Loop and get all our output
This particular logger will continue to run through an entire Packer run.
The decision to continue logging is due to the fact that session-manager-plugin
doesn't give a good way of knowing if the command failed or was successful other
than looking at the logs. Seeing as the plugin is updated frequently and that the
log information is a bit sparse this logger will indefinitely relying on other
steps to fail if the tunnel is unable to be created. If successful then the user
will get more information on the tunnel connection when running in a debug mode.
*/
go func(ctx context.Context, prefix string) {
for {
select {
case <-ctx.Done():
return
case output := <-stderrCh:
if output != "" {
log.Printf("[ERROR] %s: %s", prefix, output)
}
case output := <-stdoutCh:
if output != "" {
log.Printf("[DEBUG] %s: %s", prefix, output)
}
}
}
}(ctx, sessionManagerPluginName)
log.Printf("[DEBUG %s] opening session tunnel to instance %q for session %q", sessionManagerPluginName,
aws.StringValue(d.sessionParams.Target),
aws.StringValue(d.session.SessionId),
)
if err := cmd.Start(); err != nil {
err = fmt.Errorf("error encountered when calling %s: %s\n", sessionManagerPluginName, err)
return err
}
return nil
}
// StopSession terminates an active Session Manager session
func (d *SSMDriver) StopSession() error {
if d.session == nil || d.session.SessionId == nil {
return fmt.Errorf("Unable to find a valid session to instance %q; skipping the termination step",
aws.StringValue(d.sessionParams.Target))
}
_, err := d.SvcClient.TerminateSession(&ssm.TerminateSessionInput{SessionId: d.session.SessionId})
if err != nil {
err = fmt.Errorf("Error terminating SSM Session %q. Please terminate the session manually: %s", aws.StringValue(d.session.SessionId), err)
}
return err
}
// Args validates the driver inputs before returning an ordered set of arguments to pass to the driver command.
func (d *SSMDriver) Args() ([]string, error) {
if d.session == nil {
return nil, fmt.Errorf("an active Amazon SSM Session is required before trying to open a session tunnel")
}
// AWS session-manager-plugin requires a valid session be passed in JSON.
sessionDetails, err := json.Marshal(d.session)
if err != nil {
return nil, fmt.Errorf("error encountered in reading session details %s", err)
}
// AWS session-manager-plugin requires the parameters used in the session to be passed in JSON as well.
sessionParameters, err := json.Marshal(d.sessionParams)
if err != nil {
return nil, fmt.Errorf("error encountered in reading session parameter details %s", err)
}
// Args must be in this order
args := []string{
string(sessionDetails),
d.Region,
sessionCommand,
d.ProfileName,
string(sessionParameters),
d.SvcEndpoint,
}
return args, nil
}
+188
View File
@@ -0,0 +1,188 @@
package common
import (
"context"
"fmt"
"reflect"
"strings"
"testing"
)
func NewSSMDriverWithMockSvc(svc *MockSSMSvc) *SSMDriver {
config := SSMDriverConfig{
SvcClient: svc,
Region: "east",
ProfileName: "default",
SvcEndpoint: "example.com",
}
driver := SSMDriver{
SSMDriverConfig: config,
pluginCmdFunc: func(ctx context.Context) error { return nil },
}
return &driver
}
func TestSSMDriver_StartSession(t *testing.T) {
mockSvc := MockSSMSvc{}
driver := NewSSMDriverWithMockSvc(&mockSvc)
if driver.SvcClient == nil {
t.Fatalf("SvcClient for driver should not be nil")
}
session, err := driver.StartSession(context.TODO(), MockStartSessionInput("fakeinstance"))
if err != nil {
t.Fatalf("calling StartSession should not error but got %v", err)
}
if !mockSvc.StartSessionCalled {
t.Fatalf("expected test to call ssm mocks but didn't")
}
if session == nil {
t.Errorf("expected session to be set after a successful call to StartSession")
}
if !reflect.DeepEqual(session, MockStartSessionOutput()) {
t.Errorf("expected session to be %v but got %v", MockStartSessionOutput(), session)
}
}
func TestSSMDriver_StartSessionWithError(t *testing.T) {
mockSvc := MockSSMSvc{StartSessionError: fmt.Errorf("bogus error")}
driver := NewSSMDriverWithMockSvc(&mockSvc)
if driver.SvcClient == nil {
t.Fatalf("SvcClient for driver should not be nil")
}
session, err := driver.StartSession(context.TODO(), MockStartSessionInput("fakeinstance"))
if err == nil {
t.Fatalf("StartSession should have thrown an error but didn't")
}
if !mockSvc.StartSessionCalled {
t.Errorf("expected test to call StartSession mock but didn't")
}
if session != nil {
t.Errorf("expected session to be nil after a bad StartSession call, but got %v", session)
}
}
func TestSSMDriver_StopSession(t *testing.T) {
mockSvc := MockSSMSvc{}
driver := NewSSMDriverWithMockSvc(&mockSvc)
if driver.SvcClient == nil {
t.Fatalf("SvcClient for driver should not be nil")
}
// Calling StopSession before StartSession should fail
err := driver.StopSession()
if err == nil {
t.Fatalf("calling StopSession() on a driver that has no started session should fail")
}
if driver.session != nil {
t.Errorf("expected session to be default to nil")
}
if mockSvc.TerminateSessionCalled {
t.Fatalf("a call to TerminateSession should not occur when there is no valid SSM session")
}
// Lets try calling start session, then stopping to see what happens.
session, err := driver.StartSession(context.TODO(), MockStartSessionInput("fakeinstance"))
if err != nil {
t.Fatalf("calling StartSession should not error but got %v", err)
}
if !mockSvc.StartSessionCalled {
t.Fatalf("expected test to call StartSession mock but didn't")
}
if session == nil || driver.session != session {
t.Errorf("expected session to be set after a successful call to StartSession")
}
if !reflect.DeepEqual(session, MockStartSessionOutput()) {
t.Errorf("expected session to be %v but got %v", MockStartSessionOutput(), session)
}
err = driver.StopSession()
if err != nil {
t.Errorf("calling StopSession() on a driver on a started session should not fail")
}
if !mockSvc.TerminateSessionCalled {
t.Fatalf("expected test to call StopSession mock but didn't")
}
}
func TestSSMDriver_Args(t *testing.T) {
tt := []struct {
Name string
ProfileName string
SkipStartSession bool
ErrorExpected bool
}{
{
Name: "NilSession",
SkipStartSession: true,
ErrorExpected: true,
},
{
Name: "NonNilSession",
ErrorExpected: false,
},
{
Name: "SessionWithProfileName",
ProfileName: "default",
ErrorExpected: false,
},
}
for _, tc := range tt {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
mockSvc := MockSSMSvc{}
driver := NewSSMDriverWithMockSvc(&mockSvc)
driver.ProfileName = tc.ProfileName
if driver.SvcClient == nil {
t.Fatalf("svcclient for driver should not be nil")
}
if !tc.SkipStartSession {
_, err := driver.StartSession(context.TODO(), MockStartSessionInput("fakeinstance"))
if err != nil {
t.Fatalf("got an error when calling StartSession %v", err)
}
}
args, err := driver.Args()
if tc.ErrorExpected && err == nil {
t.Fatalf("Driver.Args with a %q should have failed but instead no error was returned", tc.Name)
}
if tc.ErrorExpected {
return
}
if err != nil {
t.Fatalf("got an error when it should've worked %v", err)
}
// validate launch script
expectedArgString := fmt.Sprintf(`{"SessionId":"packerid","StreamUrl":"http://packer.io","TokenValue":"packer-token"} east StartSession %s {"DocumentName":"AWS-StartPortForwardingSession","Parameters":{"localPortNumber":["8001"],"portNumber":["22"]},"Target":"fakeinstance"} example.com`, tc.ProfileName)
argString := strings.Join(args, " ")
if argString != expectedArgString {
t.Errorf("Expected launch script to be %q but got %q", expectedArgString, argString)
}
})
}
}
+56
View File
@@ -0,0 +1,56 @@
package common
import (
"context"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
)
type MockSSMSvc struct {
ssmiface.SSMAPI
StartSessionError error
TerminateSessionError error
StartSessionCalled bool
TerminateSessionCalled bool
}
func (svc *MockSSMSvc) StartSessionWithContext(ctx aws.Context, input *ssm.StartSessionInput, options ...request.Option) (*ssm.StartSessionOutput, error) {
svc.StartSessionCalled = true
return MockStartSessionOutput(), svc.StartSessionError
}
func (svc *MockSSMSvc) TerminateSession(input *ssm.TerminateSessionInput) (*ssm.TerminateSessionOutput, error) {
svc.TerminateSessionCalled = true
return new(ssm.TerminateSessionOutput), svc.TerminateSessionError
}
func MockPluginCmdFunc(ctx context.Context) error {
return nil
}
func MockStartSessionOutput() *ssm.StartSessionOutput {
id, url, token := "packerid", "http://packer.io", "packer-token"
output := ssm.StartSessionOutput{
SessionId: &id,
StreamUrl: &url,
TokenValue: &token,
}
return &output
}
func MockStartSessionInput(instance string) ssm.StartSessionInput {
params := map[string][]*string{
"portNumber": []*string{aws.String("22")},
"localPortNumber": []*string{aws.String("8001")},
}
input := ssm.StartSessionInput{
DocumentName: aws.String("AWS-StartPortForwardingSession"),
Parameters: params,
Target: aws.String(instance),
}
return input
}
+42 -34
View File
@@ -3,13 +3,12 @@ package common
import (
"context"
"fmt"
"time"
"strconv"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ssm"
pssm "github.com/hashicorp/packer/builder/amazon/common/ssm"
"github.com/hashicorp/packer/common/net"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
@@ -21,8 +20,8 @@ type StepCreateSSMTunnel struct {
LocalPortNumber int
RemotePortNumber int
SSMAgentEnabled bool
PauseBeforeSSM time.Duration
stopSSMCommand func()
instanceId string
driver *SSMDriver
}
// Run executes the Packer build step that creates a session tunnel.
@@ -33,17 +32,6 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
return multistep.ActionContinue
}
// Wait for the remote port to become available
if s.PauseBeforeSSM > 0 {
ui.Say(fmt.Sprintf("Waiting %s before establishing the SSM session...", s.PauseBeforeSSM))
select {
case <-time.After(s.PauseBeforeSSM):
break
case <-ctx.Done():
return multistep.ActionHalt
}
}
// Configure local port number
if err := s.ConfigureLocalHostPort(ctx); err != nil {
err := fmt.Errorf("error finding an available port to initiate a session tunnel: %s", err)
@@ -60,38 +48,42 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
state.Put("error", err)
return multistep.ActionHalt
}
s.instanceId = aws.StringValue(instance.InstanceId)
state.Put("sessionPort", s.LocalPortNumber)
ssmCtx, ssmCancel := context.WithCancel(ctx)
s.stopSSMCommand = ssmCancel
go func() {
if s.driver == nil {
ssmconn := ssm.New(s.AWSSession)
err := pssm.Session{
SvcClient: ssmconn,
InstanceID: aws.StringValue(instance.InstanceId),
RemotePort: s.RemotePortNumber,
LocalPort: s.LocalPortNumber,
Region: s.Region,
}.Start(ssmCtx, ui)
if err != nil {
ui.Error(fmt.Sprintf("ssm error: %s", err))
cfg := SSMDriverConfig{
SvcClient: ssmconn,
Region: s.Region,
SvcEndpoint: ssmconn.Endpoint,
}
}()
driver := SSMDriver{SSMDriverConfig: cfg}
s.driver = &driver
}
input := s.BuildTunnelInputForInstance(s.instanceId)
_, err := s.driver.StartSession(ctx, input)
if err != nil {
err = fmt.Errorf("error encountered in establishing a tunnel %s", err)
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
ui.Message(fmt.Sprintf("PortForwarding session %q has been started", s.instanceId))
state.Put("sessionPort", s.LocalPortNumber)
return multistep.ActionContinue
}
// Cleanup terminates an active session on AWS, which in turn terminates the associated tunnel process running on the local machine.
func (s *StepCreateSSMTunnel) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
if !s.SSMAgentEnabled {
return
}
if s.stopSSMCommand != nil {
s.stopSSMCommand()
if err := s.driver.StopSession(); err != nil {
ui.Error(err.Error())
}
}
@@ -124,3 +116,19 @@ func (s *StepCreateSSMTunnel) ConfigureLocalHostPort(ctx context.Context) error
return nil
}
func (s *StepCreateSSMTunnel) BuildTunnelInputForInstance(instance string) ssm.StartSessionInput {
dst, src := strconv.Itoa(s.RemotePortNumber), strconv.Itoa(s.LocalPortNumber)
params := map[string][]*string{
"portNumber": []*string{aws.String(dst)},
"localPortNumber": []*string{aws.String(src)},
}
input := ssm.StartSessionInput{
DocumentName: aws.String("AWS-StartPortForwardingSession"),
Parameters: params,
Target: aws.String(instance),
}
return input
}
@@ -0,0 +1,139 @@
package common
import (
"context"
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/packer"
)
func TestStepCreateSSMTunnel_Run(t *testing.T) {
mockSvc := MockSSMSvc{}
config := SSMDriverConfig{
SvcClient: &mockSvc,
SvcEndpoint: "example.com",
}
mockDriver := NewSSMDriver(config)
mockDriver.pluginCmdFunc = MockPluginCmdFunc
state := testState()
state.Put("ui", &packer.NoopUi{})
state.Put("instance", &ec2.Instance{InstanceId: aws.String("i-something")})
step := StepCreateSSMTunnel{
driver: mockDriver,
}
step.Run(context.Background(), state)
err := state.Get("error")
if err != nil {
err = err.(error)
t.Fatalf("the call to Run failed with an error when it should've executed: %v", err)
}
if mockSvc.StartSessionCalled {
t.Errorf("StartSession should not be called when SSMAgentEnabled is false")
}
// Run when SSMAgentEnabled is true
step.SSMAgentEnabled = true
step.Run(context.Background(), state)
err = state.Get("error")
if err != nil {
err = err.(error)
t.Fatalf("the call to Run failed with an error when it should've executed: %v", err)
}
if !mockSvc.StartSessionCalled {
t.Errorf("calling run with the correct inputs should call StartSession")
}
step.Cleanup(state)
if !mockSvc.TerminateSessionCalled {
t.Errorf("calling cleanup on a successful run should call TerminateSession")
}
}
func TestStepCreateSSMTunnel_Cleanup(t *testing.T) {
mockSvc := MockSSMSvc{}
config := SSMDriverConfig{
SvcClient: &mockSvc,
SvcEndpoint: "example.com",
}
mockDriver := NewSSMDriver(config)
mockDriver.pluginCmdFunc = MockPluginCmdFunc
step := StepCreateSSMTunnel{
SSMAgentEnabled: true,
driver: mockDriver,
}
state := testState()
state.Put("ui", &packer.NoopUi{})
state.Put("instance", &ec2.Instance{InstanceId: aws.String("i-something")})
step.Cleanup(state)
if mockSvc.TerminateSessionCalled {
t.Fatalf("calling cleanup on a non started session should not call TerminateSession")
}
}
func TestStepCreateSSMTunnel_BuildTunnelInputForInstance(t *testing.T) {
step := StepCreateSSMTunnel{
Region: "region",
LocalPortNumber: 8001,
RemotePortNumber: 22,
SSMAgentEnabled: true,
}
input := step.BuildTunnelInputForInstance("i-something")
target := aws.StringValue(input.Target)
if target != "i-something" {
t.Errorf("input should contain instance id as target but it got %q", target)
}
params := map[string][]*string{
"portNumber": []*string{aws.String("22")},
"localPortNumber": []*string{aws.String("8001")},
}
if !reflect.DeepEqual(input.Parameters, params) {
t.Errorf("input should contain the expected port parameters but it got %v", input.Parameters)
}
}
func TestStepCreateSSMTunnel_ConfigureLocalHostPort(t *testing.T) {
tt := []struct {
Name string
Step StepCreateSSMTunnel
PortCheck func(int) bool
}{
{"WithLocalPortNumber", StepCreateSSMTunnel{LocalPortNumber: 9001}, func(port int) bool { return port == 9001 }},
{"WithNoLocalPortNumber", StepCreateSSMTunnel{}, func(port int) bool { return port >= 8000 && port <= 9000 }},
}
for _, tc := range tt {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
step := tc.Step
if err := step.ConfigureLocalHostPort(context.TODO()); err != nil {
t.Errorf("failed to configure a port on localhost")
}
if !tc.PortCheck(step.LocalPortNumber) {
t.Errorf("failed to configure a port on localhost")
}
})
}
}
+1 -2
View File
@@ -8,7 +8,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
"github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
@@ -92,7 +91,7 @@ func (s *StepCreateTags) Run(ctx context.Context, state multistep.StateBag) mult
// Retry creating tags for about 2.5 minutes
err = retry.Config{Tries: 11, ShouldRetry: func(error) bool {
if awserrors.Matches(err, "InvalidAMIID.NotFound", "") || awserrors.Matches(err, "InvalidSnapshot.NotFound", "") {
if IsAWSErr(err, "InvalidAMIID.NotFound", "") || IsAWSErr(err, "InvalidSnapshot.NotFound", "") {
return true
}
return false
@@ -7,7 +7,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/builder"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
@@ -22,7 +22,7 @@ type StepModifyAMIAttributes struct {
Description string
Ctx interpolate.Context
GeneratedData *packerbuilderdata.GeneratedData
GeneratedData *builder.GeneratedData
}
func (s *StepModifyAMIAttributes) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
+2 -3
View File
@@ -9,7 +9,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
"github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
@@ -40,7 +39,7 @@ func (s *StepPreValidate) Run(ctx context.Context, state multistep.StateBag) mul
err := retry.Config{
Tries: 11,
ShouldRetry: func(err error) bool {
if awserrors.Matches(err, "AuthFailure", "") {
if IsAWSErr(err, "AuthFailure", "") {
log.Printf("Waiting for Vault-generated AWS credentials" +
" to pass authentication... trying again.")
return true
@@ -132,7 +131,7 @@ func (s *StepPreValidate) checkVpc(conn ec2iface.EC2API) error {
}
res, err := conn.DescribeVpcs(&ec2.DescribeVpcsInput{VpcIds: []*string{aws.String(s.VpcId)}})
if awserrors.Matches(err, "InvalidVpcID.NotFound", "") || err != nil {
if IsAWSErr(err, "InvalidVpcID.NotFound", "") || err != nil {
return fmt.Errorf("Error retrieving VPC information for vpc_id %s: %s", s.VpcId, err)
}
@@ -11,7 +11,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
"github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
@@ -34,7 +33,6 @@ type StepRunSourceInstance struct {
IsRestricted bool
SourceAMI string
Tags map[string]string
Tenancy string
UserData string
UserDataFile string
VolumeTags map[string]string
@@ -197,15 +195,11 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
runOpts.InstanceInitiatedShutdownBehavior = &s.InstanceInitiatedShutdownBehavior
}
if s.Tenancy != "" {
runOpts.Placement.Tenancy = aws.String(s.Tenancy)
}
var runResp *ec2.Reservation
err = retry.Config{
Tries: 11,
ShouldRetry: func(err error) bool {
if awserrors.Matches(err, "InvalidParameterValue", "iamInstanceProfile") {
if IsAWSErr(err, "InvalidParameterValue", "iamInstanceProfile") {
return true
}
return false
@@ -216,7 +210,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
return err
})
if awserrors.Matches(err, "VPCIdNotSpecified", "No default VPC for this user") && subnetId == "" {
if IsAWSErr(err, "VPCIdNotSpecified", "No default VPC for this user") && subnetId == "" {
err := fmt.Errorf("Error launching source instance: a valid Subnet Id was not specified")
state.Put("error", err)
ui.Error(err.Error())
@@ -254,7 +248,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
var r *ec2.DescribeInstancesOutput
err = retry.Config{Tries: 11, ShouldRetry: func(err error) bool {
if awserrors.Matches(err, "InvalidInstanceID.NotFound", "") {
if IsAWSErr(err, "InvalidInstanceID.NotFound", "") {
return true
}
return false
@@ -299,7 +293,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
ec2Tags.Report(ui)
// Retry creating tags for about 2.5 minutes
err = retry.Config{Tries: 11, ShouldRetry: func(error) bool {
if awserrors.Matches(err, "InvalidInstanceID.NotFound", "") {
if IsAWSErr(err, "InvalidInstanceID.NotFound", "") {
return true
}
return false
+31 -60
View File
@@ -6,13 +6,10 @@ import (
"fmt"
"io/ioutil"
"log"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
"github.com/hashicorp/packer/common/random"
"github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/helper/communicator"
@@ -36,7 +33,6 @@ type StepRunSpotInstance struct {
ExpectedRootDevice string
InstanceInitiatedShutdownBehavior string
InstanceType string
Region string
SourceAMI string
SpotPrice string
SpotTags map[string]string
@@ -160,7 +156,7 @@ func (s *StepRunSpotInstance) LoadUserData() (string, error) {
}
func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(ec2iface.EC2API)
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Launching a spot AWS instance...")
@@ -199,7 +195,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
}
// Convert tags from the tag map provided by the user into *ec2.Tag s
ec2Tags, err := TagMap(s.Tags).EC2Tags(s.Ctx, s.Region, state)
ec2Tags, err := TagMap(s.Tags).EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
if err != nil {
err := fmt.Errorf("Error generating tags for source instance: %s", err)
state.Put("error", err)
@@ -223,14 +219,6 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
}
marketOptions.SetMarketType(ec2.MarketTypeSpot)
spotTags, err := TagMap(s.SpotTags).EC2Tags(s.Ctx, s.Region, state)
if err != nil {
err := fmt.Errorf("Error generating tags for spot request: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Create a launch template for the instance
ui.Message("Loading User Data File...")
@@ -253,14 +241,6 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
LaunchTemplateName: aws.String(launchTemplateName),
VersionDescription: aws.String("template generated by packer for launching spot instances"),
}
if len(spotTags) > 0 {
launchTemplate.TagSpecifications = []*ec2.TagSpecification{
{
ResourceType: aws.String("launch-template"),
Tags: spotTags,
},
}
}
// Tell EC2 to create the template
_, err = ec2conn.CreateLaunchTemplate(launchTemplate)
@@ -298,53 +278,36 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
Type: aws.String("instant"),
}
var createOutput *ec2.CreateFleetOutput
err = retry.Config{
Tries: 11,
ShouldRetry: func(err error) bool {
if strings.Contains(err.Error(), "Invalid IAM Instance Profile name") {
// eventual consistency of the profile. PutRolePolicy &
// AddRoleToInstanceProfile are eventually consistent and once
// we can wait on those operations, this can be removed.
return true
}
return false
},
RetryDelay: (&retry.Backoff{InitialBackoff: 500 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
createOutput, err = ec2conn.CreateFleet(createFleetInput)
if err == nil && createOutput.Errors != nil {
err = fmt.Errorf("errors: %v", createOutput.Errors)
}
// We can end up with errors because one of the allowed availability
// zones doesn't have one of the allowed instance types; as long as
// an instance is launched, these errors aren't important.
if len(createOutput.Instances) > 0 {
if err != nil {
log.Printf("create request failed for some instances %v", err.Error())
}
return nil
}
if err != nil {
log.Printf("create request failed %v", err)
}
return err
})
// Create the request for the spot instance.
req, createOutput := ec2conn.CreateFleetRequest(createFleetInput)
ui.Message(fmt.Sprintf("Sending spot request (%s)...", req.RequestID))
// Actually send the spot connection request.
err = req.Send()
if err != nil {
if createOutput.FleetId != nil {
err = fmt.Errorf("Error waiting for fleet request (%s): %s", *createOutput.FleetId, err)
} else {
err = fmt.Errorf("Error waiting for fleet request: %s", err)
}
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(createOutput.Instances) == 0 {
// We can end up with errors because one of the allowed availability
// zones doesn't have one of the allowed instance types; as long as
// an instance is launched, these errors aren't important.
if len(createOutput.Errors) > 0 {
errString := fmt.Sprintf("Error waiting for fleet request (%s) to become ready:", *createOutput.FleetId)
for _, outErr := range createOutput.Errors {
errString = errString + aws.StringValue(outErr.ErrorMessage)
}
err = fmt.Errorf(errString)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instanceId = *createOutput.Instances[0].InstanceIds[0]
@@ -379,6 +342,14 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
instance := describeOutput.Reservations[0].Instances[0]
// Tag the spot instance request (not the eventual spot instance)
spotTags, err := TagMap(s.SpotTags).EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
if err != nil {
err := fmt.Errorf("Error generating tags for spot request: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(spotTags) > 0 && len(s.SpotTags) > 0 {
spotTags.Report(ui)
// Use the instance ID to find out the SIR, so that we can tag the spot
@@ -407,7 +378,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
// Retry creating tags for about 2.5 minutes
err = retry.Config{Tries: 11, ShouldRetry: func(error) bool {
if awserrors.Matches(err, "InvalidInstanceID.NotFound", "") {
if IsAWSErr(err, "InvalidInstanceID.NotFound", "") {
return true
}
return false
@@ -438,7 +409,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
if len(volumeIds) > 0 && len(s.VolumeTags) > 0 {
ui.Say("Adding tags to source EBS Volumes")
volumeTags, err := TagMap(s.VolumeTags).EC2Tags(s.Ctx, s.Region, state)
volumeTags, err := TagMap(s.VolumeTags).EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
if err != nil {
err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err)
state.Put("error", err)
@@ -2,13 +2,11 @@ package common
import (
"bytes"
"context"
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
@@ -45,7 +43,6 @@ func getBasicStep() *StepRunSpotInstance {
ExpectedRootDevice: "ebs",
InstanceInitiatedShutdownBehavior: "stop",
InstanceType: "t2.micro",
Region: "us-east-1",
SourceAMI: "",
SpotPrice: "auto",
SpotTags: nil,
@@ -137,226 +134,3 @@ func TestCreateTemplateData_NoEphemeral(t *testing.T) {
// t.Fatalf("Should have created 26 mappings to keep ephemeral drives from appearing.")
// }
}
type runSpotEC2ConnMock struct {
ec2iface.EC2API
CreateLaunchTemplateParams []*ec2.CreateLaunchTemplateInput
CreateLaunchTemplateFn func(*ec2.CreateLaunchTemplateInput) (*ec2.CreateLaunchTemplateOutput, error)
CreateFleetParams []*ec2.CreateFleetInput
CreateFleetFn func(*ec2.CreateFleetInput) (*ec2.CreateFleetOutput, error)
CreateTagsParams []*ec2.CreateTagsInput
CreateTagsFn func(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error)
DescribeInstancesParams []*ec2.DescribeInstancesInput
DescribeInstancesFn func(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
}
func (m *runSpotEC2ConnMock) CreateLaunchTemplate(req *ec2.CreateLaunchTemplateInput) (*ec2.CreateLaunchTemplateOutput, error) {
m.CreateLaunchTemplateParams = append(m.CreateLaunchTemplateParams, req)
resp, err := m.CreateLaunchTemplateFn(req)
return resp, err
}
func (m *runSpotEC2ConnMock) CreateFleet(req *ec2.CreateFleetInput) (*ec2.CreateFleetOutput, error) {
m.CreateFleetParams = append(m.CreateFleetParams, req)
if m.CreateFleetFn != nil {
resp, err := m.CreateFleetFn(req)
return resp, err
} else {
return nil, nil
}
}
func (m *runSpotEC2ConnMock) DescribeInstances(req *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
m.DescribeInstancesParams = append(m.DescribeInstancesParams, req)
if m.DescribeInstancesFn != nil {
resp, err := m.DescribeInstancesFn(req)
return resp, err
} else {
return nil, nil
}
}
func (m *runSpotEC2ConnMock) CreateTags(req *ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) {
m.CreateTagsParams = append(m.CreateTagsParams, req)
if m.CreateTagsFn != nil {
resp, err := m.CreateTagsFn(req)
return resp, err
} else {
return nil, nil
}
}
func defaultEc2Mock(instanceId, spotRequestId, volumeId *string) *runSpotEC2ConnMock {
instance := &ec2.Instance{
InstanceId: instanceId,
SpotInstanceRequestId: spotRequestId,
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
{
Ebs: &ec2.EbsInstanceBlockDevice{
VolumeId: volumeId,
},
},
},
}
return &runSpotEC2ConnMock{
CreateLaunchTemplateFn: func(in *ec2.CreateLaunchTemplateInput) (*ec2.CreateLaunchTemplateOutput, error) {
return &ec2.CreateLaunchTemplateOutput{
LaunchTemplate: nil,
Warning: nil,
}, nil
},
CreateFleetFn: func(*ec2.CreateFleetInput) (*ec2.CreateFleetOutput, error) {
return &ec2.CreateFleetOutput{
Errors: nil,
FleetId: nil,
Instances: []*ec2.CreateFleetInstance{
{
InstanceIds: []*string{instanceId},
},
},
}, nil
},
DescribeInstancesFn: func(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
return &ec2.DescribeInstancesOutput{
NextToken: nil,
Reservations: []*ec2.Reservation{
{
Instances: []*ec2.Instance{instance},
},
},
}, nil
},
}
}
func TestRun(t *testing.T) {
instanceId := aws.String("test-instance-id")
spotRequestId := aws.String("spot-id")
volumeId := aws.String("volume-id")
ec2Mock := defaultEc2Mock(instanceId, spotRequestId, volumeId)
uiMock := packer.TestUi(t)
state := tStateSpot()
state.Put("ec2", ec2Mock)
state.Put("ui", uiMock)
state.Put("source_image", testImage())
stepRunSpotInstance := getBasicStep()
stepRunSpotInstance.Tags["Name"] = "Packer Builder"
stepRunSpotInstance.Tags["test-tag"] = "test-value"
stepRunSpotInstance.SpotTags = map[string]string{
"spot-tag": "spot-tag-value",
}
stepRunSpotInstance.VolumeTags = map[string]string{
"volume-tag": "volume-tag-value",
}
ctx := context.TODO()
action := stepRunSpotInstance.Run(ctx, state)
if err := state.Get("error"); err != nil {
t.Fatalf("should not error, but: %v", err)
}
if action != multistep.ActionContinue {
t.Fatalf("shoul continue, but: %v", action)
}
if len(ec2Mock.CreateLaunchTemplateParams) != 1 {
t.Fatalf("createLaunchTemplate should be invoked once, but invoked %v", len(ec2Mock.CreateLaunchTemplateParams))
}
launchTemplateName := ec2Mock.CreateLaunchTemplateParams[0].LaunchTemplateName
if len(ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications) != 1 {
t.Fatalf("exactly one launch template tag specification expected")
}
if *ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications[0].ResourceType != "launch-template" {
t.Fatalf("resource type 'launch-template' expected")
}
if len(ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications[0].Tags) != 1 {
t.Fatalf("1 launch template tag expected")
}
nameTag := ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications[0].Tags[0]
if *nameTag.Key != "spot-tag" || *nameTag.Value != "spot-tag-value" {
t.Fatalf("expected spot-tag: spot-tag-value")
}
if len(ec2Mock.CreateFleetParams) != 1 {
t.Fatalf("createFleet should be invoked once, but invoked %v", len(ec2Mock.CreateLaunchTemplateParams))
}
if *ec2Mock.CreateFleetParams[0].TargetCapacitySpecification.DefaultTargetCapacityType != "spot" {
t.Fatalf("capacity type should be spot")
}
if *ec2Mock.CreateFleetParams[0].TargetCapacitySpecification.TotalTargetCapacity != 1 {
t.Fatalf("target capacity should be 1")
}
if len(ec2Mock.CreateFleetParams[0].LaunchTemplateConfigs) != 1 {
t.Fatalf("exactly one launch config template expected")
}
if *ec2Mock.CreateFleetParams[0].LaunchTemplateConfigs[0].LaunchTemplateSpecification.LaunchTemplateName != *launchTemplateName {
t.Fatalf("launchTemplateName should match in createLaunchTemplate and createFleet requests")
}
if len(ec2Mock.DescribeInstancesParams) != 1 {
t.Fatalf("describeInstancesParams should be invoked once, but invoked %v", len(ec2Mock.DescribeInstancesParams))
}
if *ec2Mock.DescribeInstancesParams[0].InstanceIds[0] != *instanceId {
t.Fatalf("instanceId should match from createFleet response")
}
uiMock.Say(fmt.Sprintf("%v", ec2Mock.CreateTagsParams))
if len(ec2Mock.CreateTagsParams) != 3 {
t.Fatalf("createTags should be invoked 3 times")
}
if len(ec2Mock.CreateTagsParams[0].Resources) != 1 || *ec2Mock.CreateTagsParams[0].Resources[0] != *spotRequestId {
t.Fatalf("should create tags for spot request")
}
if len(ec2Mock.CreateTagsParams[1].Resources) != 1 || *ec2Mock.CreateTagsParams[1].Resources[0] != *instanceId {
t.Fatalf("should create tags for instance")
}
if len(ec2Mock.CreateTagsParams[2].Resources) != 1 || ec2Mock.CreateTagsParams[2].Resources[0] != volumeId {
t.Fatalf("should create tags for volume")
}
}
func TestRun_NoSpotTags(t *testing.T) {
instanceId := aws.String("test-instance-id")
spotRequestId := aws.String("spot-id")
volumeId := aws.String("volume-id")
ec2Mock := defaultEc2Mock(instanceId, spotRequestId, volumeId)
uiMock := packer.TestUi(t)
state := tStateSpot()
state.Put("ec2", ec2Mock)
state.Put("ui", uiMock)
state.Put("source_image", testImage())
stepRunSpotInstance := getBasicStep()
stepRunSpotInstance.Tags["Name"] = "Packer Builder"
stepRunSpotInstance.Tags["test-tag"] = "test-value"
stepRunSpotInstance.VolumeTags = map[string]string{
"volume-tag": "volume-tag-value",
}
ctx := context.TODO()
action := stepRunSpotInstance.Run(ctx, state)
if err := state.Get("error"); err != nil {
t.Fatalf("should not error, but: %v", err)
}
if action != multistep.ActionContinue {
t.Fatalf("shoul continue, but: %v", action)
}
if len(ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications) != 0 {
t.Fatalf("0 launch template tags expected")
}
}
@@ -4,7 +4,7 @@ import (
"context"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/builder"
"github.com/hashicorp/packer/helper/multistep"
)
@@ -13,7 +13,7 @@ import (
// },
type StepSetGeneratedData struct {
GeneratedData *packerbuilderdata.GeneratedData
GeneratedData *builder.GeneratedData
}
func (s *StepSetGeneratedData) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@@ -6,7 +6,6 @@ import (
"time"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
"github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
@@ -43,7 +42,7 @@ func (s *StepStopEBSBackedInstance) Run(ctx context.Context, state multistep.Sta
// Work around this by retrying a few times, up to about 5 minutes.
err := retry.Config{Tries: 6, ShouldRetry: func(error) bool {
if awserrors.Matches(err, "InvalidInstanceID.NotFound", "") {
if IsAWSErr(err, "InvalidInstanceID.NotFound", "") {
return true
}
return false
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/builder"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
@@ -23,7 +23,7 @@ func (t EC2Tags) Report(ui packer.Ui) {
func (t TagMap) EC2Tags(ictx interpolate.Context, region string, state multistep.StateBag) (EC2Tags, error) {
var ec2Tags []*ec2.Tag
generatedData := packerbuilderdata.GeneratedData{State: state}
generatedData := builder.GeneratedData{State: state}
ictx.Data = extractBuildInfo(region, state, &generatedData)
for key, value := range t {
+2 -11
View File
@@ -15,9 +15,9 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/hcl2template"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
@@ -85,22 +85,16 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
b.config.ctx.Funcs = awscommon.TemplateFuncs
err := config.Decode(&b.config, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"ami_description",
"run_tags",
"run_tag",
"run_volume_tags",
"run_volume_tag",
"spot_tags",
"spot_tag",
"snapshot_tags",
"snapshot_tag",
"tags",
"tag",
},
},
}, raws...)
@@ -169,7 +163,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
state.Put("awsSession", session)
state.Put("hook", hook)
state.Put("ui", ui)
generatedData := &packerbuilderdata.GeneratedData{State: state}
generatedData := &builder.GeneratedData{State: state}
var instanceStep multistep.Step
@@ -186,7 +180,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
ExpectedRootDevice: "ebs",
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType,
Region: *ec2conn.Config.Region,
SourceAMI: b.config.SourceAmi,
SpotPrice: b.config.SpotPrice,
SpotTags: b.config.SpotTags,
@@ -213,7 +206,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
SourceAMI: b.config.SourceAmi,
Tags: b.config.RunTags,
Tenancy: b.config.Tenancy,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
VolumeTags: b.config.VolumeRunTags,
@@ -277,7 +269,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepCreateSSMTunnel{
AWSSession: session,
Region: *ec2conn.Config.Region,
PauseBeforeSSM: b.config.PauseBeforeSSM,
LocalPortNumber: b.config.SessionManagerPort,
RemotePortNumber: b.config.Comm.Port(),
SSMAgentEnabled: b.config.SSMAgentEnabled(),
-14
View File
@@ -19,9 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
AssumeRole *common.FlatAssumeRoleConfig `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"`
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2" hcl:"custom_endpoint_ec2"`
CredsFilename *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"`
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages" hcl:"decode_authorization_messages"`
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
MaxRetries *int `mapstructure:"max_retries" required:"false" cty:"max_retries" hcl:"max_retries"`
@@ -31,7 +29,6 @@ type FlatConfig struct {
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
@@ -81,7 +78,6 @@ type FlatConfig struct {
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
@@ -96,8 +92,6 @@ type FlatConfig struct {
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
@@ -138,7 +132,6 @@ type FlatConfig struct {
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface" hcl:"ssh_interface"`
PauseBeforeSSM *string `mapstructure:"pause_before_ssm" cty:"pause_before_ssm" hcl:"pause_before_ssm"`
SessionManagerPort *int `mapstructure:"session_manager_port" cty:"session_manager_port" hcl:"session_manager_port"`
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" required:"false" cty:"ami_block_device_mappings" hcl:"ami_block_device_mappings"`
LaunchMappings []common.FlatBlockDevice `mapstructure:"launch_block_device_mappings" required:"false" cty:"launch_block_device_mappings" hcl:"launch_block_device_mappings"`
@@ -167,9 +160,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@@ -179,7 +170,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
@@ -229,7 +219,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
@@ -244,8 +233,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
@@ -286,7 +273,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"pause_before_ssm": &hcldec.AttrSpec{Name: "pause_before_ssm", Type: cty.String, Required: false},
"session_manager_port": &hcldec.AttrSpec{Name: "session_manager_port", Type: cty.Number, Required: false},
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
"launch_block_device_mappings": &hcldec.BlockListSpec{TypeName: "launch_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
+5 -48
View File
@@ -6,7 +6,6 @@ package ebs
import (
"fmt"
"os"
"testing"
"github.com/aws/aws-sdk-go/aws"
@@ -105,10 +104,10 @@ func checkSnapshotsDeleted(snapshotIds []*string) builderT.TestCheckFunc {
func TestBuilderAcc_amiSharing(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccSharingPreCheck(t) },
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: buildSharingConfig(os.Getenv("TESTACC_AWS_ACCOUNT_ID")),
Check: checkAMISharing(2, os.Getenv("TESTACC_AWS_ACCOUNT_ID"), "all"),
Template: testBuilderAccSharing,
Check: checkAMISharing(2, "932021504756", "all"),
})
}
@@ -246,23 +245,9 @@ func checkBootEncrypted() builderT.TestCheckFunc {
}
}
func TestBuilderAcc_SessionManagerInterface(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccSessionManagerInterface,
})
}
func testAccPreCheck(t *testing.T) {
}
func testAccSharingPreCheck(t *testing.T) {
if v := os.Getenv("TESTACC_AWS_ACCOUNT_ID"); v == "" {
t.Fatal(fmt.Sprintf("TESTACC_AWS_ACCOUNT_ID must be set for acceptance tests"))
}
}
func testEC2Conn() (*ec2.EC2, error) {
access := &common.AccessConfig{RawRegion: "us-east-1"}
session, err := access.Session()
@@ -329,6 +314,7 @@ const testBuilderAccForceDeleteSnapshot = `
}
`
// share with catsby
const testBuilderAccSharing = `
{
"builders": [{
@@ -338,7 +324,7 @@ const testBuilderAccSharing = `
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"ami_name": "packer-test {{timestamp}}",
"ami_users":["%s"],
"ami_users":["932021504756"],
"ami_groups":["all"]
}]
}
@@ -358,31 +344,6 @@ const testBuilderAccEncrypted = `
}
`
const testBuilderAccSessionManagerInterface = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami_filter": {
"filters": {
"virtualization-type": "hvm",
"name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*",
"root-device-type": "ebs"
},
"owners": [
"099720109477"
],
"most_recent": true
},
"ssh_username": "ubuntu",
"ssh_interface": "session_manager",
"iam_instance_profile": "SSMInstanceProfile",
"ami_name": "packer-ssm-test-{{timestamp}}"
}]
}
`
func buildForceDeregisterConfig(val, name string) string {
return fmt.Sprintf(testBuilderAccForceDeregister, val, name)
}
@@ -390,7 +351,3 @@ func buildForceDeregisterConfig(val, name string) string {
func buildForceDeleteSnapshotConfig(val, name string) string {
return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name)
}
func buildSharingConfig(val string) string {
return fmt.Sprintf(testBuilderAccSharing, val)
}
+1 -2
View File
@@ -9,7 +9,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
"github.com/hashicorp/packer/common/random"
"github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/helper/multistep"
@@ -62,7 +61,7 @@ func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multi
err = retry.Config{
Tries: 0,
ShouldRetry: func(err error) bool {
if awserrors.Matches(err, "InvalidParameterValue", "Instance is not in state") {
if awscommon.IsAWSErr(err, "InvalidParameterValue", "Instance is not in state") {
return true
}
return false
@@ -3,6 +3,9 @@
package ebssurrogate
import (
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/template/interpolate"
@@ -38,6 +41,50 @@ func (bds BlockDevices) BuildEC2BlockDeviceMappings() []*ec2.BlockDeviceMapping
return blockDevices
}
func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapping {
mapping := &ec2.BlockDeviceMapping{
DeviceName: aws.String(blockDevice.DeviceName),
}
if blockDevice.NoDevice {
mapping.NoDevice = aws.String("")
return mapping
} else if blockDevice.VirtualName != "" {
if strings.HasPrefix(blockDevice.VirtualName, "ephemeral") {
mapping.VirtualName = aws.String(blockDevice.VirtualName)
}
return mapping
}
ebsBlockDevice := &ec2.EbsBlockDevice{
DeleteOnTermination: aws.Bool(blockDevice.DeleteOnTermination),
}
if blockDevice.VolumeType != "" {
ebsBlockDevice.VolumeType = aws.String(blockDevice.VolumeType)
}
if blockDevice.VolumeSize > 0 {
ebsBlockDevice.VolumeSize = aws.Int64(blockDevice.VolumeSize)
}
// IOPS is only valid for io1 type
if blockDevice.VolumeType == "io1" {
ebsBlockDevice.Iops = aws.Int64(blockDevice.IOPS)
}
// You cannot specify Encrypted if you specify a Snapshot ID
if blockDevice.SnapshotId != "" {
ebsBlockDevice.SnapshotId = aws.String(blockDevice.SnapshotId)
}
ebsBlockDevice.Encrypted = blockDevice.Encrypted.ToBoolPointer()
mapping.Ebs = ebsBlockDevice
return mapping
}
func (bds BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
for _, block := range bds {
if err := block.Prepare(ctx); err != nil {
+2 -13
View File
@@ -13,9 +13,9 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/hcl2template"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
@@ -84,22 +84,16 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
b.config.ctx.Funcs = awscommon.TemplateFuncs
err := config.Decode(&b.config, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"ami_description",
"run_tags",
"run_tag",
"run_volume_tags",
"run_volume_tag",
"snapshot_tags",
"snapshot_tag",
"spot_tags",
"spot_tag",
"tags",
"tag",
},
},
}, raws...)
@@ -192,7 +186,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
state.Put("awsSession", session)
state.Put("hook", hook)
state.Put("ui", ui)
generatedData := &packerbuilderdata.GeneratedData{State: state}
generatedData := &builder.GeneratedData{State: state}
var instanceStep multistep.Step
@@ -209,7 +203,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
ExpectedRootDevice: "ebs",
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType,
Region: *ec2conn.Config.Region,
SourceAMI: b.config.SourceAmi,
SpotPrice: b.config.SpotPrice,
SpotInstanceTypes: b.config.SpotInstanceTypes,
@@ -235,7 +228,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
SourceAMI: b.config.SourceAmi,
Tags: b.config.RunTags,
Tenancy: b.config.Tenancy,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
VolumeTags: b.config.VolumeRunTags,
@@ -301,7 +293,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepCreateSSMTunnel{
AWSSession: session,
Region: *ec2conn.Config.Region,
PauseBeforeSSM: b.config.PauseBeforeSSM,
LocalPortNumber: b.config.SessionManagerPort,
RemotePortNumber: b.config.Comm.Port(),
SSMAgentEnabled: b.config.SSMAgentEnabled(),
@@ -340,8 +331,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
PollingConfig: b.config.PollingConfig,
LaunchDevices: launchDevices,
SnapshotOmitMap: b.config.LaunchMappings.GetOmissions(),
SnapshotTags: b.config.SnapshotTags,
Ctx: b.config.ctx,
},
&awscommon.StepDeregisterAMI{
AccessConfig: &b.config.AccessConfig,
@@ -62,9 +62,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
AssumeRole *common.FlatAssumeRoleConfig `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"`
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2" hcl:"custom_endpoint_ec2"`
CredsFilename *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"`
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages" hcl:"decode_authorization_messages"`
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
MaxRetries *int `mapstructure:"max_retries" required:"false" cty:"max_retries" hcl:"max_retries"`
@@ -74,7 +72,6 @@ type FlatConfig struct {
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
@@ -103,7 +100,6 @@ type FlatConfig struct {
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
@@ -118,8 +114,6 @@ type FlatConfig struct {
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
@@ -160,7 +154,6 @@ type FlatConfig struct {
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface" hcl:"ssh_interface"`
PauseBeforeSSM *string `mapstructure:"pause_before_ssm" cty:"pause_before_ssm" hcl:"pause_before_ssm"`
SessionManagerPort *int `mapstructure:"session_manager_port" cty:"session_manager_port" hcl:"session_manager_port"`
AMIName *string `mapstructure:"ami_name" required:"true" cty:"ami_name" hcl:"ami_name"`
AMIDescription *string `mapstructure:"ami_description" required:"false" cty:"ami_description" hcl:"ami_description"`
@@ -211,9 +204,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@@ -223,7 +214,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
@@ -252,7 +242,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
@@ -267,8 +256,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
@@ -309,7 +296,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"pause_before_ssm": &hcldec.AttrSpec{Name: "pause_before_ssm", Type: cty.String, Required: false},
"session_manager_port": &hcldec.AttrSpec{Name: "session_manager_port", Type: cty.Number, Required: false},
"ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false},
"ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false},
@@ -6,13 +6,11 @@ import (
"sync"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
multierror "github.com/hashicorp/go-multierror"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
// StepSnapshotVolumes creates snapshots of the created volumes.
@@ -25,8 +23,6 @@ type StepSnapshotVolumes struct {
snapshotIds map[string]string
snapshotMutex sync.Mutex
SnapshotOmitMap map[string]bool
SnapshotTags map[string]string
Ctx interpolate.Context
}
func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName string, state multistep.StateBag) error {
@@ -44,34 +40,12 @@ func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName str
return fmt.Errorf("Volume ID for device %s not found", deviceName)
}
ui.Say("Creating snapshot tags")
snapshotTags, err := awscommon.TagMap(s.SnapshotTags).EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return err
}
snapshotTags.Report(ui)
ui.Say(fmt.Sprintf("Creating snapshot of EBS Volume %s...", volumeId))
description := fmt.Sprintf("Packer: %s", time.Now().String())
// Collect tags for tagging on resource creation
var tagSpecs []*ec2.TagSpecification
if len(snapshotTags) > 0 {
snapTags := &ec2.TagSpecification{
ResourceType: aws.String("snapshot"),
Tags: snapshotTags,
}
tagSpecs = append(tagSpecs, snapTags)
}
createSnapResp, err := ec2conn.CreateSnapshot(&ec2.CreateSnapshotInput{
VolumeId: &volumeId,
Description: &description,
TagSpecifications: tagSpecs,
VolumeId: &volumeId,
Description: &description,
})
if err != nil {
return err
+2 -18
View File
@@ -12,9 +12,9 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/hcl2template"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
@@ -100,21 +100,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
SourceAMI: `{{ .SourceAMI }} `,
}
err := config.Decode(&b.config, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"run_tags",
"run_tag",
"run_volume_tags",
"run_volume_tag",
"spot_tags",
"spot_tag",
"tags",
"tag",
},
},
}, raws...)
if err != nil {
return nil, nil, err
@@ -180,7 +167,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
state.Put("iam", iam)
state.Put("hook", hook)
state.Put("ui", ui)
generatedData := &packerbuilderdata.GeneratedData{State: state}
generatedData := &builder.GeneratedData{State: state}
var instanceStep multistep.Step
@@ -197,7 +184,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
ExpectedRootDevice: "ebs",
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType,
Region: *ec2conn.Config.Region,
SourceAMI: b.config.SourceAmi,
SpotInstanceTypes: b.config.SpotInstanceTypes,
SpotPrice: b.config.SpotPrice,
@@ -223,7 +209,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
SourceAMI: b.config.SourceAmi,
Tags: b.config.RunTags,
Tenancy: b.config.Tenancy,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
VolumeTags: b.config.VolumeRunTags,
@@ -278,7 +263,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepCreateSSMTunnel{
AWSSession: session,
Region: *ec2conn.Config.Region,
PauseBeforeSSM: b.config.PauseBeforeSSM,
LocalPortNumber: b.config.SessionManagerPort,
RemotePortNumber: b.config.Comm.Port(),
SSMAgentEnabled: b.config.SSMAgentEnabled(),
@@ -64,9 +64,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
AssumeRole *common.FlatAssumeRoleConfig `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"`
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2" hcl:"custom_endpoint_ec2"`
CredsFilename *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"`
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages" hcl:"decode_authorization_messages"`
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
MaxRetries *int `mapstructure:"max_retries" required:"false" cty:"max_retries" hcl:"max_retries"`
@@ -76,7 +74,6 @@ type FlatConfig struct {
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
@@ -105,7 +102,6 @@ type FlatConfig struct {
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
@@ -120,8 +116,6 @@ type FlatConfig struct {
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
@@ -162,7 +156,6 @@ type FlatConfig struct {
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface" hcl:"ssh_interface"`
PauseBeforeSSM *string `mapstructure:"pause_before_ssm" cty:"pause_before_ssm" hcl:"pause_before_ssm"`
SessionManagerPort *int `mapstructure:"session_manager_port" cty:"session_manager_port" hcl:"session_manager_port"`
AMIENASupport *bool `mapstructure:"ena_support" required:"false" cty:"ena_support" hcl:"ena_support"`
AMISriovNetSupport *bool `mapstructure:"sriov_support" required:"false" cty:"sriov_support" hcl:"sriov_support"`
@@ -191,9 +184,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@@ -203,7 +194,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
@@ -232,7 +222,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
@@ -247,8 +236,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
@@ -289,7 +276,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"pause_before_ssm": &hcldec.AttrSpec{Name: "pause_before_ssm", Type: cty.String, Required: false},
"session_manager_port": &hcldec.AttrSpec{Name: "session_manager_port", Type: cty.Number, Required: false},
"ena_support": &hcldec.AttrSpec{Name: "ena_support", Type: cty.Bool, Required: false},
"sriov_support": &hcldec.AttrSpec{Name: "sriov_support", Type: cty.Bool, Required: false},
+2 -6
View File
@@ -15,9 +15,9 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
@@ -107,7 +107,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
b.config.ctx.Funcs = awscommon.TemplateFuncs
err := config.Decode(&b.config, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
@@ -250,7 +249,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
state.Put("awsSession", session)
state.Put("hook", hook)
state.Put("ui", ui)
generatedData := &packerbuilderdata.GeneratedData{State: state}
generatedData := &builder.GeneratedData{State: state}
var instanceStep multistep.Step
@@ -265,7 +264,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Debug: b.config.PackerDebug,
EbsOptimized: b.config.EbsOptimized,
InstanceType: b.config.InstanceType,
Region: *ec2conn.Config.Region,
SourceAMI: b.config.SourceAmi,
SpotPrice: b.config.SpotPrice,
SpotInstanceTypes: b.config.SpotInstanceTypes,
@@ -288,7 +286,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
SourceAMI: b.config.SourceAmi,
Tags: b.config.RunTags,
Tenancy: b.config.Tenancy,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
}
@@ -346,7 +343,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepCreateSSMTunnel{
AWSSession: session,
Region: *ec2conn.Config.Region,
PauseBeforeSSM: b.config.PauseBeforeSSM,
LocalPortNumber: b.config.SessionManagerPort,
RemotePortNumber: b.config.Comm.Port(),
SSMAgentEnabled: b.config.SSMAgentEnabled(),
@@ -19,9 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
AssumeRole *common.FlatAssumeRoleConfig `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"`
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2" hcl:"custom_endpoint_ec2"`
CredsFilename *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"`
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages" hcl:"decode_authorization_messages"`
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
MaxRetries *int `mapstructure:"max_retries" required:"false" cty:"max_retries" hcl:"max_retries"`
@@ -31,7 +29,6 @@ type FlatConfig struct {
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
@@ -81,7 +78,6 @@ type FlatConfig struct {
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
@@ -96,8 +92,6 @@ type FlatConfig struct {
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
@@ -138,7 +132,6 @@ type FlatConfig struct {
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface" hcl:"ssh_interface"`
PauseBeforeSSM *string `mapstructure:"pause_before_ssm" cty:"pause_before_ssm" hcl:"pause_before_ssm"`
SessionManagerPort *int `mapstructure:"session_manager_port" cty:"session_manager_port" hcl:"session_manager_port"`
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" required:"false" cty:"ami_block_device_mappings" hcl:"ami_block_device_mappings"`
LaunchMappings []common.FlatBlockDevice `mapstructure:"launch_block_device_mappings" required:"false" cty:"launch_block_device_mappings" hcl:"launch_block_device_mappings"`
@@ -173,9 +166,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@@ -185,7 +176,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
@@ -235,7 +225,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
@@ -250,8 +239,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
@@ -292,7 +279,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"pause_before_ssm": &hcldec.AttrSpec{Name: "pause_before_ssm", Type: cty.String, Required: false},
"session_manager_port": &hcldec.AttrSpec{Name: "session_manager_port", Type: cty.Number, Required: false},
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
"launch_block_device_mappings": &hcldec.BlockListSpec{TypeName: "launch_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
+18 -20
View File
@@ -128,8 +128,8 @@ func byConcatDecorators(decorators ...autorest.RespondDecorator) autorest.Respon
}
}
func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storageAccountName string,
cloud *azure.Environment, sharedGalleryTimeout time.Duration, pollingDuration time.Duration,
func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string,
cloud *azure.Environment, SharedGalleryTimeout time.Duration, PollingDuration time.Duration,
servicePrincipalToken, servicePrincipalTokenVault *adal.ServicePrincipalToken) (*AzureClient, error) {
var azureClient = &AzureClient{}
@@ -141,56 +141,56 @@ func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storag
azureClient.DeploymentsClient.RequestInspector = withInspection(maxlen)
azureClient.DeploymentsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.DeploymentsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DeploymentsClient.UserAgent)
azureClient.DeploymentsClient.Client.PollingDuration = pollingDuration
azureClient.DeploymentsClient.Client.PollingDuration = PollingDuration
azureClient.DeploymentOperationsClient = resources.NewDeploymentOperationsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.DeploymentOperationsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.DeploymentOperationsClient.RequestInspector = withInspection(maxlen)
azureClient.DeploymentOperationsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.DeploymentOperationsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DeploymentOperationsClient.UserAgent)
azureClient.DeploymentOperationsClient.Client.PollingDuration = pollingDuration
azureClient.DeploymentOperationsClient.Client.PollingDuration = PollingDuration
azureClient.DisksClient = compute.NewDisksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.DisksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.DisksClient.RequestInspector = withInspection(maxlen)
azureClient.DisksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.DisksClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DisksClient.UserAgent)
azureClient.DisksClient.Client.PollingDuration = pollingDuration
azureClient.DisksClient.Client.PollingDuration = PollingDuration
azureClient.GroupsClient = resources.NewGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.GroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.GroupsClient.RequestInspector = withInspection(maxlen)
azureClient.GroupsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.GroupsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GroupsClient.UserAgent)
azureClient.GroupsClient.Client.PollingDuration = pollingDuration
azureClient.GroupsClient.Client.PollingDuration = PollingDuration
azureClient.ImagesClient = compute.NewImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.ImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.ImagesClient.RequestInspector = withInspection(maxlen)
azureClient.ImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.ImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.ImagesClient.UserAgent)
azureClient.ImagesClient.Client.PollingDuration = pollingDuration
azureClient.ImagesClient.Client.PollingDuration = PollingDuration
azureClient.InterfacesClient = network.NewInterfacesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.InterfacesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.InterfacesClient.RequestInspector = withInspection(maxlen)
azureClient.InterfacesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.InterfacesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.InterfacesClient.UserAgent)
azureClient.InterfacesClient.Client.PollingDuration = pollingDuration
azureClient.InterfacesClient.Client.PollingDuration = PollingDuration
azureClient.SubnetsClient = network.NewSubnetsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.SubnetsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.SubnetsClient.RequestInspector = withInspection(maxlen)
azureClient.SubnetsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.SubnetsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.SubnetsClient.UserAgent)
azureClient.SubnetsClient.Client.PollingDuration = pollingDuration
azureClient.SubnetsClient.Client.PollingDuration = PollingDuration
azureClient.VirtualNetworksClient = network.NewVirtualNetworksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.VirtualNetworksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.VirtualNetworksClient.RequestInspector = withInspection(maxlen)
azureClient.VirtualNetworksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.VirtualNetworksClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VirtualNetworksClient.UserAgent)
azureClient.VirtualNetworksClient.Client.PollingDuration = pollingDuration
azureClient.VirtualNetworksClient.Client.PollingDuration = PollingDuration
azureClient.SecurityGroupsClient = network.NewSecurityGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.SecurityGroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
@@ -203,44 +203,42 @@ func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storag
azureClient.PublicIPAddressesClient.RequestInspector = withInspection(maxlen)
azureClient.PublicIPAddressesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.PublicIPAddressesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.PublicIPAddressesClient.UserAgent)
azureClient.PublicIPAddressesClient.Client.PollingDuration = pollingDuration
azureClient.PublicIPAddressesClient.Client.PollingDuration = PollingDuration
azureClient.VirtualMachinesClient = compute.NewVirtualMachinesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.VirtualMachinesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.VirtualMachinesClient.RequestInspector = withInspection(maxlen)
azureClient.VirtualMachinesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), templateCapture(azureClient), errorCapture(azureClient))
azureClient.VirtualMachinesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VirtualMachinesClient.UserAgent)
azureClient.VirtualMachinesClient.Client.PollingDuration = pollingDuration
azureClient.VirtualMachinesClient.Client.PollingDuration = PollingDuration
azureClient.SnapshotsClient = compute.NewSnapshotsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.SnapshotsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.SnapshotsClient.RequestInspector = withInspection(maxlen)
azureClient.SnapshotsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.SnapshotsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.SnapshotsClient.UserAgent)
azureClient.SnapshotsClient.Client.PollingDuration = pollingDuration
azureClient.SnapshotsClient.Client.PollingDuration = PollingDuration
azureClient.AccountsClient = armStorage.NewAccountsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.AccountsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.AccountsClient.RequestInspector = withInspection(maxlen)
azureClient.AccountsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.AccountsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.AccountsClient.UserAgent)
azureClient.AccountsClient.Client.PollingDuration = pollingDuration
azureClient.AccountsClient.Client.PollingDuration = PollingDuration
azureClient.GalleryImageVersionsClient = newCompute.NewGalleryImageVersionsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.GalleryImageVersionsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.GalleryImageVersionsClient.RequestInspector = withInspection(maxlen)
azureClient.GalleryImageVersionsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.GalleryImageVersionsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GalleryImageVersionsClient.UserAgent)
azureClient.GalleryImageVersionsClient.Client.PollingDuration = sharedGalleryTimeout
azureClient.GalleryImageVersionsClient.SubscriptionID = sigSubscriptionID
azureClient.GalleryImageVersionsClient.Client.PollingDuration = SharedGalleryTimeout
azureClient.GalleryImagesClient = newCompute.NewGalleryImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.GalleryImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.GalleryImagesClient.RequestInspector = withInspection(maxlen)
azureClient.GalleryImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.GalleryImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GalleryImagesClient.UserAgent)
azureClient.GalleryImagesClient.Client.PollingDuration = pollingDuration
azureClient.GalleryImagesClient.SubscriptionID = sigSubscriptionID
azureClient.GalleryImagesClient.Client.PollingDuration = PollingDuration
keyVaultURL, err := url.Parse(cloud.KeyVaultEndpoint)
if err != nil {
@@ -252,7 +250,7 @@ func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storag
azureClient.VaultClient.RequestInspector = withInspection(maxlen)
azureClient.VaultClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.VaultClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VaultClient.UserAgent)
azureClient.VaultClient.Client.PollingDuration = pollingDuration
azureClient.VaultClient.Client.PollingDuration = PollingDuration
// This client is different than the above because it manages the vault
// itself rather than the contents of the vault.
@@ -261,7 +259,7 @@ func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storag
azureClient.VaultClientDelete.RequestInspector = withInspection(maxlen)
azureClient.VaultClientDelete.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.VaultClientDelete.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VaultClientDelete.UserAgent)
azureClient.VaultClientDelete.Client.PollingDuration = pollingDuration
azureClient.VaultClientDelete.Client.PollingDuration = PollingDuration
// If this is a managed disk build, this should be ignored.
if resourceGroupName != "" && storageAccountName != "" {
@@ -1,4 +1,4 @@
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.
ERROR: -> BadRequest
ERROR: -> InvalidRequestFormat : Cannot parse the request.
ERROR: -> InvalidJson : Error converting value "playground" to type 'Microsoft.WindowsAzure.Networking.Nrp.Frontend.Contract.Csm.Public.IpAllocationMethod'. Path 'properties.publicIPAllocationMethod', line 1, position 130.
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.
ERROR: -> BadRequest
ERROR: -> InvalidRequestFormat : Cannot parse the request.
ERROR: -> InvalidJson : Error converting value "playground" to type 'Microsoft.WindowsAzure.Networking.Nrp.Frontend.Contract.Csm.Public.IpAllocationMethod'. Path 'properties.publicIPAllocationMethod', line 1, position 130.
@@ -1 +1 @@
ERROR: -> ResourceNotFound : The Resource 'Microsoft.Compute/images/PackerUbuntuImage' under resource group 'packer-test00' was not found.
ERROR: -> ResourceNotFound : The Resource 'Microsoft.Compute/images/PackerUbuntuImage' under resource group 'packer-test00' was not found.
+3 -2
View File
@@ -84,7 +84,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
ui.Message("Creating Azure Resource Manager (ARM) client ...")
azureClient, err := NewAzureClient(
b.config.ClientConfig.SubscriptionID,
b.config.SharedGalleryDestination.SigDestinationSubscription,
b.config.ResourceGroupName,
b.config.StorageAccount,
b.config.ClientConfig.CloudEnvironment(),
@@ -222,6 +221,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
NewStepSnapshotDataDisks(azureClient, ui, &b.config),
NewStepCaptureImage(azureClient, ui),
NewStepPublishToSharedImageGallery(azureClient, ui, &b.config),
NewStepDeleteAdditionalDisks(azureClient, ui),
}
} else if b.config.OSType == constants.Target_Windows {
steps = []multistep.Step{
@@ -262,6 +262,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
NewStepSnapshotDataDisks(azureClient, ui, &b.config),
NewStepCaptureImage(azureClient, ui),
NewStepPublishToSharedImageGallery(azureClient, ui, &b.config),
NewStepDeleteAdditionalDisks(azureClient, ui),
)
} else {
return nil, fmt.Errorf("Builder does not support the os_type '%s'", b.config.OSType)
@@ -388,7 +389,7 @@ func (b *Builder) getBlobAccount(ctx context.Context, client *AzureClient, resou
func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
stateBag.Put(constants.AuthorizedKey, b.config.sshAuthorizedKey)
stateBag.Put(constants.ArmTags, packerAzureCommon.MapToAzureTags(b.config.AzureTags))
stateBag.Put(constants.ArmTags, b.config.AzureTags)
stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
+5 -87
View File
@@ -6,6 +6,7 @@ package arm
// * ARM_CLIENT_ID
// * ARM_CLIENT_SECRET
// * ARM_SUBSCRIPTION_ID
// * ARM_OBJECT_ID
// * ARM_STORAGE_ACCOUNT
//
// The subscription in question should have a resource group
@@ -46,14 +47,6 @@ func TestBuilderAcc_ManagedDisk_Windows_Build_Resource_Group(t *testing.T) {
})
}
func TestBuilderAcc_ManagedDisk_Windows_Build_Resource_Group_Additional_Disk(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccManagedDiskWindowsBuildResourceGroupAdditionalDisk,
})
}
func TestBuilderAcc_ManagedDisk_Windows_DeviceLogin(t *testing.T) {
if os.Getenv(DeviceLoginAcceptanceTest) == "" {
t.Skip(fmt.Sprintf(
@@ -90,19 +83,6 @@ func TestBuilderAcc_ManagedDisk_Linux_DeviceLogin(t *testing.T) {
})
}
func TestBuilderAcc_ManagedDisk_Linux_AzureCLI(t *testing.T) {
if os.Getenv("AZURE_CLI_AUTH") == "" {
t.Skip("Azure CLI Acceptance tests skipped unless env 'AZURE_CLI_AUTH' is set, and an active `az login` session has been established")
return
}
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccManagedDiskLinuxAzureCLI,
})
}
func TestBuilderAcc_Blob_Windows(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@@ -171,7 +151,7 @@ const testBuilderAccManagedDiskWindowsBuildResourceGroup = `
"build_resource_group_name" : "packer-acceptance-test",
"managed_image_resource_group_name": "packer-acceptance-test",
"managed_image_name": "testBuilderAccManagedDiskWindowsBuildResourceGroup-{{timestamp}}",
"managed_image_name": "testBuilderAccManagedDiskWindows-{{timestamp}}",
"os_type": "Windows",
"image_publisher": "MicrosoftWindowsServer",
@@ -190,42 +170,6 @@ const testBuilderAccManagedDiskWindowsBuildResourceGroup = `
}
`
const testBuilderAccManagedDiskWindowsBuildResourceGroupAdditionalDisk = `
{
"variables": {
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
},
"builders": [{
"type": "test",
"client_id": "{{user ` + "`client_id`" + `}}",
"client_secret": "{{user ` + "`client_secret`" + `}}",
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
"build_resource_group_name" : "packer-acceptance-test",
"managed_image_resource_group_name": "packer-acceptance-test",
"managed_image_name": "testBuilderAccManagedDiskWindowsBuildResourceGroupAdditionDisk-{{timestamp}}",
"os_type": "Windows",
"image_publisher": "MicrosoftWindowsServer",
"image_offer": "WindowsServer",
"image_sku": "2012-R2-Datacenter",
"communicator": "winrm",
"winrm_use_ssl": "true",
"winrm_insecure": "true",
"winrm_timeout": "3m",
"winrm_username": "packer",
"async_resourcegroup_delete": "true",
"vm_size": "Standard_DS2_v2",
"disk_additional_size": [10,15]
}]
}
`
const testBuilderAccManagedDiskWindowsDeviceLogin = `
{
"variables": {
@@ -279,11 +223,7 @@ const testBuilderAccManagedDiskLinux = `
"image_sku": "16.04-LTS",
"location": "South Central US",
"vm_size": "Standard_DS2_v2",
"azure_tags": {
"env": "testing",
"builder": "packer"
}
"vm_size": "Standard_DS2_v2"
}]
}
`
@@ -318,6 +258,7 @@ const testBuilderAccBlobWindows = `
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}",
"object_id": "{{env ` + "`ARM_OBJECT_ID`" + `}}",
"storage_account": "{{env ` + "`ARM_STORAGE_ACCOUNT`" + `}}"
},
"builders": [{
@@ -326,6 +267,7 @@ const testBuilderAccBlobWindows = `
"client_id": "{{user ` + "`client_id`" + `}}",
"client_secret": "{{user ` + "`client_secret`" + `}}",
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
"object_id": "{{user ` + "`object_id`" + `}}",
"storage_account": "{{user ` + "`storage_account`" + `}}",
"resource_group_name": "packer-acceptance-test",
@@ -379,27 +321,3 @@ const testBuilderAccBlobLinux = `
}]
}
`
const testBuilderAccManagedDiskLinuxAzureCLI = `
{
"builders": [{
"type": "test",
"use_azure_cli_auth": true,
"managed_image_resource_group_name": "packer-acceptance-test",
"managed_image_name": "testBuilderAccManagedDiskLinuxAzureCLI-{{timestamp}}",
"os_type": "Linux",
"image_publisher": "Canonical",
"image_offer": "UbuntuServer",
"image_sku": "16.04-LTS",
"location": "South Central US",
"vm_size": "Standard_DS2_v2",
"azure_tags": {
"env": "testing",
"builder": "packer"
}
}]
}
`
-32
View File
@@ -33,35 +33,3 @@ func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
}
}
}
func TestStateBagShouldPoluateExpectedTags(t *testing.T) {
var testSubject Builder
expectedTags := map[string]string{
"env": "test",
"builder": "packer",
}
armConfig := getArmBuilderConfiguration()
armConfig["azure_tags"] = expectedTags
_, _, err := testSubject.Prepare(armConfig, getPackerConfiguration())
if err != nil {
t.Fatalf("failed to prepare: %s", err)
}
tags, ok := testSubject.stateBag.Get(constants.ArmTags).(map[string]*string)
if !ok {
t.Errorf("Expected the builder's state bag to contain tags of type %T, but didn't.", testSubject.config.AzureTags)
}
if len(tags) != len(expectedTags) {
t.Errorf("expect tags from state to be the same length as tags from config")
}
for k, v := range tags {
if expectedTags[k] != *v {
t.Errorf("expect tag value of %s to be %s, but got %s", k, expectedTags[k], *v)
}
}
}
+32 -67
View File
@@ -90,7 +90,6 @@ type SharedImageGallery struct {
}
type SharedImageGalleryDestination struct {
SigDestinationSubscription string `mapstructure:"subscription"`
SigDestinationResourceGroup string `mapstructure:"resource_group"`
SigDestinationGalleryName string `mapstructure:"gallery_name"`
SigDestinationImageName string `mapstructure:"image_name"`
@@ -119,64 +118,31 @@ type Config struct {
// Use a [Shared Gallery
// image](https://azure.microsoft.com/en-us/blog/announcing-the-public-preview-of-shared-image-gallery/)
// as the source for this build. *VHD targets are incompatible with this
// build type* - the target must be a *Managed Image*. When using shared_image_gallery as a source, image_publisher,
// image_offer, image_sku, image_version, and custom_managed_image_name should not be set.
// build type* - the target must be a *Managed Image*.
//
// In JSON
// ```json
// "shared_image_gallery": {
// "subscription": "00000000-0000-0000-0000-00000000000",
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0"
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
// ```
// In HCL2
// ```hcl
// shared_image_gallery {
// subscription = "00000000-0000-0000-0000-00000000000"
// resource_group = "ResourceGroup"
// gallery_name = "GalleryName"
// image_name = "ImageName"
// image_version = "1.0.0"
// }
// managed_image_name = "TargetImageName"
// managed_image_resource_group_name = "TargetResourceGroup"
// ```
// "shared_image_gallery": {
// "subscription": "00000000-0000-0000-0000-00000000000",
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0"
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
SharedGallery SharedImageGallery `mapstructure:"shared_image_gallery" required:"false"`
// The name of the Shared Image Gallery under which the managed image will be published as Shared Gallery Image version.
//
// Following is an example.
//
// In JSON
// ```json
// "shared_image_gallery_destination": {
// "subscription": "00000000-0000-0000-0000-00000000000",
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0",
// "replication_regions": ["regionA", "regionB", "regionC"]
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
// ```
// In HCL2
// ```hcl
// shared_image_gallery_destination {
// subscription = "00000000-0000-0000-0000-00000000000"
// resource_group = "ResourceGroup"
// gallery_name = "GalleryName"
// image_name = "ImageName"
// image_version = "1.0.0"
// replication_regions = ["regionA", "regionB", "regionC"]
// }
// managed_image_name = "TargetImageName"
// managed_image_resource_group_name = "TargetResourceGroup"
// ```
// "shared_image_gallery_destination": {
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0",
// "replication_regions": ["regionA", "regionB", "regionC"]
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
SharedGalleryDestination SharedImageGalleryDestination `mapstructure:"shared_image_gallery_destination"`
// How long to wait for an image to be published to the shared image
// gallery before timing out. If your Packer build is failing on the
@@ -288,7 +254,7 @@ type Config struct {
// Group, VM, NIC, VNET, Public IP, KeyVault, etc. The user can define up
// to 15 tags. Tag names cannot exceed 512 characters, and tag values
// cannot exceed 256 characters.
AzureTags map[string]string `mapstructure:"azure_tags" required:"false"`
AzureTags map[string]*string `mapstructure:"azure_tags" required:"false"`
// Same as [`azure_tags`](#azure_tags) but defined as a singular repeatable block
// containing a `name` and a `value` field. In HCL2 mode the
// [`dynamic_block`](/docs/configuration/from-1.5/expressions#dynamic-blocks)
@@ -513,7 +479,7 @@ func (c *Config) toImageParameters() *compute.Image {
},
},
Location: to.StringPtr(c.Location),
Tags: azcommon.MapToAzureTags(c.AzureTags),
Tags: c.AzureTags,
}
}
@@ -580,7 +546,6 @@ func (c *Config) createCertificate() (string, error) {
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
c.ctx.Funcs = azcommon.TemplateFuncs
err := config.Decode(c, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &c.ctx,
}, raws...)
@@ -597,7 +562,10 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
// copy singular blocks
c.AzureTag.CopyOn(&c.AzureTags)
for _, kv := range c.AzureTag {
v := kv.Value
c.AzureTags[kv.Name] = &v
}
err = c.ClientConfig.SetDefaultValues()
if err != nil {
@@ -805,8 +773,8 @@ func assertTagProperties(c *Config, errs *packer.MultiError) {
if len(k) > 512 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 512 character limit", k, len(k)))
}
if len(v) > 256 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", v, len(v)))
if len(*v) > 256 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", *v, len(*v)))
}
}
}
@@ -1010,9 +978,6 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
if len(c.SharedGalleryDestination.SigDestinationReplicationRegions) == 0 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A list of replication_regions must be specified for shared_image_gallery_destination"))
}
if c.SharedGalleryDestination.SigDestinationSubscription == "" {
c.SharedGalleryDestination.SigDestinationSubscription = c.ClientConfig.SubscriptionID
}
}
if c.SharedGalleryTimeout == 0 {
// default to a one-hour timeout. In the sdk, the default is 15 m.
@@ -1061,13 +1026,13 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("if either plan_name, plan_product, plan_publisher, or plan_promotion_code are defined then plan_name, plan_product, and plan_publisher must be defined"))
} else {
if c.AzureTags == nil {
c.AzureTags = make(map[string]string)
c.AzureTags = make(map[string]*string)
}
c.AzureTags["PlanInfo"] = c.PlanInfo.PlanName
c.AzureTags["PlanProduct"] = c.PlanInfo.PlanProduct
c.AzureTags["PlanPublisher"] = c.PlanInfo.PlanPublisher
c.AzureTags["PlanPromotionCode"] = c.PlanInfo.PlanPromotionCode
c.AzureTags["PlanInfo"] = &c.PlanInfo.PlanName
c.AzureTags["PlanProduct"] = &c.PlanInfo.PlanProduct
c.AzureTags["PlanPublisher"] = &c.PlanInfo.PlanPublisher
c.AzureTags["PlanPromotionCode"] = &c.PlanInfo.PlanPromotionCode
}
}
+1 -9
View File
@@ -25,7 +25,6 @@ type FlatConfig struct {
ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"`
TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"`
SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id" hcl:"subscription_id"`
UseAzureCLIAuth *bool `mapstructure:"use_azure_cli_auth" required:"false" cty:"use_azure_cli_auth" hcl:"use_azure_cli_auth"`
UserAssignedManagedIdentities []string `mapstructure:"user_assigned_managed_identities" required:"false" cty:"user_assigned_managed_identities" hcl:"user_assigned_managed_identities"`
CaptureNamePrefix *string `mapstructure:"capture_name_prefix" cty:"capture_name_prefix" hcl:"capture_name_prefix"`
CaptureContainerName *string `mapstructure:"capture_container_name" cty:"capture_container_name" hcl:"capture_container_name"`
@@ -50,7 +49,7 @@ type FlatConfig struct {
ManagedImageOSDiskSnapshotName *string `mapstructure:"managed_image_os_disk_snapshot_name" required:"false" cty:"managed_image_os_disk_snapshot_name" hcl:"managed_image_os_disk_snapshot_name"`
ManagedImageDataDiskSnapshotPrefix *string `mapstructure:"managed_image_data_disk_snapshot_prefix" required:"false" cty:"managed_image_data_disk_snapshot_prefix" hcl:"managed_image_data_disk_snapshot_prefix"`
ManagedImageZoneResilient *bool `mapstructure:"managed_image_zone_resilient" required:"false" cty:"managed_image_zone_resilient" hcl:"managed_image_zone_resilient"`
AzureTags map[string]string `mapstructure:"azure_tags" required:"false" cty:"azure_tags" hcl:"azure_tags"`
AzureTags map[string]*string `mapstructure:"azure_tags" required:"false" cty:"azure_tags" hcl:"azure_tags"`
AzureTag []hcl2template.FlatNameValue `mapstructure:"azure_tag" required:"false" cty:"azure_tag" hcl:"azure_tag"`
ResourceGroupName *string `mapstructure:"resource_group_name" cty:"resource_group_name" hcl:"resource_group_name"`
StorageAccount *string `mapstructure:"storage_account" cty:"storage_account" hcl:"storage_account"`
@@ -81,8 +80,6 @@ type FlatConfig struct {
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
@@ -152,7 +149,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false},
"tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false},
"subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false},
"use_azure_cli_auth": &hcldec.AttrSpec{Name: "use_azure_cli_auth", Type: cty.Bool, Required: false},
"user_assigned_managed_identities": &hcldec.AttrSpec{Name: "user_assigned_managed_identities", Type: cty.List(cty.String), Required: false},
"capture_name_prefix": &hcldec.AttrSpec{Name: "capture_name_prefix", Type: cty.String, Required: false},
"capture_container_name": &hcldec.AttrSpec{Name: "capture_container_name", Type: cty.String, Required: false},
@@ -208,8 +204,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
@@ -317,7 +311,6 @@ func (*FlatSharedImageGallery) HCL2Spec() map[string]hcldec.Spec {
// FlatSharedImageGalleryDestination is an auto-generated flat version of SharedImageGalleryDestination.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatSharedImageGalleryDestination struct {
SigDestinationSubscription *string `mapstructure:"subscription" cty:"subscription" hcl:"subscription"`
SigDestinationResourceGroup *string `mapstructure:"resource_group" cty:"resource_group" hcl:"resource_group"`
SigDestinationGalleryName *string `mapstructure:"gallery_name" cty:"gallery_name" hcl:"gallery_name"`
SigDestinationImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
@@ -337,7 +330,6 @@ func (*SharedImageGalleryDestination) FlatMapstructure() interface{ HCL2Spec() m
// The decoded values from this spec will then be applied to a FlatSharedImageGalleryDestination.
func (*FlatSharedImageGalleryDestination) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"subscription": &hcldec.AttrSpec{Name: "subscription", Type: cty.String, Required: false},
"resource_group": &hcldec.AttrSpec{Name: "resource_group", Type: cty.String, Required: false},
"gallery_name": &hcldec.AttrSpec{Name: "gallery_name", Type: cty.String, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
+18 -43
View File
@@ -6,9 +6,7 @@ import (
"time"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/packer/builder/azure/common/constants"
"github.com/hashicorp/packer/hcl2template"
)
// List of configuration parameters that are required by the ARM builder.
@@ -912,55 +910,32 @@ func TestConfigShouldAcceptTags(t *testing.T) {
},
}
c := Config{
AzureTag: hcl2template.NameValues{
{Name: "tag03", Value: "value03"},
},
}
var c Config
_, err := c.Prepare(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(c.AzureTags, map[string]string{
"tag01": "value01",
"tag02": "value02",
"tag03": "value03",
}); diff != "" {
t.Fatalf("unexpected azure tags: %s", diff)
}
}
func TestConfigShouldAcceptTag(t *testing.T) {
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
if len(c.AzureTags) != 2 {
t.Fatalf("expected to find 2 tags, but got %d", len(c.AzureTags))
}
c := Config{
AzureTag: hcl2template.NameValues{
{Name: "tag03", Value: "value03"},
},
if _, ok := c.AzureTags["tag01"]; !ok {
t.Error("expected to find key=\"tag01\", but did not")
}
_, err := c.Prepare(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
if _, ok := c.AzureTags["tag02"]; !ok {
t.Error("expected to find key=\"tag02\", but did not")
}
if diff := cmp.Diff(c.AzureTags, map[string]string{
"tag03": "value03",
}); diff != "" {
t.Fatalf("unexpected azure tags: %s", diff)
value := c.AzureTags["tag01"]
if *value != "value01" {
t.Errorf("expected AzureTags[\"tag01\"] to have value \"value01\", but got %q", *value)
}
value = c.AzureTags["tag02"]
if *value != "value02" {
t.Errorf("expected AzureTags[\"tag02\"] to have value \"value02\", but got %q", *value)
}
}
@@ -2072,8 +2047,8 @@ func TestConfig_PrepareProvidedWinRMPassword(t *testing.T) {
}
}
func getArmBuilderConfiguration() map[string]interface{} {
m := make(map[string]interface{})
func getArmBuilderConfiguration() map[string]string {
m := make(map[string]string)
for _, v := range requiredConfigValues {
m[v] = "ignored00"
}
@@ -61,13 +61,7 @@ func (s *StepCreateResourceGroup) Run(ctx context.Context, state multistep.State
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var location = state.Get(constants.ArmLocation).(string)
tags, ok := state.Get(constants.ArmTags).(map[string]*string)
if !ok {
err := fmt.Errorf("failed to extract tags from state bag")
state.Put(constants.Error, err)
s.error(err)
return multistep.ActionHalt
}
var tags = state.Get(constants.ArmTags).(map[string]*string)
exists, err := s.exists(ctx, resourceGroupName)
if err != nil {
@@ -220,32 +220,3 @@ func createTestExistingStateBagStepCreateResourceGroup() multistep.StateBag {
stateBag.Put(constants.ArmTags, tags)
return stateBag
}
func TestStepCreateResourceGroupShouldFailIfTagsFailCast(t *testing.T) {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
value := "Unit Test: Tags"
tags := map[string]string{
"tag01": value,
}
stateBag.Put(constants.ArmTags, tags)
var testSubject = &StepCreateResourceGroup{
create: func(context.Context, string, string, map[string]*string) error { return nil },
say: func(message string) {},
error: func(e error) {},
exists: func(context.Context, string) (bool, error) { return false, nil },
}
var result = testSubject.Run(context.Background(), stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
@@ -61,11 +61,7 @@ func (s *StepDeleteAdditionalDisk) deleteManagedDisk(ctx context.Context, resour
func (s *StepDeleteAdditionalDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
s.say("Deleting the temporary Additional disk ...")
var dataDisks []string
if disks := state.Get(constants.ArmAdditionalDiskVhds); disks != nil {
dataDisks = disks.([]string)
}
var dataDisks = state.Get(constants.ArmAdditionalDiskVhds).([]string)
var isManagedDisk = state.Get(constants.ArmIsManagedImage).(bool)
var isExistingResourceGroup = state.Get(constants.ArmIsExistingResourceGroup).(bool)
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
+81 -82
View File
@@ -6,10 +6,8 @@ import (
"fmt"
"net/url"
"strings"
"time"
"github.com/hashicorp/packer/builder/azure/common/constants"
"github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@@ -17,7 +15,7 @@ import (
type StepDeployTemplate struct {
client *AzureClient
deploy func(ctx context.Context, resourceGroupName string, deploymentName string) error
delete func(ctx context.Context, deploymentName, resourceGroupName string) error
delete func(ctx context.Context, client *AzureClient, resourceType string, resourceName string, resourceGroupName string) error
disk func(ctx context.Context, resourceGroupName string, computeName string) (string, string, error)
deleteDisk func(ctx context.Context, imageType string, imageName string, resourceGroupName string) error
say func(message string)
@@ -38,7 +36,7 @@ func NewStepDeployTemplate(client *AzureClient, ui packer.Ui, config *Config, de
}
step.deploy = step.deployTemplate
step.delete = step.deleteDeploymentResources
step.delete = deleteResource
step.disk = step.getImageDetails
step.deleteDisk = step.deleteImage
return step
@@ -64,9 +62,8 @@ func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
}
}()
// Only clean up if this is an existing resource group that has been verified to exist.
// ArmIsResourceGroupCreated is set in step_create_resource_group to true, when Packer has verified that the resource group exists.
// ArmIsExistingResourceGroup is set to true when build_resource_group is set in the Packer configuration.
//Only clean up if this was an existing resource group and the resource group
//is marked as created
existingResourceGroup := state.Get(constants.ArmIsExistingResourceGroup).(bool)
resourceGroupCreated := state.Get(constants.ArmIsResourceGroupCreated).(bool)
if !existingResourceGroup || !resourceGroupCreated {
@@ -78,12 +75,74 @@ func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
deploymentName := s.name
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
err := s.deleteDeploymentResources(context.TODO(), deploymentName, resourceGroupName)
if err != nil {
s.reportIfError(err, resourceGroupName)
// Get image disk details before deleting the image; otherwise we won't be able to
// delete the disk as the image request will return a 404
computeName := state.Get(constants.ArmComputeName).(string)
imageType, imageName, err := s.disk(context.TODO(), resourceGroupName, computeName)
if err != nil && !strings.Contains(err.Error(), "ResourceNotFound") {
ui.Error(fmt.Sprintf("Could not retrieve OS Image details: %s", err))
}
NewStepDeleteAdditionalDisks(s.client, ui).Run(context.TODO(), state)
ui.Say(" -> Deployment Resources within: " + deploymentName)
if deploymentName != "" {
maxResources := int32(50)
deploymentOperations, err := s.client.DeploymentOperationsClient.ListComplete(context.TODO(), resourceGroupName, deploymentName, &maxResources)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting resources. Please delete them manually.\n\n"+
"Name: %s\n"+
"Error: %s", resourceGroupName, err))
}
for deploymentOperations.NotDone() {
deploymentOperation := deploymentOperations.Value()
// Sometimes an empty operation is added to the list by Azure
if deploymentOperation.Properties.TargetResource == nil {
if err := deploymentOperations.Next(); err != nil {
ui.Error(fmt.Sprintf("Error moving to to next deployment operation ...\n\n"+
"Name: %s\n"+
"Error: %s", resourceGroupName, err))
break
}
continue
}
ui.Say(fmt.Sprintf(" -> %s : '%s'",
*deploymentOperation.Properties.TargetResource.ResourceType,
*deploymentOperation.Properties.TargetResource.ResourceName))
err = s.delete(context.TODO(), s.client,
*deploymentOperation.Properties.TargetResource.ResourceType,
*deploymentOperation.Properties.TargetResource.ResourceName,
resourceGroupName)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
"Name: %s\n"+
"Error: %s", *deploymentOperation.Properties.TargetResource.ResourceName, err))
}
if err = deploymentOperations.Next(); err != nil {
ui.Error(fmt.Sprintf("Error deleting resources. Please delete them manually.\n\n"+
"Name: %s\n"+
"Error: %s", resourceGroupName, err))
break
}
}
// The disk is not defined as an operation in the template so it has to be deleted separately
if imageType == "" && imageName == "" {
return
}
ui.Say(fmt.Sprintf(" -> %s : '%s'", imageType, imageName))
err = s.deleteDisk(context.TODO(), imageType, imageName, resourceGroupName)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
"Name: %s\n"+
"Error: %s", imageName, err))
}
}
}
func (s *StepDeployTemplate) deployTemplate(ctx context.Context, resourceGroupName string, deploymentName string) error {
@@ -93,31 +152,27 @@ func (s *StepDeployTemplate) deployTemplate(ctx context.Context, resourceGroupNa
}
f, err := s.client.DeploymentsClient.CreateOrUpdate(ctx, resourceGroupName, deploymentName, *deployment)
if err == nil {
err = f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
}
if err != nil {
s.say(s.client.LastError.Error())
return err
}
err = f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
if err == nil {
s.say(s.client.LastError.Error())
}
return err
}
func (s *StepDeployTemplate) deleteTemplate(ctx context.Context, state multistep.StateBag) error {
deploymentName := s.name
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var deploymentName = s.name
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Removing the created Deployment object: '%s'", deploymentName))
f, err := s.client.DeploymentsClient.Delete(ctx, resourceGroupName, deploymentName)
if err != nil {
return err
if err == nil {
err = f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
}
return f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
return err
}
func (s *StepDeployTemplate) getImageDetails(ctx context.Context, resourceGroupName string, computeName string) (string, string, error) {
@@ -131,12 +186,11 @@ func (s *StepDeployTemplate) getImageDetails(ctx context.Context, resourceGroupN
if vm.StorageProfile.OsDisk.Vhd != nil {
imageType = "image"
imageName = *vm.StorageProfile.OsDisk.Vhd.URI
return imageType, imageName, nil
} else {
imageType = "Microsoft.Compute/disks"
imageName = *vm.StorageProfile.OsDisk.ManagedDisk.ID
}
imageType = "Microsoft.Compute/disks"
imageName = *vm.StorageProfile.OsDisk.ManagedDisk.ID
return imageType, imageName, nil
}
@@ -213,58 +267,3 @@ func (s *StepDeployTemplate) deleteImage(ctx context.Context, imageType string,
return blob.Delete(nil)
}
func (s *StepDeployTemplate) deleteDeploymentResources(ctx context.Context, deploymentName, resourceGroupName string) error {
var maxResources int32 = 50
deploymentOperations, err := s.client.DeploymentOperationsClient.ListComplete(ctx, resourceGroupName, deploymentName, &maxResources)
if err != nil {
s.reportIfError(err, resourceGroupName)
return err
}
for deploymentOperations.NotDone() {
deploymentOperation := deploymentOperations.Value()
// Sometimes an empty operation is added to the list by Azure
if deploymentOperation.Properties.TargetResource == nil {
_ = deploymentOperations.Next()
continue
}
resourceName := *deploymentOperation.Properties.TargetResource.ResourceName
resourceType := *deploymentOperation.Properties.TargetResource.ResourceType
s.say(fmt.Sprintf(" -> %s : '%s'", resourceType, resourceName))
err = retry.Config{
Tries: 10,
RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 600 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
err := deleteResource(ctx, s.client,
resourceType,
resourceName,
resourceGroupName)
if err != nil {
s.reportIfError(err, resourceName)
}
return nil
})
if err != nil {
s.reportIfError(err, resourceName)
}
if err = deploymentOperations.Next(); err != nil {
return err
}
}
return nil
}
func (s *StepDeployTemplate) reportIfError(err error, resourceName string) {
if err != nil {
s.say(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
"Name: %s\n"+
"Error: %s", resourceName, err.Error()))
s.error(err)
}
}
@@ -38,7 +38,6 @@
"tenantId": "[parameters('tenantId')]"
}
],
"enableSoftDelete": "true",
"enabledForDeployment": "true",
"enabledForTemplateDeployment": "true",
"sku": {
-1
View File
@@ -153,7 +153,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
b.config.ctx.Funcs["vm"] = CreateVMMetadataTemplateFunc()
md := &mapstructure.Metadata{}
err := config.Decode(&b.config, &config.DecodeOpts{
PluginType: BuilderID,
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
-2
View File
@@ -24,7 +24,6 @@ type FlatConfig struct {
ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"`
TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"`
SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id" hcl:"subscription_id"`
UseAzureCLIAuth *bool `mapstructure:"use_azure_cli_auth" required:"false" cty:"use_azure_cli_auth" hcl:"use_azure_cli_auth"`
FromScratch *bool `mapstructure:"from_scratch" cty:"from_scratch" hcl:"from_scratch"`
Source *string `mapstructure:"source" required:"true" cty:"source" hcl:"source"`
CommandWrapper *string `mapstructure:"command_wrapper" cty:"command_wrapper" hcl:"command_wrapper"`
@@ -77,7 +76,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false},
"tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false},
"subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false},
"use_azure_cli_auth": &hcldec.AttrSpec{Name: "use_azure_cli_auth", Type: cty.Bool, Required: false},
"from_scratch": &hcldec.AttrSpec{Name: "from_scratch", Type: cty.Bool, Required: false},
"source": &hcldec.AttrSpec{Name: "source", Type: cty.String, Required: false},
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
@@ -16,9 +16,8 @@ type SharedImageGalleryDestination struct {
ImageName string `mapstructure:"image_name" required:"true"`
ImageVersion string `mapstructure:"image_version" required:"true"`
TargetRegions []TargetRegion `mapstructure:"target_regions"`
ExcludeFromLatest bool `mapstructure:"exclude_from_latest"`
ExcludeFromLatestTypo bool `mapstructure:"exlude_from_latest" undocumented:"true"`
TargetRegions []TargetRegion `mapstructure:"target_regions"`
ExcludeFromLatest bool `mapstructure:"exlude_from_latest"`
}
// TargetRegion describes a region where the shared image should be replicated
@@ -62,10 +61,5 @@ func (sigd *SharedImageGalleryDestination) Validate(prefix string) (errs []error
warns = append(warns,
fmt.Sprintf("%s.target_regions is empty; image will only be available in the region of the gallery", prefix))
}
if sigd.ExcludeFromLatestTypo == true && sigd.ExcludeFromLatest == false {
warns = append(warns,
fmt.Sprintf("%s.exlude_from_latest is being deprecated, please use exclude_from_latest", prefix))
sigd.ExcludeFromLatest = sigd.ExcludeFromLatestTypo
}
return
}
@@ -9,13 +9,12 @@ import (
// FlatSharedImageGalleryDestination is an auto-generated flat version of SharedImageGalleryDestination.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatSharedImageGalleryDestination struct {
ResourceGroup *string `mapstructure:"resource_group" required:"true" cty:"resource_group" hcl:"resource_group"`
GalleryName *string `mapstructure:"gallery_name" required:"true" cty:"gallery_name" hcl:"gallery_name"`
ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"`
ImageVersion *string `mapstructure:"image_version" required:"true" cty:"image_version" hcl:"image_version"`
TargetRegions []FlatTargetRegion `mapstructure:"target_regions" cty:"target_regions" hcl:"target_regions"`
ExcludeFromLatest *bool `mapstructure:"exclude_from_latest" cty:"exclude_from_latest" hcl:"exclude_from_latest"`
ExcludeFromLatestTypo *bool `mapstructure:"exlude_from_latest" undocumented:"true" cty:"exlude_from_latest" hcl:"exlude_from_latest"`
ResourceGroup *string `mapstructure:"resource_group" required:"true" cty:"resource_group" hcl:"resource_group"`
GalleryName *string `mapstructure:"gallery_name" required:"true" cty:"gallery_name" hcl:"gallery_name"`
ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"`
ImageVersion *string `mapstructure:"image_version" required:"true" cty:"image_version" hcl:"image_version"`
TargetRegions []FlatTargetRegion `mapstructure:"target_regions" cty:"target_regions" hcl:"target_regions"`
ExcludeFromLatest *bool `mapstructure:"exlude_from_latest" cty:"exlude_from_latest" hcl:"exlude_from_latest"`
}
// FlatMapstructure returns a new FlatSharedImageGalleryDestination.
@@ -30,13 +29,12 @@ func (*SharedImageGalleryDestination) FlatMapstructure() interface{ HCL2Spec() m
// The decoded values from this spec will then be applied to a FlatSharedImageGalleryDestination.
func (*FlatSharedImageGalleryDestination) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"resource_group": &hcldec.AttrSpec{Name: "resource_group", Type: cty.String, Required: false},
"gallery_name": &hcldec.AttrSpec{Name: "gallery_name", Type: cty.String, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
"target_regions": &hcldec.BlockListSpec{TypeName: "target_regions", Nested: hcldec.ObjectSpec((*FlatTargetRegion)(nil).HCL2Spec())},
"exclude_from_latest": &hcldec.AttrSpec{Name: "exclude_from_latest", Type: cty.Bool, Required: false},
"exlude_from_latest": &hcldec.AttrSpec{Name: "exlude_from_latest", Type: cty.Bool, Required: false},
"resource_group": &hcldec.AttrSpec{Name: "resource_group", Type: cty.String, Required: false},
"gallery_name": &hcldec.AttrSpec{Name: "gallery_name", Type: cty.String, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
"target_regions": &hcldec.BlockListSpec{TypeName: "target_regions", Nested: hcldec.ObjectSpec((*FlatTargetRegion)(nil).HCL2Spec())},
"exlude_from_latest": &hcldec.AttrSpec{Name: "exlude_from_latest", Type: cty.Bool, Required: false},
}
return s
}
@@ -21,13 +21,12 @@ func TestSharedImageGalleryDestination_ResourceID(t *testing.T) {
func TestSharedImageGalleryDestination_Validate(t *testing.T) {
type fields struct {
ResourceGroup string
GalleryName string
ImageName string
ImageVersion string
TargetRegions []TargetRegion
ExcludeFromLatest bool
ExcludeFromLatestTypo bool
ResourceGroup string
GalleryName string
ImageName string
ImageVersion string
TargetRegions []TargetRegion
ExcludeFromLatest bool
}
tests := []struct {
name string
@@ -67,29 +66,6 @@ func TestSharedImageGalleryDestination_Validate(t *testing.T) {
},
wantWarns: []string{"sigdest.target_regions is empty; image will only be available in the region of the gallery"},
},
{
name: "warn if using exlude_from_latest",
fields: fields{
ResourceGroup: "ResourceGroup",
GalleryName: "GalleryName",
ImageName: "ImageName",
ImageVersion: "0.1.2",
TargetRegions: []TargetRegion{
TargetRegion{
Name: "region1",
ReplicaCount: 5,
StorageAccountType: "Standard_ZRS",
},
TargetRegion{
Name: "region2",
ReplicaCount: 3,
StorageAccountType: "Standard_LRS",
},
},
ExcludeFromLatestTypo: true,
},
wantWarns: []string{"sigdest.exlude_from_latest is being deprecated, please use exclude_from_latest"},
},
{
name: "version format",
wantErrs: []string{
@@ -129,13 +105,12 @@ func TestSharedImageGalleryDestination_Validate(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sigd := &SharedImageGalleryDestination{
ResourceGroup: tt.fields.ResourceGroup,
GalleryName: tt.fields.GalleryName,
ImageName: tt.fields.ImageName,
ImageVersion: tt.fields.ImageVersion,
TargetRegions: tt.fields.TargetRegions,
ExcludeFromLatest: tt.fields.ExcludeFromLatest,
ExcludeFromLatestTypo: tt.fields.ExcludeFromLatestTypo,
ResourceGroup: tt.fields.ResourceGroup,
GalleryName: tt.fields.GalleryName,
ImageName: tt.fields.ImageName,
ImageVersion: tt.fields.ImageVersion,
TargetRegions: tt.fields.TargetRegions,
ExcludeFromLatest: tt.fields.ExcludeFromLatest,
}
gotErrs, gotWarns := sigd.Validate("sigdest")
-32
View File
@@ -54,14 +54,6 @@ type Config struct {
SubscriptionID string `mapstructure:"subscription_id"`
authType string
// Flag to use Azure CLI authentication. Defaults to false.
// CLI auth will use the information from an active `az login` session to connect to Azure and set the subscription id and tenant id associated to the signed in account.
// If enabled, it will use the authentication provided by the `az` CLI.
// Azure CLI authentication will use the credential marked as `isDefault` and can be verified using `az account show`.
// Works with normal authentication (`az login`) and service principals (`az login --service-principal --username APP_ID --password PASSWORD --tenant TENANT_ID`).
// Ignores all other configurations if enabled.
UseAzureCLIAuth bool `mapstructure:"use_azure_cli_auth" required:"false"`
}
const (
@@ -70,7 +62,6 @@ const (
authTypeClientSecret = "ClientSecret"
authTypeClientCert = "ClientCertificate"
authTypeClientBearerJWT = "ClientBearerJWT"
authTypeAzureCLI = "AzureCLI"
)
const DefaultCloudEnvironmentName = "Public"
@@ -133,10 +124,6 @@ func (c Config) Validate(errs *packer.MultiError) {
// readable by the ObjectID of the App. There may be another way to handle
// this case, but I am not currently aware of it - send feedback.
if c.UseCLI() {
return
}
if c.UseMSI() {
return
}
@@ -206,10 +193,6 @@ func (c Config) useDeviceLogin() bool {
c.ClientCertPath == ""
}
func (c Config) UseCLI() bool {
return c.UseAzureCLIAuth == true
}
func (c Config) UseMSI() bool {
return c.SubscriptionID == "" &&
c.ClientID == "" &&
@@ -247,9 +230,6 @@ func (c Config) GetServicePrincipalToken(
case authTypeDeviceLogin:
say("Getting tokens using device flow")
auth = NewDeviceFlowOAuthTokenProvider(*c.cloudEnvironment, say, c.TenantID)
case authTypeAzureCLI:
say("Getting tokens using Azure CLI")
auth = NewCliOAuthTokenProvider(*c.cloudEnvironment, say, c.TenantID)
case authTypeMSI:
say("Getting tokens using Managed Identity for Azure")
auth = NewMSIOAuthTokenProvider(*c.cloudEnvironment)
@@ -288,8 +268,6 @@ func (c *Config) FillParameters() error {
if c.authType == "" {
if c.useDeviceLogin() {
c.authType = authTypeDeviceLogin
} else if c.UseCLI() {
c.authType = authTypeAzureCLI
} else if c.UseMSI() {
c.authType = authTypeMSI
} else if c.ClientSecret != "" {
@@ -317,16 +295,6 @@ func (c *Config) FillParameters() error {
}
}
if c.authType == authTypeAzureCLI {
tenantID, subscriptionID, err := getIDsFromAzureCLI()
if err != nil {
return fmt.Errorf("error fetching tenantID and subscriptionID from Azure CLI (are you logged on using `az login`?): %v", err)
}
c.TenantID = tenantID
c.SubscriptionID = subscriptionID
}
if c.TenantID == "" {
tenantID, err := findTenantID(*c.cloudEnvironment, c.SubscriptionID)
if err != nil {
@@ -29,13 +29,6 @@ func Test_ClientConfig_RequiredParametersSet(t *testing.T) {
config: Config{},
wantErr: false,
},
{
name: "use_azure_cli_auth will trigger Azure CLI auth",
config: Config{
UseAzureCLIAuth: true,
},
wantErr: false,
},
{
name: "subscription_id is set will trigger device flow",
config: Config{
@@ -165,26 +158,6 @@ func Test_ClientConfig_DeviceLogin(t *testing.T) {
}
}
func Test_ClientConfig_AzureCli(t *testing.T) {
// Azure CLI tests skipped unless env 'AZURE_CLI_AUTH' is set, and an active `az login` session has been established
getEnvOrSkip(t, "AZURE_CLI_AUTH")
cfg := Config{
UseAzureCLIAuth: true,
cloudEnvironment: getCloud(),
}
assertValid(t, cfg)
err := cfg.FillParameters()
if err != nil {
t.Fatalf("Expected nil err, but got: %v", err)
}
if cfg.authType != authTypeAzureCLI {
t.Fatalf("Expected authType to be %q, but got: %q", authTypeAzureCLI, cfg.authType)
}
}
func Test_ClientConfig_ClientPassword(t *testing.T) {
cfg := Config{
SubscriptionID: getEnvOrSkip(t, "AZURE_SUBSCRIPTION"),
@@ -1,99 +0,0 @@
package client
import (
"context"
"errors"
"fmt"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/azure/cli"
)
// for managed identity auth
type cliOAuthTokenProvider struct {
env azure.Environment
say func(string)
tenantID string
}
func NewCliOAuthTokenProvider(env azure.Environment, say func(string), tenantID string) oAuthTokenProvider {
return &cliOAuthTokenProvider{
env: env,
say: say,
tenantID: tenantID,
}
}
func (tp *cliOAuthTokenProvider) getServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
return tp.getServicePrincipalTokenWithResource(tp.env.ResourceManagerEndpoint)
}
func (tp *cliOAuthTokenProvider) getServicePrincipalTokenWithResource(resource string) (*adal.ServicePrincipalToken, error) {
token, err := cli.GetTokenFromCLI(resource)
if err != nil {
tp.say(fmt.Sprintf("unable to get token from azure cli: %v", err))
return nil, err
}
oAuthConfig, err := adal.NewOAuthConfig(resource, tp.tenantID)
if err != nil {
tp.say(fmt.Sprintf("unable to generate OAuth Config: %v", err))
return nil, err
}
adalToken, err := token.ToADALToken()
if err != nil {
tp.say(fmt.Sprintf("unable to get ADAL Token from azure cli token: %v", err))
return nil, err
}
spt, err := adal.NewServicePrincipalTokenFromManualToken(*oAuthConfig, clientIDs[tp.env.Name], resource, adalToken)
if err != nil {
tp.say(fmt.Sprintf("unable to get service principal token from adal token: %v", err))
return nil, err
}
// Custom refresh function to make it possible to use Azure CLI to refresh tokens.
// Inspired by HashiCorps go-azure-helpers: https://github.com/hashicorp/go-azure-helpers/blob/373622ce2effb0cf299051ea019cb657f357a4d8/authentication/auth_method_azure_cli_token.go#L96-L109
var customRefreshFunc adal.TokenRefresh = func(ctx context.Context, resource string) (*adal.Token, error) {
token, err := cli.GetTokenFromCLI(resource)
if err != nil {
tp.say(fmt.Sprintf("token refresh - unable to get token from azure cli: %v", err))
return nil, err
}
adalToken, err := token.ToADALToken()
if err != nil {
tp.say(fmt.Sprintf("token refresh - unable to get ADAL Token from azure cli token: %v", err))
return nil, err
}
return &adalToken, nil
}
spt.SetCustomRefreshFunc(customRefreshFunc)
return spt, nil
}
// getIDsFromAzureCLI returns the TenantID and SubscriptionID from an active Azure CLI login session
func getIDsFromAzureCLI() (string, string, error) {
profilePath, err := cli.ProfilePath()
if err != nil {
return "", "", err
}
profile, err := cli.LoadProfile(profilePath)
if err != nil {
return "", "", err
}
for _, p := range profile.Subscriptions {
if p.IsDefault {
return p.TenantID, p.ID, nil
}
}
return "", "", errors.New("Unable to find default subscription")
}
-10
View File
@@ -1,10 +0,0 @@
package common
func MapToAzureTags(in map[string]string) map[string]*string {
res := map[string]*string{}
for k := range in {
v := in[k]
res[k] = &v
}
return res
}
+10 -11
View File
@@ -25,16 +25,16 @@ type Parameters struct {
/////////////////////////////////////////////////
// Template > Resource
type Resource struct {
ApiVersion *string `json:"apiVersion"`
Name *string `json:"name"`
Type *string `json:"type"`
Location *string `json:"location,omitempty"`
DependsOn *[]string `json:"dependsOn,omitempty"`
Plan *Plan `json:"plan,omitempty"`
Properties *Properties `json:"properties,omitempty"`
Tags *map[string]string `json:"tags,omitempty"`
Resources *[]Resource `json:"resources,omitempty"`
Identity *Identity `json:"identity,omitempty"`
ApiVersion *string `json:"apiVersion"`
Name *string `json:"name"`
Type *string `json:"type"`
Location *string `json:"location,omitempty"`
DependsOn *[]string `json:"dependsOn,omitempty"`
Plan *Plan `json:"plan,omitempty"`
Properties *Properties `json:"properties,omitempty"`
Tags *map[string]*string `json:"tags,omitempty"`
Resources *[]Resource `json:"resources,omitempty"`
Identity *Identity `json:"identity,omitempty"`
}
type Plan struct {
@@ -85,7 +85,6 @@ type Properties struct {
DNSSettings *network.PublicIPAddressDNSSettings `json:"dnsSettings,omitempty"`
EnabledForDeployment *string `json:"enabledForDeployment,omitempty"`
EnabledForTemplateDeployment *string `json:"enabledForTemplateDeployment,omitempty"`
EnableSoftDelete *string `json:"enableSoftDelete,omitempty"`
HardwareProfile *compute.HardwareProfile `json:"hardwareProfile,omitempty"`
IPConfigurations *[]network.IPConfiguration `json:"ipConfigurations,omitempty"`
NetworkProfile *compute.NetworkProfile `json:"networkProfile,omitempty"`
@@ -374,7 +374,7 @@ func (s *TemplateBuilder) SetNetworkSecurityGroup(ipAddresses []string, port int
return nil
}
func (s *TemplateBuilder) SetTags(tags *map[string]string) error {
func (s *TemplateBuilder) SetTags(tags *map[string]*string) error {
if tags == nil || len(*tags) == 0 {
return nil
}
@@ -554,7 +554,6 @@ const KeyVault = `{
"properties": {
"enabledForDeployment": "true",
"enabledForTemplateDeployment": "true",
"enableSoftDelete": "true",
"tenantId": "[parameters('tenantId')]",
"accessPolicies": [
{
-1
View File
@@ -389,7 +389,6 @@ func newConfig(raws ...interface{}) (*Config, []string, error) {
var c Config
c.ctx.Funcs = TemplateFuncs
err := config.Decode(&c, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &c.ctx,
}, raws...)
-6
View File
@@ -51,7 +51,6 @@ type FlatConfig struct {
ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"`
TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"`
SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id" hcl:"subscription_id"`
UseAzureCLIAuth *bool `mapstructure:"use_azure_cli_auth" required:"false" cty:"use_azure_cli_auth" hcl:"use_azure_cli_auth"`
CaptureNamePrefix *string `mapstructure:"capture_name_prefix" cty:"capture_name_prefix" hcl:"capture_name_prefix"`
CaptureContainerName *string `mapstructure:"capture_container_name" cty:"capture_container_name" hcl:"capture_container_name"`
SharedGallery *FlatSharedImageGallery `mapstructure:"shared_image_gallery" cty:"shared_image_gallery" hcl:"shared_image_gallery"`
@@ -94,8 +93,6 @@ type FlatConfig struct {
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
@@ -164,7 +161,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false},
"tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false},
"subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false},
"use_azure_cli_auth": &hcldec.AttrSpec{Name: "use_azure_cli_auth", Type: cty.Bool, Required: false},
"capture_name_prefix": &hcldec.AttrSpec{Name: "capture_name_prefix", Type: cty.String, Required: false},
"capture_container_name": &hcldec.AttrSpec{Name: "capture_container_name", Type: cty.String, Required: false},
"shared_image_gallery": &hcldec.BlockSpec{TypeName: "shared_image_gallery", Nested: hcldec.ObjectSpec((*FlatSharedImageGallery)(nil).HCL2Spec())},
@@ -207,8 +203,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
-41
View File
@@ -1,41 +0,0 @@
{
"variables": {
"client_id": "{{env `ARM_CLIENT_ID`}}",
"client_secret": "{{env `ARM_CLIENT_SECRET`}}",
"subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}",
"resource_group": "{{env `ARM_IMAGE_RESOURCEGROUP_ID`}}",
"gallery_name": "{{env `ARM_GALLERY_NAME`}}"
},
"builders": [{
"type": "azure-chroot",
"client_id": "{{user `client_id`}}",
"client_secret": "{{user `client_secret`}}",
"subscription_id": "{{user `subscription_id`}}",
"source": "Canonical:UbuntuServer:20.04-LTS:latest",
"shared_image_destination": {
"resource_group": "{{user `resource_group`}}",
"gallery_name": "{{user `gallery_name`}}",
"image_name": "MyUbuntuOSImage",
"image_version": "1.0.0",
"exclude_from_latest": false,
"target_regions": [
{
"name": "eastus",
"replicas": "1",
"storage_account_type": "standard_zrs"
}
]
}
}],
"provisioners": [{
"inline": [
"apt update",
"apt upgrade -y"
],
"inline_shebang": "/bin/sh -x",
"type": "shell"
}]
}
-6
View File
@@ -20,7 +20,6 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
@@ -29,8 +28,6 @@ type FlatConfig struct {
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
@@ -135,7 +132,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
@@ -144,8 +140,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
-4
View File
@@ -24,8 +24,6 @@ type FlatConfig struct {
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
@@ -110,8 +108,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
+11 -14
View File
@@ -30,22 +30,19 @@ func (a *ImportArtifact) Id() string {
}
func (a *ImportArtifact) String() string {
var tags []string
switch t := a.StateData["docker_tags"].(type) {
case []string:
tags = t
case []interface{}:
for _, name := range t {
if n, ok := name.(string); ok {
tags = append(tags, n)
}
tags := a.StateData["docker_tags"]
if tags == nil {
return fmt.Sprintf("Imported Docker image: %s", a.Id())
}
cast := tags.([]interface{})
names := []string{}
for _, name := range cast {
if n, ok := name.(string); ok {
names = append(names, n)
}
}
if len(tags) > 0 {
return fmt.Sprintf("Imported Docker image: %s with tags %s",
a.Id(), strings.Join(tags, " "))
}
return fmt.Sprintf("Imported Docker image: %s", a.Id())
return fmt.Sprintf("Imported Docker image: %s with tags %s",
a.Id(), strings.Join(names, " "))
}
func (a *ImportArtifact) State(name string) interface{} {
+11 -19
View File
@@ -6,7 +6,6 @@ import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
@@ -30,9 +29,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
return nil, warnings, errs
}
return []string{
"ImageSha256",
}, warnings, nil
return nil, warnings, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
@@ -47,16 +44,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
}
log.Printf("[DEBUG] Docker version: %s", version.String())
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("hook", hook)
state.Put("ui", ui)
generatedData := &packerbuilderdata.GeneratedData{State: state}
// Setup the driver that will talk to Docker
state.Put("driver", driver)
steps := []multistep.Step{
&StepTempDir{},
&StepPull{},
@@ -80,11 +67,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
log.Print("[DEBUG] Container will be discarded")
} else if b.config.Commit {
log.Print("[DEBUG] Container will be committed")
steps = append(steps,
new(StepCommit),
&StepSetGeneratedData{ // Adds ImageSha256 variable available after StepCommit
GeneratedData: generatedData,
})
steps = append(steps, new(StepCommit))
} else if b.config.ExportPath != "" {
log.Printf("[DEBUG] Container will be exported to %s", b.config.ExportPath)
steps = append(steps, new(StepExport))
@@ -92,6 +75,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
return nil, errArtifactNotUsed
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("hook", hook)
state.Put("ui", ui)
// Setup the driver that will talk to Docker
state.Put("driver", driver)
// Run!
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
-1
View File
@@ -127,7 +127,6 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
var md mapstructure.Metadata
err := config.Decode(c, &config.DecodeOpts{
Metadata: &md,
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateFilter: &interpolate.RenderFilter{
-4
View File
@@ -24,8 +24,6 @@ type FlatConfig struct {
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
@@ -123,8 +121,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
-3
View File
@@ -26,9 +26,6 @@ type Driver interface {
// for external access.
IPAddress(id string) (string, error)
// Sha256 returns the sha256 id of the image
Sha256(id string) (string, error)
// Login. This will lock the driver from performing another Login
// until Logout is called. Therefore, any users MUST call Logout.
Login(repo, username, password string) error
-17
View File
@@ -159,23 +159,6 @@ func (d *DockerDriver) IPAddress(id string) (string, error) {
return strings.TrimSpace(stdout.String()), nil
}
func (d *DockerDriver) Sha256(id string) (string, error) {
var stderr, stdout bytes.Buffer
cmd := exec.Command(
"docker",
"inspect",
"--format",
"{{ .Id }}",
id)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("Error: %s\n\nStderr: %s", err, stderr.String())
}
return strings.TrimSpace(stdout.String()), nil
}
func (d *DockerDriver) Login(repo, user, pass string) error {
d.l.Lock()
-11
View File
@@ -28,11 +28,6 @@ type MockDriver struct {
IPAddressResult string
IPAddressErr error
Sha256Called bool
Sha256Id string
Sha256Result string
Sha256Err error
KillCalled bool
KillID string
KillError error
@@ -123,12 +118,6 @@ func (d *MockDriver) IPAddress(id string) (string, error) {
return d.IPAddressResult, d.IPAddressErr
}
func (d *MockDriver) Sha256(id string) (string, error) {
d.Sha256Called = true
d.Sha256Id = id
return d.Sha256Result, d.Sha256Err
}
func (d *MockDriver) Login(r, u, p string) error {
d.LoginCalled = true
d.LoginRepo = r
+93 -5
View File
@@ -1,26 +1,114 @@
package docker
import (
"fmt"
"io"
"log"
"os/exec"
"regexp"
"strings"
"sync"
"syscall"
"github.com/hashicorp/packer/common/shell-local/localexec"
"github.com/hashicorp/packer/common/iochan"
"github.com/hashicorp/packer/packer"
)
func runAndStream(cmd *exec.Cmd, ui packer.Ui) error {
stdout_r, stdout_w := io.Pipe()
stderr_r, stderr_w := io.Pipe()
defer stdout_w.Close()
defer stderr_w.Close()
args := make([]string, len(cmd.Args)-1)
copy(args, cmd.Args[1:])
// Scrub password from the log output.
capturedPassword := ""
for i, v := range args {
if v == "-p" || v == "--password" {
capturedPassword = args[i+1]
args[i+1] = "<Filtered>"
break
}
}
// run local command and stream output to UI.
return localexec.RunAndStream(cmd, ui, []string{capturedPassword})
log.Printf("Executing: %s %v", cmd.Path, args)
cmd.Stdout = stdout_w
cmd.Stderr = stderr_w
if err := cmd.Start(); err != nil {
return err
}
// Create the channels we'll use for data
exitCh := make(chan int, 1)
stdoutCh := iochan.LineReader(stdout_r)
stderrCh := iochan.LineReader(stderr_r)
// Start the goroutine to watch for the exit
go func() {
defer stdout_w.Close()
defer stderr_w.Close()
exitStatus := 0
err := cmd.Wait()
if exitErr, ok := err.(*exec.ExitError); ok {
exitStatus = 1
// There is no process-independent way to get the REAL
// exit status so we just try to go deeper.
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
}
}
exitCh <- exitStatus
}()
// This waitgroup waits for the streaming to end
var streamWg sync.WaitGroup
streamWg.Add(2)
streamFunc := func(ch <-chan string) {
defer streamWg.Done()
for data := range ch {
data = cleanOutputLine(data)
if data != "" {
ui.Message(data)
}
}
}
// Stream stderr/stdout
go streamFunc(stderrCh)
go streamFunc(stdoutCh)
// Wait for the process to end and then wait for the streaming to end
exitStatus := <-exitCh
streamWg.Wait()
if exitStatus != 0 {
return fmt.Errorf("Bad exit status: %d", exitStatus)
}
return nil
}
// cleanOutputLine cleans up a line so that '\r' don't muck up the
// UI output when we're reading from a remote command.
func cleanOutputLine(line string) string {
// Build a regular expression that will get rid of shell codes
re := regexp.MustCompile("(?i)\x1b\\[([0-9]{1,2}(;[0-9]{1,2})?)?[a|b|m|k]")
line = re.ReplaceAllString(line, "")
// Trim surrounding whitespace
line = strings.TrimSpace(line)
// Trim up to the first carriage return, since that text would be
// lost anyways.
idx := strings.LastIndex(line, "\r")
if idx > -1 {
line = line[idx+1:]
}
return line
}
@@ -1,4 +1,4 @@
package localexec
package docker
import (
"testing"
-30
View File
@@ -1,30 +0,0 @@
package docker
import (
"context"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/helper/multistep"
)
type StepSetGeneratedData struct {
GeneratedData *packerbuilderdata.GeneratedData
}
func (s *StepSetGeneratedData) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
sha256 := "ERR_IMAGE_SHA256_NOT_FOUND"
if imageId, ok := state.GetOk("image_id"); ok {
s256, err := driver.Sha256(imageId.(string))
if err == nil {
sha256 = s256
}
}
s.GeneratedData.Put("ImageSha256", sha256)
return multistep.ActionContinue
}
func (s *StepSetGeneratedData) Cleanup(_ multistep.StateBag) {
// No cleanup...
}
@@ -1,51 +0,0 @@
package docker
import (
"context"
"testing"
"github.com/hashicorp/packer/common/packerbuilderdata"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepSetGeneratedData_Run(t *testing.T) {
state := testState(t)
step := new(StepSetGeneratedData)
step.GeneratedData = &packerbuilderdata.GeneratedData{State: state}
driver := state.Get("driver").(*MockDriver)
driver.Sha256Result = "80B3BB1B1696E73A9B19DEEF92F664F8979F948DF348088B61F9A3477655AF64"
state.Put("image_id", "12345")
if action := step.Run(context.TODO(), state); action != multistep.ActionContinue {
t.Fatalf("Should not halt")
}
if !driver.Sha256Called {
t.Fatalf("driver.SHA256 should be called")
}
if driver.Sha256Id != "12345" {
t.Fatalf("driver.SHA256 got wrong image it: %s", driver.Sha256Id)
}
genData := state.Get("generated_data").(map[string]interface{})
imgSha256 := genData["ImageSha256"].(string)
if imgSha256 != driver.Sha256Result {
t.Fatalf("Expected ImageSha256 to be %s but was %s", driver.Sha256Result, imgSha256)
}
// Image ID not implement
state = testState(t)
step.GeneratedData = &packerbuilderdata.GeneratedData{State: state}
driver = state.Get("driver").(*MockDriver)
notImplementedMsg := "ERR_IMAGE_SHA256_NOT_FOUND"
if action := step.Run(context.TODO(), state); action != multistep.ActionContinue {
t.Fatalf("Should not halt")
}
if driver.Sha256Called {
t.Fatalf("driver.SHA256 should not be called")
}
genData = state.Get("generated_data").(map[string]interface{})
imgSha256 = genData["ImageSha256"].(string)
if imgSha256 != notImplementedMsg {
t.Fatalf("Expected ImageSha256 to be %s but was %s", notImplementedMsg, imgSha256)
}
}
@@ -1,12 +1,7 @@
package packerbuilderdata
package builder
import "github.com/hashicorp/packer/helper/multistep"
// This is used in the BasicPlaceholderData() func in the packer/provisioner.go
// To force users to access generated data via the "generated" func.
const PlaceholderMsg = "To set this dynamically in the Packer template, " +
"you must use the `build` function"
// GeneratedData manages variables exported by a builder after
// it started. It uses the builder's multistep.StateBag internally, make sure it
// is not nil before calling any functions.
@@ -1,4 +1,4 @@
package packerbuilderdata
package builder
import (
"testing"
+19 -32
View File
@@ -9,39 +9,26 @@ import (
"golang.org/x/oauth2/jwt"
)
type ServiceAccount struct {
jsonKey []byte
jwt *jwt.Config
}
// ProcessAccountFile will return a ServiceAccount for the JSON account file stored in text.
// Otherwise it will return an error if text does not look or reference a valid account file.
func ProcessAccountFile(text string) (*ServiceAccount, error) {
func ProcessAccountFile(text string) (*jwt.Config, error) {
// Assume text is a JSON string
if conf, err := google.JWTConfigFromJSON([]byte(text), DriverScopes...); err == nil {
return &ServiceAccount{
jsonKey: []byte(text),
jwt: conf,
}, nil
}
// If text was not JSON, assume it is a file path instead
if _, err := os.Stat(text); os.IsNotExist(err) {
return nil, fmt.Errorf("account_file path does not exist: %s", text)
}
data, err := ioutil.ReadFile(text)
conf, err := google.JWTConfigFromJSON([]byte(text), DriverScopes...)
if err != nil {
return nil, fmt.Errorf("Error reading account_file from path '%s': %s", text, err)
// If text was not JSON, assume it is a file path instead
if _, err := os.Stat(text); os.IsNotExist(err) {
return nil, fmt.Errorf(
"account_file path does not exist: %s",
text)
}
data, err := ioutil.ReadFile(text)
if err != nil {
return nil, fmt.Errorf(
"Error reading account_file from path '%s': %s",
text, err)
}
conf, err = google.JWTConfigFromJSON(data, DriverScopes...)
if err != nil {
return nil, fmt.Errorf("Error parsing account_file: %s", err)
}
}
conf, err := google.JWTConfigFromJSON(data, DriverScopes...)
if err != nil {
return nil, fmt.Errorf("Error parsing account_file: %s", err)
}
return &ServiceAccount{
jsonKey: data,
jwt: conf,
}, nil
return conf, nil
}
+9 -23
View File
@@ -36,15 +36,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
// Run executes a googlecompute Packer build and returns a packer.Artifact
// representing a GCE machine image.
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
cfg := GCEDriverConfig{
Ui: ui,
ProjectId: b.config.ProjectId,
Account: b.config.account,
ImpersonateServiceAccountName: b.config.ImpersonateServiceAccount,
VaultOauthEngineName: b.config.VaultGCPOauthEngine,
}
driver, err := NewDriverGCE(cfg)
driver, err := NewDriverGCE(
ui, b.config.ProjectId, b.config.account, b.config.VaultGCPOauthEngine)
if err != nil {
return nil, err
}
@@ -59,16 +52,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
// Build the steps.
steps := []multistep.Step{
new(StepCheckExistingImage),
&communicator.StepSSHKeyGen{
CommConf: &b.config.Comm,
SSHTemporaryKeyPair: b.config.Comm.SSH.SSHTemporaryKeyPair,
&StepCreateSSHKey{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("gce_%s.pem", b.config.PackerBuildName),
},
multistep.If(b.config.PackerDebug && b.config.Comm.SSHPrivateKeyFile == "",
&communicator.StepDumpSSHKey{
Path: fmt.Sprintf("gce_%s.pem", b.config.PackerBuildName),
SSH: &b.config.Comm.SSH,
},
),
&StepImportOSLoginSSHKey{
Debug: b.config.PackerDebug,
},
@@ -83,11 +70,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Debug: b.config.PackerDebug,
},
&StepStartTunnel{
IAPConf: &b.config.IAPConfig,
CommConf: &b.config.Comm,
AccountFile: b.config.AccountFile,
ImpersonateAccount: b.config.ImpersonateServiceAccount,
ProjectId: b.config.ProjectId,
IAPConf: &b.config.IAPConfig,
CommConf: &b.config.Comm,
AccountFile: b.config.AccountFile,
ProjectId: b.config.ProjectId,
},
&communicator.StepConnect{
Config: &b.config.Comm,
+8 -18
View File
@@ -17,6 +17,7 @@ import (
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"golang.org/x/oauth2/jwt"
compute "google.golang.org/api/compute/v1"
)
@@ -34,8 +35,6 @@ type Config struct {
// run Packer on a GCE instance with a service account. Instructions for
// creating the file or using service accounts are above.
AccountFile string `mapstructure:"account_file" required:"false"`
// This allows service account impersonation as per the [docs](https://cloud.google.com/iam/docs/impersonating-service-accounts).
ImpersonateServiceAccount string `mapstructure:"impersonate_service_account" required:"false"`
// The project ID that will be used to launch instances and store images.
ProjectId string `mapstructure:"project_id" required:"true"`
// Full or partial URL of the guest accelerator type. GPU accelerators can
@@ -76,8 +75,6 @@ type Config struct {
EnableIntegrityMonitoring bool `mapstructure:"enable_integrity_monitoring" required:"false"`
// Whether to use an IAP proxy.
IAPConfig `mapstructure:",squash"`
// Skip creating the image. Useful for setting to `true` during a build test stage. Defaults to `false`.
SkipCreateImage bool `mapstructure:"skip_create_image" required:"false"`
// The unique name of the resulting image. Defaults to
// `packer-{{timestamp}}`.
ImageName string `mapstructure:"image_name" required:"false"`
@@ -87,19 +84,13 @@ type Config struct {
// * kmsKeyName - The name of the encryption key that is stored in Google Cloud KMS.
// * RawKey: - A 256-bit customer-supplied encryption key, encodes in RFC 4648 base64.
//
// examples:
// example:
//
// ```json
// {
// "kmsKeyName": "projects/${project}/locations/${region}/keyRings/computeEngine/cryptoKeys/computeEngine/cryptoKeyVersions/4"
// }
// ```
//
// ```hcl
// image_encryption_key {
// kmsKeyName = "projects/${var.project}/locations/${var.region}/keyRings/computeEngine/cryptoKeys/computeEngine/cryptoKeyVersions/4"
// }
// ```
ImageEncryptionKey *CustomerEncryptionKey `mapstructure:"image_encryption_key" required:"false"`
// The name of the image family to which the resulting image belongs. You
// can create disks by specifying an image family instead of a specific
@@ -275,7 +266,7 @@ type Config struct {
UseOSLogin bool `mapstructure:"use_os_login" required:"false"`
// Can be set instead of account_file. If set, this builder will use
// HashiCorp Vault to generate an Oauth token for authenticating against
// Google Cloud. The value should be the path of the token generator
// Google's cloud. The value should be the path of the token generator
// within vault.
// For information on how to configure your Vault + GCP engine to produce
// Oauth tokens, see https://www.vaultproject.io/docs/auth/gcp
@@ -289,7 +280,7 @@ type Config struct {
// Example: "us-central1-a"
Zone string `mapstructure:"zone" required:"true"`
account *ServiceAccount
account *jwt.Config
imageAlreadyExists bool
ctx interpolate.Context
}
@@ -297,7 +288,6 @@ type Config struct {
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
c.ctx.Funcs = TemplateFuncs
err := config.Decode(c, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateFilter: &interpolate.RenderFilter{
@@ -491,9 +481,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
// Authenticating via an account file
if c.AccountFile != "" {
if c.VaultGCPOauthEngine != "" && c.ImpersonateServiceAccount != "" {
if c.VaultGCPOauthEngine != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("You cannot "+
"specify impersonate_service_account, account_file and vault_gcp_oauth_engine at the same time"))
"specify both account_file and vault_gcp_oauth_engine."))
}
cfg, err := ProcessAccountFile(c.AccountFile)
if err != nil {
@@ -545,11 +535,11 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
type CustomerEncryptionKey struct {
// KmsKeyName: The name of the encryption key that is stored in Google
// Cloud KMS.
KmsKeyName string `mapstructure:"kmsKeyName" json:"kmsKeyName,omitempty"`
KmsKeyName string `json:"kmsKeyName,omitempty"`
// RawKey: Specifies a 256-bit customer-supplied encryption key, encoded
// in RFC 4648 base64 to either encrypt or decrypt this resource.
RawKey string `mapstructure:"rawKey" json:"rawKey,omitempty"`
RawKey string `json:"rawKey,omitempty"`
}
func (k *CustomerEncryptionKey) ComputeType() *compute.CustomerEncryptionKey {

Some files were not shown because too many files have changed in this diff Show More