Merge pull request #1 from hashicorp/master
Synchronize fork from upstream repository
This commit is contained in:
commit
e490b7651d
|
@ -1,68 +1,131 @@
|
|||
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/
|
||||
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/
|
||||
# Golang CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
<<: *golang
|
||||
test-linux:
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
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:
|
||||
<<: *golang
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
environment:
|
||||
GO111MODULE: "off"
|
||||
steps:
|
||||
- checkout
|
||||
- run: GO111MODULE=on go run . --help
|
||||
- run: make check-vendor-vs-mod
|
||||
check-fmt:
|
||||
<<: *golang
|
||||
executor: golang
|
||||
steps:
|
||||
- checkout
|
||||
- run: make fmt-check
|
||||
check-generate:
|
||||
<<: *golang
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- checkout
|
||||
- run: make generate-check
|
||||
build_linux:
|
||||
<<: *golang
|
||||
environment:
|
||||
GOOS: linux
|
||||
executor: golang
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: linux
|
||||
build_windows:
|
||||
<<: *golang
|
||||
environment:
|
||||
GOOS: windows
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: windows
|
||||
build_darwin:
|
||||
<<: *golang
|
||||
environment:
|
||||
GOOS: darwin
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: darwin
|
||||
build_freebsd:
|
||||
<<: *golang
|
||||
environment:
|
||||
GOOS: freebsd
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: freebsd
|
||||
build_solaris:
|
||||
<<: *golang
|
||||
environment:
|
||||
GOOS: solaris
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: solaris
|
||||
build_openbsd:
|
||||
<<: *golang
|
||||
environment:
|
||||
GOOS: openbsd
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: openbsd
|
||||
store_artifacts:
|
||||
<<: *golang
|
||||
executor: golang
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
|
@ -80,12 +143,18 @@ 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
|
||||
build_and_check_vendor_vs_module:
|
||||
test:
|
||||
jobs:
|
||||
- test-linux
|
||||
- test-darwin
|
||||
- test-windows
|
||||
check-code:
|
||||
jobs:
|
||||
- build
|
||||
- check-vendor-vs-mod
|
||||
- check-fmt
|
||||
- check-generate
|
||||
build_packer_binaries:
|
||||
jobs:
|
||||
- build_linux:
|
||||
filters:
|
||||
tags:
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
*.json text eol=lf
|
||||
*.md text eol=lf
|
||||
*.ps1 text eol=lf
|
||||
*.hcl text eol=lf
|
||||
common/test-fixtures/root/* eol=lf
|
||||
|
|
|
@ -246,6 +246,19 @@ 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:
|
||||
|
@ -292,3 +305,12 @@ 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 via RPC 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.
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
_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
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
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.
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
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.
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
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
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
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,3 +26,4 @@ packer-test*.log
|
|||
Thumbs.db
|
||||
/packer.exe
|
||||
.project
|
||||
cache
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
behavior "regexp_issue_labeler" "panic_label" {
|
||||
regexp = "panic:"
|
||||
labels = ["crash", "bug"]
|
||||
}
|
||||
|
||||
behavior "remove_labels_on_reply" "remove_stale" {
|
||||
labels = ["waiting-reply", "stale"]
|
||||
only_non_maintainers = true
|
||||
}
|
||||
|
||||
poll "closed_issue_locker" "locker" {
|
||||
schedule = "0 50 1 * * *"
|
||||
closed_for = "720h" # 30 days
|
||||
max_issues = 500
|
||||
sleep_between_issues = "5s"
|
||||
|
||||
message = <<-EOF
|
||||
I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues.
|
||||
|
||||
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.
|
||||
EOF
|
||||
}
|
282
CHANGELOG.md
282
CHANGELOG.md
|
@ -1,11 +1,289 @@
|
|||
## 1.4.4 (Upcoming)
|
||||
## 1.5.2 (Upcoming)
|
||||
** New Builder ** The vsphere-iso builder, previously maintained by JetBrains,
|
||||
has been merged with the Packer core. It will be officially supported by the
|
||||
Packer team at HashiCorp moving forward. [GH-8480]
|
||||
|
||||
### IMPROVEMENTS:
|
||||
* builder/openstack: Store WinRM password for provisioners to use [GH-7940]
|
||||
* builder/alicloud: Add AlicloudProfile option. [GH-8560]
|
||||
* builder/amazon: Add source AMI owner ID/name to template engines [GH-8550]
|
||||
* builder/azure: Set expiry for image versions in SIG [GH-8561]
|
||||
* builder/proxmox: Add option to upload the boot ISO rather than pointing out a
|
||||
previously manually uploaded one. [GH-8624]
|
||||
* builder/vagrant: Fix a crash in the Vagrant driver [GH-8607]
|
||||
* core: clean up messy log line in plugin execution. [GH-8542]
|
||||
* core: Fix loading external plugins defined in PACKER_CONFIG [GH-8582]
|
||||
* core: Log name of postprocessor running to disambiguate long chains of post-
|
||||
processors. [GH-8613]
|
||||
* core: step_download: return without error if Urls is empty [GH-8579]
|
||||
* post-processor/vsphere-template] Simplify method to use vm.MarkAsTemplate
|
||||
(optionally) [GH-8511]
|
||||
* scripts: Fix some issues with mapstructure-to-hcl2 code generator. [GH-8574]
|
||||
* scripts: Update Vagrant bootstrapping scripts [GH-8604]
|
||||
|
||||
### Bug Fixes:
|
||||
* builder/alicloud: Fix "security group doesn't exist" error when there are >10
|
||||
security groups. [GH-8535]
|
||||
* builder/amazon: Allow AWS builder pre-validation to pass when subnet filters
|
||||
are present [GH-8622]
|
||||
* builder/azure: Fix bug where deployments were not being cleaned up: [GH-8496]
|
||||
* builder/null: Fix crash when configuring builder using HCL2. [GH-8612]
|
||||
* builder/osc: Fix ssh host detection in Public Cloud and Nets [GH-8414]
|
||||
* builder/vagrant: Fix bug with reading key from a path with spaces [GH-8605]
|
||||
* builder/virtualbox-ovf: Remove config dependency from StepImport [GH-8509]
|
||||
* builder/virtualbox-vm: use config as a non pointer to avoid a panic [GH-8576]
|
||||
* core: Fix crash when build.sources is set to an invalid name [GH-8569]
|
||||
* core: Fix error loading .packerconfig [GH-8623]
|
||||
* core: Fix loading of external plugins. GH-8543]
|
||||
* post-processor/docker-tag: Fix regression if no tags were specified.
|
||||
[GH-8593]
|
||||
* post-processor/vagrant-cloud: Update error handling for Vagrant Cloud API
|
||||
[GH-8594]
|
||||
* post-processor/vagrant: correctly handle the diskSize property as a qemu size
|
||||
string [GH-8567]
|
||||
* provisioner/ansible: Fix password sanitization to account for empty string
|
||||
values. [GH-8570]
|
||||
* provisioner/shell: Fix bug with shell provisioner failing to clean up the
|
||||
environment var file when env_var_file is true. [GH-8639]
|
||||
|
||||
## 1.5.1 (December 20, 2019)
|
||||
This was a fast-follow release to fix a number of panics that we introduced when
|
||||
making changes for HCL2.
|
||||
|
||||
### IMPROVEMENTS:
|
||||
* builder/alicloud: Add show_expired option for describing images [GH-8425]
|
||||
|
||||
### Bug Fixes:
|
||||
* builder/cloudstack: Fix panics associated with loading config [GH-8513]
|
||||
* builder/hyperv/iso: Fix panics associated with loading config [GH-8513]
|
||||
* builder/hyperv/vmcx: Fix panics associated with loading config [GH-8513]
|
||||
* builder/jdcloud: Update jdcloud statebag to use pointers for config [GH-8518]
|
||||
* builder/linode: Fix panics associated with loading config [GH-8513]
|
||||
* builder/lxc: Fix panics associated with loading config [GH-8513]
|
||||
* builder/lxd: Fix panics associated with loading config [GH-8513]
|
||||
* builder/oneandone: Fix panics associated with loading config [GH-8513]
|
||||
* builder/oracle/classic: Fix panics associated with loading config [GH-8513]
|
||||
* builder/oracle/oci: Fix panics associated with loading config [GH-8513]
|
||||
* builder/osc/bsuvolume: Fix panics associated with loading config [GH-8513]
|
||||
* builder/parallels/pvm: Fix panics associated with loading config [GH-8513]
|
||||
* builder/profitbricks: Fix panics associated with loading config [GH-8513]
|
||||
* builder/scaleway: Fix panics associated with loading config [GH-8513]
|
||||
* builder/vagrant: Fix panics associated with loading config [GH-8513]
|
||||
* builder/virtualbox/ovf: Fix panics associated with loading config [GH-8513]
|
||||
* builder/virtualbox: Configure NAT interface before forwarded port mapping
|
||||
#8514
|
||||
* post-processor/vagrant-cloud: Configure NAT interface before forwarded port
|
||||
mapping [GH-8514]
|
||||
|
||||
## 1.5.0 (December 18, 2019)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
* builder/amazon: Add no_ephemeral template option to remove ephemeral drives
|
||||
from launch mappings. [GH-8393]
|
||||
* builder/amazon: Add validation for "subnet_id" when specifying "vpc_id"
|
||||
[GH-8360] [GH-8387] [GH-8391]
|
||||
* builder/amazon: allow enabling ena/sr-iov on ebssurrogate spot instances
|
||||
[GH-8397]
|
||||
* builder/amazon: Retry runinstances aws api call to mitigate throttling
|
||||
[GH-8342]
|
||||
* builder/hyperone: Update builder schema and tags [GH-8444]
|
||||
* builder/qemu: Add display template option for qemu. [GH-7676]
|
||||
* builder/qemu: Disk Size is now read as a string to support units. [GH-8320]
|
||||
[GH-7546]
|
||||
* builder/qemu: Add fixer to convert disk size from int to string [GH-8390]
|
||||
* builder/qemu: Disk Size is now read as a string to support units. [GH-8320]
|
||||
[GH-7546]
|
||||
* builder/qemu: When a user adds a new drive in qemuargs, process it to make
|
||||
sure that necessary settings are applied to that drive. [GH-8380]
|
||||
* builder/vmware: Fix error message when ovftool is missing [GH-8371]
|
||||
* core: Cleanup logging for external plugins [GH-8471]
|
||||
* core: HCL2 template support is now in beta. [GH-8423]
|
||||
* core: Interpolation within provisioners can now access build-specific values
|
||||
like Host IP, communicator password, and more. [GH-7866]
|
||||
* core: Various fixes to error handling. [GH-8343] [GH-8333] [GH-8316]
|
||||
[GH-8354] [GH-8361] [GH-8363] [GH-8370]
|
||||
* post-processor/docker-tag: Add support for multiple tags. [GH-8392]
|
||||
* post-processor/shell-local: Add "valid_exit_codes" option to shell-local.
|
||||
[GH-8401]
|
||||
* provisioner/chef-client: Add version selection option. [GH-8468]
|
||||
* provisioner/shell-local: Add "valid_exit_codes" option to shell-local.
|
||||
[GH-8401]
|
||||
* provisioner/shell: Add support for the "env_var_format" parameter [GH-8319]
|
||||
|
||||
### BUG FIXES:
|
||||
* builder/amazon: Fix request retry mechanism to launch aws instance [GH-8430]
|
||||
* builder/azure: Fix PollDuration option which was overriden in some clients.
|
||||
[GH-8490]
|
||||
* builder/hyperv: Fix bug in checking VM name that could cause flakiness if
|
||||
many VMs are defined. [GH-8357]
|
||||
* builder/vagrant: Use absolute path for Vagrantfile [GH-8321]
|
||||
* builder/virtualbox: Fix panic in snapshot builder. [GH-8336] [GH-8329]
|
||||
* communicator/winrm: Resolve ntlm nil pointer bug by bumping go-ntlmssp
|
||||
dependency [GH-8369]
|
||||
* communicator: Fix proxy connection settings to use "SSHProxyUsername" and
|
||||
"SSHProxyPassword" where relevant instead of bastion username and password.
|
||||
[GH-8375]
|
||||
* core: Fix bug where Packer froze if asked to log an extremely long line
|
||||
[GH-8356]
|
||||
* core: Fix iso_target_path option; don't cache when target path is non-nil
|
||||
[GH-8394]
|
||||
* core: Return exit code 1 when builder type is not found [GH-8474]
|
||||
* core: Return exit code 1 when builder type is not found [GH-8475]
|
||||
* core: Update to newest version of go-tty to re-enable CTRL-S and CTRL-Q usage
|
||||
[GH-8364]
|
||||
|
||||
### BACKWARDS INCOMPATIBILITIES:
|
||||
* builder/amazon: Complete deprecation of clean_ami_name template func
|
||||
[GH-8320] [GH-8193]
|
||||
* core: Changes have been made to both the Prepare() method signature on the
|
||||
builder interface and on the Provision() method signature on the
|
||||
provisioner interface. [GH-7866]
|
||||
* provisioner/ansible-local: The "galaxycommand" option has been renamed to
|
||||
"galaxy_command". A fixer has been written for this, which can be invoked
|
||||
with `packer fix`. [GH-8411]
|
||||
|
||||
## 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)
|
||||
|
||||
|
|
73
CODEOWNERS
73
CODEOWNERS
|
@ -2,23 +2,63 @@
|
|||
|
||||
# builders
|
||||
|
||||
/builder/alicloud/ @chhaj5236
|
||||
/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/alicloud/ @chhaj5236 @alexyueer
|
||||
/website/source/docs/builders/alicloud* @chhaj5236 @alexyueer
|
||||
|
||||
/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/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
|
||||
/builder/hcloud/ @LKaemmerling
|
||||
/builder/hyperone @m110 @gregorybrzeski @ad-m
|
||||
/builder/ucloud/ @shawnmssu
|
||||
/builder/yandex @GennadySpb @alexanderKhaustov @seukyaso
|
||||
/builder/osc @marinsalinas
|
||||
/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
|
||||
|
||||
|
||||
# provisioners
|
||||
|
||||
|
@ -32,3 +72,4 @@
|
|||
/post-processor/exoscale-import/ @falzm @mcorbin
|
||||
/post-processor/googlecompute-export/ crunkleton@google.com
|
||||
/post-processor/vsphere-template/ nelson@bennu.cl
|
||||
/post-processor/ucloud-import/ @shawnmssu
|
||||
|
|
22
Makefile
22
Makefile
|
@ -45,14 +45,15 @@ install-build-deps: ## Install dependencies for bin build
|
|||
@go get github.com/mitchellh/gox
|
||||
|
||||
install-gen-deps: ## Install dependencies for code generation
|
||||
@go get golang.org/x/tools/cmd/goimports
|
||||
@./scripts/off_gopath.sh; if [ $$? -eq 0 ]; then \
|
||||
go get github.com/mna/pigeon@master; \
|
||||
else \
|
||||
go get -u github.com/mna/pigeon; \
|
||||
fi
|
||||
|
||||
@go get github.com/alvaroloes/enumer
|
||||
# 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
|
||||
|
||||
dev: ## Build and install a development build
|
||||
@grep 'const VersionPrerelease = ""' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \
|
||||
|
@ -95,9 +96,12 @@ 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,8 +20,6 @@ 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
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
// +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
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func checkProcess(currentPID int) (bool, error) {
|
||||
return false, fmt.Errorf("cannot determine if process is backgrounded in " +
|
||||
"openbsd")
|
||||
}
|
|
@ -1,22 +1,47 @@
|
|||
//go:generate struct-markdown
|
||||
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/hashicorp/packer/version"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// Config of alicloud
|
||||
type AlicloudAccessConfig struct {
|
||||
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"`
|
||||
// This is the Alicloud access key. It must be provided when profile not exist, but it can also be
|
||||
// sourced from the ALICLOUD_ACCESS_KEY environment variable.
|
||||
AlicloudAccessKey string `mapstructure:"access_key" required:"false"`
|
||||
// This is the Alicloud secret key. It must be provided when profile not exist, but it can also be
|
||||
// sourced from the ALICLOUD_SECRET_KEY environment variable.
|
||||
AlicloudSecretKey string `mapstructure:"secret_key" required:"false"`
|
||||
// This is the Alicloud region. It must be provided when profile not exist, but it can also be
|
||||
// sourced from the ALICLOUD_REGION environment variables.
|
||||
AlicloudRegion string `mapstructure:"region" required:"false"`
|
||||
// The region validation can be skipped if this value is true, the default
|
||||
// value is false.
|
||||
AlicloudSkipValidation bool `mapstructure:"skip_region_validation" required:"false"`
|
||||
// The image validation can be skipped if this value is true, the default
|
||||
// value is false.
|
||||
AlicloudSkipImageValidation bool `mapstructure:"skip_image_validation" required:"false"`
|
||||
// This is th Alicloud profile. If access_key not exist, is must be provided, but it can also be
|
||||
// sourced from the ALICLOUD_PROFILE environment variables.
|
||||
AlicloudProfile string `mapstructure:"profile" required:"false"`
|
||||
// This is the Alicloud shared credentials file path. If this file path exist, os will read access key
|
||||
// and secret key from this file.
|
||||
AlicloudSharedCredentialsFile string `mapstructure:"shared_credentials_file" 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"`
|
||||
|
||||
client *ClientWrapper
|
||||
}
|
||||
|
@ -33,8 +58,22 @@ func (c *AlicloudAccessConfig) Client() (*ClientWrapper, error) {
|
|||
c.SecurityToken = os.Getenv("SECURITY_TOKEN")
|
||||
}
|
||||
|
||||
client, err := ecs.NewClientWithStsToken(c.AlicloudRegion, c.AlicloudAccessKey,
|
||||
c.AlicloudSecretKey, c.SecurityToken)
|
||||
var getProviderConfig = func(str string, key string) string {
|
||||
value, err := getConfigFromProfile(c, key)
|
||||
if err == nil && value != nil {
|
||||
str = value.(string)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
if c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "" {
|
||||
c.AlicloudAccessKey = getProviderConfig(c.AlicloudAccessKey, "access_key_id")
|
||||
c.AlicloudSecretKey = getProviderConfig(c.AlicloudSecretKey, "access_key_secret")
|
||||
c.AlicloudRegion = getProviderConfig(c.AlicloudRegion, "region_id")
|
||||
c.SecurityToken = getProviderConfig(c.SecurityToken, "sts_token")
|
||||
}
|
||||
|
||||
client, err := ecs.NewClientWithStsToken(c.AlicloudRegion, c.AlicloudAccessKey, c.AlicloudSecretKey, c.SecurityToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -74,7 +113,13 @@ func (c *AlicloudAccessConfig) Config() error {
|
|||
if c.AlicloudSecretKey == "" {
|
||||
c.AlicloudSecretKey = os.Getenv("ALICLOUD_SECRET_KEY")
|
||||
}
|
||||
if c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "" {
|
||||
if c.AlicloudProfile == "" {
|
||||
c.AlicloudProfile = os.Getenv("ALICLOUD_PROFILE")
|
||||
}
|
||||
if c.AlicloudSharedCredentialsFile == "" {
|
||||
c.AlicloudSharedCredentialsFile = os.Getenv("ALICLOUD_SHARED_CREDENTIALS_FILE")
|
||||
}
|
||||
if (c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "") && c.AlicloudProfile == "" {
|
||||
return fmt.Errorf("ALICLOUD_ACCESS_KEY and ALICLOUD_SECRET_KEY must be set in template file or environment variables.")
|
||||
}
|
||||
return nil
|
||||
|
@ -116,3 +161,66 @@ func (c *AlicloudAccessConfig) getSupportedRegions() ([]string, error) {
|
|||
|
||||
return validRegions, nil
|
||||
}
|
||||
|
||||
func getConfigFromProfile(c *AlicloudAccessConfig, ProfileKey string) (interface{}, error) {
|
||||
providerConfig := make(map[string]interface{})
|
||||
current := c.AlicloudProfile
|
||||
if current != "" {
|
||||
profilePath, err := homedir.Expand(c.AlicloudSharedCredentialsFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if profilePath == "" {
|
||||
profilePath = fmt.Sprintf("%s/.aliyun/config.json", os.Getenv("HOME"))
|
||||
if runtime.GOOS == "windows" {
|
||||
profilePath = fmt.Sprintf("%s/.aliyun/config.json", os.Getenv("USERPROFILE"))
|
||||
}
|
||||
}
|
||||
_, err = os.Stat(profilePath)
|
||||
if !os.IsNotExist(err) {
|
||||
data, err := ioutil.ReadFile(profilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config := map[string]interface{}{}
|
||||
err = json.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range config["profiles"].([]interface{}) {
|
||||
if current == v.(map[string]interface{})["name"] {
|
||||
providerConfig = v.(map[string]interface{})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mode := ""
|
||||
if v, ok := providerConfig["mode"]; ok {
|
||||
mode = v.(string)
|
||||
} else {
|
||||
return v, nil
|
||||
}
|
||||
switch ProfileKey {
|
||||
case "access_key_id", "access_key_secret":
|
||||
if mode == "EcsRamRole" {
|
||||
return "", nil
|
||||
}
|
||||
case "ram_role_name":
|
||||
if mode != "EcsRamRole" {
|
||||
return "", nil
|
||||
}
|
||||
case "sts_token":
|
||||
if mode != "StsToken" {
|
||||
return "", nil
|
||||
}
|
||||
case "ram_role_arn", "ram_session_name":
|
||||
if mode != "RamRoleArn" {
|
||||
return "", nil
|
||||
}
|
||||
case "expired_seconds":
|
||||
if mode != "RamRoleArn" {
|
||||
return float64(0), nil
|
||||
}
|
||||
}
|
||||
return providerConfig[ProfileKey], nil
|
||||
}
|
||||
|
|
|
@ -32,5 +32,21 @@ func TestAlicloudAccessConfigPrepareRegion(t *testing.T) {
|
|||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AlicloudAccessKey = ""
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatalf("should have err")
|
||||
}
|
||||
|
||||
c.AlicloudProfile = "default"
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AlicloudProfile = ""
|
||||
os.Setenv("ALICLOUD_PROFILE", "default")
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AlicloudSkipValidation = false
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//go:generate mapstructure-to-hcl2 -type Config,AlicloudDiskDevice
|
||||
|
||||
// The alicloud contains a packer.Builder implementation that
|
||||
// builds ecs images for alicloud.
|
||||
package ecs
|
||||
|
@ -6,6 +8,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
|
@ -39,7 +42,9 @@ const (
|
|||
ALICLOUD_DEFAULT_LONG_TIMEOUT = 3600
|
||||
)
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
|
@ -51,7 +56,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}, raws...)
|
||||
b.config.ctx.EnableEnv = true
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if b.config.PackerConfig.PackerForce {
|
||||
|
@ -66,11 +71,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, errs
|
||||
return nil, nil, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AlicloudAccessKey, b.config.AlicloudSecretKey)
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
// 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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatAlicloudDiskDevice)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a AlicloudDiskDevice.
|
||||
// This spec is used by HCL to read the fields of AlicloudDiskDevice.
|
||||
// The decoded values from this spec will then be applied to a 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:"false" cty:"access_key"`
|
||||
AlicloudSecretKey *string `mapstructure:"secret_key" required:"false" cty:"secret_key"`
|
||||
AlicloudRegion *string `mapstructure:"region" required:"false" cty:"region"`
|
||||
AlicloudSkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"`
|
||||
AlicloudSkipImageValidation *bool `mapstructure:"skip_image_validation" required:"false" cty:"skip_image_validation"`
|
||||
AlicloudProfile *string `mapstructure:"profile" required:"false" cty:"profile"`
|
||||
AlicloudSharedCredentialsFile *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file"`
|
||||
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 `mapstructure:"ssh_public_key" cty:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" 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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Config.
|
||||
// This spec is used by HCL to read the fields of Config.
|
||||
// The decoded values from this spec will then be applied to a 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},
|
||||
"skip_image_validation": &hcldec.AttrSpec{Name: "skip_image_validation", Type: cty.Bool, Required: false},
|
||||
"profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false},
|
||||
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, 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.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,6 +4,7 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
helperconfig "github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
|
@ -34,7 +35,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) {
|
|||
"access_key": []string{},
|
||||
}
|
||||
|
||||
warnings, err := b.Prepare(c)
|
||||
_, warnings, err := b.Prepare(c)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -49,7 +50,7 @@ func TestBuilderPrepare_ECSImageName(t *testing.T) {
|
|||
|
||||
// Test good
|
||||
config["image_name"] = "ecs.n1.tiny"
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -60,7 +61,7 @@ func TestBuilderPrepare_ECSImageName(t *testing.T) {
|
|||
// Test bad
|
||||
config["ecs_image_name"] = "foo {{"
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -71,7 +72,7 @@ func TestBuilderPrepare_ECSImageName(t *testing.T) {
|
|||
// Test bad
|
||||
delete(config, "image_name")
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -86,7 +87,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
|||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -119,20 +120,22 @@ func TestBuilderPrepare_Devices(t *testing.T) {
|
|||
"disk_device": "/dev/xvdc",
|
||||
},
|
||||
}
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(b.config.ECSSystemDiskMapping, AlicloudDiskDevice{
|
||||
expected := AlicloudDiskDevice{
|
||||
DiskCategory: "cloud",
|
||||
Description: "system disk",
|
||||
DiskName: "system_disk",
|
||||
DiskSize: 60,
|
||||
}) {
|
||||
t.Fatalf("system disk is not set properly, actual: %#v", b.config.ECSSystemDiskMapping)
|
||||
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)
|
||||
}
|
||||
if !reflect.DeepEqual(b.config.ECSImagesDiskMappings, []AlicloudDiskDevice{
|
||||
{
|
||||
|
@ -157,7 +160,7 @@ func TestBuilderPrepare_IgnoreDataDisks(t *testing.T) {
|
|||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -170,7 +173,7 @@ func TestBuilderPrepare_IgnoreDataDisks(t *testing.T) {
|
|||
}
|
||||
|
||||
config["image_ignore_data_disks"] = "false"
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -183,7 +186,7 @@ func TestBuilderPrepare_IgnoreDataDisks(t *testing.T) {
|
|||
}
|
||||
|
||||
config["image_ignore_data_disks"] = "true"
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -200,7 +203,7 @@ func TestBuilderPrepare_WaitSnapshotReadyTimeout(t *testing.T) {
|
|||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -216,7 +219,7 @@ func TestBuilderPrepare_WaitSnapshotReadyTimeout(t *testing.T) {
|
|||
}
|
||||
|
||||
config["wait_snapshot_ready_timeout"] = ALICLOUD_DEFAULT_TIMEOUT
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
|
|
@ -33,14 +33,11 @@ func TestWaitForExpectedExceedRetryTimes(t *testing.T) {
|
|||
waitDone <- true
|
||||
}()
|
||||
|
||||
timeTolerance := 1 * time.Second
|
||||
select {
|
||||
case <-waitDone:
|
||||
if iter != defaultRetryTimes {
|
||||
t.Fatalf("WaitForExpected should terminate at the %d iterations", defaultRetryTimes)
|
||||
}
|
||||
case <-time.After(defaultRetryTimes*defaultRetryInterval + timeTolerance):
|
||||
t.Fatalf("WaitForExpected should terminate within %f seconds", (defaultRetryTimes*defaultRetryInterval + timeTolerance).Seconds())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//go:generate struct-markdown
|
||||
|
||||
package ecs
|
||||
|
||||
import (
|
||||
|
@ -5,41 +7,192 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type AlicloudDiskDevice struct {
|
||||
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"`
|
||||
// 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"`
|
||||
}
|
||||
|
||||
type AlicloudDiskDevices struct {
|
||||
ECSSystemDiskMapping AlicloudDiskDevice `mapstructure:"system_disk_mapping"`
|
||||
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings"`
|
||||
// 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"`
|
||||
}
|
||||
|
||||
type AlicloudImageConfig struct {
|
||||
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"`
|
||||
// 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"`
|
||||
}
|
||||
|
||||
func (c *AlicloudImageConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//go:generate struct-markdown
|
||||
|
||||
package ecs
|
||||
|
||||
import (
|
||||
|
@ -8,35 +10,115 @@ 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"`
|
||||
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"`
|
||||
|
||||
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"`
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
|
||||
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"`
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
|
|
|
@ -13,7 +13,9 @@ func testConfig() *RunConfig {
|
|||
AlicloudSourceImage: "alicloud_images",
|
||||
InstanceType: "ecs.n1.tiny",
|
||||
Comm: communicator.Config{
|
||||
SSHUsername: "alicloud",
|
||||
SSH: communicator.SSH{
|
||||
SSHUsername: "alicloud",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@ func (s *stepCheckAlicloudSourceImage) Run(ctx context.Context, state multistep.
|
|||
describeImagesRequest := ecs.CreateDescribeImagesRequest()
|
||||
describeImagesRequest.RegionId = config.AlicloudRegion
|
||||
describeImagesRequest.ImageId = config.AlicloudSourceImage
|
||||
if config.AlicloudSkipImageValidation {
|
||||
describeImagesRequest.ShowExpired = "true"
|
||||
}
|
||||
imagesResponse, err := client.DescribeImages(describeImagesRequest)
|
||||
if err != nil {
|
||||
return halt(state, err, "Error querying alicloud image")
|
||||
|
|
|
@ -36,7 +36,7 @@ func (s *stepConfigAlicloudSecurityGroup) Run(ctx context.Context, state multist
|
|||
if len(s.SecurityGroupId) != 0 {
|
||||
describeSecurityGroupsRequest := ecs.CreateDescribeSecurityGroupsRequest()
|
||||
describeSecurityGroupsRequest.RegionId = s.RegionId
|
||||
|
||||
describeSecurityGroupsRequest.SecurityGroupId = s.SecurityGroupId
|
||||
if networkType == InstanceNetworkVpc {
|
||||
vpcId := state.Get("vpcid").(string)
|
||||
describeSecurityGroupsRequest.VpcId = vpcId
|
||||
|
|
|
@ -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 != nil && *config.ImageEncrypted {
|
||||
if config.ImageEncrypted.True() {
|
||||
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 != nil && *config.ImageEncrypted
|
||||
encryptedSet := config.ImageEncrypted.True()
|
||||
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
|
|
@ -12,12 +12,13 @@ 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 *bool
|
||||
IOOptimized confighelper.Trilean
|
||||
InstanceType string
|
||||
UserData string
|
||||
UserDataFile string
|
||||
|
@ -76,6 +77,9 @@ func (s *stepCreateAlicloudInstance) Run(ctx context.Context, state multistep.St
|
|||
ui.Message(fmt.Sprintf("Created instance: %s", instanceId))
|
||||
s.instance = &instances.Instances.Instance[0]
|
||||
state.Put("instance", s.instance)
|
||||
// instance_id is the generic term used so that users can have access to the
|
||||
// instance id inside of the provisioners, used in step_provision.
|
||||
state.Put("instance_id", instanceId)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
@ -142,12 +146,10 @@ func (s *stepCreateAlicloudInstance) buildCreateInstanceRequest(state multistep.
|
|||
request.InternetChargeType = s.InternetChargeType
|
||||
request.InternetMaxBandwidthOut = requests.Integer(convertNumber(s.InternetMaxBandwidthOut))
|
||||
|
||||
if s.IOOptimized != nil {
|
||||
if *s.IOOptimized {
|
||||
request.IoOptimized = IOOptimizedOptimized
|
||||
} else {
|
||||
request.IoOptimized = IOOptimizedNone
|
||||
}
|
||||
if s.IOOptimized.True() {
|
||||
request.IoOptimized = IOOptimizedOptimized
|
||||
} else if s.IOOptimized.False() {
|
||||
request.IoOptimized = IOOptimizedNone
|
||||
}
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
|
@ -174,8 +176,8 @@ func (s *stepCreateAlicloudInstance) buildCreateInstanceRequest(state multistep.
|
|||
dataDisk.Description = imageDisk.Description
|
||||
dataDisk.DeleteWithInstance = strconv.FormatBool(imageDisk.DeleteWithInstance)
|
||||
dataDisk.Device = imageDisk.Device
|
||||
if imageDisk.Encrypted != nil {
|
||||
dataDisk.Encrypted = strconv.FormatBool(*imageDisk.Encrypted)
|
||||
if imageDisk.Encrypted != confighelper.TriUnset {
|
||||
dataDisk.Encrypted = strconv.FormatBool(imageDisk.Encrypted.True())
|
||||
}
|
||||
|
||||
dataDisks = append(dataDisks, dataDisk)
|
||||
|
|
|
@ -3,9 +3,9 @@ package ecs
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
|
|
@ -7,6 +7,7 @@ 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"
|
||||
)
|
||||
|
@ -20,7 +21,7 @@ type stepRegionCopyAlicloudImage struct {
|
|||
func (s *stepRegionCopyAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
|
||||
if config.ImageEncrypted != nil {
|
||||
if config.ImageEncrypted != confighelper.TriUnset {
|
||||
s.AlicloudImageDestinationRegions = append(s.AlicloudImageDestinationRegions, s.RegionId)
|
||||
s.AlicloudImageDestinationNames = append(s.AlicloudImageDestinationNames, config.AlicloudImageName)
|
||||
}
|
||||
|
@ -38,7 +39,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 == nil {
|
||||
if destinationRegion == s.RegionId && config.ImageEncrypted == confighelper.TriUnset {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -52,8 +53,8 @@ func (s *stepRegionCopyAlicloudImage) Run(ctx context.Context, state multistep.S
|
|||
copyImageRequest.ImageId = srcImageId
|
||||
copyImageRequest.DestinationRegionId = destinationRegion
|
||||
copyImageRequest.DestinationImageName = ecsImageName
|
||||
if config.ImageEncrypted != nil {
|
||||
copyImageRequest.Encrypted = requests.NewBoolean(*config.ImageEncrypted)
|
||||
if config.ImageEncrypted != confighelper.TriUnset {
|
||||
copyImageRequest.Encrypted = requests.NewBoolean(config.ImageEncrypted.True())
|
||||
}
|
||||
|
||||
imageResponse, err := client.CopyImage(copyImageRequest)
|
||||
|
@ -65,7 +66,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 != nil {
|
||||
if config.ImageEncrypted != confighelper.TriUnset {
|
||||
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,17 +1,23 @@
|
|||
// 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.
|
||||
//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.
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/hashicorp/packer/builder"
|
||||
"runtime"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
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"
|
||||
|
@ -21,36 +27,154 @@ 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.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"`
|
||||
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/source_ami_filter are no
|
||||
// longer used and the following options become required:
|
||||
// ami_virtualization_type, pre_mount_commands and root_volume_size.
|
||||
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"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (c *Config) GetContext() interpolate.Context {
|
||||
return c.ctx
|
||||
}
|
||||
|
||||
type wrappedCommandTemplate struct {
|
||||
Command string
|
||||
}
|
||||
|
@ -60,7 +184,9 @@ type Builder struct {
|
|||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
|
@ -79,7 +205,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if b.config.Architecture == "" {
|
||||
|
@ -171,8 +297,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"))
|
||||
}
|
||||
|
@ -197,11 +323,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return warns, errs
|
||||
return nil, warns, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
return warns, nil
|
||||
generatedData := []string{"SourceAMIName", "Device", "MountPath"}
|
||||
return generatedData, warns, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
|
@ -230,7 +357,8 @@ 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", CommandWrapper(wrappedCommand))
|
||||
state.Put("wrappedCommand", common.CommandWrapper(wrappedCommand))
|
||||
generatedData := &builder.GeneratedData{State: state}
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
|
@ -256,7 +384,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
|
||||
steps = append(steps,
|
||||
&StepFlock{},
|
||||
&StepPrepareDevice{},
|
||||
&StepPrepareDevice{
|
||||
GeneratedData: generatedData,
|
||||
},
|
||||
&StepCreateVolume{
|
||||
RootVolumeType: b.config.RootVolumeType,
|
||||
RootVolumeSize: b.config.RootVolumeSize,
|
||||
|
@ -265,20 +395,25 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
},
|
||||
&StepAttachVolume{},
|
||||
&StepEarlyUnflock{},
|
||||
&StepPreMountCommands{
|
||||
&chroot.StepPreMountCommands{
|
||||
Commands: b.config.PreMountCommands,
|
||||
},
|
||||
&StepMountDevice{
|
||||
MountOptions: b.config.MountOptions,
|
||||
MountPartition: b.config.MountPartition,
|
||||
GeneratedData: generatedData,
|
||||
},
|
||||
&StepPostMountCommands{
|
||||
&chroot.StepPostMountCommands{
|
||||
Commands: b.config.PostMountCommands,
|
||||
},
|
||||
&StepMountExtra{},
|
||||
&StepCopyFiles{},
|
||||
&StepChrootProvision{},
|
||||
&StepEarlyCleanup{},
|
||||
&chroot.StepMountExtra{
|
||||
ChrootMounts: b.config.ChrootMounts,
|
||||
},
|
||||
&chroot.StepCopyFiles{
|
||||
Files: b.config.CopyFiles,
|
||||
},
|
||||
&chroot.StepChrootProvision{},
|
||||
&chroot.StepEarlyCleanup{},
|
||||
&StepSnapshot{},
|
||||
&awscommon.StepDeregisterAMI{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
|
@ -291,6 +426,7 @@ 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,
|
||||
|
@ -309,6 +445,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
SnapshotUsers: b.config.SnapshotUsers,
|
||||
SnapshotGroups: b.config.SnapshotGroups,
|
||||
Ctx: b.config.ctx,
|
||||
GeneratedData: generatedData,
|
||||
},
|
||||
&awscommon.StepCreateTags{
|
||||
Tags: b.config.AMITags,
|
||||
|
@ -336,6 +473,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
Amis: state.Get("amis").(map[string]string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Session: session,
|
||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
// 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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Config.
|
||||
// This spec is used by HCL to read the fields of Config.
|
||||
// The decoded values from this spec will then be applied to a 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: "tags", 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: "snapshot_tags", 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.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
|
||||
"chroot_mounts": &hcldec.AttrSpec{Name: "chroot_mounts", Type: cty.List(cty.List(cty.String)), Required: false},
|
||||
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
|
||||
"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: "root_volume_tags", ElementType: cty.String, Required: false},
|
||||
"ami_architecture": &hcldec.AttrSpec{Name: "ami_architecture", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -31,7 +31,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
|||
// Test good
|
||||
config["ami_name"] = "foo"
|
||||
config["skip_region_validation"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
|||
// Test bad
|
||||
config["ami_name"] = "foo {{"
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
|||
// Test bad
|
||||
delete(config, "ami_name")
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ func TestBuilderPrepare_ChrootMounts(t *testing.T) {
|
|||
config := testConfig()
|
||||
|
||||
config["chroot_mounts"] = nil
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ func TestBuilderPrepare_ChrootMountsBadDefaults(t *testing.T) {
|
|||
config["chroot_mounts"] = [][]string{
|
||||
{"bad"},
|
||||
}
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ func TestBuilderPrepare_SourceAmi(t *testing.T) {
|
|||
config := testConfig()
|
||||
|
||||
config["source_ami"] = ""
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ func TestBuilderPrepare_SourceAmi(t *testing.T) {
|
|||
}
|
||||
|
||||
config["source_ami"] = "foo"
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ func TestBuilderPrepare_CommandWrapper(t *testing.T) {
|
|||
config := testConfig()
|
||||
|
||||
config["command_wrapper"] = "echo hi; {{.Command}}"
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ func TestBuilderPrepare_CopyFiles(t *testing.T) {
|
|||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ func TestBuilderPrepare_CopyFilesNoDefault(t *testing.T) {
|
|||
config := testConfig()
|
||||
|
||||
config["copy_files"] = []string{}
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ func TestBuilderPrepare_RootDeviceNameAndAMIMappings(t *testing.T) {
|
|||
config["root_device_name"] = "/dev/sda"
|
||||
config["ami_block_device_mappings"] = []interface{}{map[string]string{}}
|
||||
config["root_volume_size"] = 15
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) == 0 {
|
||||
t.Fatal("Missing warning, stating block device mappings will be overwritten")
|
||||
} else if len(warnings) > 1 {
|
||||
|
@ -187,7 +187,7 @@ func TestBuilderPrepare_AMIMappingsNoRootDeviceName(t *testing.T) {
|
|||
config := testConfig()
|
||||
|
||||
config["ami_block_device_mappings"] = []interface{}{map[string]string{}}
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ func TestBuilderPrepare_RootDeviceNameNoAMIMappings(t *testing.T) {
|
|||
config := testConfig()
|
||||
|
||||
config["root_device_name"] = "/dev/sda"
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -209,3 +209,28 @@ func TestBuilderPrepare_RootDeviceNameNoAMIMappings(t *testing.T) {
|
|||
t.Fatalf("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
generatedData, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
if len(generatedData) == 0 {
|
||||
t.Fatalf("Generated data should not be empty")
|
||||
}
|
||||
if generatedData[0] != "SourceAMIName" {
|
||||
t.Fatalf("Generated data should contain SourceAMIName")
|
||||
}
|
||||
if generatedData[1] != "Device" {
|
||||
t.Fatalf("Generated data should contain Device")
|
||||
}
|
||||
if generatedData[2] != "MountPath" {
|
||||
t.Fatalf("Generated data should contain MountPath")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
)
|
||||
|
||||
func TestCopyFile(t *testing.T) {
|
||||
|
@ -26,7 +28,7 @@ func TestCopyFile(t *testing.T) {
|
|||
}
|
||||
first.Sync()
|
||||
|
||||
cmd := ShellCommand(fmt.Sprintf("cp %s %s", first.Name(), newName))
|
||||
cmd := common.ShellCommand(fmt.Sprintf("cp %s %s", first.Name(), newName))
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("Couldn't copy file")
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
package chroot
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/common/chroot"
|
||||
)
|
||||
|
||||
func TestAttachVolumeCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepAttachVolume)
|
||||
if _, ok := raw.(Cleanup); !ok {
|
||||
if _, ok := raw.(chroot.Cleanup); !ok {
|
||||
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer/common/chroot"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
@ -13,7 +14,7 @@ import (
|
|||
type StepEarlyUnflock struct{}
|
||||
|
||||
func (s *StepEarlyUnflock) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
cleanup := state.Get("flock_cleanup").(Cleanup)
|
||||
cleanup := state.Get("flock_cleanup").(chroot.Cleanup)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
log.Println("Unlocking file lock...")
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
package chroot
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/common/chroot"
|
||||
)
|
||||
|
||||
func TestFlockCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepFlock)
|
||||
if _, ok := raw.(Cleanup); !ok {
|
||||
if _, ok := raw.(chroot.Cleanup); !ok {
|
||||
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,14 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/hashicorp/packer/builder"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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"
|
||||
|
@ -28,7 +30,8 @@ type StepMountDevice struct {
|
|||
MountOptions []string
|
||||
MountPartition string
|
||||
|
||||
mountPath string
|
||||
mountPath string
|
||||
GeneratedData *builder.GeneratedData
|
||||
}
|
||||
|
||||
func (s *StepMountDevice) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
|
@ -39,7 +42,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").(CommandWrapper)
|
||||
wrappedCommand := state.Get("wrappedCommand").(common.CommandWrapper)
|
||||
|
||||
var virtualizationType string
|
||||
if config.FromScratch || config.AMIVirtType != "" {
|
||||
|
@ -104,7 +107,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 := ShellCommand(mountCommand)
|
||||
cmd := common.ShellCommand(mountCommand)
|
||||
cmd.Stderr = stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
err := fmt.Errorf(
|
||||
|
@ -117,6 +120,7 @@ func (s *StepMountDevice) Run(ctx context.Context, state multistep.StateBag) mul
|
|||
// Set the mount path so we remember to unmount it later
|
||||
s.mountPath = mountPath
|
||||
state.Put("mount_path", s.mountPath)
|
||||
s.GeneratedData.Put("MountPath", s.mountPath)
|
||||
state.Put("mount_device_cleanup", s)
|
||||
|
||||
return multistep.ActionContinue
|
||||
|
@ -135,7 +139,7 @@ func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error {
|
|||
}
|
||||
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||
wrappedCommand := state.Get("wrappedCommand").(common.CommandWrapper)
|
||||
|
||||
ui.Say("Unmounting the root device...")
|
||||
unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", s.mountPath))
|
||||
|
@ -143,7 +147,7 @@ func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error {
|
|||
return fmt.Errorf("Error creating unmount command: %s", err)
|
||||
}
|
||||
|
||||
cmd := ShellCommand(unmountCommand)
|
||||
cmd := common.ShellCommand(unmountCommand)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("Error unmounting root device: %s", err)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
package chroot
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/common/chroot"
|
||||
)
|
||||
|
||||
func TestMountDeviceCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepMountDevice)
|
||||
if _, ok := raw.(Cleanup); !ok {
|
||||
if _, ok := raw.(chroot.Cleanup); !ok {
|
||||
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package chroot
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/hashicorp/packer/builder"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
|
||||
// StepPrepareDevice finds an available device and sets it.
|
||||
type StepPrepareDevice struct {
|
||||
GeneratedData *builder.GeneratedData
|
||||
}
|
||||
|
||||
func (s *StepPrepareDevice) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
|
@ -40,6 +42,7 @@ func (s *StepPrepareDevice) Run(ctx context.Context, state multistep.StateBag) m
|
|||
|
||||
log.Printf("Device: %s", device)
|
||||
state.Put("device", device)
|
||||
s.GeneratedData.Put("Device", device)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ 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"
|
||||
)
|
||||
|
@ -14,8 +16,9 @@ import (
|
|||
// StepRegisterAMI creates the AMI.
|
||||
type StepRegisterAMI struct {
|
||||
RootVolumeSize int64
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMIENASupport confighelper.Trilean
|
||||
EnableAMISriovNetSupport bool
|
||||
AMISkipBuildRegion bool
|
||||
}
|
||||
|
||||
func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
|
@ -28,12 +31,29 @@ 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)
|
||||
registerOpts = buildBaseRegisterOpts(config, nil, s.RootVolumeSize, snapshotID, amiName)
|
||||
} else {
|
||||
image := state.Get("source_image").(*ec2.Image)
|
||||
registerOpts = buildBaseRegisterOpts(config, image, s.RootVolumeSize, snapshotID)
|
||||
registerOpts = buildBaseRegisterOpts(config, image, s.RootVolumeSize, snapshotID, amiName)
|
||||
}
|
||||
|
||||
if s.EnableAMISriovNetSupport {
|
||||
|
@ -41,7 +61,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 != nil && *s.EnableAMIENASupport {
|
||||
if s.EnableAMIENASupport.True() {
|
||||
// Set EnaSupport to true
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
registerOpts.EnaSupport = aws.Bool(true)
|
||||
|
@ -74,7 +94,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) *ec2.RegisterImageInput {
|
||||
func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSize int64, snapshotID string, amiName string) *ec2.RegisterImageInput {
|
||||
var (
|
||||
mappings []*ec2.BlockDeviceMapping
|
||||
rootDeviceName string
|
||||
|
@ -82,7 +102,7 @@ func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSiz
|
|||
|
||||
generatingNewBlockDeviceMappings := config.FromScratch || len(config.AMIMappings) > 0
|
||||
if generatingNewBlockDeviceMappings {
|
||||
mappings = config.AMIBlockDevices.BuildAMIDevices()
|
||||
mappings = config.AMIMappings.BuildEC2BlockDeviceMappings()
|
||||
rootDeviceName = config.RootDeviceName
|
||||
} else {
|
||||
// If config.FromScratch is false, source image must be set
|
||||
|
@ -116,7 +136,7 @@ func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSiz
|
|||
|
||||
if config.FromScratch {
|
||||
return &ec2.RegisterImageInput{
|
||||
Name: &config.AMIName,
|
||||
Name: &amiName,
|
||||
Architecture: aws.String(config.Architecture),
|
||||
RootDeviceName: aws.String(rootDeviceName),
|
||||
VirtualizationType: aws.String(config.AMIVirtType),
|
||||
|
@ -124,12 +144,12 @@ func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSiz
|
|||
}
|
||||
}
|
||||
|
||||
return buildRegisterOptsFromExistingImage(config, sourceImage, newMappings, rootDeviceName)
|
||||
return buildRegisterOptsFromExistingImage(config, sourceImage, newMappings, rootDeviceName, amiName)
|
||||
}
|
||||
|
||||
func buildRegisterOptsFromExistingImage(config *Config, image *ec2.Image, mappings []*ec2.BlockDeviceMapping, rootDeviceName string) *ec2.RegisterImageInput {
|
||||
func buildRegisterOptsFromExistingImage(config *Config, image *ec2.Image, mappings []*ec2.BlockDeviceMapping, rootDeviceName string, amiName string) *ec2.RegisterImageInput {
|
||||
registerOpts := &ec2.RegisterImageInput{
|
||||
Name: &config.AMIName,
|
||||
Name: &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)
|
||||
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
|
||||
|
||||
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)
|
||||
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
|
||||
|
||||
expected := config.AMIVirtType
|
||||
if *opts.VirtualizationType != expected {
|
||||
|
@ -92,16 +92,14 @@ func TestStepRegisterAmi_buildRegisterOptsFromScratch(t *testing.T) {
|
|||
config := Config{
|
||||
FromScratch: true,
|
||||
PackerConfig: common.PackerConfig{},
|
||||
AMIBlockDevices: amazon.AMIBlockDevices{
|
||||
AMIMappings: []amazon.BlockDevice{
|
||||
{
|
||||
DeviceName: rootDeviceName,
|
||||
},
|
||||
AMIMappings: []amazon.BlockDevice{
|
||||
{
|
||||
DeviceName: rootDeviceName,
|
||||
},
|
||||
},
|
||||
RootDeviceName: rootDeviceName,
|
||||
}
|
||||
registerOpts := buildBaseRegisterOpts(&config, nil, 10, snapshotID)
|
||||
registerOpts := buildBaseRegisterOpts(&config, nil, 10, snapshotID, config.AMIName)
|
||||
|
||||
if len(registerOpts.BlockDeviceMappings) != 1 {
|
||||
t.Fatal("Expected block device mapping of length 1")
|
||||
|
@ -140,7 +138,7 @@ func TestStepRegisterAmi_buildRegisterOptFromExistingImage(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotID)
|
||||
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotID, config.AMIName)
|
||||
|
||||
if len(registerOpts.BlockDeviceMappings) != 2 {
|
||||
t.Fatal("Expected block device mapping of length 2")
|
||||
|
@ -169,11 +167,9 @@ func TestStepRegisterAmi_buildRegisterOptFromExistingImageWithBlockDeviceMapping
|
|||
config := Config{
|
||||
FromScratch: false,
|
||||
PackerConfig: common.PackerConfig{},
|
||||
AMIBlockDevices: amazon.AMIBlockDevices{
|
||||
AMIMappings: []amazon.BlockDevice{
|
||||
{
|
||||
DeviceName: rootDeviceName,
|
||||
},
|
||||
AMIMappings: []amazon.BlockDevice{
|
||||
{
|
||||
DeviceName: rootDeviceName,
|
||||
},
|
||||
},
|
||||
RootDeviceName: rootDeviceName,
|
||||
|
@ -200,7 +196,7 @@ func TestStepRegisterAmi_buildRegisterOptFromExistingImageWithBlockDeviceMapping
|
|||
},
|
||||
},
|
||||
}
|
||||
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotId)
|
||||
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotId, config.AMIName)
|
||||
|
||||
if len(registerOpts.BlockDeviceMappings) != 1 {
|
||||
t.Fatal("Expected block device mapping of length 1")
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type VaultAWSEngineOptions
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
|
@ -8,7 +11,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
|
@ -19,9 +21,18 @@ import (
|
|||
)
|
||||
|
||||
type VaultAWSEngineOptions struct {
|
||||
Name string `mapstructure:"name"`
|
||||
RoleARN string `mapstructure:"role_arn"`
|
||||
TTL string `mapstructure:"ttl"`
|
||||
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"`
|
||||
EngineName string `mapstructure:"engine_name"`
|
||||
}
|
||||
|
||||
|
@ -32,19 +43,85 @@ func (v *VaultAWSEngineOptions) Empty() bool {
|
|||
|
||||
// AccessConfig is for common configuration related to AWS access
|
||||
type AccessConfig struct {
|
||||
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"`
|
||||
// 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"`
|
||||
|
||||
getEC2Connection func() ec2iface.EC2API
|
||||
}
|
||||
|
@ -103,15 +180,17 @@ func (c *AccessConfig) Session() (*session.Session, error) {
|
|||
c.session = sess
|
||||
|
||||
cp, err := c.session.Config.Credentials.Get()
|
||||
if err != nil {
|
||||
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
|
||||
return nil, fmt.Errorf("No valid credential sources found for AWS Builder. " +
|
||||
"Please see https://www.packer.io/docs/builders/amazon.html#specifying-amazon-credentials " +
|
||||
"for more information on providing credentials for the AWS Builder.")
|
||||
} else {
|
||||
return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)
|
||||
}
|
||||
|
||||
if isAWSErr(err, "NoCredentialProviders", "") {
|
||||
return nil, fmt.Errorf("No valid credential sources found for AWS Builder. " +
|
||||
"Please see https://www.packer.io/docs/builders/amazon.html#specifying-amazon-credentials " +
|
||||
"for more information on providing credentials for the AWS Builder.")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
|
||||
|
||||
if c.DecodeAuthZMessages {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// 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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatVaultAWSEngineOptions)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a VaultAWSEngineOptions.
|
||||
// This spec is used by HCL to read the fields of VaultAWSEngineOptions.
|
||||
// The decoded values from this spec will then be applied to a 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,3 +1,5 @@
|
|||
//go:generate struct-markdown
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
|
@ -5,31 +7,125 @@ 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 {
|
||||
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"`
|
||||
// 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"`
|
||||
}
|
||||
|
||||
func stringInSlice(s []string, searchstr string) bool {
|
||||
|
@ -62,7 +158,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 != nil && *c.AMIEncryptBootVolume {
|
||||
if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume.True() {
|
||||
errs = append(errs, fmt.Errorf("Cannot share AMI encrypted with default KMS key"))
|
||||
}
|
||||
if len(c.AMIRegionKMSKeyIDs) > 0 {
|
||||
|
@ -74,17 +170,23 @@ func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context
|
|||
}
|
||||
}
|
||||
|
||||
var kmsKeys []string
|
||||
kmsKeys := make([]string, 0)
|
||||
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, c.AMIKmsKeyId)
|
||||
if len(kmsKey) > 0 {
|
||||
kmsKeys = append(kmsKeys, kmsKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
|
@ -92,8 +194,9 @@ func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context
|
|||
}
|
||||
|
||||
if len(c.SnapshotUsers) > 0 {
|
||||
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.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.AMIRegionKMSKeyIDs) > 0 {
|
||||
for _, kmsKey := range c.AMIRegionKMSKeyIDs {
|
||||
|
|
|
@ -8,6 +8,7 @@ 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 {
|
||||
|
@ -68,7 +69,6 @@ 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 = &[]bool{true}[0]
|
||||
c.AMIEncryptBootVolume = config.TriTrue
|
||||
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 = &[]bool{true}[0]
|
||||
c.AMIEncryptBootVolume = config.TriTrue
|
||||
|
||||
accessConf := testAccessConfig()
|
||||
|
||||
|
@ -177,7 +177,7 @@ func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
|
|||
|
||||
func TestAMIConfigPrepare_ValidateKmsKey(t *testing.T) {
|
||||
c := testAMIConfig()
|
||||
c.AMIEncryptBootVolume = aws.Bool(true)
|
||||
c.AMIEncryptBootVolume = config.TriTrue
|
||||
|
||||
accessConf := testAccessConfig()
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@ type Artifact struct {
|
|||
// BuilderId is the unique ID for the builder that created this AMI
|
||||
BuilderIdValue string
|
||||
|
||||
// StateData should store data such as GeneratedData
|
||||
// to be shared with post-processors
|
||||
StateData map[string]interface{}
|
||||
|
||||
// EC2 connection for performing API stuff.
|
||||
Session *session.Session
|
||||
}
|
||||
|
@ -55,6 +59,10 @@ func (a *Artifact) String() string {
|
|||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
if _, ok := a.StateData[name]; ok {
|
||||
return a.StateData[name]
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "atlas.artifact.metadata":
|
||||
return a.stateAtlasMetadata()
|
||||
|
@ -85,15 +93,10 @@ func (a *Artifact) Destroy() error {
|
|||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
// Deregister ami
|
||||
input := &ec2.DeregisterImageInput{
|
||||
ImageId: &imageId,
|
||||
}
|
||||
if _, err := regionConn.DeregisterImage(input); err != nil {
|
||||
err = DestroyAMIs([]*string{&imageId}, regionConn)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
// TODO(mitchellh): Delete the snapshots associated with an AMI too
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
|
|
|
@ -62,3 +62,29 @@ west: bar
|
|||
t.Fatalf("bad: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactState(t *testing.T) {
|
||||
expectedData := "this is the data"
|
||||
artifact := &Artifact{
|
||||
StateData: map[string]interface{}{"state_data": expectedData},
|
||||
}
|
||||
|
||||
// Valid state
|
||||
result := artifact.State("state_data")
|
||||
if result != expectedData {
|
||||
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
|
||||
}
|
||||
|
||||
// Invalid state
|
||||
result = artifact.State("invalid_key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for invalid state data name")
|
||||
}
|
||||
|
||||
// Nil StateData should not fail and should return nil
|
||||
artifact = &Artifact{}
|
||||
result = artifact.State("key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for nil StateData")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type BlockDevice
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
|
@ -6,130 +9,153 @@ 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"
|
||||
)
|
||||
|
||||
// BlockDevice
|
||||
// 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
|
||||
// "[{
|
||||
// "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
|
||||
//
|
||||
type BlockDevice struct {
|
||||
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"`
|
||||
// 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"`
|
||||
}
|
||||
|
||||
type BlockDevices struct {
|
||||
AMIBlockDevices `mapstructure:",squash"`
|
||||
LaunchBlockDevices `mapstructure:",squash"`
|
||||
}
|
||||
type BlockDevices []BlockDevice
|
||||
|
||||
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 {
|
||||
func (bds BlockDevices) BuildEC2BlockDeviceMappings() []*ec2.BlockDeviceMapping {
|
||||
var blockDevices []*ec2.BlockDeviceMapping
|
||||
|
||||
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)
|
||||
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()
|
||||
|
||||
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 != nil && *b.Encrypted == false {
|
||||
if b.KmsKeyId != "" && b.Encrypted.False() {
|
||||
return fmt.Errorf("The device %v, must also have `encrypted: "+
|
||||
"true` when setting a kms_key_id.", b.DeviceName)
|
||||
}
|
||||
|
||||
return nil
|
||||
_, err := interpolate.RenderInterface(&b, ctx)
|
||||
return 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()))
|
||||
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 *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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
// 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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatBlockDevice)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a BlockDevice.
|
||||
// This spec is used by HCL to read the fields of BlockDevice.
|
||||
// The decoded values from this spec will then be applied to a 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,11 +1,12 @@
|
|||
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) {
|
||||
|
@ -71,7 +72,7 @@ func TestBlockDevice(t *testing.T) {
|
|||
VolumeType: "gp2",
|
||||
VolumeSize: 8,
|
||||
DeleteOnTermination: true,
|
||||
Encrypted: aws.Bool(true),
|
||||
Encrypted: config.TriTrue,
|
||||
},
|
||||
|
||||
Result: &ec2.BlockDeviceMapping{
|
||||
|
@ -90,7 +91,7 @@ func TestBlockDevice(t *testing.T) {
|
|||
VolumeType: "gp2",
|
||||
VolumeSize: 8,
|
||||
DeleteOnTermination: true,
|
||||
Encrypted: aws.Bool(true),
|
||||
Encrypted: config.TriTrue,
|
||||
KmsKeyId: "2Fa48a521f-3aff-4b34-a159-376ac5d37812",
|
||||
},
|
||||
|
||||
|
@ -145,26 +146,20 @@ func TestBlockDevice(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
amiBlockDevices := AMIBlockDevices{
|
||||
AMIMappings: []BlockDevice{*tc.Config},
|
||||
}
|
||||
var amiBlockDevices BlockDevices = []BlockDevice{*tc.Config}
|
||||
|
||||
launchBlockDevices := LaunchBlockDevices{
|
||||
LaunchMappings: []BlockDevice{*tc.Config},
|
||||
}
|
||||
var launchBlockDevices BlockDevices = []BlockDevice{*tc.Config}
|
||||
|
||||
expected := []*ec2.BlockDeviceMapping{tc.Result}
|
||||
|
||||
amiResults := amiBlockDevices.BuildAMIDevices()
|
||||
if !reflect.DeepEqual(expected, amiResults) {
|
||||
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
|
||||
expected, amiResults)
|
||||
amiResults := amiBlockDevices.BuildEC2BlockDeviceMappings()
|
||||
if diff := cmp.Diff(expected, amiResults); 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)
|
||||
launchResults := launchBlockDevices.BuildEC2BlockDeviceMappings()
|
||||
if diff := cmp.Diff(expected, launchResults); diff != "" {
|
||||
t.Fatalf("Bad block device: %s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,14 @@ 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: k,
|
||||
Values: []*string{v},
|
||||
Name: &a,
|
||||
Values: []*string{&b},
|
||||
})
|
||||
}
|
||||
return filters
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
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 groups != nil && len(groups) > 1 {
|
||||
if len(groups) > 1 {
|
||||
result, decodeErr := decoder.DecodeAuthorizationMessage(&sts.DecodeAuthorizationMessageInput{
|
||||
EncodedMessage: aws.String(groups[2]),
|
||||
})
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
)
|
||||
|
||||
// DestroyAMIs deregisters the AWS machine images in imageids from an active AWS account
|
||||
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 {
|
||||
|
||||
ctx := context.TODO()
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(err error) bool {
|
||||
return isAWSErr(err, "UnauthorizedOperation", "")
|
||||
},
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) error {
|
||||
_, err := ec2conn.DeregisterImage(&ec2.DeregisterImageInput{
|
||||
ImageId: i.ImageId,
|
||||
})
|
||||
return err
|
||||
})
|
||||
|
||||
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 = retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(err error) bool {
|
||||
return isAWSErr(err, "UnauthorizedOperation", "")
|
||||
},
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) error {
|
||||
_, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{
|
||||
SnapshotId: b.Ebs.SnapshotId,
|
||||
})
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error deleting existing snapshot: %s", err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Deleted snapshot: %s", *b.Ebs.SnapshotId)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns true if the error matches all these conditions:
|
||||
// * err is of type awserr.Error
|
||||
// * Error.Code() matches code
|
||||
// * Error.Message() contains message
|
||||
func isAWSErr(err error, code string, message string) bool {
|
||||
if err, ok := err.(awserr.Error); ok {
|
||||
return err.Code() == code && strings.Contains(err.Message(), message)
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -3,17 +3,20 @@ package common
|
|||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/builder"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
type BuildInfoTemplate struct {
|
||||
BuildRegion string
|
||||
SourceAMI string
|
||||
SourceAMIName string
|
||||
SourceAMITags map[string]string
|
||||
BuildRegion string
|
||||
SourceAMI string
|
||||
SourceAMIName string
|
||||
SourceAMIOwner string
|
||||
SourceAMIOwnerName string
|
||||
SourceAMITags map[string]string
|
||||
}
|
||||
|
||||
func extractBuildInfo(region string, state multistep.StateBag) *BuildInfoTemplate {
|
||||
func extractBuildInfo(region string, state multistep.StateBag, generatedData *builder.GeneratedData) *BuildInfoTemplate {
|
||||
rawSourceAMI, hasSourceAMI := state.GetOk("source_image")
|
||||
if !hasSourceAMI {
|
||||
return &BuildInfoTemplate{
|
||||
|
@ -27,10 +30,14 @@ func extractBuildInfo(region string, state multistep.StateBag) *BuildInfoTemplat
|
|||
sourceAMITags[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value)
|
||||
}
|
||||
|
||||
return &BuildInfoTemplate{
|
||||
BuildRegion: region,
|
||||
SourceAMI: aws.StringValue(sourceAMI.ImageId),
|
||||
SourceAMIName: aws.StringValue(sourceAMI.Name),
|
||||
SourceAMITags: sourceAMITags,
|
||||
buildInfoTemplate := &BuildInfoTemplate{
|
||||
BuildRegion: region,
|
||||
SourceAMI: aws.StringValue(sourceAMI.ImageId),
|
||||
SourceAMIName: aws.StringValue(sourceAMI.Name),
|
||||
SourceAMIOwner: aws.StringValue(sourceAMI.OwnerId),
|
||||
SourceAMIOwnerName: aws.StringValue(sourceAMI.ImageOwnerAlias),
|
||||
SourceAMITags: sourceAMITags,
|
||||
}
|
||||
generatedData.Put("SourceAMIName", buildInfoTemplate.SourceAMIName)
|
||||
return buildInfoTemplate
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer/builder"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
@ -11,8 +12,10 @@ import (
|
|||
|
||||
func testImage() *ec2.Image {
|
||||
return &ec2.Image{
|
||||
ImageId: aws.String("ami-abcd1234"),
|
||||
Name: aws.String("ami_test_name"),
|
||||
ImageId: aws.String("ami-abcd1234"),
|
||||
Name: aws.String("ami_test_name"),
|
||||
OwnerId: aws.String("ami_test_owner_id"),
|
||||
ImageOwnerAlias: aws.String("ami_test_owner_alias"),
|
||||
Tags: []*ec2.Tag{
|
||||
{
|
||||
Key: aws.String("key-1"),
|
||||
|
@ -31,9 +34,15 @@ func testState() multistep.StateBag {
|
|||
return state
|
||||
}
|
||||
|
||||
func testGeneratedData(state multistep.StateBag) builder.GeneratedData {
|
||||
generatedData := builder.GeneratedData{State: state}
|
||||
return generatedData
|
||||
}
|
||||
|
||||
func TestInterpolateBuildInfo_extractBuildInfo_noSourceImage(t *testing.T) {
|
||||
state := testState()
|
||||
buildInfo := extractBuildInfo("foo", state)
|
||||
generatedData := testGeneratedData(state)
|
||||
buildInfo := extractBuildInfo("foo", state, &generatedData)
|
||||
|
||||
expected := BuildInfoTemplate{
|
||||
BuildRegion: "foo",
|
||||
|
@ -46,12 +55,15 @@ func TestInterpolateBuildInfo_extractBuildInfo_noSourceImage(t *testing.T) {
|
|||
func TestInterpolateBuildInfo_extractBuildInfo_withSourceImage(t *testing.T) {
|
||||
state := testState()
|
||||
state.Put("source_image", testImage())
|
||||
buildInfo := extractBuildInfo("foo", state)
|
||||
generatedData := testGeneratedData(state)
|
||||
buildInfo := extractBuildInfo("foo", state, &generatedData)
|
||||
|
||||
expected := BuildInfoTemplate{
|
||||
BuildRegion: "foo",
|
||||
SourceAMI: "ami-abcd1234",
|
||||
SourceAMIName: "ami_test_name",
|
||||
BuildRegion: "foo",
|
||||
SourceAMI: "ami-abcd1234",
|
||||
SourceAMIName: "ami_test_name",
|
||||
SourceAMIOwner: "ami_test_owner_id",
|
||||
SourceAMIOwnerName: "ami_test_owner_alias",
|
||||
SourceAMITags: map[string]string{
|
||||
"key-1": "value-1",
|
||||
"key-2": "value-2",
|
||||
|
@ -61,3 +73,16 @@ func TestInterpolateBuildInfo_extractBuildInfo_withSourceImage(t *testing.T) {
|
|||
t.Fatalf("Unexpected BuildInfoTemplate: expected %#v got %#v\n", expected, *buildInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpolateBuildInfo_extractBuildInfo_GeneratedDataWithSourceImageName(t *testing.T) {
|
||||
state := testState()
|
||||
state.Put("source_image", testImage())
|
||||
generatedData := testGeneratedData(state)
|
||||
extractBuildInfo("foo", state, &generatedData)
|
||||
|
||||
generatedDataState := state.Get("generated_data").(map[string]interface{})
|
||||
|
||||
if generatedDataState["SourceAMIName"] != "ami_test_name" {
|
||||
t.Fatalf("Unexpected state SourceAMIName: expected %#v got %#v\n", "ami_test_name", generatedDataState["SourceAMIName"])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type AmiFilterOptions,SecurityGroupFilterOptions,SubnetFilterOptions,VpcFilterOptions,PolicyDocument,Statement
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
|
@ -16,11 +19,20 @@ 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 {
|
||||
i := owner
|
||||
res = append(res, &i)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (d *AmiFilterOptions) Empty() bool {
|
||||
return len(d.Owners) == 0 && len(d.Filters) == 0
|
||||
}
|
||||
|
@ -30,7 +42,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"`
|
||||
}
|
||||
|
@ -40,7 +52,18 @@ func (d *SubnetFilterOptions) Empty() bool {
|
|||
}
|
||||
|
||||
type VpcFilterOptions struct {
|
||||
Filters map[*string]*string
|
||||
Filters map[string]string
|
||||
}
|
||||
|
||||
type Statement struct {
|
||||
Effect string
|
||||
Action []string
|
||||
Resource string
|
||||
}
|
||||
|
||||
type PolicyDocument struct {
|
||||
Version string
|
||||
Statement []Statement
|
||||
}
|
||||
|
||||
func (d *VpcFilterOptions) Empty() bool {
|
||||
|
@ -48,7 +71,7 @@ func (d *VpcFilterOptions) Empty() bool {
|
|||
}
|
||||
|
||||
type SecurityGroupFilterOptions struct {
|
||||
Filters map[*string]*string
|
||||
Filters map[string]string
|
||||
}
|
||||
|
||||
func (d *SecurityGroupFilterOptions) Empty() bool {
|
||||
|
@ -58,38 +81,316 @@ 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 {
|
||||
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"`
|
||||
// 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"`
|
||||
// Whether or not to check if the IAM instance profile exists. Defaults to false
|
||||
SkipProfileValidation bool `mapstructure:"skip_profile_validation" 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"`
|
||||
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHInterface string `mapstructure:"ssh_interface"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
// One of `public_ip`, `private_ip`, `public_dns`, or `private_dns`. If
|
||||
// set, either the public IP address, private IP address, public DNS name
|
||||
// or private DNS name will be used as the host for SSH. The default behaviour
|
||||
// if inside a VPC is to use the public IP address if available, otherwise
|
||||
// the private IP address will be used. If not in a VPC the public DNS name
|
||||
// will be used. Also works for WinRM.
|
||||
//
|
||||
// Where Packer is configured for an outbound proxy but WinRM traffic
|
||||
// should be direct, `ssh_interface` must be set to `private_dns` and
|
||||
// `<region>.compute.internal` included in the `NO_PROXY` environment
|
||||
// variable.
|
||||
SSHInterface string `mapstructure:"ssh_interface"`
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
// 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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatAmiFilterOptions)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a AmiFilterOptions.
|
||||
// This spec is used by HCL to read the fields of AmiFilterOptions.
|
||||
// The decoded values from this spec will then be applied to a 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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatPolicyDocument)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a PolicyDocument.
|
||||
// This spec is used by HCL to read the fields of PolicyDocument.
|
||||
// The decoded values from this spec will then be applied to a FlatPolicyDocument.
|
||||
func (*FlatPolicyDocument) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
|
||||
"statement": &hcldec.BlockListSpec{TypeName: "statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())},
|
||||
}
|
||||
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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatSecurityGroupFilterOptions)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a SecurityGroupFilterOptions.
|
||||
// This spec is used by HCL to read the fields of SecurityGroupFilterOptions.
|
||||
// The decoded values from this spec will then be applied to a 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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatStatement)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Statement.
|
||||
// This spec is used by HCL to read the fields of Statement.
|
||||
// The decoded values from this spec will then be applied to a FlatStatement.
|
||||
func (*FlatStatement) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"effect": &hcldec.AttrSpec{Name: "effect", Type: cty.String, Required: false},
|
||||
"action": &hcldec.AttrSpec{Name: "action", Type: cty.List(cty.String), Required: false},
|
||||
"resource": &hcldec.AttrSpec{Name: "resource", Type: cty.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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatSubnetFilterOptions)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a SubnetFilterOptions.
|
||||
// This spec is used by HCL to read the fields of SubnetFilterOptions.
|
||||
// The decoded values from this spec will then be applied to a 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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatVpcFilterOptions)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a VpcFilterOptions.
|
||||
// This spec is used by HCL to read the fields of VpcFilterOptions.
|
||||
// The decoded values from this spec will then be applied to a 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,7 +24,9 @@ func testConfig() *RunConfig {
|
|||
InstanceType: "m1.small",
|
||||
|
||||
Comm: communicator.Config{
|
||||
SSHUsername: "foo",
|
||||
SSH: communicator.SSH{
|
||||
SSHUsername: "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +73,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)")
|
||||
}
|
||||
|
@ -82,7 +84,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)
|
||||
|
|
|
@ -3,6 +3,7 @@ package common
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
|
@ -20,12 +21,16 @@ 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) func(multistep.StateBag) (string, error) {
|
||||
func SSHHost(e ec2Describer, sshInterface string, host string) func(multistep.StateBag) (string, error) {
|
||||
return func(state multistep.StateBag) (string, error) {
|
||||
if host != "" {
|
||||
log.Printf("Using 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,10 +9,11 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
privateIP = "10.0.0.1"
|
||||
publicIP = "192.168.1.1"
|
||||
privateDNS = "private.dns.test"
|
||||
publicDNS = "public.dns.test"
|
||||
privateIP = "10.0.0.1"
|
||||
publicIP = "192.168.1.1"
|
||||
privateDNS = "private.dns.test"
|
||||
publicDNS = "public.dns.test"
|
||||
sshHostTemplate = "custom.host.value"
|
||||
)
|
||||
|
||||
func TestSSHHost(t *testing.T) {
|
||||
|
@ -25,39 +26,46 @@ func TestSSHHost(t *testing.T) {
|
|||
vpcId string
|
||||
sshInterface string
|
||||
|
||||
ok bool
|
||||
wantHost string
|
||||
ok bool
|
||||
wantHost string
|
||||
sshHostOverride 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, 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},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
testSSHHost(t, c.allowTries, c.vpcId, c.sshInterface, c.ok, c.wantHost)
|
||||
testSSHHost(t, c.allowTries, c.vpcId, c.sshInterface, c.ok, c.wantHost,
|
||||
c.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)
|
||||
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)
|
||||
|
||||
e := &fakeEC2Describer{
|
||||
allowTries: allowTries,
|
||||
|
@ -68,7 +76,7 @@ func testSSHHost(t *testing.T, allowTries int, vpcId string, sshInterface string
|
|||
publicDNS: publicDNS,
|
||||
}
|
||||
|
||||
f := SSHHost(e, sshInterface)
|
||||
f := SSHHost(e, sshInterface, sshHostOverride)
|
||||
st := &multistep.BasicStateBag{}
|
||||
st.Put("instance", &ec2.Instance{
|
||||
InstanceId: aws.String("instance-id"),
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
|
@ -52,6 +53,29 @@ func WaitUntilAMIAvailable(ctx aws.Context, conn ec2iface.EC2API, imageId string
|
|||
ctx,
|
||||
&imageInput,
|
||||
waitOpts...)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), request.WaiterResourceNotReadyErrorCode) {
|
||||
err = fmt.Errorf("Failed with ResourceNotReady error, which can "+
|
||||
"have a variety of causes. For help troubleshooting, check "+
|
||||
"our docs: "+
|
||||
"https://www.packer.io/docs/builders/amazon.html#resourcenotready-error\n"+
|
||||
"original error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func WaitUntilInstanceRunning(ctx aws.Context, conn *ec2.EC2, instanceId string) error {
|
||||
|
||||
instanceInput := ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{&instanceId},
|
||||
}
|
||||
|
||||
err := conn.WaitUntilInstanceRunningWithContext(
|
||||
ctx,
|
||||
&instanceInput,
|
||||
getWaiterOptions()...)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ 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"
|
||||
)
|
||||
|
@ -17,7 +18,7 @@ type StepAMIRegionCopy struct {
|
|||
Regions []string
|
||||
AMIKmsKeyId string
|
||||
RegionKeyIds map[string]string
|
||||
EncryptBootVolume *bool // nil means preserve
|
||||
EncryptBootVolume config.Trilean // nil means preserve
|
||||
Name string
|
||||
OriginalRegion string
|
||||
|
||||
|
@ -53,7 +54,7 @@ func (s *StepAMIRegionCopy) DeduplicateRegions(intermediary bool) {
|
|||
}
|
||||
|
||||
// Now print all those keys into the region slice again
|
||||
for k, _ := range RegionMap {
|
||||
for k := range RegionMap {
|
||||
RegionSlice = append(RegionSlice, k)
|
||||
}
|
||||
|
||||
|
@ -74,7 +75,7 @@ func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) m
|
|||
s.toDelete = ami
|
||||
}
|
||||
|
||||
if s.EncryptBootVolume != nil && *s.EncryptBootVolume {
|
||||
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.
|
||||
|
@ -82,9 +83,16 @@ func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) m
|
|||
s.RegionKeyIds = make(map[string]string)
|
||||
}
|
||||
|
||||
// Make sure the kms_key_id for the original region is in the map
|
||||
if _, ok := s.RegionKeyIds[s.OriginalRegion]; !ok {
|
||||
s.RegionKeyIds[s.OriginalRegion] = s.AMIKmsKeyId
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +110,7 @@ func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) m
|
|||
var regKeyID string
|
||||
ui.Message(fmt.Sprintf("Copying to: %s", region))
|
||||
|
||||
if s.EncryptBootVolume != nil && *s.EncryptBootVolume {
|
||||
if s.EncryptBootVolume.True() {
|
||||
// Encrypt is true, explicitly
|
||||
regKeyID = s.RegionKeyIds[region]
|
||||
} else {
|
||||
|
@ -112,7 +120,9 @@ func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) m
|
|||
|
||||
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)
|
||||
id, snapshotIds, err := s.amiRegionCopy(ctx, state, s.AccessConfig,
|
||||
s.Name, ami, region, s.OriginalRegion, regKeyID,
|
||||
s.EncryptBootVolume.ToBoolPointer())
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
amis[region] = id
|
||||
|
@ -149,49 +159,12 @@ 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")
|
||||
|
||||
resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{&s.toDelete},
|
||||
})
|
||||
|
||||
err := DestroyAMIs([]*string{&s.toDelete}, ec2conn)
|
||||
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) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"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"
|
||||
)
|
||||
|
@ -109,7 +110,7 @@ func TestStepAMIRegionCopy_duplicates(t *testing.T) {
|
|||
AMIKmsKeyId: "12345",
|
||||
// Original region key in regionkeyids is different than in amikmskeyid
|
||||
RegionKeyIds: map[string]string{"us-east-1": "12345"},
|
||||
EncryptBootVolume: aws.Bool(true),
|
||||
EncryptBootVolume: config.TriTrue,
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
|
@ -153,7 +154,7 @@ func TestStepAMIRegionCopy_duplicates(t *testing.T) {
|
|||
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||
AccessConfig: testAccessConfig(),
|
||||
Regions: []string{"us-east-1"},
|
||||
EncryptBootVolume: aws.Bool(false),
|
||||
EncryptBootVolume: config.TriFalse,
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
|
@ -179,7 +180,7 @@ func TestStepAMIRegionCopy_duplicates(t *testing.T) {
|
|||
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: aws.Bool(true),
|
||||
EncryptBootVolume: config.TriTrue,
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
|
@ -226,7 +227,7 @@ func TestStepAmiRegionCopy_nil_encryption(t *testing.T) {
|
|||
Regions: make([]string, 0),
|
||||
AMIKmsKeyId: "",
|
||||
RegionKeyIds: make(map[string]string),
|
||||
EncryptBootVolume: nil,
|
||||
EncryptBootVolume: config.TriUnset,
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
|
@ -252,7 +253,7 @@ func TestStepAmiRegionCopy_true_encryption(t *testing.T) {
|
|||
Regions: make([]string, 0),
|
||||
AMIKmsKeyId: "",
|
||||
RegionKeyIds: make(map[string]string),
|
||||
EncryptBootVolume: aws.Bool(true),
|
||||
EncryptBootVolume: config.TriTrue,
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
|
@ -278,7 +279,7 @@ func TestStepAmiRegionCopy_nil_intermediary(t *testing.T) {
|
|||
Regions: make([]string, 0),
|
||||
AMIKmsKeyId: "",
|
||||
RegionKeyIds: make(map[string]string),
|
||||
EncryptBootVolume: aws.Bool(false),
|
||||
EncryptBootVolume: config.TriFalse,
|
||||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
}
|
||||
|
@ -360,7 +361,7 @@ func TestStepAmiRegionCopy_AMISkipBuildRegion(t *testing.T) {
|
|||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
AMISkipBuildRegion: false,
|
||||
EncryptBootVolume: aws.Bool(true),
|
||||
EncryptBootVolume: config.TriTrue,
|
||||
}
|
||||
// mock out the region connection code
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
|
@ -386,7 +387,7 @@ func TestStepAmiRegionCopy_AMISkipBuildRegion(t *testing.T) {
|
|||
Name: "fake-ami-name",
|
||||
OriginalRegion: "us-east-1",
|
||||
AMISkipBuildRegion: true,
|
||||
EncryptBootVolume: aws.Bool(true),
|
||||
EncryptBootVolume: config.TriTrue,
|
||||
}
|
||||
// mock out the region connection code
|
||||
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||
|
|
|
@ -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 {
|
||||
BlockDevices BlockDevices
|
||||
LaunchMappings BlockDevices
|
||||
}
|
||||
|
||||
func (s *StepCleanupVolumes) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
|
@ -60,7 +60,7 @@ func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
|||
})
|
||||
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Error describing volumes: %s", err))
|
||||
ui.Error(fmt.Sprintf("Error describing volumes: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -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.BlockDevices.LaunchMappings {
|
||||
for _, b := range s.LaunchMappings {
|
||||
for volKey, volName := range volList {
|
||||
if volName == b.DeviceName {
|
||||
delete(volList, volKey)
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
|
@ -91,17 +90,12 @@ func (s *StepCreateTags) Run(ctx context.Context, state multistep.StateBag) mult
|
|||
snapshotTags.Report(ui)
|
||||
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(error) bool {
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
switch awsErr.Code() {
|
||||
case "InvalidAMIID.NotFound", "InvalidSnapshot.NotFound":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
err = retry.Config{Tries: 11, ShouldRetry: func(error) bool {
|
||||
if isAWSErr(err, "InvalidAMIID.NotFound", "") || isAWSErr(err, "InvalidSnapshot.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 {
|
||||
// Tag images and snapshots
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
@ -45,14 +45,13 @@ 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(state, cancel)
|
||||
password, err = s.waitForPassword(ctx, state)
|
||||
waitDone <- true
|
||||
}()
|
||||
|
||||
|
@ -76,16 +75,12 @@ WaitLoop:
|
|||
err := fmt.Errorf("Timeout waiting for password.")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
close(cancel)
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,35 +90,46 @@ WaitLoop:
|
|||
"Password (since debug is enabled): %s", s.Comm.WinRMPassword))
|
||||
}
|
||||
// store so that we can access this later during provisioning
|
||||
|
||||
commonhelper.SetSharedState("winrm_password", s.Comm.WinRMPassword, s.BuildName)
|
||||
state.Put("winrm_password", s.Comm.WinRMPassword)
|
||||
packer.LogSecretFilter.Set(s.Comm.WinRMPassword)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepGetPassword) Cleanup(multistep.StateBag) {
|
||||
commonhelper.RemoveSharedStateFile("winrm_password", s.BuildName)
|
||||
}
|
||||
func (s *StepGetPassword) Cleanup(multistep.StateBag) {}
|
||||
|
||||
func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) {
|
||||
func (s *StepGetPassword) waitForPassword(ctx context.Context, state multistep.StateBag) (string, error) {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
privateKey := s.Comm.SSHPrivateKey
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-cancel:
|
||||
case <-ctx.Done():
|
||||
log.Println("[INFO] Retrieve password wait cancelled. Exiting loop.")
|
||||
return "", errors.New("Retrieve password wait cancelled")
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
|
||||
resp, err := ec2conn.GetPasswordData(&ec2.GetPasswordDataInput{
|
||||
InstanceId: instance.InstanceId,
|
||||
// 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
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error retrieving auto-generated instance password: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"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
|
||||
SkipProfileValidation bool
|
||||
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 {
|
||||
if !s.SkipProfileValidation {
|
||||
_, 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))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,10 +3,10 @@ package common
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/builder"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
|
@ -20,6 +20,8 @@ type StepModifyAMIAttributes struct {
|
|||
ProductCodes []string
|
||||
Description string
|
||||
Ctx interpolate.Context
|
||||
|
||||
GeneratedData *builder.GeneratedData
|
||||
}
|
||||
|
||||
func (s *StepModifyAMIAttributes) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
|
@ -43,7 +45,7 @@ func (s *StepModifyAMIAttributes) Run(ctx context.Context, state multistep.State
|
|||
}
|
||||
|
||||
var err error
|
||||
s.Ctx.Data = extractBuildInfo(*ec2conn.Config.Region, state)
|
||||
s.Ctx.Data = extractBuildInfo(*ec2conn.Config.Region, state, s.GeneratedData)
|
||||
s.Description, err = interpolate.Render(s.Description, &s.Ctx)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error interpolating AMI description: %s", err)
|
||||
|
|
|
@ -5,22 +5,29 @@ 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 *bool
|
||||
Skip bool
|
||||
EnableAMIENASupport confighelper.Trilean
|
||||
EnableAMISriovNetSupport bool
|
||||
}
|
||||
|
||||
func (s *StepModifyEBSBackedInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ec2conn := state.Get("ec2").(ec2iface.EC2API)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Skip when it is a spot instance
|
||||
if s.Skip {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
|
||||
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
|
||||
if s.EnableAMISriovNetSupport {
|
||||
|
@ -40,9 +47,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 != nil {
|
||||
if s.EnableAMIENASupport != confighelper.TriUnset {
|
||||
var prefix string
|
||||
if *s.EnableAMIENASupport {
|
||||
if s.EnableAMIENASupport.True() {
|
||||
prefix = "En"
|
||||
} else {
|
||||
prefix = "Dis"
|
||||
|
@ -50,7 +57,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: aws.Bool(*s.EnableAMIENASupport)},
|
||||
EnaSupport: &ec2.AttributeBooleanValue{Value: s.EnableAMIENASupport.ToBoolPointer()},
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error %sabling Enhanced Networking (ENA) on %s: %s", strings.ToLower(prefix), *instance.InstanceId, err)
|
||||
|
|
|
@ -7,7 +7,6 @@ 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"
|
||||
|
@ -53,7 +52,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[aws.String("state")] = aws.String("available")
|
||||
s.VpcFilter.Filters["state"] = "available"
|
||||
|
||||
log.Printf("Using VPC Filters %v", params)
|
||||
|
||||
|
@ -79,13 +78,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[aws.String("state")] = aws.String("available")
|
||||
s.SubnetFilter.Filters["state"] = "available"
|
||||
|
||||
if s.VpcId != "" {
|
||||
s.SubnetFilter.Filters[aws.String("vpc-id")] = &s.VpcId
|
||||
s.SubnetFilter.Filters["vpc-id"] = s.VpcId
|
||||
}
|
||||
if s.AvailabilityZone != "" {
|
||||
s.SubnetFilter.Filters[aws.String("availability-zone")] = &s.AvailabilityZone
|
||||
s.SubnetFilter.Filters["availabilityZone"] = s.AvailabilityZone
|
||||
}
|
||||
params.Filters = buildEc2Filters(s.SubnetFilter.Filters)
|
||||
log.Printf("Using Subnet Filters %v", params)
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"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/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
@ -21,6 +21,9 @@ type StepPreValidate struct {
|
|||
DestAmiName string
|
||||
ForceDeregister bool
|
||||
AMISkipBuildRegion bool
|
||||
VpcId string
|
||||
SubnetId string
|
||||
HasSubnetFilter bool
|
||||
}
|
||||
|
||||
func (s *StepPreValidate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
|
@ -36,7 +39,7 @@ func (s *StepPreValidate) Run(ctx context.Context, state multistep.StateBag) mul
|
|||
err := retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(err error) bool {
|
||||
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "AuthFailure" {
|
||||
if isAWSErr(err, "AuthFailure", "") {
|
||||
log.Printf("Waiting for Vault-generated AWS credentials" +
|
||||
" to pass authentication... trying again.")
|
||||
return true
|
||||
|
@ -77,6 +80,7 @@ func (s *StepPreValidate) Run(ctx context.Context, state multistep.StateBag) mul
|
|||
ui.Say("Force Deregister flag found, skipping prevalidating AMI Name")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.AMISkipBuildRegion {
|
||||
ui.Say("skip_build_region was set; not prevalidating AMI name")
|
||||
return multistep.ActionContinue
|
||||
|
@ -84,15 +88,24 @@ func (s *StepPreValidate) Run(ctx context.Context, state multistep.StateBag) mul
|
|||
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
|
||||
// Validate VPC settings for non-default VPCs
|
||||
ui.Say("Prevalidating any provided VPC information")
|
||||
if err := s.checkVpc(ec2conn); err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Prevalidating AMI Name: %s", s.DestAmiName))
|
||||
resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
req, resp := ec2conn.DescribeImagesRequest(&ec2.DescribeImagesInput{
|
||||
Filters: []*ec2.Filter{{
|
||||
Name: aws.String("name"),
|
||||
Values: []*string{aws.String(s.DestAmiName)},
|
||||
}}})
|
||||
req.RetryCount = 11
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying AMI: %s", err)
|
||||
if err := req.Send(); err != nil {
|
||||
err = fmt.Errorf("Error querying AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
|
@ -108,4 +121,27 @@ func (s *StepPreValidate) Run(ctx context.Context, state multistep.StateBag) mul
|
|||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepPreValidate) checkVpc(conn ec2iface.EC2API) error {
|
||||
if s.VpcId == "" || (s.VpcId != "" && (s.SubnetId != "" || s.HasSubnetFilter)) {
|
||||
// Skip validation if:
|
||||
// * The user has not provided a VpcId.
|
||||
// * Both VpcId and SubnetId are provided; AWS API will error if something is wrong.
|
||||
// * Both VpcId and SubnetFilter are provided
|
||||
return nil
|
||||
}
|
||||
|
||||
res, err := conn.DescribeVpcs(&ec2.DescribeVpcsInput{VpcIds: []*string{aws.String(s.VpcId)}})
|
||||
if isAWSErr(err, "InvalidVpcID.NotFound", "") || err != nil {
|
||||
return fmt.Errorf("Error retrieving VPC information for vpc_id %s: %s", s.VpcId, err)
|
||||
}
|
||||
|
||||
if res != nil && len(res.Vpcs) == 1 && res.Vpcs[0] != nil {
|
||||
if isDefault := aws.BoolValue(res.Vpcs[0].IsDefault); !isDefault {
|
||||
return fmt.Errorf("Error: subnet_id or subnet_filter must be provided for non-default VPCs (%s)", s.VpcId)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cleanup ...
|
||||
func (s *StepPreValidate) Cleanup(multistep.StateBag) {}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
)
|
||||
|
||||
//DescribeVpcs mocks an ec2.DescribeVpcsOutput for a given input
|
||||
func (m *mockEC2Conn) DescribeVpcs(input *ec2.DescribeVpcsInput) (*ec2.DescribeVpcsOutput, error) {
|
||||
|
||||
if input == nil || aws.StringValue(input.VpcIds[0]) == "" {
|
||||
return nil, fmt.Errorf("oops looks like we need more input")
|
||||
}
|
||||
|
||||
var isDefault bool
|
||||
vpcID := aws.StringValue(input.VpcIds[0])
|
||||
|
||||
//only one default VPC per region
|
||||
if strings.Contains("vpc-default-id", vpcID) {
|
||||
isDefault = true
|
||||
}
|
||||
|
||||
output := &ec2.DescribeVpcsOutput{
|
||||
Vpcs: []*ec2.Vpc{
|
||||
{IsDefault: aws.Bool(isDefault),
|
||||
VpcId: aws.String(vpcID),
|
||||
},
|
||||
},
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func TestStepPreValidate_checkVpc(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
step StepPreValidate
|
||||
errorExpected bool
|
||||
}{
|
||||
{"DefaultVpc", StepPreValidate{VpcId: "vpc-default-id"}, false},
|
||||
{"NonDefaultVpcNoSubnet", StepPreValidate{VpcId: "vpc-1234567890"}, true},
|
||||
{"NonDefaultVpcWithSubnet", StepPreValidate{VpcId: "vpc-1234567890", SubnetId: "subnet-1234567890"}, false},
|
||||
{"SubnetWithNoVpc", StepPreValidate{SubnetId: "subnet-1234567890"}, false},
|
||||
{"NoVpcInformation", StepPreValidate{}, false},
|
||||
{"NonDefaultVpcWithSubnetFilter", StepPreValidate{VpcId: "vpc-1234567890", HasSubnetFilter: true}, false},
|
||||
}
|
||||
|
||||
mockConn, err := getMockConn(nil, "")
|
||||
if err != nil {
|
||||
t.Fatal("unable to get a mock connection")
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.step.checkVpc(mockConn)
|
||||
|
||||
if tc.errorExpected && err == nil {
|
||||
t.Errorf("expected a validation error for %q but got %q", tc.name, err)
|
||||
}
|
||||
|
||||
if !tc.errorExpected && err != nil {
|
||||
t.Errorf("expected a validation to pass for %q but got %q", tc.name, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -9,7 +9,6 @@ import (
|
|||
"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/retry"
|
||||
|
@ -21,14 +20,13 @@ import (
|
|||
|
||||
type StepRunSourceInstance struct {
|
||||
AssociatePublicIpAddress bool
|
||||
BlockDevices BlockDevices
|
||||
LaunchMappings EC2BlockDeviceMappingsBuilder
|
||||
Comm *communicator.Config
|
||||
Ctx interpolate.Context
|
||||
Debug bool
|
||||
EbsOptimized bool
|
||||
EnableT2Unlimited bool
|
||||
ExpectedRootDevice string
|
||||
IamInstanceProfile string
|
||||
InstanceInitiatedShutdownBehavior string
|
||||
InstanceType string
|
||||
IsRestricted bool
|
||||
|
@ -37,6 +35,7 @@ type StepRunSourceInstance struct {
|
|||
UserData string
|
||||
UserDataFile string
|
||||
VolumeTags TagMap
|
||||
NoEphemeral bool
|
||||
|
||||
instanceId string
|
||||
}
|
||||
|
@ -45,6 +44,8 @@ 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
|
||||
|
@ -110,12 +111,31 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
|||
UserData: &userData,
|
||||
MaxCount: aws.Int64(1),
|
||||
MinCount: aws.Int64(1),
|
||||
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
|
||||
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
|
||||
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: iamInstanceProfile},
|
||||
BlockDeviceMappings: s.LaunchMappings.BuildEC2BlockDeviceMappings(),
|
||||
Placement: &ec2.Placement{AvailabilityZone: &az},
|
||||
EbsOptimized: &s.EbsOptimized,
|
||||
}
|
||||
|
||||
if s.NoEphemeral {
|
||||
// This is only relevant for windows guests. Ephemeral drives by
|
||||
// default are assigned to drive names xvdca-xvdcz.
|
||||
// When vms are launched from the AWS console, they're automatically
|
||||
// removed from the block devices if the user hasn't said to use them,
|
||||
// but the SDK does not perform this cleanup. The following code just
|
||||
// manually removes the ephemeral drives from the mapping so that they
|
||||
// don't clutter up console views and cause confusion.
|
||||
log.Printf("no_ephemeral was set, so creating drives xvdca-xvdcz as empty mappings")
|
||||
DefaultEphemeralDeviceLetters := "abcdefghijklmnopqrstuvwxyz"
|
||||
for _, letter := range DefaultEphemeralDeviceLetters {
|
||||
bd := &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String("xvdc" + string(letter)),
|
||||
NoDevice: aws.String(""),
|
||||
}
|
||||
runOpts.BlockDeviceMappings = append(runOpts.BlockDeviceMappings, bd)
|
||||
}
|
||||
}
|
||||
|
||||
if s.EnableT2Unlimited {
|
||||
creditOption := "unlimited"
|
||||
runOpts.CreditSpecification = &ec2.CreditSpecificationRequest{CpuCredits: &creditOption}
|
||||
|
@ -174,7 +194,28 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
|||
runOpts.InstanceInitiatedShutdownBehavior = &s.InstanceInitiatedShutdownBehavior
|
||||
}
|
||||
|
||||
runResp, err := ec2conn.RunInstances(runOpts)
|
||||
var runResp *ec2.Reservation
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(err error) bool {
|
||||
if isAWSErr(err, "InvalidParameterValue", "iamInstanceProfile") {
|
||||
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 {
|
||||
runResp, err = ec2conn.RunInstances(runOpts)
|
||||
return err
|
||||
})
|
||||
|
||||
if isAWSErr(err, "VPCIdNotSpecified", "No default VPC for this user") && subnetId == "" {
|
||||
err := fmt.Errorf("Error launching source instance: a valid Subnet Id was not specified")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error launching source instance: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -192,21 +233,37 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
|||
describeInstance := &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{aws.String(instanceId)},
|
||||
}
|
||||
if err := ec2conn.WaitUntilInstanceRunningWithContext(ctx, describeInstance); err != nil {
|
||||
|
||||
if err := WaitUntilInstanceRunning(ctx, ec2conn, instanceId); err != nil {
|
||||
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
r, err := ec2conn.DescribeInstances(describeInstance)
|
||||
// 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.
|
||||
|
||||
var r *ec2.DescribeInstancesOutput
|
||||
err = retry.Config{Tries: 11, ShouldRetry: func(err error) bool {
|
||||
if isAWSErr(err, "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 {
|
||||
|
@ -224,6 +281,9 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
|||
}
|
||||
|
||||
state.Put("instance", instance)
|
||||
// instance_id is the generic term used so that users can have access to the
|
||||
// instance id inside of the provisioners, used in step_provision.
|
||||
state.Put("instance_id", instance.InstanceId)
|
||||
|
||||
// If we're in a region that doesn't support tagging on instance creation,
|
||||
// do that now.
|
||||
|
@ -231,17 +291,12 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
|||
if s.IsRestricted {
|
||||
ec2Tags.Report(ui)
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(error) bool {
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
switch awsErr.Code() {
|
||||
case "InvalidInstanceID.NotFound":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
err = retry.Config{Tries: 11, ShouldRetry: func(error) bool {
|
||||
if isAWSErr(err, "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 {
|
||||
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"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/hashicorp/packer/common/retry"
|
||||
|
@ -19,15 +18,18 @@ import (
|
|||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type EC2BlockDeviceMappingsBuilder interface {
|
||||
BuildEC2BlockDeviceMappings() []*ec2.BlockDeviceMapping
|
||||
}
|
||||
|
||||
type StepRunSpotInstance struct {
|
||||
AssociatePublicIpAddress bool
|
||||
BlockDevices BlockDevices
|
||||
LaunchMappings EC2BlockDeviceMappingsBuilder
|
||||
BlockDurationMinutes int64
|
||||
Debug bool
|
||||
Comm *communicator.Config
|
||||
EbsOptimized bool
|
||||
ExpectedRootDevice string
|
||||
IamInstanceProfile string
|
||||
InstanceInitiatedShutdownBehavior string
|
||||
InstanceType string
|
||||
SourceAMI string
|
||||
|
@ -39,12 +41,32 @@ type StepRunSpotInstance struct {
|
|||
UserData string
|
||||
UserDataFile string
|
||||
Ctx interpolate.Context
|
||||
NoEphemeral bool
|
||||
|
||||
instanceId string
|
||||
}
|
||||
|
||||
func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,
|
||||
state multistep.StateBag, marketOptions *ec2.LaunchTemplateInstanceMarketOptionsRequest) *ec2.RequestLaunchTemplateData {
|
||||
blockDeviceMappings := s.LaunchMappings.BuildEC2BlockDeviceMappings()
|
||||
if s.NoEphemeral {
|
||||
// This is only relevant for windows guests. Ephemeral drives by
|
||||
// default are assigned to drive names xvdca-xvdcz.
|
||||
// When vms are launched from the AWS console, they're automatically
|
||||
// removed from the block devices if the user hasn't said to use them,
|
||||
// but the SDK does not perform this cleanup. The following code just
|
||||
// manually removes the ephemeral drives from the mapping so that they
|
||||
// don't clutter up console views and cause confusion.
|
||||
log.Printf("no_ephemeral was set, so creating drives xvdca-xvdcz as empty mappings")
|
||||
DefaultEphemeralDeviceLetters := "abcdefghijklmnopqrstuvwxyz"
|
||||
for _, letter := range DefaultEphemeralDeviceLetters {
|
||||
bd := &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String("xvdc" + string(letter)),
|
||||
NoDevice: aws.String(""),
|
||||
}
|
||||
blockDeviceMappings = append(blockDeviceMappings, bd)
|
||||
}
|
||||
}
|
||||
// Convert the BlockDeviceMapping into a
|
||||
// LaunchTemplateBlockDeviceMappingRequest. These structs are identical,
|
||||
// except for the EBS field -- on one, that field contains a
|
||||
|
@ -53,7 +75,6 @@ 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.BlockDevices.BuildLaunchDevices()
|
||||
var launchMappingRequests []*ec2.LaunchTemplateBlockDeviceMappingRequest
|
||||
for _, mapping := range blockDeviceMappings {
|
||||
launchRequest := &ec2.LaunchTemplateBlockDeviceMappingRequest{
|
||||
|
@ -65,12 +86,14 @@ 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: &s.IamInstanceProfile},
|
||||
IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{Name: iamInstanceProfile},
|
||||
ImageId: &s.SourceAMI,
|
||||
InstanceMarketOptions: marketOptions,
|
||||
Placement: &ec2.LaunchTemplatePlacementRequest{
|
||||
|
@ -260,24 +283,58 @@ 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 fleet request (%s): %s", *createOutput.FleetId, err)
|
||||
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.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)
|
||||
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
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
|
@ -289,30 +346,8 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
|||
|
||||
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.
|
||||
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(error) bool { return true },
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) error {
|
||||
_, err := ec2conn.DescribeInstances(&ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{aws.String(instanceId)},
|
||||
})
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error describing instance for spot request tags: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
describeOutput, err := ec2conn.DescribeInstances(&ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{aws.String(instanceId)},
|
||||
})
|
||||
sir := describeOutput.Reservations[0].Instances[0].SpotInstanceRequestId
|
||||
|
||||
// Apply tags to the spot request.
|
||||
|
@ -335,34 +370,13 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
|||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
ShouldRetry: func(error) bool {
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
switch awsErr.Code() {
|
||||
case "InvalidInstanceID.NotFound":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
err = retry.Config{Tries: 11, ShouldRetry: func(error) bool {
|
||||
if isAWSErr(err, "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 {
|
||||
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||
|
@ -427,6 +441,9 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
|||
}
|
||||
|
||||
state.Put("instance", instance)
|
||||
// instance_id is the generic term used so that users can have access to the
|
||||
// instance id inside of the provisioners, used in step_provision.
|
||||
state.Put("instance_id", instance.InstanceId)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
|
|
@ -2,68 +2,16 @@ package common
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"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)
|
||||
|
@ -73,6 +21,7 @@ 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
|
||||
|
@ -81,22 +30,16 @@ func tStateSpot() multistep.StateBag {
|
|||
func getBasicStep() *StepRunSpotInstance {
|
||||
stepRunSpotInstance := StepRunSpotInstance{
|
||||
AssociatePublicIpAddress: false,
|
||||
BlockDevices: BlockDevices{
|
||||
AMIBlockDevices: AMIBlockDevices{
|
||||
AMIMappings: []BlockDevice(nil),
|
||||
},
|
||||
LaunchBlockDevices: LaunchBlockDevices{
|
||||
LaunchMappings: []BlockDevice(nil),
|
||||
},
|
||||
},
|
||||
BlockDurationMinutes: 0,
|
||||
Debug: false,
|
||||
LaunchMappings: BlockDevices{},
|
||||
BlockDurationMinutes: 0,
|
||||
Debug: false,
|
||||
Comm: &communicator.Config{
|
||||
SSHKeyPairName: "foo",
|
||||
SSH: communicator.SSH{
|
||||
SSHKeyPairName: "foo",
|
||||
},
|
||||
},
|
||||
EbsOptimized: false,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: "",
|
||||
InstanceInitiatedShutdownBehavior: "stop",
|
||||
InstanceType: "t2.micro",
|
||||
SourceAMI: "",
|
||||
|
@ -130,6 +73,10 @@ func TestCreateTemplateData(t *testing.T) {
|
|||
t.Fatalf("Template should have contained a networkInterface object: recieved %#v", template.NetworkInterfaces)
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -137,4 +84,13 @@ func TestCreateTemplateData(t *testing.T) {
|
|||
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.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ func (s *StepSecurityGroup) Run(ctx context.Context, state multistep.StateBag) m
|
|||
|
||||
params := &ec2.DescribeSecurityGroupsInput{}
|
||||
if vpcId != "" {
|
||||
s.SecurityGroupFilter.Filters[aws.String("vpc-id")] = &vpcId
|
||||
s.SecurityGroupFilter.Filters["vpc-id"] = vpcId
|
||||
}
|
||||
params.Filters = buildEc2Filters(s.SecurityGroupFilter.Filters)
|
||||
|
||||
|
@ -183,7 +183,8 @@ func (s *StepSecurityGroup) Cleanup(state multistep.StateBag) {
|
|||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error cleaning up security group. Please delete the group manually: %s", s.createdGroupId))
|
||||
"Error cleaning up security group. Please delete the group manually:"+
|
||||
" err: %s; security group ID: %s", err, s.createdGroupId))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ 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"
|
||||
)
|
||||
|
@ -20,7 +21,7 @@ import (
|
|||
type StepSourceAMIInfo struct {
|
||||
SourceAmi string
|
||||
EnableAMISriovNetSupport bool
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMIENASupport confighelper.Trilean
|
||||
AMIVirtType string
|
||||
AmiFilters AmiFilterOptions
|
||||
}
|
||||
|
@ -57,7 +58,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.Owners
|
||||
params.Owners = s.AmiFilters.GetOwners()
|
||||
}
|
||||
|
||||
log.Printf("Using AMI Filters %v", params)
|
||||
|
@ -94,7 +95,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 != nil && *s.EnableAMIENASupport) || s.EnableAMISriovNetSupport {
|
||||
if s.EnableAMIENASupport.True() || s.EnableAMISriovNetSupport {
|
||||
err = s.canEnableEnhancedNetworking(image)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
|
@ -41,17 +40,12 @@ func (s *StepStopEBSBackedInstance) Run(ctx context.Context, state multistep.Sta
|
|||
// does not exist.
|
||||
|
||||
// Work around this by retrying a few times, up to about 5 minutes.
|
||||
err := retry.Config{
|
||||
Tries: 6,
|
||||
ShouldRetry: func(error) bool {
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
switch awsErr.Code() {
|
||||
case "InvalidInstanceID.NotFound":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
err := retry.Config{Tries: 6, ShouldRetry: func(error) bool {
|
||||
if isAWSErr(err, "InvalidInstanceID.NotFound", "") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 60 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) error {
|
||||
ui.Message(fmt.Sprintf("Stopping instance"))
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/builder"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
|
@ -26,7 +27,8 @@ func (t TagMap) IsSet() bool {
|
|||
|
||||
func (t TagMap) EC2Tags(ictx interpolate.Context, region string, state multistep.StateBag) (EC2Tags, error) {
|
||||
var ec2Tags []*ec2.Tag
|
||||
ictx.Data = extractBuildInfo(region, state)
|
||||
generatedData := builder.GeneratedData{State: state}
|
||||
ictx.Data = extractBuildInfo(region, state, &generatedData)
|
||||
|
||||
for key, value := range t {
|
||||
interpolatedKey, err := interpolate.Render(key, &ictx)
|
||||
|
|
|
@ -3,8 +3,6 @@ package common
|
|||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
|
||||
packertpl "github.com/hashicorp/packer/common/template"
|
||||
)
|
||||
|
||||
func isalphanumeric(b byte) bool {
|
||||
|
@ -39,5 +37,4 @@ func templateCleanAMIName(s string) string {
|
|||
|
||||
var TemplateFuncs = template.FuncMap{
|
||||
"clean_resource_name": templateCleanAMIName,
|
||||
"clean_ami_name": packertpl.DeprecatedTemplateFunc("clean_ami_name", "clean_resource_name", templateCleanAMIName),
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
//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.
|
||||
//
|
||||
|
@ -8,8 +11,10 @@ package ebs
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer/builder"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
|
@ -26,9 +31,39 @@ type Config struct {
|
|||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
awscommon.AMIConfig `mapstructure:",squash"`
|
||||
awscommon.BlockDevices `mapstructure:",squash"`
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
VolumeRunTags awscommon.TagMap `mapstructure:"run_volume_tags"`
|
||||
// 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"`
|
||||
// Relevant only to Windows guests: If you set this flag, we'll add clauses
|
||||
// to the launch_block_device_mappings that make sure ephemeral drives
|
||||
// don't show up in the EC2 console. If you launched from the EC2 console,
|
||||
// you'd get this automatically, but the SDK does not provide this service.
|
||||
// For more information, see
|
||||
// https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/InstanceStorage.html.
|
||||
// Because we don't validate the OS type of your guest, it is up to you to
|
||||
// make sure you don't set this for *nix guests; behavior may be
|
||||
// unpredictable.
|
||||
NoEphemeral bool `mapstructure:"no_ephemeral" required:"false"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
@ -38,7 +73,9 @@ type Builder struct {
|
|||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
|
@ -55,7 +92,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if b.config.PackerConfig.PackerForce {
|
||||
|
@ -69,10 +106,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
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.BlockDevices.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.RunConfig.Prepare(&b.config.ctx)...)
|
||||
|
||||
if b.config.IsSpotInstance() && ((b.config.AMIENASupport != nil && *b.config.AMIENASupport) || b.config.AMISriovNetSupport) {
|
||||
if b.config.IsSpotInstance() && (b.config.AMIENASupport.True() || 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 "+
|
||||
|
@ -88,11 +126,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return warns, errs
|
||||
return nil, warns, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
return warns, nil
|
||||
|
||||
generatedData := []string{"SourceAMIName"}
|
||||
return generatedData, warns, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
|
@ -103,30 +143,31 @@ 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)
|
||||
generatedData := &builder.GeneratedData{State: state}
|
||||
|
||||
var instanceStep multistep.Step
|
||||
|
||||
if b.config.IsSpotInstance() {
|
||||
instanceStep = &awscommon.StepRunSpotInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
LaunchMappings: b.config.LaunchMappings,
|
||||
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,
|
||||
|
@ -137,18 +178,18 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
VolumeTags: b.config.VolumeRunTags,
|
||||
NoEphemeral: b.config.NoEphemeral,
|
||||
}
|
||||
} else {
|
||||
instanceStep = &awscommon.StepRunSourceInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
LaunchMappings: b.config.LaunchMappings,
|
||||
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(),
|
||||
|
@ -157,6 +198,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
VolumeTags: b.config.VolumeRunTags,
|
||||
NoEphemeral: b.config.NoEphemeral,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,6 +208,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
DestAmiName: b.config.AMIName,
|
||||
ForceDeregister: b.config.AMIForceDeregister,
|
||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
||||
VpcId: b.config.VpcId,
|
||||
SubnetId: b.config.SubnetId,
|
||||
HasSubnetFilter: len(b.config.SubnetFilter.Filters) > 0,
|
||||
},
|
||||
&awscommon.StepSourceAMIInfo{
|
||||
SourceAmi: b.config.SourceAmi,
|
||||
|
@ -194,8 +239,13 @@ 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,
|
||||
SkipProfileValidation: b.config.SkipProfileValidation,
|
||||
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
|
||||
},
|
||||
&awscommon.StepCleanupVolumes{
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
LaunchMappings: b.config.LaunchMappings,
|
||||
},
|
||||
instanceStep,
|
||||
&awscommon.StepGetPassword{
|
||||
|
@ -208,7 +258,9 @@ 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.SSHInterface,
|
||||
b.config.Comm.Host(),
|
||||
),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
|
@ -251,6 +303,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
SnapshotUsers: b.config.SnapshotUsers,
|
||||
SnapshotGroups: b.config.SnapshotGroups,
|
||||
Ctx: b.config.ctx,
|
||||
GeneratedData: generatedData,
|
||||
},
|
||||
&awscommon.StepCreateTags{
|
||||
Tags: b.config.AMITags,
|
||||
|
@ -277,6 +330,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
Amis: state.Get("amis").(map[string]string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Session: session,
|
||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
// 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"`
|
||||
SkipProfileValidation *bool `mapstructure:"skip_profile_validation" required:"false" cty:"skip_profile_validation"`
|
||||
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 `mapstructure:"ssh_public_key" cty:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" 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"`
|
||||
NoEphemeral *bool `mapstructure:"no_ephemeral" required:"false" cty:"no_ephemeral"`
|
||||
}
|
||||
|
||||
// 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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Config.
|
||||
// This spec is used by HCL to read the fields of Config.
|
||||
// The decoded values from this spec will then be applied to a 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: "tags", 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: "snapshot_tags", 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},
|
||||
"skip_profile_validation": &hcldec.AttrSpec{Name: "skip_profile_validation", Type: cty.Bool, 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.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
|
||||
"launch_block_device_mappings": &hcldec.BlockListSpec{TypeName: "launch_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
|
||||
"run_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "run_volume_tags", ElementType: cty.String, Required: false},
|
||||
"no_ephemeral": &hcldec.AttrSpec{Name: "no_ephemeral", Type: cty.Bool, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -32,7 +32,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) {
|
|||
"access_key": []string{},
|
||||
}
|
||||
|
||||
warnings, err := b.Prepare(c)
|
||||
_, warnings, err := b.Prepare(c)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
|||
// Test good
|
||||
config["ami_name"] = "foo"
|
||||
config["skip_region_validation"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
|||
// Test bad
|
||||
config["ami_name"] = "foo {{"
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
|||
// Test bad
|
||||
delete(config, "ami_name")
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
|||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
|
|||
// Test good
|
||||
config["shutdown_behavior"] = "terminate"
|
||||
config["skip_region_validation"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
|
|||
|
||||
// Test good
|
||||
config["shutdown_behavior"] = "stop"
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
|
|||
|
||||
// Test bad
|
||||
config["shutdown_behavior"] = "foobar"
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -129,3 +129,22 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
|
|||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
generatedData, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
if len(generatedData) == 0 {
|
||||
t.Fatalf("Generated data should not be empty")
|
||||
}
|
||||
if generatedData[0] != "SourceAMIName" {
|
||||
t.Fatalf("Generated data should contain SourceAMIName")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ 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 != nil && *config.AMIEncryptBootVolume != false || s.AMISkipBuildRegion {
|
||||
if config.AMIEncryptBootVolume.True() || s.AMISkipBuildRegion {
|
||||
state.Put("intermediary_image", true)
|
||||
|
||||
// From AWS SDK docs: You can encrypt a copy of an unencrypted snapshot,
|
||||
|
@ -45,7 +45,7 @@ func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multi
|
|||
createOpts := &ec2.CreateImageInput{
|
||||
InstanceId: instance.InstanceId,
|
||||
Name: &amiName,
|
||||
BlockDeviceMappings: config.BlockDevices.BuildAMIDevices(),
|
||||
BlockDeviceMappings: config.AMIMappings.BuildEC2BlockDeviceMappings(),
|
||||
}
|
||||
|
||||
createResp, err := ec2conn.CreateImage(createOpts)
|
||||
|
@ -66,15 +66,18 @@ func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multi
|
|||
ui.Say("Waiting for AMI to become ready...")
|
||||
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *createResp.ImageId); err != nil {
|
||||
log.Printf("Error waiting for AMI: %s", err)
|
||||
imagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{createResp.ImageId}})
|
||||
if err != nil {
|
||||
imResp, imerr := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{createResp.ImageId}})
|
||||
if imerr != nil {
|
||||
log.Printf("Unable to determine reason waiting for AMI failed: %s", err)
|
||||
err = fmt.Errorf("Unknown error waiting for AMI.")
|
||||
} else {
|
||||
stateReason := imagesResp.Images[0].StateReason
|
||||
err = fmt.Errorf("Error waiting for AMI. Reason: %s", stateReason)
|
||||
err = fmt.Errorf("Unknown error waiting for AMI; %s", err)
|
||||
}
|
||||
if imResp != nil && len(imResp.Images) > 0 {
|
||||
image := imResp.Images[0]
|
||||
if image != nil {
|
||||
stateReason := image.StateReason
|
||||
err = fmt.Errorf("Error waiting for AMI. Reason: %s", stateReason)
|
||||
}
|
||||
}
|
||||
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
//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,3 +1,6 @@
|
|||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config,RootBlockDevice,BlockDevice
|
||||
|
||||
// The ebssurrogate package contains a packer.Builder implementation that
|
||||
// builds a new EBS-backed AMI using an ephemeral instance.
|
||||
package ebssurrogate
|
||||
|
@ -6,8 +9,10 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer/builder"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
|
@ -23,12 +28,41 @@ type Config struct {
|
|||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
awscommon.BlockDevices `mapstructure:",squash"`
|
||||
awscommon.AMIConfig `mapstructure:",squash"`
|
||||
|
||||
RootDevice RootBlockDevice `mapstructure:"ami_root_device"`
|
||||
// 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.
|
||||
VolumeRunTags awscommon.TagMap `mapstructure:"run_volume_tags"`
|
||||
Architecture string `mapstructure:"ami_architecture"`
|
||||
// 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"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
@ -38,7 +72,9 @@ type Builder struct {
|
|||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
|
@ -55,7 +91,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if b.config.PackerConfig.PackerForce {
|
||||
|
@ -69,7 +105,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
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.BlockDevices.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.RootDevice.Prepare(&b.config.ctx)...)
|
||||
|
||||
if b.config.AMIVirtType == "" {
|
||||
|
@ -77,7 +114,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}
|
||||
|
||||
foundRootVolume := false
|
||||
for _, launchDevice := range b.config.BlockDevices.LaunchMappings {
|
||||
for _, launchDevice := range b.config.LaunchMappings {
|
||||
if launchDevice.DeviceName == b.config.RootDevice.SourceDeviceName {
|
||||
foundRootVolume = true
|
||||
if launchDevice.OmitFromArtifact {
|
||||
|
@ -90,13 +127,6 @@ 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 != 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 "+
|
||||
|
@ -119,12 +149,13 @@ 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, warns, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
|
||||
return warns, nil
|
||||
generatedData := []string{"SourceAMIName"}
|
||||
return generatedData, warns, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
|
@ -132,7 +163,9 @@ 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)
|
||||
|
@ -140,23 +173,24 @@ 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)
|
||||
generatedData := &builder.GeneratedData{State: state}
|
||||
|
||||
var instanceStep multistep.Step
|
||||
|
||||
if b.config.IsSpotInstance() {
|
||||
instanceStep = &awscommon.StepRunSpotInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
LaunchMappings: b.config.LaunchMappings,
|
||||
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,
|
||||
|
@ -171,14 +205,13 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
} else {
|
||||
instanceStep = &awscommon.StepRunSourceInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
LaunchMappings: b.config.LaunchMappings,
|
||||
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(),
|
||||
|
@ -190,14 +223,17 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
}
|
||||
}
|
||||
|
||||
amiDevices := b.config.BuildAMIDevices()
|
||||
launchDevices := b.config.BuildLaunchDevices()
|
||||
amiDevices := b.config.AMIMappings.BuildEC2BlockDeviceMappings()
|
||||
launchDevices := b.config.LaunchMappings.BuildEC2BlockDeviceMappings()
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
&awscommon.StepPreValidate{
|
||||
DestAmiName: b.config.AMIName,
|
||||
ForceDeregister: b.config.AMIForceDeregister,
|
||||
VpcId: b.config.VpcId,
|
||||
SubnetId: b.config.SubnetId,
|
||||
HasSubnetFilter: len(b.config.SubnetFilter.Filters) > 0,
|
||||
},
|
||||
&awscommon.StepSourceAMIInfo{
|
||||
SourceAmi: b.config.SourceAmi,
|
||||
|
@ -226,8 +262,13 @@ 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,
|
||||
SkipProfileValidation: b.config.SkipProfileValidation,
|
||||
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
|
||||
},
|
||||
&awscommon.StepCleanupVolumes{
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
LaunchMappings: b.config.LaunchMappings.Common(),
|
||||
},
|
||||
instanceStep,
|
||||
&awscommon.StepGetPassword{
|
||||
|
@ -240,7 +281,9 @@ 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.SSHInterface,
|
||||
b.config.Comm.Host(),
|
||||
),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
|
@ -252,12 +295,13 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
DisableStopInstance: b.config.DisableStopInstance,
|
||||
},
|
||||
&awscommon.StepModifyEBSBackedInstance{
|
||||
Skip: b.config.IsSpotInstance(),
|
||||
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
||||
EnableAMIENASupport: b.config.AMIENASupport,
|
||||
},
|
||||
&StepSnapshotVolumes{
|
||||
LaunchDevices: launchDevices,
|
||||
SnapshotOmitMap: b.config.GetOmissions(),
|
||||
SnapshotOmitMap: b.config.LaunchMappings.GetOmissions(),
|
||||
},
|
||||
&awscommon.StepDeregisterAMI{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
|
@ -273,7 +317,8 @@ 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.GetOmissions(),
|
||||
LaunchOmitMap: b.config.LaunchMappings.GetOmissions(),
|
||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
||||
},
|
||||
&awscommon.StepAMIRegionCopy{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
|
@ -292,6 +337,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
SnapshotUsers: b.config.SnapshotUsers,
|
||||
SnapshotGroups: b.config.SnapshotGroups,
|
||||
Ctx: b.config.ctx,
|
||||
GeneratedData: generatedData,
|
||||
},
|
||||
&awscommon.StepCreateTags{
|
||||
Tags: b.config.AMITags,
|
||||
|
@ -315,6 +361,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
Amis: amis.(map[string]string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Session: session,
|
||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
|
|
|
@ -0,0 +1,331 @@
|
|||
// Code generated by "mapstructure-to-hcl2 -type Config,RootBlockDevice,BlockDevice"; DO NOT EDIT.
|
||||
package ebssurrogate
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer/builder/amazon/common"
|
||||
"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"`
|
||||
OmitFromArtifact *bool `mapstructure:"omit_from_artifact" cty:"omit_from_artifact"`
|
||||
}
|
||||
|
||||
// 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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatBlockDevice)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a BlockDevice.
|
||||
// This spec is used by HCL to read the fields of BlockDevice.
|
||||
// The decoded values from this spec will then be applied to a 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},
|
||||
"omit_from_artifact": &hcldec.AttrSpec{Name: "omit_from_artifact", 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"`
|
||||
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"`
|
||||
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"`
|
||||
SkipProfileValidation *bool `mapstructure:"skip_profile_validation" required:"false" cty:"skip_profile_validation"`
|
||||
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 `mapstructure:"ssh_public_key" cty:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" 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"`
|
||||
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"`
|
||||
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" required:"false" cty:"ami_block_device_mappings"`
|
||||
LaunchMappings []FlatBlockDevice `mapstructure:"launch_block_device_mappings" required:"false" cty:"launch_block_device_mappings"`
|
||||
RootDevice *FlatRootBlockDevice `mapstructure:"ami_root_device" required:"true" cty:"ami_root_device"`
|
||||
VolumeRunTags common.TagMap `mapstructure:"run_volume_tags" cty:"run_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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Config.
|
||||
// This spec is used by HCL to read the fields of Config.
|
||||
// The decoded values from this spec will then be applied to a 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())},
|
||||
"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},
|
||||
"skip_profile_validation": &hcldec.AttrSpec{Name: "skip_profile_validation", Type: cty.Bool, 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_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: "tags", 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: "snapshot_tags", 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},
|
||||
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
|
||||
"launch_block_device_mappings": &hcldec.BlockListSpec{TypeName: "launch_block_device_mappings", Nested: hcldec.ObjectSpec((*FlatBlockDevice)(nil).HCL2Spec())},
|
||||
"ami_root_device": &hcldec.BlockSpec{TypeName: "ami_root_device", Nested: hcldec.ObjectSpec((*FlatRootBlockDevice)(nil).HCL2Spec())},
|
||||
"run_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "run_volume_tags", ElementType: cty.String, Required: false},
|
||||
"ami_architecture": &hcldec.AttrSpec{Name: "ami_architecture", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatRootBlockDevice is an auto-generated flat version of RootBlockDevice.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatRootBlockDevice struct {
|
||||
SourceDeviceName *string `mapstructure:"source_device_name" cty:"source_device_name"`
|
||||
DeviceName *string `mapstructure:"device_name" required:"false" cty:"device_name"`
|
||||
DeleteOnTermination *bool `mapstructure:"delete_on_termination" required:"false" cty:"delete_on_termination"`
|
||||
IOPS *int64 `mapstructure:"iops" required:"false" cty:"iops"`
|
||||
VolumeType *string `mapstructure:"volume_type" required:"false" cty:"volume_type"`
|
||||
VolumeSize *int64 `mapstructure:"volume_size" required:"false" cty:"volume_size"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatRootBlockDevice.
|
||||
// FlatRootBlockDevice is an auto-generated flat version of RootBlockDevice.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*RootBlockDevice) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatRootBlockDevice)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a RootBlockDevice.
|
||||
// This spec is used by HCL to read the fields of RootBlockDevice.
|
||||
// The decoded values from this spec will then be applied to a FlatRootBlockDevice.
|
||||
func (*FlatRootBlockDevice) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"source_device_name": &hcldec.AttrSpec{Name: "source_device_name", Type: cty.String, Required: false},
|
||||
"device_name": &hcldec.AttrSpec{Name: "device_name", Type: cty.String, Required: false},
|
||||
"delete_on_termination": &hcldec.AttrSpec{Name: "delete_on_termination", Type: cty.Bool, Required: false},
|
||||
"iops": &hcldec.AttrSpec{Name: "iops", Type: cty.Number, 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},
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package ebssurrogate
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer/builder/amazon/common"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
@ -31,7 +32,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) {
|
|||
"access_key": []string{},
|
||||
}
|
||||
|
||||
warnings, err := b.Prepare(c)
|
||||
_, warnings, err := b.Prepare(c)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -46,7 +47,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
|||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -54,3 +55,37 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
|||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
|
||||
var b Builder
|
||||
// Basic configuration
|
||||
b.config.RootDevice = RootBlockDevice{
|
||||
SourceDeviceName: "device name",
|
||||
DeviceName: "device name",
|
||||
}
|
||||
b.config.LaunchMappings = BlockDevices{
|
||||
BlockDevice{
|
||||
BlockDevice: common.BlockDevice{
|
||||
DeviceName: "device name",
|
||||
},
|
||||
OmitFromArtifact: false,
|
||||
},
|
||||
}
|
||||
b.config.AMIVirtType = "type"
|
||||
config := testConfig()
|
||||
config["ami_name"] = "name"
|
||||
|
||||
generatedData, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
if len(generatedData) == 0 {
|
||||
t.Fatalf("Generated data should not be empty")
|
||||
}
|
||||
if generatedData[0] != "SourceAMIName" {
|
||||
t.Fatalf("Generated data should contain SourceAMIName")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//go:generate struct-markdown
|
||||
|
||||
package ebssurrogate
|
||||
|
||||
import (
|
||||
|
@ -7,12 +9,30 @@ import (
|
|||
)
|
||||
|
||||
type RootBlockDevice struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
func (c *RootBlockDevice) Prepare(ctx *interpolate.Context) []error {
|
||||
|
|
|
@ -7,6 +7,8 @@ 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,11 +18,12 @@ type StepRegisterAMI struct {
|
|||
RootDevice RootBlockDevice
|
||||
AMIDevices []*ec2.BlockDeviceMapping
|
||||
LaunchDevices []*ec2.BlockDeviceMapping
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMIENASupport confighelper.Trilean
|
||||
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 {
|
||||
|
@ -33,8 +36,25 @@ 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: &config.AMIName,
|
||||
Name: &amiName,
|
||||
Architecture: aws.String(s.Architecture),
|
||||
RootDeviceName: aws.String(s.RootDevice.DeviceName),
|
||||
VirtualizationType: aws.String(config.AMIVirtType),
|
||||
|
@ -46,7 +66,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 != nil && *s.EnableAMIENASupport {
|
||||
if s.EnableAMIENASupport.True() {
|
||||
// Set EnaSupport to true
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
registerOpts.EnaSupport = aws.Bool(true)
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
type StepSnapshotVolumes struct {
|
||||
LaunchDevices []*ec2.BlockDeviceMapping
|
||||
snapshotIds map[string]string
|
||||
snapshotMutex sync.Mutex
|
||||
SnapshotOmitMap map[string]bool
|
||||
}
|
||||
|
||||
|
@ -50,7 +51,9 @@ func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName str
|
|||
}
|
||||
|
||||
// Set the snapshot ID so we can delete it later
|
||||
s.snapshotMutex.Lock()
|
||||
s.snapshotIds[deviceName] = *createSnapResp.SnapshotId
|
||||
s.snapshotMutex.Unlock()
|
||||
|
||||
// Wait for snapshot to be created
|
||||
err = awscommon.WaitUntilSnapshotDone(ctx, ec2conn, *createSnapResp.SnapshotId)
|
||||
|
@ -104,11 +107,13 @@ func (s *StepSnapshotVolumes) Cleanup(state multistep.StateBag) {
|
|||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
ui.Say("Removing snapshots since we cancelled or halted...")
|
||||
s.snapshotMutex.Lock()
|
||||
for _, snapshotId := range s.snapshotIds {
|
||||
_, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{SnapshotId: &snapshotId})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error: %s", err))
|
||||
}
|
||||
}
|
||||
s.snapshotMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,10 @@ type Artifact struct {
|
|||
// BuilderId is the unique ID for the builder that created this AMI
|
||||
BuilderIdValue string
|
||||
|
||||
// StateData should store data such as GeneratedData
|
||||
// to be shared with post-processors
|
||||
StateData map[string]interface{}
|
||||
|
||||
// EC2 connection for performing API stuff.
|
||||
Conn *ec2.EC2
|
||||
}
|
||||
|
@ -56,6 +60,9 @@ func (a *Artifact) String() string {
|
|||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
if _, ok := a.StateData[name]; ok {
|
||||
return a.StateData[name]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package ebsvolume
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestArtifactState(t *testing.T) {
|
||||
expectedData := "this is the data"
|
||||
artifact := &Artifact{
|
||||
StateData: map[string]interface{}{"state_data": expectedData},
|
||||
}
|
||||
|
||||
// Valid state
|
||||
result := artifact.State("state_data")
|
||||
if result != expectedData {
|
||||
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
|
||||
}
|
||||
|
||||
// Invalid state
|
||||
result = artifact.State("invalid_key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for invalid state data name")
|
||||
}
|
||||
|
||||
// Nil StateData should not fail and should return nil
|
||||
artifact = &Artifact{}
|
||||
result = artifact.State("key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for nil StateData")
|
||||
}
|
||||
}
|
|
@ -1,29 +1,37 @@
|
|||
//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 awscommon.TagMap `mapstructure:"tags"`
|
||||
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"`
|
||||
}
|
||||
|
||||
func commonBlockDevices(mappings []BlockDevice, ctx *interpolate.Context) (awscommon.BlockDevices, error) {
|
||||
result := make([]awscommon.BlockDevice, len(mappings))
|
||||
type BlockDevices []BlockDevice
|
||||
|
||||
for i, mapping := range mappings {
|
||||
interpolateBlockDev, err := interpolate.RenderInterface(&mapping.BlockDevice, ctx)
|
||||
if err != nil {
|
||||
return awscommon.BlockDevices{}, err
|
||||
}
|
||||
result[i] = *interpolateBlockDev.(*awscommon.BlockDevice)
|
||||
func (bds BlockDevices) BuildEC2BlockDeviceMappings() []*ec2.BlockDeviceMapping {
|
||||
var blockDevices []*ec2.BlockDeviceMapping
|
||||
|
||||
for _, blockDevice := range bds {
|
||||
blockDevices = append(blockDevices, blockDevice.BuildEC2BlockDeviceMapping())
|
||||
}
|
||||
|
||||
return awscommon.BlockDevices{
|
||||
LaunchBlockDevices: awscommon.LaunchBlockDevices{
|
||||
LaunchMappings: result,
|
||||
},
|
||||
}, nil
|
||||
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)
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
// The ebsvolume package contains a packer.Builder implementation that
|
||||
// builds EBS volumes for Amazon EC2 using an ephemeral instance,
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config,BlockDevice
|
||||
|
||||
// The ebsvolume package contains a packer.Builder implementation that builds
|
||||
// EBS volumes for Amazon EC2 using an ephemeral instance,
|
||||
package ebsvolume
|
||||
|
||||
import (
|
||||
|
@ -7,6 +10,8 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
|
@ -23,12 +28,47 @@ type Config struct {
|
|||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
|
||||
VolumeMappings []BlockDevice `mapstructure:"ebs_volumes"`
|
||||
AMIENASupport *bool `mapstructure:"ena_support"`
|
||||
AMISriovNetSupport bool `mapstructure:"sriov_support"`
|
||||
// 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"`
|
||||
|
||||
launchBlockDevices awscommon.BlockDevices
|
||||
ctx interpolate.Context
|
||||
// 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
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
|
@ -41,7 +81,9 @@ type EngineVarsTemplate struct {
|
|||
SourceAMI string
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
||||
// Create passthrough for {{ .BuildRegion }} and {{ .SourceAMI }} variables
|
||||
// so we can fill them in later
|
||||
|
@ -54,7 +96,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
InterpolateContext: &b.config.ctx,
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
|
@ -70,12 +112,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
b.config.launchBlockDevices, err = commonBlockDevices(b.config.VolumeMappings, &b.config.ctx)
|
||||
b.config.launchBlockDevices = b.config.VolumeMappings
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
|
||||
if b.config.IsSpotInstance() && ((b.config.AMIENASupport != nil && *b.config.AMIENASupport) || b.config.AMISriovNetSupport) {
|
||||
if b.config.IsSpotInstance() && ((b.config.AMIENASupport.True()) || 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 "+
|
||||
|
@ -91,11 +133,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return warns, errs
|
||||
return nil, warns, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
return warns, nil
|
||||
|
||||
generatedData := []string{"SourceAMIName"}
|
||||
return generatedData, warns, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
|
@ -104,12 +148,14 @@ 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)
|
||||
|
||||
|
@ -118,35 +164,34 @@ 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,
|
||||
BlockDevices: b.config.launchBlockDevices,
|
||||
LaunchMappings: b.config.launchBlockDevices,
|
||||
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
||||
Ctx: b.config.ctx,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
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,
|
||||
SpotInstanceTypes: b.config.SpotInstanceTypes,
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
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,
|
||||
BlockDevices: b.config.launchBlockDevices,
|
||||
LaunchMappings: 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(),
|
||||
|
@ -154,6 +199,7 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,6 +231,11 @@ 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,
|
||||
SkipProfileValidation: b.config.SkipProfileValidation,
|
||||
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
|
||||
},
|
||||
instanceStep,
|
||||
&stepTagEBSVolumes{
|
||||
VolumeMapping: b.config.VolumeMappings,
|
||||
|
@ -200,7 +251,9 @@ 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.SSHInterface,
|
||||
b.config.Comm.Host(),
|
||||
),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
|
@ -231,6 +284,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
Volumes: state.Get("ebsvolumes").(EbsVolumes),
|
||||
BuilderIdValue: BuilderId,
|
||||
Conn: ec2conn,
|
||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Created Volumes: %s", artifact))
|
||||
return artifact, nil
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
// Code generated by "mapstructure-to-hcl2 -type Config,BlockDevice"; DO NOT EDIT.
|
||||
package ebsvolume
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer/builder/amazon/common"
|
||||
"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"`
|
||||
Tags common.TagMap `mapstructure:"tags" required:"false" cty:"tags"`
|
||||
}
|
||||
|
||||
// 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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatBlockDevice)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a BlockDevice.
|
||||
// This spec is used by HCL to read the fields of BlockDevice.
|
||||
// The decoded values from this spec will then be applied to a 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},
|
||||
"tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, 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"`
|
||||
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"`
|
||||
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"`
|
||||
SkipProfileValidation *bool `mapstructure:"skip_profile_validation" required:"false" cty:"skip_profile_validation"`
|
||||
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 `mapstructure:"ssh_public_key" cty:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" 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"`
|
||||
AMIENASupport *bool `mapstructure:"ena_support" required:"false" cty:"ena_support"`
|
||||
AMISriovNetSupport *bool `mapstructure:"sriov_support" required:"false" cty:"sriov_support"`
|
||||
VolumeMappings []FlatBlockDevice `mapstructure:"ebs_volumes" required:"false" cty:"ebs_volumes"`
|
||||
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{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Config.
|
||||
// This spec is used by HCL to read the fields of Config.
|
||||
// The decoded values from this spec will then be applied to a 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())},
|
||||
"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},
|
||||
"skip_profile_validation": &hcldec.AttrSpec{Name: "skip_profile_validation", Type: cty.Bool, 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},
|
||||
"ena_support": &hcldec.AttrSpec{Name: "ena_support", Type: cty.Bool, Required: false},
|
||||
"sriov_support": &hcldec.AttrSpec{Name: "sriov_support", Type: cty.Bool, Required: false},
|
||||
"ebs_volumes": &hcldec.BlockListSpec{TypeName: "ebs_volumes", Nested: hcldec.ObjectSpec((*FlatBlockDevice)(nil).HCL2Spec())},
|
||||
"run_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "run_volume_tags", ElementType: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -31,7 +31,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) {
|
|||
"access_key": []string{},
|
||||
}
|
||||
|
||||
warnings, err := b.Prepare(c)
|
||||
_, warnings, err := b.Prepare(c)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
|||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
|
|||
// Test good
|
||||
config["shutdown_behavior"] = "terminate"
|
||||
config["skip_region_validation"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
|
|||
|
||||
// Test good
|
||||
config["shutdown_behavior"] = "stop"
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
|
|||
|
||||
// Test bad
|
||||
config["shutdown_behavior"] = "foobar"
|
||||
warnings, err = b.Prepare(config)
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
@ -90,3 +90,22 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
|
|||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
generatedData, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
if len(generatedData) == 0 {
|
||||
t.Fatalf("Generated data should not be empty")
|
||||
}
|
||||
if generatedData[0] != "SourceAMIName" {
|
||||
t.Fatalf("Generated data should contain SourceAMIName")
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue