Compare commits

..

1 Commits

Author SHA1 Message Date
Wilken Rivera ad7c2593ab Initial work 2020-08-05 13:44:21 -04:00
1518 changed files with 106854 additions and 187614 deletions
+4 -4
View File
@@ -7,11 +7,11 @@ version: 2.1
executors:
golang:
docker:
- image: 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:
+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
@@ -8,7 +8,6 @@ jobs:
steps:
- name: Add track-internal
uses: andymckay/labeler@1.0.2
if: github.event.issue.pull_request == null
with:
repo-token: ${{ secrets.Github_Token }}
add-labels: "track-internal"
+1 -210
View File
@@ -1,213 +1,4 @@
## 1.6.5 (Upcoming)
### BUG FIXES:
* builder/azure-chroot: Fix typo in option `exlude_from_latest` to
`exclude_from_latest`. Old name will still be respected. [GH-10034]
* builder/openstack: Fix source image validation regression when using filters.
[GH-10065]
* core/hcl2: Packer HCL's "Coalesce" function now behaves same way as
Terraform's. [GH-10016]
* core/HCL: Hide sensitive variables from output. [GH-10031]
* 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/google: Add service account impersonation. [GH-9968] [GH-10054]
* 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/scaleway: Allow the user to use an image label (eg ubuntu_focal)
instead of a hardcoded UUID on the Scaleway builder. [GH-10061]
## 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]
### IMPROVEMENTS:
* builder/amazon: Add all of the custom AWS template engines to `build`
template function for use by provisioners. [GH-9751]
* builder/amazon: Add aws_polling config option to override env variables.
[GH-9777]
* 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
progress bar belongs to. [GH-9780]
* core: Ui now pretty prints build durations. [GH-9749]
* core: When a build is cancelled, Packer will skip postprocessors gracefully
rather than failing them. [GH-9720]
* integrations/secretsmanager: Add support for plaintext non-key/pair secrets.
[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
SaltStack formulas. [GH-9726]
### BUG FIXES:
* build: Fix bug in code generator that caused generation to fail in nested
packer/packer dirs [GH-9728]
* build: Fix Makefile so that default target doesn't crash and creates dev
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
to hang. [GH-9708]
* builder/hyperv: Send boot command in small chunks to make it more stable.
[GH-9765]
* builder/scaleway: Fix config issue that made scaleway builder fail when used
with HCL2 config. [GH-9677]
* builder/vmware: Fully destroy vm if it was cancelled or errored. This will
make orphaned vms easier to destroy through vCenter. [GH-9782]
* builder/vsphere: Fix `alt`, `ctrl`, and `shift` keypresses in the
boot_command. [GH-9702] [GH-9739]
* builder/vsphere: Fix bug where Packer timed out if two interfaces were
defined but only one had an available IP. [GH-9748]
* builder/vsphere: Fix the configuration_parameters option so that it is always
applied, not just when the tool sync policy is set. [GH-9713]
* communicator: Fix `pause_before_connect` option to force a reconnect after
the pause. [GH-9772]
* core: Make `max_retries` provisioner option a string to allow variable
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.2 (Upcoming)
## 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
@@ -25,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 testrace dev releasebin package dev fmt fmt-check mode-check fmt-docs fmt-examples
ci: testrace ## Test in continuous integration
+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"`
}
+3 -14
View File
@@ -335,9 +335,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
}
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
generatedData := awscommon.GetGeneratedDataList()
generatedData = append(generatedData, "Device", "MountPath")
generatedData := []string{"SourceAMIName", "Device", "MountPath"}
return generatedData, warns, nil
}
@@ -398,15 +396,12 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
GeneratedData: generatedData,
},
&StepCreateVolume{
PollingConfig: b.config.PollingConfig,
RootVolumeType: b.config.RootVolumeType,
RootVolumeSize: b.config.RootVolumeSize,
RootVolumeTags: b.config.RootVolumeTags,
Ctx: b.config.ctx,
},
&StepAttachVolume{
PollingConfig: b.config.PollingConfig,
},
&StepAttachVolume{},
&StepEarlyUnflock{},
&chroot.StepPreMountCommands{
Commands: b.config.PreMountCommands,
@@ -425,14 +420,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&chroot.StepCopyFiles{
Files: b.config.CopyFiles,
},
&awscommon.StepSetGeneratedData{
GeneratedData: generatedData,
},
&chroot.StepChrootProvision{},
&chroot.StepEarlyCleanup{},
&StepSnapshot{
PollingConfig: b.config.PollingConfig,
},
&StepSnapshot{},
&awscommon.StepDeregisterAMI{
AccessConfig: &b.config.AccessConfig,
ForceDeregister: b.config.AMIForceDeregister,
@@ -445,7 +435,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
PollingConfig: b.config.PollingConfig,
},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
@@ -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,10 +50,8 @@ 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"`
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" hcl2-schema-generator:"ami_block_device_mappings,direct" required:"false" cty:"ami_block_device_mappings" hcl:"ami_block_device_mappings"`
ChrootMounts [][]string `mapstructure:"chroot_mounts" required:"false" cty:"chroot_mounts" hcl:"chroot_mounts"`
CommandWrapper *string `mapstructure:"command_wrapper" required:"false" cty:"command_wrapper" hcl:"command_wrapper"`
@@ -120,9 +116,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,10 +125,8 @@ 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())},
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
"chroot_mounts": &hcldec.AttrSpec{Name: "chroot_mounts", Type: cty.List(cty.List(cty.String)), Required: false},
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
+2 -17
View File
@@ -227,25 +227,10 @@ func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
if generatedData[0] != "SourceAMIName" {
t.Fatalf("Generated data should contain SourceAMIName")
}
if generatedData[1] != "BuildRegion" {
t.Fatalf("Generated data should contain BuildRegion")
}
if generatedData[2] != "SourceAMI" {
t.Fatalf("Generated data should contain SourceAMI")
}
if generatedData[3] != "SourceAMICreationDate" {
t.Fatalf("Generated data should contain SourceAMICreationDate")
}
if generatedData[4] != "SourceAMIOwner" {
t.Fatalf("Generated data should contain SourceAMIOwner")
}
if generatedData[5] != "SourceAMIOwnerName" {
t.Fatalf("Generated data should contain SourceAMIOwnerName")
}
if generatedData[6] != "Device" {
if generatedData[1] != "Device" {
t.Fatalf("Generated data should contain Device")
}
if generatedData[7] != "MountPath" {
if generatedData[2] != "MountPath" {
t.Fatalf("Generated data should contain MountPath")
}
}
+4 -5
View File
@@ -19,9 +19,8 @@ import (
// device string - The location where the volume was attached.
// attach_cleanup CleanupFunc
type StepAttachVolume struct {
PollingConfig *awscommon.AWSPollingConfig
attached bool
volumeId string
attached bool
volumeId string
}
func (s *StepAttachVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@@ -52,7 +51,7 @@ func (s *StepAttachVolume) Run(ctx context.Context, state multistep.StateBag) mu
s.volumeId = volumeId
// Wait for the volume to become attached
err = s.PollingConfig.WaitUntilVolumeAttached(ctx, ec2conn, s.volumeId)
err = awscommon.WaitUntilVolumeAttached(ctx, ec2conn, s.volumeId)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
@@ -88,7 +87,7 @@ func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error {
s.attached = false
// Wait for the volume to detach
err = s.PollingConfig.WaitUntilVolumeDetached(aws.BackgroundContext(), ec2conn, s.volumeId)
err = awscommon.WaitUntilVolumeDetached(aws.BackgroundContext(), ec2conn, s.volumeId)
if err != nil {
return fmt.Errorf("Error waiting for volume: %s", err)
}
+1 -2
View File
@@ -20,7 +20,6 @@ import (
// Produces:
// volume_id string - The ID of the created volume
type StepCreateVolume struct {
PollingConfig *awscommon.AWSPollingConfig
volumeId string
RootVolumeSize int64
RootVolumeType string
@@ -111,7 +110,7 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
log.Printf("Volume ID: %s", s.volumeId)
// Wait for the volume to become ready
err = s.PollingConfig.WaitUntilVolumeAvailable(ctx, ec2conn, s.volumeId)
err = awscommon.WaitUntilVolumeAvailable(ctx, ec2conn, s.volumeId)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
+1 -2
View File
@@ -15,7 +15,6 @@ import (
// StepRegisterAMI creates the AMI.
type StepRegisterAMI struct {
PollingConfig *awscommon.AWSPollingConfig
RootVolumeSize int64
EnableAMIENASupport confighelper.Trilean
EnableAMISriovNetSupport bool
@@ -82,7 +81,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
state.Put("amis", amis)
ui.Say("Waiting for AMI to become ready...")
if err := s.PollingConfig.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
+2 -3
View File
@@ -16,8 +16,7 @@ import (
// Produces:
// snapshot_id string - ID of the created snapshot
type StepSnapshot struct {
PollingConfig *awscommon.AWSPollingConfig
snapshotId string
snapshotId string
}
func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@@ -44,7 +43,7 @@ func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId))
// Wait for the snapshot to be ready
err = s.PollingConfig.WaitUntilSnapshotDone(ctx, ec2conn, s.snapshotId)
err = awscommon.WaitUntilSnapshotDone(ctx, ec2conn, s.snapshotId)
if err != nil {
err := fmt.Errorf("Error waiting for snapshot: %s", err)
state.Put("error", err)
+11 -127
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,67 +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/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"`
@@ -100,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`
@@ -145,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
@@ -199,9 +138,6 @@ type AccessConfig struct {
// }
// ```
VaultAWSEngine VaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false"`
// [Polling configuration](#polling-configuration) for the AWS waiter. Configures the waiter that checks
// resource state.
PollingConfig *AWSPollingConfig `mapstructure:"aws_polling" required:"false"`
getEC2Connection func() ec2iface.EC2API
}
@@ -213,13 +149,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)
}
@@ -237,16 +176,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,7 +201,9 @@ func (c *AccessConfig) Session() (*session.Session, error) {
cp, err := c.session.Config.Credentials.Get()
if IsAWSErr(err, "NoCredentialProviders", "") {
return nil, c.NewNoValidCredentialSourcesError(err)
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 {
@@ -284,6 +215,7 @@ func (c *AccessConfig) Session() (*session.Session, error) {
if c.DecodeAuthZMessages {
DecodeAuthZMessages(c.session)
}
LogEnvOverrideWarnings()
return c.session, nil
}
@@ -303,42 +235,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"
@@ -400,21 +296,9 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
fmt.Errorf("`access_key` and `secret_key` must both be either set or not set."))
}
if c.PollingConfig == nil {
c.PollingConfig = new(AWSPollingConfig)
}
c.PollingConfig.LogEnvOverrideWarnings()
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 {
@@ -13,7 +13,6 @@ func testAccessConfig() *AccessConfig {
getEC2Connection: func() ec2iface.EC2API {
return &mockEC2Client{}
},
PollingConfig: new(AWSPollingConfig),
}
}
+7 -21
View File
@@ -79,32 +79,18 @@ type AMIConfig struct {
// Default false.
AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot" required:"false"`
// Whether or not to encrypt the resulting AMI when
// copying a provisioned instance to an AMI. By default, Packer will keep
// the encryption setting to what it was in the source image. Setting false
// will result in an unencrypted image, and true will result in an encrypted
// one.
//
// copying a provisioned instance to an AMI. By default, Packer will keep the
// encryption setting to what it was in the source image. Setting false will
// result in an unencrypted image, and true will result in an encrypted one.
// If you have used the `launch_block_device_mappings` to set an encryption
// key and that key is the same as the one you want the image encrypted with
// at the end, then you don't need to set this field; leaving it empty will
// prevent an unnecessary extra copy step and save you some time.
AMIEncryptBootVolume config.Trilean `mapstructure:"encrypt_boot" required:"false"`
// ID, alias or ARN of the KMS key to use for AMI encryption. This
// only applies to the main `region` -- any regions the AMI gets copied to
// copied will be encrypted by the default EBS KMS key for that region,
// unless you set region-specific keys in AMIRegionKMSKeyIDs.
//
// Set this value if you select `encrypt_boot`, but don't want to use the
// region's default KMS key.
//
// If you have a custom kms key you'd like to apply to the launch volume,
// and are only building in one region, it is more efficient to leave this
// and `encrypt_boot` empty and to instead set the key id in the
// launch_block_device_mappings (you can find an example below). This saves
// potentially many minutes at the end of the build by preventing Packer
// from having to copy and re-encrypt the image at the end of the build.
//
// For valid formats see *KmsKeyId* in the [AWS API docs -
// ID, alias or ARN of the KMS key to use for boot volume encryption. This
// only applies to the main `region`, other regions where the AMI will be
// copied will be encrypted by the default EBS KMS key. For valid formats
// see *KmsKeyId* in the [AWS API docs -
// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html).
// This field is validated by Packer, when using an alias, you will have to
// prefix `kms_key_id` with `alias/`.
+8 -13
View File
@@ -13,7 +13,7 @@ import (
"github.com/hashicorp/packer/template/interpolate"
)
// These will be attached when launching your instance. Your
// These will be attached when booting a new instance from your AMI. Your
// options here may vary depending on the type of VM you use.
//
// Example use case:
@@ -24,7 +24,7 @@ import (
// JSON example:
//
// ```json
// launch_block_device_mappings: [
// ami_block_device_mappings: [
// {
// "device_name": "/dev/sda1",
// "encrypted": true,
@@ -36,16 +36,13 @@ import (
// HCL2 example:
//
// ```hcl
// launch_block_device_mappings {
// ami_block_device_mappings {
// device_name = "/dev/sda1"
// encrypted = true
// kms_key_id = "1a2b3c4d-5e6f-1a2b-3c4d-5e6f1a2b3c4d"
// }
// ```
//
// Please note that the kms_key_id option in this example exists for
// launch_block_device_mappings but not ami_block_device_mappings.
//
// Documentation for Block Devices Mappings can be found here:
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html
//
@@ -83,14 +80,12 @@ type BlockDevice struct {
// The size of the volume, in GiB. Required if not specifying a
// snapshot_id.
VolumeSize int64 `mapstructure:"volume_size" required:"false"`
// ID, alias or ARN of the KMS key to use for boot volume encryption.
// This option exists for launch_block_device_mappings but not
// ami_block_device_mappings. The kms key id defined here only applies to
// the original build region; if the AMI gets copied to other regions, the
// volume in those regions will be encrypted by the default EBS KMS key.
// For valid formats see KmsKeyId in the [AWS API docs -
// ID, alias or ARN of the KMS key to use for boot volume encryption. This
// only applies to the main region, other regions where the AMI will be
// copied will be encrypted by the default EBS KMS key. For valid formats
// see KmsKeyId in the [AWS API docs -
// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html)
// This field is validated by Packer. When using an alias, you will have to
// This field is validated by Packer, when using an alias, you will have to
// prefix kms_key_id with alias/.
KmsKeyId string `mapstructure:"kms_key_id" required:"false"`
}
@@ -40,24 +40,6 @@ func extractBuildInfo(region string, state multistep.StateBag, generatedData *bu
SourceAMIOwnerName: aws.StringValue(sourceAMI.ImageOwnerAlias),
SourceAMITags: sourceAMITags,
}
generatedData.Put("BuildRegion", buildInfoTemplate.BuildRegion)
generatedData.Put("SourceAMI", buildInfoTemplate.SourceAMI)
generatedData.Put("SourceAMICreationDate", buildInfoTemplate.SourceAMICreationDate)
generatedData.Put("SourceAMIName", buildInfoTemplate.SourceAMIName)
generatedData.Put("SourceAMIOwner", buildInfoTemplate.SourceAMIOwner)
generatedData.Put("SourceAMIOwnerName", buildInfoTemplate.SourceAMIOwnerName)
return buildInfoTemplate
}
func GetGeneratedDataList() []string {
return []string{
"SourceAMIName",
"BuildRegion",
"SourceAMI",
"SourceAMICreationDate",
"SourceAMIOwner",
"SourceAMIOwnerName",
}
}
+5 -16
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 {
@@ -473,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`.
@@ -542,10 +535,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
msg := fmt.Errorf(`no iam_instance_profile defined; session_manager connectivity requires a valid instance profile with AmazonSSMManagedInstanceCore permissions. Alternatively a temporary_iam_instance_profile_policy_document can be used.`)
errs = append(errs, msg)
}
if c.PauseBeforeSSM == 0 {
c.PauseBeforeSSM = 10 * time.Second
}
}
if c.Comm.SSHKeyPairName != "" {
+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
}
+3 -18
View File
@@ -46,7 +46,7 @@ func NewSSMDriver(config SSMDriverConfig) *SSMDriver {
// 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", aws.StringValue(input.Target))
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{
@@ -110,30 +110,15 @@ func (d *SSMDriver) openTunnelForSession(ctx context.Context) error {
select {
case <-ctx.Done():
return
case output, ok := <-stderrCh:
if !ok {
stderrCh = nil
break
}
case output := <-stderrCh:
if output != "" {
log.Printf("[ERROR] %s: %s", prefix, output)
}
case output, ok := <-stdoutCh:
if !ok {
stdoutCh = nil
break
}
case output := <-stdoutCh:
if output != "" {
log.Printf("[DEBUG] %s: %s", prefix, output)
}
}
if stdoutCh == nil && stderrCh == nil {
log.Printf("[DEBUG] %s: %s", prefix, "active session has been terminated; stopping all log polling processes.")
return
}
}
}(ctx, sessionManagerPluginName)
+33 -82
View File
@@ -1,5 +1,3 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type AWSPollingConfig
package common
import (
@@ -40,45 +38,12 @@ type StateChangeConf struct {
// Following are wrapper functions that use Packer's environment-variables to
// determine retry logic, then call the AWS SDK's built-in waiters.
// Polling configuration for the AWS waiter. Configures the waiter for resources creation or actions like attaching
// volumes or importing image.
// Usage example:
//
// In JSON:
// ```json
// "aws_polling" : {
// "delay_seconds": 30,
// "max_attempts": 50
// }
// ```
//
// In HCL2:
// ```hcl
// aws_polling {
// delay_seconds = 30
// max_attempts = 50
// }
// ```
//
type AWSPollingConfig struct {
// Specifies the maximum number of attempts the waiter will check for resource state.
// This value can also be set via the AWS_MAX_ATTEMPTS.
// If both option and environment variable are set, the max_attempts will be considered over the AWS_MAX_ATTEMPTS.
// If none is set, defaults to AWS waiter default which is 40 max_attempts.
MaxAttempts int `mapstructure:"max_attempts" required:"false"`
// Specifies the delay in seconds between attempts to check the resource state.
// This value can also be set via the AWS_POLL_DELAY_SECONDS.
// If both option and environment variable are set, the delay_seconds will be considered over the AWS_POLL_DELAY_SECONDS.
// If none is set, defaults to AWS waiter default which is 15 seconds.
DelaySeconds int `mapstructure:"delay_seconds" required:"false"`
}
func (w *AWSPollingConfig) WaitUntilAMIAvailable(ctx aws.Context, conn ec2iface.EC2API, imageId string) error {
func WaitUntilAMIAvailable(ctx aws.Context, conn ec2iface.EC2API, imageId string) error {
imageInput := ec2.DescribeImagesInput{
ImageIds: []*string{&imageId},
}
waitOpts := w.getWaiterOptions()
waitOpts := getWaiterOptions()
if len(waitOpts) == 0 {
// Bump this default to 30 minutes because the aws default
// of ten minutes doesn't work for some of our long-running copies.
@@ -101,7 +66,7 @@ func (w *AWSPollingConfig) WaitUntilAMIAvailable(ctx aws.Context, conn ec2iface.
return err
}
func (w *AWSPollingConfig) WaitUntilInstanceRunning(ctx aws.Context, conn *ec2.EC2, instanceId string) error {
func WaitUntilInstanceRunning(ctx aws.Context, conn *ec2.EC2, instanceId string) error {
instanceInput := ec2.DescribeInstancesInput{
InstanceIds: []*string{&instanceId},
@@ -110,11 +75,12 @@ func (w *AWSPollingConfig) WaitUntilInstanceRunning(ctx aws.Context, conn *ec2.E
err := conn.WaitUntilInstanceRunningWithContext(
ctx,
&instanceInput,
w.getWaiterOptions()...)
getWaiterOptions()...)
return err
}
func (w *AWSPollingConfig) WaitUntilInstanceTerminated(ctx aws.Context, conn *ec2.EC2, instanceId string) error {
func WaitUntilInstanceTerminated(ctx aws.Context, conn *ec2.EC2, instanceId string) error {
instanceInput := ec2.DescribeInstancesInput{
InstanceIds: []*string{&instanceId},
}
@@ -122,12 +88,12 @@ func (w *AWSPollingConfig) WaitUntilInstanceTerminated(ctx aws.Context, conn *ec
err := conn.WaitUntilInstanceTerminatedWithContext(
ctx,
&instanceInput,
w.getWaiterOptions()...)
getWaiterOptions()...)
return err
}
// This function works for both requesting and cancelling spot instances.
func (w *AWSPollingConfig) WaitUntilSpotRequestFulfilled(ctx aws.Context, conn *ec2.EC2, spotRequestId string) error {
func WaitUntilSpotRequestFulfilled(ctx aws.Context, conn *ec2.EC2, spotRequestId string) error {
spotRequestInput := ec2.DescribeSpotInstanceRequestsInput{
SpotInstanceRequestIds: []*string{&spotRequestId},
}
@@ -135,11 +101,11 @@ func (w *AWSPollingConfig) WaitUntilSpotRequestFulfilled(ctx aws.Context, conn *
err := conn.WaitUntilSpotInstanceRequestFulfilledWithContext(
ctx,
&spotRequestInput,
w.getWaiterOptions()...)
getWaiterOptions()...)
return err
}
func (w *AWSPollingConfig) WaitUntilVolumeAvailable(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
func WaitUntilVolumeAvailable(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
volumeInput := ec2.DescribeVolumesInput{
VolumeIds: []*string{&volumeId},
}
@@ -147,11 +113,11 @@ func (w *AWSPollingConfig) WaitUntilVolumeAvailable(ctx aws.Context, conn *ec2.E
err := conn.WaitUntilVolumeAvailableWithContext(
ctx,
&volumeInput,
w.getWaiterOptions()...)
getWaiterOptions()...)
return err
}
func (w *AWSPollingConfig) WaitUntilSnapshotDone(ctx aws.Context, conn *ec2.EC2, snapshotID string) error {
func WaitUntilSnapshotDone(ctx aws.Context, conn *ec2.EC2, snapshotID string) error {
snapInput := ec2.DescribeSnapshotsInput{
SnapshotIds: []*string{&snapshotID},
}
@@ -159,13 +125,13 @@ func (w *AWSPollingConfig) WaitUntilSnapshotDone(ctx aws.Context, conn *ec2.EC2,
err := conn.WaitUntilSnapshotCompletedWithContext(
ctx,
&snapInput,
w.getWaiterOptions()...)
getWaiterOptions()...)
return err
}
// Wrappers for our custom AWS waiters
func (w *AWSPollingConfig) WaitUntilVolumeAttached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
func WaitUntilVolumeAttached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
volumeInput := ec2.DescribeVolumesInput{
VolumeIds: []*string{&volumeId},
}
@@ -173,11 +139,11 @@ func (w *AWSPollingConfig) WaitUntilVolumeAttached(ctx aws.Context, conn *ec2.EC
err := WaitForVolumeToBeAttached(conn,
ctx,
&volumeInput,
w.getWaiterOptions()...)
getWaiterOptions()...)
return err
}
func (w *AWSPollingConfig) WaitUntilVolumeDetached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
func WaitUntilVolumeDetached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
volumeInput := ec2.DescribeVolumesInput{
VolumeIds: []*string{&volumeId},
}
@@ -185,11 +151,11 @@ func (w *AWSPollingConfig) WaitUntilVolumeDetached(ctx aws.Context, conn *ec2.EC
err := WaitForVolumeToBeDetached(conn,
ctx,
&volumeInput,
w.getWaiterOptions()...)
getWaiterOptions()...)
return err
}
func (w *AWSPollingConfig) WaitUntilImageImported(ctx aws.Context, conn *ec2.EC2, taskID string) error {
func WaitUntilImageImported(ctx aws.Context, conn *ec2.EC2, taskID string) error {
importInput := ec2.DescribeImportImageTasksInput{
ImportTaskIds: []*string{&taskID},
}
@@ -197,7 +163,7 @@ func (w *AWSPollingConfig) WaitUntilImageImported(ctx aws.Context, conn *ec2.EC2
err := WaitForImageToBeImported(conn,
ctx,
&importInput,
w.getWaiterOptions()...)
getWaiterOptions()...)
return err
}
@@ -332,18 +298,8 @@ type overridableWaitVars struct {
awsTimeoutSeconds envInfo
}
func (w *AWSPollingConfig) getWaiterOptions() []request.WaiterOption {
func getWaiterOptions() []request.WaiterOption {
envOverrides := getEnvOverrides()
if w.MaxAttempts != 0 {
envOverrides.awsMaxAttempts.Val = w.MaxAttempts
envOverrides.awsMaxAttempts.overridden = true
}
if w.DelaySeconds != 0 {
envOverrides.awsPollDelaySeconds.Val = w.DelaySeconds
envOverrides.awsPollDelaySeconds.overridden = true
}
waitOpts := applyEnvOverrides(envOverrides)
return waitOpts
}
@@ -377,38 +333,33 @@ func getEnvOverrides() overridableWaitVars {
return envValues
}
func (w *AWSPollingConfig) LogEnvOverrideWarnings() {
pollDelayEnv := os.Getenv("AWS_POLL_DELAY_SECONDS")
timeoutSecondsEnv := os.Getenv("AWS_TIMEOUT_SECONDS")
maxAttemptsEnv := os.Getenv("AWS_MAX_ATTEMPTS")
func LogEnvOverrideWarnings() {
pollDelay := os.Getenv("AWS_POLL_DELAY_SECONDS")
timeoutSeconds := os.Getenv("AWS_TIMEOUT_SECONDS")
maxAttempts := os.Getenv("AWS_MAX_ATTEMPTS")
maxAttemptsIsSet := maxAttemptsEnv != "" || w.MaxAttempts != 0
timeoutSecondsIsSet := timeoutSecondsEnv != ""
pollDelayIsSet := pollDelayEnv != "" || w.DelaySeconds != 0
if maxAttemptsIsSet && timeoutSecondsIsSet {
if maxAttempts != "" && timeoutSeconds != "" {
warning := fmt.Sprintf("[WARNING] (aws): AWS_MAX_ATTEMPTS and " +
"AWS_TIMEOUT_SECONDS are both set. Packer will use " +
"AWS_MAX_ATTEMPTS and discard AWS_TIMEOUT_SECONDS.")
if !pollDelayIsSet {
if pollDelay == "" {
warning = fmt.Sprintf("%s Since you have not set the poll delay, "+
"Packer will default to a 2-second delay.", warning)
}
log.Printf(warning)
} else if timeoutSecondsIsSet {
} else if timeoutSeconds != "" {
log.Printf("[WARNING] (aws): env var AWS_TIMEOUT_SECONDS is " +
"deprecated in favor of AWS_MAX_ATTEMPTS env or aws_polling_max_attempts config option. " +
"If you have not explicitly set AWS_POLL_DELAY_SECONDS env or aws_polling_delay_seconds config option, " +
"we are defaulting to a poll delay of 2 seconds, regardless of the AWS waiter's default.")
"deprecated in favor of AWS_MAX_ATTEMPTS. If you have not " +
"explicitly set AWS_POLL_DELAY_SECONDS, we are defaulting to a " +
"poll delay of 2 seconds, regardless of the AWS waiter's default.")
}
if !maxAttemptsIsSet && !timeoutSecondsIsSet && !pollDelayIsSet {
if maxAttempts == "" && timeoutSeconds == "" && pollDelay == "" {
log.Printf("[INFO] (aws): No AWS timeout and polling overrides have been set. " +
"Packer will default to waiter-specific delays and timeouts. If you would " +
"like to customize the length of time between retries and max " +
"number of retries you may do so by setting the environment " +
"variables AWS_POLL_DELAY_SECONDS and AWS_MAX_ATTEMPTS or the " +
"configuration options aws_polling_delay_seconds and aws_polling_max_attempts " +
"to your desired values.")
"variables AWS_POLL_DELAY_SECONDS and AWS_MAX_ATTEMPTS to your " +
"desired values.")
}
}
-32
View File
@@ -1,32 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type AWSPollingConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatAWSPollingConfig is an auto-generated flat version of AWSPollingConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatAWSPollingConfig struct {
MaxAttempts *int `mapstructure:"max_attempts" required:"false" cty:"max_attempts" hcl:"max_attempts"`
DelaySeconds *int `mapstructure:"delay_seconds" required:"false" cty:"delay_seconds" hcl:"delay_seconds"`
}
// FlatMapstructure returns a new FlatAWSPollingConfig.
// FlatAWSPollingConfig is an auto-generated flat version of AWSPollingConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*AWSPollingConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatAWSPollingConfig)
}
// HCL2Spec returns the hcl spec of a AWSPollingConfig.
// This spec is used by HCL to read the fields of AWSPollingConfig.
// The decoded values from this spec will then be applied to a FlatAWSPollingConfig.
func (*FlatAWSPollingConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"max_attempts": &hcldec.AttrSpec{Name: "max_attempts", Type: cty.Number, Required: false},
"delay_seconds": &hcldec.AttrSpec{Name: "delay_seconds", Type: cty.Number, Required: false},
}
return s
}
@@ -209,7 +209,7 @@ func (s *StepAMIRegionCopy) amiRegionCopy(ctx context.Context, state multistep.S
}
// Wait for the image to become ready
if err := s.AccessConfig.PollingConfig.WaitUntilAMIAvailable(ctx, regionconn, *resp.ImageId); err != nil {
if err := WaitUntilAMIAvailable(ctx, regionconn, *resp.ImageId); err != nil {
return "", snapshotIds, fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s",
*resp.ImageId, target, err)
}
@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"strconv"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
@@ -22,7 +21,6 @@ type StepCreateSSMTunnel struct {
RemotePortNumber int
SSMAgentEnabled bool
instanceId string
PauseBeforeSSM time.Duration
driver *SSMDriver
}
@@ -34,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 for 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)
@@ -19,7 +19,6 @@ import (
)
type StepRunSourceInstance struct {
PollingConfig *AWSPollingConfig
AssociatePublicIpAddress bool
LaunchMappings EC2BlockDeviceMappingsBuilder
Comm *communicator.Config
@@ -235,7 +234,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
InstanceIds: []*string{aws.String(instanceId)},
}
if err := s.PollingConfig.WaitUntilInstanceRunning(ctx, ec2conn, instanceId); err != nil {
if err := WaitUntilInstanceRunning(ctx, ec2conn, instanceId); err != nil {
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err)
state.Put("error", err)
ui.Error(err.Error())
@@ -365,7 +364,7 @@ func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) {
return
}
if err := s.PollingConfig.WaitUntilInstanceTerminated(aws.BackgroundContext(), ec2conn, s.instanceId); err != nil {
if err := WaitUntilInstanceTerminated(aws.BackgroundContext(), ec2conn, s.instanceId); err != nil {
ui.Error(err.Error())
}
}
+20 -39
View File
@@ -6,7 +6,6 @@ import (
"fmt"
"io/ioutil"
"log"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
@@ -24,7 +23,6 @@ type EC2BlockDeviceMappingsBuilder interface {
}
type StepRunSpotInstance struct {
PollingConfig *AWSPollingConfig
AssociatePublicIpAddress bool
LaunchMappings EC2BlockDeviceMappingsBuilder
BlockDurationMinutes int64
@@ -279,53 +277,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]
@@ -485,7 +466,7 @@ func (s *StepRunSpotInstance) Cleanup(state multistep.StateBag) {
return
}
if err := s.PollingConfig.WaitUntilInstanceTerminated(aws.BackgroundContext(), ec2conn, s.instanceId); err != nil {
if err := WaitUntilInstanceTerminated(aws.BackgroundContext(), ec2conn, s.instanceId); err != nil {
ui.Error(err.Error())
}
}
@@ -29,7 +29,6 @@ func tStateSpot() multistep.StateBag {
func getBasicStep() *StepRunSpotInstance {
stepRunSpotInstance := StepRunSpotInstance{
PollingConfig: new(AWSPollingConfig),
AssociatePublicIpAddress: false,
LaunchMappings: BlockDevices{},
BlockDurationMinutes: 0,
@@ -1,29 +0,0 @@
package common
import (
"context"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/builder"
"github.com/hashicorp/packer/helper/multistep"
)
// &awscommon.StepSetGeneratedData{
// GeneratedData: generatedData,
// },
type StepSetGeneratedData struct {
GeneratedData *builder.GeneratedData
}
func (s *StepSetGeneratedData) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
extractBuildInfo(*ec2conn.Config.Region, state, s.GeneratedData)
return multistep.ActionContinue
}
func (s *StepSetGeneratedData) Cleanup(state multistep.StateBag) {
// No cleanup...
}
@@ -12,7 +12,6 @@ import (
)
type StepStopEBSBackedInstance struct {
PollingConfig *AWSPollingConfig
Skip bool
DisableStopInstance bool
}
@@ -75,7 +74,7 @@ func (s *StepStopEBSBackedInstance) Run(ctx context.Context, state multistep.Sta
&ec2.DescribeInstancesInput{
InstanceIds: []*string{instance.InstanceId},
},
s.PollingConfig.getWaiterOptions()...)
getWaiterOptions()...)
if err != nil {
err := fmt.Errorf("Error waiting for instance to stop: %s", err)
+1 -9
View File
@@ -140,7 +140,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
generatedData := awscommon.GetGeneratedDataList()
generatedData := []string{"SourceAMIName"}
return generatedData, warns, nil
}
@@ -169,7 +169,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
if b.config.IsSpotInstance() {
instanceStep = &awscommon.StepRunSpotInstance{
PollingConfig: b.config.PollingConfig,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
LaunchMappings: b.config.LaunchMappings,
BlockDurationMinutes: b.config.BlockDurationMinutes,
@@ -192,7 +191,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
}
} else {
instanceStep = &awscommon.StepRunSourceInstance{
PollingConfig: b.config.PollingConfig,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
LaunchMappings: b.config.LaunchMappings,
Comm: &b.config.RunConfig.Comm,
@@ -269,7 +267,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(),
@@ -287,15 +284,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
&awscommon.StepSetGeneratedData{
GeneratedData: generatedData,
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.RunConfig.Comm,
},
&awscommon.StepStopEBSBackedInstance{
PollingConfig: b.config.PollingConfig,
Skip: b.config.IsSpotInstance(),
DisableStopInstance: b.config.DisableStopInstance,
},
@@ -312,7 +305,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
},
&stepCreateAMI{
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
PollingConfig: b.config.PollingConfig,
},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
-10
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,10 +29,8 @@ 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"`
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"`
AMIVirtType *string `mapstructure:"ami_virtualization_type" required:"false" cty:"ami_virtualization_type" hcl:"ami_virtualization_type"`
@@ -135,7 +131,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"`
@@ -164,9 +159,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},
@@ -176,10 +169,8 @@ 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())},
"ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false},
"ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false},
"ami_virtualization_type": &hcldec.AttrSpec{Name: "ami_virtualization_type", Type: cty.String, Required: false},
@@ -280,7 +271,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 -15
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"),
})
}
@@ -249,12 +248,6 @@ func checkBootEncrypted() builderT.TestCheckFunc {
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()
@@ -321,6 +314,7 @@ const testBuilderAccForceDeleteSnapshot = `
}
`
// share with catsby
const testBuilderAccSharing = `
{
"builders": [{
@@ -330,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"]
}]
}
@@ -357,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)
}
-18
View File
@@ -144,25 +144,7 @@ func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if generatedData[0] != "SourceAMIName" {
t.Fatalf("Generated data should contain SourceAMIName")
}
if generatedData[1] != "BuildRegion" {
t.Fatalf("Generated data should contain BuildRegion")
}
if generatedData[2] != "SourceAMI" {
t.Fatalf("Generated data should contain SourceAMI")
}
if generatedData[3] != "SourceAMICreationDate" {
t.Fatalf("Generated data should contain SourceAMICreationDate")
}
if generatedData[4] != "SourceAMIOwner" {
t.Fatalf("Generated data should contain SourceAMIOwner")
}
if generatedData[5] != "SourceAMIOwnerName" {
t.Fatalf("Generated data should contain SourceAMIOwnerName")
}
}
+5 -11
View File
@@ -16,7 +16,6 @@ import (
)
type stepCreateAMI struct {
PollingConfig *awscommon.AWSPollingConfig
image *ec2.Image
AMISkipBuildRegion bool
}
@@ -86,23 +85,18 @@ func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multi
// Wait for the image to become ready
ui.Say("Waiting for AMI to become ready...")
if waitErr := s.PollingConfig.WaitUntilAMIAvailable(ctx, ec2conn, *createResp.ImageId); waitErr != nil {
// waitErr should get bubbled up if the issue is a wait timeout
err := fmt.Errorf("Error waiting for AMI: %s", waitErr)
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *createResp.ImageId); err != nil {
log.Printf("Error waiting for AMI: %s", err)
imResp, imerr := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{createResp.ImageId}})
if imerr != nil {
// If there's a failure describing images, bubble that error up too, but don't erase the waitErr.
log.Printf("DescribeImages call was unable to determine reason waiting for AMI failed: %s", imerr)
err = fmt.Errorf("Unknown error waiting for AMI; %s. DescribeImages returned an error: %s", waitErr, imerr)
log.Printf("Unable to determine reason waiting for AMI failed: %s", err)
err = fmt.Errorf("Unknown error waiting for AMI; %s", err)
}
if imResp != nil && len(imResp.Images) > 0 {
// Finally, if there's a stateReason, store that with the wait err
image := imResp.Images[0]
if image != nil {
stateReason := image.StateReason
if stateReason != nil {
err = fmt.Errorf("Error waiting for AMI: %s. DescribeImages returned the state reason: %s", waitErr, stateReason)
}
err = fmt.Errorf("Error waiting for AMI. Reason: %s", stateReason)
}
}
state.Put("error", err)
@@ -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 {
+13 -24
View File
@@ -163,7 +163,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
generatedData := awscommon.GetGeneratedDataList()
generatedData := []string{"SourceAMIName"}
return generatedData, warns, nil
}
@@ -192,7 +192,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
if b.config.IsSpotInstance() {
instanceStep = &awscommon.StepRunSpotInstance{
PollingConfig: b.config.PollingConfig,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
LaunchMappings: b.config.LaunchMappings,
BlockDurationMinutes: b.config.BlockDurationMinutes,
@@ -214,7 +213,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
}
} else {
instanceStep = &awscommon.StepRunSourceInstance{
PollingConfig: b.config.PollingConfig,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
LaunchMappings: b.config.LaunchMappings,
Comm: &b.config.RunConfig.Comm,
@@ -240,12 +238,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
// Build the steps
steps := []multistep.Step{
&awscommon.StepPreValidate{
DestAmiName: b.config.AMIName,
ForceDeregister: b.config.AMIForceDeregister,
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
VpcId: b.config.VpcId,
SubnetId: b.config.SubnetId,
HasSubnetFilter: !b.config.SubnetFilter.Empty(),
DestAmiName: b.config.AMIName,
ForceDeregister: b.config.AMIForceDeregister,
VpcId: b.config.VpcId,
SubnetId: b.config.SubnetId,
HasSubnetFilter: !b.config.SubnetFilter.Empty(),
},
&awscommon.StepSourceAMIInfo{
SourceAmi: b.config.SourceAmi,
@@ -293,7 +290,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(),
@@ -311,15 +307,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
&awscommon.StepSetGeneratedData{
GeneratedData: generatedData,
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.RunConfig.Comm,
},
&awscommon.StepStopEBSBackedInstance{
PollingConfig: b.config.PollingConfig,
Skip: b.config.IsSpotInstance(),
DisableStopInstance: b.config.DisableStopInstance,
},
@@ -329,7 +321,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
EnableAMIENASupport: b.config.AMIENASupport,
},
&StepSnapshotVolumes{
PollingConfig: b.config.PollingConfig,
LaunchDevices: launchDevices,
SnapshotOmitMap: b.config.LaunchMappings.GetOmissions(),
},
@@ -349,17 +340,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Architecture: b.config.Architecture,
LaunchOmitMap: b.config.LaunchMappings.GetOmissions(),
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
PollingConfig: b.config.PollingConfig,
},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,
AMIKmsKeyId: b.config.AMIKmsKeyId,
RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
OriginalRegion: *ec2conn.Config.Region,
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,
AMIKmsKeyId: b.config.AMIKmsKeyId,
RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
OriginalRegion: *ec2conn.Config.Region,
},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
@@ -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,10 +72,8 @@ 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"`
AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" required:"false" cty:"associate_public_ip_address" hcl:"associate_public_ip_address"`
AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone" hcl:"availability_zone"`
BlockDurationMinutes *int64 `mapstructure:"block_duration_minutes" required:"false" cty:"block_duration_minutes" hcl:"block_duration_minutes"`
@@ -157,7 +153,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"`
@@ -208,9 +203,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},
@@ -220,10 +213,8 @@ 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())},
"associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false},
"availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false},
"block_duration_minutes": &hcldec.AttrSpec{Name: "block_duration_minutes", Type: cty.Number, Required: false},
@@ -303,7 +294,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},
@@ -86,25 +86,7 @@ func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if generatedData[0] != "SourceAMIName" {
t.Fatalf("Generated data should contain SourceAMIName")
}
if generatedData[1] != "BuildRegion" {
t.Fatalf("Generated data should contain BuildRegion")
}
if generatedData[2] != "SourceAMI" {
t.Fatalf("Generated data should contain SourceAMI")
}
if generatedData[3] != "SourceAMICreationDate" {
t.Fatalf("Generated data should contain SourceAMICreationDate")
}
if generatedData[4] != "SourceAMIOwner" {
t.Fatalf("Generated data should contain SourceAMIOwner")
}
if generatedData[5] != "SourceAMIOwnerName" {
t.Fatalf("Generated data should contain SourceAMIOwnerName")
}
}
@@ -15,7 +15,6 @@ import (
// StepRegisterAMI creates the AMI.
type StepRegisterAMI struct {
PollingConfig *awscommon.AWSPollingConfig
RootDevice RootBlockDevice
AMIDevices []*ec2.BlockDeviceMapping
LaunchDevices []*ec2.BlockDeviceMapping
@@ -87,7 +86,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
// Wait for the image to become ready
ui.Say("Waiting for AMI to become ready...")
if err := s.PollingConfig.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@@ -7,7 +7,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/builder/amazon/common"
)
const sourceDeviceName = "/dev/xvdf"
@@ -24,7 +23,6 @@ func newStepRegisterAMI(amiDevices, launchDevices []*ec2.BlockDeviceMapping) *St
},
AMIDevices: amiDevices,
LaunchDevices: launchDevices,
PollingConfig: new(common.AWSPollingConfig),
}
}
@@ -18,7 +18,6 @@ import (
// Produces:
// snapshot_ids map[string]string - IDs of the created snapshots
type StepSnapshotVolumes struct {
PollingConfig *awscommon.AWSPollingConfig
LaunchDevices []*ec2.BlockDeviceMapping
snapshotIds map[string]string
snapshotMutex sync.Mutex
@@ -57,7 +56,7 @@ func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName str
s.snapshotMutex.Unlock()
// Wait for snapshot to be created
err = s.PollingConfig.WaitUntilSnapshotDone(ctx, ec2conn, *createSnapResp.SnapshotId)
err = awscommon.WaitUntilSnapshotDone(ctx, ec2conn, *createSnapResp.SnapshotId)
return err
}
+1 -10
View File
@@ -12,7 +12,6 @@ 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/hcl2template"
@@ -147,7 +146,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
generatedData := awscommon.GetGeneratedDataList()
generatedData := []string{"SourceAMIName"}
return generatedData, warns, nil
}
@@ -167,13 +166,11 @@ 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 := &builder.GeneratedData{State: state}
var instanceStep multistep.Step
if b.config.IsSpotInstance() {
instanceStep = &awscommon.StepRunSpotInstance{
PollingConfig: b.config.PollingConfig,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
LaunchMappings: b.config.launchBlockDevices,
BlockDurationMinutes: b.config.BlockDurationMinutes,
@@ -195,7 +192,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
}
} else {
instanceStep = &awscommon.StepRunSourceInstance{
PollingConfig: b.config.PollingConfig,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
LaunchMappings: b.config.launchBlockDevices,
Comm: &b.config.RunConfig.Comm,
@@ -263,7 +259,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(),
@@ -281,15 +276,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
&awscommon.StepSetGeneratedData{
GeneratedData: generatedData,
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.RunConfig.Comm,
},
&awscommon.StepStopEBSBackedInstance{
PollingConfig: b.config.PollingConfig,
Skip: b.config.IsSpotInstance(),
DisableStopInstance: b.config.DisableStopInstance,
},
@@ -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,10 +74,8 @@ 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"`
AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" required:"false" cty:"associate_public_ip_address" hcl:"associate_public_ip_address"`
AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone" hcl:"availability_zone"`
BlockDurationMinutes *int64 `mapstructure:"block_duration_minutes" required:"false" cty:"block_duration_minutes" hcl:"block_duration_minutes"`
@@ -159,7 +155,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"`
@@ -188,9 +183,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},
@@ -200,10 +193,8 @@ 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())},
"associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false},
"availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false},
"block_duration_minutes": &hcldec.AttrSpec{Name: "block_duration_minutes", Type: cty.Number, Required: false},
@@ -283,7 +274,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},
-18
View File
@@ -105,25 +105,7 @@ func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if generatedData[0] != "SourceAMIName" {
t.Fatalf("Generated data should contain SourceAMIName")
}
if generatedData[1] != "BuildRegion" {
t.Fatalf("Generated data should contain BuildRegion")
}
if generatedData[2] != "SourceAMI" {
t.Fatalf("Generated data should contain SourceAMI")
}
if generatedData[3] != "SourceAMICreationDate" {
t.Fatalf("Generated data should contain SourceAMICreationDate")
}
if generatedData[4] != "SourceAMIOwner" {
t.Fatalf("Generated data should contain SourceAMIOwner")
}
if generatedData[5] != "SourceAMIOwnerName" {
t.Fatalf("Generated data should contain SourceAMIOwnerName")
}
}
+1 -8
View File
@@ -227,7 +227,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
}
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
generatedData := awscommon.GetGeneratedDataList()
generatedData := []string{"SourceAMIName"}
return generatedData, warns, nil
}
@@ -255,7 +255,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
if b.config.IsSpotInstance() {
instanceStep = &awscommon.StepRunSpotInstance{
PollingConfig: b.config.PollingConfig,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
LaunchMappings: b.config.LaunchMappings,
BlockDurationMinutes: b.config.BlockDurationMinutes,
@@ -274,7 +273,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
}
} else {
instanceStep = &awscommon.StepRunSourceInstance{
PollingConfig: b.config.PollingConfig,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
LaunchMappings: b.config.LaunchMappings,
Comm: &b.config.RunConfig.Comm,
@@ -343,7 +341,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(),
@@ -363,9 +360,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
&awscommon.StepSetGeneratedData{
GeneratedData: generatedData,
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.RunConfig.Comm,
@@ -388,7 +382,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
PollingConfig: b.config.PollingConfig,
},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
@@ -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,10 +29,8 @@ 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"`
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"`
AMIVirtType *string `mapstructure:"ami_virtualization_type" required:"false" cty:"ami_virtualization_type" hcl:"ami_virtualization_type"`
@@ -135,7 +131,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"`
@@ -170,9 +165,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},
@@ -182,10 +175,8 @@ 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())},
"ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false},
"ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false},
"ami_virtualization_type": &hcldec.AttrSpec{Name: "ami_virtualization_type", Type: cty.String, Required: false},
@@ -286,7 +277,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
View File
@@ -319,25 +319,7 @@ func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if generatedData[0] != "SourceAMIName" {
t.Fatalf("Generated data should contain SourceAMIName")
}
if generatedData[1] != "BuildRegion" {
t.Fatalf("Generated data should contain BuildRegion")
}
if generatedData[2] != "SourceAMI" {
t.Fatalf("Generated data should contain SourceAMI")
}
if generatedData[3] != "SourceAMICreationDate" {
t.Fatalf("Generated data should contain SourceAMICreationDate")
}
if generatedData[4] != "SourceAMIOwner" {
t.Fatalf("Generated data should contain SourceAMIOwner")
}
if generatedData[5] != "SourceAMIOwnerName" {
t.Fatalf("Generated data should contain SourceAMIOwnerName")
}
}
+1 -2
View File
@@ -14,7 +14,6 @@ import (
)
type StepRegisterAMI struct {
PollingConfig *awscommon.AWSPollingConfig
EnableAMIENASupport confighelper.Trilean
EnableAMISriovNetSupport bool
AMISkipBuildRegion bool
@@ -81,7 +80,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
// Wait for the image to become ready
ui.Say("Waiting for AMI to become ready...")
if err := s.PollingConfig.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
+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.
+5 -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,8 @@ 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),
NewStepDeleteResourceGroup(azureClient, ui),
NewStepDeleteOSDisk(azureClient, ui),
NewStepDeleteAdditionalDisks(azureClient, ui),
}
} else if b.config.OSType == constants.Target_Windows {
@@ -263,6 +264,8 @@ 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),
NewStepDeleteResourceGroup(azureClient, ui),
NewStepDeleteOSDisk(azureClient, ui),
NewStepDeleteAdditionalDisks(azureClient, ui),
)
} else {
@@ -390,7 +393,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)
+1 -5
View File
@@ -223,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"
}]
}
`
-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)
}
}
}
+33 -72
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"`
@@ -104,8 +103,7 @@ type Config struct {
// Authentication via OAUTH
ClientConfig client.Config `mapstructure:",squash"`
// A list of one or more fully-qualified resource IDs of user assigned
// managed identities to be configured on the VM.
// If set with one or more resource ids of user assigned managed identities, they will be configured on the VM.
// See [documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token)
// for how to acquire tokens within the VM.
// To assign a user assigned managed identity to a VM, the provided account or service principal must have [Managed Identity Operator](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#managed-identity-operator)
@@ -119,64 +117,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 +253,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 +478,7 @@ func (c *Config) toImageParameters() *compute.Image {
},
},
Location: to.StringPtr(c.Location),
Tags: azcommon.MapToAzureTags(c.AzureTags),
Tags: c.AzureTags,
}
}
@@ -596,7 +561,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 {
@@ -804,8 +772,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)))
}
}
}
@@ -819,10 +787,6 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
for _, rid := range c.UserAssignedManagedIdentities {
r, err := client.ParseResourceID(rid)
if err != nil {
err := fmt.Errorf("Error parsing resource ID from `user_assigned_managed_identities`; please make sure"+
" that this value follows the full resource id format: "+
"/subscriptions/<SUBSCRIPTON_ID>/resourcegroups/<RESOURCE_GROUP>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<USER_ASSIGNED_IDENTITY_NAME>.\n"+
" Original error: %s", err)
errs = packer.MultiErrorAppend(errs, err)
} else {
if !strings.EqualFold(r.Provider, "Microsoft.ManagedIdentity") {
@@ -1009,9 +973,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.
@@ -1060,13 +1021,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 -3
View File
@@ -49,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"`
@@ -311,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"`
@@ -331,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"
}
+21 -30
View File
@@ -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 {
@@ -117,30 +111,27 @@ func (s *StepCreateResourceGroup) Cleanup(state multistep.StateBag) {
if state.Get(constants.ArmIsExistingResourceGroup).(bool) {
ui.Say("\nThe resource group was not created by Packer, not deleting ...")
return
}
} else {
ui.Say("\nCleanup requested, deleting resource group ...")
ctx := context.TODO()
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
if exists, err := s.exists(ctx, resourceGroupName); !exists || err != nil {
return
}
ui.Say("\nCleanup requested, deleting resource group ...")
f, err := s.client.GroupsClient.Delete(ctx, resourceGroupName)
if err == nil {
if state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
s.say(fmt.Sprintf("\n Not waiting for Resource Group delete as requested by user. Resource Group Name is %s", resourceGroupName))
} else {
err = f.WaitForCompletionRef(ctx, s.client.GroupsClient.Client)
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
ctx := context.TODO()
f, err := s.client.GroupsClient.Delete(ctx, resourceGroupName)
if err == nil {
if state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
s.say(fmt.Sprintf("\n Not waiting for Resource Group delete as requested by user. Resource Group Name is %s", resourceGroupName))
} else {
err = f.WaitForCompletionRef(ctx, s.client.GroupsClient.Client)
}
}
if err != nil {
ui.Error(fmt.Sprintf("Error deleting resource group. Please delete it manually.\n\n"+
"Name: %s\n"+
"Error: %s", resourceGroupName, err))
return
}
if !state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
ui.Say("Resource group has been deleted.")
}
}
if err != nil {
ui.Error(fmt.Sprintf("Error deleting resource group. Please delete it manually.\n\n"+
"Name: %s\n"+
"Error: %s", resourceGroupName, err))
return
}
if !state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
ui.Say("Resource group has been deleted.")
}
}
@@ -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)
}
}
@@ -35,13 +35,8 @@ func NewStepDeleteAdditionalDisks(client *AzureClient, ui packer.Ui) *StepDelete
func (s *StepDeleteAdditionalDisk) deleteBlob(storageContainerName string, blobName string) error {
blob := s.client.BlobStorageClient.GetContainerReference(storageContainerName).GetBlobReference(blobName)
_, err := blob.BreakLease(nil)
if err != nil && !strings.Contains(err.Error(), "LeaseNotPresentWithLeaseOperation") {
s.say(s.client.LastError.Error())
return err
}
err := blob.Delete(nil)
err = blob.Delete(nil)
if err != nil {
s.say(s.client.LastError.Error())
}
@@ -85,26 +80,25 @@ func (s *StepDeleteAdditionalDisk) Run(ctx context.Context, state multistep.Stat
s.say("Failed to delete the managed Additional Disk!")
return processStepResult(err, s.error, state)
}
continue
}
u, err := url.Parse(additionaldisk)
if err != nil {
s.say("Failed to parse the Additional Disk's VHD URI!")
return processStepResult(err, s.error, state)
}
xs := strings.Split(u.Path, "/")
if len(xs) < 3 {
err = errors.New("Failed to parse Additional Disk's VHD URI!")
} else {
var storageAccountName = xs[1]
var blobName = strings.Join(xs[2:], "/")
u, err := url.Parse(additionaldisk)
if err != nil {
s.say("Failed to parse the Additional Disk's VHD URI!")
return processStepResult(err, s.error, state)
}
err = s.delete(storageAccountName, blobName)
}
if err != nil {
return processStepResult(err, s.error, state)
xs := strings.Split(u.Path, "/")
if len(xs) < 3 {
err = errors.New("Failed to parse Additional Disk's VHD URI!")
} else {
var storageAccountName = xs[1]
var blobName = strings.Join(xs[2:], "/")
err = s.delete(storageAccountName, blobName)
}
if err != nil {
return processStepResult(err, s.error, state)
}
}
}
return multistep.ActionContinue
+99
View File
@@ -0,0 +1,99 @@
package arm
import (
"context"
"errors"
"fmt"
"net/url"
"strings"
"github.com/hashicorp/packer/builder/azure/common/constants"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepDeleteOSDisk struct {
client *AzureClient
delete func(string, string) error
deleteManaged func(context.Context, string, string) error
say func(message string)
error func(e error)
}
func NewStepDeleteOSDisk(client *AzureClient, ui packer.Ui) *StepDeleteOSDisk {
var step = &StepDeleteOSDisk{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.delete = step.deleteBlob
step.deleteManaged = step.deleteManagedDisk
return step
}
func (s *StepDeleteOSDisk) deleteBlob(storageContainerName string, blobName string) error {
blob := s.client.BlobStorageClient.GetContainerReference(storageContainerName).GetBlobReference(blobName)
err := blob.Delete(nil)
if err != nil {
s.say(s.client.LastError.Error())
}
return err
}
func (s *StepDeleteOSDisk) deleteManagedDisk(ctx context.Context, resourceGroupName string, imageName string) error {
xs := strings.Split(imageName, "/")
diskName := xs[len(xs)-1]
f, err := s.client.DisksClient.Delete(ctx, resourceGroupName, diskName)
if err == nil {
err = f.WaitForCompletionRef(ctx, s.client.DisksClient.Client)
}
return err
}
func (s *StepDeleteOSDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
s.say("Deleting the temporary OS disk ...")
var osDisk = state.Get(constants.ArmOSDiskVhd).(string)
var isManagedDisk = state.Get(constants.ArmIsManagedImage).(bool)
var isExistingResourceGroup = state.Get(constants.ArmIsExistingResourceGroup).(bool)
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
if isManagedDisk && !isExistingResourceGroup {
s.say(fmt.Sprintf(" -> OS Disk : skipping, managed disk was used..."))
return multistep.ActionContinue
}
s.say(fmt.Sprintf(" -> OS Disk : '%s'", osDisk))
var err error
if isManagedDisk {
err = s.deleteManaged(ctx, resourceGroupName, osDisk)
if err != nil {
s.say("Failed to delete the managed OS Disk!")
return processStepResult(err, s.error, state)
}
return multistep.ActionContinue
}
u, err := url.Parse(osDisk)
if err != nil {
s.say("Failed to parse the OS Disk's VHD URI!")
return processStepResult(err, s.error, state)
}
xs := strings.Split(u.Path, "/")
if len(xs) < 3 {
err = errors.New("Failed to parse OS Disk's VHD URI!")
} else {
var storageAccountName = xs[1]
var blobName = strings.Join(xs[2:], "/")
err = s.delete(storageAccountName, blobName)
}
return processStepResult(err, s.error, state)
}
func (*StepDeleteOSDisk) Cleanup(multistep.StateBag) {
}
@@ -0,0 +1,227 @@
package arm
import (
"context"
"errors"
"fmt"
"testing"
"github.com/hashicorp/packer/builder/azure/common/constants"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepDeleteOSDiskShouldFailIfGetFails(t *testing.T) {
var testSubject = &StepDeleteOSDisk{
delete: func(string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
deleteManaged: func(context.Context, string, string) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://storage.blob.core.windows.net/images/pkrvm_os.vhd")
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)
}
}
func TestStepDeleteOSDiskShouldPassIfGetPasses(t *testing.T) {
var testSubject = &StepDeleteOSDisk{
delete: func(string, string) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://storage.blob.core.windows.net/images/pkrvm_os.vhd")
var result = testSubject.Run(context.Background(), stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepDeleteOSDiskShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualStorageContainerName string
var actualBlobName string
var testSubject = &StepDeleteOSDisk{
delete: func(storageContainerName string, blobName string) error {
actualStorageContainerName = storageContainerName
actualBlobName = blobName
return nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://storage.blob.core.windows.net/images/pkrvm_os.vhd")
var result = testSubject.Run(context.Background(), stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if actualStorageContainerName != "images" {
t.Fatalf("Expected the storage container name to be 'images', but found '%s'.", actualStorageContainerName)
}
if actualBlobName != "pkrvm_os.vhd" {
t.Fatalf("Expected the blob name to be 'pkrvm_os.vhd', but found '%s'.", actualBlobName)
}
}
func TestStepDeleteOSDiskShouldHandleComplexStorageContainerNames(t *testing.T) {
var actualStorageContainerName string
var actualBlobName string
var testSubject = &StepDeleteOSDisk{
delete: func(storageContainerName string, blobName string) error {
actualStorageContainerName = storageContainerName
actualBlobName = blobName
return nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://storage.blob.core.windows.net/abc/def/pkrvm_os.vhd")
testSubject.Run(context.Background(), stateBag)
if actualStorageContainerName != "abc" {
t.Fatalf("Expected the storage container name to be 'abc/def', but found '%s'.", actualStorageContainerName)
}
if actualBlobName != "def/pkrvm_os.vhd" {
t.Fatalf("Expected the blob name to be 'pkrvm_os.vhd', but found '%s'.", actualBlobName)
}
}
func TestStepDeleteOSDiskShouldFailIfVHDNameCannotBeURLParsed(t *testing.T) {
var testSubject = &StepDeleteOSDisk{
delete: func(string, string) error { return nil },
say: func(message string) {},
error: func(e error) {},
deleteManaged: func(context.Context, string, string) error { return nil },
}
// Invalid URL per https://golang.org/src/net/url/url_test.go
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://[fe80::1%en0]/")
var result = testSubject.Run(context.Background(), stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%v'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to not stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepDeleteOSDiskShouldFailIfVHDNameIsTooShort(t *testing.T) {
var testSubject = &StepDeleteOSDisk{
delete: func(string, string) error { return nil },
say: func(message string) {},
error: func(e error) {},
deleteManaged: func(context.Context, string, string) error { return nil },
}
stateBag := DeleteTestStateBagStepDeleteOSDisk("storage.blob.core.windows.net/abc")
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 not stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepDeleteOSDiskShouldPassIfManagedDiskInTempResourceGroup(t *testing.T) {
var testSubject = &StepDeleteOSDisk{
delete: func(string, string) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmOSDiskVhd, "subscriptions/123-456-789/resourceGroups/existingresourcegroup/providers/Microsoft.Compute/disks/osdisk")
stateBag.Put(constants.ArmIsManagedImage, true)
stateBag.Put(constants.ArmIsExistingResourceGroup, false)
stateBag.Put(constants.ArmResourceGroupName, "testgroup")
var result = testSubject.Run(context.Background(), stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepDeleteOSDiskShouldFailIfManagedDiskInExistingResourceGroupFailsToDelete(t *testing.T) {
var testSubject = &StepDeleteOSDisk{
delete: func(string, string) error { return nil },
say: func(message string) {},
error: func(e error) {},
deleteManaged: func(context.Context, string, string) error { return errors.New("UNIT TEST FAIL!") },
}
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmOSDiskVhd, "subscriptions/123-456-789/resourceGroups/existingresourcegroup/providers/Microsoft.Compute/disks/osdisk")
stateBag.Put(constants.ArmIsManagedImage, true)
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
stateBag.Put(constants.ArmResourceGroupName, "testgroup")
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 not stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepDeleteOSDiskShouldFailIfManagedDiskInExistingResourceGroupIsDeleted(t *testing.T) {
var testSubject = &StepDeleteOSDisk{
delete: func(string, string) error { return nil },
say: func(message string) {},
error: func(e error) {},
deleteManaged: func(context.Context, string, string) error { return nil },
}
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmOSDiskVhd, "subscriptions/123-456-789/resourceGroups/existingresourcegroup/providers/Microsoft.Compute/disks/osdisk")
stateBag.Put(constants.ArmIsManagedImage, true)
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
stateBag.Put(constants.ArmResourceGroupName, "testgroup")
var result = testSubject.Run(context.Background(), stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func DeleteTestStateBagStepDeleteOSDisk(osDiskVhd string) multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmOSDiskVhd, osDiskVhd)
stateBag.Put(constants.ArmIsManagedImage, false)
stateBag.Put(constants.ArmIsExistingResourceGroup, false)
stateBag.Put(constants.ArmResourceGroupName, "testgroup")
return stateBag
}
@@ -0,0 +1,153 @@
package arm
import (
"context"
"fmt"
"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"
)
const (
maxResourcesToDelete = 50
)
type StepDeleteResourceGroup struct {
client *AzureClient
delete func(ctx context.Context, state multistep.StateBag, resourceGroupName string) error
say func(message string)
error func(e error)
}
func NewStepDeleteResourceGroup(client *AzureClient, ui packer.Ui) *StepDeleteResourceGroup {
var step = &StepDeleteResourceGroup{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.delete = step.deleteResourceGroup
return step
}
func (s *StepDeleteResourceGroup) deleteResourceGroup(ctx context.Context, state multistep.StateBag, resourceGroupName string) error {
var err error
if state.Get(constants.ArmIsExistingResourceGroup).(bool) {
s.say("\nThe resource group was not created by Packer, only deleting individual resources ...")
var deploymentName = state.Get(constants.ArmDeploymentName).(string)
err = s.deleteDeploymentResources(ctx, deploymentName, resourceGroupName)
if err != nil {
return err
}
if keyVaultDeploymentName, ok := state.GetOk(constants.ArmKeyVaultDeploymentName); ok {
// Only delete if custom keyvault was not provided.
if exists := state.Get(constants.ArmIsExistingKeyVault).(bool); !exists {
s.say("\n Deleting the keyvault deployment because it was created by Packer...")
err = s.deleteDeploymentResources(ctx, keyVaultDeploymentName.(string), resourceGroupName)
if err != nil {
return err
}
}
}
return nil
} else {
s.say("\nThe resource group was created by Packer, deleting ...")
f, err := s.client.GroupsClient.Delete(ctx, resourceGroupName)
if err == nil {
if state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
// No need to wait for the completion for delete if request is Accepted
s.say(fmt.Sprintf("\nResource Group is being deleted, not waiting for deletion due to config. Resource Group Name '%s'", resourceGroupName))
} else {
f.WaitForCompletionRef(ctx, s.client.GroupsClient.Client)
}
}
if err != nil {
s.say(s.client.LastError.Error())
}
return err
}
}
func (s *StepDeleteResourceGroup) deleteDeploymentResources(ctx context.Context, deploymentName, resourceGroupName string) error {
maxResources := int32(maxResourcesToDelete)
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))
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 = deploymentOperations.Next(); err != nil {
return err
}
}
return nil
}
func (s *StepDeleteResourceGroup) 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)
}
}
func (s *StepDeleteResourceGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
s.say("Deleting resource group ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
err := s.delete(ctx, state, resourceGroupName)
if err != nil {
state.Put(constants.Error, err)
s.error(err)
return multistep.ActionHalt
}
state.Put(constants.ArmIsResourceGroupCreated, false)
return multistep.ActionContinue
}
func (*StepDeleteResourceGroup) Cleanup(multistep.StateBag) {
}
@@ -0,0 +1,78 @@
package arm
import (
"context"
"fmt"
"testing"
"github.com/hashicorp/packer/builder/azure/common/constants"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepDeleteResourceGroupShouldFailIfDeleteFails(t *testing.T) {
var testSubject = &StepDeleteResourceGroup{
delete: func(context.Context, multistep.StateBag, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteResourceGroup()
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)
}
}
func TestStepDeleteResourceGroupShouldPassIfDeletePasses(t *testing.T) {
var testSubject = &StepDeleteResourceGroup{
delete: func(context.Context, multistep.StateBag, string) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteResourceGroup()
var result = testSubject.Run(context.Background(), stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepDeleteResourceGroupShouldDeleteStateBagArmResourceGroupCreated(t *testing.T) {
var testSubject = &StepDeleteResourceGroup{
delete: func(context.Context, multistep.StateBag, string) error {
return nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteResourceGroup()
testSubject.Run(context.Background(), stateBag)
value, ok := stateBag.GetOk(constants.ArmIsResourceGroupCreated)
if !ok {
t.Fatal("Expected the resource bag value arm.IsResourceGroupCreated to exist")
}
if value.(bool) {
t.Fatalf("Expected arm.IsResourceGroupCreated to be false, but got %q", value)
}
}
func DeleteTestStateBagStepDeleteResourceGroup() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
stateBag.Put(constants.ArmIsResourceGroupCreated, "Unit Test: IsResourceGroupCreated")
return stateBag
}
+95 -119
View File
@@ -42,109 +42,6 @@ func NewStepDeployTemplate(client *AzureClient, ui packer.Ui, config *Config, de
return step
}
func (s *StepDeployTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
s.say("Deploying deployment template ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", s.name))
return processStepResult(
s.deploy(ctx, resourceGroupName, s.name),
s.error, state)
}
func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
defer func() {
err := s.deleteTemplate(context.Background(), state)
if err != nil {
s.say(s.client.LastError.Error())
}
}()
//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 {
return
}
ui := state.Get("ui").(packer.Ui)
ui.Say("\nThe resource group was not created by Packer, deleting individual resources ...")
deploymentName := s.name
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
// 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))
}
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 {
deployment, err := s.factory(s.config)
if err != nil {
@@ -165,32 +62,48 @@ func (s *StepDeployTemplate) deleteTemplate(ctx context.Context, state multistep
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 {
err = f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
}
if err != nil {
s.say(s.client.LastError.Error())
}
return err
}
func (s *StepDeployTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
s.say("Deploying deployment template ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", s.name))
return processStepResult(
s.deploy(ctx, resourceGroupName, s.name),
s.error, state)
}
func (s *StepDeployTemplate) getImageDetails(ctx context.Context, resourceGroupName string, computeName string) (string, string, error) {
//We can't depend on constants.ArmOSDiskVhd being set
var imageName, imageType string
var imageName string
var imageType string
vm, err := s.client.VirtualMachinesClient.Get(ctx, resourceGroupName, computeName, "")
if err != nil {
return imageName, imageType, err
}
if vm.StorageProfile.OsDisk.Vhd != nil {
imageType = "image"
imageName = *vm.StorageProfile.OsDisk.Vhd.URI
} else {
imageType = "Microsoft.Compute/disks"
imageName = *vm.StorageProfile.OsDisk.ManagedDisk.ID
if vm.StorageProfile.OsDisk.Vhd != nil {
imageType = "image"
imageName = *vm.StorageProfile.OsDisk.Vhd.URI
} else {
imageType = "Microsoft.Compute/disks"
imageName = *vm.StorageProfile.OsDisk.ManagedDisk.ID
}
}
return imageType, imageName, nil
}
@@ -245,7 +158,6 @@ func (s *StepDeployTemplate) deleteImage(ctx context.Context, imageType string,
}
return err
}
// VHD image
u, err := url.Parse(imageName)
if err != nil {
@@ -259,11 +171,75 @@ func (s *StepDeployTemplate) deleteImage(ctx context.Context, imageType string,
var blobName = strings.Join(xs[2:], "/")
blob := s.client.BlobStorageClient.GetContainerReference(storageAccountName).GetBlobReference(blobName)
_, err = blob.BreakLease(nil)
if err != nil && !strings.Contains(err.Error(), "LeaseNotPresentWithLeaseOperation") {
s.say(s.client.LastError.Error())
return err
err = blob.Delete(nil)
return err
}
func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
defer s.deleteTemplate(context.Background(), state)
//Only clean up if this was an existing resource group and the resource group
//is marked as created
var existingResourceGroup = state.Get(constants.ArmIsExistingResourceGroup).(bool)
var resourceGroupCreated = state.Get(constants.ArmIsResourceGroupCreated).(bool)
if !existingResourceGroup || !resourceGroupCreated {
return
}
ui := state.Get("ui").(packer.Ui)
ui.Say("\nThe resource group was not created by Packer, deleting individual resources ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var computeName = state.Get(constants.ArmComputeName).(string)
var deploymentName = s.name
imageType, imageName, err := s.disk(context.TODO(), resourceGroupName, computeName)
if err != nil {
ui.Error("Could not retrieve OS Image details")
}
return blob.Delete(nil)
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 {
deploymentOperations.Next()
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 has to be
// deleted separately
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))
}
}
}
+2 -5
View File
@@ -391,11 +391,8 @@ func checkHyperVGeneration(s string) interface{} {
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
switch runtime.GOOS {
case "linux", "freebsd":
break
default:
return nil, errors.New("the azure-chroot builder only works on Linux and FreeBSD environments")
if runtime.GOOS != "linux" {
return nil, errors.New("the azure-chroot builder only works on Linux environments")
}
err := b.config.ClientConfig.FillParameters()
+34 -2
View File
@@ -3,18 +3,24 @@ package chroot
import (
"context"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"github.com/hashicorp/packer/builder/azure/common/client"
)
type DiskAttacher interface {
AttachDisk(ctx context.Context, disk string) (lun int32, err error)
DiskPathForLun(lun int32) string
WaitForDevice(ctx context.Context, i int32) (device string, err error)
DetachDisk(ctx context.Context, disk string) (err error)
WaitForDetach(ctx context.Context, diskID string) error
@@ -32,7 +38,31 @@ type diskAttacher struct {
vm *client.ComputeInfo // store info about this VM so that we don't have to ask metadata service on every call
}
var DiskNotFoundError = errors.New("Disk not found")
func (diskAttacher) DiskPathForLun(lun int32) string {
return fmt.Sprintf("/dev/disk/azure/scsi1/lun%d", lun)
}
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
path := da.DiskPathForLun(lun)
for {
link, err := os.Readlink(path)
if err == nil {
return filepath.Abs("/dev/disk/azure/scsi1/" + link)
} else if err != os.ErrNotExist {
if pe, ok := err.(*os.PathError); ok && pe.Err != syscall.ENOENT {
return "", err
}
}
select {
case <-time.After(100 * time.Millisecond):
// continue
case <-ctx.Done():
return "", ctx.Err()
}
}
}
func (da *diskAttacher) DetachDisk(ctx context.Context, diskID string) error {
log.Println("Fetching list of disks currently attached to VM")
@@ -81,6 +111,8 @@ func (da *diskAttacher) WaitForDetach(ctx context.Context, diskID string) error
}
}
var DiskNotFoundError = errors.New("Disk not found")
func (da *diskAttacher) AttachDisk(ctx context.Context, diskID string) (int32, error) {
dataDisks, err := da.getDisks(ctx)
if err != nil {
@@ -1,54 +0,0 @@
package chroot
import (
"bufio"
"bytes"
"context"
"fmt"
"os/exec"
"regexp"
"strings"
"time"
)
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
// This builder will always be running in Azure, where data disks show up
// on scbus5 target 0. The camcontrol command always outputs LUNs in
// unpadded hexadecimal format.
regexStr := fmt.Sprintf(`at scbus5 target 0 lun %x \(.*?da([\d]+)`, lun)
devRegex, err := regexp.Compile(regexStr)
if err != nil {
return "", err
}
for {
cmd := exec.Command("camcontrol", "devlist")
var out bytes.Buffer
cmd.Stdout = &out
err = cmd.Run()
if err != nil {
return "", err
}
outString := out.String()
scanner := bufio.NewScanner(strings.NewReader(outString))
for scanner.Scan() {
line := scanner.Text()
// Check if this is the correct bus, target, and LUN.
if matches := devRegex.FindStringSubmatch(line); matches != nil {
// If this function immediately returns, devfs won't have
// created the device yet.
time.Sleep(1000 * time.Millisecond)
return fmt.Sprintf("/dev/da%s", matches[1]), nil
}
}
if err = scanner.Err(); err != nil {
return "", err
}
select {
case <-time.After(100 * time.Millisecond):
// continue
case <-ctx.Done():
return "", ctx.Err()
}
}
}
@@ -1,36 +0,0 @@
package chroot
import (
"context"
"fmt"
"os"
"path/filepath"
"syscall"
"time"
)
func diskPathForLun(lun int32) string {
return fmt.Sprintf("/dev/disk/azure/scsi1/lun%d", lun)
}
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
path := diskPathForLun(lun)
for {
link, err := os.Readlink(path)
if err == nil {
return filepath.Abs("/dev/disk/azure/scsi1/" + link)
} else if err != os.ErrNotExist {
if pe, ok := err.(*os.PathError); ok && pe.Err != syscall.ENOENT {
return "", err
}
}
select {
case <-time.After(100 * time.Millisecond):
// continue
case <-ctx.Done():
return "", ctx.Err()
}
}
}
@@ -1,11 +0,0 @@
// +build !linux,!freebsd
package chroot
import (
"context"
)
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
panic("The azure-chroot builder does not work on this platform.")
}
@@ -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")
+1 -8
View File
@@ -9,7 +9,6 @@ import (
"log"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/hashicorp/packer/common"
@@ -63,13 +62,7 @@ func (s *StepMountDevice) Run(ctx context.Context, state multistep.StateBag) mul
return multistep.ActionHalt
}
var deviceMount string
switch runtime.GOOS {
case "freebsd":
deviceMount = fmt.Sprintf("%sp%s", device, s.MountPartition)
default:
deviceMount = fmt.Sprintf("%s%s", device, s.MountPartition)
}
deviceMount := fmt.Sprintf("%s%s", device, s.MountPartition)
state.Put("deviceMount", deviceMount)
@@ -1,72 +0,0 @@
package chroot
import (
"context"
"fmt"
"io/ioutil"
"os"
"runtime"
"testing"
"time"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepMountDevice_Run(t *testing.T) {
switch runtime.GOOS {
case "linux", "freebsd":
break
default:
t.Skip("Unsupported operating system")
}
mountPath, err := ioutil.TempDir("", "stepmountdevicetest")
if err != nil {
t.Errorf("Unable to create a temporary directory: %q", err)
}
step := &StepMountDevice{
MountOptions: []string{"foo"},
MountPartition: "42",
MountPath: mountPath,
}
var gotCommand string
var wrapper common.CommandWrapper
wrapper = func(ran string) (string, error) {
gotCommand = ran
return "", nil
}
state := new(multistep.BasicStateBag)
state.Put("wrappedCommand", wrapper)
state.Put("device", "/dev/quux")
ui, getErrs := testUI()
state.Put("ui", ui)
var config Config
state.Put("config", &config)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
got := step.Run(ctx, state)
if got != multistep.ActionContinue {
t.Errorf("Expected 'continue', but got '%v'", got)
}
var expectedMountDevice string
switch runtime.GOOS {
case "freebsd":
expectedMountDevice = "/dev/quuxp42"
default: // currently just Linux
expectedMountDevice = "/dev/quux42"
}
expectedCommand := fmt.Sprintf("mount -o foo %s %s", expectedMountDevice, mountPath)
if gotCommand != expectedCommand {
t.Errorf("Expected '%v', but got '%v'", expectedCommand, gotCommand)
}
os.Remove(mountPath)
_ = getErrs
}
-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 -10
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 {
@@ -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
}
-2
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"`
@@ -133,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},
+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
@@ -5,7 +5,6 @@ import (
"log"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
@@ -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 := &builder.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)
-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/helper/builder/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/builder"
"github.com/hashicorp/packer/helper/multistep"
)
type StepSetGeneratedData struct {
GeneratedData *builder.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/builder"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepSetGeneratedData_Run(t *testing.T) {
state := testState(t)
step := new(StepSetGeneratedData)
step.GeneratedData = &builder.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 = &builder.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)
}
}
+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
}
+6 -14
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
}
@@ -77,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,
+5 -6
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
@@ -267,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
@@ -281,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
}
@@ -482,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 {
-2
View File
@@ -64,7 +64,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"`
AccountFile *string `mapstructure:"account_file" required:"false" cty:"account_file" hcl:"account_file"`
ImpersonateServiceAccount *string `mapstructure:"impersonate_service_account" required:"false" cty:"impersonate_service_account" hcl:"impersonate_service_account"`
ProjectId *string `mapstructure:"project_id" required:"true" cty:"project_id" hcl:"project_id"`
AcceleratorType *string `mapstructure:"accelerator_type" required:"false" cty:"accelerator_type" hcl:"accelerator_type"`
AcceleratorCount *int64 `mapstructure:"accelerator_count" required:"false" cty:"accelerator_count" hcl:"accelerator_count"`
@@ -183,7 +182,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},
"account_file": &hcldec.AttrSpec{Name: "account_file", Type: cty.String, Required: false},
"impersonate_service_account": &hcldec.AttrSpec{Name: "impersonate_service_account", Type: cty.String, Required: false},
"project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false},
"accelerator_type": &hcldec.AttrSpec{Name: "accelerator_type", Type: cty.String, Required: false},
"accelerator_count": &hcldec.AttrSpec{Name: "accelerator_count", Type: cty.Number, Required: false},
+20 -31
View File
@@ -10,11 +10,11 @@ import (
"errors"
"fmt"
"log"
"net/http"
"strings"
"time"
compute "google.golang.org/api/compute/v1"
"google.golang.org/api/option"
oslogin "google.golang.org/api/oslogin/v1"
"github.com/hashicorp/packer/common/retry"
@@ -24,6 +24,7 @@ import (
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
)
// driverGCE is a Driver implementation that actually talks to GCE.
@@ -35,14 +36,6 @@ type driverGCE struct {
ui packer.Ui
}
type GCEDriverConfig struct {
Ui packer.Ui
ProjectId string
Account *ServiceAccount
ImpersonateServiceAccountName string
VaultOauthEngineName string
}
var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"}
// Define a TokenSource that gets tokens from Vault
@@ -78,34 +71,31 @@ func (ots OauthTokenSource) Token() (*oauth2.Token, error) {
}
func NewClientOptionGoogle(account *ServiceAccount, vaultOauth string, impersonatesa string) (option.ClientOption, error) {
func NewClientGCE(conf *jwt.Config, vaultOauth string) (*http.Client, error) {
var err error
var opts option.ClientOption
var client *http.Client
if vaultOauth != "" {
// Auth with Vault Oauth
log.Printf("Using Vault to generate Oauth token.")
ts := OauthTokenSource{vaultOauth}
opts = option.WithTokenSource(ts)
return oauth2.NewClient(context.TODO(), ts), nil
} else if impersonatesa != "" {
opts = option.ImpersonateCredentials(impersonatesa)
} else if account != nil && account.jwt != nil && len(account.jwt.PrivateKey) > 0 {
} else if conf != nil && len(conf.PrivateKey) > 0 {
// Auth with AccountFile if provided
log.Printf("[INFO] Requesting Google token via account_file...")
log.Printf("[INFO] -- Email: %s", account.jwt.Email)
log.Printf("[INFO] -- Email: %s", conf.Email)
log.Printf("[INFO] -- Scopes: %s", DriverScopes)
log.Printf("[INFO] -- Private Key Length: %d", len(account.jwt.PrivateKey))
log.Printf("[INFO] -- Private Key Length: %d", len(conf.PrivateKey))
opts = option.WithCredentialsJSON(account.jsonKey)
// Initiate an http.Client. The following GET request will be
// authorized and authenticated on the behalf of
// your service account.
client = conf.Client(context.TODO())
} else {
log.Printf("[INFO] Requesting Google token via GCE API Default Client Token Source...")
ts, err := google.DefaultTokenSource(context.TODO(), "https://www.googleapis.com/auth/cloud-platform")
if err != nil {
return nil, err
}
opts = option.WithTokenSource(ts)
client, err = google.DefaultClient(context.TODO(), DriverScopes...)
// The DefaultClient uses the DefaultTokenSource of the google lib.
// The DefaultTokenSource uses the "Application Default Credentials"
// It looks for credentials in the following places, preferring the first location found:
@@ -124,23 +114,22 @@ func NewClientOptionGoogle(account *ServiceAccount, vaultOauth string, impersona
return nil, err
}
return opts, nil
return client, nil
}
func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
opts, err := NewClientOptionGoogle(config.Account, config.VaultOauthEngineName, config.ImpersonateServiceAccountName)
func NewDriverGCE(ui packer.Ui, p string, conf *jwt.Config, vaultOauth string) (Driver, error) {
client, err := NewClientGCE(conf, vaultOauth)
if err != nil {
return nil, err
}
log.Printf("[INFO] Instantiating GCE client...")
service, err := compute.NewService(context.TODO(), opts)
service, err := compute.New(client)
if err != nil {
return nil, err
}
log.Printf("[INFO] Instantiating OS Login client...")
osLoginService, err := oslogin.NewService(context.TODO(), opts)
osLoginService, err := oslogin.New(client)
if err != nil {
return nil, err
}
@@ -149,10 +138,10 @@ func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
service.UserAgent = useragent.String()
return &driverGCE{
projectId: config.ProjectId,
projectId: p,
service: service,
osLoginService: osLoginService,
ui: config.Ui,
ui: ui,
}, nil
}
@@ -56,7 +56,7 @@ func (s *StepCreateWindowsPassword) Run(ctx context.Context, state multistep.Sta
email := ""
if c.account != nil {
email = c.account.jwt.Email
email = c.account.Email
}
data := WindowsPasswordConfig{
@@ -47,7 +47,7 @@ func (s *StepImportOSLoginSSHKey) Run(ctx context.Context, state multistep.State
state.Put("ssh_key_public_sha256", hex.EncodeToString(sha256sum[:]))
if config.account != nil {
s.accountEmail = config.account.jwt.Email
s.accountEmail = config.account.Email
}
if s.accountEmail == "" {

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