Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| deb133452d | |||
| b6b86f54ef | |||
| 112a67365d |
+40
-109
@@ -1,131 +1,68 @@
|
||||
orbs:
|
||||
win: circleci/windows@1.0.0
|
||||
version: 2.1
|
||||
|
||||
executors:
|
||||
golang:
|
||||
docker:
|
||||
- image: circleci/golang:1.13
|
||||
darwin:
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
|
||||
commands:
|
||||
install-go-run-tests-unix:
|
||||
parameters:
|
||||
GOOS:
|
||||
type: string
|
||||
GOVERSION:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- run: curl https://dl.google.com/go/go<< parameters.GOVERSION >>.<< parameters.GOOS >>-amd64.tar.gz | tar -C ~/ -xz
|
||||
- run: ~/go/bin/go test ./...
|
||||
install-go-run-tests-windows:
|
||||
parameters:
|
||||
GOVERSION:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- run: curl https://dl.google.com/go/go<< parameters.GOVERSION >>.windows-amd64.zip --output ~/go<< parameters.GOVERSION >>.windows-amd64.zip
|
||||
- run: unzip ~/go<< parameters.GOVERSION >>.windows-amd64.zip -d ~/
|
||||
- run: ~/go/bin/go test ./...
|
||||
build-and-persist-packer-binary:
|
||||
parameters:
|
||||
GOOS:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- run: GOOS=<< parameters.GOOS >> go build -ldflags="-s -w" -o ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH) .
|
||||
- run: zip ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH).zip ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH)
|
||||
- run: rm ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH)
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- ./pkg/
|
||||
defaults: &golang
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- checkout
|
||||
- run: go build -ldflags="-s -w" -o ./pkg/packer_$(go env GOOS)_$(go env GOARCH) .
|
||||
- run: zip ./pkg/packer_$(go env GOOS)_$(go env GOARCH).zip ./pkg/packer_$(go env GOOS)_$(go env GOARCH)
|
||||
- run: rm ./pkg/packer_$(go env GOOS)_$(go env GOARCH)
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- ./pkg/
|
||||
# Golang CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
||||
version: 2
|
||||
jobs:
|
||||
test-linux:
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
build:
|
||||
<<: *golang
|
||||
steps:
|
||||
- checkout
|
||||
- run: make ci
|
||||
test-darwin:
|
||||
executor: darwin
|
||||
working_directory: ~/go/src/github.com/hashicorp/packer
|
||||
environment:
|
||||
GO111MODULE: "off"
|
||||
steps:
|
||||
- install-go-run-tests-unix:
|
||||
GOOS: darwin
|
||||
GOVERSION: "1.13"
|
||||
test-windows:
|
||||
executor:
|
||||
name: win/vs2019
|
||||
shell: bash.exe
|
||||
steps:
|
||||
- install-go-run-tests-windows:
|
||||
GOVERSION: "1.13"
|
||||
check-vendor-vs-mod:
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
environment:
|
||||
GO111MODULE: "off"
|
||||
<<: *golang
|
||||
steps:
|
||||
- checkout
|
||||
- run: GO111MODULE=on go run . --help
|
||||
- run: make check-vendor-vs-mod
|
||||
check-fmt:
|
||||
executor: golang
|
||||
<<: *golang
|
||||
steps:
|
||||
- checkout
|
||||
- run: make fmt-check
|
||||
check-generate:
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
<<: *golang
|
||||
steps:
|
||||
- checkout
|
||||
- run: make generate-check
|
||||
build_linux:
|
||||
executor: golang
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: linux
|
||||
<<: *golang
|
||||
environment:
|
||||
GOOS: linux
|
||||
build_windows:
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: windows
|
||||
<<: *golang
|
||||
environment:
|
||||
GOOS: windows
|
||||
build_darwin:
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: darwin
|
||||
<<: *golang
|
||||
environment:
|
||||
GOOS: darwin
|
||||
build_freebsd:
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: freebsd
|
||||
<<: *golang
|
||||
environment:
|
||||
GOOS: freebsd
|
||||
build_solaris:
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: solaris
|
||||
<<: *golang
|
||||
environment:
|
||||
GOOS: solaris
|
||||
build_openbsd:
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: openbsd
|
||||
<<: *golang
|
||||
environment:
|
||||
GOOS: openbsd
|
||||
store_artifacts:
|
||||
executor: golang
|
||||
<<: *golang
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
@@ -143,18 +80,12 @@ jobs:
|
||||
ghr -prerelease -t ${GITHUB_TOKEN_AZR} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${CIRCLE_TAG} ./pkg/
|
||||
workflows:
|
||||
version: 2
|
||||
test:
|
||||
jobs:
|
||||
- test-linux
|
||||
- test-darwin
|
||||
- test-windows
|
||||
check-code:
|
||||
build_and_check_vendor_vs_module:
|
||||
jobs:
|
||||
- build
|
||||
- check-vendor-vs-mod
|
||||
- check-fmt
|
||||
- check-generate
|
||||
build_packer_binaries:
|
||||
jobs:
|
||||
- build_linux:
|
||||
filters:
|
||||
tags:
|
||||
|
||||
@@ -246,19 +246,6 @@ start](https://github.com/golang/go/wiki/Modules#quick-start) for examples.
|
||||
Please only apply the minimal vendor changes to get your PR to work. Packer
|
||||
does not attempt to track the latest version for each dependency.
|
||||
|
||||
#### Code generation
|
||||
|
||||
Packer relies on `go generate` to generate a [peg parser for boot
|
||||
commands](https://github.com/hashicorp/packer/blob/master/common/bootcommand/boot_command.go),
|
||||
[docs](https://github.com/hashicorp/packer/blob/master/website/source/partials/builder/amazon/chroot/_Config-not-required.html.md)
|
||||
and HCL2's bridging code. Packer's testing suite will run `make check-generate`
|
||||
to check that all the generated files Packer needs are what they should be.
|
||||
`make generate` re-generates all these file and can take a while depending on
|
||||
your machine's performances. To make it faster it is recommended to run
|
||||
localized code generation. Say you are working on the Amazon builder: running
|
||||
`go generate ./builder/amazon/...` will do that for you. Make sure that the
|
||||
latest code generation tool is installed by running `make install-gen-deps`.
|
||||
|
||||
#### Running Unit Tests
|
||||
|
||||
You can run tests for individual packages using commands like this:
|
||||
@@ -305,12 +292,3 @@ make testacc TEST=./builder/amazon/ebs TESTARGS="-run TestBuilderAcc_forceDelete
|
||||
Acceptance tests typically require other environment variables to be set for
|
||||
things such as API tokens and keys. Each test should error and tell you which
|
||||
credentials are missing, so those are not documented here.
|
||||
|
||||
#### Debugging Plugins
|
||||
|
||||
Each packer plugin runs in a separate process and communicates with RCP over a
|
||||
socket therefore using a debugger will not work (be complicated at least).
|
||||
|
||||
But most of the Packer code is really simple and easy to follow with PACKER_LOG
|
||||
turned on. If that doesn't work adding some extra debug print outs when you have
|
||||
homed in on the problem is usually enough.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
_Please read these instructions before submitting_
|
||||
|
||||
**DELETE THIS TEMPLATE BEFORE SUBMITTING**
|
||||
|
||||
_Only use GitHub issues to report bugs or feature requests, see
|
||||
https://www.packer.io/community.html_
|
||||
|
||||
For example, _Timeouts waiting for SSH/WinRM_ are generally not bugs within packer and are better addressed by the mailing list. Ask on the mailing list if you are unsure.
|
||||
|
||||
If you are planning to open a pull-request just open the pull-request instead of making an issue first.
|
||||
|
||||
FOR FEATURES:
|
||||
|
||||
Describe the feature you want and your use case _clearly_.
|
||||
|
||||
FOR BUGS:
|
||||
|
||||
Describe the problem and include the following information:
|
||||
|
||||
- Packer version from `packer version`
|
||||
- Host platform
|
||||
- Debug log output from `PACKER_LOG=1 packer build template.json`.
|
||||
Please paste this in a gist https://gist.github.com
|
||||
- The _simplest example template and scripts_ needed to reproduce the bug.
|
||||
Include these in your gist https://gist.github.com
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: You're experiencing an issue with Packer that is different than the documented behavior.
|
||||
labels: bug
|
||||
---
|
||||
|
||||
When filing a bug, please include the following headings if possible. Any
|
||||
example text in this template can be deleted.
|
||||
|
||||
#### Overview of the Issue
|
||||
|
||||
A paragraph or two about the issue you're experiencing.
|
||||
|
||||
#### Reproduction Steps
|
||||
|
||||
Steps to reproduce this issue
|
||||
|
||||
### Packer version
|
||||
|
||||
From `packer version`
|
||||
|
||||
### Simplified Packer Buildfile
|
||||
|
||||
If the file is longer than a few dozen lines, please include the URL to the
|
||||
[gist](https://gist.github.com/) of the log or use the [Github detailed
|
||||
format](https://gist.github.com/ericclemmons/b146fe5da72ca1f706b2ef72a20ac39d)
|
||||
instead of posting it directly in the issue.
|
||||
|
||||
### Operating system and Environment details
|
||||
|
||||
OS, Architecture, and any other information you can provide about the
|
||||
environment.
|
||||
|
||||
### Log Fragments and crash.log files
|
||||
|
||||
Include appropriate log fragments. If the log is longer than a few dozen lines,
|
||||
please include the URL to the [gist](https://gist.github.com/) of the log or
|
||||
use the [Github detailed format](https://gist.github.com/ericclemmons/b146fe5da72ca1f706b2ef72a20ac39d) instead of posting it directly in the issue.
|
||||
|
||||
Set the env var `PACKER_LOG=1` for maximum log detail.
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: If you have something you think Packer could improve or add support for.
|
||||
labels: enhancement
|
||||
---
|
||||
|
||||
Please search the existing issues for relevant feature requests, and use the
|
||||
reaction feature
|
||||
(https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/)
|
||||
to add upvotes to pre-existing requests.
|
||||
|
||||
#### Feature Description
|
||||
|
||||
A written overview of the feature.
|
||||
|
||||
#### Use Case(s)
|
||||
|
||||
Any relevant use-cases that you see.
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: If you have a question, please check out our other community resources instead of opening an issue.
|
||||
labels: question
|
||||
---
|
||||
|
||||
Issues on GitHub are intended to be related to bugs or feature requests, so we
|
||||
recommend using our other community resources instead of asking here if you
|
||||
have a question.
|
||||
|
||||
- Packer Guides: https://www.packer.io/guides/index.html
|
||||
- Discussion List: https://groups.google.com/group/packer-tool
|
||||
- Any other questions can be sent to the packer section of the HashiCorp
|
||||
forum: https://discuss.hashicorp.com/c/packer
|
||||
- Packer community links: https://www.packer.io/community.html
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: SSH or WinRM times out
|
||||
about: I have a waiting SSH or WinRM error.
|
||||
labels: communicator-question
|
||||
---
|
||||
|
||||
Got one of the following errors ? See if the related guides can help.
|
||||
|
||||
* `Waiting for WinRM to become available` ?
|
||||
|
||||
- See our basic WinRm Packer guide: https://www.packer.io/guides/automatic-operating-system-installs/autounattend_windows.html
|
||||
|
||||
* `Waiting for SSH to become available` ?
|
||||
|
||||
- See our basic SSH Packer guide: https://www.packer.io/guides/automatic-operating-system-installs/preseed_ubuntu.html
|
||||
|
||||
|
||||
Issues on GitHub are intended to be related to bugs or feature requests, so we recommend using our other community resources instead of asking here if you have a question.
|
||||
|
||||
- Packer Guides: https://www.packer.io/guides/index.html
|
||||
- Discussion List: https://groups.google.com/group/packer-tool
|
||||
- Any other questions can be sent to the packer section of the HashiCorp
|
||||
forum: https://discuss.hashicorp.com/c/packer
|
||||
- Packer community links: https://www.packer.io/community.html
|
||||
@@ -26,4 +26,3 @@ packer-test*.log
|
||||
Thumbs.db
|
||||
/packer.exe
|
||||
.project
|
||||
cache
|
||||
|
||||
-239
@@ -1,242 +1,3 @@
|
||||
## 1.5.0 (Upcoming)
|
||||
## 1.4.5 (November 4, 2019)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
* added ucloud-import post-processsor to import custom image for UCloud UHost
|
||||
instance [GH-8261]
|
||||
* builder/amazon: New option to specify IAM policy for a temporary instance
|
||||
profile [GH-8247]
|
||||
* builder/amazon: improved validation around encrypt_boot and kms_key_id for a
|
||||
better experience [GH-8288]
|
||||
* builder/azure-arm: Allow specification of polling duration [GH-8226]
|
||||
* builder/azure-chroot: Add Azure chroot builder [GH-8185] & refactored some
|
||||
common code together after it [GH-8269]
|
||||
* builder/azure: Deploy NSG if list of IP addresses is provided in config
|
||||
[GH-8203]
|
||||
* builder/azure: Set correct user agent for Azure client set [GH-8259]
|
||||
* builder/cloudstack: Add instance_display_name for cloudstack builder
|
||||
[GH-8280]
|
||||
* builder/hyperv: Add the additional_disk_size option tho the hyperv vmcx
|
||||
builder. [GH-8246]
|
||||
* builder/openstack: Add option to discover provisioning network [GH-8279]
|
||||
* builder/oracle-oci: Support defined tags for oci builder [GH-8172]
|
||||
* builder/proxmox: Add ability to select CPU type [GH-8201]
|
||||
* builder/proxmox: Add support for SCSI controller selection [GH-8199]
|
||||
* builder/proxmoz: Bump Proxmox dependency: [GH-8241]
|
||||
* builder/tencent: Add retry on remote api call [GH-8250]
|
||||
* builder/vagrant: Pass through logs from vagrant in real time rather than
|
||||
buffering until command is complete [GH-8274]
|
||||
* builder/vagrant: add insert_key option for toggling whether to add Vagrant's
|
||||
insecure key [GH-8274]
|
||||
* builder/virtualbox: enabled pcie disks usage, but this feature is in beta and
|
||||
won't work out of the box yet [GH-8305]
|
||||
* communicator/winrm: Prevent busy loop while waiting for WinRM connection
|
||||
[GH-8213]
|
||||
* core: Add strftime function in templates [GH-8208]
|
||||
* core: Improve error message when comment is bad [GH-8267]
|
||||
* post-processor/amazon-import: delete intermediary snapshots [GH-8307]
|
||||
* Fix various dropped errors an removed unused code: [GH-8230] [GH-8265]
|
||||
[GH-8276] [GH-8281] [GH-8309] [GH-8311] [GH-8304] [GH-8303] [GH-8293]
|
||||
|
||||
### BUG FIXES:
|
||||
* builder/amazon: Fix region copy for non-ebs amazon builders [GH-8212]
|
||||
* builder/amazon: Fix spot instance bug where builder would fail if one
|
||||
availability zone could not support the requested spot instance type, even
|
||||
if another AZ could do so. [GH-8184]
|
||||
* builder/azure: Fix build failure after a retry config generation error.
|
||||
[GH-8209]
|
||||
* builder/docker: Use a unique temp dir for each build to prevent concurrent
|
||||
builds from stomping on each other [GH-8192]
|
||||
* builder/hyperv: Improve filter for determining which files to compact
|
||||
[GH-8248]
|
||||
* builder/hyperv: Use first adapter, rather than failing, when multiple
|
||||
adapters are attached to host OS's VM switch [GH-8234]
|
||||
* builder/openstack: Fix setting openstack metadata for use_blockstorage_volume
|
||||
[GH-8186]
|
||||
* builder/openstack: Warn instead of failing on terminate if instance is
|
||||
already shut down [GH-8176]
|
||||
* post-processor/digitalocean-import: Fix panic when 'image_regions' not set
|
||||
[GH-8179]
|
||||
* provisioner/powershell: Fix powershell syntax error causing failed builds
|
||||
[GH-8195]
|
||||
|
||||
## 1.4.4 (October 1, 2019)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
** new core feature** Error cleanup provisioner [GH-8155]
|
||||
* builder/amazon: Add ability to set `run_volume_tags` [GH-8051]
|
||||
* builder/amazon: Add AWS API call reties on AMI prevalidation [GH-8034]
|
||||
* builder/azure: Refactor client config [GH-8121]
|
||||
* builder/cloudstack: New step to detach iso. [GH-8106]
|
||||
* builder/googlecompute: Fail fast when image name is invalid. [GH-8112]
|
||||
* builder/googlecompute: Users can now query Vault for an Oauth token rather
|
||||
than setting an account file [GH-8143]
|
||||
* builder/hcloud: Allow selecting image based on filters [GH-7945]
|
||||
* builder/hyper-v: Decrease the delay between Hyper-V VM startup and hyper-v
|
||||
builder's ability to send keystrokes to the target VM. [GH-7970]
|
||||
* builder/openstack: Store WinRM password for provisioners to use [GH-7940]
|
||||
* builder/proxmox: Shorten default boot_key_interval to 5ms from 100ms
|
||||
[GH-8088]
|
||||
* builder/proxmox: Allow running the template VM in a Proxmox resource pool
|
||||
[GH-7862]
|
||||
* builder/ucloud: Make ucloud builder's base url configurable [GH-8095]
|
||||
* builder/virtualbox-vm: Make target snapshot optional [GH-8011] [GH-8004]
|
||||
* builder/vmware: Allow user to attach floppy files to remote vmx builds
|
||||
[GH-8132]
|
||||
* builder/yandex: Add ability to retry API requests [GH-8142]
|
||||
* builder/yandex: Support GPU instances and set source image by name [GH-8091]
|
||||
* communicator/ssh: Support for SSH port tunneling [GH-7918]
|
||||
* core: Add a new `floppy_label` option [GH-8099]
|
||||
* core: Added version compatibility to console command [GH-8080]
|
||||
* post-processor/vagrant-cloud: Allow blank access_token for private vagrant
|
||||
box hosting [GH-8097]
|
||||
* post-processor/vagrant-cloud: Allow use of the Artifice post-processor with
|
||||
the Vagrant Cloud post-processor [GH-8018] [GH-8027]
|
||||
* post-processor/vsphere: Removed redundant whitelist check for builders,
|
||||
allowing users to use post-processor withough the VMWare builder [GH-8064]
|
||||
|
||||
### BUG FIXES:
|
||||
* builder/amazon: Fix FleetID crash. [GH-8013]
|
||||
* builder/amazon: Gracefully handle rate limiting when retrieving winrm
|
||||
password. [GH-8087]
|
||||
* builder/amazon: Fix race condition in spot instance launching [GH-8165]
|
||||
* builder/amazon: Amazon builders now respect ssh_host option [GH-8162]
|
||||
* builder/amazon: Update the AWS sdk to resolve some credential handling issues
|
||||
[GH-8131]
|
||||
* builder/azure: Avoid a panic in getObjectIdFromToken [GH-8047]
|
||||
* builder/googlecompute: Fix crash caused by nil account file. [GH-8102]
|
||||
* builder/hyper-v: Fix when management interface is not part of virtual switch
|
||||
[GH-8017]
|
||||
* builder/openstack: Fix dropped error when creating image client. [GH-8110]
|
||||
* builder/openstack: Fix race condition created when adding metadata [GH-8016]
|
||||
* builder/outscale: Get SSH Host from VM.Nics instead of VM Root [GH-8077]
|
||||
* builder/proxmox: Bump proxmox api dep, fixing bug with checking http status
|
||||
during boot command [GH-8083]
|
||||
* builder/proxmox: Check that disk format is set when pool type requires it
|
||||
[GH-8084]
|
||||
* builder/proxmox: Fix panic caused by cancelling build [GH-8067] [GH-8072]
|
||||
* builder/qemu: Fix dropped error when retrieving version [GH-8050]
|
||||
* builder/vagrant: Fix dropped errors in code and tests. [GH-8118]
|
||||
* builder/vagrant: Fix provisioning boxes, define source and output boxes
|
||||
[GH-7957]
|
||||
* builder/vagrant: Fix ssh and package steps to use source syntax. [GH-8125]
|
||||
* builder/vagrant: Use GlobalID when provided [GH-8092]
|
||||
* builder/virtualbox: Fix windows pathing problem for guest additions checksum
|
||||
download. [GH-7996]
|
||||
* builder/virtualbox: LoadSnapshots succeeds even if machine has no snapshots
|
||||
[GH-8096]
|
||||
* builder/vmware: fix dropped test errors [GH-8170]
|
||||
* core: Fix bug where sensitive variables contianing commas were not being
|
||||
properly sanitized in UI calls. [GH-7997]
|
||||
* core: Fix handling of booleans where "unset" is a value distinct from
|
||||
"false". [GH-8021]
|
||||
* core: Fix tests that swallowed errors in goroutines [GH-8094]
|
||||
* core: Fix bug where Packer could no longer run as background process [GH-8101]
|
||||
* core: Fix zsh auto-completion [GH-8160]
|
||||
* communicator/ssh: Friendlier message warning user that their creds may be
|
||||
wrong [GH-8167]
|
||||
* post-processor/amazon-import: Fix non-default encryption. [GH-8113]
|
||||
* post-processor/vagrant-cloud: Fix dropped errors [GH-8156]
|
||||
* provisioner/ansible: Fix provisioner dropped errors [GH-8045]
|
||||
|
||||
### BACKWARDS INCOMPATIBILITIES:
|
||||
* core: "sed" template function has been deprecated in favor of "replace" and
|
||||
"replace_all" functins [GH-8119]
|
||||
|
||||
## 1.4.3 (August 14, 2019)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
* **new builder** UCloud builder [GH-7775]
|
||||
* **new builder** Outscale [GH-7459]
|
||||
* **new builder** VirtualBox Snapshot [GH-7780]
|
||||
* **new builder** JDCloud [GH-7962]
|
||||
* **new post-processor** Exoscale Import post-processor [GH-7822] [GH-7946]
|
||||
* build: Change Makefile to behave differently inside and outside the gopath
|
||||
when generating code. [GH-7827]
|
||||
* builder/amazon: Don't calculate spot bids; Amazon has changed spot pricing to
|
||||
no longer require this. [GH-7813]
|
||||
* builder/google: Add suse-byos-cloud to list of public GCP cloud image
|
||||
projects [GH-7935]
|
||||
* builder/openstack: New `image_min_disk` option [GH-7290]
|
||||
* builder/openstack: New option `use_blockstorage_volume` to set openstack
|
||||
image metadata [GH-7792]
|
||||
* builder/openstack: Select instance network on which to assign floating ip
|
||||
[GH-7884]
|
||||
* builder/qemu: Implement VNC password functionality [GH-7836]
|
||||
* builder/scaleway: Allow removing volume after image creation for Scaleway
|
||||
builder [GH-7887]
|
||||
* builder/tencent: Add `run_tags` to option to tag instance. [GH-7810]
|
||||
* builder/tencent: Remove unnecessary image name validation check. [GH-7786]
|
||||
* builder/tencent: Support data disks for tencentcloud builder [GH-7815]
|
||||
* builder/vmware: Fix intense CPU usage because of poorly handled errors.
|
||||
[GH-7877]
|
||||
* communicator: Use context for timeouts, interruption in ssh and winrm
|
||||
communicators [GH-7868]
|
||||
* core: Change how on-error=abort is handled to prevent EOF errors that mask
|
||||
real issues [GH-7913]
|
||||
* core: Clean up logging vs ui call in step download [GH-7936]
|
||||
* core: New environment var option to allow user to set location of config
|
||||
directory [GH-7912]
|
||||
* core: Remove obsolete Cancel functions from builtin provisioners [GH-7917]
|
||||
* post-processor/vagrant: Add option to allow box Vagrantfiles to be generated
|
||||
during the build [GH-7951]
|
||||
* provisioner/ansible: Add support for installing roles with ansible-galaxy
|
||||
[GH-7916
|
||||
* provisioner/salt-masterless: Modify file upload to handle non-root case.
|
||||
[GH-7833]
|
||||
|
||||
### BUG FIXES:
|
||||
* builder/amazon: Add error to warn users of spot_tags regression. [GH-7989]
|
||||
* builder/amazon: Allow EC2 Spot Fleet packer instances to run in parallel
|
||||
[GH-7818]
|
||||
* builder/amazon: Fix failures and duplication in Amazon region copy and
|
||||
encryption step. [GH-7870] [GH-7923]
|
||||
* builder/amazon: No longer store names of volumes which get deleted on
|
||||
termination inside ebssurrogate artifact. [GH-7829]
|
||||
* builder/amazon: Update aws-sdk-go to v1.22.2, resolving some AssumeRole
|
||||
issues [GH-7967]
|
||||
* builder/azure: Create configurable polling duration and set higher default
|
||||
for image copies to prevent timeouts on successful copies [GH-7920]
|
||||
* builder/digitalocean: increase timeout for Digital Ocean snapshot creation.
|
||||
[GH-7841]
|
||||
* builder/docker: Check container os, not host os, when creating container dir
|
||||
default [GH-7939]
|
||||
* builder/docker: Fix bug where PACKER_TMP_DIR was created with root perms on
|
||||
linux [GH-7905]
|
||||
* builder/docker: Fix file download hang caused by blocking ReadAll call
|
||||
[GH-7814]
|
||||
* builder/google: Fix outdated oauth URL. [GH-7835] [GH-7927]
|
||||
* builder/hyperv: Improve code for detecting IP address [GH-7880]
|
||||
* builder/ucloud: Update the api about stop instance to fix the read-only image
|
||||
build by ucloud-uhost [GH-7914]
|
||||
* builder/vagrant: Fix bug where source_path was being used instead of box_name
|
||||
when generating the Vagrantfile. [GH-7859]
|
||||
* builder/virtualbox: Honor value of 'Comment' field in ssh keypair generation.
|
||||
[GH-7922]
|
||||
* builder/vmware: Fix validation regression that occurred when user provided a
|
||||
checksum file [GH-7804]
|
||||
* buildere/azure: Fix crash with managed images not published to shared image
|
||||
gallery. [GH-7837]
|
||||
* communicator/ssh: Move ssh_interface back into individual builders from ssh
|
||||
communicator to prevent validation issues where it isn't implemented.
|
||||
[GH-7831]
|
||||
* console: Fix console help text [GH-7960]
|
||||
* core: Fix bug in template parsing where function errors were getting
|
||||
swallowed. [GH-7854]
|
||||
* core: Fix regression where a local filepath containing `//` was no longer
|
||||
properly resolving to `/`. [GH-7888]
|
||||
* core: Fix regression where we could no longer access isos on SMB shares.
|
||||
[GH-7800]
|
||||
* core: Make ssh_host template option always override all builders' IP
|
||||
discovery. [GH-7832]
|
||||
* core: Regenerate boot_command PEG code [GH-7977]
|
||||
* fix: clean up help text and fixer order to make sure all fixers are called
|
||||
[GH-7903]
|
||||
* provisioner/inspec: Use --input-file instead of --attrs to avoid deprecation
|
||||
warning [GH-7893]
|
||||
* provisioner/salt-masterless: Make salt-masterless provisioner respect
|
||||
disable_sudo directive for all commands [GH-7774]
|
||||
|
||||
## 1.4.2 (June 26, 2019)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
+13
-56
@@ -3,62 +3,20 @@
|
||||
# builders
|
||||
|
||||
/builder/alicloud/ @chhaj5236
|
||||
/website/source/docs/builders/alicloud* @chhaj5236
|
||||
|
||||
/builder/azure/ @paulmey
|
||||
/website/source/docs/builders/azure* @paulmey
|
||||
|
||||
/builder/hyperv/ @taliesins
|
||||
/website/source/docs/builders/hyperv* @taliesins
|
||||
|
||||
/builder/jdcloud/ @XiaohanLiang @remrain
|
||||
/website/source/docs/builders/jdcloud* @XiaohanLiang @remrain
|
||||
|
||||
/builder/linode/ @displague @ctreatma @stvnjacobs
|
||||
/website/source/docs/builders/linode* @displague @ctreatma @stvnjacobs
|
||||
|
||||
/builder/lxc/ @ChrisLundquist
|
||||
/website/source/docs/builders/lxc* @ChrisLundquist
|
||||
|
||||
/builder/lxd/ @ChrisLundquist
|
||||
/website/source/docs/builders/lxd* @ChrisLundquist
|
||||
|
||||
/builder/oneandone/ @jasmingacic
|
||||
/website/source/docs/builders/oneandone* @jasmingacic
|
||||
|
||||
/builder/oracle/ @prydie @owainlewis
|
||||
/website/source/docs/builders/oracle* @prydie @owainlewis
|
||||
|
||||
/builder/profitbricks/ @jasmingacic
|
||||
/website/source/docs/builders/profitbricks* @jasmingacic
|
||||
|
||||
/builder/triton/ @sean-
|
||||
/website/source/docs/builders/triton* @sean-
|
||||
|
||||
/builder/ncloud/ @YuSungDuk
|
||||
/website/source/docs/builders/ncloud* @YuSungDuk
|
||||
|
||||
/builder/proxmox/ @carlpett
|
||||
/website/source/docs/builders/proxmox* @carlpett
|
||||
|
||||
/builder/azure/ @paulmey
|
||||
/builder/hyperv/ @taliesins
|
||||
/builder/linode/ @displague @ctreatma @stvnjacobs
|
||||
/builder/lxc/ @ChrisLundquist
|
||||
/builder/lxd/ @ChrisLundquist
|
||||
/builder/oneandone/ @jasmingacic
|
||||
/builder/oracle/ @prydie @owainlewis
|
||||
/builder/profitbricks/ @jasmingacic
|
||||
/builder/triton/ @sean-
|
||||
/builder/ncloud/ @YuSungDuk
|
||||
/builder/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
|
||||
/website/source/docs/builders/scaleway* @sieben @mvaude @jqueuniet @fflorens @brmzkw
|
||||
|
||||
/builder/hcloud/ @LKaemmerling
|
||||
/website/source/docs/builders/hcloud* @LKaemmerling
|
||||
|
||||
/builder/hyperone/ @m110 @gregorybrzeski @ad-m
|
||||
/website/source/docs/builders/hyperone* @m110 @gregorybrzeski @ad-m
|
||||
|
||||
/builder/ucloud/ @shawnmssu
|
||||
/website/source/docs/builders/ucloud* @shawnmssu
|
||||
|
||||
/builder/yandex/ @GennadySpb @alexanderKhaustov @seukyaso
|
||||
/website/source/docs/builders/yandex* @GennadySpb @alexanderKhaustov @seukyaso
|
||||
|
||||
/builder/osc/ @marinsalinas
|
||||
/website/source/docs/builders/osc* @marinsalinas
|
||||
|
||||
/builder/hcloud/ @LKaemmerling
|
||||
/builder/hyperone @m110 @gregorybrzeski @ad-m
|
||||
/builder/yandex @GennadySpb @alexanderKhaustov @seukyaso
|
||||
|
||||
# provisioners
|
||||
|
||||
@@ -69,6 +27,5 @@
|
||||
|
||||
/post-processor/alicloud-import/ dongxiao.zzh@alibaba-inc.com
|
||||
/post-processor/checksum/ v.tolstov@selfip.ru
|
||||
/post-processor/exoscale-import/ @falzm @mcorbin
|
||||
/post-processor/googlecompute-export/ crunkleton@google.com
|
||||
/post-processor/vsphere-template/ nelson@bennu.cl
|
||||
|
||||
@@ -45,15 +45,9 @@ install-build-deps: ## Install dependencies for bin build
|
||||
@go get github.com/mitchellh/gox
|
||||
|
||||
install-gen-deps: ## Install dependencies for code generation
|
||||
# to avoid having to tidy our go deps, we `go get` our binaries from a temp
|
||||
# dir. `go get` will change our deps and the following deps are not part of
|
||||
# out code dependencies; so a go mod tidy will remove them again. `go
|
||||
# install` seems to install the last tagged version and we want to install
|
||||
# master.
|
||||
@(cd $(TEMPDIR) && GO111MODULE=on go get github.com/mna/pigeon@master)
|
||||
@(cd $(TEMPDIR) && GO111MODULE=on go get github.com/alvaroloes/enumer@master)
|
||||
@go install ./cmd/struct-markdown
|
||||
@go install ./cmd/mapstructure-to-hcl2
|
||||
@go get golang.org/x/tools/cmd/goimports
|
||||
@go get -u github.com/mna/pigeon
|
||||
@go get github.com/alvaroloes/enumer
|
||||
|
||||
dev: ## Build and install a development build
|
||||
@grep 'const VersionPrerelease = ""' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \
|
||||
@@ -96,12 +90,9 @@ fmt-examples:
|
||||
# generate runs `go generate` to build the dynamically generated
|
||||
# source files.
|
||||
generate: install-gen-deps ## Generate dynamically generated code
|
||||
@echo "==> removing autogenerated markdown..."
|
||||
@find website/source/ -type f | xargs grep -l '^<!-- Code generated' | xargs rm
|
||||
@echo "==> removing autogenerated code..."
|
||||
@find post-processor common helper template builder provisioner -type f | xargs grep -l '^// Code generated' | xargs rm
|
||||
go generate ./...
|
||||
go fmt common/bootcommand/boot_command.go
|
||||
goimports -w common/bootcommand/boot_command.go
|
||||
go fmt command/plugin.go
|
||||
|
||||
generate-check: generate ## Check go code generation is on par
|
||||
|
||||
@@ -20,6 +20,8 @@ install:
|
||||
- echo %Path%
|
||||
- go version
|
||||
- go env
|
||||
- go get github.com/mitchellh/gox
|
||||
- go get golang.org/x/tools/cmd/stringer
|
||||
|
||||
build_script:
|
||||
- git rev-parse HEAD
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// +build !openbsd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/shirou/gopsutil/process"
|
||||
)
|
||||
|
||||
func checkProcess(currentPID int) (bool, error) {
|
||||
myProc, err := process.NewProcess(int32(currentPID))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Process check error: %s", err)
|
||||
}
|
||||
bg, err := myProc.Background()
|
||||
if err != nil {
|
||||
return bg, fmt.Errorf("Process background check error: %s", err)
|
||||
}
|
||||
|
||||
return bg, nil
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func checkProcess(currentPID int) (bool, error) {
|
||||
return false, fmt.Errorf("cannot determine if process is backgrounded in " +
|
||||
"openbsd")
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:generate struct-markdown
|
||||
|
||||
package ecs
|
||||
|
||||
import (
|
||||
@@ -14,21 +12,11 @@ import (
|
||||
|
||||
// Config of alicloud
|
||||
type AlicloudAccessConfig struct {
|
||||
// This is the Alicloud access key. It must be provided, but it can also be
|
||||
// sourced from the ALICLOUD_ACCESS_KEY environment variable.
|
||||
AlicloudAccessKey string `mapstructure:"access_key" required:"true"`
|
||||
// This is the Alicloud secret key. It must be provided, but it can also be
|
||||
// sourced from the ALICLOUD_SECRET_KEY environment variable.
|
||||
AlicloudSecretKey string `mapstructure:"secret_key" required:"true"`
|
||||
// This is the Alicloud region. It must be provided, but it can also be
|
||||
// sourced from the ALICLOUD_REGION environment variables.
|
||||
AlicloudRegion string `mapstructure:"region" required:"true"`
|
||||
// The region validation can be skipped if this value is true, the default
|
||||
// value is false.
|
||||
AlicloudSkipValidation bool `mapstructure:"skip_region_validation" required:"false"`
|
||||
// STS access token, can be set through template or by exporting as
|
||||
// environment variable such as `export SECURITY_TOKEN=value`.
|
||||
SecurityToken string `mapstructure:"security_token" required:"false"`
|
||||
AlicloudAccessKey string `mapstructure:"access_key"`
|
||||
AlicloudSecretKey string `mapstructure:"secret_key"`
|
||||
AlicloudRegion string `mapstructure:"region"`
|
||||
AlicloudSkipValidation bool `mapstructure:"skip_region_validation"`
|
||||
SecurityToken string `mapstructure:"security_token"`
|
||||
|
||||
client *ClientWrapper
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:generate mapstructure-to-hcl2 -type Config,AlicloudDiskDevice
|
||||
|
||||
// The alicloud contains a packer.Builder implementation that
|
||||
// builds ecs images for alicloud.
|
||||
package ecs
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,AlicloudDiskDevice"; DO NOT EDIT.
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatAlicloudDiskDevice is an auto-generated flat version of AlicloudDiskDevice.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatAlicloudDiskDevice struct {
|
||||
DiskName *string `mapstructure:"disk_name" required:"false" cty:"disk_name"`
|
||||
DiskCategory *string `mapstructure:"disk_category" required:"false" cty:"disk_category"`
|
||||
DiskSize *int `mapstructure:"disk_size" required:"false" cty:"disk_size"`
|
||||
SnapshotId *string `mapstructure:"disk_snapshot_id" required:"false" cty:"disk_snapshot_id"`
|
||||
Description *string `mapstructure:"disk_description" required:"false" cty:"disk_description"`
|
||||
DeleteWithInstance *bool `mapstructure:"disk_delete_with_instance" required:"false" cty:"disk_delete_with_instance"`
|
||||
Device *string `mapstructure:"disk_device" required:"false" cty:"disk_device"`
|
||||
Encrypted *bool `mapstructure:"disk_encrypted" required:"false" cty:"disk_encrypted"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatAlicloudDiskDevice.
|
||||
// FlatAlicloudDiskDevice is an auto-generated flat version of AlicloudDiskDevice.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*AlicloudDiskDevice) FlatMapstructure() interface{} { return new(FlatAlicloudDiskDevice) }
|
||||
|
||||
// HCL2Spec returns the hcldec.Spec of a FlatAlicloudDiskDevice.
|
||||
// This spec is used by HCL to read the fields of FlatAlicloudDiskDevice.
|
||||
func (*FlatAlicloudDiskDevice) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"disk_name": &hcldec.AttrSpec{Name: "disk_name", Type: cty.String, Required: false},
|
||||
"disk_category": &hcldec.AttrSpec{Name: "disk_category", Type: cty.String, Required: false},
|
||||
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
|
||||
"disk_snapshot_id": &hcldec.AttrSpec{Name: "disk_snapshot_id", Type: cty.String, Required: false},
|
||||
"disk_description": &hcldec.AttrSpec{Name: "disk_description", Type: cty.String, Required: false},
|
||||
"disk_delete_with_instance": &hcldec.AttrSpec{Name: "disk_delete_with_instance", Type: cty.Bool, Required: false},
|
||||
"disk_device": &hcldec.AttrSpec{Name: "disk_device", Type: cty.String, Required: false},
|
||||
"disk_encrypted": &hcldec.AttrSpec{Name: "disk_encrypted", Type: cty.Bool, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatConfig struct {
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"`
|
||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
|
||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
|
||||
AlicloudAccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key"`
|
||||
AlicloudSecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key"`
|
||||
AlicloudRegion *string `mapstructure:"region" required:"true" cty:"region"`
|
||||
AlicloudSkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"`
|
||||
SecurityToken *string `mapstructure:"security_token" required:"false" cty:"security_token"`
|
||||
AlicloudImageName *string `mapstructure:"image_name" required:"true" cty:"image_name"`
|
||||
AlicloudImageVersion *string `mapstructure:"image_version" required:"false" cty:"image_version"`
|
||||
AlicloudImageDescription *string `mapstructure:"image_description" required:"false" cty:"image_description"`
|
||||
AlicloudImageShareAccounts []string `mapstructure:"image_share_account" required:"false" cty:"image_share_account"`
|
||||
AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account" cty:"image_unshare_account"`
|
||||
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions" required:"false" cty:"image_copy_regions"`
|
||||
AlicloudImageDestinationNames []string `mapstructure:"image_copy_names" required:"false" cty:"image_copy_names"`
|
||||
ImageEncrypted *bool `mapstructure:"image_encrypted" required:"false" cty:"image_encrypted"`
|
||||
AlicloudImageForceDelete *bool `mapstructure:"image_force_delete" required:"false" cty:"image_force_delete"`
|
||||
AlicloudImageForceDeleteSnapshots *bool `mapstructure:"image_force_delete_snapshots" required:"false" cty:"image_force_delete_snapshots"`
|
||||
AlicloudImageForceDeleteInstances *bool `mapstructure:"image_force_delete_instances" cty:"image_force_delete_instances"`
|
||||
AlicloudImageIgnoreDataDisks *bool `mapstructure:"image_ignore_data_disks" required:"false" cty:"image_ignore_data_disks"`
|
||||
AlicloudImageTags map[string]string `mapstructure:"tags" required:"false" cty:"tags"`
|
||||
ECSSystemDiskMapping *FlatAlicloudDiskDevice `mapstructure:"system_disk_mapping" required:"false" cty:"system_disk_mapping"`
|
||||
ECSImagesDiskMappings []FlatAlicloudDiskDevice `mapstructure:"image_disk_mappings" required:"false" cty:"image_disk_mappings"`
|
||||
AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" cty:"associate_public_ip_address"`
|
||||
ZoneId *string `mapstructure:"zone_id" required:"false" cty:"zone_id"`
|
||||
IOOptimized *bool `mapstructure:"io_optimized" required:"false" cty:"io_optimized"`
|
||||
InstanceType *string `mapstructure:"instance_type" required:"true" cty:"instance_type"`
|
||||
Description *string `mapstructure:"description" cty:"description"`
|
||||
AlicloudSourceImage *string `mapstructure:"source_image" required:"true" cty:"source_image"`
|
||||
ForceStopInstance *bool `mapstructure:"force_stop_instance" required:"false" cty:"force_stop_instance"`
|
||||
DisableStopInstance *bool `mapstructure:"disable_stop_instance" required:"false" cty:"disable_stop_instance"`
|
||||
SecurityGroupId *string `mapstructure:"security_group_id" required:"false" cty:"security_group_id"`
|
||||
SecurityGroupName *string `mapstructure:"security_group_name" required:"false" cty:"security_group_name"`
|
||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data"`
|
||||
UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file"`
|
||||
VpcId *string `mapstructure:"vpc_id" required:"false" cty:"vpc_id"`
|
||||
VpcName *string `mapstructure:"vpc_name" required:"false" cty:"vpc_name"`
|
||||
CidrBlock *string `mapstructure:"vpc_cidr_block" required:"false" cty:"vpc_cidr_block"`
|
||||
VSwitchId *string `mapstructure:"vswitch_id" required:"false" cty:"vswitch_id"`
|
||||
VSwitchName *string `mapstructure:"vswitch_name" required:"false" cty:"vswitch_name"`
|
||||
InstanceName *string `mapstructure:"instance_name" required:"false" cty:"instance_name"`
|
||||
InternetChargeType *string `mapstructure:"internet_charge_type" required:"false" cty:"internet_charge_type"`
|
||||
InternetMaxBandwidthOut *int `mapstructure:"internet_max_bandwidth_out" required:"false" cty:"internet_max_bandwidth_out"`
|
||||
WaitSnapshotReadyTimeout *int `mapstructure:"wait_snapshot_ready_timeout" required:"false" cty:"wait_snapshot_ready_timeout"`
|
||||
Type *string `mapstructure:"communicator" cty:"communicator"`
|
||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"`
|
||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"`
|
||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"`
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"`
|
||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"`
|
||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"`
|
||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"`
|
||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"`
|
||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"`
|
||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"`
|
||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"`
|
||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"`
|
||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"`
|
||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"`
|
||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"`
|
||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"`
|
||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"`
|
||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"`
|
||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"`
|
||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"`
|
||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"`
|
||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"`
|
||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"`
|
||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"`
|
||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"`
|
||||
SSHPublicKey []byte `cty:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `cty:"ssh_private_key"`
|
||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"`
|
||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"`
|
||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"`
|
||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"`
|
||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"`
|
||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
|
||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
|
||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
|
||||
SSHPrivateIp *bool `mapstructure:"ssh_private_ip" required:"false" cty:"ssh_private_ip"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) }
|
||||
|
||||
// HCL2Spec returns the hcldec.Spec of a FlatConfig.
|
||||
// This spec is used by HCL to read the fields of FlatConfig.
|
||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
||||
"packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false},
|
||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
||||
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
|
||||
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
|
||||
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
|
||||
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
|
||||
"security_token": &hcldec.AttrSpec{Name: "security_token", 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},
|
||||
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
|
||||
"image_share_account": &hcldec.AttrSpec{Name: "image_share_account", Type: cty.List(cty.String), Required: false},
|
||||
"image_unshare_account": &hcldec.AttrSpec{Name: "image_unshare_account", Type: cty.List(cty.String), Required: false},
|
||||
"image_copy_regions": &hcldec.AttrSpec{Name: "image_copy_regions", Type: cty.List(cty.String), Required: false},
|
||||
"image_copy_names": &hcldec.AttrSpec{Name: "image_copy_names", Type: cty.List(cty.String), Required: false},
|
||||
"image_encrypted": &hcldec.AttrSpec{Name: "image_encrypted", Type: cty.Bool, Required: false},
|
||||
"image_force_delete": &hcldec.AttrSpec{Name: "image_force_delete", Type: cty.Bool, Required: false},
|
||||
"image_force_delete_snapshots": &hcldec.AttrSpec{Name: "image_force_delete_snapshots", Type: cty.Bool, Required: false},
|
||||
"image_force_delete_instances": &hcldec.AttrSpec{Name: "image_force_delete_instances", Type: cty.Bool, Required: false},
|
||||
"image_ignore_data_disks": &hcldec.AttrSpec{Name: "image_ignore_data_disks", Type: cty.Bool, Required: false},
|
||||
"tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, Required: false},
|
||||
"system_disk_mapping": &hcldec.BlockSpec{TypeName: "system_disk_mapping", Nested: hcldec.ObjectSpec((*FlatAlicloudDiskDevice)(nil).HCL2Spec())},
|
||||
"image_disk_mappings": &hcldec.BlockListSpec{TypeName: "image_disk_mappings", Nested: &hcldec.BlockSpec{TypeName: "image_disk_mappings", Nested: hcldec.ObjectSpec((*FlatAlicloudDiskDevice)(nil).HCL2Spec())}},
|
||||
"associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false},
|
||||
"zone_id": &hcldec.AttrSpec{Name: "zone_id", Type: cty.String, Required: false},
|
||||
"io_optimized": &hcldec.AttrSpec{Name: "io_optimized", Type: cty.Bool, Required: false},
|
||||
"instance_type": &hcldec.AttrSpec{Name: "instance_type", Type: cty.String, Required: false},
|
||||
"description": &hcldec.AttrSpec{Name: "description", Type: cty.String, Required: false},
|
||||
"source_image": &hcldec.AttrSpec{Name: "source_image", Type: cty.String, Required: false},
|
||||
"force_stop_instance": &hcldec.AttrSpec{Name: "force_stop_instance", Type: cty.Bool, Required: false},
|
||||
"disable_stop_instance": &hcldec.AttrSpec{Name: "disable_stop_instance", Type: cty.Bool, Required: false},
|
||||
"security_group_id": &hcldec.AttrSpec{Name: "security_group_id", Type: cty.String, Required: false},
|
||||
"security_group_name": &hcldec.AttrSpec{Name: "security_group_name", Type: 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},
|
||||
"vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false},
|
||||
"vpc_name": &hcldec.AttrSpec{Name: "vpc_name", Type: cty.String, Required: false},
|
||||
"vpc_cidr_block": &hcldec.AttrSpec{Name: "vpc_cidr_block", Type: cty.String, Required: false},
|
||||
"vswitch_id": &hcldec.AttrSpec{Name: "vswitch_id", Type: cty.String, Required: false},
|
||||
"vswitch_name": &hcldec.AttrSpec{Name: "vswitch_name", Type: cty.String, Required: false},
|
||||
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
|
||||
"internet_charge_type": &hcldec.AttrSpec{Name: "internet_charge_type", Type: cty.String, Required: false},
|
||||
"internet_max_bandwidth_out": &hcldec.AttrSpec{Name: "internet_max_bandwidth_out", Type: cty.Number, Required: false},
|
||||
"wait_snapshot_ready_timeout": &hcldec.AttrSpec{Name: "wait_snapshot_ready_timeout", Type: cty.Number, Required: false},
|
||||
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
||||
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
||||
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
||||
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
||||
"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_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
||||
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
||||
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
||||
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
||||
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
||||
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
||||
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
||||
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
||||
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
||||
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
||||
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
||||
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
||||
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
||||
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
||||
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
||||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
||||
"ssh_private_ip": &hcldec.AttrSpec{Name: "ssh_private_ip", Type: cty.Bool, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
helperconfig "github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
@@ -127,15 +126,13 @@ func TestBuilderPrepare_Devices(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
expected := AlicloudDiskDevice{
|
||||
if !reflect.DeepEqual(b.config.ECSSystemDiskMapping, AlicloudDiskDevice{
|
||||
DiskCategory: "cloud",
|
||||
Description: "system disk",
|
||||
DiskName: "system_disk",
|
||||
DiskSize: 60,
|
||||
Encrypted: helperconfig.TriUnset,
|
||||
}
|
||||
if !reflect.DeepEqual(b.config.ECSSystemDiskMapping, expected) {
|
||||
t.Fatalf("system disk is not set properly, actual: %v; expected: %v", b.config.ECSSystemDiskMapping, expected)
|
||||
}) {
|
||||
t.Fatalf("system disk is not set properly, actual: %#v", b.config.ECSSystemDiskMapping)
|
||||
}
|
||||
if !reflect.DeepEqual(b.config.ECSImagesDiskMappings, []AlicloudDiskDevice{
|
||||
{
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:generate struct-markdown
|
||||
|
||||
package ecs
|
||||
|
||||
import (
|
||||
@@ -7,192 +5,41 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type AlicloudDiskDevice struct {
|
||||
// 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://.
|
||||
DiskName string `mapstructure:"disk_name" required:"false"`
|
||||
// Category of the system disk. Optional values
|
||||
// are:
|
||||
// - cloud - general cloud disk
|
||||
// - cloud_efficiency - efficiency cloud disk
|
||||
// - cloud_ssd - cloud SSD
|
||||
DiskCategory string `mapstructure:"disk_category" required:"false"`
|
||||
// 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}.
|
||||
DiskSize int `mapstructure:"disk_size" required:"false"`
|
||||
// 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.
|
||||
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
|
||||
// console. It cannot begin with http:// or https://.
|
||||
Description string `mapstructure:"disk_description" required:"false"`
|
||||
// Whether or not the disk is
|
||||
// released along with the instance:
|
||||
DeleteWithInstance bool `mapstructure:"disk_delete_with_instance" required:"false"`
|
||||
// Device information of the related instance:
|
||||
// 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
|
||||
// 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.
|
||||
Encrypted config.Trilean `mapstructure:"disk_encrypted" required:"false"`
|
||||
DiskName string `mapstructure:"disk_name"`
|
||||
DiskCategory string `mapstructure:"disk_category"`
|
||||
DiskSize int `mapstructure:"disk_size"`
|
||||
SnapshotId string `mapstructure:"disk_snapshot_id"`
|
||||
Description string `mapstructure:"disk_description"`
|
||||
DeleteWithInstance bool `mapstructure:"disk_delete_with_instance"`
|
||||
Device string `mapstructure:"disk_device"`
|
||||
Encrypted *bool `mapstructure:"disk_encrypted"`
|
||||
}
|
||||
|
||||
type AlicloudDiskDevices struct {
|
||||
// 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}.
|
||||
//
|
||||
ECSSystemDiskMapping AlicloudDiskDevice `mapstructure:"system_disk_mapping" required:"false"`
|
||||
// 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.
|
||||
//
|
||||
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings" required:"false"`
|
||||
ECSSystemDiskMapping AlicloudDiskDevice `mapstructure:"system_disk_mapping"`
|
||||
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings"`
|
||||
}
|
||||
|
||||
type AlicloudImageConfig struct {
|
||||
// The name of the user-defined image, [2, 128]
|
||||
// English or Chinese characters. It must begin with an uppercase/lowercase
|
||||
// letter or a Chinese character, and may contain numbers, _ or -. It
|
||||
// cannot begin with http:// or https://.
|
||||
AlicloudImageName string `mapstructure:"image_name" required:"true"`
|
||||
// The version number of the image, with a length
|
||||
// limit of 1 to 40 English characters.
|
||||
AlicloudImageVersion string `mapstructure:"image_version" required:"false"`
|
||||
// The description of the image, with a length
|
||||
// limit of 0 to 256 characters. Leaving it blank means null, which is the
|
||||
// default value. It cannot begin with http:// or https://.
|
||||
AlicloudImageDescription string `mapstructure:"image_description" required:"false"`
|
||||
// The IDs of to-be-added Aliyun
|
||||
// accounts to which the image is shared. The number of accounts is 1 to 10.
|
||||
// If number of accounts is greater than 10, this parameter is ignored.
|
||||
AlicloudImageShareAccounts []string `mapstructure:"image_share_account" required:"false"`
|
||||
AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account"`
|
||||
// Copy to the destination regionIds.
|
||||
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions" required:"false"`
|
||||
// The name of the destination image,
|
||||
// [2, 128] English or Chinese characters. It must begin with an
|
||||
// uppercase/lowercase letter or a Chinese character, and may contain numbers,
|
||||
// _ or -. It cannot begin with http:// or https://.
|
||||
AlicloudImageDestinationNames []string `mapstructure:"image_copy_names" required:"false"`
|
||||
// Whether or not to encrypt the target images, including those copied if image_copy_regions is specified. If this option
|
||||
// is set to true, a temporary image will be created from the provisioned
|
||||
// instance in the main region and an encrypted copy will be generated in the
|
||||
// same region. By default, Packer will keep the encryption setting to what
|
||||
// it was in the source image.
|
||||
ImageEncrypted config.Trilean `mapstructure:"image_encrypted" required:"false"`
|
||||
// If this value is true, when the target image names including those
|
||||
// copied are duplicated with existing images, it will delete the existing
|
||||
// images and then create the target images, otherwise, the creation will
|
||||
// fail. The default value is false. Check `image_name` and
|
||||
// `image_copy_names` options for names of target images. If
|
||||
// [-force](https://packer.io/docs/commands/build.html#force) option is
|
||||
// provided in `build` command, this option can be omitted and taken as
|
||||
// true.
|
||||
AlicloudImageForceDelete bool `mapstructure:"image_force_delete" required:"false"`
|
||||
// If this value is true, when delete the duplicated existing images, the
|
||||
// source snapshots of those images will be delete either. If
|
||||
// [-force](https://packer.io/docs/commands/build.html#force) option is
|
||||
// provided in `build` command, this option can be omitted and taken as
|
||||
// true.
|
||||
AlicloudImageForceDeleteSnapshots bool `mapstructure:"image_force_delete_snapshots" required:"false"`
|
||||
AlicloudImageForceDeleteInstances bool `mapstructure:"image_force_delete_instances"`
|
||||
// If this value is true, the image
|
||||
// created will not include any snapshot of data disks. This option would be
|
||||
// useful for any circumstance that default data disks with instance types are
|
||||
// not concerned. The default value is false.
|
||||
AlicloudImageIgnoreDataDisks bool `mapstructure:"image_ignore_data_disks" required:"false"`
|
||||
// The region validation can be skipped
|
||||
// if this value is true, the default value is false.
|
||||
AlicloudImageSkipRegionValidation bool `mapstructure:"skip_region_validation" required:"false"`
|
||||
// Tags applied to the destination
|
||||
// image and relevant snapshots.
|
||||
AlicloudImageTags map[string]string `mapstructure:"tags" required:"false"`
|
||||
AlicloudDiskDevices `mapstructure:",squash"`
|
||||
AlicloudImageName string `mapstructure:"image_name"`
|
||||
AlicloudImageVersion string `mapstructure:"image_version"`
|
||||
AlicloudImageDescription string `mapstructure:"image_description"`
|
||||
AlicloudImageShareAccounts []string `mapstructure:"image_share_account"`
|
||||
AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account"`
|
||||
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions"`
|
||||
AlicloudImageDestinationNames []string `mapstructure:"image_copy_names"`
|
||||
ImageEncrypted *bool `mapstructure:"image_encrypted"`
|
||||
AlicloudImageForceDelete bool `mapstructure:"image_force_delete"`
|
||||
AlicloudImageForceDeleteSnapshots bool `mapstructure:"image_force_delete_snapshots"`
|
||||
AlicloudImageForceDeleteInstances bool `mapstructure:"image_force_delete_instances"`
|
||||
AlicloudImageIgnoreDataDisks bool `mapstructure:"image_ignore_data_disks"`
|
||||
AlicloudImageSkipRegionValidation bool `mapstructure:"skip_region_validation"`
|
||||
AlicloudImageTags map[string]string `mapstructure:"tags"`
|
||||
AlicloudDiskDevices `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
func (c *AlicloudImageConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:generate struct-markdown
|
||||
|
||||
package ecs
|
||||
|
||||
import (
|
||||
@@ -10,115 +8,35 @@ import (
|
||||
|
||||
"github.com/hashicorp/packer/common/uuid"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type RunConfig struct {
|
||||
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
|
||||
// ID of the zone to which the disk belongs.
|
||||
ZoneId string `mapstructure:"zone_id" required:"false"`
|
||||
// Whether an ECS instance is I/O optimized or not. If this option is not
|
||||
// provided, the value will be determined by product API according to what
|
||||
// `instance_type` is used.
|
||||
IOOptimized config.Trilean `mapstructure:"io_optimized" required:"false"`
|
||||
// Type of the instance. For values, see [Instance Type
|
||||
// Table](https://www.alibabacloud.com/help/doc-detail/25378.htm?spm=a3c0i.o25499en.a3.9.14a36ac8iYqKRA).
|
||||
// You can also obtain the latest instance type table by invoking the
|
||||
// [Querying Instance Type
|
||||
// Table](https://intl.aliyun.com/help/doc-detail/25620.htm?spm=a3c0i.o25499en.a3.6.Dr1bik)
|
||||
// interface.
|
||||
InstanceType string `mapstructure:"instance_type" required:"true"`
|
||||
Description string `mapstructure:"description"`
|
||||
// This is the base image id which you want to
|
||||
// create your customized images.
|
||||
AlicloudSourceImage string `mapstructure:"source_image" required:"true"`
|
||||
// Whether to force shutdown upon device
|
||||
// restart. The default value is `false`.
|
||||
//
|
||||
// If it is set to `false`, the system is shut down normally; if it is set to
|
||||
// `true`, the system is forced to shut down.
|
||||
ForceStopInstance bool `mapstructure:"force_stop_instance" required:"false"`
|
||||
// If this option is set to true, Packer
|
||||
// will not stop the instance for you, and you need to make sure the instance
|
||||
// will be stopped in the final provisioner command. Otherwise, Packer will
|
||||
// timeout while waiting the instance to be stopped. This option is provided
|
||||
// for some specific scenarios that you want to stop the instance by yourself.
|
||||
// E.g., Sysprep a windows which may shutdown the instance within its command.
|
||||
// The default value is false.
|
||||
DisableStopInstance bool `mapstructure:"disable_stop_instance" required:"false"`
|
||||
// ID of the security group to which a newly
|
||||
// created instance belongs. Mutual access is allowed between instances in one
|
||||
// security group. If not specified, the newly created instance will be added
|
||||
// to the default security group. If the default group doesn’t exist, or the
|
||||
// number of instances in it has reached the maximum limit, a new security
|
||||
// group will be created automatically.
|
||||
SecurityGroupId string `mapstructure:"security_group_id" required:"false"`
|
||||
// The security group name. The default value
|
||||
// is blank. [2, 128] English or Chinese characters, must begin with an
|
||||
// uppercase/lowercase letter or Chinese character. Can contain numbers, .,
|
||||
// _ or -. It cannot begin with http:// or https://.
|
||||
SecurityGroupName string `mapstructure:"security_group_name" required:"false"`
|
||||
// User data to apply when launching the instance. Note
|
||||
// that you need to be careful about escaping characters due to the templates
|
||||
// being JSON. It is often more convenient to use user_data_file, instead.
|
||||
// Packer will not automatically wait for a user script to finish before
|
||||
// shutting down the instance this must be handled in a provisioner.
|
||||
UserData string `mapstructure:"user_data" required:"false"`
|
||||
// Path to a file that will be used for the user
|
||||
// data when launching the instance.
|
||||
UserDataFile string `mapstructure:"user_data_file" required:"false"`
|
||||
// VPC ID allocated by the system.
|
||||
VpcId string `mapstructure:"vpc_id" required:"false"`
|
||||
// The VPC name. The default value is blank. [2, 128]
|
||||
// English or Chinese characters, must begin with an uppercase/lowercase
|
||||
// letter or Chinese character. Can contain numbers, _ and -. The disk
|
||||
// description will appear on the console. Cannot begin with http:// or
|
||||
// https://.
|
||||
VpcName string `mapstructure:"vpc_name" required:"false"`
|
||||
// Value options: 192.168.0.0/16 and
|
||||
// 172.16.0.0/16. When not specified, the default value is 172.16.0.0/16.
|
||||
CidrBlock string `mapstructure:"vpc_cidr_block" required:"false"`
|
||||
// The ID of the VSwitch to be used.
|
||||
VSwitchId string `mapstructure:"vswitch_id" required:"false"`
|
||||
// The ID of the VSwitch to be used.
|
||||
VSwitchName string `mapstructure:"vswitch_name" required:"false"`
|
||||
// Display name of the instance, which is a string of 2 to 128 Chinese or
|
||||
// English characters. It must begin with an uppercase/lowercase letter or
|
||||
// a Chinese character and can contain numerals, `.`, `_`, or `-`. The
|
||||
// instance name is displayed on the Alibaba Cloud console. If this
|
||||
// parameter is not specified, the default value is InstanceId of the
|
||||
// instance. It cannot begin with `http://` or `https://`.
|
||||
InstanceName string `mapstructure:"instance_name" required:"false"`
|
||||
// Internet charge type, which can be
|
||||
// `PayByTraffic` or `PayByBandwidth`. Optional values:
|
||||
// - `PayByBandwidth`
|
||||
// - `PayByTraffic`
|
||||
//
|
||||
// If this parameter is not specified, the default value is `PayByBandwidth`.
|
||||
// For the regions out of China, currently only support `PayByTraffic`, you
|
||||
// must set it manfully.
|
||||
InternetChargeType string `mapstructure:"internet_charge_type" required:"false"`
|
||||
// Maximum outgoing bandwidth to the
|
||||
// public network, measured in Mbps (Mega bits per second).
|
||||
//
|
||||
// Value range:
|
||||
// - `PayByBandwidth`: \[0, 100\]. If this parameter is not specified, API
|
||||
// automatically sets it to 0 Mbps.
|
||||
// - `PayByTraffic`: \[1, 100\]. If this parameter is not specified, an
|
||||
// error is returned.
|
||||
InternetMaxBandwidthOut int `mapstructure:"internet_max_bandwidth_out" required:"false"`
|
||||
// Timeout of creating snapshot(s).
|
||||
// The default timeout is 3600 seconds if this option is not set or is set
|
||||
// to 0. For those disks containing lots of data, it may require a higher
|
||||
// timeout value.
|
||||
WaitSnapshotReadyTimeout int `mapstructure:"wait_snapshot_ready_timeout" required:"false"`
|
||||
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
|
||||
ZoneId string `mapstructure:"zone_id"`
|
||||
IOOptimized *bool `mapstructure:"io_optimized"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
Description string `mapstructure:"description"`
|
||||
AlicloudSourceImage string `mapstructure:"source_image"`
|
||||
ForceStopInstance bool `mapstructure:"force_stop_instance"`
|
||||
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SecurityGroupName string `mapstructure:"security_group_name"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
UserDataFile string `mapstructure:"user_data_file"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
VpcName string `mapstructure:"vpc_name"`
|
||||
CidrBlock string `mapstructure:"vpc_cidr_block"`
|
||||
VSwitchId string `mapstructure:"vswitch_id"`
|
||||
VSwitchName string `mapstructure:"vswitch_id"`
|
||||
InstanceName string `mapstructure:"instance_name"`
|
||||
InternetChargeType string `mapstructure:"internet_charge_type"`
|
||||
InternetMaxBandwidthOut int `mapstructure:"internet_max_bandwidth_out"`
|
||||
WaitSnapshotReadyTimeout int `mapstructure:"wait_snapshot_ready_timeout"`
|
||||
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
// If this value is true, packer will connect to
|
||||
// the ECS created through private ip instead of allocating a public ip or an
|
||||
// EIP. The default value is false.
|
||||
SSHPrivateIp bool `mapstructure:"ssh_private_ip" required:"false"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
|
||||
@@ -13,9 +13,7 @@ func testConfig() *RunConfig {
|
||||
AlicloudSourceImage: "alicloud_images",
|
||||
InstanceType: "ecs.n1.tiny",
|
||||
Comm: communicator.Config{
|
||||
SSH: communicator.SSH{
|
||||
SSHUsername: "alicloud",
|
||||
},
|
||||
SSHUsername: "alicloud",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func (s *stepCheckAlicloudSourceImage) Run(ctx context.Context, state multistep.
|
||||
|
||||
images := imagesResponse.Images.Image
|
||||
|
||||
// Describe marketplace image
|
||||
// Describe markerplace image
|
||||
describeImagesRequest.ImageOwnerAlias = "marketplace"
|
||||
marketImagesResponse, err := client.DescribeImages(describeImagesRequest)
|
||||
if err != nil {
|
||||
|
||||
@@ -30,7 +30,7 @@ func (s *stepCreateAlicloudImage) Run(ctx context.Context, state multistep.State
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
tempImageName := config.AlicloudImageName
|
||||
if config.ImageEncrypted.True() {
|
||||
if config.ImageEncrypted != nil && *config.ImageEncrypted {
|
||||
tempImageName = fmt.Sprintf("packer_%s", random.AlphaNum(7))
|
||||
ui.Say(fmt.Sprintf("Creating temporary image for encryption: %s", tempImageName))
|
||||
} else {
|
||||
@@ -85,7 +85,7 @@ func (s *stepCreateAlicloudImage) Cleanup(state multistep.StateBag) {
|
||||
}
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
encryptedSet := config.ImageEncrypted.True()
|
||||
encryptedSet := config.ImageEncrypted != nil && *config.ImageEncrypted
|
||||
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
||||
@@ -12,13 +12,12 @@ import (
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
||||
confighelper "github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepCreateAlicloudInstance struct {
|
||||
IOOptimized confighelper.Trilean
|
||||
IOOptimized *bool
|
||||
InstanceType string
|
||||
UserData string
|
||||
UserDataFile string
|
||||
@@ -143,10 +142,12 @@ func (s *stepCreateAlicloudInstance) buildCreateInstanceRequest(state multistep.
|
||||
request.InternetChargeType = s.InternetChargeType
|
||||
request.InternetMaxBandwidthOut = requests.Integer(convertNumber(s.InternetMaxBandwidthOut))
|
||||
|
||||
if s.IOOptimized.True() {
|
||||
request.IoOptimized = IOOptimizedOptimized
|
||||
} else if s.IOOptimized.False() {
|
||||
request.IoOptimized = IOOptimizedNone
|
||||
if s.IOOptimized != nil {
|
||||
if *s.IOOptimized {
|
||||
request.IoOptimized = IOOptimizedOptimized
|
||||
} else {
|
||||
request.IoOptimized = IOOptimizedNone
|
||||
}
|
||||
}
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
@@ -173,8 +174,8 @@ func (s *stepCreateAlicloudInstance) buildCreateInstanceRequest(state multistep.
|
||||
dataDisk.Description = imageDisk.Description
|
||||
dataDisk.DeleteWithInstance = strconv.FormatBool(imageDisk.DeleteWithInstance)
|
||||
dataDisk.Device = imageDisk.Device
|
||||
if imageDisk.Encrypted != confighelper.TriUnset {
|
||||
dataDisk.Encrypted = strconv.FormatBool(imageDisk.Encrypted.True())
|
||||
if imageDisk.Encrypted != nil {
|
||||
dataDisk.Encrypted = strconv.FormatBool(*imageDisk.Encrypted)
|
||||
}
|
||||
|
||||
dataDisks = append(dataDisks, dataDisk)
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
||||
confighelper "github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
@@ -21,7 +20,7 @@ type stepRegionCopyAlicloudImage struct {
|
||||
func (s *stepRegionCopyAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
|
||||
if config.ImageEncrypted != confighelper.TriUnset {
|
||||
if config.ImageEncrypted != nil {
|
||||
s.AlicloudImageDestinationRegions = append(s.AlicloudImageDestinationRegions, s.RegionId)
|
||||
s.AlicloudImageDestinationNames = append(s.AlicloudImageDestinationNames, config.AlicloudImageName)
|
||||
}
|
||||
@@ -39,7 +38,7 @@ func (s *stepRegionCopyAlicloudImage) Run(ctx context.Context, state multistep.S
|
||||
|
||||
ui.Say(fmt.Sprintf("Coping image %s from %s...", srcImageId, s.RegionId))
|
||||
for index, destinationRegion := range s.AlicloudImageDestinationRegions {
|
||||
if destinationRegion == s.RegionId && config.ImageEncrypted == confighelper.TriUnset {
|
||||
if destinationRegion == s.RegionId && config.ImageEncrypted == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -53,8 +52,8 @@ func (s *stepRegionCopyAlicloudImage) Run(ctx context.Context, state multistep.S
|
||||
copyImageRequest.ImageId = srcImageId
|
||||
copyImageRequest.DestinationRegionId = destinationRegion
|
||||
copyImageRequest.DestinationImageName = ecsImageName
|
||||
if config.ImageEncrypted != confighelper.TriUnset {
|
||||
copyImageRequest.Encrypted = requests.NewBoolean(config.ImageEncrypted.True())
|
||||
if config.ImageEncrypted != nil {
|
||||
copyImageRequest.Encrypted = requests.NewBoolean(*config.ImageEncrypted)
|
||||
}
|
||||
|
||||
imageResponse, err := client.CopyImage(copyImageRequest)
|
||||
@@ -66,7 +65,7 @@ func (s *stepRegionCopyAlicloudImage) Run(ctx context.Context, state multistep.S
|
||||
ui.Message(fmt.Sprintf("Copy image from %s(%s) to %s(%s)", s.RegionId, srcImageId, destinationRegion, imageResponse.ImageId))
|
||||
}
|
||||
|
||||
if config.ImageEncrypted != confighelper.TriUnset {
|
||||
if config.ImageEncrypted != nil {
|
||||
if _, err := client.WaitForImageStatus(s.RegionId, alicloudImages[s.RegionId], ImageStatusAvailable, time.Duration(ALICLOUD_DEFAULT_LONG_TIMEOUT)*time.Second); err != nil {
|
||||
return halt(state, err, fmt.Sprintf("Timeout waiting image %s finish copying", alicloudImages[s.RegionId]))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config,BlockDevices,BlockDevice
|
||||
|
||||
// The chroot package is able to create an Amazon AMI without requiring the
|
||||
// launch of a new instance for every build. It does this by attaching and
|
||||
// mounting the root volume of another AMI and chrooting into that directory.
|
||||
// It then creates an AMI from that attached drive.
|
||||
// The chroot package is able to create an Amazon AMI without requiring
|
||||
// the launch of a new instance for every build. It does this by attaching
|
||||
// and mounting the root volume of another AMI and chrooting into that
|
||||
// directory. It then creates an AMI from that attached drive.
|
||||
package chroot
|
||||
|
||||
import (
|
||||
@@ -15,7 +12,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/chroot"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -25,155 +21,36 @@ import (
|
||||
// The unique ID for this builder
|
||||
const BuilderId = "mitchellh.amazon.chroot"
|
||||
|
||||
// Config is the configuration that is chained through the steps and settable
|
||||
// from the template.
|
||||
// Config is the configuration that is chained through the steps and
|
||||
// settable from the template.
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AMIConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
// Add one or more [block device
|
||||
// mappings](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html)
|
||||
// to the AMI. If this field is populated, and you are building from an
|
||||
// existing source image, the block device mappings in the source image
|
||||
// will be overwritten. This means you must have a block device mapping
|
||||
// entry for your root volume, `root_volume_size` and `root_device_name`.
|
||||
// See the [BlockDevices](#block-devices-configuration) documentation for
|
||||
// fields.
|
||||
AMIMappings awscommon.BlockDevices `mapstructure:"ami_block_device_mappings" hcl2-schema-generator:"ami_block_device_mappings,direct" required:"false"`
|
||||
// This is a list of devices to mount into the chroot environment. This
|
||||
// configuration parameter requires some additional documentation which is
|
||||
// in the Chroot Mounts section. Please read that section for more
|
||||
// information on how to use this.
|
||||
ChrootMounts [][]string `mapstructure:"chroot_mounts" required:"false"`
|
||||
// How to run shell commands. This defaults to {{.Command}}. This may be
|
||||
// useful to set if you want to set environmental variables or perhaps run
|
||||
// it with sudo or so on. This is a configuration template where the
|
||||
// .Command variable is replaced with the command to be run. Defaults to
|
||||
// {{.Command}}.
|
||||
CommandWrapper string `mapstructure:"command_wrapper" required:"false"`
|
||||
// Paths to files on the running EC2 instance that will be copied into the
|
||||
// chroot environment prior to provisioning. Defaults to /etc/resolv.conf
|
||||
// so that DNS lookups work. Pass an empty list to skip copying
|
||||
// /etc/resolv.conf. You may need to do this if you're building an image
|
||||
// that uses systemd.
|
||||
CopyFiles []string `mapstructure:"copy_files" required:"false"`
|
||||
// The path to the device where the root volume of the source AMI will be
|
||||
// attached. This defaults to "" (empty string), which forces Packer to
|
||||
// find an open device automatically.
|
||||
DevicePath string `mapstructure:"device_path" required:"false"`
|
||||
// When we call the mount command (by default mount -o device dir), the
|
||||
// string provided in nvme_mount_path will replace device in that command.
|
||||
// When this option is not set, device in that command will be something
|
||||
// like /dev/sdf1, mirroring the attached device name. This assumption
|
||||
// works for most instances but will fail with c5 and m5 instances. In
|
||||
// order to use the chroot builder with c5 and m5 instances, you must
|
||||
// manually set nvme_device_path and device_path.
|
||||
NVMEDevicePath string `mapstructure:"nvme_device_path" required:"false"`
|
||||
// Build a new volume instead of starting from an existing AMI root volume
|
||||
// snapshot. Default false. If true, source_ami is no longer used and the
|
||||
// following options become required: ami_virtualization_type,
|
||||
// pre_mount_commands and root_volume_size. The below options are also
|
||||
// required in this mode only:
|
||||
FromScratch bool `mapstructure:"from_scratch" required:"false"`
|
||||
// Options to supply the mount command when mounting devices. Each option
|
||||
// will be prefixed with -o and supplied to the mount command ran by
|
||||
// Packer. Because this command is ran in a shell, user discretion is
|
||||
// advised. See this manual page for the mount command for valid file
|
||||
// system specific options.
|
||||
MountOptions []string `mapstructure:"mount_options" required:"false"`
|
||||
// The partition number containing the / partition. By default this is the
|
||||
// first partition of the volume, (for example, xvda1) but you can
|
||||
// designate the entire block device by setting "mount_partition": "0" in
|
||||
// your config, which will mount xvda instead.
|
||||
MountPartition string `mapstructure:"mount_partition" required:"false"`
|
||||
// The path where the volume will be mounted. This is where the chroot
|
||||
// environment will be. This defaults to
|
||||
// /mnt/packer-amazon-chroot-volumes/{{.Device}}. This is a configuration
|
||||
// template where the .Device variable is replaced with the name of the
|
||||
// device where the volume is attached.
|
||||
MountPath string `mapstructure:"mount_path" required:"false"`
|
||||
// As pre_mount_commands, but the commands are executed after mounting the
|
||||
// root device and before the extra mount and copy steps. The device and
|
||||
// mount path are provided by {{.Device}} and {{.MountPath}}.
|
||||
PostMountCommands []string `mapstructure:"post_mount_commands" required:"false"`
|
||||
// A series of commands to execute after attaching the root volume and
|
||||
// before mounting the chroot. This is not required unless using
|
||||
// from_scratch. If so, this should include any partitioning and filesystem
|
||||
// creation commands. The path to the device is provided by {{.Device}}.
|
||||
PreMountCommands []string `mapstructure:"pre_mount_commands" required:"false"`
|
||||
// The root device name. For example, xvda.
|
||||
RootDeviceName string `mapstructure:"root_device_name" required:"false"`
|
||||
// The size of the root volume in GB for the chroot environment and the
|
||||
// resulting AMI. Default size is the snapshot size of the source_ami
|
||||
// unless from_scratch is true, in which case this field must be defined.
|
||||
RootVolumeSize int64 `mapstructure:"root_volume_size" required:"false"`
|
||||
// The type of EBS volume for the chroot environment and resulting AMI. The
|
||||
// default value is the type of the source_ami, unless from_scratch is
|
||||
// true, in which case the default value is gp2. You can only specify io1
|
||||
// if building based on top of a source_ami which is also io1.
|
||||
RootVolumeType string `mapstructure:"root_volume_type" required:"false"`
|
||||
// The source AMI whose root volume will be copied and provisioned on the
|
||||
// currently running instance. This must be an EBS-backed AMI with a root
|
||||
// volume snapshot that you have access to. Note: this is not used when
|
||||
// from_scratch is set to true.
|
||||
SourceAmi string `mapstructure:"source_ami" required:"true"`
|
||||
// Filters used to populate the source_ami field. Example:
|
||||
//
|
||||
//
|
||||
// ``` json
|
||||
// {
|
||||
// "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
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// This selects the most recent Ubuntu 16.04 HVM EBS AMI from Canonical. NOTE:
|
||||
// This will fail unless *exactly* one AMI is returned. In the above example,
|
||||
// `most_recent` will cause this to succeed by selecting the newest image.
|
||||
//
|
||||
// - `filters` (map of strings) - filters used to select a `source_ami`.
|
||||
// NOTE: This will fail unless *exactly* one AMI is returned. Any filter
|
||||
// described in the docs for
|
||||
// [DescribeImages](http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeImages.html)
|
||||
// is valid.
|
||||
//
|
||||
// - `owners` (array of strings) - Filters the images by their owner. You
|
||||
// may specify one or more AWS account IDs, "self" (which will use the
|
||||
// account whose credentials you are using to run Packer), or an AWS owner
|
||||
// alias: for example, "amazon", "aws-marketplace", or "microsoft". This
|
||||
// option is required for security reasons.
|
||||
//
|
||||
// - `most_recent` (boolean) - Selects the newest created image when true.
|
||||
// This is most useful for selecting a daily distro build.
|
||||
//
|
||||
// You may set this in place of `source_ami` or in conjunction with it. If you
|
||||
// set this in conjunction with `source_ami`, the `source_ami` will be added
|
||||
// to the filter. The provided `source_ami` must meet all of the filtering
|
||||
// criteria provided in `source_ami_filter`; this pins the AMI returned by the
|
||||
// filter, but will cause Packer to fail if the `source_ami` does not exist.
|
||||
SourceAmiFilter awscommon.AmiFilterOptions `mapstructure:"source_ami_filter" required:"false"`
|
||||
// Tags to apply to the volumes that are *launched*. This is a [template
|
||||
// engine](/docs/templates/engine.html), see [Build template
|
||||
// data](#build-template-data) for more information.
|
||||
RootVolumeTags awscommon.TagMap `mapstructure:"root_volume_tags" required:"false"`
|
||||
// what architecture to use when registering the final AMI; valid options
|
||||
// are "x86_64" or "arm64". Defaults to "x86_64".
|
||||
Architecture string `mapstructure:"ami_architecture" required:"false"`
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AMIBlockDevices `mapstructure:",squash"`
|
||||
awscommon.AMIConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
|
||||
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
|
||||
CommandWrapper string `mapstructure:"command_wrapper"`
|
||||
CopyFiles []string `mapstructure:"copy_files"`
|
||||
DevicePath string `mapstructure:"device_path"`
|
||||
NVMEDevicePath string `mapstructure:"nvme_device_path"`
|
||||
FromScratch bool `mapstructure:"from_scratch"`
|
||||
MountOptions []string `mapstructure:"mount_options"`
|
||||
MountPartition string `mapstructure:"mount_partition"`
|
||||
MountPath string `mapstructure:"mount_path"`
|
||||
PostMountCommands []string `mapstructure:"post_mount_commands"`
|
||||
PreMountCommands []string `mapstructure:"pre_mount_commands"`
|
||||
RootDeviceName string `mapstructure:"root_device_name"`
|
||||
RootVolumeSize int64 `mapstructure:"root_volume_size"`
|
||||
RootVolumeType string `mapstructure:"root_volume_type"`
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
SourceAmiFilter awscommon.AmiFilterOptions `mapstructure:"source_ami_filter"`
|
||||
RootVolumeTags awscommon.TagMap `mapstructure:"root_volume_tags"`
|
||||
Architecture string `mapstructure:"ami_architecture"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (c *Config) GetContext() interpolate.Context {
|
||||
return c.ctx
|
||||
}
|
||||
|
||||
type wrappedCommandTemplate struct {
|
||||
Command string
|
||||
}
|
||||
@@ -294,8 +171,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
if len(b.config.AMIMappings) > 0 && b.config.RootDeviceName != "" {
|
||||
if b.config.RootVolumeSize == 0 {
|
||||
// Although, they can specify the device size in the block
|
||||
// device mapping, it's easier to be specific here.
|
||||
// Although, they can specify the device size in the block device mapping, it's easier to
|
||||
// be specific here.
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("root_volume_size is required if ami_block_device_mappings is specified"))
|
||||
}
|
||||
@@ -353,7 +230,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
state.Put("awsSession", session)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
state.Put("wrappedCommand", common.CommandWrapper(wrappedCommand))
|
||||
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
@@ -388,24 +265,20 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
},
|
||||
&StepAttachVolume{},
|
||||
&StepEarlyUnflock{},
|
||||
&chroot.StepPreMountCommands{
|
||||
&StepPreMountCommands{
|
||||
Commands: b.config.PreMountCommands,
|
||||
},
|
||||
&StepMountDevice{
|
||||
MountOptions: b.config.MountOptions,
|
||||
MountPartition: b.config.MountPartition,
|
||||
},
|
||||
&chroot.StepPostMountCommands{
|
||||
&StepPostMountCommands{
|
||||
Commands: b.config.PostMountCommands,
|
||||
},
|
||||
&chroot.StepMountExtra{
|
||||
ChrootMounts: b.config.ChrootMounts,
|
||||
},
|
||||
&chroot.StepCopyFiles{
|
||||
Files: b.config.CopyFiles,
|
||||
},
|
||||
&chroot.StepChrootProvision{},
|
||||
&chroot.StepEarlyCleanup{},
|
||||
&StepMountExtra{},
|
||||
&StepCopyFiles{},
|
||||
&StepChrootProvision{},
|
||||
&StepEarlyCleanup{},
|
||||
&StepSnapshot{},
|
||||
&awscommon.StepDeregisterAMI{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
@@ -418,7 +291,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
RootVolumeSize: b.config.RootVolumeSize,
|
||||
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
||||
EnableAMIENASupport: b.config.AMIENASupport,
|
||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
||||
},
|
||||
&awscommon.StepAMIRegionCopy{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,BlockDevices,BlockDevice"; DO NOT EDIT.
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatConfig struct {
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"`
|
||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
|
||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
|
||||
AMIName *string `mapstructure:"ami_name" required:"true" cty:"ami_name"`
|
||||
AMIDescription *string `mapstructure:"ami_description" required:"false" cty:"ami_description"`
|
||||
AMIVirtType *string `mapstructure:"ami_virtualization_type" required:"false" cty:"ami_virtualization_type"`
|
||||
AMIUsers []string `mapstructure:"ami_users" required:"false" cty:"ami_users"`
|
||||
AMIGroups []string `mapstructure:"ami_groups" required:"false" cty:"ami_groups"`
|
||||
AMIProductCodes []string `mapstructure:"ami_product_codes" required:"false" cty:"ami_product_codes"`
|
||||
AMIRegions []string `mapstructure:"ami_regions" required:"false" cty:"ami_regions"`
|
||||
AMISkipRegionValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"`
|
||||
AMITags common.TagMap `mapstructure:"tags" required:"false" cty:"tags"`
|
||||
AMIENASupport *bool `mapstructure:"ena_support" required:"false" cty:"ena_support"`
|
||||
AMISriovNetSupport *bool `mapstructure:"sriov_support" required:"false" cty:"sriov_support"`
|
||||
AMIForceDeregister *bool `mapstructure:"force_deregister" required:"false" cty:"force_deregister"`
|
||||
AMIForceDeleteSnapshot *bool `mapstructure:"force_delete_snapshot" required:"false" cty:"force_delete_snapshot"`
|
||||
AMIEncryptBootVolume *bool `mapstructure:"encrypt_boot" required:"false" cty:"encrypt_boot"`
|
||||
AMIKmsKeyId *string `mapstructure:"kms_key_id" required:"false" cty:"kms_key_id"`
|
||||
AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids" required:"false" cty:"region_kms_key_ids"`
|
||||
AMISkipBuildRegion *bool `mapstructure:"skip_save_build_region" cty:"skip_save_build_region"`
|
||||
SnapshotTags common.TagMap `mapstructure:"snapshot_tags" required:"false" cty:"snapshot_tags"`
|
||||
SnapshotUsers []string `mapstructure:"snapshot_users" required:"false" cty:"snapshot_users"`
|
||||
SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false" cty:"snapshot_groups"`
|
||||
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key"`
|
||||
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2"`
|
||||
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages"`
|
||||
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify"`
|
||||
MFACode *string `mapstructure:"mfa_code" required:"false" cty:"mfa_code"`
|
||||
ProfileName *string `mapstructure:"profile" required:"false" cty:"profile"`
|
||||
RawRegion *string `mapstructure:"region" required:"true" cty:"region"`
|
||||
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key"`
|
||||
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check"`
|
||||
Token *string `mapstructure:"token" required:"false" cty:"token"`
|
||||
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine"`
|
||||
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" hcl2-schema-generator:"ami_block_device_mappings,direct" required:"false" cty:"ami_block_device_mappings"`
|
||||
ChrootMounts [][]string `mapstructure:"chroot_mounts" required:"false" cty:"chroot_mounts"`
|
||||
CommandWrapper *string `mapstructure:"command_wrapper" required:"false" cty:"command_wrapper"`
|
||||
CopyFiles []string `mapstructure:"copy_files" required:"false" cty:"copy_files"`
|
||||
DevicePath *string `mapstructure:"device_path" required:"false" cty:"device_path"`
|
||||
NVMEDevicePath *string `mapstructure:"nvme_device_path" required:"false" cty:"nvme_device_path"`
|
||||
FromScratch *bool `mapstructure:"from_scratch" required:"false" cty:"from_scratch"`
|
||||
MountOptions []string `mapstructure:"mount_options" required:"false" cty:"mount_options"`
|
||||
MountPartition *string `mapstructure:"mount_partition" required:"false" cty:"mount_partition"`
|
||||
MountPath *string `mapstructure:"mount_path" required:"false" cty:"mount_path"`
|
||||
PostMountCommands []string `mapstructure:"post_mount_commands" required:"false" cty:"post_mount_commands"`
|
||||
PreMountCommands []string `mapstructure:"pre_mount_commands" required:"false" cty:"pre_mount_commands"`
|
||||
RootDeviceName *string `mapstructure:"root_device_name" required:"false" cty:"root_device_name"`
|
||||
RootVolumeSize *int64 `mapstructure:"root_volume_size" required:"false" cty:"root_volume_size"`
|
||||
RootVolumeType *string `mapstructure:"root_volume_type" required:"false" cty:"root_volume_type"`
|
||||
SourceAmi *string `mapstructure:"source_ami" required:"true" cty:"source_ami"`
|
||||
SourceAmiFilter *common.FlatAmiFilterOptions `mapstructure:"source_ami_filter" required:"false" cty:"source_ami_filter"`
|
||||
RootVolumeTags common.TagMap `mapstructure:"root_volume_tags" required:"false" cty:"root_volume_tags"`
|
||||
Architecture *string `mapstructure:"ami_architecture" required:"false" cty:"ami_architecture"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) }
|
||||
|
||||
// HCL2Spec returns the hcldec.Spec of a FlatConfig.
|
||||
// This spec is used by HCL to read the fields of FlatConfig.
|
||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
||||
"packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false},
|
||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
||||
"ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false},
|
||||
"ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false},
|
||||
"ami_virtualization_type": &hcldec.AttrSpec{Name: "ami_virtualization_type", Type: cty.String, Required: false},
|
||||
"ami_users": &hcldec.AttrSpec{Name: "ami_users", Type: cty.List(cty.String), Required: false},
|
||||
"ami_groups": &hcldec.AttrSpec{Name: "ami_groups", Type: cty.List(cty.String), Required: false},
|
||||
"ami_product_codes": &hcldec.AttrSpec{Name: "ami_product_codes", Type: cty.List(cty.String), Required: false},
|
||||
"ami_regions": &hcldec.AttrSpec{Name: "ami_regions", Type: cty.List(cty.String), Required: false},
|
||||
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
|
||||
"tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false},
|
||||
"ena_support": &hcldec.AttrSpec{Name: "ena_support", Type: cty.Bool, Required: false},
|
||||
"sriov_support": &hcldec.AttrSpec{Name: "sriov_support", Type: cty.Bool, Required: false},
|
||||
"force_deregister": &hcldec.AttrSpec{Name: "force_deregister", Type: cty.Bool, Required: false},
|
||||
"force_delete_snapshot": &hcldec.AttrSpec{Name: "force_delete_snapshot", Type: cty.Bool, Required: false},
|
||||
"encrypt_boot": &hcldec.AttrSpec{Name: "encrypt_boot", Type: cty.Bool, Required: false},
|
||||
"kms_key_id": &hcldec.AttrSpec{Name: "kms_key_id", Type: cty.String, Required: false},
|
||||
"region_kms_key_ids": &hcldec.BlockAttrsSpec{TypeName: "region_kms_key_ids", ElementType: cty.String, Required: false},
|
||||
"skip_save_build_region": &hcldec.AttrSpec{Name: "skip_save_build_region", Type: cty.Bool, Required: false},
|
||||
"snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false},
|
||||
"snapshot_users": &hcldec.AttrSpec{Name: "snapshot_users", Type: cty.List(cty.String), Required: false},
|
||||
"snapshot_groups": &hcldec.AttrSpec{Name: "snapshot_groups", Type: cty.List(cty.String), Required: false},
|
||||
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
|
||||
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
|
||||
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
|
||||
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
|
||||
"mfa_code": &hcldec.AttrSpec{Name: "mfa_code", Type: cty.String, Required: false},
|
||||
"profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false},
|
||||
"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},
|
||||
"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())},
|
||||
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: &hcldec.BlockSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())}},
|
||||
"chroot_mounts": &hcldec.BlockListSpec{TypeName: "chroot_mounts", Nested: &hcldec.AttrSpec{Name: "chroot_mounts", Type: cty.List(cty.String), Required: false}},
|
||||
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
|
||||
"copy_files": &hcldec.AttrSpec{Name: "copy_files", Type: cty.List(cty.String), Required: false},
|
||||
"device_path": &hcldec.AttrSpec{Name: "device_path", Type: cty.String, Required: false},
|
||||
"nvme_device_path": &hcldec.AttrSpec{Name: "nvme_device_path", Type: cty.String, Required: false},
|
||||
"from_scratch": &hcldec.AttrSpec{Name: "from_scratch", Type: cty.Bool, Required: false},
|
||||
"mount_options": &hcldec.AttrSpec{Name: "mount_options", Type: cty.List(cty.String), Required: false},
|
||||
"mount_partition": &hcldec.AttrSpec{Name: "mount_partition", Type: cty.String, Required: false},
|
||||
"mount_path": &hcldec.AttrSpec{Name: "mount_path", Type: cty.String, Required: false},
|
||||
"post_mount_commands": &hcldec.AttrSpec{Name: "post_mount_commands", Type: cty.List(cty.String), Required: false},
|
||||
"pre_mount_commands": &hcldec.AttrSpec{Name: "pre_mount_commands", Type: cty.List(cty.String), Required: false},
|
||||
"root_device_name": &hcldec.AttrSpec{Name: "root_device_name", Type: cty.String, Required: false},
|
||||
"root_volume_size": &hcldec.AttrSpec{Name: "root_volume_size", Type: cty.Number, Required: false},
|
||||
"root_volume_type": &hcldec.AttrSpec{Name: "root_volume_type", Type: cty.String, Required: false},
|
||||
"source_ami": &hcldec.AttrSpec{Name: "source_ami", Type: cty.String, Required: false},
|
||||
"source_ami_filter": &hcldec.BlockSpec{TypeName: "source_ami_filter", Nested: hcldec.ObjectSpec((*common.FlatAmiFilterOptions)(nil).HCL2Spec())},
|
||||
"root_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false},
|
||||
"ami_architecture": &hcldec.AttrSpec{Name: "ami_architecture", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func (c *Communicator) Start(ctx context.Context, cmd *packer.RemoteCmd) error {
|
||||
func (c *Communicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
|
||||
dst = filepath.Join(c.Chroot, dst)
|
||||
log.Printf("Uploading to chroot dir: %s", dst)
|
||||
tf, err := tmp.File("packer-outscale-chroot")
|
||||
tf, err := tmp.File("packer-amazon-chroot")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error preparing shell script: %s", err)
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error
|
||||
}
|
||||
|
||||
func (c *Communicator) DownloadDir(src string, dst string, exclude []string) error {
|
||||
return fmt.Errorf("DownloadDir is not implemented for outscale-chroot")
|
||||
return fmt.Errorf("DownloadDir is not implemented for amazon-chroot")
|
||||
}
|
||||
|
||||
func (c *Communicator) Download(src string, w io.Writer) error {
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
)
|
||||
|
||||
func TestCopyFile(t *testing.T) {
|
||||
@@ -28,7 +26,7 @@ func TestCopyFile(t *testing.T) {
|
||||
}
|
||||
first.Sync()
|
||||
|
||||
cmd := common.ShellCommand(fmt.Sprintf("cp %s %s", first.Name(), newName))
|
||||
cmd := ShellCommand(fmt.Sprintf("cp %s %s", first.Name(), newName))
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("Couldn't copy file")
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/common/chroot"
|
||||
)
|
||||
import "testing"
|
||||
|
||||
func TestAttachVolumeCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepAttachVolume)
|
||||
if _, ok := raw.(chroot.Cleanup); !ok {
|
||||
if _, ok := raw.(Cleanup); !ok {
|
||||
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer/common/chroot"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
@@ -14,7 +13,7 @@ import (
|
||||
type StepEarlyUnflock struct{}
|
||||
|
||||
func (s *StepEarlyUnflock) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
cleanup := state.Get("flock_cleanup").(chroot.Cleanup)
|
||||
cleanup := state.Get("flock_cleanup").(Cleanup)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
log.Println("Unlocking file lock...")
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/common/chroot"
|
||||
)
|
||||
import "testing"
|
||||
|
||||
func TestFlockCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepFlock)
|
||||
if _, ok := raw.(chroot.Cleanup); !ok {
|
||||
if _, ok := raw.(Cleanup); !ok {
|
||||
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
@@ -40,7 +39,7 @@ func (s *StepMountDevice) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
// customizable device path for mounting NVME block devices on c5 and m5 HVM
|
||||
device = config.NVMEDevicePath
|
||||
}
|
||||
wrappedCommand := state.Get("wrappedCommand").(common.CommandWrapper)
|
||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||
|
||||
var virtualizationType string
|
||||
if config.FromScratch || config.AMIVirtType != "" {
|
||||
@@ -105,7 +104,7 @@ func (s *StepMountDevice) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
log.Printf("[DEBUG] (step mount) mount command is %s", mountCommand)
|
||||
cmd := common.ShellCommand(mountCommand)
|
||||
cmd := ShellCommand(mountCommand)
|
||||
cmd.Stderr = stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
err := fmt.Errorf(
|
||||
@@ -136,7 +135,7 @@ func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error {
|
||||
}
|
||||
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
wrappedCommand := state.Get("wrappedCommand").(common.CommandWrapper)
|
||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||
|
||||
ui.Say("Unmounting the root device...")
|
||||
unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", s.mountPath))
|
||||
@@ -144,7 +143,7 @@ func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error {
|
||||
return fmt.Errorf("Error creating unmount command: %s", err)
|
||||
}
|
||||
|
||||
cmd := common.ShellCommand(unmountCommand)
|
||||
cmd := ShellCommand(unmountCommand)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("Error unmounting root device: %s", err)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/common/chroot"
|
||||
)
|
||||
import "testing"
|
||||
|
||||
func TestMountDeviceCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepMountDevice)
|
||||
if _, ok := raw.(chroot.Cleanup); !ok {
|
||||
if _, ok := raw.(Cleanup); !ok {
|
||||
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,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/common/random"
|
||||
confighelper "github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
@@ -16,9 +14,8 @@ import (
|
||||
// StepRegisterAMI creates the AMI.
|
||||
type StepRegisterAMI struct {
|
||||
RootVolumeSize int64
|
||||
EnableAMIENASupport confighelper.Trilean
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMISriovNetSupport bool
|
||||
AMISkipBuildRegion bool
|
||||
}
|
||||
|
||||
func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
@@ -31,29 +28,12 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
|
||||
var registerOpts *ec2.RegisterImageInput
|
||||
|
||||
// Create the image
|
||||
amiName := config.AMIName
|
||||
state.Put("intermediary_image", false)
|
||||
if config.AMIEncryptBootVolume.True() || s.AMISkipBuildRegion {
|
||||
state.Put("intermediary_image", true)
|
||||
|
||||
// From AWS SDK docs: You can encrypt a copy of an unencrypted snapshot,
|
||||
// but you cannot use it to create an unencrypted copy of an encrypted
|
||||
// snapshot. Your default CMK for EBS is used unless you specify a
|
||||
// non-default key using KmsKeyId.
|
||||
|
||||
// If encrypt_boot is nil or true, we need to create a temporary image
|
||||
// so that in step_region_copy, we can copy it with the correct
|
||||
// encryption
|
||||
amiName = random.AlphaNum(7)
|
||||
}
|
||||
|
||||
// Source Image is only required to be passed if the image is not from scratch
|
||||
if config.FromScratch {
|
||||
registerOpts = buildBaseRegisterOpts(config, nil, s.RootVolumeSize, snapshotID, amiName)
|
||||
registerOpts = buildBaseRegisterOpts(config, nil, s.RootVolumeSize, snapshotID)
|
||||
} else {
|
||||
image := state.Get("source_image").(*ec2.Image)
|
||||
registerOpts = buildBaseRegisterOpts(config, image, s.RootVolumeSize, snapshotID, amiName)
|
||||
registerOpts = buildBaseRegisterOpts(config, image, s.RootVolumeSize, snapshotID)
|
||||
}
|
||||
|
||||
if s.EnableAMISriovNetSupport {
|
||||
@@ -61,7 +41,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
|
||||
registerOpts.SriovNetSupport = aws.String("simple")
|
||||
}
|
||||
if s.EnableAMIENASupport.True() {
|
||||
if s.EnableAMIENASupport != nil && *s.EnableAMIENASupport {
|
||||
// Set EnaSupport to true
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
registerOpts.EnaSupport = aws.Bool(true)
|
||||
@@ -94,7 +74,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {}
|
||||
|
||||
// Builds the base register opts with architecture, name, root block device, mappings, virtualizationtype
|
||||
func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSize int64, snapshotID string, amiName string) *ec2.RegisterImageInput {
|
||||
func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSize int64, snapshotID string) *ec2.RegisterImageInput {
|
||||
var (
|
||||
mappings []*ec2.BlockDeviceMapping
|
||||
rootDeviceName string
|
||||
@@ -102,7 +82,7 @@ func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSiz
|
||||
|
||||
generatingNewBlockDeviceMappings := config.FromScratch || len(config.AMIMappings) > 0
|
||||
if generatingNewBlockDeviceMappings {
|
||||
mappings = config.AMIMappings.BuildEC2BlockDeviceMappings()
|
||||
mappings = config.AMIBlockDevices.BuildAMIDevices()
|
||||
rootDeviceName = config.RootDeviceName
|
||||
} else {
|
||||
// If config.FromScratch is false, source image must be set
|
||||
@@ -136,7 +116,7 @@ func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSiz
|
||||
|
||||
if config.FromScratch {
|
||||
return &ec2.RegisterImageInput{
|
||||
Name: &amiName,
|
||||
Name: &config.AMIName,
|
||||
Architecture: aws.String(config.Architecture),
|
||||
RootDeviceName: aws.String(rootDeviceName),
|
||||
VirtualizationType: aws.String(config.AMIVirtType),
|
||||
@@ -144,12 +124,12 @@ func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSiz
|
||||
}
|
||||
}
|
||||
|
||||
return buildRegisterOptsFromExistingImage(config, sourceImage, newMappings, rootDeviceName, amiName)
|
||||
return buildRegisterOptsFromExistingImage(config, sourceImage, newMappings, rootDeviceName)
|
||||
}
|
||||
|
||||
func buildRegisterOptsFromExistingImage(config *Config, image *ec2.Image, mappings []*ec2.BlockDeviceMapping, rootDeviceName string, amiName string) *ec2.RegisterImageInput {
|
||||
func buildRegisterOptsFromExistingImage(config *Config, image *ec2.Image, mappings []*ec2.BlockDeviceMapping, rootDeviceName string) *ec2.RegisterImageInput {
|
||||
registerOpts := &ec2.RegisterImageInput{
|
||||
Name: &amiName,
|
||||
Name: &config.AMIName,
|
||||
Architecture: image.Architecture,
|
||||
RootDeviceName: &rootDeviceName,
|
||||
BlockDeviceMappings: mappings,
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestStepRegisterAmi_buildRegisterOpts_pv(t *testing.T) {
|
||||
|
||||
blockDevices := []*ec2.BlockDeviceMapping{}
|
||||
|
||||
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
|
||||
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName)
|
||||
|
||||
expected := config.AMIVirtType
|
||||
if *opts.VirtualizationType != expected {
|
||||
@@ -64,7 +64,7 @@ func TestStepRegisterAmi_buildRegisterOpts_hvm(t *testing.T) {
|
||||
|
||||
blockDevices := []*ec2.BlockDeviceMapping{}
|
||||
|
||||
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
|
||||
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName)
|
||||
|
||||
expected := config.AMIVirtType
|
||||
if *opts.VirtualizationType != expected {
|
||||
@@ -92,14 +92,16 @@ func TestStepRegisterAmi_buildRegisterOptsFromScratch(t *testing.T) {
|
||||
config := Config{
|
||||
FromScratch: true,
|
||||
PackerConfig: common.PackerConfig{},
|
||||
AMIMappings: []amazon.BlockDevice{
|
||||
amazon.BlockDevice{
|
||||
DeviceName: rootDeviceName,
|
||||
AMIBlockDevices: amazon.AMIBlockDevices{
|
||||
AMIMappings: []amazon.BlockDevice{
|
||||
{
|
||||
DeviceName: rootDeviceName,
|
||||
},
|
||||
},
|
||||
},
|
||||
RootDeviceName: rootDeviceName,
|
||||
}
|
||||
registerOpts := buildBaseRegisterOpts(&config, nil, 10, snapshotID, config.AMIName)
|
||||
registerOpts := buildBaseRegisterOpts(&config, nil, 10, snapshotID)
|
||||
|
||||
if len(registerOpts.BlockDeviceMappings) != 1 {
|
||||
t.Fatal("Expected block device mapping of length 1")
|
||||
@@ -138,7 +140,7 @@ func TestStepRegisterAmi_buildRegisterOptFromExistingImage(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotID, config.AMIName)
|
||||
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotID)
|
||||
|
||||
if len(registerOpts.BlockDeviceMappings) != 2 {
|
||||
t.Fatal("Expected block device mapping of length 2")
|
||||
@@ -167,9 +169,11 @@ func TestStepRegisterAmi_buildRegisterOptFromExistingImageWithBlockDeviceMapping
|
||||
config := Config{
|
||||
FromScratch: false,
|
||||
PackerConfig: common.PackerConfig{},
|
||||
AMIMappings: []amazon.BlockDevice{
|
||||
amazon.BlockDevice{
|
||||
DeviceName: rootDeviceName,
|
||||
AMIBlockDevices: amazon.AMIBlockDevices{
|
||||
AMIMappings: []amazon.BlockDevice{
|
||||
{
|
||||
DeviceName: rootDeviceName,
|
||||
},
|
||||
},
|
||||
},
|
||||
RootDeviceName: rootDeviceName,
|
||||
@@ -196,7 +200,7 @@ func TestStepRegisterAmi_buildRegisterOptFromExistingImageWithBlockDeviceMapping
|
||||
},
|
||||
},
|
||||
}
|
||||
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotId, config.AMIName)
|
||||
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotId)
|
||||
|
||||
if len(registerOpts.BlockDeviceMappings) != 1 {
|
||||
t.Fatal("Expected block device mapping of length 1")
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type VaultAWSEngineOptions
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
@@ -22,18 +19,9 @@ import (
|
||||
)
|
||||
|
||||
type VaultAWSEngineOptions struct {
|
||||
Name string `mapstructure:"name"`
|
||||
RoleARN string `mapstructure:"role_arn"`
|
||||
// Specifies the TTL for the use of the STS token. This
|
||||
// is specified as a string with a duration suffix. Valid only when
|
||||
// credential_type is assumed_role or federation_token. When not
|
||||
// specified, the default_sts_ttl set for the role will be used. If that
|
||||
// is also not set, then the default value of 3600s will be used. AWS
|
||||
// places limits on the maximum TTL allowed. See the AWS documentation on
|
||||
// the DurationSeconds parameter for AssumeRole (for assumed_role
|
||||
// credential types) and GetFederationToken (for federation_token
|
||||
// credential types) for more details.
|
||||
TTL string `mapstructure:"ttl" required:"false"`
|
||||
Name string `mapstructure:"name"`
|
||||
RoleARN string `mapstructure:"role_arn"`
|
||||
TTL string `mapstructure:"ttl"`
|
||||
EngineName string `mapstructure:"engine_name"`
|
||||
}
|
||||
|
||||
@@ -44,85 +32,19 @@ func (v *VaultAWSEngineOptions) Empty() bool {
|
||||
|
||||
// AccessConfig is for common configuration related to AWS access
|
||||
type AccessConfig struct {
|
||||
// The access key used to communicate with AWS. [Learn how to set this]
|
||||
// (/docs/builders/amazon.html#specifying-amazon-credentials). On EBS, this
|
||||
// is not required if you are using `use_vault_aws_engine` for
|
||||
// authentication instead.
|
||||
AccessKey string `mapstructure:"access_key" required:"true"`
|
||||
// This option is useful if you use a cloud
|
||||
// provider whose API is compatible with aws EC2. Specify another endpoint
|
||||
// like this https://ec2.custom.endpoint.com.
|
||||
CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2" required:"false"`
|
||||
// Enable automatic decoding of any encoded authorization (error) messages
|
||||
// using the `sts:DecodeAuthorizationMessage` API. Note: requires that the
|
||||
// effective user/role have permissions to `sts:DecodeAuthorizationMessage`
|
||||
// on resource `*`. Default `false`.
|
||||
DecodeAuthZMessages bool `mapstructure:"decode_authorization_messages" required:"false"`
|
||||
// This allows skipping TLS
|
||||
// verification of the AWS EC2 endpoint. The default is false.
|
||||
InsecureSkipTLSVerify bool `mapstructure:"insecure_skip_tls_verify" required:"false"`
|
||||
// The MFA
|
||||
// [TOTP](https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm)
|
||||
// code. This should probably be a user variable since it changes all the
|
||||
// time.
|
||||
MFACode string `mapstructure:"mfa_code" required:"false"`
|
||||
// The profile to use in the shared credentials file for
|
||||
// AWS. See Amazon's documentation on [specifying
|
||||
// profiles](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-profiles)
|
||||
// for more details.
|
||||
ProfileName string `mapstructure:"profile" required:"false"`
|
||||
// The name of the region, such as `us-east-1`, in which
|
||||
// to launch the EC2 instance to create the AMI.
|
||||
// When chroot building, this value is guessed from environment.
|
||||
RawRegion string `mapstructure:"region" required:"true"`
|
||||
// The secret key used to communicate with AWS. [Learn how to set
|
||||
// this](amazon.html#specifying-amazon-credentials). This is not required
|
||||
// if you are using `use_vault_aws_engine` for authentication instead.
|
||||
SecretKey string `mapstructure:"secret_key" required:"true"`
|
||||
// Set to true if you want to skip
|
||||
// validation of the ami_regions configuration option. Default false.
|
||||
SkipValidation bool `mapstructure:"skip_region_validation" required:"false"`
|
||||
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
|
||||
// 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
|
||||
// environmental variable.
|
||||
Token string `mapstructure:"token" required:"false"`
|
||||
session *session.Session
|
||||
// Get credentials from Hashicorp Vault's aws secrets engine. You must
|
||||
// already have created a role to use. For more information about
|
||||
// generating credentials via the Vault engine, see the [Vault
|
||||
// docs.](https://www.vaultproject.io/api/secret/aws/index.html#generate-credentials)
|
||||
// If you set this flag, you must also set the below options:
|
||||
// - `name` (string) - Required. Specifies the name of the role to generate
|
||||
// credentials against. This is part of the request URL.
|
||||
// - `engine_name` (string) - The name of the aws secrets engine. In the
|
||||
// Vault docs, this is normally referred to as "aws", and Packer will
|
||||
// default to "aws" if `engine_name` is not set.
|
||||
// - `role_arn` (string)- The ARN of the role to assume if credential\_type
|
||||
// on the Vault role is assumed\_role. Must match one of the allowed role
|
||||
// ARNs in the Vault role. Optional if the Vault role only allows a single
|
||||
// AWS role ARN; required otherwise.
|
||||
// - `ttl` (string) - Specifies the TTL for the use of the STS token. This
|
||||
// is specified as a string with a duration suffix. Valid only when
|
||||
// credential\_type is assumed\_role or federation\_token. When not
|
||||
// specified, the default\_sts\_ttl set for the role will be used. If that
|
||||
// is also not set, then the default value of 3600s will be used. AWS
|
||||
// places limits on the maximum TTL allowed. See the AWS documentation on
|
||||
// the DurationSeconds parameter for AssumeRole (for assumed\_role
|
||||
// credential types) and GetFederationToken (for federation\_token
|
||||
// credential types) for more details.
|
||||
//
|
||||
// ``` json
|
||||
// {
|
||||
// "vault_aws_engine": {
|
||||
// "name": "myrole",
|
||||
// "role_arn": "myarn",
|
||||
// "ttl": "3600s"
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
VaultAWSEngine VaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false"`
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2"`
|
||||
DecodeAuthZMessages bool `mapstructure:"decode_authorization_messages"`
|
||||
InsecureSkipTLSVerify bool `mapstructure:"insecure_skip_tls_verify"`
|
||||
MFACode string `mapstructure:"mfa_code"`
|
||||
ProfileName string `mapstructure:"profile"`
|
||||
RawRegion string `mapstructure:"region"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
SkipValidation bool `mapstructure:"skip_region_validation"`
|
||||
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
|
||||
Token string `mapstructure:"token"`
|
||||
session *session.Session
|
||||
VaultAWSEngine VaultAWSEngineOptions `mapstructure:"vault_aws_engine"`
|
||||
|
||||
getEC2Connection func() ec2iface.EC2API
|
||||
}
|
||||
@@ -256,7 +178,7 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
}
|
||||
|
||||
// Make sure it's obvious from the config how we're getting credentials:
|
||||
// Vault, Packer config, or environment.
|
||||
// Vault, Packer config, or environemnt.
|
||||
if !c.VaultAWSEngine.Empty() {
|
||||
if len(c.AccessKey) > 0 {
|
||||
errs = append(errs,
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// Code generated by "mapstructure-to-hcl2 -type VaultAWSEngineOptions"; DO NOT EDIT.
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatVaultAWSEngineOptions is an auto-generated flat version of VaultAWSEngineOptions.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatVaultAWSEngineOptions struct {
|
||||
Name *string `mapstructure:"name" cty:"name"`
|
||||
RoleARN *string `mapstructure:"role_arn" cty:"role_arn"`
|
||||
TTL *string `mapstructure:"ttl" required:"false" cty:"ttl"`
|
||||
EngineName *string `mapstructure:"engine_name" cty:"engine_name"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatVaultAWSEngineOptions.
|
||||
// FlatVaultAWSEngineOptions is an auto-generated flat version of VaultAWSEngineOptions.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*VaultAWSEngineOptions) FlatMapstructure() interface{} { return new(FlatVaultAWSEngineOptions) }
|
||||
|
||||
// HCL2Spec returns the hcldec.Spec of a FlatVaultAWSEngineOptions.
|
||||
// This spec is used by HCL to read the fields of FlatVaultAWSEngineOptions.
|
||||
func (*FlatVaultAWSEngineOptions) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false},
|
||||
"role_arn": &hcldec.AttrSpec{Name: "role_arn", Type: cty.String, Required: false},
|
||||
"ttl": &hcldec.AttrSpec{Name: "ttl", Type: cty.String, Required: false},
|
||||
"engine_name": &hcldec.AttrSpec{Name: "engine_name", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:generate struct-markdown
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
@@ -7,125 +5,31 @@ import (
|
||||
"log"
|
||||
"regexp"
|
||||
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// AMIConfig is for common configuration related to creating AMIs.
|
||||
type AMIConfig struct {
|
||||
// The name of the resulting AMI that will appear when managing AMIs in the
|
||||
// AWS console or via APIs. This must be unique. To help make this unique,
|
||||
// use a function like timestamp (see [template
|
||||
// engine](../templates/engine.html) for more info).
|
||||
AMIName string `mapstructure:"ami_name" required:"true"`
|
||||
// The description to set for the resulting
|
||||
// AMI(s). By default this description is empty. This is a template
|
||||
// engine, see Build template
|
||||
// data for more information.
|
||||
AMIDescription string `mapstructure:"ami_description" required:"false"`
|
||||
// The type of virtualization for the AMI
|
||||
// you are building. This option is required to register HVM images. Can be
|
||||
// paravirtual (default) or hvm.
|
||||
AMIVirtType string `mapstructure:"ami_virtualization_type" required:"false"`
|
||||
// A list of account IDs that have access to
|
||||
// launch the resulting AMI(s). By default no additional users other than the
|
||||
// user creating the AMI has permissions to launch it.
|
||||
AMIUsers []string `mapstructure:"ami_users" required:"false"`
|
||||
// A list of groups that have access to
|
||||
// launch the resulting AMI(s). By default no groups have permission to launch
|
||||
// the AMI. all will make the AMI publicly accessible.
|
||||
AMIGroups []string `mapstructure:"ami_groups" required:"false"`
|
||||
// A list of product codes to
|
||||
// associate with the AMI. By default no product codes are associated with the
|
||||
// AMI.
|
||||
AMIProductCodes []string `mapstructure:"ami_product_codes" required:"false"`
|
||||
// A list of regions to copy the AMI to.
|
||||
// Tags and attributes are copied along with the AMI. AMI copying takes time
|
||||
// depending on the size of the AMI, but will generally take many minutes.
|
||||
AMIRegions []string `mapstructure:"ami_regions" required:"false"`
|
||||
// Set to true if you want to skip
|
||||
// validation of the ami_regions configuration option. Default false.
|
||||
AMISkipRegionValidation bool `mapstructure:"skip_region_validation" required:"false"`
|
||||
// Tags applied to the AMI. This is a
|
||||
// [template engine](/docs/templates/engine.html), see [Build template
|
||||
// data](#build-template-data) for more information.
|
||||
AMITags TagMap `mapstructure:"tags" required:"false"`
|
||||
// Enable enhanced networking (ENA but not SriovNetSupport) on
|
||||
// HVM-compatible AMIs. If set, add `ec2:ModifyInstanceAttribute` to your
|
||||
// AWS IAM policy.
|
||||
//
|
||||
// Note: you must make sure enhanced networking is enabled on your
|
||||
// instance. See [Amazon's documentation on enabling enhanced
|
||||
// networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking).
|
||||
AMIENASupport config.Trilean `mapstructure:"ena_support" required:"false"`
|
||||
// Enable enhanced networking (SriovNetSupport but not ENA) on
|
||||
// HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute` to your
|
||||
// AWS IAM policy. Note: you must make sure enhanced networking is enabled
|
||||
// on your instance. See [Amazon's documentation on enabling enhanced
|
||||
// networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking).
|
||||
// Default `false`.
|
||||
AMISriovNetSupport bool `mapstructure:"sriov_support" required:"false"`
|
||||
// Force Packer to first deregister an existing
|
||||
// AMI if one with the same name already exists. Default false.
|
||||
AMIForceDeregister bool `mapstructure:"force_deregister" required:"false"`
|
||||
// Force Packer to delete snapshots
|
||||
// associated with AMIs, which have been deregistered by force_deregister.
|
||||
// Default false.
|
||||
AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot" required:"false"`
|
||||
// Whether or not to encrypt the resulting AMI when
|
||||
// copying a provisioned instance to an AMI. By default, Packer will keep the
|
||||
// encryption setting to what it was in the source image. Setting false will
|
||||
// result in an unencrypted image, and true will result in an encrypted one.
|
||||
// If you have used the `launch_block_device_mappings` to set an encryption
|
||||
// key and that key is the same as the one you want the image encrypted with
|
||||
// at the end, then you don't need to set this field; leaving it empty will
|
||||
// prevent an unnecessary extra copy step and save you some time.
|
||||
AMIEncryptBootVolume config.Trilean `mapstructure:"encrypt_boot" required:"false"`
|
||||
// ID, alias or ARN of the KMS key to use for boot volume encryption. This
|
||||
// only applies to the main `region`, other regions where the AMI will be
|
||||
// copied will be encrypted by the default EBS KMS key. For valid formats
|
||||
// see *KmsKeyId* in the [AWS API docs -
|
||||
// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html).
|
||||
// This field is validated by Packer, when using an alias, you will have to
|
||||
// prefix `kms_key_id` with `alias/`.
|
||||
AMIKmsKeyId string `mapstructure:"kms_key_id" required:"false"`
|
||||
// regions to copy the ami to, along with the custom kms key id (alias or
|
||||
// arn) to use for encryption for that region. Keys must match the regions
|
||||
// provided in `ami_regions`. If you just want to encrypt using a default
|
||||
// ID, you can stick with `kms_key_id` and `ami_regions`. If you want a
|
||||
// region to be encrypted with that region's default key ID, you can use an
|
||||
// empty string `""` instead of a key id in this map. (e.g. `"us-east-1":
|
||||
// ""`) However, you cannot use default key IDs if you are using this in
|
||||
// conjunction with `snapshot_users` -- in that situation you must use
|
||||
// custom keys. For valid formats see *KmsKeyId* in the [AWS API docs -
|
||||
// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html).
|
||||
//
|
||||
// This option supercedes the `kms_key_id` option -- if you set both, and
|
||||
// they are different, Packer will respect the value in
|
||||
// `region_kms_key_ids` for your build region and silently disregard the
|
||||
// value provided in `kms_key_id`.
|
||||
AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids" required:"false"`
|
||||
// If true, Packer will not check whether an AMI with the `ami_name` exists
|
||||
// in the region it is building in. It will use an intermediary AMI name,
|
||||
// which it will not convert to an AMI in the build region. It will copy
|
||||
// the intermediary AMI into any regions provided in `ami_regions`, then
|
||||
// delete the intermediary AMI. Default `false`.
|
||||
AMISkipBuildRegion bool `mapstructure:"skip_save_build_region"`
|
||||
// Tags to apply to snapshot.
|
||||
// They will override AMI tags if already applied to snapshot. This is a
|
||||
// [template engine](../templates/engine.html), see [Build template
|
||||
// data](#build-template-data) for more information.
|
||||
SnapshotTags TagMap `mapstructure:"snapshot_tags" required:"false"`
|
||||
// A list of account IDs that have
|
||||
// access to create volumes from the snapshot(s). By default no additional
|
||||
// users other than the user creating the AMI has permissions to create
|
||||
// volumes from the backing snapshot(s).
|
||||
SnapshotUsers []string `mapstructure:"snapshot_users" required:"false"`
|
||||
// A list of groups that have access to
|
||||
// create volumes from the snapshot(s). By default no groups have permission
|
||||
// to create volumes from the snapshot(s). all will make the snapshot
|
||||
// publicly accessible.
|
||||
SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false"`
|
||||
AMIName string `mapstructure:"ami_name"`
|
||||
AMIDescription string `mapstructure:"ami_description"`
|
||||
AMIVirtType string `mapstructure:"ami_virtualization_type"`
|
||||
AMIUsers []string `mapstructure:"ami_users"`
|
||||
AMIGroups []string `mapstructure:"ami_groups"`
|
||||
AMIProductCodes []string `mapstructure:"ami_product_codes"`
|
||||
AMIRegions []string `mapstructure:"ami_regions"`
|
||||
AMISkipRegionValidation bool `mapstructure:"skip_region_validation"`
|
||||
AMITags TagMap `mapstructure:"tags"`
|
||||
AMIENASupport *bool `mapstructure:"ena_support"`
|
||||
AMISriovNetSupport bool `mapstructure:"sriov_support"`
|
||||
AMIForceDeregister bool `mapstructure:"force_deregister"`
|
||||
AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot"`
|
||||
AMIEncryptBootVolume *bool `mapstructure:"encrypt_boot"`
|
||||
AMIKmsKeyId string `mapstructure:"kms_key_id"`
|
||||
AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids"`
|
||||
SnapshotTags TagMap `mapstructure:"snapshot_tags"`
|
||||
SnapshotUsers []string `mapstructure:"snapshot_users"`
|
||||
SnapshotGroups []string `mapstructure:"snapshot_groups"`
|
||||
AMISkipBuildRegion bool `mapstructure:"skip_save_build_region"`
|
||||
}
|
||||
|
||||
func stringInSlice(s []string, searchstr string) bool {
|
||||
@@ -158,7 +62,7 @@ func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context
|
||||
|
||||
// Prevent sharing of default KMS key encrypted volumes with other aws users
|
||||
if len(c.AMIUsers) > 0 {
|
||||
if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume.True() {
|
||||
if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume != nil && *c.AMIEncryptBootVolume {
|
||||
errs = append(errs, fmt.Errorf("Cannot share AMI encrypted with default KMS key"))
|
||||
}
|
||||
if len(c.AMIRegionKMSKeyIDs) > 0 {
|
||||
@@ -170,23 +74,17 @@ func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context
|
||||
}
|
||||
}
|
||||
|
||||
kmsKeys := make([]string, 0)
|
||||
var kmsKeys []string
|
||||
if len(c.AMIKmsKeyId) > 0 {
|
||||
kmsKeys = append(kmsKeys, c.AMIKmsKeyId)
|
||||
}
|
||||
if len(c.AMIRegionKMSKeyIDs) > 0 {
|
||||
for _, kmsKey := range c.AMIRegionKMSKeyIDs {
|
||||
if len(kmsKey) > 0 {
|
||||
kmsKeys = append(kmsKeys, kmsKey)
|
||||
if len(kmsKey) == 0 {
|
||||
kmsKeys = append(kmsKeys, c.AMIKmsKeyId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(kmsKeys) > 0 && !c.AMIEncryptBootVolume.True() {
|
||||
errs = append(errs, fmt.Errorf("If you have set either "+
|
||||
"region_kms_key_ids or kms_key_id, encrypt_boot must also be true."))
|
||||
|
||||
}
|
||||
for _, kmsKey := range kmsKeys {
|
||||
if !validateKmsKey(kmsKey) {
|
||||
errs = append(errs, fmt.Errorf("%s is not a valid KMS Key Id.", kmsKey))
|
||||
@@ -194,9 +92,8 @@ func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context
|
||||
}
|
||||
|
||||
if len(c.SnapshotUsers) > 0 {
|
||||
if len(c.AMIKmsKeyId) == 0 && len(c.AMIRegionKMSKeyIDs) == 0 && c.AMIEncryptBootVolume.True() {
|
||||
errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted "+
|
||||
"with default KMS key, see https://www.packer.io/docs/builders/amazon-ebs.html#region_kms_key_ids for more information"))
|
||||
if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume != nil && *c.AMIEncryptBootVolume {
|
||||
errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
|
||||
}
|
||||
if len(c.AMIRegionKMSKeyIDs) > 0 {
|
||||
for _, kmsKey := range c.AMIRegionKMSKeyIDs {
|
||||
|
||||
@@ -8,7 +8,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/helper/config"
|
||||
)
|
||||
|
||||
func testAMIConfig() *AMIConfig {
|
||||
@@ -69,6 +68,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatalf("shouldn't have err: %#v", errs)
|
||||
}
|
||||
errs = errs[:0]
|
||||
|
||||
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-1"}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
@@ -138,7 +138,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
|
||||
c.SnapshotUsers = []string{"foo", "bar"}
|
||||
c.AMIKmsKeyId = "123-abc-456"
|
||||
c.AMIEncryptBootVolume = config.TriTrue
|
||||
c.AMIEncryptBootVolume = &[]bool{true}[0]
|
||||
c.AMIRegions = []string{"us-east-1", "us-west-1"}
|
||||
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||
"us-east-1": "123-456-7890",
|
||||
@@ -161,7 +161,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
|
||||
c := testAMIConfig()
|
||||
c.AMIUsers = []string{"testAccountID"}
|
||||
c.AMIEncryptBootVolume = config.TriTrue
|
||||
c.AMIEncryptBootVolume = &[]bool{true}[0]
|
||||
|
||||
accessConf := testAccessConfig()
|
||||
|
||||
@@ -177,7 +177,7 @@ func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
|
||||
|
||||
func TestAMIConfigPrepare_ValidateKmsKey(t *testing.T) {
|
||||
c := testAMIConfig()
|
||||
c.AMIEncryptBootVolume = config.TriTrue
|
||||
c.AMIEncryptBootVolume = aws.Bool(true)
|
||||
|
||||
accessConf := testAccessConfig()
|
||||
|
||||
|
||||
@@ -85,10 +85,15 @@ func (a *Artifact) Destroy() error {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
err = DestroyAMIs([]*string{&imageId}, regionConn)
|
||||
if err != nil {
|
||||
// Deregister ami
|
||||
input := &ec2.DeregisterImageInput{
|
||||
ImageId: &imageId,
|
||||
}
|
||||
if _, err := regionConn.DeregisterImage(input); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
// TODO(mitchellh): Delete the snapshots associated with an AMI too
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type BlockDevice
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
@@ -9,156 +6,130 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// These will be attached when booting a new instance from your AMI. Your
|
||||
// options here may vary depending on the type of VM you use.
|
||||
//
|
||||
// Example use case:
|
||||
//
|
||||
// The following mapping will tell Packer to encrypt the root volume of the
|
||||
// build instance at launch using a specific non-default kms key:
|
||||
//
|
||||
// ``` json
|
||||
// "launch_block_device_mappings": [{
|
||||
// "device_name": "/dev/sda1",
|
||||
// "encrypted": true,
|
||||
// "kms_key_id": "1a2b3c4d-5e6f-1a2b-3c4d-5e6f1a2b3c4d"
|
||||
// }]
|
||||
// ```
|
||||
//
|
||||
// Documentation for Block Devices Mappings can be found here:
|
||||
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html
|
||||
//
|
||||
// These mappings give you control over either the volumes generated for the
|
||||
// Packer build via `launch_block_device_mappings`, or the volumes that Packer will
|
||||
// save with the artifact AMI via `ami_block_device_mappings`.
|
||||
// BlockDevice
|
||||
type BlockDevice struct {
|
||||
// Indicates whether the EBS volume is deleted on instance termination.
|
||||
// Default false. NOTE: If this value is not explicitly set to true and
|
||||
// volumes are not cleaned up by an alternative method, additional volumes
|
||||
// will accumulate after every build.
|
||||
DeleteOnTermination bool `mapstructure:"delete_on_termination" required:"false"`
|
||||
// The device name exposed to the instance (for example, /dev/sdh or xvdh).
|
||||
// Required for every device in the block device mapping.
|
||||
DeviceName string `mapstructure:"device_name" required:"false"`
|
||||
// Indicates whether or not to encrypt the volume. By default, Packer will
|
||||
// keep the encryption setting to what it was in the source image. Setting
|
||||
// false will result in an unencrypted device, and true will result in an
|
||||
// encrypted one.
|
||||
Encrypted config.Trilean `mapstructure:"encrypted" required:"false"`
|
||||
// The number of I/O operations per second (IOPS) that the volume supports.
|
||||
// See the documentation on
|
||||
// [IOPs](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_EbsBlockDevice.html)
|
||||
// for more information
|
||||
IOPS int64 `mapstructure:"iops" required:"false"`
|
||||
// Suppresses the specified device included in the block device mapping of
|
||||
// the AMI.
|
||||
NoDevice bool `mapstructure:"no_device" required:"false"`
|
||||
// The ID of the snapshot.
|
||||
SnapshotId string `mapstructure:"snapshot_id" required:"false"`
|
||||
// The virtual device name. See the documentation on Block Device Mapping
|
||||
// for more information.
|
||||
VirtualName string `mapstructure:"virtual_name" required:"false"`
|
||||
// The volume type. gp2 for General Purpose (SSD) volumes, io1 for
|
||||
// Provisioned IOPS (SSD) volumes, st1 for Throughput Optimized HDD, sc1
|
||||
// for Cold HDD, and standard for Magnetic volumes.
|
||||
VolumeType string `mapstructure:"volume_type" required:"false"`
|
||||
// The size of the volume, in GiB. Required if not specifying a
|
||||
// snapshot_id.
|
||||
VolumeSize int64 `mapstructure:"volume_size" required:"false"`
|
||||
// ID, alias or ARN of the KMS key to use for boot volume encryption. This
|
||||
// only applies to the main region, other regions where the AMI will be
|
||||
// copied will be encrypted by the default EBS KMS key. For valid formats
|
||||
// see KmsKeyId in the [AWS API docs -
|
||||
// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html)
|
||||
// This field is validated by Packer, when using an alias, you will have to
|
||||
// prefix kms_key_id with alias/.
|
||||
KmsKeyId string `mapstructure:"kms_key_id" required:"false"`
|
||||
DeleteOnTermination bool `mapstructure:"delete_on_termination"`
|
||||
DeviceName string `mapstructure:"device_name"`
|
||||
Encrypted *bool `mapstructure:"encrypted"`
|
||||
IOPS int64 `mapstructure:"iops"`
|
||||
NoDevice bool `mapstructure:"no_device"`
|
||||
SnapshotId string `mapstructure:"snapshot_id"`
|
||||
VirtualName string `mapstructure:"virtual_name"`
|
||||
VolumeType string `mapstructure:"volume_type"`
|
||||
VolumeSize int64 `mapstructure:"volume_size"`
|
||||
KmsKeyId string `mapstructure:"kms_key_id"`
|
||||
// ebssurrogate only
|
||||
OmitFromArtifact bool `mapstructure:"omit_from_artifact"`
|
||||
}
|
||||
|
||||
type BlockDevices []BlockDevice
|
||||
type BlockDevices struct {
|
||||
AMIBlockDevices `mapstructure:",squash"`
|
||||
LaunchBlockDevices `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
func (bds BlockDevices) BuildEC2BlockDeviceMappings() []*ec2.BlockDeviceMapping {
|
||||
type AMIBlockDevices struct {
|
||||
AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings"`
|
||||
}
|
||||
|
||||
type LaunchBlockDevices struct {
|
||||
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings"`
|
||||
}
|
||||
|
||||
func buildBlockDevices(b []BlockDevice) []*ec2.BlockDeviceMapping {
|
||||
var blockDevices []*ec2.BlockDeviceMapping
|
||||
|
||||
for _, blockDevice := range bds {
|
||||
blockDevices = append(blockDevices, blockDevice.BuildEC2BlockDeviceMapping())
|
||||
for _, blockDevice := range b {
|
||||
mapping := &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String(blockDevice.DeviceName),
|
||||
}
|
||||
|
||||
if blockDevice.NoDevice {
|
||||
mapping.NoDevice = aws.String("")
|
||||
} else if blockDevice.VirtualName != "" {
|
||||
if strings.HasPrefix(blockDevice.VirtualName, "ephemeral") {
|
||||
mapping.VirtualName = aws.String(blockDevice.VirtualName)
|
||||
}
|
||||
} else {
|
||||
ebsBlockDevice := &ec2.EbsBlockDevice{
|
||||
DeleteOnTermination: aws.Bool(blockDevice.DeleteOnTermination),
|
||||
}
|
||||
|
||||
if blockDevice.VolumeType != "" {
|
||||
ebsBlockDevice.VolumeType = aws.String(blockDevice.VolumeType)
|
||||
}
|
||||
|
||||
if blockDevice.VolumeSize > 0 {
|
||||
ebsBlockDevice.VolumeSize = aws.Int64(blockDevice.VolumeSize)
|
||||
}
|
||||
|
||||
// IOPS is only valid for io1 type
|
||||
if blockDevice.VolumeType == "io1" {
|
||||
ebsBlockDevice.Iops = aws.Int64(blockDevice.IOPS)
|
||||
}
|
||||
|
||||
// You cannot specify Encrypted if you specify a Snapshot ID
|
||||
if blockDevice.SnapshotId != "" {
|
||||
ebsBlockDevice.SnapshotId = aws.String(blockDevice.SnapshotId)
|
||||
}
|
||||
ebsBlockDevice.Encrypted = blockDevice.Encrypted
|
||||
|
||||
if blockDevice.KmsKeyId != "" {
|
||||
ebsBlockDevice.KmsKeyId = aws.String(blockDevice.KmsKeyId)
|
||||
}
|
||||
|
||||
mapping.Ebs = ebsBlockDevice
|
||||
}
|
||||
|
||||
blockDevices = append(blockDevices, mapping)
|
||||
}
|
||||
return blockDevices
|
||||
}
|
||||
|
||||
func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapping {
|
||||
|
||||
mapping := &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String(blockDevice.DeviceName),
|
||||
}
|
||||
|
||||
if blockDevice.NoDevice {
|
||||
mapping.NoDevice = aws.String("")
|
||||
return mapping
|
||||
} else if blockDevice.VirtualName != "" {
|
||||
if strings.HasPrefix(blockDevice.VirtualName, "ephemeral") {
|
||||
mapping.VirtualName = aws.String(blockDevice.VirtualName)
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
|
||||
ebsBlockDevice := &ec2.EbsBlockDevice{
|
||||
DeleteOnTermination: aws.Bool(blockDevice.DeleteOnTermination),
|
||||
}
|
||||
|
||||
if blockDevice.VolumeType != "" {
|
||||
ebsBlockDevice.VolumeType = aws.String(blockDevice.VolumeType)
|
||||
}
|
||||
|
||||
if blockDevice.VolumeSize > 0 {
|
||||
ebsBlockDevice.VolumeSize = aws.Int64(blockDevice.VolumeSize)
|
||||
}
|
||||
|
||||
// IOPS is only valid for io1 type
|
||||
if blockDevice.VolumeType == "io1" {
|
||||
ebsBlockDevice.Iops = aws.Int64(blockDevice.IOPS)
|
||||
}
|
||||
|
||||
// You cannot specify Encrypted if you specify a Snapshot ID
|
||||
if blockDevice.SnapshotId != "" {
|
||||
ebsBlockDevice.SnapshotId = aws.String(blockDevice.SnapshotId)
|
||||
}
|
||||
ebsBlockDevice.Encrypted = blockDevice.Encrypted.ToBoolPointer()
|
||||
|
||||
if blockDevice.KmsKeyId != "" {
|
||||
ebsBlockDevice.KmsKeyId = aws.String(blockDevice.KmsKeyId)
|
||||
}
|
||||
|
||||
mapping.Ebs = ebsBlockDevice
|
||||
|
||||
return mapping
|
||||
}
|
||||
|
||||
func (b *BlockDevice) Prepare(ctx *interpolate.Context) error {
|
||||
if b.DeviceName == "" {
|
||||
return fmt.Errorf("The `device_name` must be specified " +
|
||||
"for every device in the block device mapping.")
|
||||
}
|
||||
|
||||
// Warn that encrypted must be true or nil when setting kms_key_id
|
||||
if b.KmsKeyId != "" && b.Encrypted.False() {
|
||||
// Warn that encrypted must be true when setting kms_key_id
|
||||
if b.KmsKeyId != "" && b.Encrypted != nil && *b.Encrypted == false {
|
||||
return fmt.Errorf("The device %v, must also have `encrypted: "+
|
||||
"true` when setting a kms_key_id.", b.DeviceName)
|
||||
}
|
||||
|
||||
_, err := interpolate.RenderInterface(&b, ctx)
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bds BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
|
||||
for _, block := range bds {
|
||||
if err := block.Prepare(ctx); err != nil {
|
||||
errs = append(errs, err)
|
||||
func (b *BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
|
||||
for _, d := range b.AMIMappings {
|
||||
if err := d.Prepare(ctx); err != nil {
|
||||
errs = append(errs, fmt.Errorf("AMIMapping: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
for _, d := range b.LaunchMappings {
|
||||
if err := d.Prepare(ctx); err != nil {
|
||||
errs = append(errs, fmt.Errorf("LaunchMapping: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func (b *AMIBlockDevices) BuildAMIDevices() []*ec2.BlockDeviceMapping {
|
||||
return buildBlockDevices(b.AMIMappings)
|
||||
}
|
||||
|
||||
func (b *LaunchBlockDevices) BuildLaunchDevices() []*ec2.BlockDeviceMapping {
|
||||
return buildBlockDevices(b.LaunchMappings)
|
||||
}
|
||||
|
||||
func (b *LaunchBlockDevices) GetOmissions() map[string]bool {
|
||||
omitMap := make(map[string]bool)
|
||||
|
||||
for _, blockDevice := range b.LaunchMappings {
|
||||
omitMap[blockDevice.DeviceName] = blockDevice.OmitFromArtifact
|
||||
}
|
||||
|
||||
return omitMap
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
// Code generated by "mapstructure-to-hcl2 -type BlockDevice"; DO NOT EDIT.
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatBlockDevice is an auto-generated flat version of BlockDevice.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatBlockDevice struct {
|
||||
DeleteOnTermination *bool `mapstructure:"delete_on_termination" required:"false" cty:"delete_on_termination"`
|
||||
DeviceName *string `mapstructure:"device_name" required:"false" cty:"device_name"`
|
||||
Encrypted *bool `mapstructure:"encrypted" required:"false" cty:"encrypted"`
|
||||
IOPS *int64 `mapstructure:"iops" required:"false" cty:"iops"`
|
||||
NoDevice *bool `mapstructure:"no_device" required:"false" cty:"no_device"`
|
||||
SnapshotId *string `mapstructure:"snapshot_id" required:"false" cty:"snapshot_id"`
|
||||
VirtualName *string `mapstructure:"virtual_name" required:"false" cty:"virtual_name"`
|
||||
VolumeType *string `mapstructure:"volume_type" required:"false" cty:"volume_type"`
|
||||
VolumeSize *int64 `mapstructure:"volume_size" required:"false" cty:"volume_size"`
|
||||
KmsKeyId *string `mapstructure:"kms_key_id" required:"false" cty:"kms_key_id"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatBlockDevice.
|
||||
// FlatBlockDevice is an auto-generated flat version of BlockDevice.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*BlockDevice) FlatMapstructure() interface{} { return new(FlatBlockDevice) }
|
||||
|
||||
// HCL2Spec returns the hcldec.Spec of a FlatBlockDevice.
|
||||
// This spec is used by HCL to read the fields of FlatBlockDevice.
|
||||
func (*FlatBlockDevice) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"delete_on_termination": &hcldec.AttrSpec{Name: "delete_on_termination", Type: cty.Bool, Required: false},
|
||||
"device_name": &hcldec.AttrSpec{Name: "device_name", Type: cty.String, Required: false},
|
||||
"encrypted": &hcldec.AttrSpec{Name: "encrypted", Type: cty.Bool, Required: false},
|
||||
"iops": &hcldec.AttrSpec{Name: "iops", Type: cty.Number, Required: false},
|
||||
"no_device": &hcldec.AttrSpec{Name: "no_device", Type: cty.Bool, Required: false},
|
||||
"snapshot_id": &hcldec.AttrSpec{Name: "snapshot_id", Type: cty.String, Required: false},
|
||||
"virtual_name": &hcldec.AttrSpec{Name: "virtual_name", Type: cty.String, Required: false},
|
||||
"volume_type": &hcldec.AttrSpec{Name: "volume_type", Type: cty.String, Required: false},
|
||||
"volume_size": &hcldec.AttrSpec{Name: "volume_size", Type: cty.Number, Required: false},
|
||||
"kms_key_id": &hcldec.AttrSpec{Name: "kms_key_id", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
)
|
||||
|
||||
func TestBlockDevice(t *testing.T) {
|
||||
@@ -72,7 +71,7 @@ func TestBlockDevice(t *testing.T) {
|
||||
VolumeType: "gp2",
|
||||
VolumeSize: 8,
|
||||
DeleteOnTermination: true,
|
||||
Encrypted: config.TriTrue,
|
||||
Encrypted: aws.Bool(true),
|
||||
},
|
||||
|
||||
Result: &ec2.BlockDeviceMapping{
|
||||
@@ -91,7 +90,7 @@ func TestBlockDevice(t *testing.T) {
|
||||
VolumeType: "gp2",
|
||||
VolumeSize: 8,
|
||||
DeleteOnTermination: true,
|
||||
Encrypted: config.TriTrue,
|
||||
Encrypted: aws.Bool(true),
|
||||
KmsKeyId: "2Fa48a521f-3aff-4b34-a159-376ac5d37812",
|
||||
},
|
||||
|
||||
@@ -146,20 +145,26 @@ func TestBlockDevice(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
var amiBlockDevices BlockDevices = []BlockDevice{*tc.Config}
|
||||
amiBlockDevices := AMIBlockDevices{
|
||||
AMIMappings: []BlockDevice{*tc.Config},
|
||||
}
|
||||
|
||||
var launchBlockDevices BlockDevices = []BlockDevice{*tc.Config}
|
||||
launchBlockDevices := LaunchBlockDevices{
|
||||
LaunchMappings: []BlockDevice{*tc.Config},
|
||||
}
|
||||
|
||||
expected := []*ec2.BlockDeviceMapping{tc.Result}
|
||||
|
||||
amiResults := amiBlockDevices.BuildEC2BlockDeviceMappings()
|
||||
if diff := cmp.Diff(expected, amiResults); diff != "" {
|
||||
t.Fatalf("Bad block device: %s", diff)
|
||||
amiResults := amiBlockDevices.BuildAMIDevices()
|
||||
if !reflect.DeepEqual(expected, amiResults) {
|
||||
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
|
||||
expected, amiResults)
|
||||
}
|
||||
|
||||
launchResults := launchBlockDevices.BuildEC2BlockDeviceMappings()
|
||||
if diff := cmp.Diff(expected, launchResults); diff != "" {
|
||||
t.Fatalf("Bad block device: %s", diff)
|
||||
launchResults := launchBlockDevices.BuildLaunchDevices()
|
||||
if !reflect.DeepEqual(expected, launchResults) {
|
||||
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
|
||||
expected, launchResults)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,12 @@ import (
|
||||
)
|
||||
|
||||
// Build a slice of EC2 (AMI/Subnet/VPC) filter options from the filters provided.
|
||||
func buildEc2Filters(input map[string]string) []*ec2.Filter {
|
||||
func buildEc2Filters(input map[*string]*string) []*ec2.Filter {
|
||||
var filters []*ec2.Filter
|
||||
for k, v := range input {
|
||||
a := k
|
||||
b := v
|
||||
filters = append(filters, &ec2.Filter{
|
||||
Name: &a,
|
||||
Values: []*string{&b},
|
||||
Name: k,
|
||||
Values: []*string{v},
|
||||
})
|
||||
}
|
||||
return filters
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStepSourceAmiInfo_BuildFilter(t *testing.T) {
|
||||
filter_key := "name"
|
||||
filter_value := "foo"
|
||||
filter_key2 := "name2"
|
||||
filter_value2 := "foo2"
|
||||
|
||||
inputFilter := map[string]string{filter_key: filter_value, filter_key2: filter_value2}
|
||||
outputFilter := buildEc2Filters(inputFilter)
|
||||
|
||||
// deconstruct filter back into things we can test
|
||||
foundMap := map[string]bool{filter_key: false, filter_key2: false}
|
||||
for _, filter := range outputFilter {
|
||||
for key, value := range inputFilter {
|
||||
if *filter.Name == key && *filter.Values[0] == value {
|
||||
foundMap[key] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range foundMap {
|
||||
if !v {
|
||||
t.Fatalf("Fail: should have found value for key: %s", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ type stsDecoder interface {
|
||||
func decodeAWSError(decoder stsDecoder, err error) error {
|
||||
|
||||
groups := encodedFailureMessagePattern.FindStringSubmatch(err.Error())
|
||||
if len(groups) > 1 {
|
||||
if groups != nil && len(groups) > 1 {
|
||||
result, decodeErr := decoder.DecodeAuthorizationMessage(&sts.DecodeAuthorizationMessageInput{
|
||||
EncodedMessage: aws.String(groups[2]),
|
||||
})
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
)
|
||||
|
||||
func DestroyAMIs(imageids []*string, ec2conn *ec2.EC2) error {
|
||||
resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: imageids,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error describing AMI: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Deregister image by name.
|
||||
for _, i := range resp.Images {
|
||||
_, err := ec2conn.DeregisterImage(&ec2.DeregisterImageInput{
|
||||
ImageId: i.ImageId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error deregistering existing AMI: %s", err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Deregistered AMI id: %s", *i.ImageId)
|
||||
|
||||
// Delete snapshot(s) by image
|
||||
for _, b := range i.BlockDeviceMappings {
|
||||
if b.Ebs != nil && aws.StringValue(b.Ebs.SnapshotId) != "" {
|
||||
_, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{
|
||||
SnapshotId: b.Ebs.SnapshotId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error deleting existing snapshot: %s", err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Deleted snapshot: %s", *b.Ebs.SnapshotId)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type AmiFilterOptions,SecurityGroupFilterOptions,SubnetFilterOptions,VpcFilterOptions,PolicyDocument,Statement
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
@@ -19,19 +16,11 @@ import (
|
||||
var reShutdownBehavior = regexp.MustCompile("^(stop|terminate)$")
|
||||
|
||||
type AmiFilterOptions struct {
|
||||
Filters map[string]string
|
||||
Owners []string
|
||||
Filters map[*string]*string
|
||||
Owners []*string
|
||||
MostRecent bool `mapstructure:"most_recent"`
|
||||
}
|
||||
|
||||
func (d *AmiFilterOptions) GetOwners() []*string {
|
||||
res := make([]*string, 0, len(d.Owners))
|
||||
for _, owner := range d.Owners {
|
||||
res = append(res, &owner)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (d *AmiFilterOptions) Empty() bool {
|
||||
return len(d.Owners) == 0 && len(d.Filters) == 0
|
||||
}
|
||||
@@ -41,7 +30,7 @@ func (d *AmiFilterOptions) NoOwner() bool {
|
||||
}
|
||||
|
||||
type SubnetFilterOptions struct {
|
||||
Filters map[string]string
|
||||
Filters map[*string]*string
|
||||
MostFree bool `mapstructure:"most_free"`
|
||||
Random bool `mapstructure:"random"`
|
||||
}
|
||||
@@ -51,18 +40,7 @@ func (d *SubnetFilterOptions) Empty() bool {
|
||||
}
|
||||
|
||||
type VpcFilterOptions struct {
|
||||
Filters map[string]string
|
||||
}
|
||||
|
||||
type Statement struct {
|
||||
Effect string
|
||||
Action []string
|
||||
Resource string
|
||||
}
|
||||
|
||||
type PolicyDocument struct {
|
||||
Version string
|
||||
Statement []Statement
|
||||
Filters map[*string]*string
|
||||
}
|
||||
|
||||
func (d *VpcFilterOptions) Empty() bool {
|
||||
@@ -70,7 +48,7 @@ func (d *VpcFilterOptions) Empty() bool {
|
||||
}
|
||||
|
||||
type SecurityGroupFilterOptions struct {
|
||||
Filters map[string]string
|
||||
Filters map[*string]*string
|
||||
}
|
||||
|
||||
func (d *SecurityGroupFilterOptions) Empty() bool {
|
||||
@@ -80,302 +58,37 @@ func (d *SecurityGroupFilterOptions) Empty() bool {
|
||||
// RunConfig contains configuration for running an instance from a source
|
||||
// AMI and details on how to access that launched image.
|
||||
type RunConfig struct {
|
||||
// If using a non-default VPC,
|
||||
// public IP addresses are not provided by default. If this is true, your
|
||||
// new instance will get a Public IP. default: false
|
||||
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address" required:"false"`
|
||||
// Destination availability zone to launch
|
||||
// instance in. Leave this empty to allow Amazon to auto-assign.
|
||||
AvailabilityZone string `mapstructure:"availability_zone" required:"false"`
|
||||
// Requires spot_price to be set. The
|
||||
// required duration for the Spot Instances (also known as Spot blocks). This
|
||||
// value must be a multiple of 60 (60, 120, 180, 240, 300, or 360). You can't
|
||||
// specify an Availability Zone group or a launch group if you specify a
|
||||
// duration.
|
||||
BlockDurationMinutes int64 `mapstructure:"block_duration_minutes" required:"false"`
|
||||
// Packer normally stops the build instance after all provisioners have
|
||||
// run. For Windows instances, it is sometimes desirable to [run
|
||||
// Sysprep](http://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ami-create-standard.html)
|
||||
// which will stop the instance for you. If this is set to `true`, Packer
|
||||
// *will not* stop the instance but will assume that you will send the stop
|
||||
// signal yourself through your final provisioner. You can do this with a
|
||||
// [windows-shell
|
||||
// provisioner](https://www.packer.io/docs/provisioners/windows-shell.html).
|
||||
// Note that Packer will still wait for the instance to be stopped, and
|
||||
// failing to send the stop signal yourself, when you have set this flag to
|
||||
// `true`, will cause a timeout.
|
||||
// Example of a valid shutdown command:
|
||||
//
|
||||
// ``` json
|
||||
// {
|
||||
// "type": "windows-shell",
|
||||
// "inline": ["\"c:\\Program Files\\Amazon\\Ec2ConfigService\\ec2config.exe\" -sysprep"]
|
||||
// }
|
||||
// ```
|
||||
DisableStopInstance bool `mapstructure:"disable_stop_instance" required:"false"`
|
||||
// Mark instance as [EBS
|
||||
// Optimized](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSOptimized.html).
|
||||
// Default `false`.
|
||||
EbsOptimized bool `mapstructure:"ebs_optimized" required:"false"`
|
||||
// Enabling T2 Unlimited allows the source instance to burst additional CPU
|
||||
// beyond its available [CPU
|
||||
// Credits](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-credits-baseline-concepts.html)
|
||||
// for as long as the demand exists. This is in contrast to the standard
|
||||
// configuration that only allows an instance to consume up to its
|
||||
// available CPU Credits. See the AWS documentation for [T2
|
||||
// Unlimited](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-unlimited.html)
|
||||
// and the **T2 Unlimited Pricing** section of the [Amazon EC2 On-Demand
|
||||
// Pricing](https://aws.amazon.com/ec2/pricing/on-demand/) document for
|
||||
// more information. By default this option is disabled and Packer will set
|
||||
// up a [T2
|
||||
// Standard](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-std.html)
|
||||
// instance instead.
|
||||
//
|
||||
// To use T2 Unlimited you must use a T2 instance type, e.g. `t2.micro`.
|
||||
// Additionally, T2 Unlimited cannot be used in conjunction with Spot
|
||||
// Instances, e.g. when the `spot_price` option has been configured.
|
||||
// Attempting to do so will cause an error.
|
||||
//
|
||||
// !> **Warning!** Additional costs may be incurred by enabling T2
|
||||
// Unlimited - even for instances that would usually qualify for the
|
||||
// [AWS Free Tier](https://aws.amazon.com/free/).
|
||||
EnableT2Unlimited bool `mapstructure:"enable_t2_unlimited" required:"false"`
|
||||
// The name of an [IAM instance
|
||||
// profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html)
|
||||
// to launch the EC2 instance with.
|
||||
IamInstanceProfile string `mapstructure:"iam_instance_profile" required:"false"`
|
||||
// Temporary IAM instance profile policy document
|
||||
// If IamInstanceProfile is specified it will be used instead. Example:
|
||||
//
|
||||
// ```json
|
||||
//{
|
||||
// "Version": "2012-10-17",
|
||||
// "Statement": [
|
||||
// {
|
||||
// "Action": [
|
||||
// "logs:*"
|
||||
// ],
|
||||
// "Effect": "Allow",
|
||||
// "Resource": "*"
|
||||
// }
|
||||
// ]
|
||||
//}
|
||||
// ```
|
||||
//
|
||||
TemporaryIamInstanceProfilePolicyDocument *PolicyDocument `mapstructure:"temporary_iam_instance_profile_policy_document" required:"false"`
|
||||
// Automatically terminate instances on
|
||||
// shutdown in case Packer exits ungracefully. Possible values are stop and
|
||||
// terminate. Defaults to stop.
|
||||
InstanceInitiatedShutdownBehavior string `mapstructure:"shutdown_behavior" required:"false"`
|
||||
// The EC2 instance type to use while building the
|
||||
// AMI, such as t2.small.
|
||||
InstanceType string `mapstructure:"instance_type" required:"true"`
|
||||
// Filters used to populate the `security_group_ids` field. Example:
|
||||
//
|
||||
// ``` json
|
||||
// {
|
||||
// "security_group_filter": {
|
||||
// "filters": {
|
||||
// "tag:Class": "packer"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// This selects the SG's with tag `Class` with the value `packer`.
|
||||
//
|
||||
// - `filters` (map of strings) - filters used to select a
|
||||
// `security_group_ids`. Any filter described in the docs for
|
||||
// [DescribeSecurityGroups](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSecurityGroups.html)
|
||||
// is valid.
|
||||
//
|
||||
// `security_group_ids` take precedence over this.
|
||||
SecurityGroupFilter SecurityGroupFilterOptions `mapstructure:"security_group_filter" required:"false"`
|
||||
// Tags to apply to the instance that is that is *launched* to create the
|
||||
// EBS volumes. This is a [template engine](/docs/templates/engine.html),
|
||||
// see [Build template data](#build-template-data) for more information.
|
||||
RunTags map[string]string `mapstructure:"run_tags" required:"false"`
|
||||
// The ID (not the name) of the security
|
||||
// group to assign to the instance. By default this is not set and Packer will
|
||||
// automatically create a new temporary security group to allow SSH access.
|
||||
// Note that if this is specified, you must be sure the security group allows
|
||||
// access to the ssh_port given below.
|
||||
SecurityGroupId string `mapstructure:"security_group_id" required:"false"`
|
||||
// A list of security groups as
|
||||
// described above. Note that if this is specified, you must omit the
|
||||
// security_group_id.
|
||||
SecurityGroupIds []string `mapstructure:"security_group_ids" required:"false"`
|
||||
// The source AMI whose root volume will be copied and
|
||||
// provisioned on the currently running instance. This must be an EBS-backed
|
||||
// AMI with a root volume snapshot that you have access to. Note: this is not
|
||||
// used when from_scratch is set to true.
|
||||
SourceAmi string `mapstructure:"source_ami" required:"true"`
|
||||
// Filters used to populate the `source_ami`
|
||||
// field. Example:
|
||||
//
|
||||
// ``` json
|
||||
// {
|
||||
// "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
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// This selects the most recent Ubuntu 16.04 HVM EBS AMI from Canonical. NOTE:
|
||||
// This will fail unless *exactly* one AMI is returned. In the above example,
|
||||
// `most_recent` will cause this to succeed by selecting the newest image.
|
||||
//
|
||||
// - `filters` (map of strings) - filters used to select a `source_ami`.
|
||||
// NOTE: This will fail unless *exactly* one AMI is returned. Any filter
|
||||
// described in the docs for
|
||||
// [DescribeImages](http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeImages.html)
|
||||
// is valid.
|
||||
//
|
||||
// - `owners` (array of strings) - Filters the images by their owner. You
|
||||
// may specify one or more AWS account IDs, "self" (which will use the
|
||||
// account whose credentials you are using to run Packer), or an AWS owner
|
||||
// alias: for example, `amazon`, `aws-marketplace`, or `microsoft`. This
|
||||
// option is required for security reasons.
|
||||
//
|
||||
// - `most_recent` (boolean) - Selects the newest created image when true.
|
||||
// This is most useful for selecting a daily distro build.
|
||||
//
|
||||
// You may set this in place of `source_ami` or in conjunction with it. If you
|
||||
// set this in conjunction with `source_ami`, the `source_ami` will be added
|
||||
// to the filter. The provided `source_ami` must meet all of the filtering
|
||||
// criteria provided in `source_ami_filter`; this pins the AMI returned by the
|
||||
// filter, but will cause Packer to fail if the `source_ami` does not exist.
|
||||
SourceAmiFilter AmiFilterOptions `mapstructure:"source_ami_filter" required:"false"`
|
||||
// a list of acceptable instance
|
||||
// types to run your build on. We will request a spot instance using the max
|
||||
// price of spot_price and the allocation strategy of "lowest price".
|
||||
// Your instance will be launched on an instance type of the lowest available
|
||||
// price that you have in your list. This is used in place of instance_type.
|
||||
// You may only set either spot_instance_types or instance_type, not both.
|
||||
// This feature exists to help prevent situations where a Packer build fails
|
||||
// because a particular availability zone does not have capacity for the
|
||||
// specific instance_type requested in instance_type.
|
||||
SpotInstanceTypes []string `mapstructure:"spot_instance_types" required:"false"`
|
||||
// The maximum hourly price to pay for a spot instance
|
||||
// to create the AMI. Spot instances are a type of instance that EC2 starts
|
||||
// when the current spot price is less than the maximum price you specify.
|
||||
// Spot price will be updated based on available spot instance capacity and
|
||||
// current spot instance requests. It may save you some costs. You can set
|
||||
// this to auto for Packer to automatically discover the best spot price or
|
||||
// to "0" to use an on demand instance (default).
|
||||
SpotPrice string `mapstructure:"spot_price" required:"false"`
|
||||
// Required if spot_price is set to
|
||||
// auto. This tells Packer what sort of AMI you're launching to find the
|
||||
// best spot price. This must be one of: Linux/UNIX, SUSE Linux,
|
||||
// Windows, Linux/UNIX (Amazon VPC), SUSE Linux (Amazon VPC),
|
||||
// Windows (Amazon VPC)
|
||||
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product" required:"false"`
|
||||
// Requires spot_price to be
|
||||
// set. This tells Packer to apply tags to the spot request that is issued.
|
||||
SpotTags map[string]string `mapstructure:"spot_tags" required:"false"`
|
||||
// Filters used to populate the `subnet_id` field.
|
||||
// Example:
|
||||
//
|
||||
// ``` json
|
||||
// {
|
||||
// "subnet_filter": {
|
||||
// "filters": {
|
||||
// "tag:Class": "build"
|
||||
// },
|
||||
// "most_free": true,
|
||||
// "random": false
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// This selects the Subnet with tag `Class` with the value `build`, which has
|
||||
// the most free IP addresses. NOTE: This will fail unless *exactly* one
|
||||
// Subnet is returned. By using `most_free` or `random` one will be selected
|
||||
// from those matching the filter.
|
||||
//
|
||||
// - `filters` (map of strings) - filters used to select a `subnet_id`.
|
||||
// NOTE: This will fail unless *exactly* one Subnet is returned. Any
|
||||
// filter described in the docs for
|
||||
// [DescribeSubnets](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSubnets.html)
|
||||
// is valid.
|
||||
//
|
||||
// - `most_free` (boolean) - The Subnet with the most free IPv4 addresses
|
||||
// will be used if multiple Subnets matches the filter.
|
||||
//
|
||||
// - `random` (boolean) - A random Subnet will be used if multiple Subnets
|
||||
// matches the filter. `most_free` have precendence over this.
|
||||
//
|
||||
// `subnet_id` take precedence over this.
|
||||
SubnetFilter SubnetFilterOptions `mapstructure:"subnet_filter" required:"false"`
|
||||
// If using VPC, the ID of the subnet, such as
|
||||
// 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"`
|
||||
// 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.
|
||||
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name" required:"false"`
|
||||
// A list of IPv4 CIDR blocks to be authorized access to the instance, when
|
||||
// packer is creating a temporary security group.
|
||||
//
|
||||
// The default is [`0.0.0.0/0`] (i.e., allow any IPv4 source). This is only
|
||||
// used when `security_group_id` or `security_group_ids` is not specified.
|
||||
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false"`
|
||||
// User data to apply when launching the instance. Note
|
||||
// that you need to be careful about escaping characters due to the templates
|
||||
// being JSON. It is often more convenient to use user_data_file, instead.
|
||||
// Packer will not automatically wait for a user script to finish before
|
||||
// shutting down the instance this must be handled in a provisioner.
|
||||
UserData string `mapstructure:"user_data" required:"false"`
|
||||
// Path to a file that will be used for the user
|
||||
// data when launching the instance.
|
||||
UserDataFile string `mapstructure:"user_data_file" required:"false"`
|
||||
// Filters used to populate the `vpc_id` field.
|
||||
// Example:
|
||||
//
|
||||
// ``` json
|
||||
// {
|
||||
// "vpc_filter": {
|
||||
// "filters": {
|
||||
// "tag:Class": "build",
|
||||
// "isDefault": "false",
|
||||
// "cidr": "/24"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// This selects the VPC with tag `Class` with the value `build`, which is not
|
||||
// the default VPC, and have a IPv4 CIDR block of `/24`. NOTE: This will fail
|
||||
// unless *exactly* one VPC is returned.
|
||||
//
|
||||
// - `filters` (map of strings) - filters used to select a `vpc_id`. NOTE:
|
||||
// This will fail unless *exactly* one VPC is returned. Any filter
|
||||
// described in the docs for
|
||||
// [DescribeVpcs](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVpcs.html)
|
||||
// is valid.
|
||||
//
|
||||
// `vpc_id` take precedence over this.
|
||||
VpcFilter VpcFilterOptions `mapstructure:"vpc_filter" required:"false"`
|
||||
// If launching into a VPC subnet, Packer needs the VPC ID
|
||||
// in order to create a temporary security group within the VPC. Requires
|
||||
// subnet_id to be set. If this field is left blank, Packer will try to get
|
||||
// the VPC ID from the subnet_id.
|
||||
VpcId string `mapstructure:"vpc_id" required:"false"`
|
||||
// The timeout for waiting for a Windows
|
||||
// password for Windows instances. Defaults to 20 minutes. Example value:
|
||||
// 10m
|
||||
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout" required:"false"`
|
||||
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
|
||||
AvailabilityZone string `mapstructure:"availability_zone"`
|
||||
BlockDurationMinutes int64 `mapstructure:"block_duration_minutes"`
|
||||
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
|
||||
EbsOptimized bool `mapstructure:"ebs_optimized"`
|
||||
EnableT2Unlimited bool `mapstructure:"enable_t2_unlimited"`
|
||||
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
|
||||
InstanceInitiatedShutdownBehavior string `mapstructure:"shutdown_behavior"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
SecurityGroupFilter SecurityGroupFilterOptions `mapstructure:"security_group_filter"`
|
||||
RunTags map[string]string `mapstructure:"run_tags"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SecurityGroupIds []string `mapstructure:"security_group_ids"`
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
SourceAmiFilter AmiFilterOptions `mapstructure:"source_ami_filter"`
|
||||
SpotInstanceTypes []string `mapstructure:"spot_instance_types"`
|
||||
SpotPrice string `mapstructure:"spot_price"`
|
||||
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
|
||||
SpotTags map[string]string `mapstructure:"spot_tags"`
|
||||
SubnetFilter SubnetFilterOptions `mapstructure:"subnet_filter"`
|
||||
SubnetId string `mapstructure:"subnet_id"`
|
||||
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
|
||||
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
UserDataFile string `mapstructure:"user_data_file"`
|
||||
VpcFilter VpcFilterOptions `mapstructure:"vpc_filter"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"`
|
||||
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHInterface string `mapstructure:"ssh_interface"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
@@ -401,12 +114,12 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
errs := c.Comm.Prepare(ctx)
|
||||
|
||||
// Validating ssh_interface
|
||||
if c.SSHInterface != "public_ip" &&
|
||||
c.SSHInterface != "private_ip" &&
|
||||
c.SSHInterface != "public_dns" &&
|
||||
c.SSHInterface != "private_dns" &&
|
||||
c.SSHInterface != "" {
|
||||
errs = append(errs, fmt.Errorf("Unknown interface type: %s", c.SSHInterface))
|
||||
if c.Comm.SSHInterface != "public_ip" &&
|
||||
c.Comm.SSHInterface != "private_ip" &&
|
||||
c.Comm.SSHInterface != "public_dns" &&
|
||||
c.Comm.SSHInterface != "private_dns" &&
|
||||
c.Comm.SSHInterface != "" {
|
||||
errs = append(errs, fmt.Errorf("Unknown interface type: %s", c.Comm.SSHInterface))
|
||||
}
|
||||
|
||||
if c.Comm.SSHKeyPairName != "" {
|
||||
@@ -440,6 +153,20 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
"block_duration_minutes must be multiple of 60"))
|
||||
}
|
||||
|
||||
if c.SpotPrice == "auto" {
|
||||
if c.SpotPriceAutoProduct == "" {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"spot_price_auto_product must be specified when spot_price is auto"))
|
||||
}
|
||||
}
|
||||
|
||||
if c.SpotPriceAutoProduct != "" {
|
||||
if c.SpotPrice != "auto" {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"spot_price should be set to auto when spot_price_auto_product is specified"))
|
||||
}
|
||||
}
|
||||
|
||||
if c.SpotTags != nil {
|
||||
if c.SpotPrice == "" || c.SpotPrice == "0" {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
// Code generated by "mapstructure-to-hcl2 -type AmiFilterOptions,SecurityGroupFilterOptions,SubnetFilterOptions,VpcFilterOptions,PolicyDocument,Statement"; DO NOT EDIT.
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatAmiFilterOptions is an auto-generated flat version of AmiFilterOptions.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatAmiFilterOptions struct {
|
||||
Filters map[string]string `cty:"filters"`
|
||||
Owners []string `cty:"owners"`
|
||||
MostRecent *bool `mapstructure:"most_recent" cty:"most_recent"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatAmiFilterOptions.
|
||||
// FlatAmiFilterOptions is an auto-generated flat version of AmiFilterOptions.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*AmiFilterOptions) FlatMapstructure() interface{} { return new(FlatAmiFilterOptions) }
|
||||
|
||||
// HCL2Spec returns the hcldec.Spec of a FlatAmiFilterOptions.
|
||||
// This spec is used by HCL to read the fields of FlatAmiFilterOptions.
|
||||
func (*FlatAmiFilterOptions) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false},
|
||||
"owners": &hcldec.AttrSpec{Name: "owners", Type: cty.List(cty.String), Required: false},
|
||||
"most_recent": &hcldec.AttrSpec{Name: "most_recent", Type: cty.Bool, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatPolicyDocument is an auto-generated flat version of PolicyDocument.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatPolicyDocument struct {
|
||||
Version *string `cty:"version"`
|
||||
Statement []FlatStatement `cty:"statement"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatPolicyDocument.
|
||||
// FlatPolicyDocument is an auto-generated flat version of PolicyDocument.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*PolicyDocument) FlatMapstructure() interface{} { return new(FlatPolicyDocument) }
|
||||
|
||||
// HCL2Spec returns the hcldec.Spec of a FlatPolicyDocument.
|
||||
// This spec is used by HCL to read the fields of FlatPolicyDocument.
|
||||
func (*FlatPolicyDocument) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
|
||||
"statement": &hcldec.BlockListSpec{TypeName: "statement", Nested: &hcldec.BlockSpec{TypeName: "statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())}},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatSecurityGroupFilterOptions is an auto-generated flat version of SecurityGroupFilterOptions.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatSecurityGroupFilterOptions struct {
|
||||
Filters map[string]string `cty:"filters"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatSecurityGroupFilterOptions.
|
||||
// FlatSecurityGroupFilterOptions is an auto-generated flat version of SecurityGroupFilterOptions.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*SecurityGroupFilterOptions) FlatMapstructure() interface{} {
|
||||
return new(FlatSecurityGroupFilterOptions)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcldec.Spec of a FlatSecurityGroupFilterOptions.
|
||||
// This spec is used by HCL to read the fields of FlatSecurityGroupFilterOptions.
|
||||
func (*FlatSecurityGroupFilterOptions) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatStatement is an auto-generated flat version of Statement.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatStatement struct {
|
||||
Effect *string `cty:"effect"`
|
||||
Action []string `cty:"action"`
|
||||
Resource *string `cty:"resource"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatStatement.
|
||||
// FlatStatement is an auto-generated flat version of Statement.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*Statement) FlatMapstructure() interface{} { return new(FlatStatement) }
|
||||
|
||||
// HCL2Spec returns the hcldec.Spec of a FlatStatement.
|
||||
// This spec is used by HCL to read the fields of FlatStatement.
|
||||
func (*FlatStatement) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"effect": &hcldec.AttrSpec{Name: "effect", Type: cty.String, Required: false},
|
||||
"action": &hcldec.AttrSpec{Name: "action", Type: cty.List(cty.String), Required: false},
|
||||
"resource": &hcldec.AttrSpec{Name: "resource", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatSubnetFilterOptions is an auto-generated flat version of SubnetFilterOptions.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatSubnetFilterOptions struct {
|
||||
Filters map[string]string `cty:"filters"`
|
||||
MostFree *bool `mapstructure:"most_free" cty:"most_free"`
|
||||
Random *bool `mapstructure:"random" cty:"random"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatSubnetFilterOptions.
|
||||
// FlatSubnetFilterOptions is an auto-generated flat version of SubnetFilterOptions.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*SubnetFilterOptions) FlatMapstructure() interface{} { return new(FlatSubnetFilterOptions) }
|
||||
|
||||
// HCL2Spec returns the hcldec.Spec of a FlatSubnetFilterOptions.
|
||||
// This spec is used by HCL to read the fields of FlatSubnetFilterOptions.
|
||||
func (*FlatSubnetFilterOptions) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false},
|
||||
"most_free": &hcldec.AttrSpec{Name: "most_free", Type: cty.Bool, Required: false},
|
||||
"random": &hcldec.AttrSpec{Name: "random", Type: cty.Bool, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatVpcFilterOptions is an auto-generated flat version of VpcFilterOptions.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatVpcFilterOptions struct {
|
||||
Filters map[string]string `cty:"filters"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatVpcFilterOptions.
|
||||
// FlatVpcFilterOptions is an auto-generated flat version of VpcFilterOptions.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*VpcFilterOptions) FlatMapstructure() interface{} { return new(FlatVpcFilterOptions) }
|
||||
|
||||
// HCL2Spec returns the hcldec.Spec of a FlatVpcFilterOptions.
|
||||
// This spec is used by HCL to read the fields of FlatVpcFilterOptions.
|
||||
func (*FlatVpcFilterOptions) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -24,9 +24,7 @@ func testConfig() *RunConfig {
|
||||
InstanceType: "m1.small",
|
||||
|
||||
Comm: communicator.Config{
|
||||
SSH: communicator.SSH{
|
||||
SSHUsername: "foo",
|
||||
},
|
||||
SSHUsername: "foo",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -73,7 +71,7 @@ func TestRunConfigPrepare_SourceAmiFilterOwnersBlank(t *testing.T) {
|
||||
c := testConfigFilter()
|
||||
filter_key := "name"
|
||||
filter_value := "foo"
|
||||
c.SourceAmiFilter = AmiFilterOptions{Filters: map[string]string{filter_key: filter_value}}
|
||||
c.SourceAmiFilter = AmiFilterOptions{Filters: map[*string]*string{&filter_key: &filter_value}}
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if Owners is not specified)")
|
||||
}
|
||||
@@ -84,7 +82,7 @@ func TestRunConfigPrepare_SourceAmiFilterGood(t *testing.T) {
|
||||
owner := "123"
|
||||
filter_key := "name"
|
||||
filter_value := "foo"
|
||||
goodFilter := AmiFilterOptions{Owners: []string{owner}, Filters: map[string]string{filter_key: filter_value}}
|
||||
goodFilter := AmiFilterOptions{Owners: []*string{&owner}, Filters: map[*string]*string{&filter_key: &filter_value}}
|
||||
c.SourceAmiFilter = goodFilter
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
@@ -119,6 +117,7 @@ func TestRunConfigPrepare_EnableT2UnlimitedBadWithSpotInstanceRequest(t *testing
|
||||
c.InstanceType = "t2.micro"
|
||||
c.EnableT2Unlimited = true
|
||||
c.SpotPrice = "auto"
|
||||
c.SpotPriceAutoProduct = "Linux/UNIX"
|
||||
err := c.Prepare(nil)
|
||||
if len(err) != 1 {
|
||||
t.Fatalf("Should error if T2 Unlimited has been used in conjuntion with a Spot Price request")
|
||||
@@ -128,14 +127,19 @@ func TestRunConfigPrepare_EnableT2UnlimitedBadWithSpotInstanceRequest(t *testing
|
||||
func TestRunConfigPrepare_SpotAuto(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.SpotPrice = "auto"
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if spot_price_auto_product is not set and spot_price is set to auto")
|
||||
}
|
||||
|
||||
// Good - SpotPrice and SpotPriceAutoProduct are correctly set
|
||||
c.SpotPriceAutoProduct = "foo"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Shouldn't error (YET) even though SpotPriceAutoProduct is deprecated
|
||||
c.SpotPriceAutoProduct = "Linux/Unix"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
c.SpotPrice = ""
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if spot_price is not set to auto and spot_price_auto_product is set")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package common
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
@@ -21,16 +20,12 @@ var (
|
||||
|
||||
// SSHHost returns a function that can be given to the SSH communicator
|
||||
// for determining the SSH address based on the instance DNS name.
|
||||
func SSHHost(e ec2Describer, sshInterface string, host string) func(multistep.StateBag) (string, error) {
|
||||
func SSHHost(e ec2Describer, sshInterface string) func(multistep.StateBag) (string, error) {
|
||||
return func(state multistep.StateBag) (string, error) {
|
||||
if host != "" {
|
||||
log.Printf("Using ssh_host value: %s", host)
|
||||
return host, nil
|
||||
}
|
||||
|
||||
const tries = 2
|
||||
// <= with current structure to check result of describing `tries` times
|
||||
for j := 0; j <= tries; j++ {
|
||||
var host string
|
||||
i := state.Get("instance").(*ec2.Instance)
|
||||
if sshInterface != "" {
|
||||
switch sshInterface {
|
||||
|
||||
@@ -9,11 +9,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
privateIP = "10.0.0.1"
|
||||
publicIP = "192.168.1.1"
|
||||
privateDNS = "private.dns.test"
|
||||
publicDNS = "public.dns.test"
|
||||
sshHostTemplate = "custom.host.value"
|
||||
privateIP = "10.0.0.1"
|
||||
publicIP = "192.168.1.1"
|
||||
privateDNS = "private.dns.test"
|
||||
publicDNS = "public.dns.test"
|
||||
)
|
||||
|
||||
func TestSSHHost(t *testing.T) {
|
||||
@@ -26,46 +25,39 @@ func TestSSHHost(t *testing.T) {
|
||||
vpcId string
|
||||
sshInterface string
|
||||
|
||||
ok bool
|
||||
wantHost string
|
||||
sshHostOverride string
|
||||
ok bool
|
||||
wantHost string
|
||||
}{
|
||||
{1, "", "", true, publicDNS, ""},
|
||||
{1, "", "private_ip", true, privateIP, ""},
|
||||
{1, "vpc-id", "", true, publicIP, ""},
|
||||
{1, "vpc-id", "private_ip", true, privateIP, ""},
|
||||
{1, "vpc-id", "private_dns", true, privateDNS, ""},
|
||||
{1, "vpc-id", "public_dns", true, publicDNS, ""},
|
||||
{1, "vpc-id", "public_ip", true, publicIP, ""},
|
||||
{2, "", "", true, publicDNS, ""},
|
||||
{2, "", "private_ip", true, privateIP, ""},
|
||||
{2, "vpc-id", "", true, publicIP, ""},
|
||||
{2, "vpc-id", "private_ip", true, privateIP, ""},
|
||||
{2, "vpc-id", "private_dns", true, privateDNS, ""},
|
||||
{2, "vpc-id", "public_dns", true, publicDNS, ""},
|
||||
{2, "vpc-id", "public_ip", true, publicIP, ""},
|
||||
{3, "", "", false, "", ""},
|
||||
{3, "", "private_ip", false, "", ""},
|
||||
{3, "vpc-id", "", false, "", ""},
|
||||
{3, "vpc-id", "private_ip", false, "", ""},
|
||||
{3, "vpc-id", "private_dns", false, "", ""},
|
||||
{3, "vpc-id", "public_dns", false, "", ""},
|
||||
{3, "vpc-id", "public_ip", false, "", ""},
|
||||
{1, "", "", true, sshHostTemplate, sshHostTemplate},
|
||||
{1, "vpc-id", "", true, sshHostTemplate, sshHostTemplate},
|
||||
{2, "vpc-id", "private_dns", true, sshHostTemplate, sshHostTemplate},
|
||||
{1, "", "", true, publicDNS},
|
||||
{1, "", "private_ip", true, privateIP},
|
||||
{1, "vpc-id", "", true, publicIP},
|
||||
{1, "vpc-id", "private_ip", true, privateIP},
|
||||
{1, "vpc-id", "private_dns", true, privateDNS},
|
||||
{1, "vpc-id", "public_dns", true, publicDNS},
|
||||
{1, "vpc-id", "public_ip", true, publicIP},
|
||||
{2, "", "", true, publicDNS},
|
||||
{2, "", "private_ip", true, privateIP},
|
||||
{2, "vpc-id", "", true, publicIP},
|
||||
{2, "vpc-id", "private_ip", true, privateIP},
|
||||
{2, "vpc-id", "private_dns", true, privateDNS},
|
||||
{2, "vpc-id", "public_dns", true, publicDNS},
|
||||
{2, "vpc-id", "public_ip", true, publicIP},
|
||||
{3, "", "", false, ""},
|
||||
{3, "", "private_ip", false, ""},
|
||||
{3, "vpc-id", "", false, ""},
|
||||
{3, "vpc-id", "private_ip", false, ""},
|
||||
{3, "vpc-id", "private_dns", false, ""},
|
||||
{3, "vpc-id", "public_dns", false, ""},
|
||||
{3, "vpc-id", "public_ip", false, ""},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
testSSHHost(t, c.allowTries, c.vpcId, c.sshInterface, c.ok, c.wantHost,
|
||||
c.sshHostOverride)
|
||||
testSSHHost(t, c.allowTries, c.vpcId, c.sshInterface, c.ok, c.wantHost)
|
||||
}
|
||||
}
|
||||
|
||||
func testSSHHost(t *testing.T, allowTries int, vpcId string, sshInterface string,
|
||||
ok bool, wantHost string, sshHostOverride string) {
|
||||
t.Logf("allowTries=%d vpcId=%s sshInterface=%s ok=%t wantHost=%q sshHostOverride=%s",
|
||||
allowTries, vpcId, sshInterface, ok, wantHost, sshHostOverride)
|
||||
func testSSHHost(t *testing.T, allowTries int, vpcId string, sshInterface string, ok bool, wantHost string) {
|
||||
t.Logf("allowTries=%d vpcId=%s sshInterface=%s ok=%t wantHost=%q", allowTries, vpcId, sshInterface, ok, wantHost)
|
||||
|
||||
e := &fakeEC2Describer{
|
||||
allowTries: allowTries,
|
||||
@@ -76,7 +68,7 @@ func testSSHHost(t *testing.T, allowTries int, vpcId string, sshInterface string
|
||||
publicDNS: publicDNS,
|
||||
}
|
||||
|
||||
f := SSHHost(e, sshInterface, sshHostOverride)
|
||||
f := SSHHost(e, sshInterface)
|
||||
st := &multistep.BasicStateBag{}
|
||||
st.Put("instance", &ec2.Instance{
|
||||
InstanceId: aws.String("instance-id"),
|
||||
|
||||
@@ -35,7 +35,7 @@ type StateChangeConf struct {
|
||||
}
|
||||
|
||||
// Following are wrapper functions that use Packer's environment-variables to
|
||||
// determine retry logic, then call the AWS SDK's built-in waiters.
|
||||
// determing retry logic, then call the AWS SDK's built-in waiters.
|
||||
|
||||
func WaitUntilAMIAvailable(ctx aws.Context, conn ec2iface.EC2API, imageId string) error {
|
||||
imageInput := ec2.DescribeImagesInput{
|
||||
|
||||
@@ -8,7 +8,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/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
@@ -18,7 +17,7 @@ type StepAMIRegionCopy struct {
|
||||
Regions []string
|
||||
AMIKmsKeyId string
|
||||
RegionKeyIds map[string]string
|
||||
EncryptBootVolume config.Trilean // nil means preserve
|
||||
EncryptBootVolume *bool // nil means preserve
|
||||
Name string
|
||||
OriginalRegion string
|
||||
|
||||
@@ -27,72 +26,31 @@ type StepAMIRegionCopy struct {
|
||||
AMISkipBuildRegion bool
|
||||
}
|
||||
|
||||
func (s *StepAMIRegionCopy) DeduplicateRegions(intermediary bool) {
|
||||
// Deduplicates regions by looping over the list of regions and storing
|
||||
// the regions as keys in a map. This saves users from accidentally copying
|
||||
// regions twice if they've added a region to a map twice.
|
||||
|
||||
RegionMap := map[string]bool{}
|
||||
RegionSlice := []string{}
|
||||
|
||||
// Original build region may or may not be present in the Regions list, so
|
||||
// let's make absolutely sure it's in our map.
|
||||
RegionMap[s.OriginalRegion] = true
|
||||
for _, r := range s.Regions {
|
||||
RegionMap[r] = true
|
||||
}
|
||||
|
||||
if !intermediary || s.AMISkipBuildRegion {
|
||||
// We don't want to copy back into the original region if we aren't
|
||||
// using an intermediary image, so remove the original region from our
|
||||
// map.
|
||||
|
||||
// We also don't want to copy back into the original region if the
|
||||
// intermediary image is because we're skipping the build region.
|
||||
delete(RegionMap, s.OriginalRegion)
|
||||
|
||||
}
|
||||
|
||||
// Now print all those keys into the region slice again
|
||||
for k, _ := range RegionMap {
|
||||
RegionSlice = append(RegionSlice, k)
|
||||
}
|
||||
|
||||
s.Regions = RegionSlice
|
||||
}
|
||||
|
||||
func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
amis := state.Get("amis").(map[string]string)
|
||||
snapshots := state.Get("snapshots").(map[string][]string)
|
||||
intermediary, _ := state.Get("intermediary_image").(bool)
|
||||
|
||||
s.DeduplicateRegions(intermediary)
|
||||
ami := amis[s.OriginalRegion]
|
||||
|
||||
// Make a note to delete the intermediary AMI if necessary.
|
||||
if intermediary {
|
||||
// Always copy back into original region to preserve the ami name
|
||||
if s.EncryptBootVolume != nil || s.AMISkipBuildRegion {
|
||||
// if we haven't specificed encryption and we aren't skipping the save
|
||||
// to the build region, we don't have anything to delete
|
||||
s.toDelete = ami
|
||||
}
|
||||
|
||||
if s.EncryptBootVolume.True() {
|
||||
// encrypt_boot is true, so we have to copy the temporary
|
||||
// AMI with required encryption setting.
|
||||
// temp image was created by stepCreateAMI.
|
||||
if s.RegionKeyIds == nil {
|
||||
s.RegionKeyIds = make(map[string]string)
|
||||
if s.EncryptBootVolume != nil {
|
||||
if !s.AMISkipBuildRegion {
|
||||
s.Regions = append(s.Regions, s.OriginalRegion)
|
||||
}
|
||||
|
||||
// Make sure the kms_key_id for the original region is in the map, as
|
||||
// long as the AMIKmsKeyId isn't being defaulted.
|
||||
if s.AMIKmsKeyId != "" {
|
||||
if _, ok := s.RegionKeyIds[s.OriginalRegion]; !ok {
|
||||
s.RegionKeyIds[s.OriginalRegion] = s.AMIKmsKeyId
|
||||
}
|
||||
} else {
|
||||
if regionKey, ok := s.RegionKeyIds[s.OriginalRegion]; ok {
|
||||
s.AMIKmsKeyId = regionKey
|
||||
if *s.EncryptBootVolume {
|
||||
// encrypt_boot is true, so we have to copy the temporary
|
||||
// AMI with required encryption setting.
|
||||
// temp image was created by stepCreateAMI.
|
||||
if s.RegionKeyIds == nil {
|
||||
s.RegionKeyIds = make(map[string]string)
|
||||
}
|
||||
s.RegionKeyIds[s.OriginalRegion] = s.AMIKmsKeyId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,25 +62,20 @@ func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) m
|
||||
|
||||
var lock sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
var regKeyID string
|
||||
errs := new(packer.MultiError)
|
||||
|
||||
wg.Add(len(s.Regions))
|
||||
for _, region := range s.Regions {
|
||||
var regKeyID string
|
||||
ui.Message(fmt.Sprintf("Copying to: %s", region))
|
||||
|
||||
if s.EncryptBootVolume.True() {
|
||||
// Encrypt is true, explicitly
|
||||
if s.EncryptBootVolume != nil && *s.EncryptBootVolume {
|
||||
regKeyID = s.RegionKeyIds[region]
|
||||
} else {
|
||||
// Encrypt is nil or false; Make sure region key is empty
|
||||
regKeyID = ""
|
||||
}
|
||||
|
||||
go func(region string) {
|
||||
defer wg.Done()
|
||||
id, snapshotIds, err := s.amiRegionCopy(ctx, state, s.AccessConfig,
|
||||
s.Name, ami, region, s.OriginalRegion, regKeyID,
|
||||
s.EncryptBootVolume.ToBoolPointer())
|
||||
id, snapshotIds, err := s.amiRegionCopy(ctx, state, s.AccessConfig, s.Name, ami, region, s.OriginalRegion, regKeyID, s.EncryptBootVolume)
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
amis[region] = id
|
||||
@@ -159,12 +112,49 @@ func (s *StepAMIRegionCopy) Cleanup(state multistep.StateBag) {
|
||||
// Delete the unencrypted amis and snapshots
|
||||
ui.Say("Deregistering the AMI and deleting unencrypted temporary " +
|
||||
"AMIs and snapshots")
|
||||
err := DestroyAMIs([]*string{&s.toDelete}, ec2conn)
|
||||
|
||||
resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{&s.toDelete},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error describing AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Deregister image by name.
|
||||
for _, i := range resp.Images {
|
||||
_, err := ec2conn.DeregisterImage(&ec2.DeregisterImageInput{
|
||||
ImageId: i.ImageId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error deregistering existing AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Deregistered AMI id: %s", *i.ImageId))
|
||||
|
||||
// Delete snapshot(s) by image
|
||||
for _, b := range i.BlockDeviceMappings {
|
||||
if b.Ebs != nil && aws.StringValue(b.Ebs.SnapshotId) != "" {
|
||||
_, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{
|
||||
SnapshotId: b.Ebs.SnapshotId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error deleting existing snapshot: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Deleted snapshot: %s", *b.Ebs.SnapshotId))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getRegionConn(config *AccessConfig, target string) (ec2iface.EC2API, error) {
|
||||
|
||||
@@ -4,14 +4,12 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
@@ -27,14 +25,10 @@ type mockEC2Conn struct {
|
||||
deregisterImageCount int
|
||||
deleteSnapshotCount int
|
||||
waitCount int
|
||||
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (m *mockEC2Conn) CopyImage(copyInput *ec2.CopyImageInput) (*ec2.CopyImageOutput, error) {
|
||||
m.lock.Lock()
|
||||
m.copyImageCount++
|
||||
m.lock.Unlock()
|
||||
copiedImage := fmt.Sprintf("%s-copied-%d", *copyInput.SourceImageId, m.copyImageCount)
|
||||
output := &ec2.CopyImageOutput{
|
||||
ImageId: &copiedImage,
|
||||
@@ -44,9 +38,7 @@ func (m *mockEC2Conn) CopyImage(copyInput *ec2.CopyImageInput) (*ec2.CopyImageOu
|
||||
|
||||
// functions we have to create mock responses for in order for test to run
|
||||
func (m *mockEC2Conn) DescribeImages(*ec2.DescribeImagesInput) (*ec2.DescribeImagesOutput, error) {
|
||||
m.lock.Lock()
|
||||
m.describeImagesCount++
|
||||
m.lock.Unlock()
|
||||
output := &ec2.DescribeImagesOutput{
|
||||
Images: []*ec2.Image{{}},
|
||||
}
|
||||
@@ -54,25 +46,19 @@ func (m *mockEC2Conn) DescribeImages(*ec2.DescribeImagesInput) (*ec2.DescribeIma
|
||||
}
|
||||
|
||||
func (m *mockEC2Conn) DeregisterImage(*ec2.DeregisterImageInput) (*ec2.DeregisterImageOutput, error) {
|
||||
m.lock.Lock()
|
||||
m.deregisterImageCount++
|
||||
m.lock.Unlock()
|
||||
output := &ec2.DeregisterImageOutput{}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (m *mockEC2Conn) DeleteSnapshot(*ec2.DeleteSnapshotInput) (*ec2.DeleteSnapshotOutput, error) {
|
||||
m.lock.Lock()
|
||||
m.deleteSnapshotCount++
|
||||
m.lock.Unlock()
|
||||
output := &ec2.DeleteSnapshotOutput{}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (m *mockEC2Conn) WaitUntilImageAvailableWithContext(aws.Context, *ec2.DescribeImagesInput, ...request.WaiterOption) error {
|
||||
m.lock.Lock()
|
||||
m.waitCount++
|
||||
m.lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -98,128 +84,6 @@ func tState() multistep.StateBag {
|
||||
return state
|
||||
}
|
||||
|
||||
func TestStepAMIRegionCopy_duplicates(t *testing.T) {
|
||||
// ------------------------------------------------------------------------
|
||||
// Test that if the original region is added to both Regions and Region,
|
||||
// the ami is only copied once (with encryption).
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
stepAMIRegionCopy := StepAMIRegionCopy{
|
||||
AccessConfig: testAccessConfig(),
|
||||
Regions: []string{"us-east-1"},
|
||||
AMIKmsKeyId: "12345",
|
||||
// Original region key in regionkeyids is different than in amikmskeyid
|
||||
RegionKeyIds: map[string]string{"us-east-1": "12345"},
|
||||
EncryptBootVolume: config.TriTrue,
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
// mock out the region connection code
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
|
||||
state := tState()
|
||||
state.Put("intermediary_image", true)
|
||||
stepAMIRegionCopy.Run(context.Background(), state)
|
||||
|
||||
if len(stepAMIRegionCopy.Regions) != 1 {
|
||||
t.Fatalf("Should have added original ami to Regions one time only")
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Both Region and Regions set, but no encryption - shouldn't copy anything
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// the ami is only copied once.
|
||||
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||
AccessConfig: testAccessConfig(),
|
||||
Regions: []string{"us-east-1"},
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
// mock out the region connection code
|
||||
state.Put("intermediary_image", false)
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
stepAMIRegionCopy.Run(context.Background(), state)
|
||||
|
||||
if len(stepAMIRegionCopy.Regions) != 0 {
|
||||
t.Fatalf("Should not have added original ami to Regions; not encrypting")
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Both Region and Regions set, but no encryption - shouldn't copy anything,
|
||||
// this tests false as opposed to nil value above.
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// the ami is only copied once.
|
||||
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||
AccessConfig: testAccessConfig(),
|
||||
Regions: []string{"us-east-1"},
|
||||
EncryptBootVolume: config.TriFalse,
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
// mock out the region connection code
|
||||
state.Put("intermediary_image", false)
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
stepAMIRegionCopy.Run(context.Background(), state)
|
||||
|
||||
if len(stepAMIRegionCopy.Regions) != 0 {
|
||||
t.Fatalf("Should not have added original ami to Regions once; not" +
|
||||
"encrypting")
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Multiple regions, many duplicates, and encryption (this shouldn't ever
|
||||
// happen because of our template validation, but good to test it.)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||
AccessConfig: testAccessConfig(),
|
||||
// Many duplicates for only 3 actual values
|
||||
Regions: []string{"us-east-1", "us-west-2", "us-west-2", "ap-east-1", "ap-east-1", "ap-east-1"},
|
||||
AMIKmsKeyId: "IlikePancakes",
|
||||
// Original region key in regionkeyids is different than in amikmskeyid
|
||||
RegionKeyIds: map[string]string{"us-east-1": "12345", "us-west-2": "abcde", "ap-east-1": "xyz"},
|
||||
EncryptBootVolume: config.TriTrue,
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
// mock out the region connection code
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
state.Put("intermediary_image", true)
|
||||
stepAMIRegionCopy.Run(context.Background(), state)
|
||||
|
||||
if len(stepAMIRegionCopy.Regions) != 3 {
|
||||
t.Fatalf("Each AMI should have been added to Regions one time only.")
|
||||
}
|
||||
|
||||
// Also verify that we respect RegionKeyIds over AMIKmsKeyIds:
|
||||
if stepAMIRegionCopy.RegionKeyIds["us-east-1"] != "12345" {
|
||||
t.Fatalf("RegionKeyIds should take precedence over AmiKmsKeyIds")
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Multiple regions, many duplicates, NO encryption
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||
AccessConfig: testAccessConfig(),
|
||||
// Many duplicates for only 3 actual values
|
||||
Regions: []string{"us-east-1", "us-west-2", "us-west-2", "ap-east-1", "ap-east-1", "ap-east-1"},
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
// mock out the region connection code
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
state.Put("intermediary_image", false)
|
||||
stepAMIRegionCopy.Run(context.Background(), state)
|
||||
|
||||
if len(stepAMIRegionCopy.Regions) != 2 {
|
||||
t.Fatalf("Each AMI should have been added to Regions one time only, " +
|
||||
"and original region shouldn't be added at all")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepAmiRegionCopy_nil_encryption(t *testing.T) {
|
||||
// create step
|
||||
stepAMIRegionCopy := StepAMIRegionCopy{
|
||||
@@ -227,7 +91,7 @@ func TestStepAmiRegionCopy_nil_encryption(t *testing.T) {
|
||||
Regions: make([]string, 0),
|
||||
AMIKmsKeyId: "",
|
||||
RegionKeyIds: make(map[string]string),
|
||||
EncryptBootVolume: config.TriUnset,
|
||||
EncryptBootVolume: nil,
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
@@ -235,7 +99,6 @@ func TestStepAmiRegionCopy_nil_encryption(t *testing.T) {
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
|
||||
state := tState()
|
||||
state.Put("intermediary_image", false)
|
||||
stepAMIRegionCopy.Run(context.Background(), state)
|
||||
|
||||
if stepAMIRegionCopy.toDelete != "" {
|
||||
@@ -246,14 +109,39 @@ func TestStepAmiRegionCopy_nil_encryption(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepAmiRegionCopy_true_encryption(t *testing.T) {
|
||||
func TestStepAmiRegionCopy_false_encryption(t *testing.T) {
|
||||
// create step
|
||||
stepAMIRegionCopy := StepAMIRegionCopy{
|
||||
AccessConfig: testAccessConfig(),
|
||||
Regions: make([]string, 0),
|
||||
AMIKmsKeyId: "",
|
||||
RegionKeyIds: make(map[string]string),
|
||||
EncryptBootVolume: config.TriTrue,
|
||||
EncryptBootVolume: aws.Bool(false),
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
// mock out the region connection code
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
|
||||
state := tState()
|
||||
stepAMIRegionCopy.Run(context.Background(), state)
|
||||
|
||||
if stepAMIRegionCopy.toDelete != "ami-12345" {
|
||||
t.Fatalf("should be deleting the original intermediary ami")
|
||||
}
|
||||
if len(stepAMIRegionCopy.Regions) == 0 {
|
||||
t.Fatalf("Should have added original ami to Regions")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepAmiRegionCopy_true_encryption(t *testing.T) {
|
||||
// create step
|
||||
stepAMIRegionCopy := StepAMIRegionCopy{
|
||||
AccessConfig: testAccessConfig(),
|
||||
Regions: make([]string, 0),
|
||||
AMIKmsKeyId: "",
|
||||
RegionKeyIds: make(map[string]string),
|
||||
EncryptBootVolume: aws.Bool(true),
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
@@ -261,7 +149,6 @@ func TestStepAmiRegionCopy_true_encryption(t *testing.T) {
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
|
||||
state := tState()
|
||||
state.Put("intermediary_image", true)
|
||||
stepAMIRegionCopy.Run(context.Background(), state)
|
||||
|
||||
if stepAMIRegionCopy.toDelete == "" {
|
||||
@@ -272,41 +159,13 @@ func TestStepAmiRegionCopy_true_encryption(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepAmiRegionCopy_nil_intermediary(t *testing.T) {
|
||||
func TestStepAmiRegionCopy_true_AMISkipBuildRegion(t *testing.T) {
|
||||
// create step
|
||||
stepAMIRegionCopy := StepAMIRegionCopy{
|
||||
AccessConfig: testAccessConfig(),
|
||||
Regions: make([]string, 0),
|
||||
AMIKmsKeyId: "",
|
||||
RegionKeyIds: make(map[string]string),
|
||||
EncryptBootVolume: config.TriFalse,
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
// mock out the region connection code
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
|
||||
state := tState()
|
||||
stepAMIRegionCopy.Run(context.Background(), state)
|
||||
|
||||
if stepAMIRegionCopy.toDelete != "" {
|
||||
t.Fatalf("Should not delete original AMI if no intermediary")
|
||||
}
|
||||
if len(stepAMIRegionCopy.Regions) != 0 {
|
||||
t.Fatalf("Should not have added original ami to Regions")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepAmiRegionCopy_AMISkipBuildRegion(t *testing.T) {
|
||||
// ------------------------------------------------------------------------
|
||||
// skip build region is true
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
stepAMIRegionCopy := StepAMIRegionCopy{
|
||||
AccessConfig: testAccessConfig(),
|
||||
Regions: []string{"us-west-1"},
|
||||
Regions: make([]string, 0),
|
||||
AMIKmsKeyId: "",
|
||||
RegionKeyIds: map[string]string{"us-west-1": "abcde"},
|
||||
RegionKeyIds: make(map[string]string),
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
AMISkipBuildRegion: true,
|
||||
@@ -315,90 +174,12 @@ func TestStepAmiRegionCopy_AMISkipBuildRegion(t *testing.T) {
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
|
||||
state := tState()
|
||||
state.Put("intermediary_image", true)
|
||||
stepAMIRegionCopy.Run(context.Background(), state)
|
||||
|
||||
if stepAMIRegionCopy.toDelete == "" {
|
||||
t.Fatalf("Should delete original AMI if skip_save_build_region=true")
|
||||
}
|
||||
if len(stepAMIRegionCopy.Regions) != 1 {
|
||||
t.Fatalf("Should not have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// skip build region is false.
|
||||
// ------------------------------------------------------------------------
|
||||
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||
AccessConfig: testAccessConfig(),
|
||||
Regions: []string{"us-west-1"},
|
||||
AMIKmsKeyId: "",
|
||||
RegionKeyIds: make(map[string]string),
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
AMISkipBuildRegion: false,
|
||||
}
|
||||
// mock out the region connection code
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
|
||||
state.Put("intermediary_image", false) // not encrypted
|
||||
stepAMIRegionCopy.Run(context.Background(), state)
|
||||
|
||||
if stepAMIRegionCopy.toDelete != "" {
|
||||
t.Fatalf("Shouldn't have an intermediary AMI, so dont delete original ami")
|
||||
}
|
||||
if len(stepAMIRegionCopy.Regions) != 1 {
|
||||
t.Fatalf("Should not have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// skip build region is false, but encrypt is true
|
||||
// ------------------------------------------------------------------------
|
||||
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||
AccessConfig: testAccessConfig(),
|
||||
Regions: []string{"us-west-1"},
|
||||
AMIKmsKeyId: "",
|
||||
RegionKeyIds: map[string]string{"us-west-1": "abcde"},
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
AMISkipBuildRegion: false,
|
||||
EncryptBootVolume: config.TriTrue,
|
||||
}
|
||||
// mock out the region connection code
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
|
||||
state.Put("intermediary_image", true) //encrypted
|
||||
stepAMIRegionCopy.Run(context.Background(), state)
|
||||
|
||||
if stepAMIRegionCopy.toDelete == "" {
|
||||
t.Fatalf("Have to delete intermediary AMI")
|
||||
}
|
||||
if len(stepAMIRegionCopy.Regions) != 2 {
|
||||
t.Fatalf("Should have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// skip build region is true, and encrypt is true
|
||||
// ------------------------------------------------------------------------
|
||||
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||
AccessConfig: testAccessConfig(),
|
||||
Regions: []string{"us-west-1"},
|
||||
AMIKmsKeyId: "",
|
||||
RegionKeyIds: map[string]string{"us-west-1": "abcde"},
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
AMISkipBuildRegion: true,
|
||||
EncryptBootVolume: config.TriTrue,
|
||||
}
|
||||
// mock out the region connection code
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
|
||||
state.Put("intermediary_image", true) //encrypted
|
||||
stepAMIRegionCopy.Run(context.Background(), state)
|
||||
|
||||
if stepAMIRegionCopy.toDelete == "" {
|
||||
t.Fatalf("Have to delete intermediary AMI")
|
||||
}
|
||||
if len(stepAMIRegionCopy.Regions) != 1 {
|
||||
t.Fatalf("Should not have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
|
||||
if len(stepAMIRegionCopy.Regions) != 0 {
|
||||
t.Fatalf("Should not have added original ami to Regions")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// remain after termination of the instance. These volumes are typically ones
|
||||
// that are marked as "delete on terminate:false" in the source_ami of a build.
|
||||
type StepCleanupVolumes struct {
|
||||
LaunchMappings BlockDevices
|
||||
BlockDevices BlockDevices
|
||||
}
|
||||
|
||||
func (s *StepCleanupVolumes) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
@@ -79,7 +79,7 @@ func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
||||
|
||||
// Filter out any devices created as part of the launch mappings, since
|
||||
// we'll let amazon follow the `delete_on_termination` setting.
|
||||
for _, b := range s.LaunchMappings {
|
||||
for _, b := range s.BlockDevices.LaunchMappings {
|
||||
for volKey, volName := range volList {
|
||||
if volName == b.DeviceName {
|
||||
delete(volList, volKey)
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
@@ -46,13 +45,14 @@ func (s *StepGetPassword) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
// Get the password
|
||||
var password string
|
||||
var err error
|
||||
cancel := make(chan struct{})
|
||||
waitDone := make(chan bool, 1)
|
||||
go func() {
|
||||
ui.Say("Waiting for auto-generated password for instance...")
|
||||
ui.Message(
|
||||
"It is normal for this process to take up to 15 minutes,\n" +
|
||||
"but it usually takes around 5. Please wait.")
|
||||
password, err = s.waitForPassword(ctx, state)
|
||||
password, err = s.waitForPassword(state, cancel)
|
||||
waitDone <- true
|
||||
}()
|
||||
|
||||
@@ -76,12 +76,16 @@ WaitLoop:
|
||||
err := fmt.Errorf("Timeout waiting for password.")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
close(cancel)
|
||||
return multistep.ActionHalt
|
||||
case <-ctx.Done():
|
||||
// The step sequence was cancelled, so cancel waiting for password
|
||||
// and just start the halting process.
|
||||
log.Println("[WARN] Interrupt detected, quitting waiting for password.")
|
||||
return multistep.ActionHalt
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||
// The step sequence was cancelled, so cancel waiting for password
|
||||
// and just start the halting process.
|
||||
close(cancel)
|
||||
log.Println("[WARN] Interrupt detected, quitting waiting for password.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,38 +106,24 @@ func (s *StepGetPassword) Cleanup(multistep.StateBag) {
|
||||
commonhelper.RemoveSharedStateFile("winrm_password", s.BuildName)
|
||||
}
|
||||
|
||||
func (s *StepGetPassword) waitForPassword(ctx context.Context, state multistep.StateBag) (string, error) {
|
||||
func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
privateKey := s.Comm.SSHPrivateKey
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-cancel:
|
||||
log.Println("[INFO] Retrieve password wait cancelled. Exiting loop.")
|
||||
return "", errors.New("Retrieve password wait cancelled")
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
|
||||
// Wrap in a retry so that we don't fail on rate-limiting.
|
||||
log.Printf("Retrieving auto-generated instance password...")
|
||||
var resp *ec2.GetPasswordDataOutput
|
||||
err := retry.Config{
|
||||
Tries: 11,
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
resp, err = ec2conn.GetPasswordData(&ec2.GetPasswordDataInput{
|
||||
InstanceId: instance.InstanceId,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error retrieving auto-generated instance password: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
resp, err := ec2conn.GetPasswordData(&ec2.GetPasswordDataInput{
|
||||
InstanceId: instance.InstanceId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error retrieving auto-generated instance password: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/hashicorp/packer/common/uuid"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepIamInstanceProfile struct {
|
||||
IamInstanceProfile string
|
||||
TemporaryIamInstanceProfilePolicyDocument *PolicyDocument
|
||||
createdInstanceProfileName string
|
||||
createdRoleName string
|
||||
createdPolicyName string
|
||||
roleIsAttached bool
|
||||
}
|
||||
|
||||
func (s *StepIamInstanceProfile) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
iamsvc := state.Get("iam").(*iam.IAM)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
state.Put("iamInstanceProfile", "")
|
||||
|
||||
if len(s.IamInstanceProfile) > 0 {
|
||||
_, err := iamsvc.GetInstanceProfile(
|
||||
&iam.GetInstanceProfileInput{
|
||||
InstanceProfileName: aws.String(s.IamInstanceProfile),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Couldn't find specified instance profile: %s", err)
|
||||
log.Printf("[DEBUG] %s", err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
log.Printf("Using specified instance profile: %v", s.IamInstanceProfile)
|
||||
state.Put("iamInstanceProfile", s.IamInstanceProfile)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.TemporaryIamInstanceProfilePolicyDocument != nil {
|
||||
// Create the profile
|
||||
profileName := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||
|
||||
policy, err := json.Marshal(s.TemporaryIamInstanceProfilePolicyDocument)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating temporary instance profile for this instance: %s", profileName))
|
||||
|
||||
profileResp, err := iamsvc.CreateInstanceProfile(&iam.CreateInstanceProfileInput{
|
||||
InstanceProfileName: aws.String(profileName),
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.createdInstanceProfileName = aws.StringValue(profileResp.InstanceProfile.InstanceProfileName)
|
||||
|
||||
log.Printf("[DEBUG] Waiting for temporary instance profile: %s", s.createdInstanceProfileName)
|
||||
err = iamsvc.WaitUntilInstanceProfileExists(&iam.GetInstanceProfileInput{
|
||||
InstanceProfileName: aws.String(s.createdInstanceProfileName),
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
log.Printf("[DEBUG] Found instance profile %s", s.createdInstanceProfileName)
|
||||
} else {
|
||||
err := fmt.Errorf("Timed out waiting for instance profile %s: %s", s.createdInstanceProfileName, err)
|
||||
log.Printf("[DEBUG] %s", err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating temporary role for this instance: %s", profileName))
|
||||
|
||||
roleResp, err := iamsvc.CreateRole(&iam.CreateRoleInput{
|
||||
RoleName: aws.String(profileName),
|
||||
Description: aws.String("Temporary role for Packer"),
|
||||
AssumeRolePolicyDocument: aws.String("{\"Version\": \"2012-10-17\",\"Statement\": [{\"Effect\": \"Allow\",\"Principal\": {\"Service\": \"ec2.amazonaws.com\"},\"Action\": \"sts:AssumeRole\"}]}"),
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.createdRoleName = aws.StringValue(roleResp.Role.RoleName)
|
||||
|
||||
log.Printf("[DEBUG] Waiting for temporary role: %s", s.createdInstanceProfileName)
|
||||
err = iamsvc.WaitUntilRoleExists(&iam.GetRoleInput{
|
||||
RoleName: aws.String(s.createdRoleName),
|
||||
})
|
||||
if err == nil {
|
||||
log.Printf("[DEBUG] Found temporary role %s", s.createdRoleName)
|
||||
} else {
|
||||
err := fmt.Errorf("Timed out waiting for temporary role %s: %s", s.createdRoleName, err)
|
||||
log.Printf("[DEBUG] %s", err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Attaching policy to the temporary role: %s", profileName))
|
||||
|
||||
_, err = iamsvc.PutRolePolicy(&iam.PutRolePolicyInput{
|
||||
RoleName: roleResp.Role.RoleName,
|
||||
PolicyName: aws.String(profileName),
|
||||
PolicyDocument: aws.String(string(policy)),
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.createdPolicyName = aws.StringValue(roleResp.Role.RoleName)
|
||||
|
||||
_, err = iamsvc.AddRoleToInstanceProfile(&iam.AddRoleToInstanceProfileInput{
|
||||
RoleName: roleResp.Role.RoleName,
|
||||
InstanceProfileName: profileResp.InstanceProfile.InstanceProfileName,
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.roleIsAttached = true
|
||||
state.Put("iamInstanceProfile", aws.StringValue(profileResp.InstanceProfile.InstanceProfileName))
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepIamInstanceProfile) Cleanup(state multistep.StateBag) {
|
||||
iamsvc := state.Get("iam").(*iam.IAM)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
var err error
|
||||
|
||||
if s.roleIsAttached == true {
|
||||
ui.Say("Detaching temporary role from instance profile...")
|
||||
|
||||
_, err := iamsvc.RemoveRoleFromInstanceProfile(&iam.RemoveRoleFromInstanceProfileInput{
|
||||
InstanceProfileName: aws.String(s.createdInstanceProfileName),
|
||||
RoleName: aws.String(s.createdRoleName),
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error %s. Please delete the role manually: %s", err.Error(), s.createdRoleName))
|
||||
}
|
||||
}
|
||||
|
||||
if s.createdPolicyName != "" {
|
||||
ui.Say("Removing policy from temporary role...")
|
||||
iamsvc.DeleteRolePolicy(&iam.DeleteRolePolicyInput{
|
||||
PolicyName: aws.String(s.createdPolicyName),
|
||||
RoleName: aws.String(s.createdRoleName),
|
||||
})
|
||||
}
|
||||
if s.createdRoleName != "" {
|
||||
ui.Say("Deleting temporary role...")
|
||||
|
||||
_, err = iamsvc.DeleteRole(&iam.DeleteRoleInput{RoleName: &s.createdRoleName})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error %s. Please delete the role manually: %s", err.Error(), s.createdRoleName))
|
||||
}
|
||||
}
|
||||
|
||||
if s.createdInstanceProfileName != "" {
|
||||
ui.Say("Deleting temporary instance profile...")
|
||||
|
||||
_, err = iamsvc.DeleteInstanceProfile(&iam.DeleteInstanceProfileInput{
|
||||
InstanceProfileName: &s.createdInstanceProfileName})
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error %s. Please delete the instance profile manually: %s", err.Error(), s.createdInstanceProfileName))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,20 +5,19 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
confighelper "github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepModifyEBSBackedInstance struct {
|
||||
EnableAMIENASupport confighelper.Trilean
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMISriovNetSupport bool
|
||||
}
|
||||
|
||||
func (s *StepModifyEBSBackedInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(ec2iface.EC2API)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
@@ -41,9 +40,9 @@ func (s *StepModifyEBSBackedInstance) Run(ctx context.Context, state multistep.S
|
||||
|
||||
// Handle EnaSupport flag.
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
if s.EnableAMIENASupport != confighelper.TriUnset {
|
||||
if s.EnableAMIENASupport != nil {
|
||||
var prefix string
|
||||
if s.EnableAMIENASupport.True() {
|
||||
if *s.EnableAMIENASupport {
|
||||
prefix = "En"
|
||||
} else {
|
||||
prefix = "Dis"
|
||||
@@ -51,7 +50,7 @@ func (s *StepModifyEBSBackedInstance) Run(ctx context.Context, state multistep.S
|
||||
ui.Say(fmt.Sprintf("%sabling Enhanced Networking (ENA)...", prefix))
|
||||
_, err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
|
||||
InstanceId: instance.InstanceId,
|
||||
EnaSupport: &ec2.AttributeBooleanValue{Value: s.EnableAMIENASupport.ToBoolPointer()},
|
||||
EnaSupport: &ec2.AttributeBooleanValue{Value: aws.Bool(*s.EnableAMIENASupport)},
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error %sabling Enhanced Networking (ENA) on %s: %s", strings.ToLower(prefix), *instance.InstanceId, err)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"math/rand"
|
||||
"sort"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -52,7 +53,7 @@ func (s *StepNetworkInfo) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
if s.VpcId == "" && !s.VpcFilter.Empty() {
|
||||
params := &ec2.DescribeVpcsInput{}
|
||||
params.Filters = buildEc2Filters(s.VpcFilter.Filters)
|
||||
s.VpcFilter.Filters["state"] = "available"
|
||||
s.VpcFilter.Filters[aws.String("state")] = aws.String("available")
|
||||
|
||||
log.Printf("Using VPC Filters %v", params)
|
||||
|
||||
@@ -78,13 +79,13 @@ func (s *StepNetworkInfo) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
// Subnet
|
||||
if s.SubnetId == "" && !s.SubnetFilter.Empty() {
|
||||
params := &ec2.DescribeSubnetsInput{}
|
||||
s.SubnetFilter.Filters["state"] = "available"
|
||||
s.SubnetFilter.Filters[aws.String("state")] = aws.String("available")
|
||||
|
||||
if s.VpcId != "" {
|
||||
s.SubnetFilter.Filters["vpc-id"] = s.VpcId
|
||||
s.SubnetFilter.Filters[aws.String("vpc-id")] = &s.VpcId
|
||||
}
|
||||
if s.AvailabilityZone != "" {
|
||||
s.SubnetFilter.Filters["availabilityZone"] = s.AvailabilityZone
|
||||
s.SubnetFilter.Filters[aws.String("availability-zone")] = &s.AvailabilityZone
|
||||
}
|
||||
params.Filters = buildEc2Filters(s.SubnetFilter.Filters)
|
||||
log.Printf("Using Subnet Filters %v", params)
|
||||
|
||||
@@ -85,20 +85,19 @@ func (s *StepPreValidate) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
|
||||
ui.Say(fmt.Sprintf("Prevalidating AMI Name: %s", s.DestAmiName))
|
||||
req, resp := ec2conn.DescribeImagesRequest(&ec2.DescribeImagesInput{
|
||||
resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
Filters: []*ec2.Filter{{
|
||||
Name: aws.String("name"),
|
||||
Values: []*string{aws.String(s.DestAmiName)},
|
||||
}}})
|
||||
req.RetryCount = 11
|
||||
|
||||
err := req.Send()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(resp.Images) > 0 {
|
||||
err := fmt.Errorf("Error: AMI Name: '%s' is used by an existing AMI: %s", *resp.Images[0].Name, *resp.Images[0].ImageId)
|
||||
state.Put("error", err)
|
||||
|
||||
@@ -21,13 +21,14 @@ import (
|
||||
|
||||
type StepRunSourceInstance struct {
|
||||
AssociatePublicIpAddress bool
|
||||
LaunchMappings EC2BlockDeviceMappingsBuilder
|
||||
BlockDevices BlockDevices
|
||||
Comm *communicator.Config
|
||||
Ctx interpolate.Context
|
||||
Debug bool
|
||||
EbsOptimized bool
|
||||
EnableT2Unlimited bool
|
||||
ExpectedRootDevice string
|
||||
IamInstanceProfile string
|
||||
InstanceInitiatedShutdownBehavior string
|
||||
InstanceType string
|
||||
IsRestricted bool
|
||||
@@ -44,8 +45,6 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
|
||||
securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string))
|
||||
iamInstanceProfile := aws.String(state.Get("iamInstanceProfile").(string))
|
||||
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
userData := s.UserData
|
||||
@@ -111,8 +110,8 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
UserData: &userData,
|
||||
MaxCount: aws.Int64(1),
|
||||
MinCount: aws.Int64(1),
|
||||
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: iamInstanceProfile},
|
||||
BlockDeviceMappings: s.LaunchMappings.BuildEC2BlockDeviceMappings(),
|
||||
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
|
||||
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
|
||||
Placement: &ec2.Placement{AvailabilityZone: &az},
|
||||
EbsOptimized: &s.EbsOptimized,
|
||||
}
|
||||
@@ -200,34 +199,14 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// there's a race condition that can happen because of AWS's eventual
|
||||
// consistency where even though the wait is complete, the describe call
|
||||
// will fail. Retry a couple of times to try to mitigate that race.
|
||||
r, err := ec2conn.DescribeInstances(describeInstance)
|
||||
|
||||
var r *ec2.DescribeInstancesOutput
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(err error) bool {
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
switch awsErr.Code() {
|
||||
case "InvalidInstanceID.NotFound":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) error {
|
||||
r, err = ec2conn.DescribeInstances(describeInstance)
|
||||
return err
|
||||
})
|
||||
if err != nil || len(r.Reservations) == 0 || len(r.Reservations[0].Instances) == 0 {
|
||||
err := fmt.Errorf("Error finding source instance.")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
instance := r.Reservations[0].Instances[0]
|
||||
|
||||
if s.Debug {
|
||||
|
||||
@@ -6,12 +6,14 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strconv"
|
||||
"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/common/random"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
@@ -19,22 +21,20 @@ import (
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type EC2BlockDeviceMappingsBuilder interface {
|
||||
BuildEC2BlockDeviceMappings() []*ec2.BlockDeviceMapping
|
||||
}
|
||||
|
||||
type StepRunSpotInstance struct {
|
||||
AssociatePublicIpAddress bool
|
||||
LaunchMappings EC2BlockDeviceMappingsBuilder
|
||||
BlockDevices BlockDevices
|
||||
BlockDurationMinutes int64
|
||||
Debug bool
|
||||
Comm *communicator.Config
|
||||
EbsOptimized bool
|
||||
ExpectedRootDevice string
|
||||
IamInstanceProfile string
|
||||
InstanceInitiatedShutdownBehavior string
|
||||
InstanceType string
|
||||
SourceAMI string
|
||||
SpotPrice string
|
||||
SpotPriceProduct string
|
||||
SpotTags TagMap
|
||||
SpotInstanceTypes []string
|
||||
Tags TagMap
|
||||
@@ -43,7 +43,57 @@ type StepRunSpotInstance struct {
|
||||
UserDataFile string
|
||||
Ctx interpolate.Context
|
||||
|
||||
instanceId string
|
||||
instanceId string
|
||||
spotRequest *ec2.SpotInstanceRequest
|
||||
}
|
||||
|
||||
func (s *StepRunSpotInstance) CalculateSpotPrice(az string, ec2conn ec2iface.EC2API) (string, error) {
|
||||
// Calculate the spot price for a given availability zone
|
||||
spotPrice := s.SpotPrice
|
||||
|
||||
if spotPrice == "auto" {
|
||||
// Detect the spot price
|
||||
startTime := time.Now().Add(-1 * time.Hour)
|
||||
resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistoryInput{
|
||||
InstanceTypes: []*string{&s.InstanceType},
|
||||
ProductDescriptions: []*string{&s.SpotPriceProduct},
|
||||
AvailabilityZone: &az,
|
||||
StartTime: &startTime,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error finding spot price: %s", err)
|
||||
}
|
||||
|
||||
var price float64
|
||||
for _, history := range resp.SpotPriceHistory {
|
||||
log.Printf("[INFO] Candidate spot price: %s", *history.SpotPrice)
|
||||
current, err := strconv.ParseFloat(*history.SpotPrice, 64)
|
||||
if err != nil {
|
||||
log.Printf("[ERR] Error parsing spot price: %s", err)
|
||||
continue
|
||||
}
|
||||
if price == 0 || current < price {
|
||||
price = current
|
||||
if az == "" {
|
||||
az = *history.AvailabilityZone
|
||||
}
|
||||
}
|
||||
}
|
||||
if price == 0 {
|
||||
return "", fmt.Errorf("No candidate spot prices found!")
|
||||
} else {
|
||||
// Add 0.5 cents to minimum spot bid to ensure capacity will be available
|
||||
// Avoids price-too-low error in active markets which can fluctuate
|
||||
price = price + 0.005
|
||||
}
|
||||
|
||||
spotPrice = strconv.FormatFloat(price, 'f', -1, 64)
|
||||
}
|
||||
|
||||
s.SpotPrice = spotPrice
|
||||
|
||||
return spotPrice, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,
|
||||
@@ -56,7 +106,7 @@ func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,
|
||||
// LaunchTemplateEbsBlockDeviceRequest structs are themselves
|
||||
// identical except for the struct's name, so you can cast one directly
|
||||
// into the other.
|
||||
blockDeviceMappings := s.LaunchMappings.BuildEC2BlockDeviceMappings()
|
||||
blockDeviceMappings := s.BlockDevices.BuildLaunchDevices()
|
||||
var launchMappingRequests []*ec2.LaunchTemplateBlockDeviceMappingRequest
|
||||
for _, mapping := range blockDeviceMappings {
|
||||
launchRequest := &ec2.LaunchTemplateBlockDeviceMappingRequest{
|
||||
@@ -68,14 +118,12 @@ func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,
|
||||
launchMappingRequests = append(launchMappingRequests, launchRequest)
|
||||
}
|
||||
|
||||
iamInstanceProfile := aws.String(state.Get("iamInstanceProfile").(string))
|
||||
|
||||
// Create a launch template.
|
||||
templateData := ec2.RequestLaunchTemplateData{
|
||||
BlockDeviceMappings: launchMappingRequests,
|
||||
DisableApiTermination: aws.Bool(false),
|
||||
EbsOptimized: &s.EbsOptimized,
|
||||
IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{Name: iamInstanceProfile},
|
||||
IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{Name: &s.IamInstanceProfile},
|
||||
ImageId: &s.SourceAMI,
|
||||
InstanceMarketOptions: marketOptions,
|
||||
Placement: &ec2.LaunchTemplatePlacementRequest{
|
||||
@@ -164,6 +212,17 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
}
|
||||
az := azConfig
|
||||
|
||||
ui.Message(fmt.Sprintf("Finding spot price for %s %s...",
|
||||
s.SpotPriceProduct, s.InstanceType))
|
||||
spotPrice, err := s.CalculateSpotPrice(az, ec2conn)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Determined spot instance price of: %s.", spotPrice))
|
||||
|
||||
var instanceId string
|
||||
|
||||
ui.Say("Interpolating tags for spot instance...")
|
||||
@@ -187,10 +246,8 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
// instance yet
|
||||
ec2Tags.Report(ui)
|
||||
|
||||
spotOptions := ec2.LaunchTemplateSpotMarketOptionsRequest{}
|
||||
// The default is to set the maximum price to the OnDemand price.
|
||||
if s.SpotPrice != "auto" {
|
||||
spotOptions.SetMaxPrice(s.SpotPrice)
|
||||
spotOptions := ec2.LaunchTemplateSpotMarketOptionsRequest{
|
||||
MaxPrice: &s.SpotPrice,
|
||||
}
|
||||
if s.BlockDurationMinutes != 0 {
|
||||
spotOptions.BlockDurationMinutes = &s.BlockDurationMinutes
|
||||
@@ -202,14 +259,6 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
|
||||
// Create a launch template for the instance
|
||||
ui.Message("Loading User Data File...")
|
||||
|
||||
// Generate a random name to avoid conflicting with other
|
||||
// instances of packer running in this AWS account
|
||||
launchTemplateName := fmt.Sprintf(
|
||||
"packer-fleet-launch-template-%s",
|
||||
random.AlphaNum(7))
|
||||
state.Put("launchTemplateName", launchTemplateName) // For the cleanup step
|
||||
|
||||
userData, err := s.LoadUserData()
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
@@ -219,7 +268,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
templateData := s.CreateTemplateData(&userData, az, state, marketOptions)
|
||||
launchTemplate := &ec2.CreateLaunchTemplateInput{
|
||||
LaunchTemplateData: templateData,
|
||||
LaunchTemplateName: aws.String(launchTemplateName),
|
||||
LaunchTemplateName: aws.String("packer-fleet-launch-template"),
|
||||
VersionDescription: aws.String("template generated by packer for launching spot instances"),
|
||||
}
|
||||
|
||||
@@ -245,7 +294,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
LaunchTemplateConfigs: []*ec2.FleetLaunchTemplateConfigRequest{
|
||||
{
|
||||
LaunchTemplateSpecification: &ec2.FleetLaunchTemplateSpecificationRequest{
|
||||
LaunchTemplateName: aws.String(launchTemplateName),
|
||||
LaunchTemplateName: aws.String("packer-fleet-launch-template"),
|
||||
Version: aws.String("1"),
|
||||
},
|
||||
Overrides: overrides,
|
||||
@@ -262,60 +311,6 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
// Create the request for the spot instance.
|
||||
req, createOutput := ec2conn.CreateFleetRequest(createFleetInput)
|
||||
ui.Message(fmt.Sprintf("Sending spot request (%s)...", req.RequestID))
|
||||
// Actually send the spot connection request.
|
||||
err = req.Send()
|
||||
if err != nil {
|
||||
if createOutput.FleetId != nil {
|
||||
err = fmt.Errorf("Error waiting for fleet request (%s): %s", *createOutput.FleetId, err)
|
||||
} else {
|
||||
err = fmt.Errorf("Error waiting for fleet request: %s", err)
|
||||
}
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(createOutput.Instances) == 0 {
|
||||
// We can end up with errors because one of the allowed availability
|
||||
// zones doesn't have one of the allowed instance types; as long as
|
||||
// an instance is launched, these errors aren't important.
|
||||
if len(createOutput.Errors) > 0 {
|
||||
errString := fmt.Sprintf("Error waiting for fleet request (%s) to become ready:", *createOutput.FleetId)
|
||||
for _, outErr := range createOutput.Errors {
|
||||
errString = errString + fmt.Sprintf("%s", *outErr.ErrorMessage)
|
||||
}
|
||||
err = fmt.Errorf(errString)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
instanceId = *createOutput.Instances[0].InstanceIds[0]
|
||||
// Set the instance ID so that the cleanup works properly
|
||||
s.instanceId = instanceId
|
||||
|
||||
ui.Message(fmt.Sprintf("Instance ID: %s", instanceId))
|
||||
|
||||
// Get information about the created instance
|
||||
var describeOutput *ec2.DescribeInstancesOutput
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) error {
|
||||
describeOutput, err = ec2conn.DescribeInstances(&ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{aws.String(instanceId)},
|
||||
})
|
||||
return err
|
||||
})
|
||||
if err != nil || len(describeOutput.Reservations) == 0 || len(describeOutput.Reservations[0].Instances) == 0 {
|
||||
err := fmt.Errorf("Error finding source instance.")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
instance := describeOutput.Reservations[0].Instances[0]
|
||||
|
||||
// Tag the spot instance request (not the eventual spot instance)
|
||||
spotTags, err := s.SpotTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||
@@ -325,14 +320,9 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
spotTags.Report(ui)
|
||||
|
||||
if len(spotTags) > 0 && s.SpotTags.IsSet() {
|
||||
spotTags.Report(ui)
|
||||
// Use the instance ID to find out the SIR, so that we can tag the spot
|
||||
// request associated with this instance.
|
||||
sir := describeOutput.Reservations[0].Instances[0].SpotInstanceRequestId
|
||||
|
||||
// Apply tags to the spot request.
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(error) bool { return false },
|
||||
@@ -340,7 +330,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
}.Run(ctx, func(ctx context.Context) error {
|
||||
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||
Tags: spotTags,
|
||||
Resources: []*string{sir},
|
||||
Resources: []*string{aws.String(req.RequestID)},
|
||||
})
|
||||
return err
|
||||
})
|
||||
@@ -352,6 +342,40 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
}
|
||||
}
|
||||
|
||||
// Actually send the spot connection request.
|
||||
err = req.Send()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for spot request (%s) to become ready: %s", req.RequestID, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(createOutput.Errors) > 0 {
|
||||
err := fmt.Errorf("error sending spot request: %s", *createOutput.Errors[0])
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
instanceId = *createOutput.Instances[0].InstanceIds[0]
|
||||
|
||||
// Set the instance ID so that the cleanup works properly
|
||||
s.instanceId = instanceId
|
||||
|
||||
ui.Message(fmt.Sprintf("Instance ID: %s", instanceId))
|
||||
|
||||
r, err := ec2conn.DescribeInstances(&ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{aws.String(instanceId)},
|
||||
})
|
||||
if err != nil || len(r.Reservations) == 0 || len(r.Reservations[0].Instances) == 0 {
|
||||
err := fmt.Errorf("Error finding source instance.")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
instance := r.Reservations[0].Instances[0]
|
||||
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
@@ -433,9 +457,27 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
}
|
||||
|
||||
func (s *StepRunSpotInstance) Cleanup(state multistep.StateBag) {
|
||||
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
launchTemplateName := state.Get("launchTemplateName").(string)
|
||||
|
||||
// Cancel the spot request if it exists
|
||||
if s.spotRequest != nil {
|
||||
ui.Say("Cancelling the spot request...")
|
||||
input := &ec2.CancelSpotInstanceRequestsInput{
|
||||
SpotInstanceRequestIds: []*string{s.spotRequest.SpotInstanceRequestId},
|
||||
}
|
||||
if _, err := ec2conn.CancelSpotInstanceRequests(input); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error cancelling the spot request, may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
err := WaitUntilSpotRequestFulfilled(aws.BackgroundContext(), ec2conn, *s.spotRequest.SpotInstanceRequestId)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Terminate the source instance if it exists
|
||||
if s.instanceId != "" {
|
||||
@@ -452,7 +494,7 @@ func (s *StepRunSpotInstance) Cleanup(state multistep.StateBag) {
|
||||
|
||||
// Delete the launch template used to create the spot fleet
|
||||
deleteInput := &ec2.DeleteLaunchTemplateInput{
|
||||
LaunchTemplateName: aws.String(launchTemplateName),
|
||||
LaunchTemplateName: aws.String("packer-fleet-launch-template"),
|
||||
}
|
||||
if _, err := ec2conn.DeleteLaunchTemplate(deleteInput); err != nil {
|
||||
ui.Error(err.Error())
|
||||
|
||||
@@ -2,16 +2,69 @@ package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// Define a mock struct to be used in unit tests for common aws steps.
|
||||
type mockEC2ConnSpot struct {
|
||||
ec2iface.EC2API
|
||||
Config *aws.Config
|
||||
|
||||
// Counters to figure out what code path was taken
|
||||
describeSpotPriceHistoryCount int
|
||||
}
|
||||
|
||||
// Generates fake SpotPriceHistory data and returns it in the expected output
|
||||
// format. Also increments a
|
||||
func (m *mockEC2ConnSpot) DescribeSpotPriceHistory(copyInput *ec2.DescribeSpotPriceHistoryInput) (*ec2.DescribeSpotPriceHistoryOutput, error) {
|
||||
m.describeSpotPriceHistoryCount++
|
||||
testTime := time.Now().Add(-1 * time.Hour)
|
||||
sp := []*ec2.SpotPrice{
|
||||
{
|
||||
AvailabilityZone: aws.String("us-east-1c"),
|
||||
InstanceType: aws.String("t2.micro"),
|
||||
ProductDescription: aws.String("Linux/UNIX"),
|
||||
SpotPrice: aws.String("0.003500"),
|
||||
Timestamp: &testTime,
|
||||
},
|
||||
{
|
||||
AvailabilityZone: aws.String("us-east-1f"),
|
||||
InstanceType: aws.String("t2.micro"),
|
||||
ProductDescription: aws.String("Linux/UNIX"),
|
||||
SpotPrice: aws.String("0.003500"),
|
||||
Timestamp: &testTime,
|
||||
},
|
||||
{
|
||||
AvailabilityZone: aws.String("us-east-1b"),
|
||||
InstanceType: aws.String("t2.micro"),
|
||||
ProductDescription: aws.String("Linux/UNIX"),
|
||||
SpotPrice: aws.String("0.003500"),
|
||||
Timestamp: &testTime,
|
||||
},
|
||||
}
|
||||
output := &ec2.DescribeSpotPriceHistoryOutput{SpotPriceHistory: sp}
|
||||
|
||||
return output, nil
|
||||
|
||||
}
|
||||
|
||||
func getMockConnSpot() ec2iface.EC2API {
|
||||
mockConn := &mockEC2ConnSpot{
|
||||
Config: aws.NewConfig(),
|
||||
}
|
||||
|
||||
return mockConn
|
||||
}
|
||||
|
||||
// Create statebag for running test
|
||||
func tStateSpot() multistep.StateBag {
|
||||
state := new(multistep.BasicStateBag)
|
||||
@@ -21,7 +74,6 @@ func tStateSpot() multistep.StateBag {
|
||||
})
|
||||
state.Put("availability_zone", "us-east-1c")
|
||||
state.Put("securityGroupIds", []string{"sg-0b8984db72f213dc3"})
|
||||
state.Put("iamInstanceProfile", "packer-123")
|
||||
state.Put("subnet_id", "subnet-077fde4e")
|
||||
state.Put("source_image", "")
|
||||
return state
|
||||
@@ -30,20 +82,27 @@ func tStateSpot() multistep.StateBag {
|
||||
func getBasicStep() *StepRunSpotInstance {
|
||||
stepRunSpotInstance := StepRunSpotInstance{
|
||||
AssociatePublicIpAddress: false,
|
||||
LaunchMappings: BlockDevices{},
|
||||
BlockDurationMinutes: 0,
|
||||
Debug: false,
|
||||
Comm: &communicator.Config{
|
||||
SSH: communicator.SSH{
|
||||
SSHKeyPairName: "foo",
|
||||
BlockDevices: BlockDevices{
|
||||
AMIBlockDevices: AMIBlockDevices{
|
||||
AMIMappings: []BlockDevice(nil),
|
||||
},
|
||||
LaunchBlockDevices: LaunchBlockDevices{
|
||||
LaunchMappings: []BlockDevice(nil),
|
||||
},
|
||||
},
|
||||
BlockDurationMinutes: 0,
|
||||
Debug: false,
|
||||
Comm: &communicator.Config{
|
||||
SSHKeyPairName: "foo",
|
||||
},
|
||||
EbsOptimized: false,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: "",
|
||||
InstanceInitiatedShutdownBehavior: "stop",
|
||||
InstanceType: "t2.micro",
|
||||
SourceAMI: "",
|
||||
SpotPrice: "auto",
|
||||
SpotPriceProduct: "Linux/UNIX",
|
||||
SpotTags: TagMap(nil),
|
||||
Tags: TagMap{},
|
||||
VolumeTags: TagMap(nil),
|
||||
@@ -53,44 +112,20 @@ func getBasicStep() *StepRunSpotInstance {
|
||||
|
||||
return &stepRunSpotInstance
|
||||
}
|
||||
|
||||
func TestCreateTemplateData(t *testing.T) {
|
||||
state := tStateSpot()
|
||||
func TestCalculateSpotPrice(t *testing.T) {
|
||||
stepRunSpotInstance := getBasicStep()
|
||||
template := stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
|
||||
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
|
||||
|
||||
// expected := []*ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{
|
||||
// &ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{
|
||||
// DeleteOnTermination: aws.Bool(true),
|
||||
// DeviceIndex: aws.Int64(0),
|
||||
// Groups: aws.StringSlice([]string{"sg-0b8984db72f213dc3"}),
|
||||
// SubnetId: aws.String("subnet-077fde4e"),
|
||||
// },
|
||||
// }
|
||||
// if expected != template.NetworkInterfaces {
|
||||
if template.NetworkInterfaces == nil {
|
||||
t.Fatalf("Template should have contained a networkInterface object: recieved %#v", template.NetworkInterfaces)
|
||||
// Set spot price and spot price product
|
||||
stepRunSpotInstance.SpotPrice = "auto"
|
||||
stepRunSpotInstance.SpotPriceProduct = "Linux/UNIX"
|
||||
ec2conn := getMockConnSpot()
|
||||
// state := tStateSpot()
|
||||
spotPrice, err := stepRunSpotInstance.CalculateSpotPrice("", ec2conn)
|
||||
if err != nil {
|
||||
t.Fatalf("Should not have had an error calculating spot price")
|
||||
}
|
||||
|
||||
if *template.IamInstanceProfile.Name != state.Get("iamInstanceProfile") {
|
||||
t.Fatalf("Template should have contained a InstanceProfile name: recieved %#v", template.IamInstanceProfile.Name)
|
||||
}
|
||||
|
||||
// Rerun, this time testing that we set security group IDs
|
||||
state.Put("subnet_id", "")
|
||||
template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
|
||||
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
|
||||
if template.NetworkInterfaces != nil {
|
||||
t.Fatalf("Template shouldn't contain network interfaces object if subnet_id is unset.")
|
||||
}
|
||||
|
||||
// Rerun, this time testing that instance doesn't have instance profile is iamInstanceProfile is unset
|
||||
state.Put("iamInstanceProfile", "")
|
||||
template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
|
||||
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
|
||||
fmt.Println(template.IamInstanceProfile)
|
||||
if *template.IamInstanceProfile.Name != "" {
|
||||
t.Fatalf("Template shouldn't contain instance profile if iamInstanceProfile is unset.")
|
||||
sp, _ := strconv.ParseFloat(spotPrice, 64)
|
||||
expected := 0.008500
|
||||
if sp != expected { // 0.003500 (from spot history) + .005
|
||||
t.Fatalf("Expected spot price of \"0.008500\", not %s", spotPrice)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func (s *StepSecurityGroup) Run(ctx context.Context, state multistep.StateBag) m
|
||||
|
||||
params := &ec2.DescribeSecurityGroupsInput{}
|
||||
if vpcId != "" {
|
||||
s.SecurityGroupFilter.Filters["vpc-id"] = vpcId
|
||||
s.SecurityGroupFilter.Filters[aws.String("vpc-id")] = &vpcId
|
||||
}
|
||||
params.Filters = buildEc2Filters(s.SecurityGroupFilter.Filters)
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
confighelper "github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
@@ -21,7 +20,7 @@ import (
|
||||
type StepSourceAMIInfo struct {
|
||||
SourceAmi string
|
||||
EnableAMISriovNetSupport bool
|
||||
EnableAMIENASupport confighelper.Trilean
|
||||
EnableAMIENASupport *bool
|
||||
AMIVirtType string
|
||||
AmiFilters AmiFilterOptions
|
||||
}
|
||||
@@ -58,7 +57,7 @@ func (s *StepSourceAMIInfo) Run(ctx context.Context, state multistep.StateBag) m
|
||||
params.Filters = buildEc2Filters(s.AmiFilters.Filters)
|
||||
}
|
||||
if len(s.AmiFilters.Owners) > 0 {
|
||||
params.Owners = s.AmiFilters.GetOwners()
|
||||
params.Owners = s.AmiFilters.Owners
|
||||
}
|
||||
|
||||
log.Printf("Using AMI Filters %v", params)
|
||||
@@ -95,7 +94,7 @@ func (s *StepSourceAMIInfo) Run(ctx context.Context, state multistep.StateBag) m
|
||||
|
||||
// Enhanced Networking can only be enabled on HVM AMIs.
|
||||
// See http://goo.gl/icuXh5
|
||||
if s.EnableAMIENASupport.True() || s.EnableAMISriovNetSupport {
|
||||
if (s.EnableAMIENASupport != nil && *s.EnableAMIENASupport) || s.EnableAMISriovNetSupport {
|
||||
err = s.canEnableEnhancedNetworking(image)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
// The amazonebs package contains a packer.Builder implementation that
|
||||
// builds AMIs for Amazon EC2.
|
||||
//
|
||||
@@ -13,7 +10,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
@@ -30,29 +26,9 @@ type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
awscommon.AMIConfig `mapstructure:",squash"`
|
||||
awscommon.BlockDevices `mapstructure:",squash"`
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
// Add one or more block device mappings to the AMI. These will be attached
|
||||
// when booting a new instance from your AMI. To add a block device during
|
||||
// the Packer build see `launch_block_device_mappings` below. Your options
|
||||
// here may vary depending on the type of VM you use. See the
|
||||
// [BlockDevices](#block-devices-configuration) documentation for fields.
|
||||
AMIMappings awscommon.BlockDevices `mapstructure:"ami_block_device_mappings" required:"false"`
|
||||
// Add one or more block devices before the Packer build starts. If you add
|
||||
// instance store volumes or EBS volumes in addition to the root device
|
||||
// volume, the created AMI will contain block device mapping information
|
||||
// for those volumes. Amazon creates snapshots of the source instance's
|
||||
// root volume and any other EBS volumes described here. When you launch an
|
||||
// instance from this new AMI, the instance automatically launches with
|
||||
// these additional volumes, and will restore them from snapshots taken
|
||||
// from the source instance. See the
|
||||
// [BlockDevices](#block-devices-configuration) documentation for fields.
|
||||
LaunchMappings awscommon.BlockDevices `mapstructure:"launch_block_device_mappings" required:"false"`
|
||||
// Tags to apply to the volumes that are *launched* to create the AMI.
|
||||
// These tags are *not* applied to the resulting AMI unless they're
|
||||
// duplicated in `tags`. This is a [template
|
||||
// engine](/docs/templates/engine.html), see [Build template
|
||||
// data](#build-template-data) for more information.
|
||||
VolumeRunTags awscommon.TagMap `mapstructure:"run_volume_tags"`
|
||||
VolumeRunTags awscommon.TagMap `mapstructure:"run_volume_tags"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
@@ -88,36 +64,25 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
|
||||
// Accumulate any errors
|
||||
var errs *packer.MultiError
|
||||
var warns []string
|
||||
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
b.config.AMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AMIMappings.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.LaunchMappings.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
|
||||
|
||||
if b.config.IsSpotInstance() && (b.config.AMIENASupport.True() || b.config.AMISriovNetSupport) {
|
||||
if b.config.IsSpotInstance() && ((b.config.AMIENASupport != nil && *b.config.AMIENASupport) || b.config.AMISriovNetSupport) {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Spot instances do not support modification, which is required "+
|
||||
"when either `ena_support` or `sriov_support` are set. Please ensure "+
|
||||
"you use an AMI that already has either SR-IOV or ENA enabled."))
|
||||
}
|
||||
|
||||
if b.config.RunConfig.SpotPriceAutoProduct != "" {
|
||||
warns = append(warns, "spot_price_auto_product is deprecated and no "+
|
||||
"longer necessary for Packer builds. In future versions of "+
|
||||
"Packer, inclusion of spot_price_auto_product will error your "+
|
||||
"builds. Please take a look at our current documentation to "+
|
||||
"understand how Packer requests Spot instances.")
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return warns, errs
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
return warns, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
@@ -128,14 +93,13 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
}
|
||||
|
||||
ec2conn := ec2.New(session)
|
||||
iam := iam.New(session)
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("access_config", &b.config.AccessConfig)
|
||||
state.Put("ami_config", &b.config.AMIConfig)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("iam", iam)
|
||||
state.Put("awsSession", session)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
@@ -145,17 +109,19 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
if b.config.IsSpotInstance() {
|
||||
instanceStep = &awscommon.StepRunSpotInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
LaunchMappings: b.config.LaunchMappings,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
||||
Ctx: b.config.ctx,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Debug: b.config.PackerDebug,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||
SpotTags: b.config.SpotTags,
|
||||
Tags: b.config.RunTags,
|
||||
SpotInstanceTypes: b.config.SpotInstanceTypes,
|
||||
@@ -166,13 +132,14 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
} else {
|
||||
instanceStep = &awscommon.StepRunSourceInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
LaunchMappings: b.config.LaunchMappings,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
Debug: b.config.PackerDebug,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
EnableT2Unlimited: b.config.EnableT2Unlimited,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
@@ -218,12 +185,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs,
|
||||
},
|
||||
&awscommon.StepIamInstanceProfile{
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
|
||||
},
|
||||
&awscommon.StepCleanupVolumes{
|
||||
LaunchMappings: b.config.LaunchMappings,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
},
|
||||
instanceStep,
|
||||
&awscommon.StepGetPassword{
|
||||
@@ -236,9 +199,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: awscommon.SSHHost(
|
||||
ec2conn,
|
||||
b.config.SSHInterface,
|
||||
b.config.Comm.SSHHost,
|
||||
),
|
||||
b.config.Comm.SSHInterface),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
package ebs
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatConfig struct {
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"`
|
||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
|
||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
|
||||
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key"`
|
||||
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2"`
|
||||
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages"`
|
||||
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify"`
|
||||
MFACode *string `mapstructure:"mfa_code" required:"false" cty:"mfa_code"`
|
||||
ProfileName *string `mapstructure:"profile" required:"false" cty:"profile"`
|
||||
RawRegion *string `mapstructure:"region" required:"true" cty:"region"`
|
||||
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key"`
|
||||
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"`
|
||||
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check"`
|
||||
Token *string `mapstructure:"token" required:"false" cty:"token"`
|
||||
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine"`
|
||||
AMIName *string `mapstructure:"ami_name" required:"true" cty:"ami_name"`
|
||||
AMIDescription *string `mapstructure:"ami_description" required:"false" cty:"ami_description"`
|
||||
AMIVirtType *string `mapstructure:"ami_virtualization_type" required:"false" cty:"ami_virtualization_type"`
|
||||
AMIUsers []string `mapstructure:"ami_users" required:"false" cty:"ami_users"`
|
||||
AMIGroups []string `mapstructure:"ami_groups" required:"false" cty:"ami_groups"`
|
||||
AMIProductCodes []string `mapstructure:"ami_product_codes" required:"false" cty:"ami_product_codes"`
|
||||
AMIRegions []string `mapstructure:"ami_regions" required:"false" cty:"ami_regions"`
|
||||
AMITags common.TagMap `mapstructure:"tags" required:"false" cty:"tags"`
|
||||
AMIENASupport *bool `mapstructure:"ena_support" required:"false" cty:"ena_support"`
|
||||
AMISriovNetSupport *bool `mapstructure:"sriov_support" required:"false" cty:"sriov_support"`
|
||||
AMIForceDeregister *bool `mapstructure:"force_deregister" required:"false" cty:"force_deregister"`
|
||||
AMIForceDeleteSnapshot *bool `mapstructure:"force_delete_snapshot" required:"false" cty:"force_delete_snapshot"`
|
||||
AMIEncryptBootVolume *bool `mapstructure:"encrypt_boot" required:"false" cty:"encrypt_boot"`
|
||||
AMIKmsKeyId *string `mapstructure:"kms_key_id" required:"false" cty:"kms_key_id"`
|
||||
AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids" required:"false" cty:"region_kms_key_ids"`
|
||||
AMISkipBuildRegion *bool `mapstructure:"skip_save_build_region" cty:"skip_save_build_region"`
|
||||
SnapshotTags common.TagMap `mapstructure:"snapshot_tags" required:"false" cty:"snapshot_tags"`
|
||||
SnapshotUsers []string `mapstructure:"snapshot_users" required:"false" cty:"snapshot_users"`
|
||||
SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false" cty:"snapshot_groups"`
|
||||
AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" required:"false" cty:"associate_public_ip_address"`
|
||||
AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone"`
|
||||
BlockDurationMinutes *int64 `mapstructure:"block_duration_minutes" required:"false" cty:"block_duration_minutes"`
|
||||
DisableStopInstance *bool `mapstructure:"disable_stop_instance" required:"false" cty:"disable_stop_instance"`
|
||||
EbsOptimized *bool `mapstructure:"ebs_optimized" required:"false" cty:"ebs_optimized"`
|
||||
EnableT2Unlimited *bool `mapstructure:"enable_t2_unlimited" required:"false" cty:"enable_t2_unlimited"`
|
||||
IamInstanceProfile *string `mapstructure:"iam_instance_profile" required:"false" cty:"iam_instance_profile"`
|
||||
TemporaryIamInstanceProfilePolicyDocument *common.FlatPolicyDocument `mapstructure:"temporary_iam_instance_profile_policy_document" required:"false" cty:"temporary_iam_instance_profile_policy_document"`
|
||||
InstanceInitiatedShutdownBehavior *string `mapstructure:"shutdown_behavior" required:"false" cty:"shutdown_behavior"`
|
||||
InstanceType *string `mapstructure:"instance_type" required:"true" cty:"instance_type"`
|
||||
SecurityGroupFilter *common.FlatSecurityGroupFilterOptions `mapstructure:"security_group_filter" required:"false" cty:"security_group_filter"`
|
||||
RunTags map[string]string `mapstructure:"run_tags" required:"false" cty:"run_tags"`
|
||||
SecurityGroupId *string `mapstructure:"security_group_id" required:"false" cty:"security_group_id"`
|
||||
SecurityGroupIds []string `mapstructure:"security_group_ids" required:"false" cty:"security_group_ids"`
|
||||
SourceAmi *string `mapstructure:"source_ami" required:"true" cty:"source_ami"`
|
||||
SourceAmiFilter *common.FlatAmiFilterOptions `mapstructure:"source_ami_filter" required:"false" cty:"source_ami_filter"`
|
||||
SpotInstanceTypes []string `mapstructure:"spot_instance_types" required:"false" cty:"spot_instance_types"`
|
||||
SpotPrice *string `mapstructure:"spot_price" required:"false" cty:"spot_price"`
|
||||
SpotPriceAutoProduct *string `mapstructure:"spot_price_auto_product" required:"false" cty:"spot_price_auto_product"`
|
||||
SpotTags map[string]string `mapstructure:"spot_tags" required:"false" cty:"spot_tags"`
|
||||
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter"`
|
||||
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id"`
|
||||
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name"`
|
||||
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs"`
|
||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data"`
|
||||
UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file"`
|
||||
VpcFilter *common.FlatVpcFilterOptions `mapstructure:"vpc_filter" required:"false" cty:"vpc_filter"`
|
||||
VpcId *string `mapstructure:"vpc_id" required:"false" cty:"vpc_id"`
|
||||
WindowsPasswordTimeout *string `mapstructure:"windows_password_timeout" required:"false" cty:"windows_password_timeout"`
|
||||
Type *string `mapstructure:"communicator" cty:"communicator"`
|
||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"`
|
||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"`
|
||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"`
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"`
|
||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"`
|
||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"`
|
||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"`
|
||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"`
|
||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"`
|
||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"`
|
||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"`
|
||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"`
|
||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"`
|
||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"`
|
||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"`
|
||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"`
|
||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"`
|
||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"`
|
||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"`
|
||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"`
|
||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"`
|
||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"`
|
||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"`
|
||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"`
|
||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"`
|
||||
SSHPublicKey []byte `cty:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `cty:"ssh_private_key"`
|
||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"`
|
||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"`
|
||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"`
|
||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"`
|
||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"`
|
||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
|
||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
|
||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
|
||||
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface"`
|
||||
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" required:"false" cty:"ami_block_device_mappings"`
|
||||
LaunchMappings []common.FlatBlockDevice `mapstructure:"launch_block_device_mappings" required:"false" cty:"launch_block_device_mappings"`
|
||||
VolumeRunTags common.TagMap `mapstructure:"run_volume_tags" cty:"run_volume_tags"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) }
|
||||
|
||||
// HCL2Spec returns the hcldec.Spec of a FlatConfig.
|
||||
// This spec is used by HCL to read the fields of FlatConfig.
|
||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
||||
"packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false},
|
||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
||||
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
|
||||
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
|
||||
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
|
||||
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
|
||||
"mfa_code": &hcldec.AttrSpec{Name: "mfa_code", Type: cty.String, Required: false},
|
||||
"profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false},
|
||||
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
|
||||
"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},
|
||||
"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())},
|
||||
"ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false},
|
||||
"ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false},
|
||||
"ami_virtualization_type": &hcldec.AttrSpec{Name: "ami_virtualization_type", Type: cty.String, Required: false},
|
||||
"ami_users": &hcldec.AttrSpec{Name: "ami_users", Type: cty.List(cty.String), Required: false},
|
||||
"ami_groups": &hcldec.AttrSpec{Name: "ami_groups", Type: cty.List(cty.String), Required: false},
|
||||
"ami_product_codes": &hcldec.AttrSpec{Name: "ami_product_codes", Type: cty.List(cty.String), Required: false},
|
||||
"ami_regions": &hcldec.AttrSpec{Name: "ami_regions", Type: cty.List(cty.String), Required: false},
|
||||
"tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false},
|
||||
"ena_support": &hcldec.AttrSpec{Name: "ena_support", Type: cty.Bool, Required: false},
|
||||
"sriov_support": &hcldec.AttrSpec{Name: "sriov_support", Type: cty.Bool, Required: false},
|
||||
"force_deregister": &hcldec.AttrSpec{Name: "force_deregister", Type: cty.Bool, Required: false},
|
||||
"force_delete_snapshot": &hcldec.AttrSpec{Name: "force_delete_snapshot", Type: cty.Bool, Required: false},
|
||||
"encrypt_boot": &hcldec.AttrSpec{Name: "encrypt_boot", Type: cty.Bool, Required: false},
|
||||
"kms_key_id": &hcldec.AttrSpec{Name: "kms_key_id", Type: cty.String, Required: false},
|
||||
"region_kms_key_ids": &hcldec.BlockAttrsSpec{TypeName: "region_kms_key_ids", ElementType: cty.String, Required: false},
|
||||
"skip_save_build_region": &hcldec.AttrSpec{Name: "skip_save_build_region", Type: cty.Bool, Required: false},
|
||||
"snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false},
|
||||
"snapshot_users": &hcldec.AttrSpec{Name: "snapshot_users", Type: cty.List(cty.String), Required: false},
|
||||
"snapshot_groups": &hcldec.AttrSpec{Name: "snapshot_groups", Type: cty.List(cty.String), Required: false},
|
||||
"associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false},
|
||||
"availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false},
|
||||
"block_duration_minutes": &hcldec.AttrSpec{Name: "block_duration_minutes", Type: cty.Number, Required: false},
|
||||
"disable_stop_instance": &hcldec.AttrSpec{Name: "disable_stop_instance", Type: cty.Bool, Required: false},
|
||||
"ebs_optimized": &hcldec.AttrSpec{Name: "ebs_optimized", Type: cty.Bool, Required: false},
|
||||
"enable_t2_unlimited": &hcldec.AttrSpec{Name: "enable_t2_unlimited", Type: cty.Bool, Required: false},
|
||||
"iam_instance_profile": &hcldec.AttrSpec{Name: "iam_instance_profile", Type: cty.String, Required: false},
|
||||
"temporary_iam_instance_profile_policy_document": &hcldec.BlockSpec{TypeName: "temporary_iam_instance_profile_policy_document", Nested: hcldec.ObjectSpec((*common.FlatPolicyDocument)(nil).HCL2Spec())},
|
||||
"shutdown_behavior": &hcldec.AttrSpec{Name: "shutdown_behavior", Type: cty.String, Required: false},
|
||||
"instance_type": &hcldec.AttrSpec{Name: "instance_type", Type: cty.String, Required: false},
|
||||
"security_group_filter": &hcldec.BlockSpec{TypeName: "security_group_filter", Nested: hcldec.ObjectSpec((*common.FlatSecurityGroupFilterOptions)(nil).HCL2Spec())},
|
||||
"run_tags": &hcldec.BlockAttrsSpec{TypeName: "run_tags", ElementType: cty.String, Required: false},
|
||||
"security_group_id": &hcldec.AttrSpec{Name: "security_group_id", Type: cty.String, Required: false},
|
||||
"security_group_ids": &hcldec.AttrSpec{Name: "security_group_ids", Type: cty.List(cty.String), Required: false},
|
||||
"source_ami": &hcldec.AttrSpec{Name: "source_ami", Type: cty.String, Required: false},
|
||||
"source_ami_filter": &hcldec.BlockSpec{TypeName: "source_ami_filter", Nested: hcldec.ObjectSpec((*common.FlatAmiFilterOptions)(nil).HCL2Spec())},
|
||||
"spot_instance_types": &hcldec.AttrSpec{Name: "spot_instance_types", Type: cty.List(cty.String), Required: false},
|
||||
"spot_price": &hcldec.AttrSpec{Name: "spot_price", Type: cty.String, Required: false},
|
||||
"spot_price_auto_product": &hcldec.AttrSpec{Name: "spot_price_auto_product", Type: cty.String, Required: false},
|
||||
"spot_tags": &hcldec.BlockAttrsSpec{TypeName: "spot_tags", ElementType: cty.String, Required: false},
|
||||
"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},
|
||||
"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},
|
||||
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
|
||||
"vpc_filter": &hcldec.BlockSpec{TypeName: "vpc_filter", Nested: hcldec.ObjectSpec((*common.FlatVpcFilterOptions)(nil).HCL2Spec())},
|
||||
"vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false},
|
||||
"windows_password_timeout": &hcldec.AttrSpec{Name: "windows_password_timeout", Type: cty.String, Required: false},
|
||||
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
||||
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
||||
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
||||
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
||||
"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},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
||||
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
||||
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
||||
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
||||
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
||||
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
||||
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
||||
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
||||
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
||||
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
||||
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
||||
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
||||
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
||||
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
||||
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
||||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
||||
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
|
||||
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: &hcldec.BlockSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())}},
|
||||
"launch_block_device_mappings": &hcldec.BlockListSpec{TypeName: "launch_block_device_mappings", Nested: &hcldec.BlockSpec{TypeName: "launch_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())}},
|
||||
"run_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -26,18 +26,9 @@ func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multi
|
||||
|
||||
// Create the image
|
||||
amiName := config.AMIName
|
||||
state.Put("intermediary_image", false)
|
||||
if config.AMIEncryptBootVolume.True() || s.AMISkipBuildRegion {
|
||||
state.Put("intermediary_image", true)
|
||||
|
||||
// From AWS SDK docs: You can encrypt a copy of an unencrypted snapshot,
|
||||
// but you cannot use it to create an unencrypted copy of an encrypted
|
||||
// snapshot. Your default CMK for EBS is used unless you specify a
|
||||
// non-default key using KmsKeyId.
|
||||
|
||||
// If encrypt_boot is nil or true, we need to create a temporary image
|
||||
// so that in step_region_copy, we can copy it with the correct
|
||||
// encryption
|
||||
if config.AMIEncryptBootVolume != nil || s.AMISkipBuildRegion {
|
||||
// Create a temporary image and then create a copy of it with the
|
||||
// correct encrypt_boot
|
||||
amiName = random.AlphaNum(7)
|
||||
}
|
||||
|
||||
@@ -45,7 +36,7 @@ func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multi
|
||||
createOpts := &ec2.CreateImageInput{
|
||||
InstanceId: instance.InstanceId,
|
||||
Name: &amiName,
|
||||
BlockDeviceMappings: config.AMIMappings.BuildEC2BlockDeviceMappings(),
|
||||
BlockDeviceMappings: config.BlockDevices.BuildAMIDevices(),
|
||||
}
|
||||
|
||||
createResp, err := ec2conn.CreateImage(createOpts)
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
//go:generate struct-markdown
|
||||
|
||||
package ebssurrogate
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type BlockDevice struct {
|
||||
awscommon.BlockDevice `mapstructure:",squash"`
|
||||
|
||||
// If true, this block device will not be snapshotted and the created AMI
|
||||
// will not contain block device mapping information for this volume. If
|
||||
// false, the block device will be mapped into the final created AMI. Set
|
||||
// this option to true if you need a block device mounted in the surrogate
|
||||
// AMI but not in the final created AMI.
|
||||
OmitFromArtifact bool `mapstructure:"omit_from_artifact"`
|
||||
}
|
||||
|
||||
type BlockDevices []BlockDevice
|
||||
|
||||
func (bds BlockDevices) Common() []awscommon.BlockDevice {
|
||||
res := []awscommon.BlockDevice{}
|
||||
for _, bd := range bds {
|
||||
res = append(res, bd.BlockDevice)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (bds BlockDevices) BuildEC2BlockDeviceMappings() []*ec2.BlockDeviceMapping {
|
||||
var blockDevices []*ec2.BlockDeviceMapping
|
||||
|
||||
for _, blockDevice := range bds {
|
||||
blockDevices = append(blockDevices, blockDevice.BuildEC2BlockDeviceMapping())
|
||||
}
|
||||
return blockDevices
|
||||
}
|
||||
|
||||
func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapping {
|
||||
|
||||
mapping := &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String(blockDevice.DeviceName),
|
||||
}
|
||||
|
||||
if blockDevice.NoDevice {
|
||||
mapping.NoDevice = aws.String("")
|
||||
return mapping
|
||||
} else if blockDevice.VirtualName != "" {
|
||||
if strings.HasPrefix(blockDevice.VirtualName, "ephemeral") {
|
||||
mapping.VirtualName = aws.String(blockDevice.VirtualName)
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
|
||||
ebsBlockDevice := &ec2.EbsBlockDevice{
|
||||
DeleteOnTermination: aws.Bool(blockDevice.DeleteOnTermination),
|
||||
}
|
||||
|
||||
if blockDevice.VolumeType != "" {
|
||||
ebsBlockDevice.VolumeType = aws.String(blockDevice.VolumeType)
|
||||
}
|
||||
|
||||
if blockDevice.VolumeSize > 0 {
|
||||
ebsBlockDevice.VolumeSize = aws.Int64(blockDevice.VolumeSize)
|
||||
}
|
||||
|
||||
// IOPS is only valid for io1 type
|
||||
if blockDevice.VolumeType == "io1" {
|
||||
ebsBlockDevice.Iops = aws.Int64(blockDevice.IOPS)
|
||||
}
|
||||
|
||||
// You cannot specify Encrypted if you specify a Snapshot ID
|
||||
if blockDevice.SnapshotId != "" {
|
||||
ebsBlockDevice.SnapshotId = aws.String(blockDevice.SnapshotId)
|
||||
}
|
||||
ebsBlockDevice.Encrypted = blockDevice.Encrypted.ToBoolPointer()
|
||||
|
||||
mapping.Ebs = ebsBlockDevice
|
||||
|
||||
return mapping
|
||||
}
|
||||
|
||||
func (bds BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
|
||||
for _, block := range bds {
|
||||
if err := block.Prepare(ctx); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func (b BlockDevices) GetOmissions() map[string]bool {
|
||||
omitMap := make(map[string]bool)
|
||||
|
||||
for _, blockDevice := range b {
|
||||
omitMap[blockDevice.DeviceName] = blockDevice.OmitFromArtifact
|
||||
}
|
||||
|
||||
return omitMap
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:generate struct-markdown
|
||||
|
||||
// The ebssurrogate package contains a packer.Builder implementation that
|
||||
// builds a new EBS-backed AMI using an ephemeral instance.
|
||||
package ebssurrogate
|
||||
@@ -10,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
@@ -26,41 +23,12 @@ type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
awscommon.BlockDevices `mapstructure:",squash"`
|
||||
awscommon.AMIConfig `mapstructure:",squash"`
|
||||
|
||||
// Add one or more block device mappings to the AMI. These will be attached
|
||||
// when booting a new instance from your AMI. To add a block device during
|
||||
// the Packer build see `launch_block_device_mappings` below. Your options
|
||||
// here may vary depending on the type of VM you use. See the
|
||||
// [BlockDevices](#block-devices-configuration) documentation for fields.
|
||||
AMIMappings awscommon.BlockDevices `mapstructure:"ami_block_device_mappings" required:"false"`
|
||||
// Add one or more block devices before the Packer build starts. If you add
|
||||
// instance store volumes or EBS volumes in addition to the root device
|
||||
// volume, the created AMI will contain block device mapping information
|
||||
// for those volumes. Amazon creates snapshots of the source instance's
|
||||
// root volume and any other EBS volumes described here. When you launch an
|
||||
// instance from this new AMI, the instance automatically launches with
|
||||
// these additional volumes, and will restore them from snapshots taken
|
||||
// from the source instance. See the
|
||||
// [BlockDevices](#block-devices-configuration) documentation for fields.
|
||||
LaunchMappings BlockDevices `mapstructure:"launch_block_device_mappings" required:"false"`
|
||||
// A block device mapping describing the root device of the AMI. This looks
|
||||
// like the mappings in `ami_block_device_mapping`, except with an
|
||||
// additional field:
|
||||
//
|
||||
// - `source_device_name` (string) - The device name of the block device on
|
||||
// the source instance to be used as the root device for the AMI. This
|
||||
// must correspond to a block device in `launch_block_device_mapping`.
|
||||
RootDevice RootBlockDevice `mapstructure:"ami_root_device" required:"true"`
|
||||
// Tags to apply to the volumes that are *launched* to create the AMI.
|
||||
// These tags are *not* applied to the resulting AMI unless they're
|
||||
// duplicated in `tags`. This is a [template
|
||||
// engine](/docs/templates/engine.html), see [Build template
|
||||
// data](#build-template-data) for more information.
|
||||
RootDevice RootBlockDevice `mapstructure:"ami_root_device"`
|
||||
VolumeRunTags awscommon.TagMap `mapstructure:"run_volume_tags"`
|
||||
// what architecture to use when registering the
|
||||
// final AMI; valid options are "x86_64" or "arm64". Defaults to "x86_64".
|
||||
Architecture string `mapstructure:"ami_architecture" required:"false"`
|
||||
Architecture string `mapstructure:"ami_architecture"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
@@ -96,13 +64,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
|
||||
// Accumulate any errors
|
||||
var errs *packer.MultiError
|
||||
var warns []string
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
b.config.AMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AMIMappings.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.LaunchMappings.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RootDevice.Prepare(&b.config.ctx)...)
|
||||
|
||||
if b.config.AMIVirtType == "" {
|
||||
@@ -110,7 +76,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
|
||||
foundRootVolume := false
|
||||
for _, launchDevice := range b.config.LaunchMappings {
|
||||
for _, launchDevice := range b.config.BlockDevices.LaunchMappings {
|
||||
if launchDevice.DeviceName == b.config.RootDevice.SourceDeviceName {
|
||||
foundRootVolume = true
|
||||
if launchDevice.OmitFromArtifact {
|
||||
@@ -123,21 +89,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("no volume with name '%s' is found", b.config.RootDevice.SourceDeviceName))
|
||||
}
|
||||
|
||||
if b.config.IsSpotInstance() && (b.config.AMIENASupport.True() || b.config.AMISriovNetSupport) {
|
||||
if b.config.IsSpotInstance() && ((b.config.AMIENASupport != nil && *b.config.AMIENASupport) || b.config.AMISriovNetSupport) {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Spot instances do not support modification, which is required "+
|
||||
"when either `ena_support` or `sriov_support` are set. Please ensure "+
|
||||
"you use an AMI that already has either SR-IOV or ENA enabled."))
|
||||
}
|
||||
|
||||
if b.config.RunConfig.SpotPriceAutoProduct != "" {
|
||||
warns = append(warns, "spot_price_auto_product is deprecated and no "+
|
||||
"longer necessary for Packer builds. In future versions of "+
|
||||
"Packer, inclusion of spot_price_auto_product will error your "+
|
||||
"builds. Please take a look at our current documentation to "+
|
||||
"understand how Packer requests Spot instances.")
|
||||
}
|
||||
|
||||
if b.config.Architecture == "" {
|
||||
b.config.Architecture = "x86_64"
|
||||
}
|
||||
@@ -152,12 +110,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
errs = packer.MultiErrorAppend(errs, errors.New(`The only valid ami_architecture values are "x86_64" and "arm64"`))
|
||||
}
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return warns, errs
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
|
||||
return warns, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
@@ -165,9 +122,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ec2conn := ec2.New(session)
|
||||
iam := iam.New(session)
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
@@ -175,7 +130,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
state.Put("access_config", &b.config.AccessConfig)
|
||||
state.Put("ami_config", &b.config.AMIConfig)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("iam", iam)
|
||||
state.Put("awsSession", session)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
@@ -185,17 +139,19 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
if b.config.IsSpotInstance() {
|
||||
instanceStep = &awscommon.StepRunSpotInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
LaunchMappings: b.config.LaunchMappings,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
||||
Ctx: b.config.ctx,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Debug: b.config.PackerDebug,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||
SpotInstanceTypes: b.config.SpotInstanceTypes,
|
||||
SpotTags: b.config.SpotTags,
|
||||
Tags: b.config.RunTags,
|
||||
@@ -206,13 +162,14 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
} else {
|
||||
instanceStep = &awscommon.StepRunSourceInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
LaunchMappings: b.config.LaunchMappings,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
Debug: b.config.PackerDebug,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
EnableT2Unlimited: b.config.EnableT2Unlimited,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
@@ -224,8 +181,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
}
|
||||
}
|
||||
|
||||
amiDevices := b.config.AMIMappings.BuildEC2BlockDeviceMappings()
|
||||
launchDevices := b.config.LaunchMappings.BuildEC2BlockDeviceMappings()
|
||||
amiDevices := b.config.BuildAMIDevices()
|
||||
launchDevices := b.config.BuildLaunchDevices()
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
@@ -260,12 +217,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs,
|
||||
},
|
||||
&awscommon.StepIamInstanceProfile{
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
|
||||
},
|
||||
&awscommon.StepCleanupVolumes{
|
||||
LaunchMappings: b.config.LaunchMappings.Common(),
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
},
|
||||
instanceStep,
|
||||
&awscommon.StepGetPassword{
|
||||
@@ -278,9 +231,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: awscommon.SSHHost(
|
||||
ec2conn,
|
||||
b.config.SSHInterface,
|
||||
b.config.Comm.SSHHost,
|
||||
),
|
||||
b.config.Comm.SSHInterface),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
@@ -297,7 +248,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
},
|
||||
&StepSnapshotVolumes{
|
||||
LaunchDevices: launchDevices,
|
||||
SnapshotOmitMap: b.config.LaunchMappings.GetOmissions(),
|
||||
SnapshotOmitMap: b.config.GetOmissions(),
|
||||
},
|
||||
&awscommon.StepDeregisterAMI{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
@@ -313,8 +264,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
||||
EnableAMIENASupport: b.config.AMIENASupport,
|
||||
Architecture: b.config.Architecture,
|
||||
LaunchOmitMap: b.config.LaunchMappings.GetOmissions(),
|
||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
||||
LaunchOmitMap: b.config.GetOmissions(),
|
||||
},
|
||||
&awscommon.StepAMIRegionCopy{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:generate struct-markdown
|
||||
|
||||
package ebssurrogate
|
||||
|
||||
import (
|
||||
@@ -9,30 +7,12 @@ import (
|
||||
)
|
||||
|
||||
type RootBlockDevice struct {
|
||||
SourceDeviceName string `mapstructure:"source_device_name"`
|
||||
// The device name exposed to the instance (for
|
||||
// example, /dev/sdh or xvdh). Required for every device in the block
|
||||
// device mapping.
|
||||
DeviceName string `mapstructure:"device_name" required:"false"`
|
||||
// Indicates whether the EBS volume is
|
||||
// deleted on instance termination. Default false. NOTE: If this
|
||||
// value is not explicitly set to true and volumes are not cleaned up by
|
||||
// an alternative method, additional volumes will accumulate after every
|
||||
// build.
|
||||
DeleteOnTermination bool `mapstructure:"delete_on_termination" required:"false"`
|
||||
// The number of I/O operations per second (IOPS) that
|
||||
// the volume supports. See the documentation on
|
||||
// IOPs
|
||||
// for more information
|
||||
IOPS int64 `mapstructure:"iops" required:"false"`
|
||||
// The volume type. gp2 for General Purpose
|
||||
// (SSD) volumes, io1 for Provisioned IOPS (SSD) volumes, st1 for
|
||||
// Throughput Optimized HDD, sc1 for Cold HDD, and standard for
|
||||
// Magnetic volumes.
|
||||
VolumeType string `mapstructure:"volume_type" required:"false"`
|
||||
// The size of the volume, in GiB. Required if
|
||||
// not specifying a snapshot_id.
|
||||
VolumeSize int64 `mapstructure:"volume_size" required:"false"`
|
||||
SourceDeviceName string `mapstructure:"source_device_name"`
|
||||
DeviceName string `mapstructure:"device_name"`
|
||||
DeleteOnTermination bool `mapstructure:"delete_on_termination"`
|
||||
IOPS int64 `mapstructure:"iops"`
|
||||
VolumeType string `mapstructure:"volume_type"`
|
||||
VolumeSize int64 `mapstructure:"volume_size"`
|
||||
}
|
||||
|
||||
func (c *RootBlockDevice) Prepare(ctx *interpolate.Context) []error {
|
||||
|
||||
@@ -7,8 +7,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/common/random"
|
||||
confighelper "github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
@@ -18,12 +16,11 @@ type StepRegisterAMI struct {
|
||||
RootDevice RootBlockDevice
|
||||
AMIDevices []*ec2.BlockDeviceMapping
|
||||
LaunchDevices []*ec2.BlockDeviceMapping
|
||||
EnableAMIENASupport confighelper.Trilean
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMISriovNetSupport bool
|
||||
Architecture string
|
||||
image *ec2.Image
|
||||
LaunchOmitMap map[string]bool
|
||||
AMISkipBuildRegion bool
|
||||
}
|
||||
|
||||
func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
@@ -36,25 +33,8 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
|
||||
blockDevices := s.combineDevices(snapshotIds)
|
||||
|
||||
// Create the image
|
||||
amiName := config.AMIName
|
||||
state.Put("intermediary_image", false)
|
||||
if config.AMIEncryptBootVolume.True() || s.AMISkipBuildRegion {
|
||||
state.Put("intermediary_image", true)
|
||||
|
||||
// From AWS SDK docs: You can encrypt a copy of an unencrypted snapshot,
|
||||
// but you cannot use it to create an unencrypted copy of an encrypted
|
||||
// snapshot. Your default CMK for EBS is used unless you specify a
|
||||
// non-default key using KmsKeyId.
|
||||
|
||||
// If encrypt_boot is nil or true, we need to create a temporary image
|
||||
// so that in step_region_copy, we can copy it with the correct
|
||||
// encryption
|
||||
amiName = random.AlphaNum(7)
|
||||
}
|
||||
|
||||
registerOpts := &ec2.RegisterImageInput{
|
||||
Name: &amiName,
|
||||
Name: &config.AMIName,
|
||||
Architecture: aws.String(s.Architecture),
|
||||
RootDeviceName: aws.String(s.RootDevice.DeviceName),
|
||||
VirtualizationType: aws.String(config.AMIVirtType),
|
||||
@@ -66,7 +46,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
|
||||
registerOpts.SriovNetSupport = aws.String("simple")
|
||||
}
|
||||
if s.EnableAMIENASupport.True() {
|
||||
if s.EnableAMIENASupport != nil && *s.EnableAMIENASupport {
|
||||
// Set EnaSupport to true
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
registerOpts.EnaSupport = aws.Bool(true)
|
||||
|
||||
@@ -1,37 +1,29 @@
|
||||
//go:generate struct-markdown
|
||||
|
||||
package ebsvolume
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type BlockDevice struct {
|
||||
awscommon.BlockDevice `mapstructure:",squash"`
|
||||
// Tags to apply to the volume. These are retained after the builder
|
||||
// completes. This is a [template engine](/docs/templates/engine.html), see
|
||||
// [Build template data](#build-template-data) for more information.
|
||||
Tags awscommon.TagMap `mapstructure:"tags" required:"false"`
|
||||
awscommon.BlockDevice `mapstructure:"-,squash"`
|
||||
Tags awscommon.TagMap `mapstructure:"tags"`
|
||||
}
|
||||
|
||||
type BlockDevices []BlockDevice
|
||||
func commonBlockDevices(mappings []BlockDevice, ctx *interpolate.Context) (awscommon.BlockDevices, error) {
|
||||
result := make([]awscommon.BlockDevice, len(mappings))
|
||||
|
||||
func (bds BlockDevices) BuildEC2BlockDeviceMappings() []*ec2.BlockDeviceMapping {
|
||||
var blockDevices []*ec2.BlockDeviceMapping
|
||||
|
||||
for _, blockDevice := range bds {
|
||||
blockDevices = append(blockDevices, blockDevice.BuildEC2BlockDeviceMapping())
|
||||
}
|
||||
return blockDevices
|
||||
}
|
||||
|
||||
func (bds BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
|
||||
for _, block := range bds {
|
||||
if err := block.Prepare(ctx); err != nil {
|
||||
errs = append(errs, err)
|
||||
for i, mapping := range mappings {
|
||||
interpolateBlockDev, err := interpolate.RenderInterface(&mapping.BlockDevice, ctx)
|
||||
if err != nil {
|
||||
return awscommon.BlockDevices{}, err
|
||||
}
|
||||
result[i] = *interpolateBlockDev.(*awscommon.BlockDevice)
|
||||
}
|
||||
return errs
|
||||
|
||||
return awscommon.BlockDevices{
|
||||
LaunchBlockDevices: awscommon.LaunchBlockDevices{
|
||||
LaunchMappings: result,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
//go:generate struct-markdown
|
||||
|
||||
// The ebsvolume package contains a packer.Builder implementation that builds
|
||||
// EBS volumes for Amazon EC2 using an ephemeral instance,
|
||||
// The ebsvolume package contains a packer.Builder implementation that
|
||||
// builds EBS volumes for Amazon EC2 using an ephemeral instance,
|
||||
package ebsvolume
|
||||
|
||||
import (
|
||||
@@ -9,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
@@ -26,47 +23,12 @@ type Config struct {
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
|
||||
// Enable enhanced networking (ENA but not SriovNetSupport) on
|
||||
// HVM-compatible AMIs. If set, add `ec2:ModifyInstanceAttribute` to your
|
||||
// AWS IAM policy. Note: you must make sure enhanced networking is enabled
|
||||
// on your instance. See [Amazon's documentation on enabling enhanced
|
||||
// networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking).
|
||||
AMIENASupport config.Trilean `mapstructure:"ena_support" required:"false"`
|
||||
// Enable enhanced networking (SriovNetSupport but not ENA) on
|
||||
// HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute` to your
|
||||
// AWS IAM policy. Note: you must make sure enhanced networking is enabled
|
||||
// on your instance. See [Amazon's documentation on enabling enhanced
|
||||
// networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking).
|
||||
// Default `false`.
|
||||
AMISriovNetSupport bool `mapstructure:"sriov_support" required:"false"`
|
||||
VolumeMappings []BlockDevice `mapstructure:"ebs_volumes"`
|
||||
AMIENASupport *bool `mapstructure:"ena_support"`
|
||||
AMISriovNetSupport bool `mapstructure:"sriov_support"`
|
||||
|
||||
// Add the block device mappings to the AMI. If you add instance store
|
||||
// volumes or EBS volumes in addition to the root device volume, the
|
||||
// created AMI will contain block device mapping information for those
|
||||
// volumes. Amazon creates snapshots of the source instance's root volume
|
||||
// and any other EBS volumes described here. When you launch an instance
|
||||
// from this new AMI, the instance automatically launches with these
|
||||
// additional volumes, and will restore them from snapshots taken from the
|
||||
// source instance. See the [BlockDevices](#block-devices-configuration)
|
||||
// documentation for fields.
|
||||
VolumeMappings BlockDevices `mapstructure:"ebs_volumes" required:"false"`
|
||||
// Tags to apply to the volumes of the instance that is *launched* to
|
||||
// create EBS Volumes. These tags will *not* appear in the tags of the
|
||||
// resulting EBS volumes unless they're duplicated under `tags` in the
|
||||
// `ebs_volumes` setting. This is a [template
|
||||
// engine](/docs/templates/engine.html), see [Build template
|
||||
// data](#build-template-data) for more information.
|
||||
//
|
||||
// Note: The tags specified here will be *temporarily* applied to volumes
|
||||
// specified in `ebs_volumes` - but only while the instance is being
|
||||
// created. Packer will replace all tags on the volume with the tags
|
||||
// configured in the `ebs_volumes` section as soon as the instance is
|
||||
// reported as 'ready'.
|
||||
VolumeRunTags awscommon.TagMap `mapstructure:"run_volume_tags"`
|
||||
|
||||
launchBlockDevices BlockDevices
|
||||
|
||||
ctx interpolate.Context
|
||||
launchBlockDevices awscommon.BlockDevices
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
@@ -97,7 +59,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
|
||||
// Accumulate any errors
|
||||
var errs *packer.MultiError
|
||||
var warns []string
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.launchBlockDevices.Prepare(&b.config.ctx)...)
|
||||
@@ -108,32 +69,24 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
b.config.launchBlockDevices = b.config.VolumeMappings
|
||||
b.config.launchBlockDevices, err = commonBlockDevices(b.config.VolumeMappings, &b.config.ctx)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
|
||||
if b.config.IsSpotInstance() && ((b.config.AMIENASupport.True()) || b.config.AMISriovNetSupport) {
|
||||
if b.config.IsSpotInstance() && ((b.config.AMIENASupport != nil && *b.config.AMIENASupport) || b.config.AMISriovNetSupport) {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Spot instances do not support modification, which is required "+
|
||||
"when either `ena_support` or `sriov_support` are set. Please ensure "+
|
||||
"you use an AMI that already has either SR-IOV or ENA enabled."))
|
||||
}
|
||||
|
||||
if b.config.RunConfig.SpotPriceAutoProduct != "" {
|
||||
warns = append(warns, "spot_price_auto_product is deprecated and no "+
|
||||
"longer necessary for Packer builds. In future versions of "+
|
||||
"Packer, inclusion of spot_price_auto_product will error your "+
|
||||
"builds. Please take a look at our current documentation to "+
|
||||
"understand how Packer requests Spot instances.")
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return warns, errs
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
return warns, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
@@ -142,14 +95,12 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
return nil, err
|
||||
}
|
||||
ec2conn := ec2.New(session)
|
||||
iam := iam.New(session)
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("access_config", &b.config.AccessConfig)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("iam", iam)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
@@ -158,34 +109,36 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
if b.config.IsSpotInstance() {
|
||||
instanceStep = &awscommon.StepRunSpotInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
LaunchMappings: b.config.launchBlockDevices,
|
||||
BlockDevices: b.config.launchBlockDevices,
|
||||
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Debug: b.config.PackerDebug,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SpotInstanceTypes: b.config.SpotInstanceTypes,
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||
SpotInstanceTypes: b.config.SpotInstanceTypes,
|
||||
SpotTags: b.config.SpotTags,
|
||||
Tags: b.config.RunTags,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
VolumeTags: b.config.VolumeRunTags,
|
||||
}
|
||||
} else {
|
||||
instanceStep = &awscommon.StepRunSourceInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
LaunchMappings: b.config.launchBlockDevices,
|
||||
BlockDevices: b.config.launchBlockDevices,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
Debug: b.config.PackerDebug,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
EnableT2Unlimited: b.config.EnableT2Unlimited,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
@@ -193,7 +146,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
Tags: b.config.RunTags,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
VolumeTags: b.config.VolumeRunTags,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,10 +177,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs,
|
||||
},
|
||||
&awscommon.StepIamInstanceProfile{
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
|
||||
},
|
||||
instanceStep,
|
||||
&stepTagEBSVolumes{
|
||||
VolumeMapping: b.config.VolumeMappings,
|
||||
@@ -244,9 +192,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: awscommon.SSHHost(
|
||||
ec2conn,
|
||||
b.config.SSHInterface,
|
||||
b.config.Comm.SSHHost,
|
||||
),
|
||||
b.config.Comm.SSHInterface),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
|
||||
@@ -3,9 +3,7 @@ package ebsvolume
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -21,15 +19,11 @@ func (s *stepTagEBSVolumes) Run(ctx context.Context, state multistep.StateBag) m
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
config := state.Get("config").(*Config)
|
||||
|
||||
volumes := make(EbsVolumes)
|
||||
for _, instanceBlockDevices := range instance.BlockDeviceMappings {
|
||||
for _, configVolumeMapping := range s.VolumeMapping {
|
||||
if configVolumeMapping.DeviceName == *instanceBlockDevices.DeviceName {
|
||||
if configVolumeMapping.DeleteOnTermination {
|
||||
continue
|
||||
}
|
||||
volumes[*ec2conn.Config.Region] = append(
|
||||
volumes[*ec2conn.Config.Region],
|
||||
*instanceBlockDevices.Ebs.VolumeId)
|
||||
@@ -39,50 +33,8 @@ func (s *stepTagEBSVolumes) Run(ctx context.Context, state multistep.StateBag) m
|
||||
state.Put("ebsvolumes", volumes)
|
||||
|
||||
if len(s.VolumeMapping) > 0 {
|
||||
// If run_volume_tags were set in the template any attached EBS
|
||||
// volume will have had these tags applied when the instance was
|
||||
// created. We now need to remove these tags to ensure only the EBS
|
||||
// volume tags are applied (if any)
|
||||
if config.VolumeRunTags.IsSet() {
|
||||
ui.Say("Removing any tags applied to EBS volumes when the source instance was created...")
|
||||
|
||||
ui.Message("Compiling list of existing tags to remove...")
|
||||
existingTags, err := config.VolumeRunTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error generating list of tags to remove: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
existingTags.Report(ui)
|
||||
|
||||
// Generate the list of volumes with tags to delete.
|
||||
// Looping over the instance block device mappings allows us to
|
||||
// obtain the volumeId
|
||||
volumeIds := []string{}
|
||||
for _, mapping := range s.VolumeMapping {
|
||||
for _, v := range instance.BlockDeviceMappings {
|
||||
if *v.DeviceName == mapping.DeviceName {
|
||||
volumeIds = append(volumeIds, *v.Ebs.VolumeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the tags
|
||||
ui.Message(fmt.Sprintf("Deleting 'run_volume_tags' on EBS Volumes: %s", strings.Join(volumeIds, ", ")))
|
||||
_, err = ec2conn.DeleteTags(&ec2.DeleteTagsInput{
|
||||
Resources: aws.StringSlice(volumeIds),
|
||||
Tags: existingTags,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error deleting tags on EBS Volumes %s: %s", strings.Join(volumeIds, ", "), err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say("Tagging EBS volumes...")
|
||||
|
||||
toTag := map[string][]*ec2.Tag{}
|
||||
for _, mapping := range s.VolumeMapping {
|
||||
if len(mapping.Tags) == 0 {
|
||||
@@ -90,19 +42,15 @@ func (s *stepTagEBSVolumes) Run(ctx context.Context, state multistep.StateBag) m
|
||||
continue
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Compiling list of tags to apply to volume on %s...", mapping.DeviceName))
|
||||
tags, err := mapping.Tags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error generating tags for device %s: %s", mapping.DeviceName, err)
|
||||
err := fmt.Errorf("Error tagging device %s with %s", mapping.DeviceName, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
tags.Report(ui)
|
||||
|
||||
// Generate the map of volumes and associated tags to apply.
|
||||
// Looping over the instance block device mappings allows us to
|
||||
// obtain the volumeId
|
||||
for _, v := range instance.BlockDeviceMappings {
|
||||
if *v.DeviceName == mapping.DeviceName {
|
||||
toTag[*v.Ebs.VolumeId] = tags
|
||||
@@ -110,11 +58,9 @@ func (s *stepTagEBSVolumes) Run(ctx context.Context, state multistep.StateBag) m
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the tags
|
||||
for volumeId, tags := range toTag {
|
||||
ui.Message(fmt.Sprintf("Applying tags to EBS Volume: %s", volumeId))
|
||||
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||
Resources: aws.StringSlice([]string{volumeId}),
|
||||
Resources: []*string{&volumeId},
|
||||
Tags: tags,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -123,6 +69,7 @@ func (s *stepTagEBSVolumes) Run(ctx context.Context, state multistep.StateBag) m
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:generate struct-markdown
|
||||
|
||||
// The instance package contains a packer.Builder implementation that builds
|
||||
// AMIs for Amazon EC2 backed by instance storage, as opposed to EBS storage.
|
||||
package instance
|
||||
@@ -12,7 +10,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
@@ -25,65 +22,24 @@ import (
|
||||
// The unique ID for this builder
|
||||
const BuilderId = "mitchellh.amazon.instance"
|
||||
|
||||
// Config is the configuration that is chained through the steps and settable
|
||||
// from the template.
|
||||
// Config is the configuration that is chained through the steps and
|
||||
// settable from the template.
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
awscommon.AMIConfig `mapstructure:",squash"`
|
||||
awscommon.BlockDevices `mapstructure:",squash"`
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
|
||||
// Add one or more block device mappings to the AMI. These will be attached
|
||||
// when booting a new instance from your AMI. To add a block device during
|
||||
// the Packer build see `launch_block_device_mappings` below. Your options
|
||||
// here may vary depending on the type of VM you use. See the
|
||||
// [BlockDevices](#block-devices-configuration) documentation for fields.
|
||||
AMIMappings awscommon.BlockDevices `mapstructure:"ami_block_device_mappings" required:"false"`
|
||||
// Add one or more block devices before the Packer build starts. If you add
|
||||
// instance store volumes or EBS volumes in addition to the root device
|
||||
// volume, the created AMI will contain block device mapping information
|
||||
// for those volumes. Amazon creates snapshots of the source instance's
|
||||
// root volume and any other EBS volumes described here. When you launch an
|
||||
// instance from this new AMI, the instance automatically launches with
|
||||
// these additional volumes, and will restore them from snapshots taken
|
||||
// from the source instance. See the
|
||||
// [BlockDevices](#block-devices-configuration) documentation for fields.
|
||||
LaunchMappings awscommon.BlockDevices `mapstructure:"launch_block_device_mappings" required:"false"`
|
||||
// Your AWS account ID. This is required for bundling the AMI. This is not
|
||||
// the same as the access key. You can find your account ID in the security
|
||||
// credentials page of your AWS account.
|
||||
AccountId string `mapstructure:"account_id" required:"true"`
|
||||
// The directory on the running instance where the bundled AMI will be
|
||||
// saved prior to uploading. By default this is /tmp. This directory must
|
||||
// exist and be writable.
|
||||
BundleDestination string `mapstructure:"bundle_destination" required:"false"`
|
||||
// The prefix for files created from bundling the root volume. By default
|
||||
// this is image-{{timestamp}}. The timestamp variable should be used to
|
||||
// make sure this is unique, otherwise it can collide with other created
|
||||
// AMIs by Packer in your account.
|
||||
BundlePrefix string `mapstructure:"bundle_prefix" required:"false"`
|
||||
// The command to use to upload the bundled volume. See the "custom bundle
|
||||
// commands" section below for more information.
|
||||
BundleUploadCommand string `mapstructure:"bundle_upload_command" required:"false"`
|
||||
// The command to use to bundle the volume. See the "custom bundle
|
||||
// commands" section below for more information.
|
||||
BundleVolCommand string `mapstructure:"bundle_vol_command" required:"false"`
|
||||
// The name of the S3 bucket to upload the AMI. This bucket will be created
|
||||
// if it doesn't exist.
|
||||
S3Bucket string `mapstructure:"s3_bucket" required:"true"`
|
||||
// The local path to a valid X509 certificate for your AWS account. This is
|
||||
// used for bundling the AMI. This X509 certificate must be registered with
|
||||
// your account from the security credentials page in the AWS console.
|
||||
X509CertPath string `mapstructure:"x509_cert_path" required:"true"`
|
||||
// The local path to the private key for the X509 certificate specified by
|
||||
// x509_cert_path. This is used for bundling the AMI.
|
||||
X509KeyPath string `mapstructure:"x509_key_path" required:"true"`
|
||||
// The path on the remote machine where the X509 certificate will be
|
||||
// uploaded. This path must already exist and be writable. X509
|
||||
// certificates are uploaded after provisioning is run, so it is perfectly
|
||||
// okay to create this directory as part of the provisioning process.
|
||||
// Defaults to /tmp.
|
||||
X509UploadPath string `mapstructure:"x509_upload_path" required:"false"`
|
||||
AccountId string `mapstructure:"account_id"`
|
||||
BundleDestination string `mapstructure:"bundle_destination"`
|
||||
BundlePrefix string `mapstructure:"bundle_prefix"`
|
||||
BundleUploadCommand string `mapstructure:"bundle_upload_command"`
|
||||
BundleVolCommand string `mapstructure:"bundle_vol_command"`
|
||||
S3Bucket string `mapstructure:"s3_bucket"`
|
||||
X509CertPath string `mapstructure:"x509_cert_path"`
|
||||
X509KeyPath string `mapstructure:"x509_key_path"`
|
||||
X509UploadPath string `mapstructure:"x509_upload_path"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
@@ -170,10 +126,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
|
||||
// Accumulate any errors
|
||||
var errs *packer.MultiError
|
||||
var warns []string
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AMIMappings.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.LaunchMappings.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
b.config.AMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
|
||||
@@ -202,26 +156,18 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
errs, fmt.Errorf("x509_key_path points to bad file: %s", err))
|
||||
}
|
||||
|
||||
if b.config.IsSpotInstance() && ((b.config.AMIENASupport.True()) || b.config.AMISriovNetSupport) {
|
||||
if b.config.IsSpotInstance() && ((b.config.AMIENASupport != nil && *b.config.AMIENASupport) || b.config.AMISriovNetSupport) {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Spot instances do not support modification, which is required "+
|
||||
"when either `ena_support` or `sriov_support` are set. Please ensure "+
|
||||
"you use an AMI that already has either SR-IOV or ENA enabled."))
|
||||
}
|
||||
|
||||
if b.config.RunConfig.SpotPriceAutoProduct != "" {
|
||||
warns = append(warns, "spot_price_auto_product is deprecated and no "+
|
||||
"longer necessary for Packer builds. In future versions of "+
|
||||
"Packer, inclusion of spot_price_auto_product will error your "+
|
||||
"builds. Please take a look at our current documentation to "+
|
||||
"understand how Packer requests Spot instances.")
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return warns, errs
|
||||
return nil, errs
|
||||
}
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
return warns, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
@@ -230,7 +176,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
return nil, err
|
||||
}
|
||||
ec2conn := ec2.New(session)
|
||||
iam := iam.New(session)
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
@@ -238,7 +183,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
state.Put("access_config", &b.config.AccessConfig)
|
||||
state.Put("ami_config", &b.config.AMIConfig)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("iam", iam)
|
||||
state.Put("awsSession", session)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
@@ -248,16 +192,18 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
if b.config.IsSpotInstance() {
|
||||
instanceStep = &awscommon.StepRunSpotInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
LaunchMappings: b.config.LaunchMappings,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
||||
Ctx: b.config.ctx,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Debug: b.config.PackerDebug,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceType: b.config.InstanceType,
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
SpotInstanceTypes: b.config.SpotInstanceTypes,
|
||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||
Tags: b.config.RunTags,
|
||||
SpotTags: b.config.SpotTags,
|
||||
UserData: b.config.UserData,
|
||||
@@ -266,12 +212,13 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
} else {
|
||||
instanceStep = &awscommon.StepRunSourceInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
LaunchMappings: b.config.LaunchMappings,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
Debug: b.config.PackerDebug,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
EnableT2Unlimited: b.config.EnableT2Unlimited,
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
@@ -314,10 +261,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs,
|
||||
},
|
||||
&awscommon.StepIamInstanceProfile{
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
|
||||
},
|
||||
instanceStep,
|
||||
&awscommon.StepGetPassword{
|
||||
Debug: b.config.PackerDebug,
|
||||
@@ -329,9 +272,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: awscommon.SSHHost(
|
||||
ec2conn,
|
||||
b.config.SSHInterface,
|
||||
b.config.Comm.SSHHost,
|
||||
),
|
||||
b.config.Comm.SSHInterface),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
@@ -355,7 +296,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
&StepRegisterAMI{
|
||||
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
||||
EnableAMIENASupport: b.config.AMIENASupport,
|
||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
||||
},
|
||||
&awscommon.StepAMIRegionCopy{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
|
||||
@@ -7,16 +7,13 @@ 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/common/random"
|
||||
confighelper "github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepRegisterAMI struct {
|
||||
EnableAMIENASupport confighelper.Trilean
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMISriovNetSupport bool
|
||||
AMISkipBuildRegion bool
|
||||
}
|
||||
|
||||
func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
@@ -26,28 +23,10 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Registering the AMI...")
|
||||
|
||||
// Create the image
|
||||
amiName := config.AMIName
|
||||
state.Put("intermediary_image", false)
|
||||
if config.AMIEncryptBootVolume.True() || s.AMISkipBuildRegion {
|
||||
state.Put("intermediary_image", true)
|
||||
|
||||
// From AWS SDK docs: You can encrypt a copy of an unencrypted snapshot,
|
||||
// but you cannot use it to create an unencrypted copy of an encrypted
|
||||
// snapshot. Your default CMK for EBS is used unless you specify a
|
||||
// non-default key using KmsKeyId.
|
||||
|
||||
// If encrypt_boot is nil or true, we need to create a temporary image
|
||||
// so that in step_region_copy, we can copy it with the correct
|
||||
// encryption
|
||||
amiName = random.AlphaNum(7)
|
||||
}
|
||||
|
||||
registerOpts := &ec2.RegisterImageInput{
|
||||
ImageLocation: &manifestPath,
|
||||
Name: aws.String(amiName),
|
||||
BlockDeviceMappings: config.AMIMappings.BuildEC2BlockDeviceMappings(),
|
||||
Name: aws.String(config.AMIName),
|
||||
BlockDeviceMappings: config.BlockDevices.BuildAMIDevices(),
|
||||
}
|
||||
|
||||
if config.AMIVirtType != "" {
|
||||
@@ -59,7 +38,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
|
||||
registerOpts.SriovNetSupport = aws.String("simple")
|
||||
}
|
||||
if s.EnableAMIENASupport.True() {
|
||||
if s.EnableAMIENASupport != nil && *s.EnableAMIENASupport {
|
||||
// Set EnaSupport to true
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
registerOpts.EnaSupport = aws.Bool(true)
|
||||
|
||||
@@ -35,26 +35,13 @@ type Artifact struct {
|
||||
ManagedImageId string
|
||||
ManagedImageOSDiskSnapshotName string
|
||||
ManagedImageDataDiskSnapshotPrefix string
|
||||
// ARM resource id for Shared Image Gallery
|
||||
ManagedImageSharedImageGalleryId string
|
||||
ManagedImageSharedImageGalleryId string
|
||||
|
||||
// Additional Disks
|
||||
AdditionalDisks *[]AdditionalDiskArtifact
|
||||
}
|
||||
|
||||
func NewManagedImageArtifact(osType, resourceGroup, name, location, id, osDiskSnapshotName, dataDiskSnapshotPrefix string) (*Artifact, error) {
|
||||
return &Artifact{
|
||||
ManagedImageResourceGroupName: resourceGroup,
|
||||
ManagedImageName: name,
|
||||
ManagedImageLocation: location,
|
||||
ManagedImageId: id,
|
||||
OSType: osType,
|
||||
ManagedImageOSDiskSnapshotName: osDiskSnapshotName,
|
||||
ManagedImageDataDiskSnapshotPrefix: dataDiskSnapshotPrefix,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewManagedImageArtifactWithSIGAsDestination(osType, resourceGroup, name, location, id, osDiskSnapshotName, dataDiskSnapshotPrefix, destinationSharedImageGalleryId string) (*Artifact, error) {
|
||||
func NewManagedImageArtifact(osType, resourceGroup, name, location, id, osDiskSnapshotName, dataDiskSnapshotPrefix, destinationSharedImageGalleryId string) (*Artifact, error) {
|
||||
return &Artifact{
|
||||
ManagedImageResourceGroupName: resourceGroup,
|
||||
ManagedImageName: name,
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestArtifactIdVHD(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImage(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "fakeOsDiskSnapshotName", "fakeDataDiskSnapshotPrefix")
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "fakeOsDiskSnapshotName", "fakeDataDiskSnapshotPrefix", "fakeSharedImageGalleryId")
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
@@ -56,6 +56,7 @@ ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageOSDiskSnapshotName: fakeOsDiskSnapshotName
|
||||
ManagedImageDataDiskSnapshotPrefix: fakeDataDiskSnapshotPrefix
|
||||
ManagedImageSharedImageGalleryId: fakeSharedImageGalleryId
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
@@ -65,7 +66,7 @@ ManagedImageDataDiskSnapshotPrefix: fakeDataDiskSnapshotPrefix
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImageWithoutOSDiskSnapshotName(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "", "fakeDataDiskSnapshotPrefix")
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "", "fakeDataDiskSnapshotPrefix", "fakeSharedImageGalleryId")
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
@@ -78,6 +79,7 @@ ManagedImageName: fakeName
|
||||
ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageDataDiskSnapshotPrefix: fakeDataDiskSnapshotPrefix
|
||||
ManagedImageSharedImageGalleryId: fakeSharedImageGalleryId
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
@@ -87,7 +89,7 @@ ManagedImageDataDiskSnapshotPrefix: fakeDataDiskSnapshotPrefix
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImageWithoutDataDiskSnapshotPrefix(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "fakeOsDiskSnapshotName", "")
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "fakeOsDiskSnapshotName", "", "fakeSharedImageGalleryId")
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
@@ -100,6 +102,7 @@ ManagedImageName: fakeName
|
||||
ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageOSDiskSnapshotName: fakeOsDiskSnapshotName
|
||||
ManagedImageSharedImageGalleryId: fakeSharedImageGalleryId
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
@@ -108,8 +111,8 @@ ManagedImageOSDiskSnapshotName: fakeOsDiskSnapshotName
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImageWithSharedImageGalleryId(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifactWithSIGAsDestination("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "fakeOsDiskSnapshotName", "fakeDataDiskSnapshotPrefix", "fakeSharedImageGallery")
|
||||
func TestArtifactIDManagedImageWithoutSharedImageGalleryId(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "fakeOsDiskSnapshotName", "fakeDataDiskSnapshotPrefix", "")
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
@@ -123,7 +126,6 @@ ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageOSDiskSnapshotName: fakeOsDiskSnapshotName
|
||||
ManagedImageDataDiskSnapshotPrefix: fakeDataDiskSnapshotPrefix
|
||||
ManagedImageSharedImageGalleryId: fakeSharedImageGallery
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package client
|
||||
package arm
|
||||
|
||||
import (
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package client
|
||||
package arm
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
func NewCertOAuthTokenProvider(env azure.Environment, clientID, clientCertPath, tenantID string) (oAuthTokenProvider, error) {
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
package client
|
||||
package arm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
packerAzureCommon "github.com/hashicorp/packer/builder/azure/common"
|
||||
)
|
||||
|
||||
func NewDeviceFlowOAuthTokenProvider(env azure.Environment, say func(string), tenantID string) oAuthTokenProvider {
|
||||
@@ -35,5 +36,5 @@ func (tp *deviceflowOauthTokenProvider) getServicePrincipalTokenWithResource(res
|
||||
tp.say(fmt.Sprintf("Getting token for %s", resource))
|
||||
}
|
||||
|
||||
return Authenticate(tp.env, tp.tenantID, tp.say, resource)
|
||||
return packerAzureCommon.Authenticate(tp.env, tp.tenantID, tp.say, resource)
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package client
|
||||
package arm
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package client
|
||||
package arm
|
||||
|
||||
import (
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package client
|
||||
package arm
|
||||
|
||||
import (
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package client
|
||||
package arm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user