Compare commits
1 Commits
v1.6.5
..
fix_100000
| Author | SHA1 | Date | |
|---|---|---|---|
| bd0ba33ce1 |
@@ -7,11 +7,11 @@ version: 2.1
|
||||
executors:
|
||||
golang:
|
||||
docker:
|
||||
- image: docker.mirror.hashicorp.services/circleci/golang:1.15
|
||||
- image: circleci/golang:1.13
|
||||
resource_class: medium+
|
||||
darwin:
|
||||
macos:
|
||||
xcode: "12.0.0"
|
||||
xcode: "9.0"
|
||||
|
||||
commands:
|
||||
install-go-run-tests-unix:
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
steps:
|
||||
- install-go-run-tests-unix:
|
||||
GOOS: darwin
|
||||
GOVERSION: "1.15"
|
||||
GOVERSION: "1.13"
|
||||
- codecov/upload:
|
||||
file: coverage.txt
|
||||
test-windows:
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
shell: bash.exe
|
||||
steps:
|
||||
- install-go-run-tests-windows:
|
||||
GOVERSION: "1.15"
|
||||
GOVERSION: "1.13"
|
||||
- codecov/upload:
|
||||
file: coverage.txt
|
||||
check-lint:
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
destination: /
|
||||
build-website-docker-image:
|
||||
docker:
|
||||
- image: docker.mirror.hashicorp.services/circleci/buildpack-deps
|
||||
- image: circleci/buildpack-deps
|
||||
shell: /usr/bin/env bash -euo pipefail -c
|
||||
steps:
|
||||
- checkout
|
||||
@@ -167,14 +167,14 @@ jobs:
|
||||
echo "Dependencies have not changed, not building a new website docker image."
|
||||
else
|
||||
cd website/
|
||||
docker login -u $WEBSITE_DOCKER_USER -p $WEBSITE_DOCKER_PASS
|
||||
docker build -t hashicorp/packer-website:$IMAGE_TAG .
|
||||
docker tag hashicorp/packer-website:$IMAGE_TAG hashicorp/packer-website:latest
|
||||
docker login -u $WEBSITE_DOCKER_USER -p $WEBSITE_DOCKER_PASS
|
||||
docker push hashicorp/packer-website
|
||||
fi
|
||||
algolia-index:
|
||||
docker:
|
||||
- image: docker.mirror.hashicorp.services/node:12
|
||||
- image: node:12
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,4 +27,3 @@ Thumbs.db
|
||||
/packer.exe
|
||||
.project
|
||||
cache
|
||||
/.vscode/
|
||||
|
||||
+2
-88
@@ -1,90 +1,4 @@
|
||||
## 1.6.5 (October 30, 2020)
|
||||
|
||||
### FEATURES:
|
||||
* New Builder(s): Proxmox builder has been split into two new builders
|
||||
`proxmox-iso` and `promox-clone`. See [Proxmox
|
||||
Builder](https://packer.io/docs/builders/proxmox) for more information on
|
||||
the builder. For users of the previous `proxmox` builder please use `packer
|
||||
fix` to migrate your templates to the new `promox-iso` builder. [GH-9262]
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
* builder/amazon: SSM connection now recovers from reboots. [GH-10003]
|
||||
* builder/azure-arm: Fix build failures due to the deletion of additional
|
||||
managed disks defined in "disk_additional_size". [GH-10163]
|
||||
* builder/azure-chroot: Fix typo in option `exlude_from_latest` to
|
||||
`exclude_from_latest`. Old name will still be respected. [GH-10034]
|
||||
* builder/googlecompute: Fix HCL image_encryption_key fields and use the same
|
||||
casing in JSON and HCL2 [GH-10173]
|
||||
* builder/openstack: Fix source image validation regression when using filters.
|
||||
[GH-10065]
|
||||
* builder/proxmox: Fix unhandled buildvar type for HCL2 enabled build
|
||||
templates. [GH-10154]
|
||||
* builder/qemu: Fix a regression where Packer would not wait properly in
|
||||
step_shutdown when a null communicator was used. [GH-10178]
|
||||
* builder/qemu: Fix crash in step_run of qemu when loading commhostport form
|
||||
the statebag in a situation where the communicator is none. [GH-10145]
|
||||
* builder/vsphere-clone: Packer was not respecting the "destroy" flag set in
|
||||
the content library config, and always keeping the source vm. This has been
|
||||
fixed. [GH-10165]
|
||||
* builder/vsphere: Ensure builds are able to continue when no communicator has
|
||||
been specified `"communicator": "none"`. [GH-9964]
|
||||
* builder/vsphere: Fix CD uploads so that Packer does not try to delete a CD
|
||||
that was not successfully uploaded. [GH-10155]
|
||||
* core/hcl: Hide sensitive variables from output. [GH-10031]
|
||||
* core/hcl: Packer HCL's "Coalesce" function now behaves same way as
|
||||
Terraform's. [GH-10016]
|
||||
* core: Fix artifact handling so that input artifacts are properly preserved in
|
||||
postprocessors that don't modify artifacts. [GH-9996]
|
||||
* core: Fix pathing in cd_files to copy proper directory tree when user
|
||||
provided absolute paths. [GH-10022]
|
||||
* provisioner/ansible: Ansible galaxy no longer forces use of collections in v1
|
||||
files. [GH-10010]
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
* builder/amazon-ebssurrogate: Apply snapshot tags at snapshot creation time.
|
||||
[GH-10150]
|
||||
* builder/amazon: Add `io2` as a supported volume type. [GH-10102]
|
||||
* builder/amazon: Add support for source instance tenancy [GH-10085]
|
||||
* builder/google: Add service account impersonation. [GH-9968] [GH-10054]
|
||||
* builder/googlecompute: Add `skip_create_image` option. [GH-10115]
|
||||
* builder/googlecompute: Allow users to select the algorithm to use when
|
||||
generating temporary SSH keypair [GH-10111]
|
||||
* builder/linode: Add `state_timeout` attribute to Linode builder. [GH-10128]
|
||||
* builder/oracle-oci: New option to specify image compartment separate from
|
||||
build compartment. [GH-10040]
|
||||
* builder/oracle-oci: New option to specify boot volume size. [GH-10017]
|
||||
* builder/oracle: Add `base_image_filter` option as alternative to
|
||||
`base_image_ocid` [GH-10116]
|
||||
* builder/outscale: Migrate to new Outscale SDK. [GH-10056]
|
||||
* builder/proxmox: split Proxmox into proxmox-iso and proxmox-clone. [GH-9626]
|
||||
[GH-10166]
|
||||
* builder/scaleway: Allow the user to use an image label (eg ubuntu_focal)
|
||||
instead of a hardcoded UUID on the Scaleway builder. [GH-10061]
|
||||
* builder/vsphere: Skip iso download if hashed file is already present on
|
||||
remote datastore. [GH-10143]
|
||||
* builder/yandex: Add support for IAM credentials in the token field and
|
||||
YC_TOKEN environment variable. [GH-10158]
|
||||
* core/hcl: Add ability to set version restrictions [GH-10149]
|
||||
* core/hcl: Add build.name variable so users can access build name in addition
|
||||
to source name. [GH-10114]
|
||||
* core/hcl: Add consul_key function to HCL templates. [GH-10119]
|
||||
* core/hcl: Add HCL2 aws_secretsmanager function [GH-10124]
|
||||
* core/hcl: Add packer.version variable to hcl configs so users can access the
|
||||
Packer release version. [GH-10117]
|
||||
* core: Let user provide type of generated ssh key instead of always doing ssh-
|
||||
rsa [GH-10101]
|
||||
|
||||
## 1.6.4 (September 30, 2020)
|
||||
|
||||
### BUG FIXES:
|
||||
* builder/amazon: Fix authentication issue when using instance profiles or
|
||||
assumed roles for loading session-derived credentials. [GH-10007]
|
||||
* builder/azure: Fix crash when using `azure_tag` or `azure_tags` configuration
|
||||
options. [GH-10014]
|
||||
* builder/qemu: Ensure `qemu_img_args` are honored during the disk convert
|
||||
step. [GH-10001]
|
||||
## 1.6.4 (Upcoming)
|
||||
|
||||
## 1.6.3 (September 25, 2020)
|
||||
|
||||
@@ -107,7 +21,7 @@
|
||||
* 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.
|
||||
* builder/proxmox: Add ability to specify interfaces for http_directroy and VM.
|
||||
[GH-9874]
|
||||
* builder/proxmox: Allow the mounting of multiple ISOs via the `cd_drive`
|
||||
option. [GH-9653]
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM docker.mirror.hashicorp.services/ubuntu:16.04
|
||||
FROM ubuntu:16.04
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
TEST?=$(shell go list ./...)
|
||||
COUNT?=1
|
||||
VET?=$(shell go list ./...)
|
||||
ACC_TEST_BUILDERS?=all
|
||||
ACC_TEST_PROVISIONERS?=all
|
||||
@@ -137,7 +136,7 @@ generate-check: generate ## Check go code generation is on par
|
||||
fi
|
||||
|
||||
test: mode-check vet ## Run unit tests
|
||||
@go test -count $(COUNT) $(TEST) $(TESTARGS) -timeout=3m
|
||||
@go test $(TEST) $(TESTARGS) -timeout=3m
|
||||
|
||||
# acctest runs provisioners acceptance tests
|
||||
provisioners-acctest: install-build-deps generate
|
||||
@@ -146,14 +145,14 @@ provisioners-acctest: install-build-deps generate
|
||||
# testacc runs acceptance tests
|
||||
testacc: install-build-deps generate ## Run acceptance tests
|
||||
@echo "WARN: Acceptance tests will take a long time to run and may cost money. Ctrl-C if you want to cancel."
|
||||
PACKER_ACC=1 go test -count $(COUNT) -v $(TEST) $(TESTARGS) -timeout=120m
|
||||
PACKER_ACC=1 go test -v $(TEST) $(TESTARGS) -timeout=45m
|
||||
|
||||
testrace: mode-check vet ## Test with race detection enabled
|
||||
@GO111MODULE=off go test -count $(COUNT) -race $(TEST) $(TESTARGS) -timeout=3m -p=8
|
||||
@GO111MODULE=off go test -race $(TEST) $(TESTARGS) -timeout=3m -p=8
|
||||
|
||||
# Runs code coverage and open a html page with report
|
||||
cover:
|
||||
go test -count $(COUNT) $(TEST) $(TESTARGS) -timeout=3m -coverprofile=coverage.out
|
||||
go test $(TEST) $(TESTARGS) -timeout=3m -coverprofile=coverage.out
|
||||
go tool cover -html=coverage.out
|
||||
rm coverage.out
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
|
||||
@@ -107,8 +107,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -223,8 +221,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,6 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
|
||||
@@ -52,7 +52,6 @@ type FlatConfig struct {
|
||||
RawRegion *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"`
|
||||
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
|
||||
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
||||
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
|
||||
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
||||
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
||||
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
|
||||
@@ -131,7 +130,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
|
||||
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
|
||||
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
||||
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
|
||||
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
||||
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
||||
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
|
||||
|
||||
@@ -9,17 +9,19 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
awsCredentials "github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"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"
|
||||
"github.com/aws/aws-sdk-go/service/sts"
|
||||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
vaultapi "github.com/hashicorp/vault/api"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// AssumeRoleConfig lets users set configuration options for assuming a special
|
||||
@@ -146,8 +148,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
|
||||
@@ -272,7 +272,7 @@ func (c *AccessConfig) Session() (*session.Session, error) {
|
||||
|
||||
cp, err := c.session.Config.Credentials.Get()
|
||||
|
||||
if awserrors.Matches(err, "NoCredentialProviders", "") {
|
||||
if IsAWSErr(err, "NoCredentialProviders", "") {
|
||||
return nil, c.NewNoValidCredentialSourcesError(err)
|
||||
}
|
||||
|
||||
@@ -309,35 +309,171 @@ func (c *AccessConfig) IsChinaCloud() bool {
|
||||
// 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,
|
||||
|
||||
sharedCredentialsFilename, err := homedir.Expand(c.CredsFilename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error expanding shared credentials filename: %v", err)
|
||||
}
|
||||
|
||||
return awsbase.GetCredentials(awsbaseConfig)
|
||||
// Create a credentials chain that tries to load credentials from various
|
||||
// common sources: config vars, then local profiles.
|
||||
// Rather than using the default credentials chain, build a chain provider,
|
||||
// lazy-evaluated by aws-sdk
|
||||
providers := []awsCredentials.Provider{
|
||||
// Tries to set new credentials object using the given
|
||||
// access_key/secret_key/token. If they are not set, this will fail
|
||||
// over to the other credentials providers
|
||||
&awsCredentials.StaticProvider{Value: awsCredentials.Value{
|
||||
AccessKeyID: c.AccessKey,
|
||||
SecretAccessKey: c.SecretKey,
|
||||
SessionToken: c.Token,
|
||||
}},
|
||||
// Tries to load credentials from environment.
|
||||
&awsCredentials.EnvProvider{},
|
||||
// Tries to load credentials from local file.
|
||||
// If sharedCredentialsFilename is empty, the AWS sdk will use the
|
||||
// environment var AWS_SHARED_CREDENTIALS_FILE to determine the file
|
||||
// location, and if that's not set, AWS will use the default locations
|
||||
// of:
|
||||
// - Linux/Unix: $HOME/.aws/credentials
|
||||
// - Windows: %USERPROFILE%\.aws\credentials
|
||||
&awsCredentials.SharedCredentialsProvider{
|
||||
Filename: sharedCredentialsFilename,
|
||||
Profile: c.ProfileName,
|
||||
},
|
||||
}
|
||||
|
||||
// Validate the credentials before returning them
|
||||
creds := awsCredentials.NewChainCredentials(providers)
|
||||
cp, err := creds.Get()
|
||||
|
||||
if IsAWSErr(err, "NoCredentialProviders", "") {
|
||||
creds, err = c.GetCredentialsFromSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error loading credentials for AWS Provider: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
|
||||
|
||||
// In the "normal" flow (i.e. not assuming a role), we return here.
|
||||
if c.AssumeRole.AssumeRoleARN == "" {
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
// create a config for the assume role session based off the config we
|
||||
// created for our main sessions
|
||||
assumeRoleAWSConfig := config.Copy()
|
||||
assumeRoleAWSConfig.CredentialsChainVerboseErrors = aws.Bool(true)
|
||||
|
||||
assumeRoleSession, err := session.NewSession(assumeRoleAWSConfig)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating assume role session: %v", err)
|
||||
}
|
||||
|
||||
stsclient := sts.New(assumeRoleSession)
|
||||
assumeRoleProvider := &stscreds.AssumeRoleProvider{
|
||||
Client: stsclient,
|
||||
RoleARN: c.AssumeRole.AssumeRoleARN,
|
||||
}
|
||||
|
||||
if c.AssumeRole.AssumeRoleDurationSeconds > 0 {
|
||||
assumeRoleProvider.Duration = time.Duration(c.AssumeRole.AssumeRoleDurationSeconds) * time.Second
|
||||
}
|
||||
|
||||
if c.AssumeRole.AssumeRoleExternalID != "" {
|
||||
assumeRoleProvider.ExternalID = aws.String(c.AssumeRole.AssumeRoleExternalID)
|
||||
}
|
||||
|
||||
if c.AssumeRole.AssumeRolePolicy != "" {
|
||||
assumeRoleProvider.Policy = aws.String(c.AssumeRole.AssumeRolePolicy)
|
||||
}
|
||||
|
||||
if len(c.AssumeRole.AssumeRolePolicyARNs) > 0 {
|
||||
var policyDescriptorTypes []*sts.PolicyDescriptorType
|
||||
|
||||
for _, policyARN := range c.AssumeRole.AssumeRolePolicyARNs {
|
||||
policyDescriptorType := &sts.PolicyDescriptorType{
|
||||
Arn: aws.String(policyARN),
|
||||
}
|
||||
policyDescriptorTypes = append(policyDescriptorTypes, policyDescriptorType)
|
||||
}
|
||||
|
||||
assumeRoleProvider.PolicyArns = policyDescriptorTypes
|
||||
}
|
||||
|
||||
if c.AssumeRole.AssumeRoleSessionName != "" {
|
||||
assumeRoleProvider.RoleSessionName = c.AssumeRole.AssumeRoleSessionName
|
||||
}
|
||||
|
||||
if len(c.AssumeRole.AssumeRoleTags) > 0 {
|
||||
var tags []*sts.Tag
|
||||
|
||||
for k, v := range c.AssumeRole.AssumeRoleTags {
|
||||
tag := &sts.Tag{
|
||||
Key: aws.String(k),
|
||||
Value: aws.String(v),
|
||||
}
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
assumeRoleProvider.Tags = tags
|
||||
}
|
||||
|
||||
if len(c.AssumeRole.AssumeRoleTransitiveTagKeys) > 0 {
|
||||
assumeRoleProvider.TransitiveTagKeys = aws.StringSlice(c.AssumeRole.AssumeRoleTransitiveTagKeys)
|
||||
}
|
||||
|
||||
providers = []awsCredentials.Provider{assumeRoleProvider}
|
||||
|
||||
assumeRoleCreds := awsCredentials.NewChainCredentials(providers)
|
||||
|
||||
_, err = assumeRoleCreds.Get()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to assume role: %w", err)
|
||||
}
|
||||
|
||||
return assumeRoleCreds, nil
|
||||
}
|
||||
|
||||
// GetCredentialsFromSession returns credentials derived from a session. A
|
||||
// session uses the AWS SDK Go chain of providers so may use a provider (e.g.,
|
||||
// ProcessProvider) that is not part of the Packer provider chain.
|
||||
func (c *AccessConfig) GetCredentialsFromSession() (*awsCredentials.Credentials, error) {
|
||||
log.Printf("[INFO] Attempting to use session-derived credentials")
|
||||
// Avoid setting HTTPClient here as it will prevent the ec2metadata
|
||||
// client from automatically lowering the timeout to 1 second.
|
||||
options := &session.Options{
|
||||
Config: aws.Config{
|
||||
MaxRetries: aws.Int(0),
|
||||
Region: aws.String(c.RawRegion),
|
||||
},
|
||||
Profile: c.ProfileName,
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
}
|
||||
|
||||
sess, err := session.NewSessionWithOptions(*options)
|
||||
if err != nil {
|
||||
if IsAWSErr(err, "NoCredentialProviders", "") {
|
||||
return nil, c.NewNoValidCredentialSourcesError(err)
|
||||
}
|
||||
return nil, fmt.Errorf("Error creating AWS session: %v", err)
|
||||
}
|
||||
|
||||
creds := sess.Config.Credentials
|
||||
cp, err := sess.Config.Credentials.Get()
|
||||
if err != nil {
|
||||
return nil, c.NewNoValidCredentialSourcesError(err)
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Successfully derived credentials from session")
|
||||
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
func (c *AccessConfig) GetCredsFromVault() error {
|
||||
@@ -413,7 +549,7 @@ 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)
|
||||
"Error: %v", err)
|
||||
}
|
||||
|
||||
func (c *AccessConfig) NewEC2Connection() (ec2iface.EC2API, error) {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package awserrors
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
)
|
||||
|
||||
// Returns true if the err matches all these conditions:
|
||||
// * err is of type awserr.Error
|
||||
// * Error.Code() matches code
|
||||
// * Error.Message() contains message
|
||||
func Matches(err error, code string, message string) bool {
|
||||
if err, ok := err.(awserr.Error); ok {
|
||||
return err.Code() == code && strings.Contains(err.Message(), message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -134,8 +134,8 @@ func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapp
|
||||
ebsBlockDevice.VolumeSize = aws.Int64(blockDevice.VolumeSize)
|
||||
}
|
||||
|
||||
// IOPS is only valid for io1 and io2 types
|
||||
if blockDevice.VolumeType == "io1" || blockDevice.VolumeType == "io2" {
|
||||
// IOPS is only valid for io1 type
|
||||
if blockDevice.VolumeType == "io1" {
|
||||
ebsBlockDevice.Iops = aws.Int64(blockDevice.IOPS)
|
||||
}
|
||||
|
||||
|
||||
@@ -66,25 +66,6 @@ func TestBlockDevice(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Config: &BlockDevice{
|
||||
DeviceName: "/dev/sdb",
|
||||
VolumeType: "io2",
|
||||
VolumeSize: 8,
|
||||
DeleteOnTermination: true,
|
||||
IOPS: 1000,
|
||||
},
|
||||
|
||||
Result: &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String("/dev/sdb"),
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
VolumeType: aws.String("io2"),
|
||||
VolumeSize: aws.Int64(8),
|
||||
DeleteOnTermination: aws.Bool(true),
|
||||
Iops: aws.Int64(1000),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Config: &BlockDevice{
|
||||
DeviceName: "/dev/sdb",
|
||||
|
||||
@@ -4,11 +4,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,7 @@ func DestroyAMIs(imageids []*string, ec2conn *ec2.EC2) error {
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(err error) bool {
|
||||
return awserrors.Matches(err, "UnauthorizedOperation", "")
|
||||
return IsAWSErr(err, "UnauthorizedOperation", "")
|
||||
},
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) error {
|
||||
@@ -53,7 +54,7 @@ func DestroyAMIs(imageids []*string, ec2conn *ec2.EC2) error {
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(err error) bool {
|
||||
return awserrors.Matches(err, "UnauthorizedOperation", "")
|
||||
return IsAWSErr(err, "UnauthorizedOperation", "")
|
||||
},
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) error {
|
||||
@@ -73,3 +74,14 @@ func DestroyAMIs(imageids []*string, ec2conn *ec2.EC2) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns true if the error matches all these conditions:
|
||||
// * err is of type awserr.Error
|
||||
// * Error.Code() matches code
|
||||
// * Error.Message() contains message
|
||||
func IsAWSErr(err error, code string, message string) bool {
|
||||
if err, ok := err.(awserr.Error); ok {
|
||||
return err.Code() == code && strings.Contains(err.Message(), message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -376,12 +376,6 @@ type RunConfig struct {
|
||||
// subnet-12345def, where Packer will launch the EC2 instance. This field is
|
||||
// required if you are using an non-default VPC.
|
||||
SubnetId string `mapstructure:"subnet_id" required:"false"`
|
||||
// [Tenancy](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/dedicated-instance.html) used
|
||||
// when Packer launches the EC2 instance, allowing it to be launched on dedicated hardware.
|
||||
//
|
||||
// The default is "default", meaning shared tenancy. Allowed values are "default",
|
||||
// "dedicated" and "host".
|
||||
Tenancy string `mapstructure:"tenancy" required:"false"`
|
||||
// The name of the temporary key pair to
|
||||
// generate. By default, Packer generates a name that looks like
|
||||
// `packer_<UUID>`, where <UUID> is a 36 character unique identifier.
|
||||
@@ -548,6 +542,10 @@ 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 != "" {
|
||||
@@ -633,13 +631,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
}
|
||||
}
|
||||
|
||||
if c.Tenancy != "" &&
|
||||
c.Tenancy != "default" &&
|
||||
c.Tenancy != "dedicated" &&
|
||||
c.Tenancy != "host" {
|
||||
errs = append(errs, fmt.Errorf("Error: Unknown tenancy type %s", c.Tenancy))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
|
||||
@@ -232,23 +232,3 @@ func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
|
||||
t.Fatal("keypair name does not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_TenancyBad(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.Tenancy = "not_real"
|
||||
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatal("Should error if tenancy is set to an invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_TenancyGood(t *testing.T) {
|
||||
validTenancy := []string{"", "default", "dedicated", "host"}
|
||||
for _, vt := range validTenancy {
|
||||
c := testConfig()
|
||||
c.Tenancy = vt
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("Should not error if tenancy is set to %s", vt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
package ssm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ssm"
|
||||
"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
|
||||
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/hashicorp/packer/helper/builder/localexec"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
SvcClient ssmiface.SSMAPI
|
||||
Region string
|
||||
InstanceID string
|
||||
LocalPort, RemotePort int
|
||||
}
|
||||
|
||||
func (s Session) buildTunnelInput() *ssm.StartSessionInput {
|
||||
portNumber, localPortNumber := strconv.Itoa(s.RemotePort), strconv.Itoa(s.LocalPort)
|
||||
params := map[string][]*string{
|
||||
"portNumber": []*string{aws.String(portNumber)},
|
||||
"localPortNumber": []*string{aws.String(localPortNumber)},
|
||||
}
|
||||
|
||||
return &ssm.StartSessionInput{
|
||||
DocumentName: aws.String("AWS-StartPortForwardingSession"),
|
||||
Parameters: params,
|
||||
Target: aws.String(s.InstanceID),
|
||||
}
|
||||
}
|
||||
|
||||
// getCommand return a valid ordered set of arguments to pass to the driver command.
|
||||
func (s Session) getCommand(ctx context.Context) ([]string, string, error) {
|
||||
input := s.buildTunnelInput()
|
||||
|
||||
var session *ssm.StartSessionOutput
|
||||
err := retry.Config{
|
||||
ShouldRetry: func(err error) bool { return awserrors.Matches(err, "TargetNotConnected", "") },
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 60 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) (err error) {
|
||||
session, err = s.SvcClient.StartSessionWithContext(ctx, input)
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if session == nil {
|
||||
return nil, "", fmt.Errorf("an active Amazon SSM Session is required before trying to open a session tunnel")
|
||||
}
|
||||
|
||||
// AWS session-manager-plugin requires a valid session be passed in JSON.
|
||||
sessionDetails, err := json.Marshal(session)
|
||||
if err != nil {
|
||||
return nil, *session.SessionId, fmt.Errorf("error encountered in reading session details %s", err)
|
||||
}
|
||||
|
||||
// AWS session-manager-plugin requires the parameters used in the session to be passed in JSON as well.
|
||||
sessionParameters, err := json.Marshal(input)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error encountered in reading session parameter details %s", err)
|
||||
}
|
||||
|
||||
// Args must be in this order
|
||||
args := []string{
|
||||
string(sessionDetails),
|
||||
s.Region,
|
||||
"StartSession",
|
||||
"", // ProfileName
|
||||
string(sessionParameters),
|
||||
*session.StreamUrl,
|
||||
}
|
||||
return args, *session.SessionId, nil
|
||||
}
|
||||
|
||||
// Start an interactive Systems Manager session with a remote instance via the
|
||||
// AWS session-manager-plugin. To terminate the session you must cancell the
|
||||
// context. If you do not wish to terminate the session manually: calling
|
||||
// StopSession on a instance of this driver will terminate the active session
|
||||
// created from calling StartSession.
|
||||
func (s Session) Start(ctx context.Context, ui packer.Ui) error {
|
||||
for ctx.Err() == nil {
|
||||
log.Printf("ssm: Starting PortForwarding session to instance %s", s.InstanceID)
|
||||
args, sessionID, err := s.getCommand(ctx)
|
||||
if sessionID != "" {
|
||||
defer func() {
|
||||
_, err := s.SvcClient.TerminateSession(&ssm.TerminateSessionInput{SessionId: aws.String(sessionID)})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error terminating SSM Session %q. Please terminate the session manually: %s", sessionID, err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, "session-manager-plugin", args...)
|
||||
|
||||
ui.Message(fmt.Sprintf("Starting portForwarding session %q.", sessionID))
|
||||
err = localexec.RunAndStream(cmd, ui, nil)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ssm"
|
||||
"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/mitchellh/iochan"
|
||||
)
|
||||
|
||||
const (
|
||||
sessionManagerPluginName string = "session-manager-plugin"
|
||||
|
||||
//sessionCommand is the AWS-SDK equivalent to the command you would specify to `aws ssm ...`
|
||||
sessionCommand string = "StartSession"
|
||||
)
|
||||
|
||||
type SSMDriverConfig struct {
|
||||
SvcClient ssmiface.SSMAPI
|
||||
Region string
|
||||
ProfileName string
|
||||
SvcEndpoint string
|
||||
}
|
||||
|
||||
type SSMDriver struct {
|
||||
SSMDriverConfig
|
||||
session *ssm.StartSessionOutput
|
||||
sessionParams ssm.StartSessionInput
|
||||
pluginCmdFunc func(context.Context) error
|
||||
}
|
||||
|
||||
func NewSSMDriver(config SSMDriverConfig) *SSMDriver {
|
||||
d := SSMDriver{SSMDriverConfig: config}
|
||||
return &d
|
||||
}
|
||||
|
||||
// StartSession starts an interactive Systems Manager session with a remote instance via the AWS session-manager-plugin
|
||||
// This ssm.StartSessionOutput returned by this function can be used for terminating the session manually. If you do
|
||||
// not wish to manage the session manually calling StopSession on a instance of this driver will terminate the active session
|
||||
// created from calling StartSession.
|
||||
func (d *SSMDriver) StartSession(ctx context.Context, input ssm.StartSessionInput) (*ssm.StartSessionOutput, error) {
|
||||
log.Printf("Starting PortForwarding session to instance %q", aws.StringValue(input.Target))
|
||||
|
||||
var output *ssm.StartSessionOutput
|
||||
err := retry.Config{
|
||||
ShouldRetry: func(err error) bool { return IsAWSErr(err, "TargetNotConnected", "") },
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 60 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) (err error) {
|
||||
output, err = d.SvcClient.StartSessionWithContext(ctx, &input)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error encountered in starting session for instance %q: %s", aws.StringValue(input.Target), err)
|
||||
}
|
||||
|
||||
d.session = output
|
||||
d.sessionParams = input
|
||||
|
||||
if d.pluginCmdFunc == nil {
|
||||
d.pluginCmdFunc = d.openTunnelForSession
|
||||
}
|
||||
|
||||
if err := d.pluginCmdFunc(ctx); err != nil {
|
||||
return nil, fmt.Errorf("error encountered in starting session for instance %q: %s", aws.StringValue(input.Target), err)
|
||||
}
|
||||
|
||||
return d.session, nil
|
||||
}
|
||||
|
||||
func (d *SSMDriver) openTunnelForSession(ctx context.Context) error {
|
||||
args, err := d.Args()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encountered validating session details: %s", err)
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, sessionManagerPluginName, args...)
|
||||
|
||||
// Let's build up our logging
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the channels we'll use for data
|
||||
stdoutCh := iochan.DelimReader(stdout, '\n')
|
||||
stderrCh := iochan.DelimReader(stderr, '\n')
|
||||
|
||||
/* Loop and get all our output
|
||||
This particular logger will continue to run through an entire Packer run.
|
||||
The decision to continue logging is due to the fact that session-manager-plugin
|
||||
doesn't give a good way of knowing if the command failed or was successful other
|
||||
than looking at the logs. Seeing as the plugin is updated frequently and that the
|
||||
log information is a bit sparse this logger will indefinitely relying on other
|
||||
steps to fail if the tunnel is unable to be created. If successful then the user
|
||||
will get more information on the tunnel connection when running in a debug mode.
|
||||
*/
|
||||
go func(ctx context.Context, prefix string) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case output, ok := <-stderrCh:
|
||||
if !ok {
|
||||
stderrCh = nil
|
||||
break
|
||||
}
|
||||
|
||||
if output != "" {
|
||||
log.Printf("[ERROR] %s: %s", prefix, output)
|
||||
}
|
||||
case output, ok := <-stdoutCh:
|
||||
if !ok {
|
||||
stdoutCh = nil
|
||||
break
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
log.Printf("[DEBUG %s] opening session tunnel to instance %q for session %q", sessionManagerPluginName,
|
||||
aws.StringValue(d.sessionParams.Target),
|
||||
aws.StringValue(d.session.SessionId),
|
||||
)
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
err = fmt.Errorf("error encountered when calling %s: %s\n", sessionManagerPluginName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopSession terminates an active Session Manager session
|
||||
func (d *SSMDriver) StopSession() error {
|
||||
|
||||
if d.session == nil || d.session.SessionId == nil {
|
||||
return fmt.Errorf("Unable to find a valid session to instance %q; skipping the termination step",
|
||||
aws.StringValue(d.sessionParams.Target))
|
||||
}
|
||||
|
||||
_, err := d.SvcClient.TerminateSession(&ssm.TerminateSessionInput{SessionId: d.session.SessionId})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error terminating SSM Session %q. Please terminate the session manually: %s", aws.StringValue(d.session.SessionId), err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Args validates the driver inputs before returning an ordered set of arguments to pass to the driver command.
|
||||
func (d *SSMDriver) Args() ([]string, error) {
|
||||
if d.session == nil {
|
||||
return nil, fmt.Errorf("an active Amazon SSM Session is required before trying to open a session tunnel")
|
||||
}
|
||||
|
||||
// AWS session-manager-plugin requires a valid session be passed in JSON.
|
||||
sessionDetails, err := json.Marshal(d.session)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error encountered in reading session details %s", err)
|
||||
}
|
||||
|
||||
// AWS session-manager-plugin requires the parameters used in the session to be passed in JSON as well.
|
||||
sessionParameters, err := json.Marshal(d.sessionParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error encountered in reading session parameter details %s", err)
|
||||
}
|
||||
|
||||
// Args must be in this order
|
||||
args := []string{
|
||||
string(sessionDetails),
|
||||
d.Region,
|
||||
sessionCommand,
|
||||
d.ProfileName,
|
||||
string(sessionParameters),
|
||||
d.SvcEndpoint,
|
||||
}
|
||||
|
||||
return args, nil
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func NewSSMDriverWithMockSvc(svc *MockSSMSvc) *SSMDriver {
|
||||
config := SSMDriverConfig{
|
||||
SvcClient: svc,
|
||||
Region: "east",
|
||||
ProfileName: "default",
|
||||
SvcEndpoint: "example.com",
|
||||
}
|
||||
|
||||
driver := SSMDriver{
|
||||
SSMDriverConfig: config,
|
||||
pluginCmdFunc: func(ctx context.Context) error { return nil },
|
||||
}
|
||||
|
||||
return &driver
|
||||
}
|
||||
func TestSSMDriver_StartSession(t *testing.T) {
|
||||
mockSvc := MockSSMSvc{}
|
||||
driver := NewSSMDriverWithMockSvc(&mockSvc)
|
||||
|
||||
if driver.SvcClient == nil {
|
||||
t.Fatalf("SvcClient for driver should not be nil")
|
||||
}
|
||||
|
||||
session, err := driver.StartSession(context.TODO(), MockStartSessionInput("fakeinstance"))
|
||||
if err != nil {
|
||||
t.Fatalf("calling StartSession should not error but got %v", err)
|
||||
}
|
||||
|
||||
if !mockSvc.StartSessionCalled {
|
||||
t.Fatalf("expected test to call ssm mocks but didn't")
|
||||
}
|
||||
|
||||
if session == nil {
|
||||
t.Errorf("expected session to be set after a successful call to StartSession")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(session, MockStartSessionOutput()) {
|
||||
t.Errorf("expected session to be %v but got %v", MockStartSessionOutput(), session)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSMDriver_StartSessionWithError(t *testing.T) {
|
||||
mockSvc := MockSSMSvc{StartSessionError: fmt.Errorf("bogus error")}
|
||||
driver := NewSSMDriverWithMockSvc(&mockSvc)
|
||||
|
||||
if driver.SvcClient == nil {
|
||||
t.Fatalf("SvcClient for driver should not be nil")
|
||||
}
|
||||
|
||||
session, err := driver.StartSession(context.TODO(), MockStartSessionInput("fakeinstance"))
|
||||
if err == nil {
|
||||
t.Fatalf("StartSession should have thrown an error but didn't")
|
||||
}
|
||||
|
||||
if !mockSvc.StartSessionCalled {
|
||||
t.Errorf("expected test to call StartSession mock but didn't")
|
||||
}
|
||||
|
||||
if session != nil {
|
||||
t.Errorf("expected session to be nil after a bad StartSession call, but got %v", session)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSMDriver_StopSession(t *testing.T) {
|
||||
mockSvc := MockSSMSvc{}
|
||||
driver := NewSSMDriverWithMockSvc(&mockSvc)
|
||||
|
||||
if driver.SvcClient == nil {
|
||||
t.Fatalf("SvcClient for driver should not be nil")
|
||||
}
|
||||
|
||||
// Calling StopSession before StartSession should fail
|
||||
err := driver.StopSession()
|
||||
if err == nil {
|
||||
t.Fatalf("calling StopSession() on a driver that has no started session should fail")
|
||||
}
|
||||
|
||||
if driver.session != nil {
|
||||
t.Errorf("expected session to be default to nil")
|
||||
}
|
||||
|
||||
if mockSvc.TerminateSessionCalled {
|
||||
t.Fatalf("a call to TerminateSession should not occur when there is no valid SSM session")
|
||||
}
|
||||
|
||||
// Lets try calling start session, then stopping to see what happens.
|
||||
session, err := driver.StartSession(context.TODO(), MockStartSessionInput("fakeinstance"))
|
||||
if err != nil {
|
||||
t.Fatalf("calling StartSession should not error but got %v", err)
|
||||
}
|
||||
|
||||
if !mockSvc.StartSessionCalled {
|
||||
t.Fatalf("expected test to call StartSession mock but didn't")
|
||||
}
|
||||
|
||||
if session == nil || driver.session != session {
|
||||
t.Errorf("expected session to be set after a successful call to StartSession")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(session, MockStartSessionOutput()) {
|
||||
t.Errorf("expected session to be %v but got %v", MockStartSessionOutput(), session)
|
||||
}
|
||||
|
||||
err = driver.StopSession()
|
||||
if err != nil {
|
||||
t.Errorf("calling StopSession() on a driver on a started session should not fail")
|
||||
}
|
||||
|
||||
if !mockSvc.TerminateSessionCalled {
|
||||
t.Fatalf("expected test to call StopSession mock but didn't")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSSMDriver_Args(t *testing.T) {
|
||||
tt := []struct {
|
||||
Name string
|
||||
ProfileName string
|
||||
SkipStartSession bool
|
||||
ErrorExpected bool
|
||||
}{
|
||||
{
|
||||
Name: "NilSession",
|
||||
SkipStartSession: true,
|
||||
ErrorExpected: true,
|
||||
},
|
||||
{
|
||||
Name: "NonNilSession",
|
||||
ErrorExpected: false,
|
||||
},
|
||||
{
|
||||
Name: "SessionWithProfileName",
|
||||
ProfileName: "default",
|
||||
ErrorExpected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
mockSvc := MockSSMSvc{}
|
||||
driver := NewSSMDriverWithMockSvc(&mockSvc)
|
||||
driver.ProfileName = tc.ProfileName
|
||||
|
||||
if driver.SvcClient == nil {
|
||||
t.Fatalf("svcclient for driver should not be nil")
|
||||
}
|
||||
|
||||
if !tc.SkipStartSession {
|
||||
_, err := driver.StartSession(context.TODO(), MockStartSessionInput("fakeinstance"))
|
||||
if err != nil {
|
||||
t.Fatalf("got an error when calling StartSession %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
args, err := driver.Args()
|
||||
if tc.ErrorExpected && err == nil {
|
||||
t.Fatalf("Driver.Args with a %q should have failed but instead no error was returned", tc.Name)
|
||||
}
|
||||
|
||||
if tc.ErrorExpected {
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("got an error when it should've worked %v", err)
|
||||
}
|
||||
|
||||
// validate launch script
|
||||
expectedArgString := fmt.Sprintf(`{"SessionId":"packerid","StreamUrl":"http://packer.io","TokenValue":"packer-token"} east StartSession %s {"DocumentName":"AWS-StartPortForwardingSession","Parameters":{"localPortNumber":["8001"],"portNumber":["22"]},"Target":"fakeinstance"} example.com`, tc.ProfileName)
|
||||
argString := strings.Join(args, " ")
|
||||
if argString != expectedArgString {
|
||||
t.Errorf("Expected launch script to be %q but got %q", expectedArgString, argString)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/service/ssm"
|
||||
"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
|
||||
)
|
||||
|
||||
type MockSSMSvc struct {
|
||||
ssmiface.SSMAPI
|
||||
StartSessionError error
|
||||
TerminateSessionError error
|
||||
StartSessionCalled bool
|
||||
TerminateSessionCalled bool
|
||||
}
|
||||
|
||||
func (svc *MockSSMSvc) StartSessionWithContext(ctx aws.Context, input *ssm.StartSessionInput, options ...request.Option) (*ssm.StartSessionOutput, error) {
|
||||
svc.StartSessionCalled = true
|
||||
return MockStartSessionOutput(), svc.StartSessionError
|
||||
}
|
||||
func (svc *MockSSMSvc) TerminateSession(input *ssm.TerminateSessionInput) (*ssm.TerminateSessionOutput, error) {
|
||||
svc.TerminateSessionCalled = true
|
||||
return new(ssm.TerminateSessionOutput), svc.TerminateSessionError
|
||||
}
|
||||
|
||||
func MockPluginCmdFunc(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func MockStartSessionOutput() *ssm.StartSessionOutput {
|
||||
id, url, token := "packerid", "http://packer.io", "packer-token"
|
||||
output := ssm.StartSessionOutput{
|
||||
SessionId: &id,
|
||||
StreamUrl: &url,
|
||||
TokenValue: &token,
|
||||
}
|
||||
return &output
|
||||
}
|
||||
|
||||
func MockStartSessionInput(instance string) ssm.StartSessionInput {
|
||||
params := map[string][]*string{
|
||||
"portNumber": []*string{aws.String("22")},
|
||||
"localPortNumber": []*string{aws.String("8001")},
|
||||
}
|
||||
|
||||
input := ssm.StartSessionInput{
|
||||
DocumentName: aws.String("AWS-StartPortForwardingSession"),
|
||||
Parameters: params,
|
||||
Target: aws.String(instance),
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
@@ -3,13 +3,13 @@ package common
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/ssm"
|
||||
pssm "github.com/hashicorp/packer/builder/amazon/common/ssm"
|
||||
"github.com/hashicorp/packer/common/net"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -21,8 +21,9 @@ type StepCreateSSMTunnel struct {
|
||||
LocalPortNumber int
|
||||
RemotePortNumber int
|
||||
SSMAgentEnabled bool
|
||||
instanceId string
|
||||
PauseBeforeSSM time.Duration
|
||||
stopSSMCommand func()
|
||||
driver *SSMDriver
|
||||
}
|
||||
|
||||
// Run executes the Packer build step that creates a session tunnel.
|
||||
@@ -35,7 +36,7 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
|
||||
|
||||
// Wait for the remote port to become available
|
||||
if s.PauseBeforeSSM > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s before establishing the SSM session...", s.PauseBeforeSSM))
|
||||
ui.Say(fmt.Sprintf("Waiting %s for establishing the SSM session...", s.PauseBeforeSSM))
|
||||
select {
|
||||
case <-time.After(s.PauseBeforeSSM):
|
||||
break
|
||||
@@ -60,38 +61,42 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.instanceId = aws.StringValue(instance.InstanceId)
|
||||
|
||||
state.Put("sessionPort", s.LocalPortNumber)
|
||||
|
||||
ssmCtx, ssmCancel := context.WithCancel(ctx)
|
||||
s.stopSSMCommand = ssmCancel
|
||||
|
||||
go func() {
|
||||
if s.driver == nil {
|
||||
ssmconn := ssm.New(s.AWSSession)
|
||||
err := pssm.Session{
|
||||
SvcClient: ssmconn,
|
||||
InstanceID: aws.StringValue(instance.InstanceId),
|
||||
RemotePort: s.RemotePortNumber,
|
||||
LocalPort: s.LocalPortNumber,
|
||||
Region: s.Region,
|
||||
}.Start(ssmCtx, ui)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("ssm error: %s", err))
|
||||
cfg := SSMDriverConfig{
|
||||
SvcClient: ssmconn,
|
||||
Region: s.Region,
|
||||
SvcEndpoint: ssmconn.Endpoint,
|
||||
}
|
||||
}()
|
||||
driver := SSMDriver{SSMDriverConfig: cfg}
|
||||
s.driver = &driver
|
||||
}
|
||||
|
||||
input := s.BuildTunnelInputForInstance(s.instanceId)
|
||||
_, err := s.driver.StartSession(ctx, input)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error encountered in establishing a tunnel %s", err)
|
||||
ui.Error(err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("PortForwarding session %q has been started", s.instanceId))
|
||||
state.Put("sessionPort", s.LocalPortNumber)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Cleanup terminates an active session on AWS, which in turn terminates the associated tunnel process running on the local machine.
|
||||
func (s *StepCreateSSMTunnel) Cleanup(state multistep.StateBag) {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
if !s.SSMAgentEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
if s.stopSSMCommand != nil {
|
||||
s.stopSSMCommand()
|
||||
if err := s.driver.StopSession(); err != nil {
|
||||
ui.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,3 +129,19 @@ func (s *StepCreateSSMTunnel) ConfigureLocalHostPort(ctx context.Context) error
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (s *StepCreateSSMTunnel) BuildTunnelInputForInstance(instance string) ssm.StartSessionInput {
|
||||
dst, src := strconv.Itoa(s.RemotePortNumber), strconv.Itoa(s.LocalPortNumber)
|
||||
params := map[string][]*string{
|
||||
"portNumber": []*string{aws.String(dst)},
|
||||
"localPortNumber": []*string{aws.String(src)},
|
||||
}
|
||||
|
||||
input := ssm.StartSessionInput{
|
||||
DocumentName: aws.String("AWS-StartPortForwardingSession"),
|
||||
Parameters: params,
|
||||
Target: aws.String(instance),
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestStepCreateSSMTunnel_Run(t *testing.T) {
|
||||
mockSvc := MockSSMSvc{}
|
||||
config := SSMDriverConfig{
|
||||
SvcClient: &mockSvc,
|
||||
SvcEndpoint: "example.com",
|
||||
}
|
||||
|
||||
mockDriver := NewSSMDriver(config)
|
||||
mockDriver.pluginCmdFunc = MockPluginCmdFunc
|
||||
|
||||
state := testState()
|
||||
state.Put("ui", &packer.NoopUi{})
|
||||
state.Put("instance", &ec2.Instance{InstanceId: aws.String("i-something")})
|
||||
|
||||
step := StepCreateSSMTunnel{
|
||||
driver: mockDriver,
|
||||
}
|
||||
|
||||
step.Run(context.Background(), state)
|
||||
|
||||
err := state.Get("error")
|
||||
if err != nil {
|
||||
err = err.(error)
|
||||
t.Fatalf("the call to Run failed with an error when it should've executed: %v", err)
|
||||
}
|
||||
|
||||
if mockSvc.StartSessionCalled {
|
||||
t.Errorf("StartSession should not be called when SSMAgentEnabled is false")
|
||||
}
|
||||
|
||||
// Run when SSMAgentEnabled is true
|
||||
step.SSMAgentEnabled = true
|
||||
step.Run(context.Background(), state)
|
||||
|
||||
err = state.Get("error")
|
||||
if err != nil {
|
||||
err = err.(error)
|
||||
t.Fatalf("the call to Run failed with an error when it should've executed: %v", err)
|
||||
}
|
||||
|
||||
if !mockSvc.StartSessionCalled {
|
||||
t.Errorf("calling run with the correct inputs should call StartSession")
|
||||
}
|
||||
|
||||
step.Cleanup(state)
|
||||
if !mockSvc.TerminateSessionCalled {
|
||||
t.Errorf("calling cleanup on a successful run should call TerminateSession")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateSSMTunnel_Cleanup(t *testing.T) {
|
||||
mockSvc := MockSSMSvc{}
|
||||
config := SSMDriverConfig{
|
||||
SvcClient: &mockSvc,
|
||||
SvcEndpoint: "example.com",
|
||||
}
|
||||
|
||||
mockDriver := NewSSMDriver(config)
|
||||
mockDriver.pluginCmdFunc = MockPluginCmdFunc
|
||||
|
||||
step := StepCreateSSMTunnel{
|
||||
SSMAgentEnabled: true,
|
||||
driver: mockDriver,
|
||||
}
|
||||
|
||||
state := testState()
|
||||
state.Put("ui", &packer.NoopUi{})
|
||||
state.Put("instance", &ec2.Instance{InstanceId: aws.String("i-something")})
|
||||
|
||||
step.Cleanup(state)
|
||||
|
||||
if mockSvc.TerminateSessionCalled {
|
||||
t.Fatalf("calling cleanup on a non started session should not call TerminateSession")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestStepCreateSSMTunnel_BuildTunnelInputForInstance(t *testing.T) {
|
||||
step := StepCreateSSMTunnel{
|
||||
Region: "region",
|
||||
LocalPortNumber: 8001,
|
||||
RemotePortNumber: 22,
|
||||
SSMAgentEnabled: true,
|
||||
}
|
||||
|
||||
input := step.BuildTunnelInputForInstance("i-something")
|
||||
|
||||
target := aws.StringValue(input.Target)
|
||||
if target != "i-something" {
|
||||
t.Errorf("input should contain instance id as target but it got %q", target)
|
||||
}
|
||||
|
||||
params := map[string][]*string{
|
||||
"portNumber": []*string{aws.String("22")},
|
||||
"localPortNumber": []*string{aws.String("8001")},
|
||||
}
|
||||
if !reflect.DeepEqual(input.Parameters, params) {
|
||||
t.Errorf("input should contain the expected port parameters but it got %v", input.Parameters)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestStepCreateSSMTunnel_ConfigureLocalHostPort(t *testing.T) {
|
||||
tt := []struct {
|
||||
Name string
|
||||
Step StepCreateSSMTunnel
|
||||
PortCheck func(int) bool
|
||||
}{
|
||||
{"WithLocalPortNumber", StepCreateSSMTunnel{LocalPortNumber: 9001}, func(port int) bool { return port == 9001 }},
|
||||
{"WithNoLocalPortNumber", StepCreateSSMTunnel{}, func(port int) bool { return port >= 8000 && port <= 9000 }},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
step := tc.Step
|
||||
if err := step.ConfigureLocalHostPort(context.TODO()); err != nil {
|
||||
t.Errorf("failed to configure a port on localhost")
|
||||
}
|
||||
|
||||
if !tc.PortCheck(step.LocalPortNumber) {
|
||||
t.Errorf("failed to configure a port on localhost")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -92,7 +91,7 @@ func (s *StepCreateTags) Run(ctx context.Context, state multistep.StateBag) mult
|
||||
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
err = retry.Config{Tries: 11, ShouldRetry: func(error) bool {
|
||||
if awserrors.Matches(err, "InvalidAMIID.NotFound", "") || awserrors.Matches(err, "InvalidSnapshot.NotFound", "") {
|
||||
if IsAWSErr(err, "InvalidAMIID.NotFound", "") || IsAWSErr(err, "InvalidSnapshot.NotFound", "") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -40,7 +39,7 @@ func (s *StepPreValidate) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
err := retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(err error) bool {
|
||||
if awserrors.Matches(err, "AuthFailure", "") {
|
||||
if IsAWSErr(err, "AuthFailure", "") {
|
||||
log.Printf("Waiting for Vault-generated AWS credentials" +
|
||||
" to pass authentication... trying again.")
|
||||
return true
|
||||
@@ -132,7 +131,7 @@ func (s *StepPreValidate) checkVpc(conn ec2iface.EC2API) error {
|
||||
}
|
||||
|
||||
res, err := conn.DescribeVpcs(&ec2.DescribeVpcsInput{VpcIds: []*string{aws.String(s.VpcId)}})
|
||||
if awserrors.Matches(err, "InvalidVpcID.NotFound", "") || err != nil {
|
||||
if IsAWSErr(err, "InvalidVpcID.NotFound", "") || err != nil {
|
||||
return fmt.Errorf("Error retrieving VPC information for vpc_id %s: %s", s.VpcId, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
|
||||
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
@@ -34,7 +33,6 @@ type StepRunSourceInstance struct {
|
||||
IsRestricted bool
|
||||
SourceAMI string
|
||||
Tags map[string]string
|
||||
Tenancy string
|
||||
UserData string
|
||||
UserDataFile string
|
||||
VolumeTags map[string]string
|
||||
@@ -197,15 +195,11 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
runOpts.InstanceInitiatedShutdownBehavior = &s.InstanceInitiatedShutdownBehavior
|
||||
}
|
||||
|
||||
if s.Tenancy != "" {
|
||||
runOpts.Placement.Tenancy = aws.String(s.Tenancy)
|
||||
}
|
||||
|
||||
var runResp *ec2.Reservation
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(err error) bool {
|
||||
if awserrors.Matches(err, "InvalidParameterValue", "iamInstanceProfile") {
|
||||
if IsAWSErr(err, "InvalidParameterValue", "iamInstanceProfile") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -216,7 +210,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
return err
|
||||
})
|
||||
|
||||
if awserrors.Matches(err, "VPCIdNotSpecified", "No default VPC for this user") && subnetId == "" {
|
||||
if IsAWSErr(err, "VPCIdNotSpecified", "No default VPC for this user") && subnetId == "" {
|
||||
err := fmt.Errorf("Error launching source instance: a valid Subnet Id was not specified")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
@@ -254,7 +248,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
|
||||
var r *ec2.DescribeInstancesOutput
|
||||
err = retry.Config{Tries: 11, ShouldRetry: func(err error) bool {
|
||||
if awserrors.Matches(err, "InvalidInstanceID.NotFound", "") {
|
||||
if IsAWSErr(err, "InvalidInstanceID.NotFound", "") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -299,7 +293,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
ec2Tags.Report(ui)
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
err = retry.Config{Tries: 11, ShouldRetry: func(error) bool {
|
||||
if awserrors.Matches(err, "InvalidInstanceID.NotFound", "") {
|
||||
if IsAWSErr(err, "InvalidInstanceID.NotFound", "") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||
"github.com/hashicorp/packer/common/random"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
@@ -397,7 +396,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
err = retry.Config{Tries: 11, ShouldRetry: func(error) bool {
|
||||
if awserrors.Matches(err, "InvalidInstanceID.NotFound", "") {
|
||||
if IsAWSErr(err, "InvalidInstanceID.NotFound", "") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -43,7 +42,7 @@ func (s *StepStopEBSBackedInstance) Run(ctx context.Context, state multistep.Sta
|
||||
|
||||
// Work around this by retrying a few times, up to about 5 minutes.
|
||||
err := retry.Config{Tries: 6, ShouldRetry: func(error) bool {
|
||||
if awserrors.Matches(err, "InvalidInstanceID.NotFound", "") {
|
||||
if IsAWSErr(err, "InvalidInstanceID.NotFound", "") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -85,7 +85,6 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
@@ -207,7 +206,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
Tags: b.config.RunTags,
|
||||
Tenancy: b.config.Tenancy,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
VolumeTags: b.config.VolumeRunTags,
|
||||
|
||||
@@ -31,7 +31,6 @@ type FlatConfig struct {
|
||||
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
|
||||
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
|
||||
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
||||
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
|
||||
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
||||
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
||||
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
|
||||
@@ -81,7 +80,6 @@ type FlatConfig struct {
|
||||
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
|
||||
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
|
||||
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
|
||||
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
|
||||
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
|
||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
|
||||
@@ -96,8 +94,6 @@ type FlatConfig struct {
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -179,7 +175,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
|
||||
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
|
||||
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
||||
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
|
||||
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
||||
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
||||
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
|
||||
@@ -229,7 +224,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
|
||||
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
|
||||
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
|
||||
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
|
||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||
@@ -244,8 +238,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -246,14 +246,6 @@ func checkBootEncrypted() builderT.TestCheckFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderAcc_SessionManagerInterface(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccSessionManagerInterface,
|
||||
})
|
||||
}
|
||||
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
}
|
||||
|
||||
@@ -358,31 +350,6 @@ const testBuilderAccEncrypted = `
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccSessionManagerInterface = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
"region": "us-east-1",
|
||||
"instance_type": "m3.medium",
|
||||
"source_ami_filter": {
|
||||
"filters": {
|
||||
"virtualization-type": "hvm",
|
||||
"name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*",
|
||||
"root-device-type": "ebs"
|
||||
},
|
||||
"owners": [
|
||||
"099720109477"
|
||||
],
|
||||
"most_recent": true
|
||||
},
|
||||
"ssh_username": "ubuntu",
|
||||
"ssh_interface": "session_manager",
|
||||
"iam_instance_profile": "SSMInstanceProfile",
|
||||
"ami_name": "packer-ssm-test-{{timestamp}}"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
func buildForceDeregisterConfig(val, name string) string {
|
||||
return fmt.Sprintf(testBuilderAccForceDeregister, val, name)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||
"github.com/hashicorp/packer/common/random"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
@@ -62,7 +61,7 @@ func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multi
|
||||
err = retry.Config{
|
||||
Tries: 0,
|
||||
ShouldRetry: func(err error) bool {
|
||||
if awserrors.Matches(err, "InvalidParameterValue", "Instance is not in state") {
|
||||
if awscommon.IsAWSErr(err, "InvalidParameterValue", "Instance is not in state") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -84,7 +84,6 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
@@ -229,7 +228,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
Tags: b.config.RunTags,
|
||||
Tenancy: b.config.Tenancy,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
VolumeTags: b.config.VolumeRunTags,
|
||||
@@ -334,8 +332,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
PollingConfig: b.config.PollingConfig,
|
||||
LaunchDevices: launchDevices,
|
||||
SnapshotOmitMap: b.config.LaunchMappings.GetOmissions(),
|
||||
SnapshotTags: b.config.SnapshotTags,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
&awscommon.StepDeregisterAMI{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
|
||||
@@ -74,7 +74,6 @@ type FlatConfig struct {
|
||||
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
|
||||
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
|
||||
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
||||
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
|
||||
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
||||
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
||||
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
|
||||
@@ -103,7 +102,6 @@ type FlatConfig struct {
|
||||
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
|
||||
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
|
||||
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
|
||||
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
|
||||
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
|
||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
|
||||
@@ -118,8 +116,6 @@ type FlatConfig struct {
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -223,7 +219,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
|
||||
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
|
||||
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
||||
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
|
||||
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
||||
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
||||
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
|
||||
@@ -252,7 +247,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
|
||||
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
|
||||
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
|
||||
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
|
||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||
@@ -267,8 +261,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -6,13 +6,11 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// StepSnapshotVolumes creates snapshots of the created volumes.
|
||||
@@ -25,8 +23,6 @@ type StepSnapshotVolumes struct {
|
||||
snapshotIds map[string]string
|
||||
snapshotMutex sync.Mutex
|
||||
SnapshotOmitMap map[string]bool
|
||||
SnapshotTags map[string]string
|
||||
Ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName string, state multistep.StateBag) error {
|
||||
@@ -44,34 +40,12 @@ func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName str
|
||||
return fmt.Errorf("Volume ID for device %s not found", deviceName)
|
||||
}
|
||||
|
||||
ui.Say("Creating snapshot tags")
|
||||
snapshotTags, err := awscommon.TagMap(s.SnapshotTags).EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
snapshotTags.Report(ui)
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating snapshot of EBS Volume %s...", volumeId))
|
||||
description := fmt.Sprintf("Packer: %s", time.Now().String())
|
||||
|
||||
// Collect tags for tagging on resource creation
|
||||
var tagSpecs []*ec2.TagSpecification
|
||||
|
||||
if len(snapshotTags) > 0 {
|
||||
snapTags := &ec2.TagSpecification{
|
||||
ResourceType: aws.String("snapshot"),
|
||||
Tags: snapshotTags,
|
||||
}
|
||||
|
||||
tagSpecs = append(tagSpecs, snapTags)
|
||||
}
|
||||
|
||||
createSnapResp, err := ec2conn.CreateSnapshot(&ec2.CreateSnapshotInput{
|
||||
VolumeId: &volumeId,
|
||||
Description: &description,
|
||||
TagSpecifications: tagSpecs,
|
||||
VolumeId: &volumeId,
|
||||
Description: &description,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -100,7 +100,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
SourceAMI: `{{ .SourceAMI }} `,
|
||||
}
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
}, raws...)
|
||||
@@ -210,7 +209,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
Tags: b.config.RunTags,
|
||||
Tenancy: b.config.Tenancy,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
VolumeTags: b.config.VolumeRunTags,
|
||||
|
||||
@@ -76,7 +76,6 @@ type FlatConfig struct {
|
||||
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
|
||||
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
|
||||
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
||||
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
|
||||
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
||||
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
||||
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
|
||||
@@ -105,7 +104,6 @@ type FlatConfig struct {
|
||||
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
|
||||
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
|
||||
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
|
||||
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
|
||||
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
|
||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
|
||||
@@ -120,8 +118,6 @@ type FlatConfig struct {
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -203,7 +199,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
|
||||
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
|
||||
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
||||
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
|
||||
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
||||
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
||||
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
|
||||
@@ -232,7 +227,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
|
||||
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
|
||||
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
|
||||
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
|
||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||
@@ -247,8 +241,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -107,7 +107,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
|
||||
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
@@ -287,7 +286,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
Tags: b.config.RunTags,
|
||||
Tenancy: b.config.Tenancy,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ type FlatConfig struct {
|
||||
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
|
||||
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
|
||||
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
||||
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
|
||||
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
||||
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
||||
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
|
||||
@@ -81,7 +80,6 @@ type FlatConfig struct {
|
||||
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
|
||||
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
|
||||
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
|
||||
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
|
||||
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
|
||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
|
||||
@@ -96,8 +94,6 @@ type FlatConfig struct {
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -185,7 +181,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
|
||||
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
|
||||
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
||||
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
|
||||
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
||||
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
||||
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
|
||||
@@ -235,7 +230,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
|
||||
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
|
||||
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
|
||||
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
|
||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||
@@ -250,8 +244,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
+4
-4
@@ -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
@@ -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.
|
||||
|
||||
@@ -222,6 +222,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
NewStepSnapshotDataDisks(azureClient, ui, &b.config),
|
||||
NewStepCaptureImage(azureClient, ui),
|
||||
NewStepPublishToSharedImageGallery(azureClient, ui, &b.config),
|
||||
NewStepDeleteAdditionalDisks(azureClient, ui),
|
||||
}
|
||||
} else if b.config.OSType == constants.Target_Windows {
|
||||
steps = []multistep.Step{
|
||||
@@ -262,6 +263,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
NewStepSnapshotDataDisks(azureClient, ui, &b.config),
|
||||
NewStepCaptureImage(azureClient, ui),
|
||||
NewStepPublishToSharedImageGallery(azureClient, ui, &b.config),
|
||||
NewStepDeleteAdditionalDisks(azureClient, ui),
|
||||
)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Builder does not support the os_type '%s'", b.config.OSType)
|
||||
@@ -388,7 +390,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)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ package arm
|
||||
// * ARM_CLIENT_ID
|
||||
// * ARM_CLIENT_SECRET
|
||||
// * ARM_SUBSCRIPTION_ID
|
||||
// * ARM_OBJECT_ID
|
||||
// * ARM_STORAGE_ACCOUNT
|
||||
//
|
||||
// The subscription in question should have a resource group
|
||||
@@ -46,14 +47,6 @@ func TestBuilderAcc_ManagedDisk_Windows_Build_Resource_Group(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Windows_Build_Resource_Group_Additional_Disk(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccManagedDiskWindowsBuildResourceGroupAdditionalDisk,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Windows_DeviceLogin(t *testing.T) {
|
||||
if os.Getenv(DeviceLoginAcceptanceTest) == "" {
|
||||
t.Skip(fmt.Sprintf(
|
||||
@@ -158,7 +151,7 @@ const testBuilderAccManagedDiskWindowsBuildResourceGroup = `
|
||||
|
||||
"build_resource_group_name" : "packer-acceptance-test",
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskWindowsBuildResourceGroup-{{timestamp}}",
|
||||
"managed_image_name": "testBuilderAccManagedDiskWindows-{{timestamp}}",
|
||||
|
||||
"os_type": "Windows",
|
||||
"image_publisher": "MicrosoftWindowsServer",
|
||||
@@ -177,42 +170,6 @@ const testBuilderAccManagedDiskWindowsBuildResourceGroup = `
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccManagedDiskWindowsBuildResourceGroupAdditionalDisk = `
|
||||
{
|
||||
"variables": {
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"build_resource_group_name" : "packer-acceptance-test",
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskWindowsBuildResourceGroupAdditionDisk-{{timestamp}}",
|
||||
|
||||
"os_type": "Windows",
|
||||
"image_publisher": "MicrosoftWindowsServer",
|
||||
"image_offer": "WindowsServer",
|
||||
"image_sku": "2012-R2-Datacenter",
|
||||
|
||||
"communicator": "winrm",
|
||||
"winrm_use_ssl": "true",
|
||||
"winrm_insecure": "true",
|
||||
"winrm_timeout": "3m",
|
||||
"winrm_username": "packer",
|
||||
"async_resourcegroup_delete": "true",
|
||||
|
||||
"vm_size": "Standard_DS2_v2",
|
||||
"disk_additional_size": [10,15]
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccManagedDiskWindowsDeviceLogin = `
|
||||
{
|
||||
"variables": {
|
||||
@@ -266,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"
|
||||
}]
|
||||
}
|
||||
`
|
||||
@@ -305,6 +258,7 @@ const testBuilderAccBlobWindows = `
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}",
|
||||
"object_id": "{{env ` + "`ARM_OBJECT_ID`" + `}}",
|
||||
"storage_account": "{{env ` + "`ARM_STORAGE_ACCOUNT`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
@@ -313,6 +267,7 @@ const testBuilderAccBlobWindows = `
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
"object_id": "{{user ` + "`object_id`" + `}}",
|
||||
|
||||
"storage_account": "{{user ` + "`storage_account`" + `}}",
|
||||
"resource_group_name": "packer-acceptance-test",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -580,7 +580,6 @@ func (c *Config) createCertificate() (string, error) {
|
||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
c.ctx.Funcs = azcommon.TemplateFuncs
|
||||
err := config.Decode(c, &config.DecodeOpts{
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &c.ctx,
|
||||
}, raws...)
|
||||
|
||||
@@ -80,8 +80,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -206,8 +204,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -2072,8 +2072,8 @@ func TestConfig_PrepareProvidedWinRMPassword(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func getArmBuilderConfiguration() map[string]interface{} {
|
||||
m := make(map[string]interface{})
|
||||
func getArmBuilderConfiguration() map[string]string {
|
||||
m := make(map[string]string)
|
||||
for _, v := range requiredConfigValues {
|
||||
m[v] = "ignored00"
|
||||
}
|
||||
|
||||
@@ -61,13 +61,7 @@ func (s *StepCreateResourceGroup) Run(ctx context.Context, state multistep.State
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var location = state.Get(constants.ArmLocation).(string)
|
||||
tags, ok := state.Get(constants.ArmTags).(map[string]*string)
|
||||
if !ok {
|
||||
err := fmt.Errorf("failed to extract tags from state bag")
|
||||
state.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
var tags = state.Get(constants.ArmTags).(map[string]*string)
|
||||
|
||||
exists, err := s.exists(ctx, resourceGroupName)
|
||||
if err != nil {
|
||||
|
||||
@@ -220,32 +220,3 @@ func createTestExistingStateBagStepCreateResourceGroup() multistep.StateBag {
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
return stateBag
|
||||
}
|
||||
|
||||
func TestStepCreateResourceGroupShouldFailIfTagsFailCast(t *testing.T) {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
|
||||
|
||||
value := "Unit Test: Tags"
|
||||
tags := map[string]string{
|
||||
"tag01": value,
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, nil },
|
||||
}
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,11 +61,7 @@ func (s *StepDeleteAdditionalDisk) deleteManagedDisk(ctx context.Context, resour
|
||||
func (s *StepDeleteAdditionalDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Deleting the temporary Additional disk ...")
|
||||
|
||||
var dataDisks []string
|
||||
|
||||
if disks := state.Get(constants.ArmAdditionalDiskVhds); disks != nil {
|
||||
dataDisks = disks.([]string)
|
||||
}
|
||||
var dataDisks = state.Get(constants.ArmAdditionalDiskVhds).([]string)
|
||||
var isManagedDisk = state.Get(constants.ArmIsManagedImage).(bool)
|
||||
var isExistingResourceGroup = state.Get(constants.ArmIsExistingResourceGroup).(bool)
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
|
||||
@@ -6,10 +6,8 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
@@ -17,7 +15,7 @@ import (
|
||||
type StepDeployTemplate struct {
|
||||
client *AzureClient
|
||||
deploy func(ctx context.Context, resourceGroupName string, deploymentName string) error
|
||||
delete func(ctx context.Context, deploymentName, resourceGroupName string) error
|
||||
delete func(ctx context.Context, client *AzureClient, resourceType string, resourceName string, resourceGroupName string) error
|
||||
disk func(ctx context.Context, resourceGroupName string, computeName string) (string, string, error)
|
||||
deleteDisk func(ctx context.Context, imageType string, imageName string, resourceGroupName string) error
|
||||
say func(message string)
|
||||
@@ -38,7 +36,7 @@ func NewStepDeployTemplate(client *AzureClient, ui packer.Ui, config *Config, de
|
||||
}
|
||||
|
||||
step.deploy = step.deployTemplate
|
||||
step.delete = step.deleteDeploymentResources
|
||||
step.delete = deleteResource
|
||||
step.disk = step.getImageDetails
|
||||
step.deleteDisk = step.deleteImage
|
||||
return step
|
||||
@@ -64,9 +62,8 @@ func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
|
||||
}
|
||||
}()
|
||||
|
||||
// Only clean up if this is an existing resource group that has been verified to exist.
|
||||
// ArmIsResourceGroupCreated is set in step_create_resource_group to true, when Packer has verified that the resource group exists.
|
||||
// ArmIsExistingResourceGroup is set to true when build_resource_group is set in the Packer configuration.
|
||||
//Only clean up if this was an existing resource group and the resource group
|
||||
//is marked as created
|
||||
existingResourceGroup := state.Get(constants.ArmIsExistingResourceGroup).(bool)
|
||||
resourceGroupCreated := state.Get(constants.ArmIsResourceGroupCreated).(bool)
|
||||
if !existingResourceGroup || !resourceGroupCreated {
|
||||
@@ -78,12 +75,74 @@ func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
|
||||
|
||||
deploymentName := s.name
|
||||
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
|
||||
err := s.deleteDeploymentResources(context.TODO(), deploymentName, resourceGroupName)
|
||||
if err != nil {
|
||||
s.reportIfError(err, resourceGroupName)
|
||||
|
||||
// Get image disk details before deleting the image; otherwise we won't be able to
|
||||
// delete the disk as the image request will return a 404
|
||||
computeName := state.Get(constants.ArmComputeName).(string)
|
||||
imageType, imageName, err := s.disk(context.TODO(), resourceGroupName, computeName)
|
||||
|
||||
if err != nil && !strings.Contains(err.Error(), "ResourceNotFound") {
|
||||
ui.Error(fmt.Sprintf("Could not retrieve OS Image details: %s", err))
|
||||
}
|
||||
|
||||
NewStepDeleteAdditionalDisks(s.client, ui).Run(context.TODO(), state)
|
||||
ui.Say(" -> Deployment Resources within: " + deploymentName)
|
||||
if deploymentName != "" {
|
||||
maxResources := int32(50)
|
||||
deploymentOperations, err := s.client.DeploymentOperationsClient.ListComplete(context.TODO(), resourceGroupName, deploymentName, &maxResources)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting resources. Please delete them manually.\n\n"+
|
||||
"Name: %s\n"+
|
||||
"Error: %s", resourceGroupName, err))
|
||||
}
|
||||
|
||||
for deploymentOperations.NotDone() {
|
||||
deploymentOperation := deploymentOperations.Value()
|
||||
// Sometimes an empty operation is added to the list by Azure
|
||||
if deploymentOperation.Properties.TargetResource == nil {
|
||||
if err := deploymentOperations.Next(); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error moving to to next deployment operation ...\n\n"+
|
||||
"Name: %s\n"+
|
||||
"Error: %s", resourceGroupName, err))
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf(" -> %s : '%s'",
|
||||
*deploymentOperation.Properties.TargetResource.ResourceType,
|
||||
*deploymentOperation.Properties.TargetResource.ResourceName))
|
||||
|
||||
err = s.delete(context.TODO(), s.client,
|
||||
*deploymentOperation.Properties.TargetResource.ResourceType,
|
||||
*deploymentOperation.Properties.TargetResource.ResourceName,
|
||||
resourceGroupName)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
|
||||
"Name: %s\n"+
|
||||
"Error: %s", *deploymentOperation.Properties.TargetResource.ResourceName, err))
|
||||
}
|
||||
|
||||
if err = deploymentOperations.Next(); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting resources. Please delete them manually.\n\n"+
|
||||
"Name: %s\n"+
|
||||
"Error: %s", resourceGroupName, err))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The disk is not defined as an operation in the template so it has to be deleted separately
|
||||
if imageType == "" && imageName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf(" -> %s : '%s'", imageType, imageName))
|
||||
err = s.deleteDisk(context.TODO(), imageType, imageName, resourceGroupName)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
|
||||
"Name: %s\n"+
|
||||
"Error: %s", imageName, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) deployTemplate(ctx context.Context, resourceGroupName string, deploymentName string) error {
|
||||
@@ -93,31 +152,27 @@ func (s *StepDeployTemplate) deployTemplate(ctx context.Context, resourceGroupNa
|
||||
}
|
||||
|
||||
f, err := s.client.DeploymentsClient.CreateOrUpdate(ctx, resourceGroupName, deploymentName, *deployment)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
|
||||
}
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
|
||||
if err == nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) deleteTemplate(ctx context.Context, state multistep.StateBag) error {
|
||||
deploymentName := s.name
|
||||
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var deploymentName = s.name
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say(fmt.Sprintf("Removing the created Deployment object: '%s'", deploymentName))
|
||||
f, err := s.client.DeploymentsClient.Delete(ctx, resourceGroupName, deploymentName)
|
||||
if err != nil {
|
||||
return err
|
||||
if err == nil {
|
||||
err = f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
|
||||
}
|
||||
|
||||
return f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) getImageDetails(ctx context.Context, resourceGroupName string, computeName string) (string, string, error) {
|
||||
@@ -131,12 +186,11 @@ func (s *StepDeployTemplate) getImageDetails(ctx context.Context, resourceGroupN
|
||||
if vm.StorageProfile.OsDisk.Vhd != nil {
|
||||
imageType = "image"
|
||||
imageName = *vm.StorageProfile.OsDisk.Vhd.URI
|
||||
return imageType, imageName, nil
|
||||
} else {
|
||||
imageType = "Microsoft.Compute/disks"
|
||||
imageName = *vm.StorageProfile.OsDisk.ManagedDisk.ID
|
||||
}
|
||||
|
||||
imageType = "Microsoft.Compute/disks"
|
||||
imageName = *vm.StorageProfile.OsDisk.ManagedDisk.ID
|
||||
|
||||
return imageType, imageName, nil
|
||||
}
|
||||
|
||||
@@ -213,58 +267,3 @@ func (s *StepDeployTemplate) deleteImage(ctx context.Context, imageType string,
|
||||
|
||||
return blob.Delete(nil)
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) deleteDeploymentResources(ctx context.Context, deploymentName, resourceGroupName string) error {
|
||||
var maxResources int32 = 50
|
||||
deploymentOperations, err := s.client.DeploymentOperationsClient.ListComplete(ctx, resourceGroupName, deploymentName, &maxResources)
|
||||
if err != nil {
|
||||
s.reportIfError(err, resourceGroupName)
|
||||
return err
|
||||
}
|
||||
|
||||
for deploymentOperations.NotDone() {
|
||||
deploymentOperation := deploymentOperations.Value()
|
||||
// Sometimes an empty operation is added to the list by Azure
|
||||
if deploymentOperation.Properties.TargetResource == nil {
|
||||
_ = deploymentOperations.Next()
|
||||
continue
|
||||
}
|
||||
|
||||
resourceName := *deploymentOperation.Properties.TargetResource.ResourceName
|
||||
resourceType := *deploymentOperation.Properties.TargetResource.ResourceType
|
||||
|
||||
s.say(fmt.Sprintf(" -> %s : '%s'", resourceType, resourceName))
|
||||
|
||||
err = retry.Config{
|
||||
Tries: 10,
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 600 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) error {
|
||||
err := deleteResource(ctx, s.client,
|
||||
resourceType,
|
||||
resourceName,
|
||||
resourceGroupName)
|
||||
if err != nil {
|
||||
s.reportIfError(err, resourceName)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
s.reportIfError(err, resourceName)
|
||||
}
|
||||
|
||||
if err = deploymentOperations.Next(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) reportIfError(err error, resourceName string) {
|
||||
if err != nil {
|
||||
s.say(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
|
||||
"Name: %s\n"+
|
||||
"Error: %s", resourceName, err.Error()))
|
||||
s.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
b.config.ctx.Funcs["vm"] = CreateVMMetadataTemplateFunc()
|
||||
md := &mapstructure.Metadata{}
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
PluginType: BuilderID,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -389,7 +389,6 @@ func newConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
var c Config
|
||||
c.ctx.Funcs = TemplateFuncs
|
||||
err := config.Decode(&c, &config.DecodeOpts{
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &c.ctx,
|
||||
}, raws...)
|
||||
|
||||
@@ -93,8 +93,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -205,8 +203,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -29,8 +29,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -144,8 +142,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -24,8 +24,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -110,8 +108,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -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{} {
|
||||
|
||||
@@ -127,7 +127,6 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
var md mapstructure.Metadata
|
||||
err := config.Decode(c, &config.DecodeOpts{
|
||||
Metadata: &md,
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &c.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
|
||||
@@ -24,8 +24,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -123,8 +121,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -36,15 +36,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
// Run executes a googlecompute Packer build and returns a packer.Artifact
|
||||
// representing a GCE machine image.
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
cfg := GCEDriverConfig{
|
||||
Ui: ui,
|
||||
ProjectId: b.config.ProjectId,
|
||||
Account: b.config.account,
|
||||
ImpersonateServiceAccountName: b.config.ImpersonateServiceAccount,
|
||||
VaultOauthEngineName: b.config.VaultGCPOauthEngine,
|
||||
}
|
||||
|
||||
driver, err := NewDriverGCE(cfg)
|
||||
driver, err := NewDriverGCE(
|
||||
ui, b.config.ProjectId, b.config.account, b.config.VaultGCPOauthEngine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -59,16 +52,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
// Build the steps.
|
||||
steps := []multistep.Step{
|
||||
new(StepCheckExistingImage),
|
||||
&communicator.StepSSHKeyGen{
|
||||
CommConf: &b.config.Comm,
|
||||
SSHTemporaryKeyPair: b.config.Comm.SSH.SSHTemporaryKeyPair,
|
||||
&StepCreateSSHKey{
|
||||
Debug: b.config.PackerDebug,
|
||||
DebugKeyPath: fmt.Sprintf("gce_%s.pem", b.config.PackerBuildName),
|
||||
},
|
||||
multistep.If(b.config.PackerDebug && b.config.Comm.SSHPrivateKeyFile == "",
|
||||
&communicator.StepDumpSSHKey{
|
||||
Path: fmt.Sprintf("gce_%s.pem", b.config.PackerBuildName),
|
||||
SSH: &b.config.Comm.SSH,
|
||||
},
|
||||
),
|
||||
&StepImportOSLoginSSHKey{
|
||||
Debug: b.config.PackerDebug,
|
||||
},
|
||||
@@ -83,11 +70,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
Debug: b.config.PackerDebug,
|
||||
},
|
||||
&StepStartTunnel{
|
||||
IAPConf: &b.config.IAPConfig,
|
||||
CommConf: &b.config.Comm,
|
||||
AccountFile: b.config.AccountFile,
|
||||
ImpersonateAccount: b.config.ImpersonateServiceAccount,
|
||||
ProjectId: b.config.ProjectId,
|
||||
IAPConf: &b.config.IAPConfig,
|
||||
CommConf: &b.config.Comm,
|
||||
AccountFile: b.config.AccountFile,
|
||||
ProjectId: b.config.ProjectId,
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.Comm,
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
)
|
||||
|
||||
@@ -34,8 +35,6 @@ type Config struct {
|
||||
// run Packer on a GCE instance with a service account. Instructions for
|
||||
// creating the file or using service accounts are above.
|
||||
AccountFile string `mapstructure:"account_file" required:"false"`
|
||||
// This allows service account impersonation as per the [docs](https://cloud.google.com/iam/docs/impersonating-service-accounts).
|
||||
ImpersonateServiceAccount string `mapstructure:"impersonate_service_account" required:"false"`
|
||||
// The project ID that will be used to launch instances and store images.
|
||||
ProjectId string `mapstructure:"project_id" required:"true"`
|
||||
// Full or partial URL of the guest accelerator type. GPU accelerators can
|
||||
@@ -76,8 +75,6 @@ type Config struct {
|
||||
EnableIntegrityMonitoring bool `mapstructure:"enable_integrity_monitoring" required:"false"`
|
||||
// Whether to use an IAP proxy.
|
||||
IAPConfig `mapstructure:",squash"`
|
||||
// Skip creating the image. Useful for setting to `true` during a build test stage. Defaults to `false`.
|
||||
SkipCreateImage bool `mapstructure:"skip_create_image" required:"false"`
|
||||
// The unique name of the resulting image. Defaults to
|
||||
// `packer-{{timestamp}}`.
|
||||
ImageName string `mapstructure:"image_name" required:"false"`
|
||||
@@ -87,19 +84,13 @@ type Config struct {
|
||||
// * kmsKeyName - The name of the encryption key that is stored in Google Cloud KMS.
|
||||
// * RawKey: - A 256-bit customer-supplied encryption key, encodes in RFC 4648 base64.
|
||||
//
|
||||
// examples:
|
||||
// example:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "kmsKeyName": "projects/${project}/locations/${region}/keyRings/computeEngine/cryptoKeys/computeEngine/cryptoKeyVersions/4"
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// ```hcl
|
||||
// image_encryption_key {
|
||||
// kmsKeyName = "projects/${var.project}/locations/${var.region}/keyRings/computeEngine/cryptoKeys/computeEngine/cryptoKeyVersions/4"
|
||||
// }
|
||||
// ```
|
||||
ImageEncryptionKey *CustomerEncryptionKey `mapstructure:"image_encryption_key" required:"false"`
|
||||
// The name of the image family to which the resulting image belongs. You
|
||||
// can create disks by specifying an image family instead of a specific
|
||||
@@ -275,7 +266,7 @@ type Config struct {
|
||||
UseOSLogin bool `mapstructure:"use_os_login" required:"false"`
|
||||
// Can be set instead of account_file. If set, this builder will use
|
||||
// HashiCorp Vault to generate an Oauth token for authenticating against
|
||||
// Google Cloud. The value should be the path of the token generator
|
||||
// Google's cloud. The value should be the path of the token generator
|
||||
// within vault.
|
||||
// For information on how to configure your Vault + GCP engine to produce
|
||||
// Oauth tokens, see https://www.vaultproject.io/docs/auth/gcp
|
||||
@@ -289,7 +280,7 @@ type Config struct {
|
||||
// Example: "us-central1-a"
|
||||
Zone string `mapstructure:"zone" required:"true"`
|
||||
|
||||
account *ServiceAccount
|
||||
account *jwt.Config
|
||||
imageAlreadyExists bool
|
||||
ctx interpolate.Context
|
||||
}
|
||||
@@ -297,7 +288,6 @@ type Config struct {
|
||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
c.ctx.Funcs = TemplateFuncs
|
||||
err := config.Decode(c, &config.DecodeOpts{
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &c.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
@@ -491,9 +481,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
|
||||
// Authenticating via an account file
|
||||
if c.AccountFile != "" {
|
||||
if c.VaultGCPOauthEngine != "" && c.ImpersonateServiceAccount != "" {
|
||||
if c.VaultGCPOauthEngine != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("You cannot "+
|
||||
"specify impersonate_service_account, account_file and vault_gcp_oauth_engine at the same time"))
|
||||
"specify both account_file and vault_gcp_oauth_engine."))
|
||||
}
|
||||
cfg, err := ProcessAccountFile(c.AccountFile)
|
||||
if err != nil {
|
||||
@@ -545,11 +535,11 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
type CustomerEncryptionKey struct {
|
||||
// KmsKeyName: The name of the encryption key that is stored in Google
|
||||
// Cloud KMS.
|
||||
KmsKeyName string `mapstructure:"kmsKeyName" json:"kmsKeyName,omitempty"`
|
||||
KmsKeyName string `json:"kmsKeyName,omitempty"`
|
||||
|
||||
// RawKey: Specifies a 256-bit customer-supplied encryption key, encoded
|
||||
// in RFC 4648 base64 to either encrypt or decrypt this resource.
|
||||
RawKey string `mapstructure:"rawKey" json:"rawKey,omitempty"`
|
||||
RawKey string `json:"rawKey,omitempty"`
|
||||
}
|
||||
|
||||
func (k *CustomerEncryptionKey) ComputeType() *compute.CustomerEncryptionKey {
|
||||
|
||||
@@ -24,8 +24,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -66,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"`
|
||||
@@ -83,7 +80,6 @@ type FlatConfig struct {
|
||||
IAPHashBang *string `mapstructure:"iap_hashbang" required:"false" cty:"iap_hashbang" hcl:"iap_hashbang"`
|
||||
IAPExt *string `mapstructure:"iap_ext" required:"false" cty:"iap_ext" hcl:"iap_ext"`
|
||||
IAPTunnelLaunchWait *int `mapstructure:"iap_tunnel_launch_wait" required:"false" cty:"iap_tunnel_launch_wait" hcl:"iap_tunnel_launch_wait"`
|
||||
SkipCreateImage *bool `mapstructure:"skip_create_image" required:"false" cty:"skip_create_image" hcl:"skip_create_image"`
|
||||
ImageName *string `mapstructure:"image_name" required:"false" cty:"image_name" hcl:"image_name"`
|
||||
ImageDescription *string `mapstructure:"image_description" required:"false" cty:"image_description" hcl:"image_description"`
|
||||
ImageEncryptionKey *FlatCustomerEncryptionKey `mapstructure:"image_encryption_key" required:"false" cty:"image_encryption_key" hcl:"image_encryption_key"`
|
||||
@@ -146,8 +142,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
@@ -188,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},
|
||||
@@ -205,7 +198,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"iap_hashbang": &hcldec.AttrSpec{Name: "iap_hashbang", Type: cty.String, Required: false},
|
||||
"iap_ext": &hcldec.AttrSpec{Name: "iap_ext", Type: cty.String, Required: false},
|
||||
"iap_tunnel_launch_wait": &hcldec.AttrSpec{Name: "iap_tunnel_launch_wait", Type: cty.Number, Required: false},
|
||||
"skip_create_image": &hcldec.AttrSpec{Name: "skip_create_image", Type: cty.Bool, Required: false},
|
||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
||||
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
|
||||
"image_encryption_key": &hcldec.BlockSpec{TypeName: "image_encryption_key", Nested: hcldec.ObjectSpec((*FlatCustomerEncryptionKey)(nil).HCL2Spec())},
|
||||
@@ -246,8 +238,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
// FlatCustomerEncryptionKey is an auto-generated flat version of CustomerEncryptionKey.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatCustomerEncryptionKey struct {
|
||||
KmsKeyName *string `mapstructure:"kmsKeyName" json:"kmsKeyName,omitempty" cty:"kmsKeyName" hcl:"kmsKeyName"`
|
||||
RawKey *string `mapstructure:"rawKey" json:"rawKey,omitempty" cty:"rawKey" hcl:"rawKey"`
|
||||
KmsKeyName *string `json:"kmsKeyName,omitempty" cty:"kms_key_name" hcl:"kms_key_name"`
|
||||
RawKey *string `json:"rawKey,omitempty" cty:"raw_key" hcl:"raw_key"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatCustomerEncryptionKey.
|
||||
@@ -262,8 +254,8 @@ func (*CustomerEncryptionKey) FlatMapstructure() interface{ HCL2Spec() map[strin
|
||||
// The decoded values from this spec will then be applied to a FlatCustomerEncryptionKey.
|
||||
func (*FlatCustomerEncryptionKey) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"kmsKeyName": &hcldec.AttrSpec{Name: "kmsKeyName", Type: cty.String, Required: false},
|
||||
"rawKey": &hcldec.AttrSpec{Name: "rawKey", Type: cty.String, Required: false},
|
||||
"kms_key_name": &hcldec.AttrSpec{Name: "kms_key_name", Type: cty.String, Required: false},
|
||||
"raw_key": &hcldec.AttrSpec{Name: "raw_key", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -24,6 +25,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 +37,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 +72,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 +115,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.NewService(context.TODO(), option.WithHTTPClient(client))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Instantiating OS Login client...")
|
||||
osLoginService, err := oslogin.NewService(context.TODO(), opts)
|
||||
osLoginService, err := oslogin.NewService(context.TODO(), option.WithHTTPClient(client))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -149,10 +139,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
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,6 @@ func (s *StepCreateImage) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if config.SkipCreateImage {
|
||||
ui.Say("Skipping image creation...")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if config.PackerForce && config.imageAlreadyExists {
|
||||
ui.Say("Deleting previous image...")
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// StepCreateSSHKey represents a Packer build step that generates SSH key pairs.
|
||||
type StepCreateSSHKey struct {
|
||||
Debug bool
|
||||
DebugKeyPath string
|
||||
}
|
||||
|
||||
// Run executes the Packer build step that generates SSH key pairs.
|
||||
// The key pairs are added to the ssh config
|
||||
func (s *StepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
config := state.Get("config").(*Config)
|
||||
|
||||
if config.Comm.SSHPrivateKeyFile != "" {
|
||||
ui.Say("Using existing SSH private key")
|
||||
privateKeyBytes, err := config.Comm.ReadSSHPrivateKeyFile()
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
config.Comm.SSHPrivateKey = privateKeyBytes
|
||||
config.Comm.SSHPublicKey = nil
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say("Creating temporary SSH key for instance...")
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating temporary ssh key: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
priv_blk := pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Headers: nil,
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(priv),
|
||||
}
|
||||
|
||||
pub, err := ssh.NewPublicKey(&priv.PublicKey)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating temporary ssh key: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
config.Comm.SSHPrivateKey = pem.EncodeToMemory(&priv_blk)
|
||||
config.Comm.SSHPublicKey = ssh.MarshalAuthorizedKey(pub)
|
||||
|
||||
if s.Debug {
|
||||
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
|
||||
f, err := os.Create(s.DebugKeyPath)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Write out the key
|
||||
err = pem.Encode(f, &priv_blk)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Nothing to clean up. SSH keys are associated with a single GCE instance.
|
||||
func (s *StepCreateSSHKey) Cleanup(state multistep.StateBag) {}
|
||||
@@ -0,0 +1,86 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStepCreateSSHKey_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepCreateSSHKey)
|
||||
}
|
||||
|
||||
func TestStepCreateSSHKey_privateKey(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateSSHKey)
|
||||
cfg := state.Get("config").(*Config)
|
||||
cfg.Comm.SSHPrivateKeyFile = "test-fixtures/fake-key"
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify that we have a public/private key
|
||||
if len(cfg.Comm.SSHPrivateKey) == 0 {
|
||||
t.Fatal("should have key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateSSHKey(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateSSHKey)
|
||||
cfg := state.Get("config").(*Config)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify that we have a public/private key
|
||||
if len(cfg.Comm.SSHPrivateKey) == 0 {
|
||||
t.Fatal("should have key")
|
||||
}
|
||||
if len(cfg.Comm.SSHPublicKey) == 0 {
|
||||
t.Fatal("should have key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateSSHKey_debug(t *testing.T) {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
tf.Close()
|
||||
|
||||
state := testState(t)
|
||||
step := new(StepCreateSSHKey)
|
||||
cfg := state.Get("config").(*Config)
|
||||
step.Debug = true
|
||||
step.DebugKeyPath = tf.Name()
|
||||
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify that we have a public/private key
|
||||
if len(cfg.Comm.SSHPrivateKey) == 0 {
|
||||
t.Fatal("should have key")
|
||||
}
|
||||
if len(cfg.Comm.SSHPublicKey) == 0 {
|
||||
t.Fatal("should have key")
|
||||
}
|
||||
if _, err := os.Stat(tf.Name()); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -131,11 +131,10 @@ func (e RetryableTunnelError) Error() string {
|
||||
}
|
||||
|
||||
type StepStartTunnel struct {
|
||||
IAPConf *IAPConfig
|
||||
CommConf *communicator.Config
|
||||
AccountFile string
|
||||
ImpersonateAccount string
|
||||
ProjectId string
|
||||
IAPConf *IAPConfig
|
||||
CommConf *communicator.Config
|
||||
AccountFile string
|
||||
ProjectId string
|
||||
|
||||
tunnelDriver TunnelDriver
|
||||
}
|
||||
@@ -277,10 +276,6 @@ func (s *StepStartTunnel) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
"--zone", c.Zone, "--project", s.ProjectId,
|
||||
}
|
||||
|
||||
if s.ImpersonateAccount != "" {
|
||||
args = append(args, fmt.Sprintf("--impersonate-service-account='%s'", s.ImpersonateAccount))
|
||||
}
|
||||
|
||||
// This is the port the IAP tunnel listens on, on localhost.
|
||||
// TODO make setting LocalHostPort optional
|
||||
err = ApplyIAPTunnel(s.CommConf, s.IAPConf.IAPLocalhostPort)
|
||||
|
||||
@@ -24,8 +24,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -108,8 +106,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -25,8 +25,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -131,8 +129,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -89,7 +89,6 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
PluginType: hypervcommon.BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
|
||||
@@ -38,8 +38,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -159,8 +157,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -82,7 +82,6 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
PluginType: hypervcommon.BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
|
||||
@@ -38,8 +38,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -161,8 +159,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -17,7 +17,6 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
PluginType: BUILDER_ID,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
|
||||
@@ -26,8 +26,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -109,8 +107,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -3,7 +3,6 @@ package linode
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
@@ -249,43 +248,3 @@ func TestBuilderPrepare_Label(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_StateTimeout(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test default
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.StateTimeout != 5*time.Minute {
|
||||
t.Errorf("invalid: %s", b.config.StateTimeout)
|
||||
}
|
||||
|
||||
// Test set
|
||||
config["state_timeout"] = "5m"
|
||||
b = Builder{}
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
config["state_timeout"] = "tubes"
|
||||
b = Builder{}
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
+10
-17
@@ -9,7 +9,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
@@ -25,17 +24,16 @@ type Config struct {
|
||||
|
||||
PersonalAccessToken string `mapstructure:"linode_token"`
|
||||
|
||||
Region string `mapstructure:"region"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
Label string `mapstructure:"instance_label"`
|
||||
Tags []string `mapstructure:"instance_tags"`
|
||||
Image string `mapstructure:"image"`
|
||||
SwapSize int `mapstructure:"swap_size"`
|
||||
RootPass string `mapstructure:"root_pass"`
|
||||
RootSSHKey string `mapstructure:"root_ssh_key"`
|
||||
ImageLabel string `mapstructure:"image_label"`
|
||||
Description string `mapstructure:"image_description"`
|
||||
StateTimeout time.Duration `mapstructure:"state_timeout" required:"false"`
|
||||
Region string `mapstructure:"region"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
Label string `mapstructure:"instance_label"`
|
||||
Tags []string `mapstructure:"instance_tags"`
|
||||
Image string `mapstructure:"image"`
|
||||
SwapSize int `mapstructure:"swap_size"`
|
||||
RootPass string `mapstructure:"root_pass"`
|
||||
RootSSHKey string `mapstructure:"root_ssh_key"`
|
||||
ImageLabel string `mapstructure:"image_label"`
|
||||
Description string `mapstructure:"image_description"`
|
||||
}
|
||||
|
||||
func createRandomRootPassword() (string, error) {
|
||||
@@ -96,11 +94,6 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if c.StateTimeout == 0 {
|
||||
// Default to 5 minute timeouts waiting for state change
|
||||
c.StateTimeout = 5 * time.Minute
|
||||
}
|
||||
|
||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||
errs = packer.MultiErrorAppend(errs, es...)
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -76,7 +74,6 @@ type FlatConfig struct {
|
||||
RootSSHKey *string `mapstructure:"root_ssh_key" cty:"root_ssh_key" hcl:"root_ssh_key"`
|
||||
ImageLabel *string `mapstructure:"image_label" cty:"image_label" hcl:"image_label"`
|
||||
Description *string `mapstructure:"image_description" cty:"image_description" hcl:"image_description"`
|
||||
StateTimeout *string `mapstructure:"state_timeout" required:"false" cty:"state_timeout" hcl:"state_timeout"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
@@ -106,8 +103,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
@@ -158,7 +153,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"root_ssh_key": &hcldec.AttrSpec{Name: "root_ssh_key", Type: cty.String, Required: false},
|
||||
"image_label": &hcldec.AttrSpec{Name: "image_label", Type: cty.String, Required: false},
|
||||
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
|
||||
"state_timeout": &hcldec.AttrSpec{Name: "state_timeout", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ type stepShutdownLinode struct {
|
||||
}
|
||||
|
||||
func (s *stepShutdownLinode) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
c := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*linodego.Instance)
|
||||
|
||||
@@ -26,7 +25,7 @@ func (s *stepShutdownLinode) Run(ctx context.Context, state multistep.StateBag)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
_, err := s.client.WaitForInstanceStatus(ctx, instance.ID, linodego.InstanceOffline, int(c.StateTimeout.Seconds()))
|
||||
_, err := s.client.WaitForInstanceStatus(ctx, instance.ID, linodego.InstanceOffline, 120)
|
||||
if err != nil {
|
||||
err = errors.New("Error shutting down Linode: " + err.Error())
|
||||
state.Put("error", err)
|
||||
|
||||
@@ -36,8 +36,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -118,8 +116,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -21,7 +21,6 @@ type Config struct {
|
||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
|
||||
err := config.Decode(c, &config.DecodeOpts{
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateFilter: &interpolate.RenderFilter{},
|
||||
}, raws...)
|
||||
|
||||
@@ -24,8 +24,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -94,8 +92,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -24,8 +24,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -102,8 +100,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -40,7 +40,6 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
}, raws...)
|
||||
|
||||
@@ -52,8 +52,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -181,8 +179,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -252,21 +252,14 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
}
|
||||
}
|
||||
|
||||
hasOnlySourceImage := len(c.SourceImage) > 0 && len(c.SourceImageName) == 0 && len(c.ExternalSourceImageURL) == 0
|
||||
hasOnlySourceImageName := len(c.SourceImageName) > 0 && len(c.SourceImage) == 0 && len(c.ExternalSourceImageURL) == 0
|
||||
hasOnlyExternalSourceImageURL := len(c.ExternalSourceImageURL) > 0 && len(c.SourceImage) == 0 && len(c.SourceImageName) == 0
|
||||
|
||||
if c.SourceImage == "" && c.SourceImageName == "" && c.ExternalSourceImageURL == "" && c.SourceImageFilters.Filters.Empty() {
|
||||
errs = append(errs, errors.New("Either a source_image, a source_image_name, an external_source_image_url or source_image_filter must be specified"))
|
||||
} else {
|
||||
// Make sure we've only set one image source option
|
||||
thereCanBeOnlyOne := []bool{len(c.SourceImageName) > 0, len(c.SourceImage) > 0, len(c.ExternalSourceImageURL) > 0, !c.SourceImageFilters.Filters.Empty()}
|
||||
numSet := 0
|
||||
for _, val := range thereCanBeOnlyOne {
|
||||
if val {
|
||||
numSet += 1
|
||||
}
|
||||
}
|
||||
|
||||
if numSet > 1 {
|
||||
errs = append(errs, errors.New("Only one of the options source_image, source_image_name, external_source_image_url, or source_image_filter can be specified, not multiple."))
|
||||
}
|
||||
} else if !(hasOnlySourceImage || hasOnlySourceImageName || hasOnlyExternalSourceImageURL) {
|
||||
errs = append(errs, errors.New("Only a source_image, a source_image_name or an external_source_image_url can be specified, not multiple."))
|
||||
}
|
||||
|
||||
// if external_source_image_format is not set use qcow2 as default
|
||||
|
||||
@@ -141,7 +141,6 @@ func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c = testRunConfig()
|
||||
// test setting both ExternalSourceImageURL and SourceImageName causes an error
|
||||
c.SourceImage = ""
|
||||
c.SourceImageName = "abcd"
|
||||
@@ -150,7 +149,6 @@ func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c = testRunConfig()
|
||||
// test neither setting SourceImage, SourceImageName or ExternalSourceImageURL causes an error
|
||||
c.SourceImage = ""
|
||||
c.SourceImageName = ""
|
||||
@@ -159,7 +157,6 @@ func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c = testRunConfig()
|
||||
// test setting only ExternalSourceImageURL passes
|
||||
c.SourceImage = ""
|
||||
c.SourceImageName = ""
|
||||
@@ -177,27 +174,6 @@ func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
|
||||
if matches, _ := regexp.MatchString(p, c.SourceImageName); !matches {
|
||||
t.Fatalf("invalid format for SourceImageName: %s", c.SourceImageName)
|
||||
}
|
||||
|
||||
c = testRunConfig()
|
||||
// test setting a filter passes
|
||||
c.SourceImage = ""
|
||||
c.SourceImageName = ""
|
||||
c.ExternalSourceImageURL = ""
|
||||
c.SourceImageFilters = ImageFilter{
|
||||
Filters: ImageFilterOptions{
|
||||
Name: "Ubuntu 16.04",
|
||||
Visibility: "public",
|
||||
Owner: "1234567890",
|
||||
Tags: []string{"prod", "ready"},
|
||||
Properties: map[string]string{"os_distro": "ubuntu", "os_version": "16.04"},
|
||||
},
|
||||
MostRecent: true,
|
||||
}
|
||||
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("Should not error if everything but filter is empty: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This test case confirms that only allowed fields will be set to values
|
||||
|
||||
@@ -31,8 +31,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -121,8 +119,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest
|
||||
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails
|
||||
|
||||
package oci
|
||||
|
||||
@@ -35,16 +35,6 @@ type CreateVNICDetails struct {
|
||||
SubnetId *string `mapstructure:"subnet_id" required:"false"`
|
||||
}
|
||||
|
||||
type ListImagesRequest struct {
|
||||
// fields that can be specified under "base_image_filter"
|
||||
CompartmentId *string `mapstructure:"compartment_id"`
|
||||
DisplayName *string `mapstructure:"display_name"`
|
||||
DisplayNameSearch *string `mapstructure:"display_name_search"`
|
||||
OperatingSystem *string `mapstructure:"operating_system"`
|
||||
OperatingSystemVersion *string `mapstructure:"operating_system_version"`
|
||||
Shape *string `mapstructure:"shape"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
@@ -79,17 +69,14 @@ type Config struct {
|
||||
CompartmentID string `mapstructure:"compartment_ocid"`
|
||||
|
||||
// Image
|
||||
BaseImageID string `mapstructure:"base_image_ocid"`
|
||||
BaseImageFilter ListImagesRequest `mapstructure:"base_image_filter"`
|
||||
ImageName string `mapstructure:"image_name"`
|
||||
ImageCompartmentID string `mapstructure:"image_compartment_ocid"`
|
||||
BaseImageID string `mapstructure:"base_image_ocid"`
|
||||
ImageName string `mapstructure:"image_name"`
|
||||
|
||||
// Instance
|
||||
InstanceName *string `mapstructure:"instance_name"`
|
||||
InstanceName string `mapstructure:"instance_name"`
|
||||
InstanceTags map[string]string `mapstructure:"instance_tags"`
|
||||
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags"`
|
||||
Shape string `mapstructure:"shape"`
|
||||
BootVolumeSizeInGBs int64 `mapstructure:"disk_size"`
|
||||
|
||||
// Metadata optionally contains custom metadata key/value pairs provided in the
|
||||
// configuration. While this can be used to set metadata["user_data"] the explicit
|
||||
@@ -266,10 +253,6 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
||||
c.CompartmentID = tenancyOCID
|
||||
}
|
||||
|
||||
if c.ImageCompartmentID == "" {
|
||||
c.ImageCompartmentID = c.CompartmentID
|
||||
}
|
||||
|
||||
if c.Shape == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("'shape' must be specified"))
|
||||
@@ -280,17 +263,9 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
||||
errs, errors.New("'subnet_ocid' must be specified"))
|
||||
}
|
||||
|
||||
if (c.BaseImageID == "") && (c.BaseImageFilter == ListImagesRequest{}) {
|
||||
if c.BaseImageID == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("'base_image_ocid' or 'base_image_filter' must be specified"))
|
||||
}
|
||||
|
||||
if c.BaseImageFilter.CompartmentId == nil {
|
||||
c.BaseImageFilter.CompartmentId = &c.CompartmentID
|
||||
}
|
||||
|
||||
if c.BaseImageFilter.Shape == nil {
|
||||
c.BaseImageFilter.Shape = &c.Shape
|
||||
errs, errors.New("'base_image_ocid' must be specified"))
|
||||
}
|
||||
|
||||
// Validate tag lengths. TODO (hlowndes) maximum number of tags allowed.
|
||||
@@ -351,15 +326,6 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Set default boot volume size to 50 if not set
|
||||
// Check if size set is allowed by OCI
|
||||
if c.BootVolumeSizeInGBs == 0 {
|
||||
c.BootVolumeSizeInGBs = 50
|
||||
} else if c.BootVolumeSizeInGBs < 50 || c.BootVolumeSizeInGBs > 16384 {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("'disk_size' must be between 50 and 16384 GBs"))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails"; DO NOT EDIT.
|
||||
package oci
|
||||
|
||||
import (
|
||||
@@ -24,8 +24,6 @@ type FlatConfig struct {
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
@@ -78,14 +76,11 @@ type FlatConfig struct {
|
||||
AvailabilityDomain *string `mapstructure:"availability_domain" cty:"availability_domain" hcl:"availability_domain"`
|
||||
CompartmentID *string `mapstructure:"compartment_ocid" cty:"compartment_ocid" hcl:"compartment_ocid"`
|
||||
BaseImageID *string `mapstructure:"base_image_ocid" cty:"base_image_ocid" hcl:"base_image_ocid"`
|
||||
BaseImageFilter *FlatListImagesRequest `mapstructure:"base_image_filter" cty:"base_image_filter" hcl:"base_image_filter"`
|
||||
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
|
||||
ImageCompartmentID *string `mapstructure:"image_compartment_ocid" cty:"image_compartment_ocid" hcl:"image_compartment_ocid"`
|
||||
InstanceName *string `mapstructure:"instance_name" cty:"instance_name" hcl:"instance_name"`
|
||||
InstanceTags map[string]string `mapstructure:"instance_tags" cty:"instance_tags" hcl:"instance_tags"`
|
||||
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags" cty:"instance_defined_tags" hcl:"instance_defined_tags"`
|
||||
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
|
||||
BootVolumeSizeInGBs *int64 `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
|
||||
Metadata map[string]string `mapstructure:"metadata" cty:"metadata" hcl:"metadata"`
|
||||
UserData *string `mapstructure:"user_data" cty:"user_data" hcl:"user_data"`
|
||||
UserDataFile *string `mapstructure:"user_data_file" cty:"user_data_file" hcl:"user_data_file"`
|
||||
@@ -122,8 +117,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
@@ -176,14 +169,11 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"availability_domain": &hcldec.AttrSpec{Name: "availability_domain", Type: cty.String, Required: false},
|
||||
"compartment_ocid": &hcldec.AttrSpec{Name: "compartment_ocid", Type: cty.String, Required: false},
|
||||
"base_image_ocid": &hcldec.AttrSpec{Name: "base_image_ocid", Type: cty.String, Required: false},
|
||||
"base_image_filter": &hcldec.BlockSpec{TypeName: "base_image_filter", Nested: hcldec.ObjectSpec((*FlatListImagesRequest)(nil).HCL2Spec())},
|
||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
||||
"image_compartment_ocid": &hcldec.AttrSpec{Name: "image_compartment_ocid", Type: cty.String, Required: false},
|
||||
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
|
||||
"instance_tags": &hcldec.AttrSpec{Name: "instance_tags", Type: cty.Map(cty.String), Required: false},
|
||||
"instance_defined_tags": &hcldec.AttrSpec{Name: "instance_defined_tags", Type: cty.Map(cty.String), Required: false},
|
||||
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
|
||||
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
|
||||
"metadata": &hcldec.AttrSpec{Name: "metadata", Type: cty.Map(cty.String), Required: false},
|
||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
|
||||
@@ -233,36 +223,3 @@ func (*FlatCreateVNICDetails) HCL2Spec() map[string]hcldec.Spec {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatListImagesRequest is an auto-generated flat version of ListImagesRequest.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatListImagesRequest struct {
|
||||
CompartmentId *string `mapstructure:"compartment_id" cty:"compartment_id" hcl:"compartment_id"`
|
||||
DisplayName *string `mapstructure:"display_name" cty:"display_name" hcl:"display_name"`
|
||||
DisplayNameSearch *string `mapstructure:"display_name_search" cty:"display_name_search" hcl:"display_name_search"`
|
||||
OperatingSystem *string `mapstructure:"operating_system" cty:"operating_system" hcl:"operating_system"`
|
||||
OperatingSystemVersion *string `mapstructure:"operating_system_version" cty:"operating_system_version" hcl:"operating_system_version"`
|
||||
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatListImagesRequest.
|
||||
// FlatListImagesRequest is an auto-generated flat version of ListImagesRequest.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*ListImagesRequest) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatListImagesRequest)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a ListImagesRequest.
|
||||
// This spec is used by HCL to read the fields of ListImagesRequest.
|
||||
// The decoded values from this spec will then be applied to a FlatListImagesRequest.
|
||||
func (*FlatListImagesRequest) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"compartment_id": &hcldec.AttrSpec{Name: "compartment_id", Type: cty.String, Required: false},
|
||||
"display_name": &hcldec.AttrSpec{Name: "display_name", Type: cty.String, Required: false},
|
||||
"display_name_search": &hcldec.AttrSpec{Name: "display_name_search", Type: cty.String, Required: false},
|
||||
"operating_system": &hcldec.AttrSpec{Name: "operating_system", Type: cty.String, Required: false},
|
||||
"operating_system_version": &hcldec.AttrSpec{Name: "operating_system_version", Type: cty.String, Required: false},
|
||||
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -44,8 +44,7 @@ func testConfig(accessConfFile *os.File) map[string]interface{} {
|
||||
"create_vnic_details": map[string]interface{}{
|
||||
"nsg_ids": []string{"ocd1..."},
|
||||
},
|
||||
"shape": "VM.Standard1.1",
|
||||
"disk_size": 60,
|
||||
"shape": "VM.Standard1.1",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,36 +87,6 @@ func TestConfig(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("BaseImageFilterWithoutOCID", func(t *testing.T) {
|
||||
raw := testConfig(cfgFile)
|
||||
raw["base_image_ocid"] = ""
|
||||
raw["base_image_filter"] = map[string]interface{}{
|
||||
"display_name": "hello_world",
|
||||
}
|
||||
|
||||
var c Config
|
||||
errs := c.Prepare(raw)
|
||||
|
||||
if errs != nil {
|
||||
t.Fatalf("Unexpected error in configuration %+v", errs)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("BaseImageFilterDefault", func(t *testing.T) {
|
||||
raw := testConfig(cfgFile)
|
||||
|
||||
var c Config
|
||||
errs := c.Prepare(raw)
|
||||
if errs != nil {
|
||||
t.Fatalf("Unexpected error in configuration %+v", errs)
|
||||
}
|
||||
|
||||
if *c.BaseImageFilter.Shape != raw["shape"] {
|
||||
t.Fatalf("Default base_image_filter shape %v does not equal config shape %v",
|
||||
*c.BaseImageFilter.Shape, raw["shape"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NoAccessConfig", func(t *testing.T) {
|
||||
raw := testConfig(cfgFile)
|
||||
delete(raw, "access_cfg_file")
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
core "github.com/oracle/oci-go-sdk/core"
|
||||
@@ -52,7 +51,23 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
|
||||
metadata["user_data"] = d.cfg.UserData
|
||||
}
|
||||
|
||||
// Create VNIC details for instance
|
||||
instanceDetails := core.LaunchInstanceDetails{
|
||||
AvailabilityDomain: &d.cfg.AvailabilityDomain,
|
||||
CompartmentId: &d.cfg.CompartmentID,
|
||||
DefinedTags: d.cfg.InstanceDefinedTags,
|
||||
FreeformTags: d.cfg.InstanceTags,
|
||||
ImageId: &d.cfg.BaseImageID,
|
||||
Shape: &d.cfg.Shape,
|
||||
SubnetId: &d.cfg.SubnetID,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
// When empty, the default display name is used.
|
||||
if d.cfg.InstanceName != "" {
|
||||
instanceDetails.DisplayName = &d.cfg.InstanceName
|
||||
}
|
||||
|
||||
// Pass VNIC details, if specified, to the instance
|
||||
CreateVnicDetails := core.CreateVnicDetails{
|
||||
AssignPublicIp: d.cfg.CreateVnicDetails.AssignPublicIp,
|
||||
DisplayName: d.cfg.CreateVnicDetails.DisplayName,
|
||||
@@ -65,68 +80,7 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
|
||||
FreeformTags: d.cfg.CreateVnicDetails.FreeformTags,
|
||||
}
|
||||
|
||||
// Determine base image ID
|
||||
var imageId *string
|
||||
if d.cfg.BaseImageID != "" {
|
||||
imageId = &d.cfg.BaseImageID
|
||||
} else {
|
||||
// Pull images and determine which image ID to use, if BaseImageId not specified
|
||||
response, err := d.computeClient.ListImages(ctx, core.ListImagesRequest{
|
||||
CompartmentId: d.cfg.BaseImageFilter.CompartmentId,
|
||||
DisplayName: d.cfg.BaseImageFilter.DisplayName,
|
||||
OperatingSystem: d.cfg.BaseImageFilter.OperatingSystem,
|
||||
OperatingSystemVersion: d.cfg.BaseImageFilter.OperatingSystemVersion,
|
||||
Shape: d.cfg.BaseImageFilter.Shape,
|
||||
LifecycleState: "AVAILABLE",
|
||||
SortBy: "TIMECREATED",
|
||||
SortOrder: "DESC",
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(response.Items) == 0 {
|
||||
return "", errors.New("base_image_filter returned no images")
|
||||
}
|
||||
if d.cfg.BaseImageFilter.DisplayNameSearch != nil {
|
||||
// Return most recent image that matches regex
|
||||
imageNameRegex, err := regexp.Compile(*d.cfg.BaseImageFilter.DisplayNameSearch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, image := range response.Items {
|
||||
if imageNameRegex.MatchString(*image.DisplayName) {
|
||||
imageId = image.Id
|
||||
break
|
||||
}
|
||||
}
|
||||
if imageId == nil {
|
||||
return "", errors.New("No image matched display_name_search criteria")
|
||||
}
|
||||
} else {
|
||||
// If no regex provided, simply return most recent image pulled
|
||||
imageId = response.Items[0].Id
|
||||
}
|
||||
}
|
||||
|
||||
// Create Source details which will be used to Launch Instance
|
||||
InstanceSourceDetails := core.InstanceSourceViaImageDetails{
|
||||
ImageId: imageId,
|
||||
BootVolumeSizeInGBs: &d.cfg.BootVolumeSizeInGBs,
|
||||
}
|
||||
|
||||
// Build instance details
|
||||
instanceDetails := core.LaunchInstanceDetails{
|
||||
AvailabilityDomain: &d.cfg.AvailabilityDomain,
|
||||
CompartmentId: &d.cfg.CompartmentID,
|
||||
CreateVnicDetails: &CreateVnicDetails,
|
||||
DefinedTags: d.cfg.InstanceDefinedTags,
|
||||
DisplayName: d.cfg.InstanceName,
|
||||
FreeformTags: d.cfg.InstanceTags,
|
||||
Shape: &d.cfg.Shape,
|
||||
SourceDetails: InstanceSourceDetails,
|
||||
SubnetId: &d.cfg.SubnetID,
|
||||
Metadata: metadata,
|
||||
}
|
||||
instanceDetails.CreateVnicDetails = &CreateVnicDetails
|
||||
|
||||
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{LaunchInstanceDetails: instanceDetails})
|
||||
|
||||
@@ -140,7 +94,7 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
|
||||
// CreateImage creates a new custom image.
|
||||
func (d *driverOCI) CreateImage(ctx context.Context, id string) (core.Image, error) {
|
||||
res, err := d.computeClient.CreateImage(ctx, core.CreateImageRequest{CreateImageDetails: core.CreateImageDetails{
|
||||
CompartmentId: &d.cfg.ImageCompartmentID,
|
||||
CompartmentId: &d.cfg.CompartmentID,
|
||||
InstanceId: &id,
|
||||
DisplayName: &d.cfg.ImageName,
|
||||
FreeformTags: d.cfg.Tags,
|
||||
|
||||
+22
-12
@@ -9,7 +9,9 @@ package bsu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
osccommon "github.com/hashicorp/packer/builder/osc/common"
|
||||
@@ -19,6 +21,7 @@ import (
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
)
|
||||
|
||||
// The unique ID for this builder
|
||||
@@ -45,7 +48,6 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
b.config.ctx.Funcs = osccommon.TemplateFuncs
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
@@ -84,13 +86,24 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
oscConn := b.config.NewOSCClient()
|
||||
clientConfig, err := b.config.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
skipClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
oapiconn := oapi.NewClient(clientConfig, skipClient)
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("osc", oscConn)
|
||||
state.Put("accessConfig", &b.config.AccessConfig)
|
||||
state.Put("oapi", oapiconn)
|
||||
state.Put("clientConfig", clientConfig)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
@@ -116,7 +129,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
&osccommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
DebugKeyPath: fmt.Sprintf("osc_%s", b.config.PackerBuildName),
|
||||
DebugKeyPath: fmt.Sprintf("oapi_%s", b.config.PackerBuildName),
|
||||
},
|
||||
&osccommon.StepPublicIp{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
@@ -148,7 +161,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
VolumeTags: b.config.VolumeRunTags,
|
||||
RawRegion: b.config.RawRegion,
|
||||
},
|
||||
&osccommon.StepGetPassword{
|
||||
Debug: b.config.PackerDebug,
|
||||
@@ -158,8 +170,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: osccommon.OscSSHHost(
|
||||
oscConn.VmApi,
|
||||
Host: osccommon.SSHHost(
|
||||
oapiconn,
|
||||
b.config.SSHInterface),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
},
|
||||
@@ -178,13 +190,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
OMIName: b.config.OMIName,
|
||||
Regions: b.config.OMIRegions,
|
||||
},
|
||||
&stepCreateOMI{
|
||||
RawRegion: b.config.RawRegion,
|
||||
},
|
||||
&stepCreateOMI{},
|
||||
&osccommon.StepUpdateOMIAttributes{
|
||||
AccountIds: b.config.OMIAccountIDs,
|
||||
SnapshotAccountIds: b.config.SnapshotAccountIDs,
|
||||
RawRegion: b.config.RawRegion,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
&osccommon.StepCreateTags{
|
||||
@@ -208,6 +217,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
artifact := &osccommon.Artifact{
|
||||
Omis: omis.(map[string]string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Config: clientConfig,
|
||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user