Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fb1be8be30 |
@@ -1,12 +0,0 @@
|
||||
# Golang CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.11
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- checkout
|
||||
- run: make ci
|
||||
+21
-77
@@ -1,7 +1,7 @@
|
||||
# Contributing to Packer
|
||||
|
||||
**First:** if you're unsure or afraid of _anything_, just ask or submit the
|
||||
issue or pull request anyway. You won't be yelled at for giving your best
|
||||
issue or pull request anyways. You won't be yelled at for giving your best
|
||||
effort. The worst that can happen is that you'll be politely asked to change
|
||||
something. We appreciate any sort of contributions, and don't want a wall of
|
||||
rules to get in the way of that.
|
||||
@@ -37,7 +37,7 @@ can quickly merge or address your contributions.
|
||||
|
||||
2. The issue is verified and categorized by a Packer collaborator.
|
||||
Categorization is done via tags. For example, bugs are marked as "bugs" and
|
||||
simple fixes are marked as "good first issue".
|
||||
easy fixes are marked as "easy".
|
||||
|
||||
3. Unless it is critical, the issue is left for a period of time (sometimes many
|
||||
weeks), giving outside contributors a chance to address the issue.
|
||||
@@ -59,7 +59,7 @@ runtime in order to build packer.
|
||||
|
||||
If/when you have go installed you can already `go get` packer and `make` in
|
||||
order to compile and test Packer. These instructions target
|
||||
POSIX-like environments (macOS, Linux, Cygwin, etc.) so you may need to
|
||||
POSIX-like environments (Mac OS X, Linux, Cygwin, etc.) so you may need to
|
||||
adjust them for Windows or other shells.
|
||||
The instructions below are for go 1.7. or later.
|
||||
|
||||
@@ -107,8 +107,6 @@ From there, open your fork in your browser to open a new pull-request.
|
||||
will break if you `git clone` your fork instead of using `go get` on the main
|
||||
Packer project.
|
||||
|
||||
**Note:** See [Working on forks](#Working on forks) for a better way to use `git push ...`.
|
||||
|
||||
### Pull Request Lifecycle
|
||||
|
||||
1. You are welcome to submit your pull request for commentary or review before
|
||||
@@ -116,90 +114,36 @@ Packer project.
|
||||
"[WIP]" to indicate this. It's also a good idea to include specific questions
|
||||
or items you'd like feedback on.
|
||||
|
||||
2. Once you believe your pull request is ready to be merged, you can remove any
|
||||
"[WIP]" prefix from the title and a core team member will review.
|
||||
1. Once you believe your pull request is ready to be merged, you can remove any
|
||||
"[WIP]" prefix from the title and a core team member will review.
|
||||
|
||||
3. One of Packer's core team members will look over your contribution and
|
||||
either merge, or provide comments letting you know if there is anything left
|
||||
to do. We do our best to provide feedback in a timely manner, but it may take
|
||||
some time for us to respond. We may also have questions that we need answered
|
||||
about the code, either because something doesn't make sense to us or because
|
||||
we want to understand your thought process.
|
||||
1. One of Packer's core team members will look over your contribution and
|
||||
either provide comments letting you know if there is anything left to do. We
|
||||
do our best to provide feedback in a timely manner, but it may take some time
|
||||
for us to respond.
|
||||
|
||||
4. If we have requested changes, you can either make those changes or, if you
|
||||
disagree with the suggested changes, we can have a conversation about our reasoning and agree on a path forward. This may be a multi-step process. Our view is that pull requests are a chance to collaborate, and we welcome
|
||||
conversations about how to do things better.
|
||||
|
||||
5. Once all outstanding comments and checklist items have been addressed, your
|
||||
1. Once all outstanding comments and checklist items have been addressed, your
|
||||
contribution will be merged! Merged PRs will be included in the next
|
||||
Packer release. The core team takes care of updating the
|
||||
[CHANGELOG.md](../CHANGELOG.md) as they merge.
|
||||
|
||||
6. In rare cases, we might decide that a PR should be closed without merging.
|
||||
We'll make sure to provide clear reasoning when this happens.
|
||||
Packer release. The core team takes care of updating the CHANGELOG as they
|
||||
merge.
|
||||
|
||||
1. In rare cases, we might decide that a PR should be closed. We'll make sure to
|
||||
provide clear reasoning when this happens.
|
||||
|
||||
### Tips for Working on Packer
|
||||
|
||||
#### Getting Your Pull Requests Merged Faster
|
||||
|
||||
It is much easier to review pull requests that are:
|
||||
|
||||
1. Well-documented: Try to explain in the pull request comments what your
|
||||
change does, why you have made the change, and provide instructions for how
|
||||
to produce the new behavior introduced in the pull request. If you can,
|
||||
provide screen captures or terminal output to show what the changes look
|
||||
like. This helps the reviewers understand and test the change.
|
||||
|
||||
2. Small: Try to only make one change per pull request. If you found two bugs
|
||||
and want to fix them both, that's _awesome_, but it's still best to submit
|
||||
the fixes as separate pull requests. This makes it much easier for reviewers
|
||||
to keep in their heads all of the implications of individual code changes,
|
||||
and that means the PR takes less effort and energy to merge. In general, the
|
||||
smaller the pull request, the sooner reviewers will be able to make time to
|
||||
review it.
|
||||
|
||||
3. Passing Tests: Based on how much time we have, we may not review pull
|
||||
requests which aren't passing our tests. (Look below for advice on how to
|
||||
run unit tests). If you need help figuring out why tests are failing, please
|
||||
feel free to ask, but while we're happy to give guidance it is generally
|
||||
your responsibility to make sure that tests are passing. If your pull request
|
||||
changes an interface or invalidates an assumption that causes a bunch of
|
||||
tests to fail, then you need to fix those tests before we can merge your PR.
|
||||
|
||||
If we request changes, try to make those changes in a timely manner. Otherwise,
|
||||
PRs can go stale and be a lot more work for all of us to merge in the future.
|
||||
|
||||
Even with everyone making their best effort to be responsive, it can be
|
||||
time-consuming to get a PR merged. It can be frustrating to deal with
|
||||
the back-and-forth as we make sure that we understand the changes fully. Please
|
||||
bear with us, and please know that we appreciate the time and energy you put
|
||||
into the project.
|
||||
|
||||
#### Working on forks
|
||||
|
||||
The easiest way to work on a fork is to set it as a remote of the Packer
|
||||
project. After following the steps in "Setting up Go to work on Packer":
|
||||
|
||||
1. Navigate to the code:
|
||||
|
||||
`cd $GOPATH/src/github.com/hashicorp/packer`
|
||||
|
||||
2. Add the remote by running:
|
||||
|
||||
`git remote add <name of remote> <github url of fork>`
|
||||
|
||||
For example:
|
||||
|
||||
`git remote add mwhooker https://github.com/mwhooker/packer.git`
|
||||
|
||||
3. Checkout a feature branch:
|
||||
|
||||
`git checkout -b new-feature`
|
||||
|
||||
4. Make changes.
|
||||
1. Navigate to `$GOPATH/src/github.com/hashicorp/packer`
|
||||
2. Add the remote by running
|
||||
`git remote add <name of remote> <github url of fork>`. For example:
|
||||
`git remote add mwhooker https://github.com/mwhooker/packer.git`.
|
||||
3. Checkout a feature branch: `git checkout -b new-feature`
|
||||
4. Make changes
|
||||
5. (Optional) Push your changes to the fork:
|
||||
|
||||
`git push -u <name of remote> new-feature`
|
||||
|
||||
This way you can push to your fork to create a PR, but the code on disk still
|
||||
@@ -236,7 +180,7 @@ Packer has [acceptance tests](https://en.wikipedia.org/wiki/Acceptance_testing)
|
||||
for various builders. These typically require an API key (AWS, GCE), or
|
||||
additional software to be installed on your computer (VirtualBox, VMware).
|
||||
|
||||
If you're working on a new builder or builder feature and want to verify it is
|
||||
If you're working on a new builder or builder feature and want verify it is
|
||||
functioning (and also hasn't broken anything else), we recommend running the
|
||||
acceptance tests.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ _Please read these instructions before submitting_
|
||||
|
||||
**DELETE THIS TEMPLATE BEFORE SUBMITTING**
|
||||
|
||||
_Only use GitHub issues to report bugs or feature requests, see
|
||||
_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.
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
/src
|
||||
/website/.sass-cache
|
||||
/website/build
|
||||
/website/tmp
|
||||
.DS_Store
|
||||
.vagrant
|
||||
.idea
|
||||
|
||||
+4
-14
@@ -1,10 +1,5 @@
|
||||
env:
|
||||
- USER=travis
|
||||
|
||||
os:
|
||||
- windows
|
||||
- linux
|
||||
- osx
|
||||
- USER=travis
|
||||
|
||||
sudo: false
|
||||
|
||||
@@ -14,15 +9,11 @@ go:
|
||||
- 1.11.x
|
||||
- master
|
||||
|
||||
before_install:
|
||||
- >
|
||||
if [[ "$TRAVIS_OS_NAME" == "windows" ]];
|
||||
then choco install -y make;
|
||||
fi
|
||||
install:
|
||||
- make deps
|
||||
|
||||
script:
|
||||
- df -h
|
||||
- make ci
|
||||
- GOMAXPROCS=2 make ci
|
||||
|
||||
branches:
|
||||
only:
|
||||
@@ -31,5 +22,4 @@ branches:
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
- os: windows
|
||||
fast_finish: true
|
||||
|
||||
-134
@@ -1,142 +1,8 @@
|
||||
## 1.3.3 (December 5, 2018)
|
||||
### IMPROVEMENTS:
|
||||
* builder/alicloud: Add options for system disk properties [GH-6939]
|
||||
* builder/alicloud: Apply tags to relevant snapshots [GH-7040]
|
||||
* builder/alicloud: Support creating image without data disks [GH-7022]
|
||||
* builder/amazon: Add option for skipping TLS verification [GH-6842]
|
||||
* builder/azure: Add options for Managed Image OS Disk and Data Disk snapshots
|
||||
[GH-6980]
|
||||
* builder/hcloud: Add `snapshot_labels` option to hcloud builder [GH-7046]
|
||||
* builder/hcloud: Add ssh_keys config to hcloud builder [GH-7028]
|
||||
* builder/hcloud: Update hcloud-go version and support builds using rescue mode
|
||||
[GH-7034]
|
||||
* builder/oracle: Parameterized volume size support for Oracle classic builder
|
||||
[GH-6918]
|
||||
* builder/parallels: Add configuration options to parallels builder to specify
|
||||
cpu count and memory size [GH-7018]
|
||||
* builder/virtualbox: Add configuration options to virtualbox builder to
|
||||
specify cpu count and memory size [GH-7017]
|
||||
* builder/virtualbox: expose the VBoxManage export --iso option [GH-5950]
|
||||
* builder/vmware: Add configuration options to vmware builder to specify cpu
|
||||
count and memory size [GH-7019]
|
||||
* builder/vmware: Add new display_name template option [GH-6984]
|
||||
* builder/vmware: Extend vmware-vmx builder to allow esxi builds. [GH-4591]
|
||||
[GH-6927]
|
||||
* builder/vmware: Validate username/password for ovftool during prepare.
|
||||
[GH-6977]
|
||||
* builder/vmware: Warn users if their vmx_data overrides data that Packer uses
|
||||
the template engine to set in its default vmx template. [GH-6987]
|
||||
* communicator/ssh: Expand user path for SSH private key [GH-6946]
|
||||
* core: Add a sed template engine [GH-6580]
|
||||
* core: More explicit error message in rpc/ui.go [GH-6981]
|
||||
* core: Replaced unsafe method of determining homedir with os/user
|
||||
implementation [GH-7036]
|
||||
* core: Update vagrantfile's go version. [GH-6841]
|
||||
* post-processor/amazon-import: Support ova, raw, vmdk, and vhdx formats in the
|
||||
amazon-import post-processor. [GH-6938]
|
||||
* post-processor/vsphere-template: Add option to snapshot vm before marking as
|
||||
template [GH-6969]
|
||||
* provisioner/breakpoint: Add a new breakpoint provisioner. [GH-7058]
|
||||
* provisioner/powershell: Allow Powershell provisioner to use service accounts
|
||||
[GH-6972]
|
||||
* provisioner/shell: Add PauseAfter option to shell provisioner [GH-6913]
|
||||
|
||||
### BUG FIXES:
|
||||
* builder/amazon: Better error handling of region/credential guessing from
|
||||
metadata [GH-6931]
|
||||
* builder.amazon: move region validation to run so that we don't break
|
||||
validation when no credentials are set [GH-7032]
|
||||
* builder/hyperv: Remove -Copy:$false when calling Hyper-V\Compare-VM
|
||||
compatability report [GH-7030]
|
||||
* builder/qemu: Do not set detect-zeroes option when we want it "off" [GH-7064]
|
||||
* builder/vmware-esxi: Create export directories for vmx and ovf file types
|
||||
[GH-6985]
|
||||
* builder/vmware: Correctly parse version for VMware Fusion Tech Preview
|
||||
[GH-7016]
|
||||
* builder/vmware: Escape vSphere username when putting it into the export call
|
||||
[GH-6962]
|
||||
* post-processor/vagrant: Add "hvf" as a libvirt driver [GH-6955]
|
||||
* provisioner/ansible: inventory is no longer set to inventory_directory
|
||||
[GH-7065]
|
||||
|
||||
## 1.3.2 (October 29, 2018)
|
||||
### IMPROVEMENTS:
|
||||
* builder/alicloud: Add new `disable_stop_instance` option. [GH-6764]
|
||||
* builder/alicloud: Support adding tags to image. [GH-6719]
|
||||
* builder/alicloud: Support ssh with private ip address. [GH-6688]
|
||||
* builder/amazon: Add support to explicitly control ENA support [GH-6872]
|
||||
* builder/amazon: Add suppport for `vpc_filter`, `subnet_filter`, and
|
||||
`security_group_filter`. [GH-6374]
|
||||
* builder/amazon: Add validation for required `device_name` parameter in
|
||||
`block_device_mappings`. [GH-6845]
|
||||
* builder/amazon: Clean up security group wait code. [GH-6843]
|
||||
* builder/amazon: Update aws-sdk-go to v1.15.54, adding support for
|
||||
`credential_source`. [GH-6849]
|
||||
* builder/amazon: Use DescribeRegions for aws region validation. [GH-6512],
|
||||
[GH-6904]
|
||||
* builder/azure: Add new `shared_image_gallery` option. [GH-6798]
|
||||
* builder/googlecompute: Return an error if `startup_script_file` is specified,
|
||||
but file does not exist. [GH-6848]
|
||||
* builder/hcloud: Add Hetzner Cloud builder. [GH-6871]
|
||||
* builder/openstack: Add new `disk_format` option. [GH-6702]
|
||||
* builder/openstack: Fix bug where `source_image_name` wasn't being used to
|
||||
properly find a UUID. [GH-6751]
|
||||
* builder/openstack: Wait for volume availability when cleaning up [GH-6703]
|
||||
* builder/qemu: Add `disk_detect_zeroes` option. [GH-6827]
|
||||
* builder/scaleway: Add `boottype` parameter to config. [GH-6772]
|
||||
* builder/scaleway: Update scaleway-cli vendor. [GH-6771]
|
||||
* core: New option to add timestamps to UI output. [GH-6784]
|
||||
* post-processor/vagrant-cloud: Validate vagrant cloud auth token doing an auth
|
||||
request [GH-6914]
|
||||
* provisioner/file: Improve error messaging when file destination is a
|
||||
directory with no trailing slash. [GH-6756]
|
||||
* provisioner/powershell: Provide better error when Packer can't find
|
||||
Powershell executable. [GH-6817]
|
||||
* provisioner/shell-local: Add ability to specify OSs where shell-local can run
|
||||
[GH-6878]
|
||||
|
||||
### BUG FIXES:
|
||||
* builder/alicloud: Fix ssh configuration pointer issues that could cause a bug
|
||||
[GH-6720]
|
||||
* builder/alicloud: Fix type error in step_create_tags [GH-6763]
|
||||
* builder/amazon: Error validating credentials is no longer obscured by a
|
||||
region validation error. and some region validation refactors and
|
||||
improvements [GH-6865]
|
||||
* builder/amazon: Fix error calculating defaults in AWS waiters. [GH-6727]
|
||||
* builder/amazon: Increase default wait for image import to one hour. [GH-6818]
|
||||
* builder/amazon: Waiter now fails rather than hanging for extra time when an
|
||||
image import fails. [GH-6747]
|
||||
* builder/azure: Updated Azure/go-ntlmssp dependency to resolve an issue with
|
||||
the winrm communicator not connecting to Windows machines requiring NTLMv2
|
||||
session security
|
||||
* builder/digitalocean: Fix ssh configuration pointer issues that could cause a
|
||||
panic [GH-6729]
|
||||
* builder/hyperv/vmcx: Allow to set generation from buildfile [GH-6909]
|
||||
* builder/scaleway: Fix issues with ssh keys. [GH-6768]
|
||||
* core: Fix error where logging was always enabled when Packer was run from
|
||||
inside Terraform. [GH-6758]
|
||||
* core: Fix issue with with names containing spaces in ESX5Driver and in ssh
|
||||
communicator [GH-6891], [GH-6823]
|
||||
* core: Fix logger so it doesn't accidentally try to format unescaped strings.
|
||||
[GH-6824]
|
||||
* core: Fix race conditions in progress bar code [GH-6858], [GH-6788],
|
||||
[GH-6851]
|
||||
* core: Fix various places in multiple builders where config was not being
|
||||
passed as a pointer. [GH-6739]
|
||||
* post-processor/manifest: No longer provides an empty ID string for Azure's
|
||||
managed image artifact [GH-6822]
|
||||
* provisioner/powershell: Fix a bug in the way we set the ProgressPreference
|
||||
variable in the default `execute_command` [GH-6838]
|
||||
* provisioner/windows-restart: Fix extraneous break which forced early exit
|
||||
from our wait loop. [GH-6792]
|
||||
|
||||
## 1.3.1 (September 13, 2018)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
* builder/amazon: automatically decode encoded authorization messages if
|
||||
possible [GH-5415]
|
||||
* builder:amazon: Optional cleanup of the authorized keys file [GH-6713]
|
||||
* builder/qemu: Fixed bug where a -device in the qemuargs would override the default network settings, resulting in no network [GH-6807]
|
||||
|
||||
### BUG FIXES:
|
||||
* builder/amazon: fix bugs relating to spot instances provisioning [GH-6697]
|
||||
|
||||
+2
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
# builders
|
||||
|
||||
/builder/alicloud/ @chhaj5236
|
||||
/builder/alicloud/ dongxiao.zzh@alibaba-inc.com
|
||||
/builder/amazon/ebssurrogate/ @jen20
|
||||
/builder/amazon/ebsvolume/ @jen20
|
||||
/builder/azure/ @boumenot
|
||||
@@ -14,8 +14,7 @@
|
||||
/builder/profitbricks/ @jasmingacic
|
||||
/builder/triton/ @jen20 @sean-
|
||||
/builder/ncloud/ @YuSungDuk
|
||||
/builder/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
|
||||
/builder/hcloud/ @LKaemmerling
|
||||
/builder/scaleway/ @dimtion @edouardb
|
||||
|
||||
# provisioners
|
||||
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
FROM ubuntu:16.04
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
locales \
|
||||
openssh-server \
|
||||
sudo
|
||||
|
||||
RUN locale-gen en_US.UTF-8
|
||||
|
||||
RUN if ! getent passwd vagrant; then useradd -d /home/vagrant -m -s /bin/bash vagrant; fi \
|
||||
&& echo 'vagrant ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \
|
||||
&& mkdir -p /etc/sudoers.d \
|
||||
&& echo 'vagrant ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/vagrant \
|
||||
&& chmod 0440 /etc/sudoers.d/vagrant
|
||||
|
||||
RUN mkdir -p /home/vagrant/.ssh \
|
||||
&& chmod 0700 /home/vagrant/.ssh \
|
||||
&& wget --no-check-certificate \
|
||||
https://raw.github.com/hashicorp/vagrant/master/keys/vagrant.pub \
|
||||
-O /home/vagrant/.ssh/authorized_keys \
|
||||
&& chmod 0600 /home/vagrant/.ssh/authorized_keys \
|
||||
&& chown -R vagrant /home/vagrant/.ssh
|
||||
|
||||
RUN mkdir -p /run/sshd
|
||||
|
||||
CMD /usr/sbin/sshd -D \
|
||||
-o UseDNS=no \
|
||||
-o PidFile=/tmp/sshd.pid
|
||||
@@ -11,7 +11,7 @@ GOPATH=$(shell go env GOPATH)
|
||||
# gofmt
|
||||
UNFORMATTED_FILES=$(shell find . -not -path "./vendor/*" -name "*.go" | xargs gofmt -s -l)
|
||||
|
||||
EXECUTABLE_FILES=$(shell find . -type f -executable | egrep -v '^\./(website/[vendor|tmp]|vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats|\.git)' | egrep -v './provisioner/ansible/test-fixtures/exit1')
|
||||
EXECUTABLE_FILES=$(shell find . -type f -perm +111 | egrep -v '^\./(website/vendor|vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats|\.git)' | egrep -v './provisioner/ansible/test-fixtures/exit1')
|
||||
|
||||
# Get the git commit
|
||||
GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
||||
@@ -21,11 +21,9 @@ GOLDFLAGS=-X $(GIT_IMPORT).GitCommit=$(GIT_COMMIT)$(GIT_DIRTY)
|
||||
|
||||
export GOLDFLAGS
|
||||
|
||||
.PHONY: bin checkversion ci default deps fmt fmt-docs fmt-examples generate releasebin test testacc testrace updatedeps
|
||||
default: deps generate test dev
|
||||
|
||||
default: deps generate testrace dev releasebin package dev fmt fmt-check mode-check fmt-docs fmt-examples
|
||||
|
||||
ci: testrace
|
||||
ci: deps test
|
||||
|
||||
release: deps test releasebin package ## Build a release build
|
||||
|
||||
@@ -51,6 +49,7 @@ deps:
|
||||
@go get golang.org/x/tools/cmd/stringer
|
||||
@go get -u github.com/mna/pigeon
|
||||
@go get github.com/kardianos/govendor
|
||||
@go get golang.org/x/tools/cmd/goimports
|
||||
|
||||
dev: deps ## Build and install a development build
|
||||
@grep 'const VersionPrerelease = ""' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \
|
||||
@@ -101,25 +100,25 @@ generate: deps ## Generate dynamically generated code
|
||||
goimports -w common/bootcommand/boot_command.go
|
||||
gofmt -w command/plugin.go
|
||||
|
||||
test: fmt-check mode-check vet ## Run unit tests
|
||||
test: deps fmt-check mode-check ## Run unit tests
|
||||
@go test $(TEST) $(TESTARGS) -timeout=2m
|
||||
@go tool vet $(VET) ; if [ $$? -eq 1 ]; then \
|
||||
echo "ERROR: Vet found problems in the code."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# testacc runs acceptance tests
|
||||
testacc: deps generate ## Run acceptance tests
|
||||
@echo "WARN: Acceptance tests will take a long time to run and may cost money. Ctrl-C if you want to cancel."
|
||||
PACKER_ACC=1 go test -v $(TEST) $(TESTARGS) -timeout=45m
|
||||
|
||||
testrace: fmt-check mode-check vet ## Test with race detection enabled
|
||||
@go test -race $(TEST) $(TESTARGS) -timeout=2m -p=8
|
||||
testrace: deps ## Test for race conditions
|
||||
@go test -race $(TEST) $(TESTARGS) -timeout=2m
|
||||
|
||||
updatedeps:
|
||||
@echo "INFO: Packer deps are managed by govendor. See .github/CONTRIBUTING.md"
|
||||
|
||||
vet: ## Vet Go code
|
||||
@go tool vet $(VET) ; if [ $$? -eq 1 ]; then \
|
||||
echo "ERROR: Vet found problems in the code."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
help:
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
.PHONY: bin checkversion ci default deps fmt fmt-docs fmt-examples generate releasebin test testacc testrace updatedeps
|
||||
|
||||
@@ -80,7 +80,7 @@ Packer will build an AMI according to the "quick-start" template. The AMI
|
||||
will be available in your AWS account. To delete the AMI, you must manually
|
||||
delete it using the [AWS console](https://console.aws.amazon.com/). Packer
|
||||
builds your images, it does not manage their lifecycle. Where they go, how
|
||||
they're run, etc., is up to you.
|
||||
they're run, etc. is up to you.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
Vendored
+3
-9
@@ -5,9 +5,9 @@ LINUX_BASE_BOX = "bento/ubuntu-16.04"
|
||||
FREEBSD_BASE_BOX = "jen20/FreeBSD-12.0-CURRENT"
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
if Vagrant.has_plugin?("vagrant-cachier")
|
||||
config.cache.scope = :box
|
||||
end
|
||||
if Vagrant.has_plugin?("vagrant-cachier")
|
||||
config.cache.scope = :box
|
||||
end
|
||||
|
||||
# Compilation and development boxes
|
||||
config.vm.define "linux", autostart: true, primary: true do |vmCfg|
|
||||
@@ -73,12 +73,6 @@ def configureProviders(vmCfg, cpus: "2", memory: "2048")
|
||||
end
|
||||
end
|
||||
|
||||
vmCfg.vm.provider "docker" do |d, override|
|
||||
d.build_dir = "."
|
||||
d.has_ssh = true
|
||||
override.vm.box = nil
|
||||
end
|
||||
|
||||
return vmCfg
|
||||
end
|
||||
|
||||
|
||||
@@ -16,8 +16,55 @@ environment:
|
||||
clone_folder: c:\gopath\src\github.com\hashicorp\packer
|
||||
|
||||
install:
|
||||
- ps: |
|
||||
# Installs golang on Windows.
|
||||
#
|
||||
# # Run script:
|
||||
# .\install-go.ps1
|
||||
#
|
||||
# # Download and run script:
|
||||
# $env:GOVERSION = '1.5.3'
|
||||
# iex ((new-object net.webclient).DownloadString('SCRIPT_URL_HERE'))
|
||||
|
||||
$version = '1.11'
|
||||
|
||||
$downloadDir = $env:TEMP
|
||||
$packageName = 'golang'
|
||||
$url32 = 'https://storage.googleapis.com/golang/go' + $version + '.windows-386.zip'
|
||||
$url64 = 'https://storage.googleapis.com/golang/go' + $version + '.windows-amd64.zip'
|
||||
$goroot = "C:\go$version"
|
||||
|
||||
# Determine type of system
|
||||
if ($ENV:PROCESSOR_ARCHITECTURE -eq "AMD64") {
|
||||
$url = $url64
|
||||
} else {
|
||||
$url = $url32
|
||||
}
|
||||
|
||||
if (Test-Path "$goroot\bin\go") {
|
||||
Write-Host "Go is installed to $goroot"
|
||||
exit
|
||||
}
|
||||
|
||||
echo "Downloading $url"
|
||||
$zip = "$downloadDir\golang-$version.zip"
|
||||
if (!(Test-Path "$zip")) {
|
||||
$downloader = new-object System.Net.WebClient
|
||||
$downloader.DownloadFile($url, $zip)
|
||||
}
|
||||
|
||||
echo "Extracting $zip to $goroot"
|
||||
if (Test-Path "$downloadDir\go") {
|
||||
rm -Force -Recurse -Path "$downloadDir\go"
|
||||
}
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory("$zip", $downloadDir)
|
||||
mv "$downloadDir\go" $goroot
|
||||
- set GO15VENDOREXPERIMENT=1
|
||||
- set GOROOT=C:\go1.11
|
||||
- set Path=%GOROOT%\bin;%PATH%
|
||||
- echo %Path%
|
||||
- echo %GOROOT%
|
||||
- go version
|
||||
- go env
|
||||
- go get github.com/mitchellh/gox
|
||||
|
||||
@@ -68,6 +68,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AlicloudAccessKey, b.config.AlicloudSecretKey)
|
||||
log.Println(b.config)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -78,7 +79,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
return nil, err
|
||||
}
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("config", b.config)
|
||||
state.Put("client", client)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
@@ -138,12 +139,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
InternetChargeType: b.config.InternetChargeType,
|
||||
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
|
||||
SSHPrivateIp: b.config.SSHPrivateIp,
|
||||
})
|
||||
} else {
|
||||
steps = append(steps, &stepConfigAlicloudPublicIP{
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
SSHPrivateIp: b.config.SSHPrivateIp,
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
})
|
||||
}
|
||||
steps = append(steps,
|
||||
@@ -162,29 +161,14 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
},
|
||||
&stepStopAlicloudInstance{
|
||||
ForceStop: b.config.ForceStopInstance,
|
||||
DisableStop: b.config.DisableStopInstance,
|
||||
ForceStop: b.config.ForceStopInstance,
|
||||
},
|
||||
&stepDeleteAlicloudImageSnapshots{
|
||||
AlicloudImageForceDeleteSnapshots: b.config.AlicloudImageForceDeleteSnapshots,
|
||||
AlicloudImageForceDelete: b.config.AlicloudImageForceDelete,
|
||||
AlicloudImageName: b.config.AlicloudImageName,
|
||||
})
|
||||
|
||||
if b.config.AlicloudImageIgnoreDataDisks {
|
||||
steps = append(steps, &stepCreateAlicloudSnapshot{
|
||||
WaitSnapshotReadyTimeout: b.getSnapshotReadyTimeout(),
|
||||
})
|
||||
}
|
||||
|
||||
steps = append(steps,
|
||||
&stepCreateAlicloudImage{
|
||||
AlicloudImageIgnoreDataDisks: b.config.AlicloudImageIgnoreDataDisks,
|
||||
WaitSnapshotReadyTimeout: b.getSnapshotReadyTimeout(),
|
||||
},
|
||||
&stepCreateTags{
|
||||
Tags: b.config.AlicloudImageTags,
|
||||
},
|
||||
&stepCreateAlicloudImage{},
|
||||
&stepRegionCopyAlicloudImage{
|
||||
AlicloudImageDestinationRegions: b.config.AlicloudImageDestinationRegions,
|
||||
AlicloudImageDestinationNames: b.config.AlicloudImageDestinationNames,
|
||||
@@ -256,11 +240,3 @@ func (b *Builder) isUserDataNeeded() bool {
|
||||
func (b *Builder) isKeyPairNeeded() bool {
|
||||
return b.config.Comm.SSHKeyPairName != "" || b.config.Comm.SSHTemporaryKeyPairName != ""
|
||||
}
|
||||
|
||||
func (b *Builder) getSnapshotReadyTimeout() int {
|
||||
if b.config.WaitSnapshotReadyTimeout > 0 {
|
||||
return b.config.WaitSnapshotReadyTimeout
|
||||
}
|
||||
|
||||
return ALICLOUD_DEFAULT_LONG_TIMEOUT
|
||||
}
|
||||
|
||||
@@ -113,17 +113,6 @@ func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_imageTags(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() {
|
||||
testAccPreCheck(t)
|
||||
},
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccImageTags,
|
||||
Check: checkImageTags(),
|
||||
})
|
||||
}
|
||||
|
||||
func checkSnapshotsDeleted(snapshotIds []string) builderT.TestCheckFunc {
|
||||
return func(artifacts []packer.Artifact) error {
|
||||
// Verify the snapshots are gone
|
||||
@@ -218,51 +207,6 @@ func checkRegionCopy(regions []string) builderT.TestCheckFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func checkImageTags() builderT.TestCheckFunc {
|
||||
return func(artifacts []packer.Artifact) error {
|
||||
if len(artifacts) > 1 {
|
||||
return fmt.Errorf("more than 1 artifact")
|
||||
}
|
||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
||||
artifactRaw := artifacts[0]
|
||||
artifact, ok := artifactRaw.(*Artifact)
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
||||
}
|
||||
// describe the image, get block devices with a snapshot
|
||||
client, _ := testAliyunClient()
|
||||
tags, _, err := client.DescribeTags(
|
||||
&ecs.DescribeTagsArgs{
|
||||
RegionId: "cn-beijing",
|
||||
ResourceType: ecs.TagResourceImage,
|
||||
ResourceId: artifact.AlicloudImages["cn-beijing"],
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error retrieving Image Attributes for ECS Image Artifact (%#v) "+
|
||||
"in ECS Image Tags Test: %s", artifact, err)
|
||||
}
|
||||
failed := false
|
||||
if len(tags) != 2 {
|
||||
failed = true
|
||||
}
|
||||
if !failed {
|
||||
for i := 0; i < len(tags); i++ {
|
||||
if tags[i].TagKey == "TagKey1" && tags[i].TagValue != "TagValue1" {
|
||||
failed = true
|
||||
} else if tags[i].TagKey == "TagKey2" && tags[i].TagValue != "TagValue2" {
|
||||
failed = true
|
||||
} else if tags[i].TagKey != "TagKey1" && tags[i].TagKey != "TagKey2" {
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
return fmt.Errorf("tags is not correctly set %#v", tags)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
if v := os.Getenv("ALICLOUD_ACCESS_KEY"); v == "" {
|
||||
t.Fatal("ALICLOUD_ACCESS_KEY must be set for acceptance tests")
|
||||
@@ -292,7 +236,8 @@ const testBuilderAccBasic = `
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_20G_alibase_20180409.vhd",
|
||||
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
|
||||
"ssh_username": "ubuntu",
|
||||
"io_optimized":"true",
|
||||
"ssh_username":"root",
|
||||
"image_name": "packer-test_{{timestamp}}"
|
||||
@@ -361,22 +306,6 @@ const testBuilderAccSharing = `
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccImageTags = `
|
||||
{ "builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_20G_alibase_20180409.vhd",
|
||||
"ssh_username": "root",
|
||||
"io_optimized":"true",
|
||||
"image_name": "packer-test_{{timestamp}}",
|
||||
"tags": {
|
||||
"TagKey1": "TagValue1",
|
||||
"TagKey2": "TagValue2"
|
||||
}
|
||||
}]
|
||||
}`
|
||||
|
||||
func buildForceDeregisterConfig(val, name string) string {
|
||||
return fmt.Sprintf(testBuilderAccForceDelete, val, name)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -94,141 +93,3 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_Devices(t *testing.T) {
|
||||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
config["system_disk_mapping"] = map[string]interface{}{
|
||||
"disk_category": "cloud",
|
||||
"disk_description": "system disk",
|
||||
"disk_name": "system_disk",
|
||||
"disk_size": 60,
|
||||
}
|
||||
config["image_disk_mappings"] = []map[string]interface{}{
|
||||
{
|
||||
"disk_category": "cloud_efficiency",
|
||||
"disk_name": "data_disk1",
|
||||
"disk_size": 100,
|
||||
"disk_snapshot_id": "s-1",
|
||||
"disk_description": "data disk1",
|
||||
"disk_device": "/dev/xvdb",
|
||||
"disk_delete_with_instance": false,
|
||||
},
|
||||
{
|
||||
"disk_name": "data_disk2",
|
||||
"disk_device": "/dev/xvdc",
|
||||
},
|
||||
}
|
||||
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{
|
||||
DiskCategory: "cloud",
|
||||
Description: "system disk",
|
||||
DiskName: "system_disk",
|
||||
DiskSize: 60,
|
||||
}) {
|
||||
t.Fatalf("system disk is not set properly, actual: %#v", b.config.ECSSystemDiskMapping)
|
||||
}
|
||||
if !reflect.DeepEqual(b.config.ECSImagesDiskMappings, []AlicloudDiskDevice{
|
||||
{
|
||||
DiskCategory: "cloud_efficiency",
|
||||
DiskName: "data_disk1",
|
||||
DiskSize: 100,
|
||||
SnapshotId: "s-1",
|
||||
Description: "data disk1",
|
||||
Device: "/dev/xvdb",
|
||||
DeleteWithInstance: false,
|
||||
},
|
||||
{
|
||||
DiskName: "data_disk2",
|
||||
Device: "/dev/xvdc",
|
||||
},
|
||||
}) {
|
||||
t.Fatalf("data disks are not set properly, actual: %#v", b.config.ECSImagesDiskMappings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_IgnoreDataDisks(t *testing.T) {
|
||||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AlicloudImageIgnoreDataDisks != false {
|
||||
t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", false, b.config.AlicloudImageIgnoreDataDisks)
|
||||
}
|
||||
|
||||
config["image_ignore_data_disks"] = "false"
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AlicloudImageIgnoreDataDisks != false {
|
||||
t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", false, b.config.AlicloudImageIgnoreDataDisks)
|
||||
}
|
||||
|
||||
config["image_ignore_data_disks"] = "true"
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AlicloudImageIgnoreDataDisks != true {
|
||||
t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", true, b.config.AlicloudImageIgnoreDataDisks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_WaitSnapshotReadyTimeout(t *testing.T) {
|
||||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.WaitSnapshotReadyTimeout != 0 {
|
||||
t.Fatalf("wait_snapshot_ready_timeout is not set properly, expect: %d, actual: %d", 0, b.config.WaitSnapshotReadyTimeout)
|
||||
}
|
||||
if b.getSnapshotReadyTimeout() != ALICLOUD_DEFAULT_LONG_TIMEOUT {
|
||||
t.Fatalf("default timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_LONG_TIMEOUT, b.getSnapshotReadyTimeout())
|
||||
}
|
||||
|
||||
config["wait_snapshot_ready_timeout"] = ALICLOUD_DEFAULT_TIMEOUT
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.WaitSnapshotReadyTimeout != ALICLOUD_DEFAULT_TIMEOUT {
|
||||
t.Fatalf("wait_snapshot_ready_timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_TIMEOUT, b.config.WaitSnapshotReadyTimeout)
|
||||
}
|
||||
|
||||
if b.getSnapshotReadyTimeout() != ALICLOUD_DEFAULT_TIMEOUT {
|
||||
t.Fatalf("default timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_TIMEOUT, b.getSnapshotReadyTimeout())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,24 +21,21 @@ type AlicloudDiskDevice struct {
|
||||
}
|
||||
|
||||
type AlicloudDiskDevices struct {
|
||||
ECSSystemDiskMapping AlicloudDiskDevice `mapstructure:"system_disk_mapping"`
|
||||
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings"`
|
||||
}
|
||||
|
||||
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"`
|
||||
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"`
|
||||
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"`
|
||||
AlicloudImageForceDelete bool `mapstructure:"image_force_delete"`
|
||||
AlicloudImageForceDeleteSnapshots bool `mapstructure:"image_force_delete_snapshots"`
|
||||
AlicloudImageForceDeleteInstances bool `mapstructure:"image_force_delete_instances"`
|
||||
AlicloudImageSkipRegionValidation bool `mapstructure:"skip_region_validation"`
|
||||
AlicloudDiskDevices `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
|
||||
@@ -55,24 +55,6 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestECSImageConfigPrepare_imageTags(t *testing.T) {
|
||||
c := testAlicloudImageConfig()
|
||||
c.AlicloudImageTags = map[string]string{
|
||||
"TagKey1": "TagValue1",
|
||||
"TagKey2": "TagValue2",
|
||||
}
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(c.AlicloudImageTags) != 2 || c.AlicloudImageTags["TagKey1"] != "TagValue1" ||
|
||||
c.AlicloudImageTags["TagKey2"] != "TagValue2" {
|
||||
t.Fatalf("invalid value, expected: %s, actual: %s", map[string]string{
|
||||
"TagKey1": "TagValue1",
|
||||
"TagKey2": "TagValue2",
|
||||
}, c.AlicloudImageTags)
|
||||
}
|
||||
}
|
||||
|
||||
func regionsToString() []string {
|
||||
var regions []string
|
||||
for _, region := range common.ValidRegions {
|
||||
|
||||
@@ -20,12 +20,3 @@ func message(state multistep.StateBag, module string) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func halt(state multistep.StateBag, err error, prefix string) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
err = fmt.Errorf("%s: %s", prefix, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ type RunConfig struct {
|
||||
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"`
|
||||
@@ -32,7 +31,6 @@ type RunConfig struct {
|
||||
InstanceName string `mapstructure:"instance_name"`
|
||||
InternetChargeType string `mapstructure:"internet_charge_type"`
|
||||
InternetMaxBandwidthOut int `mapstructure:"internet_max_bandwidth_out"`
|
||||
WaitSnapshotReadyTimeout int `mapstructure:"wait_snapshot_ready_timeout"`
|
||||
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
@@ -123,54 +123,3 @@ func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
|
||||
t.Fatal("keypair name does not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SSHPrivateIp(t *testing.T) {
|
||||
c := testConfig()
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c.SSHPrivateIp != false {
|
||||
t.Fatalf("invalid value, expected: %t, actul: %t", false, c.SSHPrivateIp)
|
||||
}
|
||||
c.SSHPrivateIp = true
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c.SSHPrivateIp != true {
|
||||
t.Fatalf("invalid value, expected: %t, actul: %t", true, c.SSHPrivateIp)
|
||||
}
|
||||
c.SSHPrivateIp = false
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c.SSHPrivateIp != false {
|
||||
t.Fatalf("invalid value, expected: %t, actul: %t", false, c.SSHPrivateIp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_DisableStopInstance(t *testing.T) {
|
||||
c := testConfig()
|
||||
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c.DisableStopInstance != false {
|
||||
t.Fatalf("invalid value, expected: %t, actul: %t", false, c.DisableStopInstance)
|
||||
}
|
||||
|
||||
c.DisableStopInstance = true
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c.DisableStopInstance != true {
|
||||
t.Fatalf("invalid value, expected: %t, actul: %t", true, c.DisableStopInstance)
|
||||
}
|
||||
|
||||
c.DisableStopInstance = false
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c.DisableStopInstance != false {
|
||||
t.Fatalf("invalid value, expected: %t, actul: %t", false, c.DisableStopInstance)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ type stepAttachKeyPair struct {
|
||||
func (s *stepAttachKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
timeoutPoint := time.Now().Add(120 * time.Second)
|
||||
keyPairName := config.Comm.SSHKeyPairName
|
||||
@@ -52,7 +52,7 @@ func (s *stepAttachKeyPair) Run(_ context.Context, state multistep.StateBag) mul
|
||||
|
||||
func (s *stepAttachKeyPair) Cleanup(state multistep.StateBag) {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
keyPairName := config.Comm.SSHKeyPairName
|
||||
|
||||
@@ -16,7 +16,7 @@ type stepCheckAlicloudSourceImage struct {
|
||||
|
||||
func (s *stepCheckAlicloudSourceImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
args := &ecs.DescribeImagesArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
|
||||
@@ -16,24 +16,12 @@ type stepConfigAlicloudEIP struct {
|
||||
InternetChargeType string
|
||||
InternetMaxBandwidthOut int
|
||||
allocatedId string
|
||||
SSHPrivateIp bool
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudEIP) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
|
||||
if s.SSHPrivateIp {
|
||||
ipaddress := instance.VpcAttributes.PrivateIpAddress.IpAddress
|
||||
if len(ipaddress) == 0 {
|
||||
ui.Say("Failed to get private ip of instance")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("ipaddress", ipaddress[0])
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say("Allocating eip")
|
||||
ipaddress, allocateId, err := client.AllocateEipAddress(&ecs.AllocateEipAddressArgs{
|
||||
RegionId: common.Region(s.RegionId), InternetChargeType: common.InternetChargeType(s.InternetChargeType),
|
||||
|
||||
@@ -3,6 +3,7 @@ package ecs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
@@ -27,9 +28,10 @@ func (s *stepConfigAlicloudKeyPair) Run(_ context.Context, state multistep.State
|
||||
|
||||
if s.Comm.SSHPrivateKeyFile != "" {
|
||||
ui.Say("Using existing SSH private key")
|
||||
privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile()
|
||||
privateKeyBytes, err := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
state.Put("error", fmt.Errorf(
|
||||
"Error loading configured private key file: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
type stepConfigAlicloudPublicIP struct {
|
||||
publicIPAddress string
|
||||
RegionId string
|
||||
SSHPrivateIp bool
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudPublicIP) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
@@ -20,16 +19,6 @@ func (s *stepConfigAlicloudPublicIP) Run(_ context.Context, state multistep.Stat
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
|
||||
if s.SSHPrivateIp {
|
||||
ipaddress := instance.InnerIpAddress.IpAddress
|
||||
if len(ipaddress) == 0 {
|
||||
ui.Say("Failed to get private ip of instance")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("ipaddress", ipaddress[0])
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ipaddress, err := client.AllocatePublicIpAddress(instance.InstanceId)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
|
||||
@@ -20,7 +20,7 @@ type stepConfigAlicloudVPC struct {
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudVPC) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ func (s *stepConfigAlicloudVSwitch) Run(_ context.Context, state multistep.State
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vpcId := state.Get("vpcid").(string)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
|
||||
if len(s.VSwitchId) != 0 {
|
||||
vswitchs, _, err := client.DescribeVSwitches(&ecs.DescribeVSwitchesArgs{
|
||||
|
||||
@@ -11,13 +11,11 @@ import (
|
||||
)
|
||||
|
||||
type stepCreateAlicloudImage struct {
|
||||
AlicloudImageIgnoreDataDisks bool
|
||||
WaitSnapshotReadyTimeout int
|
||||
image *ecs.ImageType
|
||||
image *ecs.ImageType
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
@@ -26,53 +24,48 @@ func (s *stepCreateAlicloudImage) Run(_ context.Context, state multistep.StateBa
|
||||
var imageId string
|
||||
var err error
|
||||
|
||||
if s.AlicloudImageIgnoreDataDisks {
|
||||
snapshotId := state.Get("alicloudsnapshot").(string)
|
||||
imageId, err = client.CreateImage(&ecs.CreateImageArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
SnapshotId: snapshotId,
|
||||
ImageName: config.AlicloudImageName,
|
||||
ImageVersion: config.AlicloudImageVersion,
|
||||
Description: config.AlicloudImageDescription})
|
||||
} else {
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
imageId, err = client.CreateImage(&ecs.CreateImageArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
InstanceId: instance.InstanceId,
|
||||
ImageName: config.AlicloudImageName,
|
||||
ImageVersion: config.AlicloudImageVersion,
|
||||
Description: config.AlicloudImageDescription})
|
||||
}
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
imageId, err = client.CreateImage(&ecs.CreateImageArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
InstanceId: instance.InstanceId,
|
||||
ImageName: config.AlicloudImageName,
|
||||
ImageVersion: config.AlicloudImageVersion,
|
||||
Description: config.AlicloudImageDescription})
|
||||
|
||||
if err != nil {
|
||||
return halt(state, err, "Error creating image")
|
||||
err := fmt.Errorf("Error creating image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
err = client.WaitForImageReady(common.Region(config.AlicloudRegion), imageId, s.WaitSnapshotReadyTimeout)
|
||||
err = client.WaitForImageReady(common.Region(config.AlicloudRegion),
|
||||
imageId, ALICLOUD_DEFAULT_LONG_TIMEOUT)
|
||||
if err != nil {
|
||||
return halt(state, err, "Timeout waiting for image to be created")
|
||||
err := fmt.Errorf("Timeout waiting for image to be created: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
ImageId: imageId})
|
||||
if err != nil {
|
||||
return halt(state, err, "Error querying created imaged")
|
||||
err := fmt.Errorf("Error querying created image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(images) == 0 {
|
||||
return halt(state, err, "Unable to find created image")
|
||||
err := fmt.Errorf("Unable to find created image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.image = &images[0]
|
||||
|
||||
var snapshotIds = []string{}
|
||||
for _, device := range images[0].DiskDeviceMappings.DiskDeviceMapping {
|
||||
snapshotIds = append(snapshotIds, device.SnapshotId)
|
||||
}
|
||||
|
||||
state.Put("alicloudimage", imageId)
|
||||
state.Put("alicloudsnapshots", snapshotIds)
|
||||
|
||||
alicloudImages := make(map[string]string)
|
||||
alicloudImages[config.AlicloudRegion] = images[0].ImageId
|
||||
state.Put("alicloudimages", alicloudImages)
|
||||
@@ -92,7 +85,7 @@ func (s *stepCreateAlicloudImage) Cleanup(state multistep.StateBag) {
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
|
||||
ui.Say("Deleting the image because of cancellation or error...")
|
||||
if err := client.DeleteImage(common.Region(config.AlicloudRegion), s.image.ImageId); err != nil {
|
||||
|
||||
@@ -28,7 +28,7 @@ type stepCreateAlicloudInstance struct {
|
||||
|
||||
func (s *stepCreateAlicloudInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
source_image := state.Get("source_image").(*ecs.ImageType)
|
||||
network_type := state.Get("networktype").(InstanceNetWork)
|
||||
@@ -66,7 +66,6 @@ func (s *stepCreateAlicloudInstance) Run(_ context.Context, state multistep.Stat
|
||||
InstanceName: s.InstanceName,
|
||||
Password: password,
|
||||
ZoneId: s.ZoneId,
|
||||
SystemDisk: systemDeviceToDiskType(config.AlicloudImageConfig.ECSSystemDiskMapping),
|
||||
DataDisk: diskDeviceToDiskType(config.AlicloudImageConfig.ECSImagesDiskMappings),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -148,15 +147,6 @@ func (s *stepCreateAlicloudInstance) getUserData(state multistep.StateBag) (stri
|
||||
|
||||
}
|
||||
|
||||
func systemDeviceToDiskType(systemDisk AlicloudDiskDevice) ecs.SystemDiskType {
|
||||
return ecs.SystemDiskType{
|
||||
DiskName: systemDisk.DiskName,
|
||||
Category: ecs.DiskCategory(systemDisk.DiskCategory),
|
||||
Size: systemDisk.DiskSize,
|
||||
Description: systemDisk.Description,
|
||||
}
|
||||
}
|
||||
|
||||
func diskDeviceToDiskType(diskDevices []AlicloudDiskDevice) []ecs.DataDiskType {
|
||||
result := make([]ecs.DataDiskType, len(diskDevices))
|
||||
for _, diskDevice := range diskDevices {
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepCreateAlicloudSnapshot struct {
|
||||
snapshot *ecs.SnapshotType
|
||||
WaitSnapshotReadyTimeout int
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
disks, _, err := client.DescribeDisks(&ecs.DescribeDisksArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
InstanceId: instance.InstanceId,
|
||||
DiskType: ecs.DiskTypeAllSystem,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return halt(state, err, "Error describe disks")
|
||||
}
|
||||
if len(disks) == 0 {
|
||||
return halt(state, err, "Unable to find system disk of instance")
|
||||
}
|
||||
|
||||
// Create the alicloud snapshot
|
||||
ui.Say(fmt.Sprintf("Creating snapshot from system disk: %s", disks[0].DiskId))
|
||||
|
||||
snapshotId, err := client.CreateSnapshot(&ecs.CreateSnapshotArgs{
|
||||
DiskId: disks[0].DiskId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return halt(state, err, "Error creating snapshot")
|
||||
}
|
||||
|
||||
err = client.WaitForSnapShotReady(common.Region(config.AlicloudRegion), snapshotId, s.WaitSnapshotReadyTimeout)
|
||||
if err != nil {
|
||||
return halt(state, err, "Timeout waiting for snapshot to be created")
|
||||
}
|
||||
|
||||
snapshots, _, err := client.DescribeSnapshots(&ecs.DescribeSnapshotsArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
SnapshotIds: []string{snapshotId},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return halt(state, err, "Error querying created snapshot")
|
||||
}
|
||||
if len(snapshots) == 0 {
|
||||
return halt(state, err, "Unable to find created snapshot")
|
||||
}
|
||||
s.snapshot = &snapshots[0]
|
||||
state.Put("alicloudsnapshot", snapshotId)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudSnapshot) Cleanup(state multistep.StateBag) {
|
||||
if s.snapshot == nil {
|
||||
return
|
||||
}
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if !cancelled && !halted {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Deleting the snapshot because of cancellation or error...")
|
||||
if err := client.DeleteSnapshot(s.snapshot.SnapshotId); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting snapshot, it may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepCreateTags struct {
|
||||
Tags map[string]string
|
||||
}
|
||||
|
||||
func (s *stepCreateTags) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
imageId := state.Get("alicloudimage").(string)
|
||||
snapshotIds := state.Get("alicloudsnapshots").([]string)
|
||||
|
||||
if len(s.Tags) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Adding tags(%s) to image: %s", s.Tags, imageId))
|
||||
err := client.AddTags(&ecs.AddTagsArgs{
|
||||
ResourceId: imageId,
|
||||
ResourceType: ecs.TagResourceImage,
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
Tag: s.Tags,
|
||||
})
|
||||
if err != nil {
|
||||
return halt(state, err, "Error Adding tags to image")
|
||||
}
|
||||
|
||||
for _, snapshotId := range snapshotIds {
|
||||
ui.Say(fmt.Sprintf("Adding tags(%s) to snapshot: %s", s.Tags, snapshotId))
|
||||
err = client.AddTags(&ecs.AddTagsArgs{
|
||||
ResourceId: snapshotId,
|
||||
ResourceType: ecs.TagResourceSnapshot,
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
Tag: s.Tags,
|
||||
})
|
||||
if err != nil {
|
||||
return halt(state, err, "Error Adding tags to snapshot")
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
func (s *stepCreateTags) Cleanup(state multistep.StateBag) {
|
||||
// Nothing need to do, tags will be cleaned when the resource is cleaned
|
||||
}
|
||||
@@ -20,8 +20,8 @@ type stepDeleteAlicloudImageSnapshots struct {
|
||||
func (s *stepDeleteAlicloudImageSnapshots) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
config := state.Get("config").(*Config)
|
||||
|
||||
config := state.Get("config").(Config)
|
||||
ui.Say("Deleting image snapshots.")
|
||||
// Check for force delete
|
||||
if s.AlicloudImageForceDelete {
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
@@ -31,9 +31,6 @@ func (s *stepDeleteAlicloudImageSnapshots) Run(_ context.Context, state multiste
|
||||
if len(images) < 1 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Deleting duplicated image and snapshot: %s", s.AlicloudImageName))
|
||||
|
||||
for _, image := range images {
|
||||
if image.ImageOwnerAlias != string(ecs.ImageOwnerSelf) {
|
||||
log.Printf("You can only delete instances based on customized images %s ", image.ImageId)
|
||||
|
||||
@@ -14,7 +14,7 @@ type stepMountAlicloudDisk struct {
|
||||
|
||||
func (s *stepMountAlicloudDisk) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
alicloudDiskDevices := config.ECSImagesDiskMappings
|
||||
|
||||
@@ -23,7 +23,7 @@ func (s *stepPreValidate) Run(_ context.Context, state multistep.StateBag) multi
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
ui.Say("Prevalidating image name...")
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
ImageName: s.AlicloudDestImageName,
|
||||
|
||||
@@ -24,9 +24,7 @@ func (s *stepRunAlicloudInstance) Run(_ context.Context, state multistep.StateBa
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Starting instance: %s", instance.InstanceId))
|
||||
|
||||
ui.Say("Starting instance.")
|
||||
err = client.WaitForInstance(instance.InstanceId, ecs.Running, ALICLOUD_DEFAULT_TIMEOUT)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Timeout waiting for instance to start: %s", err)
|
||||
|
||||
@@ -10,8 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type stepStopAlicloudInstance struct {
|
||||
ForceStop bool
|
||||
DisableStop bool
|
||||
ForceStop bool
|
||||
}
|
||||
|
||||
func (s *stepStopAlicloudInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
@@ -19,9 +18,8 @@ func (s *stepStopAlicloudInstance) Run(_ context.Context, state multistep.StateB
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if !s.DisableStop {
|
||||
ui.Say(fmt.Sprintf("Stopping instance: %s", instance.InstanceId))
|
||||
err := client.StopInstance(instance.InstanceId, s.ForceStop)
|
||||
err := client.StopInstance(instance.InstanceId, s.ForceStop)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error stopping alicloud instance: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -30,9 +28,7 @@ func (s *stepStopAlicloudInstance) Run(_ context.Context, state multistep.StateB
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Waiting instance stopped: %s", instance.InstanceId))
|
||||
|
||||
err := client.WaitForInstance(instance.InstanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT)
|
||||
err = client.WaitForInstance(instance.InstanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for alicloud instance to stop: %s", err)
|
||||
state.Put("error", err)
|
||||
|
||||
@@ -178,6 +178,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
log.Println(b.config)
|
||||
return warns, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ func testConfig() map[string]interface{} {
|
||||
"ami_name": "foo",
|
||||
"source_ami": "foo",
|
||||
"region": "us-east-1",
|
||||
// region validation logic is checked in ami_config_test
|
||||
"skip_region_validation": true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +28,6 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
|
||||
// Test good
|
||||
config["ami_name"] = "foo"
|
||||
config["skip_region_validation"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// StepRegisterAMI creates the AMI.
|
||||
type StepRegisterAMI struct {
|
||||
RootVolumeSize int64
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMIENASupport bool
|
||||
EnableAMISriovNetSupport bool
|
||||
}
|
||||
|
||||
@@ -83,7 +83,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 {
|
||||
// Set EnaSupport to true
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
registerOpts.EnaSupport = aws.Bool(true)
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -13,28 +11,23 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
getEC2Connection func() ec2iface.EC2API
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2"`
|
||||
DecodeAuthZMessages bool `mapstructure:"decode_authorization_messages"`
|
||||
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
|
||||
}
|
||||
|
||||
// Config returns a valid aws.Config object for access to AWS services, or
|
||||
@@ -45,34 +38,25 @@ func (c *AccessConfig) Session() (*session.Session, error) {
|
||||
}
|
||||
|
||||
config := aws.NewConfig().WithCredentialsChainVerboseErrors(true)
|
||||
|
||||
staticCreds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
|
||||
if _, err := staticCreds.Get(); err != credentials.ErrStaticCredentialsEmpty {
|
||||
config.WithCredentials(staticCreds)
|
||||
}
|
||||
|
||||
// default is 3, and when it was causing failures for users being throttled
|
||||
// retries are exponentially backed off.
|
||||
config = config.WithMaxRetries(8)
|
||||
config = config.WithMaxRetries(20)
|
||||
|
||||
region, err := c.region()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not get region, "+
|
||||
"probably because it's not set or we're not running on AWS. %s", err)
|
||||
if c.RawRegion != "" {
|
||||
config = config.WithRegion(c.RawRegion)
|
||||
} else if region := c.metadataRegion(); region != "" {
|
||||
config = config.WithRegion(region)
|
||||
}
|
||||
config = config.WithRegion(region)
|
||||
|
||||
if c.CustomEndpointEc2 != "" {
|
||||
config = config.WithEndpoint(c.CustomEndpointEc2)
|
||||
}
|
||||
|
||||
if c.InsecureSkipTLSVerify {
|
||||
config := config.WithHTTPClient(cleanhttp.DefaultClient())
|
||||
transport := config.HTTPClient.Transport.(*http.Transport)
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
|
||||
opts := session.Options{
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
Config: *config,
|
||||
@@ -90,6 +74,8 @@ func (c *AccessConfig) Session() (*session.Session, error) {
|
||||
|
||||
if sess, err := session.NewSessionWithOptions(opts); err != nil {
|
||||
return nil, err
|
||||
} else if *sess.Config.Region == "" {
|
||||
return nil, fmt.Errorf("Could not find AWS region, make sure it's set.")
|
||||
} else {
|
||||
log.Printf("Found region %s", *sess.Config.Region)
|
||||
c.session = sess
|
||||
@@ -130,7 +116,7 @@ func (c *AccessConfig) IsChinaCloud() bool {
|
||||
}
|
||||
|
||||
// metadataRegion returns the region from the metadata service
|
||||
func (c *AccessConfig) metadataRegion() (string, error) {
|
||||
func (c *AccessConfig) metadataRegion() string {
|
||||
|
||||
client := cleanhttp.DefaultClient()
|
||||
|
||||
@@ -139,14 +125,13 @@ func (c *AccessConfig) metadataRegion() (string, error) {
|
||||
ec2meta := ec2metadata.New(session.New(), &aws.Config{
|
||||
HTTPClient: client,
|
||||
})
|
||||
return ec2meta.Region()
|
||||
}
|
||||
|
||||
func (c *AccessConfig) region() (string, error) {
|
||||
if c.RawRegion != "" {
|
||||
return c.RawRegion, nil
|
||||
region, err := ec2meta.Region()
|
||||
if err != nil {
|
||||
log.Println("Error getting region from metadata service, "+
|
||||
"probably because we're not running on AWS.", err)
|
||||
return ""
|
||||
}
|
||||
return c.metadataRegion()
|
||||
return region
|
||||
}
|
||||
|
||||
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
@@ -162,16 +147,11 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
fmt.Errorf("`access_key` and `secret_key` must both be either set or not set."))
|
||||
}
|
||||
|
||||
if c.RawRegion != "" && !c.SkipValidation {
|
||||
if valid := ValidateRegion(c.RawRegion); !valid {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", c.RawRegion))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (c *AccessConfig) NewEC2Connection() (ec2iface.EC2API, error) {
|
||||
if c.getEC2Connection != nil {
|
||||
return c.getEC2Connection(), nil
|
||||
}
|
||||
sess, err := c.Session()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ec2.New(sess), nil
|
||||
}
|
||||
|
||||
@@ -5,51 +5,41 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
)
|
||||
|
||||
func testAccessConfig() *AccessConfig {
|
||||
return &AccessConfig{
|
||||
getEC2Connection: func() ec2iface.EC2API {
|
||||
return &mockEC2Client{}
|
||||
},
|
||||
}
|
||||
return &AccessConfig{}
|
||||
}
|
||||
|
||||
func TestAccessConfigPrepare_Region(t *testing.T) {
|
||||
c := testAccessConfig()
|
||||
|
||||
c.RawRegion = "us-east-12"
|
||||
err := c.ValidateRegion(c.RawRegion)
|
||||
if err == nil {
|
||||
t.Fatalf("should have region validation err: %s", c.RawRegion)
|
||||
}
|
||||
|
||||
c.RawRegion = "us-east-1"
|
||||
err = c.ValidateRegion(c.RawRegion)
|
||||
if err != nil {
|
||||
t.Fatalf("shouldn't have region validation err: %s", c.RawRegion)
|
||||
}
|
||||
|
||||
c.RawRegion = "custom"
|
||||
err = c.ValidateRegion(c.RawRegion)
|
||||
if err == nil {
|
||||
t.Fatalf("should have region validation err: %s", c.RawRegion)
|
||||
}
|
||||
|
||||
c.RawRegion = "custom"
|
||||
c.SkipValidation = true
|
||||
// testing whole prepare func here; this is checking that validation is
|
||||
// skipped, so we don't need a mock connection
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.SkipValidation = false
|
||||
c.RawRegion = ""
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.RawRegion = "us-east-12"
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
c.RawRegion = "us-east-1"
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.RawRegion = "custom"
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatalf("should have err")
|
||||
}
|
||||
|
||||
c.RawRegion = "custom"
|
||||
c.SkipValidation = true
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
c.SkipValidation = false
|
||||
|
||||
}
|
||||
|
||||
func TestAccessConfigPrepare_RegionRestricted(t *testing.T) {
|
||||
|
||||
@@ -18,7 +18,7 @@ type AMIConfig struct {
|
||||
AMIRegions []string `mapstructure:"ami_regions"`
|
||||
AMISkipRegionValidation bool `mapstructure:"skip_region_validation"`
|
||||
AMITags TagMap `mapstructure:"tags"`
|
||||
AMIENASupport *bool `mapstructure:"ena_support"`
|
||||
AMIENASupport bool `mapstructure:"ena_support"`
|
||||
AMISriovNetSupport bool `mapstructure:"sriov_support"`
|
||||
AMIForceDeregister bool `mapstructure:"force_deregister"`
|
||||
AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot"`
|
||||
@@ -56,7 +56,44 @@ func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context
|
||||
}
|
||||
}
|
||||
|
||||
errs = append(errs, c.prepareRegions(accessConfig)...)
|
||||
if len(c.AMIRegions) > 0 {
|
||||
regionSet := make(map[string]struct{})
|
||||
regions := make([]string, 0, len(c.AMIRegions))
|
||||
|
||||
for _, region := range c.AMIRegions {
|
||||
// If we already saw the region, then don't look again
|
||||
if _, ok := regionSet[region]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Mark that we saw the region
|
||||
regionSet[region] = struct{}{}
|
||||
|
||||
if !c.AMISkipRegionValidation {
|
||||
// Verify the region is real
|
||||
if valid := ValidateRegion(region); !valid {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that if we have region_kms_key_ids defined,
|
||||
// the regions in ami_regions are also in region_kms_key_ids
|
||||
if len(c.AMIRegionKMSKeyIDs) > 0 {
|
||||
if _, ok := c.AMIRegionKMSKeyIDs[region]; !ok {
|
||||
errs = append(errs, fmt.Errorf("Region %s is in ami_regions but not in region_kms_key_ids", region))
|
||||
}
|
||||
}
|
||||
if (accessConfig != nil) && (region == accessConfig.RawRegion) {
|
||||
// make sure we don't try to copy to the region we originally
|
||||
// create the AMI in.
|
||||
log.Printf("Cannot copy AMI to AWS session region '%s', deleting it from `ami_regions`.", region)
|
||||
continue
|
||||
}
|
||||
regions = append(regions, region)
|
||||
}
|
||||
|
||||
c.AMIRegions = regions
|
||||
}
|
||||
|
||||
if len(c.AMIUsers) > 0 && c.AMIEncryptBootVolume {
|
||||
errs = append(errs, fmt.Errorf("Cannot share AMI with encrypted boot volume"))
|
||||
@@ -93,38 +130,3 @@ func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AMIConfig) prepareRegions(accessConfig *AccessConfig) (errs []error) {
|
||||
if len(c.AMIRegions) > 0 {
|
||||
regionSet := make(map[string]struct{})
|
||||
regions := make([]string, 0, len(c.AMIRegions))
|
||||
|
||||
for _, region := range c.AMIRegions {
|
||||
// If we already saw the region, then don't look again
|
||||
if _, ok := regionSet[region]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Mark that we saw the region
|
||||
regionSet[region] = struct{}{}
|
||||
|
||||
// Make sure that if we have region_kms_key_ids defined,
|
||||
// the regions in ami_regions are also in region_kms_key_ids
|
||||
if len(c.AMIRegionKMSKeyIDs) > 0 {
|
||||
if _, ok := c.AMIRegionKMSKeyIDs[region]; !ok {
|
||||
errs = append(errs, fmt.Errorf("Region %s is in ami_regions but not in region_kms_key_ids", region))
|
||||
}
|
||||
}
|
||||
if (accessConfig != nil) && (region == accessConfig.RawRegion) {
|
||||
// make sure we don't try to copy to the region we originally
|
||||
// create the AMI in.
|
||||
log.Printf("Cannot copy AMI to AWS session region '%s', deleting it from `ami_regions`.", region)
|
||||
continue
|
||||
}
|
||||
regions = append(regions, region)
|
||||
}
|
||||
|
||||
c.AMIRegions = regions
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
)
|
||||
|
||||
func testAMIConfig() *AMIConfig {
|
||||
@@ -17,62 +12,43 @@ func testAMIConfig() *AMIConfig {
|
||||
}
|
||||
|
||||
func getFakeAccessConfig(region string) *AccessConfig {
|
||||
c := testAccessConfig()
|
||||
c.RawRegion = region
|
||||
return c
|
||||
return &AccessConfig{
|
||||
RawRegion: region,
|
||||
}
|
||||
}
|
||||
|
||||
func TestAMIConfigPrepare_name(t *testing.T) {
|
||||
c := testAMIConfig()
|
||||
accessConf := testAccessConfig()
|
||||
if err := c.Prepare(accessConf, nil); err != nil {
|
||||
if err := c.Prepare(nil, nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AMIName = ""
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
if err := c.Prepare(nil, nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
type mockEC2Client struct {
|
||||
ec2iface.EC2API
|
||||
}
|
||||
|
||||
func (m *mockEC2Client) DescribeRegions(*ec2.DescribeRegionsInput) (*ec2.DescribeRegionsOutput, error) {
|
||||
return &ec2.DescribeRegionsOutput{
|
||||
Regions: []*ec2.Region{
|
||||
{RegionName: aws.String("us-east-1")},
|
||||
{RegionName: aws.String("us-east-2")},
|
||||
{RegionName: aws.String("us-west-1")},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
c := testAMIConfig()
|
||||
c.AMIRegions = nil
|
||||
|
||||
var errs []error
|
||||
var err error
|
||||
accessConf := testAccessConfig()
|
||||
mockConn := &mockEC2Client{}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatalf("shouldn't have err: %#v", errs)
|
||||
if err := c.Prepare(nil, nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AMIRegions, err = listEC2Regions(mockConn)
|
||||
if err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err.Error())
|
||||
c.AMIRegions = listEC2Regions()
|
||||
if err := c.Prepare(nil, nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatalf("shouldn't have err: %#v", errs)
|
||||
|
||||
c.AMIRegions = []string{"foo"}
|
||||
if err := c.Prepare(nil, nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
errs = errs[:0]
|
||||
|
||||
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-1"}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatalf("bad: %s", errs[0])
|
||||
if err := c.Prepare(nil, nil); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
|
||||
expected := []string{"us-east-1", "us-west-1"}
|
||||
@@ -81,9 +57,11 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
}
|
||||
|
||||
c.AMIRegions = []string{"custom"}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
c.AMISkipRegionValidation = true
|
||||
if err := c.Prepare(nil, nil); err != nil {
|
||||
t.Fatal("shouldn't have error")
|
||||
}
|
||||
c.AMISkipRegionValidation = false
|
||||
|
||||
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
|
||||
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||
@@ -91,8 +69,8 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
"us-west-1": "789-012-3456",
|
||||
"us-east-2": "456-789-0123",
|
||||
}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatal(fmt.Sprintf("shouldn't have error: %s", errs[0]))
|
||||
if err := c.Prepare(nil, nil); err != nil {
|
||||
t.Fatal("shouldn't have error")
|
||||
}
|
||||
|
||||
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
|
||||
@@ -101,7 +79,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
"us-west-1": "789-012-3456",
|
||||
"us-east-2": "",
|
||||
}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
if err := c.Prepare(nil, nil); err != nil {
|
||||
t.Fatal("should have passed; we are able to use default KMS key if not sharing")
|
||||
}
|
||||
|
||||
@@ -112,7 +90,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
"us-west-1": "789-012-3456",
|
||||
"us-east-2": "",
|
||||
}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
if err := c.Prepare(nil, nil); err == nil {
|
||||
t.Fatal("should have an error b/c can't use default KMS key if sharing")
|
||||
}
|
||||
|
||||
@@ -122,7 +100,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
"us-west-1": "789-012-3456",
|
||||
"us-east-2": "456-789-0123",
|
||||
}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
if err := c.Prepare(nil, nil); err == nil {
|
||||
t.Fatal("should have error b/c theres a region in the key map that isn't in ami_regions")
|
||||
}
|
||||
|
||||
@@ -131,8 +109,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
"us-east-1": "123-456-7890",
|
||||
"us-west-1": "789-012-3456",
|
||||
}
|
||||
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
if err := c.Prepare(nil, nil); err == nil {
|
||||
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
|
||||
}
|
||||
|
||||
@@ -144,15 +121,15 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
"us-east-1": "123-456-7890",
|
||||
"us-west-1": "",
|
||||
}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
if err := c.Prepare(nil, nil); err == nil {
|
||||
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
|
||||
}
|
||||
|
||||
// allow rawregion to exist in ami_regions list.
|
||||
accessConf = getFakeAccessConfig("us-east-1")
|
||||
accessConf := getFakeAccessConfig("us-east-1")
|
||||
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
|
||||
c.AMIRegionKMSKeyIDs = nil
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
if err := c.Prepare(accessConf, nil); err != nil {
|
||||
t.Fatal("should allow user to have the raw region in ami_regions")
|
||||
}
|
||||
|
||||
@@ -163,15 +140,13 @@ func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
|
||||
c.AMIUsers = []string{"testAccountID"}
|
||||
c.AMIEncryptBootVolume = true
|
||||
|
||||
accessConf := testAccessConfig()
|
||||
|
||||
c.AMIKmsKeyId = ""
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
if err := c.Prepare(nil, nil); err == nil {
|
||||
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
|
||||
}
|
||||
|
||||
c.AMIKmsKeyId = "89c3fb9a-de87-4f2a-aedc-fddc5138193c"
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
if err := c.Prepare(nil, nil); err == nil {
|
||||
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
|
||||
}
|
||||
}
|
||||
@@ -179,10 +154,8 @@ func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
|
||||
func TestAMINameValidation(t *testing.T) {
|
||||
c := testAMIConfig()
|
||||
|
||||
accessConf := testAccessConfig()
|
||||
|
||||
c.AMIName = "aa"
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
if err := c.Prepare(nil, nil); err == nil {
|
||||
t.Fatal("shouldn't be able to have an ami name with less than 3 characters")
|
||||
}
|
||||
|
||||
@@ -191,22 +164,22 @@ func TestAMINameValidation(t *testing.T) {
|
||||
longAmiName += "a"
|
||||
}
|
||||
c.AMIName = longAmiName
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
if err := c.Prepare(nil, nil); err == nil {
|
||||
t.Fatal("shouldn't be able to have an ami name with great than 128 characters")
|
||||
}
|
||||
|
||||
c.AMIName = "+aaa"
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
if err := c.Prepare(nil, nil); err == nil {
|
||||
t.Fatal("shouldn't be able to have an ami name with invalid characters")
|
||||
}
|
||||
|
||||
c.AMIName = "fooBAR1()[] ./-'@_"
|
||||
if err := c.Prepare(accessConf, nil); err != nil {
|
||||
if err := c.Prepare(nil, nil); err != nil {
|
||||
t.Fatal("should be able to use all of the allowed AMI characters")
|
||||
}
|
||||
|
||||
c.AMIName = `xyz-base-2017-04-05-1934`
|
||||
if err := c.Prepare(accessConf, nil); err != nil {
|
||||
if err := c.Prepare(nil, nil); err != nil {
|
||||
t.Fatalf("expected `xyz-base-2017-04-05-1934` to pass validation.")
|
||||
}
|
||||
|
||||
|
||||
@@ -88,10 +88,6 @@ func buildBlockDevices(b []BlockDevice) []*ec2.BlockDeviceMapping {
|
||||
}
|
||||
|
||||
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 when setting kms_key_id
|
||||
if b.KmsKeyId != "" && b.Encrypted == false {
|
||||
return fmt.Errorf("The device %v, must also have `encrypted: "+
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
)
|
||||
|
||||
// Build a slice of EC2 (AMI/Subnet/VPC) filter options from the filters provided.
|
||||
func buildEc2Filters(input map[*string]*string) []*ec2.Filter {
|
||||
var filters []*ec2.Filter
|
||||
for k, v := range input {
|
||||
filters = append(filters, &ec2.Filter{
|
||||
Name: k,
|
||||
Values: []*string{v},
|
||||
})
|
||||
}
|
||||
return filters
|
||||
}
|
||||
@@ -1,53 +1,38 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
)
|
||||
|
||||
func listEC2Regions(ec2conn ec2iface.EC2API) ([]string, error) {
|
||||
var regions []string
|
||||
resultRegions, err := ec2conn.DescribeRegions(nil)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
// aws ec2 describe-regions --query 'Regions[].{Name:RegionName}' --output text | sed 's/\(.*\)/"\1",/' | sort
|
||||
func listEC2Regions() []string {
|
||||
return []string{
|
||||
"ap-northeast-1",
|
||||
"ap-northeast-2",
|
||||
"ap-northeast-3",
|
||||
"ap-south-1",
|
||||
"ap-southeast-1",
|
||||
"ap-southeast-2",
|
||||
"ca-central-1",
|
||||
"eu-central-1",
|
||||
"eu-west-1",
|
||||
"eu-west-2",
|
||||
"eu-west-3",
|
||||
"sa-east-1",
|
||||
"us-east-1",
|
||||
"us-east-2",
|
||||
"us-west-1",
|
||||
"us-west-2",
|
||||
// not part of autogenerated list
|
||||
"us-gov-west-1",
|
||||
"cn-north-1",
|
||||
"cn-northwest-1",
|
||||
}
|
||||
for _, region := range resultRegions.Regions {
|
||||
regions = append(regions, *region.RegionName)
|
||||
}
|
||||
|
||||
return regions, nil
|
||||
}
|
||||
|
||||
// ValidateRegion returns true if the supplied region is a valid AWS
|
||||
// region and false if it's not.
|
||||
func (c *AccessConfig) ValidateRegion(regions ...string) error {
|
||||
ec2conn, err := c.NewEC2Connection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
validRegions, err := listEC2Regions(ec2conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var invalidRegions []string
|
||||
for _, region := range regions {
|
||||
found := false
|
||||
for _, validRegion := range validRegions {
|
||||
if region == validRegion {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
invalidRegions = append(invalidRegions, region)
|
||||
func ValidateRegion(region string) bool {
|
||||
for _, valid := range listEC2Regions() {
|
||||
if region == valid {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalidRegions) > 0 {
|
||||
return fmt.Errorf("Invalid region(s): %v", invalidRegions)
|
||||
}
|
||||
return nil
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -29,62 +29,33 @@ func (d *AmiFilterOptions) NoOwner() bool {
|
||||
return len(d.Owners) == 0
|
||||
}
|
||||
|
||||
type SubnetFilterOptions struct {
|
||||
Filters map[*string]*string
|
||||
MostFree bool `mapstructure:"most_free"`
|
||||
Random bool `mapstructure:"random"`
|
||||
}
|
||||
|
||||
func (d *SubnetFilterOptions) Empty() bool {
|
||||
return len(d.Filters) == 0
|
||||
}
|
||||
|
||||
type VpcFilterOptions struct {
|
||||
Filters map[*string]*string
|
||||
}
|
||||
|
||||
func (d *VpcFilterOptions) Empty() bool {
|
||||
return len(d.Filters) == 0
|
||||
}
|
||||
|
||||
type SecurityGroupFilterOptions struct {
|
||||
Filters map[*string]*string
|
||||
}
|
||||
|
||||
func (d *SecurityGroupFilterOptions) Empty() bool {
|
||||
return len(d.Filters) == 0
|
||||
}
|
||||
|
||||
// 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"`
|
||||
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"`
|
||||
TemporarySGSourceCidr string `mapstructure:"temporary_security_group_source_cidr"`
|
||||
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"`
|
||||
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"`
|
||||
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"`
|
||||
SpotPrice string `mapstructure:"spot_price"`
|
||||
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
|
||||
SpotTags map[string]string `mapstructure:"spot_tags"`
|
||||
SubnetId string `mapstructure:"subnet_id"`
|
||||
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
|
||||
TemporarySGSourceCidr string `mapstructure:"temporary_security_group_source_cidr"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
UserDataFile string `mapstructure:"user_data_file"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"`
|
||||
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
@@ -208,7 +208,7 @@ func WaitForVolumeToBeDetached(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeV
|
||||
func WaitForImageToBeImported(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeImportImageTasksInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "DescribeImages",
|
||||
MaxAttempts: 720,
|
||||
MaxAttempts: 300,
|
||||
Delay: request.ConstantWaiterDelay(5 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
@@ -217,12 +217,6 @@ func WaitForImageToBeImported(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeIm
|
||||
Argument: "ImportImageTasks[].Status",
|
||||
Expected: "completed",
|
||||
},
|
||||
{
|
||||
State: request.FailureWaiterState,
|
||||
Matcher: request.PathAnyWaiterMatch,
|
||||
Argument: "ImportImageTasks[].Status",
|
||||
Expected: "deleted",
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
@@ -295,7 +289,7 @@ func getOverride(varInfo envInfo) envInfo {
|
||||
func getEnvOverrides() overridableWaitVars {
|
||||
// Load env vars from environment.
|
||||
envValues := overridableWaitVars{
|
||||
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
|
||||
envInfo{"AWS_POLL_DELAY_SECONDS", 0, false},
|
||||
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
|
||||
envInfo{"AWS_TIMEOUT_SECONDS", 0, false},
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package common
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
@@ -25,9 +26,10 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
|
||||
|
||||
if s.Comm.SSHPrivateKeyFile != "" {
|
||||
ui.Say("Using existing SSH private key")
|
||||
privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile()
|
||||
privateKeyBytes, err := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
state.Put("error", fmt.Errorf(
|
||||
"Error loading configured private key file: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package common
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
@@ -12,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type StepModifyEBSBackedInstance struct {
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMIENASupport bool
|
||||
EnableAMISriovNetSupport bool
|
||||
}
|
||||
|
||||
@@ -38,22 +37,16 @@ func (s *StepModifyEBSBackedInstance) Run(_ context.Context, state multistep.Sta
|
||||
}
|
||||
}
|
||||
|
||||
// Handle EnaSupport flag.
|
||||
// Set EnaSupport to true.
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
if s.EnableAMIENASupport != nil {
|
||||
var prefix string
|
||||
if *s.EnableAMIENASupport {
|
||||
prefix = "En"
|
||||
} else {
|
||||
prefix = "Dis"
|
||||
}
|
||||
ui.Say(fmt.Sprintf("%sabling Enhanced Networking (ENA)...", prefix))
|
||||
if s.EnableAMIENASupport {
|
||||
ui.Say("Enabling Enhanced Networking (ENA)...")
|
||||
_, err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
|
||||
InstanceId: instance.InstanceId,
|
||||
EnaSupport: &ec2.AttributeBooleanValue{Value: aws.Bool(*s.EnableAMIENASupport)},
|
||||
EnaSupport: &ec2.AttributeBooleanValue{Value: aws.Bool(true)},
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error %sabling Enhanced Networking (ENA) on %s: %s", strings.ToLower(prefix), *instance.InstanceId, err)
|
||||
err := fmt.Errorf("Error enabling Enhanced Networking (ENA) on %s: %s", *instance.InstanceId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"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"
|
||||
)
|
||||
|
||||
// StepNetworkInfo queries AWS for information about
|
||||
// VPC's and Subnets that is used throughout the AMI creation process.
|
||||
//
|
||||
// Produces (adding them to the state bag):
|
||||
// vpc_id string - the VPC ID
|
||||
// subnet_id string - the Subnet ID
|
||||
// availability_zone string - the AZ name
|
||||
type StepNetworkInfo struct {
|
||||
VpcId string
|
||||
VpcFilter VpcFilterOptions
|
||||
SubnetId string
|
||||
SubnetFilter SubnetFilterOptions
|
||||
AvailabilityZone string
|
||||
SecurityGroupIds []string
|
||||
SecurityGroupFilter SecurityGroupFilterOptions
|
||||
}
|
||||
|
||||
type subnetsSort []*ec2.Subnet
|
||||
|
||||
func (a subnetsSort) Len() int { return len(a) }
|
||||
func (a subnetsSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a subnetsSort) Less(i, j int) bool {
|
||||
return *a[i].AvailableIpAddressCount < *a[j].AvailableIpAddressCount
|
||||
}
|
||||
|
||||
// Returns the most recent AMI out of a slice of images.
|
||||
func mostFreeSubnet(subnets []*ec2.Subnet) *ec2.Subnet {
|
||||
sortedSubnets := subnets
|
||||
sort.Sort(subnetsSort(sortedSubnets))
|
||||
return sortedSubnets[len(sortedSubnets)-1]
|
||||
}
|
||||
|
||||
func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// VPC
|
||||
if s.VpcId == "" && !s.VpcFilter.Empty() {
|
||||
params := &ec2.DescribeVpcsInput{}
|
||||
params.Filters = buildEc2Filters(s.VpcFilter.Filters)
|
||||
s.VpcFilter.Filters[aws.String("state")] = aws.String("available")
|
||||
|
||||
log.Printf("Using VPC Filters %v", params)
|
||||
|
||||
vpcResp, err := ec2conn.DescribeVpcs(params)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying VPCs: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(vpcResp.Vpcs) != 1 {
|
||||
err := fmt.Errorf("Exactly one VPC should match the filter, but %d VPC's was found matching filters: %v", len(vpcResp.Vpcs), params)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.VpcId = *vpcResp.Vpcs[0].VpcId
|
||||
ui.Message(fmt.Sprintf("Found VPC ID: %s", s.VpcId))
|
||||
}
|
||||
|
||||
// Subnet
|
||||
if s.SubnetId == "" && !s.SubnetFilter.Empty() {
|
||||
params := &ec2.DescribeSubnetsInput{}
|
||||
s.SubnetFilter.Filters[aws.String("state")] = aws.String("available")
|
||||
|
||||
if s.VpcId != "" {
|
||||
s.SubnetFilter.Filters[aws.String("vpc-id")] = &s.VpcId
|
||||
}
|
||||
if s.AvailabilityZone != "" {
|
||||
s.SubnetFilter.Filters[aws.String("availability-zone")] = &s.AvailabilityZone
|
||||
}
|
||||
params.Filters = buildEc2Filters(s.SubnetFilter.Filters)
|
||||
log.Printf("Using Subnet Filters %v", params)
|
||||
|
||||
subnetsResp, err := ec2conn.DescribeSubnets(params)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying Subnets: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(subnetsResp.Subnets) == 0 {
|
||||
err := fmt.Errorf("No Subnets was found matching filters: %v", params)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(subnetsResp.Subnets) > 1 && !s.SubnetFilter.Random && !s.SubnetFilter.MostFree {
|
||||
err := fmt.Errorf("Your filter matched %d Subnets. Please try a more specific search, or set random or most_free to true.", len(subnetsResp.Subnets))
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var subnet *ec2.Subnet
|
||||
switch {
|
||||
case s.SubnetFilter.MostFree:
|
||||
subnet = mostFreeSubnet(subnetsResp.Subnets)
|
||||
case s.SubnetFilter.Random:
|
||||
subnet = subnetsResp.Subnets[rand.Intn(len(subnetsResp.Subnets))]
|
||||
default:
|
||||
subnet = subnetsResp.Subnets[0]
|
||||
}
|
||||
s.SubnetId = *subnet.SubnetId
|
||||
ui.Message(fmt.Sprintf("Found Subnet ID: %s", s.SubnetId))
|
||||
}
|
||||
|
||||
// Try to find AZ and VPC Id from Subnet if they are not yet found/given
|
||||
if s.SubnetId != "" && (s.AvailabilityZone == "" || s.VpcId == "") {
|
||||
log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", s.SubnetId)
|
||||
resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&s.SubnetId}})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Describing the subnet: %s returned error: %s.", s.SubnetId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if s.AvailabilityZone == "" {
|
||||
s.AvailabilityZone = *resp.Subnets[0].AvailabilityZone
|
||||
log.Printf("[INFO] AvailabilityZone found: '%s'", s.AvailabilityZone)
|
||||
}
|
||||
if s.VpcId == "" {
|
||||
s.VpcId = *resp.Subnets[0].VpcId
|
||||
log.Printf("[INFO] VpcId found: '%s'", s.VpcId)
|
||||
}
|
||||
}
|
||||
|
||||
state.Put("vpc_id", s.VpcId)
|
||||
state.Put("availability_zone", s.AvailabilityZone)
|
||||
state.Put("subnet_id", s.SubnetId)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepNetworkInfo) Cleanup(multistep.StateBag) {}
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
type StepRunSourceInstance struct {
|
||||
AssociatePublicIpAddress bool
|
||||
AvailabilityZone string
|
||||
BlockDevices BlockDevices
|
||||
Comm *communicator.Config
|
||||
Ctx interpolate.Context
|
||||
@@ -32,6 +33,7 @@ type StepRunSourceInstance struct {
|
||||
InstanceType string
|
||||
IsRestricted bool
|
||||
SourceAMI string
|
||||
SubnetId string
|
||||
Tags TagMap
|
||||
UserData string
|
||||
UserDataFile string
|
||||
@@ -102,7 +104,6 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
az := state.Get("availability_zone").(string)
|
||||
runOpts := &ec2.RunInstancesInput{
|
||||
ImageId: &s.SourceAMI,
|
||||
InstanceType: &s.InstanceType,
|
||||
@@ -111,7 +112,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
MinCount: aws.Int64(1),
|
||||
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
|
||||
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
|
||||
Placement: &ec2.Placement{AvailabilityZone: &az},
|
||||
Placement: &ec2.Placement{AvailabilityZone: &s.AvailabilityZone},
|
||||
EbsOptimized: &s.EbsOptimized,
|
||||
}
|
||||
|
||||
@@ -152,20 +153,18 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
runOpts.KeyName = &s.Comm.SSHKeyPairName
|
||||
}
|
||||
|
||||
subnetId := state.Get("subnet_id").(string)
|
||||
|
||||
if subnetId != "" && s.AssociatePublicIpAddress {
|
||||
if s.SubnetId != "" && s.AssociatePublicIpAddress {
|
||||
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
|
||||
{
|
||||
DeviceIndex: aws.Int64(0),
|
||||
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
|
||||
SubnetId: aws.String(subnetId),
|
||||
SubnetId: &s.SubnetId,
|
||||
Groups: securityGroupIds,
|
||||
DeleteOnTermination: aws.Bool(true),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
runOpts.SubnetId = aws.String(subnetId)
|
||||
runOpts.SubnetId = &s.SubnetId
|
||||
runOpts.SecurityGroupIds = securityGroupIds
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
type StepRunSpotInstance struct {
|
||||
AssociatePublicIpAddress bool
|
||||
AvailabilityZone string
|
||||
BlockDevices BlockDevices
|
||||
BlockDurationMinutes int64
|
||||
Debug bool
|
||||
@@ -35,6 +36,7 @@ type StepRunSpotInstance struct {
|
||||
SpotPrice string
|
||||
SpotPriceProduct string
|
||||
SpotTags TagMap
|
||||
SubnetId string
|
||||
Tags TagMap
|
||||
VolumeTags TagMap
|
||||
UserData string
|
||||
@@ -84,12 +86,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
}
|
||||
|
||||
spotPrice := s.SpotPrice
|
||||
azConfig := ""
|
||||
if azRaw, ok := state.GetOk("availability_zone"); ok {
|
||||
azConfig = azRaw.(string)
|
||||
}
|
||||
az := azConfig
|
||||
|
||||
availabilityZone := s.AvailabilityZone
|
||||
if spotPrice == "auto" {
|
||||
ui.Message(fmt.Sprintf(
|
||||
"Finding spot price for %s %s...",
|
||||
@@ -100,7 +97,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistoryInput{
|
||||
InstanceTypes: []*string{&s.InstanceType},
|
||||
ProductDescriptions: []*string{&s.SpotPriceProduct},
|
||||
AvailabilityZone: &az,
|
||||
AvailabilityZone: &s.AvailabilityZone,
|
||||
StartTime: &startTime,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -120,8 +117,8 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
}
|
||||
if price == 0 || current < price {
|
||||
price = current
|
||||
if azConfig == "" {
|
||||
az = *history.AvailabilityZone
|
||||
if s.AvailabilityZone == "" {
|
||||
availabilityZone = *history.AvailabilityZone
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,26 +162,24 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
UserData: &userData,
|
||||
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
|
||||
Placement: &ec2.SpotPlacement{
|
||||
AvailabilityZone: &az,
|
||||
AvailabilityZone: &availabilityZone,
|
||||
},
|
||||
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
|
||||
EbsOptimized: &s.EbsOptimized,
|
||||
}
|
||||
|
||||
subnetId := state.Get("subnet_id").(string)
|
||||
|
||||
if subnetId != "" && s.AssociatePublicIpAddress {
|
||||
if s.SubnetId != "" && s.AssociatePublicIpAddress {
|
||||
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
|
||||
{
|
||||
DeviceIndex: aws.Int64(0),
|
||||
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
|
||||
SubnetId: &subnetId,
|
||||
SubnetId: &s.SubnetId,
|
||||
Groups: securityGroupIds,
|
||||
DeleteOnTermination: aws.Bool(true),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
runOpts.SubnetId = &subnetId
|
||||
runOpts.SubnetId = &s.SubnetId
|
||||
runOpts.SecurityGroupIds = securityGroupIds
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
@@ -18,8 +17,8 @@ import (
|
||||
|
||||
type StepSecurityGroup struct {
|
||||
CommConfig *communicator.Config
|
||||
SecurityGroupFilter SecurityGroupFilterOptions
|
||||
SecurityGroupIds []string
|
||||
VpcId string
|
||||
TemporarySGSourceCidr string
|
||||
|
||||
createdGroupId string
|
||||
@@ -28,7 +27,6 @@ type StepSecurityGroup struct {
|
||||
func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vpcId := state.Get("vpc_id").(string)
|
||||
|
||||
if len(s.SecurityGroupIds) > 0 {
|
||||
_, err := ec2conn.DescribeSecurityGroups(
|
||||
@@ -47,35 +45,6 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if !s.SecurityGroupFilter.Empty() {
|
||||
|
||||
params := &ec2.DescribeSecurityGroupsInput{}
|
||||
if vpcId != "" {
|
||||
s.SecurityGroupFilter.Filters[aws.String("vpc-id")] = &vpcId
|
||||
}
|
||||
params.Filters = buildEc2Filters(s.SecurityGroupFilter.Filters)
|
||||
|
||||
log.Printf("Using SecurityGroup Filters %v", params)
|
||||
|
||||
sgResp, err := ec2conn.DescribeSecurityGroups(params)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Couldn't find security groups for filter: %s", err)
|
||||
log.Printf("[DEBUG] %s", err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
securityGroupIds := []string{}
|
||||
for _, sg := range sgResp.SecurityGroups {
|
||||
securityGroupIds = append(securityGroupIds, *sg.GroupId)
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Found Security Group(s): %s", strings.Join(securityGroupIds, ", ")))
|
||||
state.Put("securityGroupIds", securityGroupIds)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
port := s.CommConfig.Port()
|
||||
if port == 0 {
|
||||
if s.CommConfig.Type != "none" {
|
||||
@@ -91,7 +60,9 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
|
||||
Description: aws.String("Temporary group for Packer"),
|
||||
}
|
||||
|
||||
group.VpcId = &vpcId
|
||||
if s.VpcId != "" {
|
||||
group.VpcId = &s.VpcId
|
||||
}
|
||||
|
||||
groupResp, err := ec2conn.CreateSecurityGroup(group)
|
||||
if err != nil {
|
||||
@@ -103,7 +74,38 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
|
||||
// Set the group ID so we can delete it later
|
||||
s.createdGroupId = *groupResp.GroupId
|
||||
|
||||
// Wait for the security group become available for authorizing
|
||||
// Authorize the SSH access for the security group
|
||||
req := &ec2.AuthorizeSecurityGroupIngressInput{
|
||||
GroupId: groupResp.GroupId,
|
||||
IpProtocol: aws.String("tcp"),
|
||||
FromPort: aws.Int64(int64(port)),
|
||||
ToPort: aws.Int64(int64(port)),
|
||||
CidrIp: aws.String(s.TemporarySGSourceCidr),
|
||||
}
|
||||
|
||||
// We loop and retry this a few times because sometimes the security
|
||||
// group isn't available immediately because AWS resources are eventually
|
||||
// consistent.
|
||||
ui.Say(fmt.Sprintf(
|
||||
"Authorizing access to port %d from %s in the temporary security group...",
|
||||
port, s.TemporarySGSourceCidr))
|
||||
for i := 0; i < 5; i++ {
|
||||
_, err = ec2conn.AuthorizeSecurityGroupIngress(req)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Error authorizing. Will sleep and retry. %s", err)
|
||||
time.Sleep((time.Duration(i) * time.Second) + 1)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating temporary security group: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Waiting for temporary security group: %s", s.createdGroupId)
|
||||
err = waitUntilSecurityGroupExists(ec2conn,
|
||||
&ec2.DescribeSecurityGroupsInput{
|
||||
@@ -119,34 +121,6 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Authorize the SSH access for the security group
|
||||
groupRules := &ec2.AuthorizeSecurityGroupIngressInput{
|
||||
GroupId: groupResp.GroupId,
|
||||
IpPermissions: []*ec2.IpPermission{
|
||||
{
|
||||
FromPort: aws.Int64(int64(port)),
|
||||
ToPort: aws.Int64(int64(port)),
|
||||
IpRanges: []*ec2.IpRange{
|
||||
{
|
||||
CidrIp: aws.String(s.TemporarySGSourceCidr),
|
||||
},
|
||||
},
|
||||
IpProtocol: aws.String("tcp"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf(
|
||||
"Authorizing access to port %d from %s in the temporary security group...",
|
||||
port, s.TemporarySGSourceCidr))
|
||||
_, err = ec2conn.AuthorizeSecurityGroupIngress(groupRules)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error authorizing temporary security group: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set some state data for use in future steps
|
||||
state.Put("securityGroupIds", []string{s.createdGroupId})
|
||||
|
||||
|
||||
@@ -20,11 +20,23 @@ import (
|
||||
type StepSourceAMIInfo struct {
|
||||
SourceAmi string
|
||||
EnableAMISriovNetSupport bool
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMIENASupport bool
|
||||
AMIVirtType string
|
||||
AmiFilters AmiFilterOptions
|
||||
}
|
||||
|
||||
// Build a slice of AMI filter options from the filters provided.
|
||||
func buildAmiFilters(input map[*string]*string) []*ec2.Filter {
|
||||
var filters []*ec2.Filter
|
||||
for k, v := range input {
|
||||
filters = append(filters, &ec2.Filter{
|
||||
Name: k,
|
||||
Values: []*string{v},
|
||||
})
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
type imageSort []*ec2.Image
|
||||
|
||||
func (a imageSort) Len() int { return len(a) }
|
||||
@@ -54,7 +66,7 @@ func (s *StepSourceAMIInfo) Run(_ context.Context, state multistep.StateBag) mul
|
||||
|
||||
// We have filters to apply
|
||||
if len(s.AmiFilters.Filters) > 0 {
|
||||
params.Filters = buildEc2Filters(s.AmiFilters.Filters)
|
||||
params.Filters = buildAmiFilters(s.AmiFilters.Filters)
|
||||
}
|
||||
if len(s.AmiFilters.Owners) > 0 {
|
||||
params.Owners = s.AmiFilters.Owners
|
||||
@@ -94,7 +106,7 @@ func (s *StepSourceAMIInfo) Run(_ context.Context, state multistep.StateBag) mul
|
||||
|
||||
// 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 || s.EnableAMISriovNetSupport {
|
||||
err = s.canEnableEnhancedNetworking(image)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
|
||||
@@ -70,7 +70,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
|
||||
|
||||
if b.config.IsSpotInstance() && ((b.config.AMIENASupport != nil && *b.config.AMIENASupport) || b.config.AMISriovNetSupport) {
|
||||
if b.config.IsSpotInstance() && (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 "+
|
||||
@@ -82,26 +82,38 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
log.Println(b.config)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
|
||||
session, err := b.config.Session()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !b.config.AMISkipRegionValidation {
|
||||
regionsToValidate := append(b.config.AMIRegions, b.config.RawRegion)
|
||||
err := b.config.AccessConfig.ValidateRegion(regionsToValidate...)
|
||||
ec2conn := ec2.New(session)
|
||||
|
||||
// If the subnet is specified but not the VpcId or AZ, try to determine them automatically
|
||||
if b.config.SubnetId != "" && (b.config.AvailabilityZone == "" || b.config.VpcId == "") {
|
||||
log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", b.config.SubnetId)
|
||||
resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error validating regions: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if b.config.AvailabilityZone == "" {
|
||||
b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone
|
||||
log.Printf("[INFO] AvailabilityZone found: '%s'", b.config.AvailabilityZone)
|
||||
}
|
||||
if b.config.VpcId == "" {
|
||||
b.config.VpcId = *resp.Subnets[0].VpcId
|
||||
log.Printf("[INFO] VpcId found: '%s'", b.config.VpcId)
|
||||
}
|
||||
}
|
||||
ec2conn := ec2.New(session)
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("config", b.config)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("awsSession", session)
|
||||
state.Put("hook", hook)
|
||||
@@ -112,6 +124,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
if b.config.IsSpotInstance() {
|
||||
instanceStep = &awscommon.StepRunSpotInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
||||
Ctx: b.config.ctx,
|
||||
@@ -126,6 +139,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||
SpotTags: b.config.SpotTags,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
@@ -134,6 +148,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
} else {
|
||||
instanceStep = &awscommon.StepRunSourceInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
@@ -146,6 +161,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
@@ -166,24 +182,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
AMIVirtType: b.config.AMIVirtType,
|
||||
},
|
||||
&awscommon.StepNetworkInfo{
|
||||
VpcId: b.config.VpcId,
|
||||
VpcFilter: b.config.VpcFilter,
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
SecurityGroupFilter: b.config.SecurityGroupFilter,
|
||||
SubnetId: b.config.SubnetId,
|
||||
SubnetFilter: b.config.SubnetFilter,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
},
|
||||
&awscommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
},
|
||||
&awscommon.StepSecurityGroup{
|
||||
SecurityGroupFilter: b.config.SecurityGroupFilter,
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
VpcId: b.config.VpcId,
|
||||
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
|
||||
},
|
||||
&awscommon.StepCleanupVolumes{
|
||||
|
||||
@@ -47,7 +47,6 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
|
||||
// Test good
|
||||
config["ami_name"] = "foo"
|
||||
config["skip_region_validation"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
@@ -100,7 +99,6 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
|
||||
|
||||
// Test good
|
||||
config["shutdown_behavior"] = "terminate"
|
||||
config["skip_region_validation"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
|
||||
@@ -17,7 +17,7 @@ type stepCreateAMI struct {
|
||||
}
|
||||
|
||||
func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
@@ -85,7 +85,7 @@ 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) {
|
||||
if b.config.IsSpotInstance() && (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 "+
|
||||
@@ -97,6 +97,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
log.Println(b.config)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -107,6 +108,23 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
}
|
||||
ec2conn := ec2.New(session)
|
||||
|
||||
// If the subnet is specified but not the VpcId or AZ, try to determine them automatically
|
||||
if b.config.SubnetId != "" && (b.config.AvailabilityZone == "" || b.config.VpcId == "") {
|
||||
log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", b.config.SubnetId)
|
||||
resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.config.AvailabilityZone == "" {
|
||||
b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone
|
||||
log.Printf("[INFO] AvailabilityZone found: '%s'", b.config.AvailabilityZone)
|
||||
}
|
||||
if b.config.VpcId == "" {
|
||||
b.config.VpcId = *resp.Subnets[0].VpcId
|
||||
log.Printf("[INFO] VpcId found: '%s'", b.config.VpcId)
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
@@ -120,6 +138,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
if b.config.IsSpotInstance() {
|
||||
instanceStep = &awscommon.StepRunSpotInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
||||
Ctx: b.config.ctx,
|
||||
@@ -134,6 +153,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||
SpotTags: b.config.SpotTags,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
@@ -142,6 +162,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
} else {
|
||||
instanceStep = &awscommon.StepRunSourceInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
@@ -154,6 +175,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
@@ -177,24 +199,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
AMIVirtType: b.config.AMIVirtType,
|
||||
},
|
||||
&awscommon.StepNetworkInfo{
|
||||
VpcId: b.config.VpcId,
|
||||
VpcFilter: b.config.VpcFilter,
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
SecurityGroupFilter: b.config.SecurityGroupFilter,
|
||||
SubnetId: b.config.SubnetId,
|
||||
SubnetFilter: b.config.SubnetFilter,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
},
|
||||
&awscommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
},
|
||||
&awscommon.StepSecurityGroup{
|
||||
SecurityGroupFilter: b.config.SecurityGroupFilter,
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
VpcId: b.config.VpcId,
|
||||
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
|
||||
},
|
||||
&awscommon.StepCleanupVolumes{
|
||||
|
||||
@@ -16,7 +16,7 @@ type StepRegisterAMI struct {
|
||||
RootDevice RootBlockDevice
|
||||
AMIDevices []*ec2.BlockDeviceMapping
|
||||
LaunchDevices []*ec2.BlockDeviceMapping
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMIENASupport bool
|
||||
EnableAMISriovNetSupport bool
|
||||
image *ec2.Image
|
||||
}
|
||||
@@ -44,7 +44,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 {
|
||||
// Set EnaSupport to true
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
registerOpts.EnaSupport = aws.Bool(true)
|
||||
|
||||
@@ -24,7 +24,7 @@ type Config struct {
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
|
||||
VolumeMappings []BlockDevice `mapstructure:"ebs_volumes"`
|
||||
AMIENASupport *bool `mapstructure:"ena_support"`
|
||||
AMIENASupport bool `mapstructure:"ena_support"`
|
||||
AMISriovNetSupport bool `mapstructure:"sriov_support"`
|
||||
|
||||
launchBlockDevices awscommon.BlockDevices
|
||||
@@ -70,7 +70,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
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 || 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 "+
|
||||
@@ -82,6 +82,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
log.Println(b.config)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -92,9 +93,26 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
}
|
||||
ec2conn := ec2.New(session)
|
||||
|
||||
// If the subnet is specified but not the VpcId or AZ, try to determine them automatically
|
||||
if b.config.SubnetId != "" && (b.config.AvailabilityZone == "" || b.config.VpcId == "") {
|
||||
log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", b.config.SubnetId)
|
||||
resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.config.AvailabilityZone == "" {
|
||||
b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone
|
||||
log.Printf("[INFO] AvailabilityZone found: '%s'", b.config.AvailabilityZone)
|
||||
}
|
||||
if b.config.VpcId == "" {
|
||||
b.config.VpcId = *resp.Subnets[0].VpcId
|
||||
log.Printf("[INFO] VpcId found: '%s'", b.config.VpcId)
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("config", b.config)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
@@ -104,6 +122,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
if b.config.IsSpotInstance() {
|
||||
instanceStep = &awscommon.StepRunSpotInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: b.config.launchBlockDevices,
|
||||
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
||||
Ctx: b.config.ctx,
|
||||
@@ -118,6 +137,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||
SpotTags: b.config.SpotTags,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
@@ -125,6 +145,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
} else {
|
||||
instanceStep = &awscommon.StepRunSourceInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: b.config.launchBlockDevices,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
@@ -137,6 +158,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
@@ -151,24 +173,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
EnableAMIENASupport: b.config.AMIENASupport,
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
},
|
||||
&awscommon.StepNetworkInfo{
|
||||
VpcId: b.config.VpcId,
|
||||
VpcFilter: b.config.VpcFilter,
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
SecurityGroupFilter: b.config.SecurityGroupFilter,
|
||||
SubnetId: b.config.SubnetId,
|
||||
SubnetFilter: b.config.SubnetFilter,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
},
|
||||
&awscommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
},
|
||||
&awscommon.StepSecurityGroup{
|
||||
SecurityGroupFilter: b.config.SecurityGroupFilter,
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
VpcId: b.config.VpcId,
|
||||
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
|
||||
},
|
||||
instanceStep,
|
||||
|
||||
@@ -61,7 +61,6 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
|
||||
|
||||
// Test good
|
||||
config["shutdown_behavior"] = "terminate"
|
||||
config["skip_region_validation"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
|
||||
@@ -156,7 +156,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
errs, fmt.Errorf("x509_key_path points to bad file: %s", err))
|
||||
}
|
||||
|
||||
if b.config.IsSpotInstance() && ((b.config.AMIENASupport != nil && *b.config.AMIENASupport) || b.config.AMISriovNetSupport) {
|
||||
if b.config.IsSpotInstance() && (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 "+
|
||||
@@ -167,6 +167,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
return nil, errs
|
||||
}
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
log.Println(b.config)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -177,6 +178,23 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
}
|
||||
ec2conn := ec2.New(session)
|
||||
|
||||
// If the subnet is specified but not the VpcId or AZ, try to determine them automatically
|
||||
if b.config.SubnetId != "" && (b.config.AvailabilityZone == "" || b.config.VpcId == "") {
|
||||
log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", b.config.SubnetId)
|
||||
resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.config.AvailabilityZone == "" {
|
||||
b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone
|
||||
log.Printf("[INFO] AvailabilityZone found: '%s'", b.config.AvailabilityZone)
|
||||
}
|
||||
if b.config.VpcId == "" {
|
||||
b.config.VpcId = *resp.Subnets[0].VpcId
|
||||
log.Printf("[INFO] VpcId found: '%s'", b.config.VpcId)
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
@@ -190,6 +208,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
if b.config.IsSpotInstance() {
|
||||
instanceStep = &awscommon.StepRunSpotInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
||||
Ctx: b.config.ctx,
|
||||
@@ -201,6 +220,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
SpotTags: b.config.SpotTags,
|
||||
UserData: b.config.UserData,
|
||||
@@ -209,6 +229,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
} else {
|
||||
instanceStep = &awscommon.StepRunSourceInstance{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
@@ -219,6 +240,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
@@ -238,15 +260,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
AMIVirtType: b.config.AMIVirtType,
|
||||
},
|
||||
&awscommon.StepNetworkInfo{
|
||||
VpcId: b.config.VpcId,
|
||||
VpcFilter: b.config.VpcFilter,
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
SecurityGroupFilter: b.config.SecurityGroupFilter,
|
||||
SubnetId: b.config.SubnetId,
|
||||
SubnetFilter: b.config.SubnetFilter,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
},
|
||||
&awscommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
@@ -254,8 +267,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
},
|
||||
&awscommon.StepSecurityGroup{
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
SecurityGroupFilter: b.config.SecurityGroupFilter,
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
VpcId: b.config.VpcId,
|
||||
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
|
||||
},
|
||||
instanceStep,
|
||||
|
||||
@@ -41,8 +41,6 @@ func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
func TestBuilderPrepare_AccountId(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
@@ -86,7 +84,6 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
|
||||
// Test good
|
||||
config["ami_name"] = "foo"
|
||||
config["skip_region_validation"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
@@ -121,7 +118,6 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
func TestBuilderPrepare_BundleDestination(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
@@ -142,7 +138,6 @@ func TestBuilderPrepare_BundleDestination(t *testing.T) {
|
||||
func TestBuilderPrepare_BundlePrefix(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
@@ -179,7 +174,6 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
func TestBuilderPrepare_S3Bucket(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
@@ -205,7 +199,6 @@ func TestBuilderPrepare_S3Bucket(t *testing.T) {
|
||||
func TestBuilderPrepare_X509CertPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
@@ -247,7 +240,6 @@ func TestBuilderPrepare_X509CertPath(t *testing.T) {
|
||||
func TestBuilderPrepare_X509KeyPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
@@ -289,7 +281,6 @@ func TestBuilderPrepare_X509KeyPath(t *testing.T) {
|
||||
func TestBuilderPrepare_X509UploadPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
type StepRegisterAMI struct {
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMIENASupport bool
|
||||
EnableAMISriovNetSupport bool
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
|
||||
registerOpts.SriovNetSupport = aws.String("simple")
|
||||
}
|
||||
if s.EnableAMIENASupport != nil && *s.EnableAMIENASupport {
|
||||
if s.EnableAMIENASupport {
|
||||
// Set EnaSupport to true
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
registerOpts.EnaSupport = aws.Bool(true)
|
||||
|
||||
@@ -4,7 +4,7 @@ Here's a list of things we like to get done in no particular order:
|
||||
- [ ] Blob/image rename post-processor
|
||||
- [ ] SSH to private ip through subnet
|
||||
- [ ] chroot builder
|
||||
- [ ] support cross-storage account image source (i.e. pre-build blob copy)
|
||||
- [ ] support cross-storage account image source (ie. pre-build blob copy)
|
||||
- [ ] look up object id when using device code (graph api /me ?)
|
||||
- [ ] device flow support for Windows
|
||||
- [x] look up tenant id in all cases (see device flow code)
|
||||
|
||||
@@ -29,26 +29,22 @@ type Artifact struct {
|
||||
TemplateUriReadOnlySas string
|
||||
|
||||
// Managed Image
|
||||
ManagedImageResourceGroupName string
|
||||
ManagedImageName string
|
||||
ManagedImageLocation string
|
||||
ManagedImageId string
|
||||
ManagedImageOSDiskSnapshotName string
|
||||
ManagedImageDataDiskSnapshotPrefix string
|
||||
ManagedImageResourceGroupName string
|
||||
ManagedImageName string
|
||||
ManagedImageLocation string
|
||||
ManagedImageId string
|
||||
|
||||
// Additional Disks
|
||||
AdditionalDisks *[]AdditionalDiskArtifact
|
||||
}
|
||||
|
||||
func NewManagedImageArtifact(osType, resourceGroup, name, location, id, osDiskSnapshotName, dataDiskSnapshotPrefix string) (*Artifact, error) {
|
||||
func NewManagedImageArtifact(osType, resourceGroup, name, location, id string) (*Artifact, error) {
|
||||
return &Artifact{
|
||||
ManagedImageResourceGroupName: resourceGroup,
|
||||
ManagedImageName: name,
|
||||
ManagedImageLocation: location,
|
||||
ManagedImageId: id,
|
||||
OSType: osType,
|
||||
ManagedImageOSDiskSnapshotName: osDiskSnapshotName,
|
||||
ManagedImageDataDiskSnapshotPrefix: dataDiskSnapshotPrefix,
|
||||
ManagedImageResourceGroupName: resourceGroup,
|
||||
ManagedImageName: name,
|
||||
ManagedImageLocation: location,
|
||||
ManagedImageId: id,
|
||||
OSType: osType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -137,10 +133,7 @@ func (*Artifact) Files() []string {
|
||||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
if a.OSDiskUri != "" {
|
||||
return a.OSDiskUri
|
||||
}
|
||||
return a.ManagedImageId
|
||||
return a.OSDiskUri
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
@@ -162,12 +155,6 @@ func (a *Artifact) String() string {
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageName: %s\n", a.ManagedImageName))
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageId: %s\n", a.ManagedImageId))
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageLocation: %s\n", a.ManagedImageLocation))
|
||||
if a.ManagedImageOSDiskSnapshotName != "" {
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageOSDiskSnapshotName: %s\n", a.ManagedImageOSDiskSnapshotName))
|
||||
}
|
||||
if a.ManagedImageDataDiskSnapshotPrefix != "" {
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageDataDiskSnapshotPrefix: %s\n", a.ManagedImageDataDiskSnapshotPrefix))
|
||||
}
|
||||
} else {
|
||||
buf.WriteString(fmt.Sprintf("StorageAccountLocation: %s\n", a.StorageAccountLocation))
|
||||
buf.WriteString(fmt.Sprintf("OSDiskUri: %s\n", a.OSDiskUri))
|
||||
|
||||
@@ -10,7 +10,7 @@ func getFakeSasUrl(name string) string {
|
||||
return fmt.Sprintf("SAS-%s", name)
|
||||
}
|
||||
|
||||
func TestArtifactIdVHD(t *testing.T) {
|
||||
func TestArtifactId(t *testing.T) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
{
|
||||
@@ -41,73 +41,6 @@ func TestArtifactIdVHD(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImage(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "fakeOsDiskSnapshotName", "fakeDataDiskSnapshotPrefix")
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
expected := `Azure.ResourceManagement.VMImage:
|
||||
|
||||
OSType: Linux
|
||||
ManagedImageResourceGroupName: fakeResourceGroup
|
||||
ManagedImageName: fakeName
|
||||
ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageOSDiskSnapshotName: fakeOsDiskSnapshotName
|
||||
ManagedImageDataDiskSnapshotPrefix: fakeDataDiskSnapshotPrefix
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
if result != expected {
|
||||
t.Fatalf("bad: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImageWithoutOSDiskSnapshotName(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "", "fakeDataDiskSnapshotPrefix")
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
expected := `Azure.ResourceManagement.VMImage:
|
||||
|
||||
OSType: Linux
|
||||
ManagedImageResourceGroupName: fakeResourceGroup
|
||||
ManagedImageName: fakeName
|
||||
ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageDataDiskSnapshotPrefix: fakeDataDiskSnapshotPrefix
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
if result != expected {
|
||||
t.Fatalf("bad: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImageWithoutDataDiskSnapshotPrefix(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "fakeOsDiskSnapshotName", "")
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
expected := `Azure.ResourceManagement.VMImage:
|
||||
|
||||
OSType: Linux
|
||||
ManagedImageResourceGroupName: fakeResourceGroup
|
||||
ManagedImageName: fakeName
|
||||
ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageOSDiskSnapshotName: fakeOsDiskSnapshotName
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
if result != expected {
|
||||
t.Fatalf("bad: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactString(t *testing.T) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
|
||||
@@ -31,10 +31,6 @@ func (a *Authenticate) getServicePrincipalTokenWithResource(resource string) (*a
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if a.clientID == "" && a.clientSecret == "" {
|
||||
return adal.NewServicePrincipalTokenFromMSI("http://169.254.169.254/metadata/identity/oauth2/token", resource)
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalToken(
|
||||
*oauthConfig,
|
||||
a.clientID,
|
||||
|
||||
@@ -40,7 +40,6 @@ type AzureClient struct {
|
||||
common.VaultClient
|
||||
armStorage.AccountsClient
|
||||
compute.DisksClient
|
||||
compute.SnapshotsClient
|
||||
|
||||
InspectorMaxLength int
|
||||
Template *CaptureTemplate
|
||||
@@ -190,12 +189,6 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
|
||||
azureClient.VirtualMachinesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), templateCapture(azureClient), errorCapture(azureClient))
|
||||
azureClient.VirtualMachinesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VirtualMachinesClient.UserAgent)
|
||||
|
||||
azureClient.SnapshotsClient = compute.NewSnapshotsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.SnapshotsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.SnapshotsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.SnapshotsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.SnapshotsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.SnapshotsClient.UserAgent)
|
||||
|
||||
azureClient.AccountsClient = armStorage.NewAccountsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.AccountsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.AccountsClient.RequestInspector = withInspection(maxlen)
|
||||
|
||||
@@ -183,8 +183,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
NewStepGetOSDisk(azureClient, ui),
|
||||
NewStepGetAdditionalDisks(azureClient, ui),
|
||||
NewStepPowerOffCompute(azureClient, ui),
|
||||
NewStepSnapshotOSDisk(azureClient, ui, b.config.isManagedImage()),
|
||||
NewStepSnapshotDataDisks(azureClient, ui, b.config.isManagedImage()),
|
||||
NewStepCaptureImage(azureClient, ui),
|
||||
NewStepDeleteResourceGroup(azureClient, ui),
|
||||
NewStepDeleteOSDisk(azureClient, ui),
|
||||
@@ -220,8 +218,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
&packerCommon.StepProvision{},
|
||||
NewStepGetOSDisk(azureClient, ui),
|
||||
NewStepGetAdditionalDisks(azureClient, ui),
|
||||
NewStepSnapshotOSDisk(azureClient, ui, b.config.isManagedImage()),
|
||||
NewStepSnapshotDataDisks(azureClient, ui, b.config.isManagedImage()),
|
||||
NewStepPowerOffCompute(azureClient, ui),
|
||||
NewStepCaptureImage(azureClient, ui),
|
||||
NewStepDeleteResourceGroup(azureClient, ui),
|
||||
@@ -263,7 +259,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
|
||||
if b.config.isManagedImage() {
|
||||
managedImageID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/images/%s", b.config.SubscriptionID, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName)
|
||||
return NewManagedImageArtifact(b.config.OSType, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, b.config.manageImageLocation, managedImageID, b.config.ManagedImageOSDiskSnapshotName, b.config.ManagedImageDataDiskSnapshotPrefix)
|
||||
return NewManagedImageArtifact(b.config.OSType, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, b.config.manageImageLocation, managedImageID)
|
||||
} else if template, ok := b.stateBag.GetOk(constants.ArmCaptureTemplate); ok {
|
||||
return NewArtifact(
|
||||
template.(*CaptureTemplate),
|
||||
@@ -365,8 +361,6 @@ func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
|
||||
stateBag.Put(constants.ArmIsManagedImage, b.config.isManagedImage())
|
||||
stateBag.Put(constants.ArmManagedImageResourceGroupName, b.config.ManagedImageResourceGroupName)
|
||||
stateBag.Put(constants.ArmManagedImageName, b.config.ManagedImageName)
|
||||
stateBag.Put(constants.ArmManagedImageOSDiskSnapshotName, b.config.ManagedImageOSDiskSnapshotName)
|
||||
stateBag.Put(constants.ArmManagedImageDataDiskSnapshotPrefix, b.config.ManagedImageDataDiskSnapshotPrefix)
|
||||
stateBag.Put(constants.ArmAsyncResourceGroupDelete, b.config.AsyncResourceGroupDelete)
|
||||
}
|
||||
|
||||
|
||||
+16
-71
@@ -56,8 +56,6 @@ var (
|
||||
reCaptureNamePrefix = regexp.MustCompile("^[A-Za-z0-9][A-Za-z0-9_\\-\\.]{0,23}$")
|
||||
reManagedDiskName = regexp.MustCompile(validManagedDiskName)
|
||||
reResourceGroupName = regexp.MustCompile(validResourceGroupNameRe)
|
||||
reSnapshotName = regexp.MustCompile("^[A-Za-z0-9_]{10,79}$")
|
||||
reSnapshotPrefix = regexp.MustCompile("^[A-Za-z0-9_]{10,59}$")
|
||||
)
|
||||
|
||||
type PlanInformation struct {
|
||||
@@ -67,14 +65,6 @@ type PlanInformation struct {
|
||||
PlanPromotionCode string `mapstructure:"plan_promotion_code"`
|
||||
}
|
||||
|
||||
type SharedImageGallery struct {
|
||||
Subscription string `mapstructure:"subscription"`
|
||||
ResourceGroup string `mapstructure:"resource_group"`
|
||||
GalleryName string `mapstructure:"gallery_name"`
|
||||
ImageName string `mapstructure:"image_name"`
|
||||
ImageVersion string `mapstructure:"image_version"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
@@ -89,9 +79,6 @@ type Config struct {
|
||||
CaptureNamePrefix string `mapstructure:"capture_name_prefix"`
|
||||
CaptureContainerName string `mapstructure:"capture_container_name"`
|
||||
|
||||
// Shared Gallery
|
||||
SharedGallery SharedImageGallery `mapstructure:"shared_image_gallery"`
|
||||
|
||||
// Compute
|
||||
ImagePublisher string `mapstructure:"image_publisher"`
|
||||
ImageOffer string `mapstructure:"image_offer"`
|
||||
@@ -106,13 +93,11 @@ type Config struct {
|
||||
Location string `mapstructure:"location"`
|
||||
VMSize string `mapstructure:"vm_size"`
|
||||
|
||||
ManagedImageResourceGroupName string `mapstructure:"managed_image_resource_group_name"`
|
||||
ManagedImageName string `mapstructure:"managed_image_name"`
|
||||
ManagedImageStorageAccountType string `mapstructure:"managed_image_storage_account_type"`
|
||||
ManagedImageOSDiskSnapshotName string `mapstructure:"managed_image_os_disk_snapshot_name"`
|
||||
ManagedImageDataDiskSnapshotPrefix string `mapstructure:"managed_image_data_disk_snapshot_prefix"`
|
||||
managedImageStorageAccountType compute.StorageAccountTypes
|
||||
manageImageLocation string
|
||||
ManagedImageResourceGroupName string `mapstructure:"managed_image_resource_group_name"`
|
||||
ManagedImageName string `mapstructure:"managed_image_name"`
|
||||
ManagedImageStorageAccountType string `mapstructure:"managed_image_storage_account_type"`
|
||||
managedImageStorageAccountType compute.StorageAccountTypes
|
||||
manageImageLocation string
|
||||
|
||||
// Deployment
|
||||
AzureTags map[string]*string `mapstructure:"azure_tags"`
|
||||
@@ -330,7 +315,7 @@ func setSshValues(c *Config) error {
|
||||
}
|
||||
|
||||
if c.Comm.SSHPrivateKeyFile != "" {
|
||||
privateKeyBytes, err := c.Comm.ReadSSHPrivateKeyFile()
|
||||
privateKeyBytes, err := ioutil.ReadFile(c.Comm.SSHPrivateKeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -518,14 +503,17 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
|
||||
if isUseDeviceLogin(c) {
|
||||
c.useDeviceLogin = true
|
||||
} else {
|
||||
if c.ClientID == "" && c.ClientSecret != "" || c.ClientID != "" && c.ClientSecret == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_id and client_secret must be specified together or not specified at all"))
|
||||
if c.ClientID == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_id must be specified"))
|
||||
}
|
||||
|
||||
if c.ClientID != "" && c.SubscriptionID == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A subscription_id must be specified when client_id & client_secret are"))
|
||||
if c.ClientSecret == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_secret must be specified"))
|
||||
}
|
||||
|
||||
if c.SubscriptionID == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A subscription_id must be specified"))
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////
|
||||
@@ -584,36 +572,19 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
|
||||
|
||||
isImageUrl := c.ImageUrl != ""
|
||||
isCustomManagedImage := c.CustomManagedImageName != "" || c.CustomManagedImageResourceGroupName != ""
|
||||
isSharedGallery := c.SharedGallery.GalleryName != ""
|
||||
isPlatformImage := c.ImagePublisher != "" || c.ImageOffer != "" || c.ImageSku != ""
|
||||
|
||||
countSourceInputs := toInt(isImageUrl) + toInt(isCustomManagedImage) + toInt(isPlatformImage) + toInt(isSharedGallery)
|
||||
countSourceInputs := toInt(isImageUrl) + toInt(isCustomManagedImage) + toInt(isPlatformImage)
|
||||
|
||||
if countSourceInputs > 1 {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Specify either a VHD (image_url), Image Reference (image_publisher, image_offer, image_sku), a Managed Disk (custom_managed_disk_image_name, custom_managed_disk_resource_group_name), or a Shared Gallery Image (shared_image_gallery)"))
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Specify either a VHD (image_url), Image Reference (image_publisher, image_offer, image_sku) or a Managed Disk (custom_managed_disk_image_name, custom_managed_disk_resource_group_name"))
|
||||
}
|
||||
|
||||
if isImageUrl && c.ManagedImageResourceGroupName != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A managed image must be created from a managed image, it cannot be created from a VHD."))
|
||||
}
|
||||
|
||||
if c.SharedGallery.GalleryName != "" {
|
||||
if c.SharedGallery.Subscription == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A shared_image_gallery.subscription must be specified"))
|
||||
}
|
||||
if c.SharedGallery.ResourceGroup == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A shared_image_gallery.resource_group must be specified"))
|
||||
}
|
||||
if c.SharedGallery.ImageName == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A shared_image_gallery.image_name must be specified"))
|
||||
}
|
||||
if c.CaptureContainerName != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("VHD Target [capture_container_name] is not supported when using Shared Image Gallery as source. Use managed_image_resource_group_name instead."))
|
||||
}
|
||||
if c.CaptureNamePrefix != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("VHD Target [capture_name_prefix] is not supported when using Shared Image Gallery as source. Use managed_image_name instead."))
|
||||
}
|
||||
} else if c.ImageUrl == "" && c.CustomManagedImageName == "" {
|
||||
if c.ImageUrl == "" && c.CustomManagedImageName == "" {
|
||||
if c.ImagePublisher == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_publisher must be specified"))
|
||||
}
|
||||
@@ -689,18 +660,6 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
|
||||
}
|
||||
}
|
||||
|
||||
if c.ManagedImageOSDiskSnapshotName != "" {
|
||||
if ok, err := assertManagedImageOSDiskSnapshotName(c.ManagedImageOSDiskSnapshotName, "managed_image_os_disk_snapshot_name"); !ok {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.ManagedImageDataDiskSnapshotPrefix != "" {
|
||||
if ok, err := assertManagedImageDataDiskSnapshotName(c.ManagedImageDataDiskSnapshotPrefix, "managed_image_data_disk_snapshot_prefix"); !ok {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.VirtualNetworkName == "" && c.VirtualNetworkResourceGroupName != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_resource_group_name is specified, so must virtual_network_name"))
|
||||
}
|
||||
@@ -754,20 +713,6 @@ func assertManagedImageName(name, setting string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func assertManagedImageOSDiskSnapshotName(name, setting string) (bool, error) {
|
||||
if !isValidAzureName(reSnapshotName, name) {
|
||||
return false, fmt.Errorf("The setting %s must only contain characters from a-z, A-Z, 0-9 and _ and the maximum length is 80 characters", setting)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func assertManagedImageDataDiskSnapshotName(name, setting string) (bool, error) {
|
||||
if !isValidAzureName(reSnapshotPrefix, name) {
|
||||
return false, fmt.Errorf("The setting %s must only contain characters from a-z, A-Z, 0-9 and _ and the maximum length (excluding the prefix) is 60 characters", setting)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func assertResourceGroupName(rgn, setting string) (bool, error) {
|
||||
if !isValidAzureName(reResourceGroupName, rgn) {
|
||||
return false, fmt.Errorf("The setting %s must match the regular expression %q, and not end with a '-' or '.'.", setting, validResourceGroupNameRe)
|
||||
|
||||
@@ -124,87 +124,6 @@ func TestConfigShouldNotDefaultImageVersionIfCustomImage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_newConfig_MSI(t *testing.T) {
|
||||
baseConfig := map[string]string{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"location": "ignore",
|
||||
"image_url": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []interface{}
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no client_id and no client_secret should enable MSI auth",
|
||||
args: []interface{}{
|
||||
baseConfig,
|
||||
getPackerConfiguration(),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "subscription_id is will be taken from MSI",
|
||||
args: []interface{}{
|
||||
baseConfig,
|
||||
map[string]string{
|
||||
"subscription_id": "error",
|
||||
},
|
||||
getPackerConfiguration(),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "client_id without client_secret should error",
|
||||
args: []interface{}{
|
||||
baseConfig,
|
||||
map[string]string{
|
||||
"client_id": "error",
|
||||
},
|
||||
getPackerConfiguration(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "client_secret without client_id should error",
|
||||
args: []interface{}{
|
||||
baseConfig,
|
||||
map[string]string{
|
||||
"client_secret": "error",
|
||||
},
|
||||
getPackerConfiguration(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing subscription_id",
|
||||
args: []interface{}{
|
||||
baseConfig,
|
||||
map[string]string{
|
||||
"client_id": "ok",
|
||||
"client_secret": "ok",
|
||||
},
|
||||
getPackerConfiguration(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, _, err := newConfig(tt.args...)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("newConfig() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldNormalizeOSTypeCase(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"capture_name_prefix": "ignore",
|
||||
@@ -628,107 +547,6 @@ func TestConfigShouldRejectMalformedCaptureContainerName(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectMalformedManagedImageOSDiskSnapshotName(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
"managed_image_resource_group_name": "ignore",
|
||||
"managed_image_name": "ignore",
|
||||
"managed_image_os_disk_snapshot_name": "ignore",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
}
|
||||
|
||||
wellFormedManagedImageOSDiskSnapshotName := []string{
|
||||
"AbcdefghijklmnopqrstuvwX",
|
||||
"underscore_underscore",
|
||||
"0leading_number",
|
||||
"really_loooooooooooooooooooooooooooooooooooooooooooooooooong",
|
||||
}
|
||||
|
||||
for _, x := range wellFormedManagedImageOSDiskSnapshotName {
|
||||
config["managed_image_os_disk_snapshot_name"] = x
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected test to pass, but it failed with the well-formed managed_image_os_disk_snapshot_name set to %q.", x)
|
||||
}
|
||||
}
|
||||
|
||||
malformedManagedImageOSDiskSnapshotName := []string{
|
||||
"min_ten",
|
||||
"-leading-hyphen",
|
||||
"trailing-hyphen-",
|
||||
"trailing-period.",
|
||||
"punc-!@#$%^&*()_+-=-punc",
|
||||
"really_looooooooooooooooooooooooooooooooooooooooooooooooooooooong_exceeding_80_char_limit",
|
||||
}
|
||||
|
||||
for _, x := range malformedManagedImageOSDiskSnapshotName {
|
||||
config["managed_image_os_disk_snapshot_name"] = x
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected test to fail, but it succeeded with the malformed managed_image_os_disk_snapshot_name set to %q.", x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectMalformedManagedImageDataDiskSnapshotPrefix(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
"managed_image_resource_group_name": "ignore",
|
||||
"managed_image_name": "ignore",
|
||||
"managed_image_data_disk_snapshot_prefix": "ignore",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
}
|
||||
|
||||
wellFormedManagedImageDataDiskSnapshotPrefix := []string{
|
||||
"min_ten_chars",
|
||||
"AbcdefghijklmnopqrstuvwX",
|
||||
"underscore_underscore",
|
||||
"0leading_number",
|
||||
"less_than_sixty_characters",
|
||||
}
|
||||
|
||||
for _, x := range wellFormedManagedImageDataDiskSnapshotPrefix {
|
||||
config["managed_image_data_disk_snapshot_prefix"] = x
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected test to pass, but it failed with the well-formed managed_image_data_disk_snapshot_prefix set to %q.", x)
|
||||
}
|
||||
}
|
||||
|
||||
malformedManagedImageDataDiskSnapshotPrefix := []string{
|
||||
"more_ten",
|
||||
"-leading-hyphen",
|
||||
"trailing-hyphen-",
|
||||
"trailing-period.",
|
||||
"punc-!@#$%^&*()_+-=-punc",
|
||||
"really_looooooooooooooooooooooooooooooooooooooooooooooooooooooong_exceeding_60_char_limit",
|
||||
}
|
||||
|
||||
for _, x := range malformedManagedImageDataDiskSnapshotPrefix {
|
||||
config["managed_image_data_disk_snapshot_prefix"] = x
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected test to fail, but it succeeded with the malformed managed_image_data_disk_snapshot_prefix set to %q.", x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldAcceptTags(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"capture_name_prefix": "ignore",
|
||||
@@ -891,150 +709,6 @@ func TestConfigShouldRejectMissingCustomDataFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectManagedImageOSDiskSnapshotNameWithoutManagedImageName(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
"managed_image_resource_group_name": "ignore",
|
||||
"managed_image_os_disk_snapshot_name": "ignore",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Fatal("expected config to reject Managed Image build with OS disk snapshot name but without managed image name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectManagedImageOSDiskSnapshotNameWithoutManagedImageResourceGroupName(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
"managed_image_name": "ignore",
|
||||
"managed_image_os_disk_snapshot_name": "ignore",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Fatal("expected config to reject Managed Image build with OS disk snapshot name but without managed image resource group name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectImageDataDiskSnapshotPrefixWithoutManagedImageName(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
"managed_image_resource_group_name": "ignore",
|
||||
"managed_image_data_disk_snapshot_prefix": "ignore",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Fatal("expected config to reject Managed Image build with data disk snapshot prefix but without managed image name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectImageDataDiskSnapshotPrefixWithoutManagedImageResourceGroupName(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
"managed_image_name": "ignore",
|
||||
"managed_image_data_disk_snapshot_prefix": "ignore",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Fatal("expected config to reject Managed Image build with data disk snapshot prefix but without managed image resource group name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldAcceptManagedImageOSDiskSnapshotNameAndManagedImageDataDiskSnapshotPrefix(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
"managed_image_resource_group_name": "ignore",
|
||||
"managed_image_name": "ignore",
|
||||
"managed_image_os_disk_snapshot_name": "ignore_ignore",
|
||||
"managed_image_data_disk_snapshot_prefix": "ignore_ignore",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal("expected config to accept platform managed image build")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectManagedImageOSDiskSnapshotNameAndManagedImageDataDiskSnapshotPrefixWithCaptureContainerName(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
"capture_container_name": "ignore",
|
||||
"managed_image_os_disk_snapshot_name": "ignore_ignore",
|
||||
"managed_image_data_disk_snapshot_prefix": "ignore_ignore",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Fatal("expected config to reject Managed Image build with data disk snapshot prefix and OS disk snapshot name with capture container name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectManagedImageOSDiskSnapshotNameAndManagedImageDataDiskSnapshotPrefixWithCaptureNamePrefix(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
"capture_name_prefix": "ignore",
|
||||
"managed_image_os_disk_snapshot_name": "ignore_ignore",
|
||||
"managed_image_data_disk_snapshot_prefix": "ignore_ignore",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Fatal("expected config to reject Managed Image build with data disk snapshot prefix and OS disk snapshot name with capture name prefix")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldAcceptPlatformManagedImageBuild(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_offer": "ignore",
|
||||
@@ -1620,51 +1294,6 @@ func TestConfigShouldAllowAsyncResourceGroupOverrideBadValue(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
func TestConfigShouldAllowSharedImageGalleryOptions(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": "linux",
|
||||
"shared_image_gallery": map[string]string{
|
||||
"subscription": "ignore",
|
||||
"resource_group": "ignore",
|
||||
"gallery_name": "ignore",
|
||||
"image_name": "ignore",
|
||||
"image_version": "ignore",
|
||||
},
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Log("expected config to accept Shared Image Gallery options", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectSharedImageGalleryWithVhdTarget(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": "linux",
|
||||
"shared_image_gallery": map[string]string{
|
||||
"subscription": "ignore",
|
||||
"resource_group": "ignore",
|
||||
"gallery_name": "ignore",
|
||||
"image_name": "ignore",
|
||||
"image_version": "ignore",
|
||||
},
|
||||
"resource_group_name": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"capture_name_prefix": "ignore",
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Log("expected an error if Shared Image Gallery source is used with VHD target", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getArmBuilderConfiguration() map[string]string {
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepSnapshotDataDisks struct {
|
||||
client *AzureClient
|
||||
create func(ctx context.Context, resourceGroupName string, srcUriVhd string, location string, tags map[string]*string, dstSnapshotName string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
isManagedImage bool
|
||||
}
|
||||
|
||||
func NewStepSnapshotDataDisks(client *AzureClient, ui packer.Ui, isManagedImage bool) *StepSnapshotDataDisks {
|
||||
var step = &StepSnapshotDataDisks{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
isManagedImage: isManagedImage,
|
||||
}
|
||||
|
||||
step.create = step.createDataDiskSnapshot
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepSnapshotDataDisks) createDataDiskSnapshot(ctx context.Context, resourceGroupName string, srcUriVhd string, location string, tags map[string]*string, dstSnapshotName string) error {
|
||||
|
||||
srcVhdToSnapshot := compute.Snapshot{
|
||||
DiskProperties: &compute.DiskProperties{
|
||||
CreationData: &compute.CreationData{
|
||||
CreateOption: compute.Copy,
|
||||
SourceResourceID: to.StringPtr(srcUriVhd),
|
||||
},
|
||||
},
|
||||
Location: to.StringPtr(location),
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
f, err := s.client.SnapshotsClient.CreateOrUpdate(ctx, resourceGroupName, dstSnapshotName, srcVhdToSnapshot)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = f.WaitForCompletion(ctx, s.client.SnapshotsClient.Client)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
createdSnapshot, err := f.Result(s.client.SnapshotsClient)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
s.say(fmt.Sprintf(" -> Managed Image Data Disk Snapshot : '%s'", *(createdSnapshot.ID)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StepSnapshotDataDisks) Run(ctx context.Context, stateBag multistep.StateBag) multistep.StepAction {
|
||||
if s.isManagedImage {
|
||||
|
||||
s.say("Taking snapshot of data disk ...")
|
||||
|
||||
var resourceGroupName = stateBag.Get(constants.ArmManagedImageResourceGroupName).(string)
|
||||
var location = stateBag.Get(constants.ArmLocation).(string)
|
||||
var tags = stateBag.Get(constants.ArmTags).(map[string]*string)
|
||||
var additionalDisks = stateBag.Get(constants.ArmAdditionalDiskVhds).([]string)
|
||||
var dstSnapshotPrefix = stateBag.Get(constants.ArmManagedImageDataDiskSnapshotPrefix).(string)
|
||||
|
||||
for i, disk := range additionalDisks {
|
||||
dstSnapshotName := dstSnapshotPrefix + strconv.Itoa(i)
|
||||
err := s.create(ctx, resourceGroupName, disk, location, tags, dstSnapshotName)
|
||||
|
||||
if err != nil {
|
||||
stateBag.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepSnapshotDataDisks) Cleanup(multistep.StateBag) {
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStepSnapshotDataDisksShouldFailIfSnapshotFails(t *testing.T) {
|
||||
var testSubject = &StepSnapshotDataDisks{
|
||||
create: func(context.Context, string, string, string, map[string]*string, string) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
isManagedImage: true,
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepSnapshotDataDisks()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepSnapshotDataDisksShouldPassIfSnapshotPasses(t *testing.T) {
|
||||
var testSubject = &StepSnapshotDataDisks{
|
||||
create: func(context.Context, string, string, string, map[string]*string, string) error {
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
isManagedImage: true,
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepSnapshotDataDisks()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepSnapshotDataDisks() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmManagedImageResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
|
||||
|
||||
value := "Unit Test: Tags"
|
||||
tags := map[string]*string{
|
||||
"tag01": &value,
|
||||
}
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
|
||||
stateBag.Put(constants.ArmAdditionalDiskVhds, []string{"subscriptions/123-456-789/resourceGroups/existingresourcegroup/providers/Microsoft.Compute/disks/osdisk"})
|
||||
stateBag.Put(constants.ArmManagedImageDataDiskSnapshotPrefix, "Unit Test: ManagedImageDataDiskSnapshotPrefix")
|
||||
|
||||
return stateBag
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepSnapshotOSDisk struct {
|
||||
client *AzureClient
|
||||
create func(ctx context.Context, resourceGroupName string, srcUriVhd string, location string, tags map[string]*string, dstSnapshotName string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
isManagedImage bool
|
||||
}
|
||||
|
||||
func NewStepSnapshotOSDisk(client *AzureClient, ui packer.Ui, isManagedImage bool) *StepSnapshotOSDisk {
|
||||
var step = &StepSnapshotOSDisk{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
isManagedImage: isManagedImage,
|
||||
}
|
||||
|
||||
step.create = step.createSnapshot
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepSnapshotOSDisk) createSnapshot(ctx context.Context, resourceGroupName string, srcUriVhd string, location string, tags map[string]*string, dstSnapshotName string) error {
|
||||
|
||||
srcVhdToSnapshot := compute.Snapshot{
|
||||
DiskProperties: &compute.DiskProperties{
|
||||
CreationData: &compute.CreationData{
|
||||
CreateOption: compute.Copy,
|
||||
SourceResourceID: to.StringPtr(srcUriVhd),
|
||||
},
|
||||
},
|
||||
Location: to.StringPtr(location),
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
f, err := s.client.SnapshotsClient.CreateOrUpdate(ctx, resourceGroupName, dstSnapshotName, srcVhdToSnapshot)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = f.WaitForCompletion(ctx, s.client.SnapshotsClient.Client)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
createdSnapshot, err := f.Result(s.client.SnapshotsClient)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
s.say(fmt.Sprintf(" -> Managed Image OS Disk Snapshot : '%s'", *(createdSnapshot.ID)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StepSnapshotOSDisk) Run(ctx context.Context, stateBag multistep.StateBag) multistep.StepAction {
|
||||
if s.isManagedImage {
|
||||
|
||||
s.say("Taking snapshot of OS disk ...")
|
||||
|
||||
var resourceGroupName = stateBag.Get(constants.ArmManagedImageResourceGroupName).(string)
|
||||
var location = stateBag.Get(constants.ArmLocation).(string)
|
||||
var tags = stateBag.Get(constants.ArmTags).(map[string]*string)
|
||||
var srcUriVhd = stateBag.Get(constants.ArmOSDiskVhd).(string)
|
||||
var dstSnapshotName = stateBag.Get(constants.ArmManagedImageOSDiskSnapshotName).(string)
|
||||
|
||||
err := s.create(ctx, resourceGroupName, srcUriVhd, location, tags, dstSnapshotName)
|
||||
|
||||
if err != nil {
|
||||
stateBag.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepSnapshotOSDisk) Cleanup(multistep.StateBag) {
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStepSnapshotOSDiskShouldFailIfSnapshotFails(t *testing.T) {
|
||||
var testSubject = &StepSnapshotOSDisk{
|
||||
create: func(context.Context, string, string, string, map[string]*string, string) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
isManagedImage: true,
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepSnapshotOSDisk()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepSnapshotOSDiskShouldPassIfSnapshotPasses(t *testing.T) {
|
||||
var testSubject = &StepSnapshotOSDisk{
|
||||
create: func(context.Context, string, string, string, map[string]*string, string) error {
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
isManagedImage: true,
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepSnapshotOSDisk()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepSnapshotOSDisk() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmManagedImageResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
|
||||
|
||||
value := "Unit Test: Tags"
|
||||
tags := map[string]*string{
|
||||
"tag01": &value,
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
|
||||
stateBag.Put(constants.ArmOSDiskVhd, "subscriptions/123-456-789/resourceGroups/existingresourcegroup/providers/Microsoft.Compute/disks/osdisk")
|
||||
stateBag.Put(constants.ArmManagedImageOSDiskSnapshotName, "Unit Test: ManagedImageOSDiskSnapshotName")
|
||||
|
||||
return stateBag
|
||||
}
|
||||
@@ -72,18 +72,6 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
|
||||
config.ImageVersion)
|
||||
|
||||
builder.SetManagedMarketplaceImage(config.Location, config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion, imageID, config.managedImageStorageAccountType)
|
||||
} else if config.SharedGallery.Subscription != "" {
|
||||
imageID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/galleries/%s/images/%s",
|
||||
config.SharedGallery.Subscription,
|
||||
config.SharedGallery.ResourceGroup,
|
||||
config.SharedGallery.GalleryName,
|
||||
config.SharedGallery.ImageName)
|
||||
if config.SharedGallery.ImageVersion != "" {
|
||||
imageID += fmt.Sprintf("/versions/%s",
|
||||
config.SharedGallery.ImageVersion)
|
||||
}
|
||||
|
||||
builder.SetSharedGalleryImage(config.Location, imageID)
|
||||
} else {
|
||||
builder.SetMarketPlaceImage(config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion)
|
||||
}
|
||||
|
||||
@@ -30,11 +30,9 @@ const (
|
||||
ArmVirtualMachineCaptureParameters string = "arm.VirtualMachineCaptureParameters"
|
||||
ArmIsExistingResourceGroup string = "arm.IsExistingResourceGroup"
|
||||
|
||||
ArmIsManagedImage string = "arm.IsManagedImage"
|
||||
ArmManagedImageResourceGroupName string = "arm.ManagedImageResourceGroupName"
|
||||
ArmManagedImageLocation string = "arm.ManagedImageLocation"
|
||||
ArmManagedImageName string = "arm.ManagedImageName"
|
||||
ArmAsyncResourceGroupDelete string = "arm.AsyncResourceGroupDelete"
|
||||
ArmManagedImageOSDiskSnapshotName string = "arm.ManagedImageOSDiskSnapshotName"
|
||||
ArmManagedImageDataDiskSnapshotPrefix string = "arm.ManagedImageDataDiskSnapshotPrefix"
|
||||
ArmIsManagedImage string = "arm.IsManagedImage"
|
||||
ArmManagedImageResourceGroupName string = "arm.ManagedImageResourceGroupName"
|
||||
ArmManagedImageLocation string = "arm.ManagedImageLocation"
|
||||
ArmManagedImageName string = "arm.ManagedImageName"
|
||||
ArmAsyncResourceGroupDelete string = "arm.AsyncResourceGroupDelete"
|
||||
)
|
||||
|
||||
@@ -145,21 +145,6 @@ func (s *TemplateBuilder) SetManagedMarketplaceImage(location, publisher, offer,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TemplateBuilder) SetSharedGalleryImage(location, imageID string) error {
|
||||
resource, err := s.getResourceByType(resourceVirtualMachine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.setVariable("apiVersion", "2018-04-01") // Required for Shared Image Gallery
|
||||
profile := resource.Properties.StorageProfile
|
||||
profile.ImageReference = &compute.ImageReference{ID: &imageID}
|
||||
profile.OsDisk.OsType = s.osType
|
||||
profile.OsDisk.Vhd = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TemplateBuilder) SetMarketPlaceImage(publisher, offer, sku, version string) error {
|
||||
resource, err := s.getResourceByType(resourceVirtualMachine)
|
||||
if err != nil {
|
||||
|
||||
-170
@@ -1,170 +0,0 @@
|
||||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminPassword": "[parameters('adminPassword')]",
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "--test-ssh-authorized-key--",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"imageReference": {
|
||||
"id": "/subscriptions/ignore/resourceGroups/ignore/providers/Microsoft.Compute/galleries/ignore/images/ignore"
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"osType": "Linux"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2018-04-01",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "[parameters('subnetName')]",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "[parameters('virtualNetworkName')]",
|
||||
"virtualNetworkResourceGroup": "[resourceGroup().name]",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
||||
@@ -181,32 +181,3 @@ func TestBuildWindows02(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Shared Image Gallery Build
|
||||
func TestSharedImageGallery00(t *testing.T) {
|
||||
testSubject, err := NewTemplateBuilder(BasicTemplate)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = testSubject.BuildLinux("--test-ssh-authorized-key--")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
imageID := "/subscriptions/ignore/resourceGroups/ignore/providers/Microsoft.Compute/galleries/ignore/images/ignore"
|
||||
err = testSubject.SetSharedGalleryImage("westcentralus", imageID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
doc, err := testSubject.ToJSON()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONBytes(t, []byte(*doc))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package cloudstack
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
@@ -22,10 +23,10 @@ func (s *stepKeypair) Run(_ context.Context, state multistep.StateBag) multistep
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if s.Comm.SSHPrivateKeyFile != "" {
|
||||
ui.Say("Using existing SSH private key")
|
||||
privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile()
|
||||
privateKeyBytes, err := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
state.Put("error", fmt.Errorf(
|
||||
"Error loading configured private key file: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
|
||||
// Set up the state
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("config", b.config)
|
||||
state.Put("client", client)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
@@ -18,7 +18,7 @@ type stepCreateDroplet struct {
|
||||
func (s *stepCreateDroplet) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*godo.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
c := state.Get("config").(Config)
|
||||
sshKeyId := state.Get("ssh_key_id").(int)
|
||||
|
||||
// Create the droplet based on configuration
|
||||
|
||||
@@ -14,7 +14,7 @@ type stepDropletInfo struct{}
|
||||
func (s *stepDropletInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*godo.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
c := state.Get("config").(Config)
|
||||
dropletID := state.Get("droplet_id").(int)
|
||||
|
||||
ui.Say("Waiting for droplet to become active...")
|
||||
|
||||
@@ -14,7 +14,7 @@ type stepPowerOff struct{}
|
||||
|
||||
func (s *stepPowerOff) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*godo.Client)
|
||||
c := state.Get("config").(*Config)
|
||||
c := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
dropletId := state.Get("droplet_id").(int)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ type stepShutdown struct{}
|
||||
|
||||
func (s *stepShutdown) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*godo.Client)
|
||||
c := state.Get("config").(*Config)
|
||||
c := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
dropletId := state.Get("droplet_id").(int)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ type stepSnapshot struct{}
|
||||
func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*godo.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
c := state.Get("config").(Config)
|
||||
dropletId := state.Get("droplet_id").(int)
|
||||
var snapshotRegions []string
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package googlecompute
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
@@ -232,13 +231,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
errs = packer.MultiErrorAppend(fmt.Errorf("you may not specify a 'service_account_email' when 'disable_default_service_account' is true"))
|
||||
}
|
||||
|
||||
if c.StartupScriptFile != "" {
|
||||
if _, err := os.Stat(c.StartupScriptFile); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("startup_script_file: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any errors.
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, nil, errs
|
||||
|
||||
@@ -329,22 +329,6 @@ func TestConfigPrepareServiceAccount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPrepareStartupScriptFile(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"project_id": "project",
|
||||
"source_image": "foo",
|
||||
"ssh_username": "packer",
|
||||
"startup_script_file": "no-such-file",
|
||||
"zone": "us-central1-a",
|
||||
}
|
||||
|
||||
_, _, errs := NewConfig(config)
|
||||
|
||||
if errs == nil || !strings.Contains(errs.Error(), "startup_script_file") {
|
||||
t.Fatalf("should error: startup_script_file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigDefaults(t *testing.T) {
|
||||
cases := []struct {
|
||||
Read func(c *Config) interface{}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// processPrivateKeyFile takes a private key file and an optional passphrase
|
||||
// and decodes it to a byte slice.
|
||||
func processPrivateKeyFile(privateKeyFile, passphrase string) ([]byte, error) {
|
||||
rawPrivateKeyBytes, err := ioutil.ReadFile(privateKeyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed loading private key file: %s", err)
|
||||
}
|
||||
|
||||
PEMBlock, _ := pem.Decode(rawPrivateKeyBytes)
|
||||
if PEMBlock == nil {
|
||||
return nil, fmt.Errorf(
|
||||
"%s does not contain a valid private key", privateKeyFile)
|
||||
}
|
||||
|
||||
if x509.IsEncryptedPEMBlock(PEMBlock) {
|
||||
if passphrase == "" {
|
||||
return nil, errors.New("a passphrase must be specified when using an encrypted private key")
|
||||
}
|
||||
|
||||
decryptedPrivateKeyBytes, err := x509.DecryptPEMBlock(PEMBlock, []byte(passphrase))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed decrypting private key: %s", err)
|
||||
}
|
||||
|
||||
b := &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: decryptedPrivateKeyBytes,
|
||||
}
|
||||
return pem.EncodeToMemory(b), nil
|
||||
}
|
||||
|
||||
return rawPrivateKeyBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testPrivateKeyFile(t *testing.T) string {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
defer tf.Close()
|
||||
|
||||
b := &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: []byte("what"),
|
||||
}
|
||||
|
||||
if err := pem.Encode(tf, b); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return tf.Name()
|
||||
}
|
||||
|
||||
func TestProcesssPrivateKeyFile(t *testing.T) {
|
||||
path := testPrivateKeyFile(t)
|
||||
defer os.Remove(path)
|
||||
|
||||
data, err := processPrivateKeyFile(path, "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(data) <= 0 {
|
||||
t.Fatalf("bad: %#v", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessPrivateKeyFile_encrypted(t *testing.T) {
|
||||
// Encrypt the file
|
||||
b, err := x509.EncryptPEMBlock(rand.Reader,
|
||||
"RSA PRIVATE KEY",
|
||||
[]byte("what"),
|
||||
[]byte("password"),
|
||||
x509.PEMCipherAES128)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
err = pem.Encode(tf, b)
|
||||
tf.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
path := tf.Name()
|
||||
|
||||
// Should have an error with a bad password
|
||||
if _, err := processPrivateKeyFile(path, "bad"); err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
if _, err := processPrivateKeyFile(path, "password"); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
@@ -28,9 +29,10 @@ func (s *StepCreateSSHKey) Run(_ context.Context, state multistep.StateBag) mult
|
||||
|
||||
if config.Comm.SSHPrivateKeyFile != "" {
|
||||
ui.Say("Using existing SSH private key")
|
||||
privateKeyBytes, err := config.Comm.ReadSSHPrivateKeyFile()
|
||||
privateKeyBytes, err := ioutil.ReadFile(config.Comm.SSHPrivateKeyFile)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
state.Put("error", fmt.Errorf(
|
||||
"Error loading configured private key file: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package hcloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
||||
)
|
||||
|
||||
type Artifact struct {
|
||||
// The name of the snapshot
|
||||
snapshotName string
|
||||
|
||||
// The ID of the image
|
||||
snapshotId int
|
||||
|
||||
// The hcloudClient for making API calls
|
||||
hcloudClient *hcloud.Client
|
||||
}
|
||||
|
||||
func (*Artifact) BuilderId() string {
|
||||
return BuilderId
|
||||
}
|
||||
|
||||
func (*Artifact) Files() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
return strconv.Itoa(a.snapshotId)
|
||||
}
|
||||
|
||||
func (a *Artifact) String() string {
|
||||
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v)", a.snapshotName, a.snapshotId)
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Destroy() error {
|
||||
log.Printf("Destroying image: %d (%s)", a.snapshotId, a.snapshotName)
|
||||
_, err := a.hcloudClient.Image.Delete(context.TODO(), &hcloud.Image{ID: a.snapshotId})
|
||||
return err
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package hcloud
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestArtifact_Impl(t *testing.T) {
|
||||
var _ packer.Artifact = (*Artifact)(nil)
|
||||
}
|
||||
|
||||
func TestArtifactId(t *testing.T) {
|
||||
a := &Artifact{"packer-foobar", 42, nil}
|
||||
expected := "42"
|
||||
|
||||
if a.Id() != expected {
|
||||
t.Fatalf("artifact ID should match: %v", expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactString(t *testing.T) {
|
||||
a := &Artifact{"packer-foobar", 42, nil}
|
||||
expected := "A snapshot was created: 'packer-foobar' (ID: 42)"
|
||||
|
||||
if a.String() != expected {
|
||||
t.Fatalf("artifact string should match: %v", expected)
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package hcloud
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
||||
)
|
||||
|
||||
// The unique id for the builder
|
||||
const BuilderId = "hcloud.builder"
|
||||
|
||||
type Builder struct {
|
||||
config Config
|
||||
runner multistep.Runner
|
||||
hcloudClient *hcloud.Client
|
||||
}
|
||||
|
||||
var pluginVersion = "1.0.0"
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
config, warnings, errs := NewConfig(raws...)
|
||||
if errs != nil {
|
||||
return warnings, errs
|
||||
}
|
||||
b.config = *config
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
opts := []hcloud.ClientOption{
|
||||
hcloud.WithToken(b.config.HCloudToken),
|
||||
hcloud.WithEndpoint(b.config.Endpoint),
|
||||
hcloud.WithPollInterval(b.config.PollInterval),
|
||||
hcloud.WithApplication("hcloud-packer", pluginVersion),
|
||||
}
|
||||
b.hcloudClient = hcloud.NewClient(opts...)
|
||||
// Set up the state
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("hcloudClient", b.hcloudClient)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
&stepCreateSSHKey{
|
||||
Debug: b.config.PackerDebug,
|
||||
DebugKeyPath: fmt.Sprintf("ssh_key_%s.pem", b.config.PackerBuildName),
|
||||
},
|
||||
&stepCreateServer{},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.Comm,
|
||||
Host: getServerIP,
|
||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&common.StepCleanupTempKeys{
|
||||
Comm: &b.config.Comm,
|
||||
},
|
||||
&stepShutdownServer{},
|
||||
&stepCreateSnapshot{},
|
||||
}
|
||||
// Run the steps
|
||||
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(state)
|
||||
// If there was an error, return that
|
||||
if rawErr, ok := state.GetOk("error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("snapshot_name"); !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
artifact := &Artifact{
|
||||
snapshotName: state.Get("snapshot_name").(string),
|
||||
snapshotId: state.Get("snapshot_id").(int),
|
||||
hcloudClient: b.hcloudClient,
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package hcloud
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer/helper/builder/testing"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccBasic,
|
||||
})
|
||||
}
|
||||
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
if v := os.Getenv("HCLOUD_TOKEN"); v == "" {
|
||||
t.Fatal("HCLOUD_TOKEN must be set for acceptance tests")
|
||||
}
|
||||
}
|
||||
|
||||
const testBuilderAccBasic = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
"location": "nbg1",
|
||||
"server_type": "cx11",
|
||||
"image": "ubuntu-18.04",
|
||||
"user_data": "",
|
||||
"user_data_file": ""
|
||||
}]
|
||||
}
|
||||
`
|
||||
@@ -1,136 +0,0 @@
|
||||
package hcloud
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/uuid"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
HCloudToken string `mapstructure:"token"`
|
||||
Endpoint string `mapstructure:"endpoint"`
|
||||
PollInterval time.Duration `mapstructure:"poll_interval"`
|
||||
|
||||
ServerName string `mapstructure:"server_name"`
|
||||
Location string `mapstructure:"location"`
|
||||
ServerType string `mapstructure:"server_type"`
|
||||
Image string `mapstructure:"image"`
|
||||
|
||||
SnapshotName string `mapstructure:"snapshot_name"`
|
||||
SnapshotLabels map[string]string `mapstructure:"snapshot_labels"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
UserDataFile string `mapstructure:"user_data_file"`
|
||||
SSHKeys []string `mapstructure:"ssh_keys"`
|
||||
|
||||
RescueMode string `mapstructure:"rescue"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
c := new(Config)
|
||||
|
||||
var md mapstructure.Metadata
|
||||
err := config.Decode(c, &config.DecodeOpts{
|
||||
Metadata: &md,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &c.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"run_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Defaults
|
||||
if c.HCloudToken == "" {
|
||||
c.HCloudToken = os.Getenv("HCLOUD_TOKEN")
|
||||
}
|
||||
if c.Endpoint == "" {
|
||||
if os.Getenv("HCLOUD_ENDPOINT") != "" {
|
||||
c.Endpoint = os.Getenv("HCLOUD_ENDPOINT")
|
||||
} else {
|
||||
c.Endpoint = hcloud.Endpoint
|
||||
}
|
||||
}
|
||||
if c.PollInterval == 0 {
|
||||
c.PollInterval = 500 * time.Millisecond
|
||||
}
|
||||
|
||||
if c.SnapshotName == "" {
|
||||
def, err := interpolate.Render("packer-{{timestamp}}", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Default to packer-{{ unix timestamp (utc) }}
|
||||
c.SnapshotName = def
|
||||
}
|
||||
|
||||
if c.ServerName == "" {
|
||||
// Default to packer-[time-ordered-uuid]
|
||||
c.ServerName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||
errs = packer.MultiErrorAppend(errs, es...)
|
||||
}
|
||||
if c.HCloudToken == "" {
|
||||
// Required configurations that will display errors if not set
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("token for auth must be specified"))
|
||||
}
|
||||
|
||||
if c.Location == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("location is required"))
|
||||
}
|
||||
|
||||
if c.ServerType == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("server type is required"))
|
||||
}
|
||||
|
||||
if c.Image == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("image is required"))
|
||||
}
|
||||
|
||||
if c.UserData != "" && c.UserDataFile != "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("only one of user_data or user_data_file can be specified"))
|
||||
} else if c.UserDataFile != "" {
|
||||
if _, err := os.Stat(c.UserDataFile); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New(fmt.Sprintf("user_data_file not found: %s", c.UserDataFile)))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, nil, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(c.HCloudToken)
|
||||
return c, nil, nil
|
||||
}
|
||||
|
||||
func getServerIP(state multistep.StateBag) (string, error) {
|
||||
return state.Get("server_ip").(string), nil
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
package hcloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
||||
)
|
||||
|
||||
type stepCreateServer struct {
|
||||
serverId int
|
||||
}
|
||||
|
||||
func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
sshKeyId := state.Get("ssh_key_id").(int)
|
||||
|
||||
// Create the server based on configuration
|
||||
ui.Say("Creating server...")
|
||||
|
||||
userData := c.UserData
|
||||
if c.UserDataFile != "" {
|
||||
contents, err := ioutil.ReadFile(c.UserDataFile)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
userData = string(contents)
|
||||
}
|
||||
|
||||
sshKeys := []*hcloud.SSHKey{{ID: sshKeyId}}
|
||||
for _, k := range c.SSHKeys {
|
||||
sshKey, _, err := client.SSHKey.Get(ctx, k)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
state.Put("error", fmt.Errorf("Error fetching SSH key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
sshKeys = append(sshKeys, sshKey)
|
||||
}
|
||||
|
||||
serverCreateResult, _, err := client.Server.Create(context.TODO(), hcloud.ServerCreateOpts{
|
||||
Name: c.ServerName,
|
||||
ServerType: &hcloud.ServerType{Name: c.ServerType},
|
||||
Image: &hcloud.Image{Name: c.Image},
|
||||
SSHKeys: sshKeys,
|
||||
Location: &hcloud.Location{Name: c.Location},
|
||||
UserData: userData,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating server: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("server_ip", serverCreateResult.Server.PublicNet.IPv4.IP.String())
|
||||
// We use this in cleanup
|
||||
s.serverId = serverCreateResult.Server.ID
|
||||
|
||||
// Store the server id for later
|
||||
state.Put("server_id", serverCreateResult.Server.ID)
|
||||
|
||||
if err := waitForAction(context.TODO(), client, serverCreateResult.Action); err != nil {
|
||||
err := fmt.Errorf("Error creating server: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
for _, nextAction := range serverCreateResult.NextActions {
|
||||
if err := waitForAction(context.TODO(), client, nextAction); err != nil {
|
||||
err := fmt.Errorf("Error creating server: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
if c.RescueMode != "" {
|
||||
if err := setRescue(context.TODO(), client, serverCreateResult.Server, c.RescueMode, sshKeys); err != nil {
|
||||
err := fmt.Errorf("Error enabling rescue mode: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
|
||||
// If the serverID isn't there, we probably never created it
|
||||
if s.serverId == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Destroy the server we just created
|
||||
ui.Say("Destroying server...")
|
||||
_, err := client.Server.Delete(context.TODO(), &hcloud.Server{ID: s.serverId})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error destroying server. Please destroy it manually: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
func setRescue(ctx context.Context, client *hcloud.Client, server *hcloud.Server, rescue string, sshKeys []*hcloud.SSHKey) error {
|
||||
rescueChanged := false
|
||||
if server.RescueEnabled {
|
||||
rescueChanged = true
|
||||
action, _, err := client.Server.DisableRescue(ctx, server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := waitForAction(ctx, client, action); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rescue != "" {
|
||||
rescueChanged = true
|
||||
res, _, err := client.Server.EnableRescue(ctx, server, hcloud.ServerEnableRescueOpts{
|
||||
Type: hcloud.ServerRescueType(rescue),
|
||||
SSHKeys: sshKeys,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := waitForAction(ctx, client, res.Action); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rescueChanged {
|
||||
action, _, err := client.Server.Reset(ctx, server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := waitForAction(ctx, client, action); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForAction(ctx context.Context, client *hcloud.Client, action *hcloud.Action) error {
|
||||
_, errCh := client.Action.WatchProgress(ctx, action)
|
||||
if err := <-errCh; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user