Compare commits

..

1 Commits

Author SHA1 Message Date
Adrien Delorme fb1be8be30 Cut version 1.3.1 2018-09-14 11:50:44 -07:00
1399 changed files with 22531 additions and 75336 deletions
-12
View File
@@ -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 -84
View File
@@ -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,10 +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 with
forks](https://help.github.com/articles/working-with-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
@@ -118,95 +114,36 @@ to use `git push ...`.
"[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. It is the contributor's
responsibility to address any changes requested. While reviewers are happy to
give guidance, it is unsustainable for us to perform the coding work necessary
to get a PR into a mergeable state.
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
@@ -243,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.
+1 -1
View File
@@ -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.
-6
View File
@@ -1,11 +1,5 @@
**DELETE THIS TEMPLATE BEFORE SUBMITTING**
In order to have a good experience with our community, we recommend that you
read the contributing guidelines for making a PR, and understand the lifecycle
of a Packer PR:
https://github.com/hashicorp/packer/blob/master/.github/CONTRIBUTING.md#opening-an-pull-request
Describe the change you are making here!
Please include tests. Check out these examples:
-1
View File
@@ -4,7 +4,6 @@
/src
/website/.sass-cache
/website/build
/website/tmp
.DS_Store
.vagrant
.idea
+4 -14
View File
@@ -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
-208
View File
@@ -1,216 +1,8 @@
## 1.3.4 (upcoming)
### IMPROVEMENTS:
* builder/alicloud: delete copied image and snapshots if corresponding options
are specified [GH-7050]
* builder/amazon: allow to interpolate more variables [GH-7059]
* builder/amazon: Check that the KMS key ID is valid [GH-7090]
* builder/amazon: Clean up logging for aws waiters so that it only runs once
per builder [GH-7080]
* builder/amazon: don't Cleanup Temp Keys when there is no communicator to
avoid a panic [GH-7100] [GH-7095]
* builder/amazon: Don't try to guess region from metadata if not set & update
aws-sdk-go [GH-7230]
* builder/amazon: Import error messages should now contain reason for failure
[GH-7207]
* builder/azure: add certificate authentication [GH-7189]
* builder/azure: allow to configure disk caching [GH-7061]
* builder/azure: use deallocate instead of just power-off [GH-7203]
* builder/hyperv: Add support for legacy network adapters on Hyper-V. [GH-7128]
* builder/hyperv: Allow user to set `version` option in the New-VM command.
[GH-7136]
* builder/openstack: Add `volume_size` option [GH-7130]
* builder/openstack: Don't require network v2 [GH-6933]
* builder/openstack: Support for tagging new images [GH-7037]
* builder/qemu: Add configuration options to specify cpu count and memory size
[GH-7156]
* builder/qemu: Add support for whpx accelerator to qemu builder [GH-7151]
* builder/vmware: Escape query as suggested in issue #7200 [GH-7223]
* core/shell: Add env vars "PACKER_HTTP_IP" and "PACKER_HTTP_PORT" to shell
provisioners [GH-7075]
* core: allow to use `-except` on post-processors [GH-7183]
* core: Clean up internal handling and creation of temporary directories
[GH-7102]
* core: Deprecate mitchellh/go-homedir package in favor of os/user [GH-7062]
* core: Download checksum match failures will now log the received checksum.
[GH-7210]
* core: Explicitly set ProxyFromEnvironment in httpclients when creating an aws
session [GH-7226]
* core: make packer inspect not print sensitive variables [GH-7084]
* post-processor/google: Add new `guest-os-features` option. [GH-7218]
* postprocessor/docker-import: Added `change` support [GH-7127]
* provisioner/ansible-remote: add `-o IdentitiesOnly=yes`as a default flag
[GH-7115]
* provisioner/chef-client: Elevated support for chef-client provisioner
[GH-7078]
* provisioner/puppet: Elevated support for puppet-* provisioner [GH-7078]
* provisioner/windows-restart: wait for already-scheduled reboot [GH-7056] and
ignore reboot specific errors [GH-7071]
### BUG FIXES:
* builder/azure: Ensure the Windows Guest Agent is fully functional before
Sysprep is executed. [GH-7176]
* builder/azure: Fix snapshot regression [GH-7111]
* builder/docker: Ensure that entrypoint and arguments get passed to docker,
not the image. [GH-7091]
* builder/hcloud: fix go mod dependency [GH-7099]
* builder/hcloud: prevent panic when ssh key was not passed [GH-7118]
* builder/hyperv: Fix the Hyper-V gen 1 guest boot order. [GH-7147]
* builder/hyperv: hyper-v builder no longer ignores `ssh_host` option.
[GH-7154]
* builder/oracle-oci: Fix crash that occurs when image is nil [GH-7126]
* builder/parallels: Fix attaching prl tools [GH-7158]
* builder/virtualbox: Fix handling of portcount argument for version 6 beta
[GH-7174] [GH-7094]
* builder/vmware: Fix bug caused by 'nil' dir field in artifact struct when
building locally [GH-7116]
* communicator/docker: Fix docker file provisioner on Windows [GH-7163]
* core: prioritize AppData over default user directory ( UserProfile )
[GH-7166]
* core: removed a flaky race condition in tests [GH-7119]
* postprocessor/vsphere: Stop setting HDDOrder, since it was breaking uploads
[GH-7108]
## 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
View File
@@ -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
View File
@@ -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
+13 -14
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
+48 -1
View File
@@ -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
@@ -27,7 +74,7 @@ build_script:
- git rev-parse HEAD
# go test $(go list ./... | grep -v vendor)
- ps: |
go.exe test -timeout=2m (go.exe list ./... `
go.exe test (go.exe list ./... `
|? { -not $_.Contains('/vendor/') } `
|? { $_ -ne 'github.com/hashicorp/packer/builder/parallels/common' } `
|? { $_ -ne 'github.com/hashicorp/packer/common' }`
+5 -36
View File
@@ -57,11 +57,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
return nil, err
}
if b.config.PackerConfig.PackerForce {
b.config.AlicloudImageForceDelete = true
b.config.AlicloudImageForceDeleteSnapshots = true
}
// Accumulate any errors
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, b.config.AlicloudAccessConfig.Prepare(&b.config.ctx)...)
@@ -73,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
}
@@ -83,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)
@@ -143,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,
@@ -167,31 +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,
AlicloudImageDestinationRegions: b.config.AlicloudImageConfig.AlicloudImageDestinationRegions,
AlicloudImageDestinationNames: b.config.AlicloudImageConfig.AlicloudImageDestinationNames,
})
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,
@@ -263,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
}
+2 -73
View File
@@ -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)
}
-139
View File
@@ -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())
}
}
+11 -14
View File
@@ -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"`
}
-18
View File
@@ -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 {
-12
View File
@@ -20,15 +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)
if prefix != "" {
err = fmt.Errorf("%s: %s", prefix, err)
}
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
-2
View File
@@ -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"`
-51
View File
@@ -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)
}
}
+2 -2
View File
@@ -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),
-12
View File
@@ -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),
+4 -2
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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{
+28 -35
View File
@@ -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 {
+1 -11
View File
@@ -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
}
}
-54
View File
@@ -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
}
@@ -15,82 +15,50 @@ type stepDeleteAlicloudImageSnapshots struct {
AlicloudImageForceDelete bool
AlicloudImageForceDeleteSnapshots bool
AlicloudImageName string
AlicloudImageDestinationRegions []string
AlicloudImageDestinationNames []string
}
func (s *stepDeleteAlicloudImageSnapshots) 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)
config := state.Get("config").(Config)
ui.Say("Deleting image snapshots.")
// Check for force delete
if s.AlicloudImageForceDelete {
err := s.deleteImageAndSnapshots(state, s.AlicloudImageName, config.AlicloudRegion)
if err != nil {
return halt(state, err, "")
}
numberOfName := len(s.AlicloudImageDestinationNames)
if numberOfName == 0 {
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
RegionId: common.Region(config.AlicloudRegion),
ImageName: s.AlicloudImageName,
})
if len(images) < 1 {
return multistep.ActionContinue
}
for index, destinationRegion := range s.AlicloudImageDestinationRegions {
if destinationRegion == config.AlicloudRegion {
for _, image := range images {
if image.ImageOwnerAlias != string(ecs.ImageOwnerSelf) {
log.Printf("You can only delete instances based on customized images %s ", image.ImageId)
continue
}
if index < numberOfName {
err = s.deleteImageAndSnapshots(state, s.AlicloudImageDestinationNames[index], destinationRegion)
if err != nil {
return halt(state, err, "")
err = client.DeleteImage(common.Region(config.AlicloudRegion), image.ImageId)
if err != nil {
err := fmt.Errorf("Failed to delete image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if s.AlicloudImageForceDeleteSnapshots {
for _, diskDevice := range image.DiskDeviceMappings.DiskDeviceMapping {
if err := client.DeleteSnapshot(diskDevice.SnapshotId); err != nil {
err := fmt.Errorf("Deleting ECS snapshot failed: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
} else {
break
}
}
}
return multistep.ActionContinue
}
func (s *stepDeleteAlicloudImageSnapshots) deleteImageAndSnapshots(state multistep.StateBag, imageName string, region string) error {
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
RegionId: common.Region(region),
ImageName: imageName,
})
if len(images) < 1 {
return nil
}
ui.Say(fmt.Sprintf("Deleting duplicated image and snapshot in %s: %s", region, imageName))
for _, image := range images {
if image.ImageOwnerAlias != string(ecs.ImageOwnerSelf) {
log.Printf("You can not delete non-customized images: %s ", image.ImageId)
continue
}
err = client.DeleteImage(common.Region(region), image.ImageId)
if err != nil {
err := fmt.Errorf("Failed to delete image: %s", err)
return err
}
if s.AlicloudImageForceDeleteSnapshots {
for _, diskDevice := range image.DiskDeviceMappings.DiskDeviceMapping {
if err := client.DeleteSnapshot(diskDevice.SnapshotId); err != nil {
err := fmt.Errorf("Deleting ECS snapshot failed: %s", err)
return err
}
}
}
}
return nil
}
func (s *stepDeleteAlicloudImageSnapshots) Cleanup(state multistep.StateBag) {
}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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,
+1 -3
View File
@@ -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)
+4 -8
View File
@@ -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)
+2 -5
View File
@@ -9,11 +9,9 @@ import (
"log"
"runtime"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
@@ -180,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
}
@@ -192,9 +191,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
if err != nil {
return nil, err
}
ec2conn := ec2.New(session, &aws.Config{
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
})
ec2conn := ec2.New(session)
wrappedCommand := func(command string) (string, error) {
ctx := b.config.ctx
-3
View File
@@ -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)
+2 -2
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
@@ -13,7 +14,6 @@ import (
"syscall"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer/tmp"
)
// Communicator is a special communicator that works by executing
@@ -67,7 +67,7 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
func (c *Communicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
dst = filepath.Join(c.Chroot, dst)
log.Printf("Uploading to chroot dir: %s", dst)
tf, err := tmp.File("packer-amazon-chroot")
tf, err := ioutil.TempFile("", "packer-amazon-chroot")
if err != nil {
return fmt.Errorf("Error preparing shell script: %s", err)
}
+2 -2
View File
@@ -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)
+61 -59
View File
@@ -1,39 +1,33 @@
package common
import (
"crypto/tls"
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/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"
cleanhttp "github.com/hashicorp/go-cleanhttp"
commonhelper "github.com/hashicorp/packer/helper/common"
"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
@@ -44,27 +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
config = config.WithMaxRetries(20)
if c.RawRegion != "" {
config = config.WithRegion(c.RawRegion)
} else if region := c.metadataRegion(); 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,
@@ -80,29 +72,30 @@ func (c *AccessConfig) Session() (*session.Session, error) {
}
}
sess, err := session.NewSessionWithOptions(opts)
if err != nil {
if sess, err := session.NewSessionWithOptions(opts); err != nil {
return nil, err
}
log.Printf("Found region %s", *sess.Config.Region)
c.session = sess
} 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
cp, err := c.session.Config.Credentials.Get()
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
return nil, fmt.Errorf("No valid credential sources found for AWS Builder. " +
"Please see https://www.packer.io/docs/builders/amazon.html#specifying-amazon-credentials " +
"for more information on providing credentials for the AWS Builder.")
} else {
return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)
cp, err := c.session.Config.Credentials.Get()
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
return nil, fmt.Errorf("No valid credential sources found for AWS Builder. " +
"Please see https://www.packer.io/docs/builders/amazon.html#specifying-amazon-credentials " +
"for more information on providing credentials for the AWS Builder.")
} else {
return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)
}
}
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
}
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
if c.DecodeAuthZMessages {
DecodeAuthZMessages(c.session)
}
LogEnvOverrideWarnings()
return c.session, nil
}
@@ -122,6 +115,25 @@ func (c *AccessConfig) IsChinaCloud() bool {
return strings.HasPrefix(c.SessionRegion(), "cn-")
}
// metadataRegion returns the region from the metadata service
func (c *AccessConfig) metadataRegion() string {
client := cleanhttp.DefaultClient()
// Keep the default timeout (100ms) low as we don't want to wait in non-EC2 environments
client.Timeout = 100 * time.Millisecond
ec2meta := ec2metadata.New(session.New(), &aws.Config{
HTTPClient: client,
})
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 region
}
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
@@ -135,21 +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
}
ec2conn := ec2.New(sess, &aws.Config{
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
})
return ec2conn, nil
}
+24 -34
View File
@@ -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) {
+39 -75
View File
@@ -3,7 +3,6 @@ package common
import (
"fmt"
"log"
"regexp"
"github.com/hashicorp/packer/template/interpolate"
)
@@ -19,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"`
@@ -57,29 +56,49 @@ 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"))
}
var kmsKeys []string
if len(c.AMIKmsKeyId) > 0 {
kmsKeys = append(kmsKeys, c.AMIKmsKeyId)
}
if len(c.AMIRegionKMSKeyIDs) > 0 {
for _, kmsKey := range c.AMIRegionKMSKeyIDs {
if len(kmsKey) == 0 {
kmsKeys = append(kmsKeys, c.AMIKmsKeyId)
}
}
}
for _, kmsKey := range kmsKeys {
if !validateKmsKey(kmsKey) {
errs = append(errs, fmt.Errorf("%s is not a valid KMS Key Id.", kmsKey))
}
}
if len(c.SnapshotUsers) > 0 {
if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume {
errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
@@ -111,58 +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
}
// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html
func validateKmsKey(kmsKey string) (valid bool) {
kmsKeyIdPattern := `[a-f0-9-]+$`
aliasPattern := `alias/[a-zA-Z0-9:/_-]+$`
kmsArnStartPattern := `^arn:aws:kms:([a-z]{2}-(gov-)?[a-z]+-\d{1})?:(\d{12}):`
if regexp.MustCompile(fmt.Sprintf("^%s", kmsKeyIdPattern)).MatchString(kmsKey) {
return true
}
if regexp.MustCompile(fmt.Sprintf("^%s", aliasPattern)).MatchString(kmsKey) {
return true
}
if regexp.MustCompile(fmt.Sprintf("%skey/%s", kmsArnStartPattern, kmsKeyIdPattern)).MatchString(kmsKey) {
return true
}
if regexp.MustCompile(fmt.Sprintf("%s%s", kmsArnStartPattern, aliasPattern)).MatchString(kmsKey) {
return true
}
return false
}
+35 -97
View File
@@ -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,61 +140,22 @@ 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")
}
}
func TestAMIConfigPrepare_ValidateKmsKey(t *testing.T) {
c := testAMIConfig()
c.AMIEncryptBootVolume = true
accessConf := testAccessConfig()
validCases := []string{
"abcd1234-e567-890f-a12b-a123b4cd56ef",
"alias/foo/bar",
"arn:aws:kms:us-east-1:012345678910:key/abcd1234-a123-456a-a12b-a123b4cd56ef",
"arn:aws:kms:us-east-1:012345678910:alias/foo/bar",
}
for _, validCase := range validCases {
c.AMIKmsKeyId = validCase
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatalf("%s should not have failed KMS key validation", validCase)
}
}
invalidCases := []string{
"ABCD1234-e567-890f-a12b-a123b4cd56ef",
"ghij1234-e567-890f-a12b-a123b4cd56ef",
"ghij1234+e567_890f-a12b-a123b4cd56ef",
"foo/bar",
"arn:aws:kms:us-east-1:012345678910:foo/bar",
}
for _, invalidCase := range invalidCases {
c.AMIKmsKeyId = invalidCase
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatalf("%s should have failed KMS key validation", invalidCase)
}
}
}
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")
}
@@ -226,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.")
}
+1 -3
View File
@@ -9,7 +9,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/packer"
)
@@ -71,8 +70,7 @@ func (a *Artifact) Destroy() error {
log.Printf("Deregistering image ID (%s) from region (%s)", imageId, region)
regionConn := ec2.New(a.Session, &aws.Config{
Region: aws.String(region),
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
Region: aws.String(region),
})
// Get image metadata
-4
View File
@@ -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: "+
-17
View File
@@ -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
}
+28 -46
View File
@@ -1,56 +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 {
if region == "" {
continue
}
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
}
+24 -53
View File
@@ -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"`
+21 -39
View File
@@ -1,7 +1,6 @@
package common
import (
"fmt"
"log"
"os"
"strconv"
@@ -209,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{
{
@@ -218,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) {
@@ -296,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},
}
@@ -308,36 +301,6 @@ func getEnvOverrides() overridableWaitVars {
return envValues
}
func LogEnvOverrideWarnings() {
pollDelay := os.Getenv("AWS_POLL_DELAY_SECONDS")
timeoutSeconds := os.Getenv("AWS_TIMEOUT_SECONDS")
maxAttempts := os.Getenv("AWS_MAX_ATTEMPTS")
if maxAttempts != "" && timeoutSeconds != "" {
warning := fmt.Sprintf("[WARNING] (aws): AWS_MAX_ATTEMPTS and " +
"AWS_TIMEOUT_SECONDS are both set. Packer will use " +
"AWS_MAX_ATTEMPTS and discard AWS_TIMEOUT_SECONDS.")
if pollDelay == "" {
warning = fmt.Sprintf("%s Since you have not set the poll delay, "+
"Packer will default to a 2-second delay.", warning)
}
log.Printf(warning)
} else if timeoutSeconds != "" {
log.Printf("[WARNING] (aws): env var AWS_TIMEOUT_SECONDS is " +
"deprecated in favor of AWS_MAX_ATTEMPTS. If you have not " +
"explicitly set AWS_POLL_DELAY_SECONDS, we are defaulting to a " +
"poll delay of 2 seconds, regardless of the AWS waiter's default.")
}
if maxAttempts == "" && timeoutSeconds == "" && pollDelay == "" {
log.Printf("[INFO] (aws): No AWS timeout and polling overrides have been set. " +
"Packer will default to waiter-specific delays and timeouts. If you would " +
"like to customize the length of time between retries and max " +
"number of retries you may do so by setting the environment " +
"variables AWS_POLL_DELAY_SECONDS and AWS_MAX_ATTEMPTS to your " +
"desired values.")
}
}
func applyEnvOverrides(envOverrides overridableWaitVars) []request.WaiterOption {
waitOpts := make([]request.WaiterOption, 0)
// If user has set poll delay seconds, overwrite it. If user has NOT,
@@ -351,7 +314,18 @@ func applyEnvOverrides(envOverrides overridableWaitVars) []request.WaiterOption
// attempts, default to whatever the waiter has set as a default.
if envOverrides.awsMaxAttempts.overridden {
waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(envOverrides.awsMaxAttempts.Val))
}
if envOverrides.awsMaxAttempts.overridden && envOverrides.awsTimeoutSeconds.overridden {
log.Printf("WARNING: AWS_MAX_ATTEMPTS and AWS_TIMEOUT_SECONDS are" +
" both set. Packer will be using AWS_MAX_ATTEMPTS and discarding " +
"AWS_TIMEOUT_SECONDS. If you have not set AWS_POLL_DELAY_SECONDS, " +
"Packer will default to a 2 second poll delay.")
} else if envOverrides.awsTimeoutSeconds.overridden {
log.Printf("DEPRECATION WARNING: env var AWS_TIMEOUT_SECONDS is " +
"deprecated in favor of AWS_MAX_ATTEMPTS. If you have not " +
"explicitly set AWS_POLL_DELAY_SECONDS, we are defaulting to a " +
"poll delay of 2 seconds, regardless of the AWS waiter's default.")
maxAttempts := envOverrides.awsTimeoutSeconds.Val / envOverrides.awsPollDelaySeconds.Val
// override the delay so we can get the timeout right
if !envOverrides.awsPollDelaySeconds.overridden {
@@ -360,6 +334,14 @@ func applyEnvOverrides(envOverrides overridableWaitVars) []request.WaiterOption
}
waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(maxAttempts))
}
if len(waitOpts) == 0 {
log.Printf("No AWS timeout and polling overrides have been set. " +
"Packer will default to waiter-specific delays and timeouts. If you would " +
"like to customize the length of time between retries and max " +
"number of retries you may do so by setting the environment " +
"variables AWS_POLL_DELAY_SECONDS and AWS_MAX_ATTEMPTS to your " +
"desired values.")
}
return waitOpts
}
@@ -7,7 +7,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@@ -100,9 +100,8 @@ func amiRegionCopy(ctx context.Context, state multistep.StateBag, config *Access
isEncrypted = true
}
regionconn := ec2.New(session.Copy(&aws.Config{
Region: aws.String(target),
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
}))
Region: aws.String(target)},
))
resp, err := regionconn.CopyImage(&ec2.CopyImageInput{
SourceRegion: &source,
+1 -3
View File
@@ -9,7 +9,6 @@ import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
retry "github.com/hashicorp/packer/common"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
@@ -36,8 +35,7 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami))
regionConn := ec2.New(session, &aws.Config{
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
Region: aws.String(region),
Region: aws.String(region),
})
// Retrieve image list for given AMI
+2 -4
View File
@@ -6,7 +6,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@@ -38,9 +37,8 @@ func (s *StepDeregisterAMI) Run(_ context.Context, state multistep.StateBag) mul
}
regionconn := ec2.New(session.Copy(&aws.Config{
Region: aws.String(region),
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
}))
Region: aws.String(region)},
))
resp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{
Owners: aws.StringSlice([]string{"self"}),
+4 -2
View File
@@ -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
}
@@ -7,7 +7,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
@@ -146,8 +145,7 @@ func (s *StepModifyAMIAttributes) Run(_ context.Context, state multistep.StateBa
for region, ami := range amis {
ui.Say(fmt.Sprintf("Modifying attributes on AMI (%s)...", ami))
regionConn := ec2.New(session, &aws.Config{
Region: aws.String(region),
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
Region: aws.String(region),
})
for name, input := range options {
ui.Message(fmt.Sprintf("Modifying: %s", name))
@@ -167,8 +165,7 @@ func (s *StepModifyAMIAttributes) Run(_ context.Context, state multistep.StateBa
for _, snapshot := range region_snapshots {
ui.Say(fmt.Sprintf("Modifying attributes on snapshot (%s)...", snapshot))
regionConn := ec2.New(session, &aws.Config{
Region: aws.String(region),
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
Region: aws.String(region),
})
for name, input := range snapshotOptions {
ui.Message(fmt.Sprintf("Modifying: %s", name))
@@ -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
-154
View File
@@ -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
}
+10 -15
View File
@@ -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
}
+36 -62
View File
@@ -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})
+15 -3
View File
@@ -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)
+24 -21
View File
@@ -9,11 +9,9 @@ import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
@@ -72,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 "+
@@ -84,28 +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, &aws.Config{
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
})
// 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)
@@ -116,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,
@@ -130,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,
@@ -138,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,
@@ -150,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,
@@ -170,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{
-2
View File
@@ -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)
+1 -1
View File
@@ -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)
+25 -16
View File
@@ -7,11 +7,9 @@ import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
@@ -87,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 "+
@@ -99,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,9 +106,24 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
if err != nil {
return nil, err
}
ec2conn := ec2.New(session, &aws.Config{
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
})
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)
@@ -124,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,
@@ -138,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,
@@ -146,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,
@@ -158,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,
@@ -181,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)
+34 -29
View File
@@ -6,11 +6,9 @@ import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
@@ -26,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
@@ -38,22 +36,18 @@ type Builder struct {
runner multistep.Runner
}
type EngineVarsTemplate struct {
BuildRegion string
SourceAMI string
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.ctx.Funcs = awscommon.TemplateFuncs
// Create passthrough for {{ .BuildRegion }} and {{ .SourceAMI }} variables
// so we can fill them in later
b.config.ctx.Data = &EngineVarsTemplate{
BuildRegion: `{{ .BuildRegion }}`,
SourceAMI: `{{ .SourceAMI }} `,
}
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"run_tags",
"spot_tags",
"ebs_volumes",
},
},
}, raws...)
if err != nil {
return nil, err
@@ -76,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 "+
@@ -88,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
}
@@ -96,13 +91,28 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
if err != nil {
return nil, err
}
ec2conn := ec2.New(session, &aws.Config{
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
})
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)
@@ -112,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,
@@ -126,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,
@@ -133,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,
@@ -145,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,
@@ -159,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,
-1
View File
@@ -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)
+25 -16
View File
@@ -9,11 +9,9 @@ import (
"os"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
@@ -158,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 "+
@@ -169,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,9 +176,24 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
if err != nil {
return nil, err
}
ec2conn := ec2.New(session, &aws.Config{
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
})
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)
@@ -194,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,
@@ -205,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,
@@ -213,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,
@@ -223,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,
@@ -242,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,
@@ -258,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,
-9
View File
@@ -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()
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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)
+11 -24
View File
@@ -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))
+1 -68
View File
@@ -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{
+34 -3
View File
@@ -2,9 +2,40 @@ package arm
import (
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
)
type oAuthTokenProvider interface {
getServicePrincipalToken() (*adal.ServicePrincipalToken, error)
getServicePrincipalTokenWithResource(resource string) (*adal.ServicePrincipalToken, error)
type Authenticate struct {
env azure.Environment
clientID string
clientSecret string
tenantID string
}
func NewAuthenticate(env azure.Environment, clientID, clientSecret, tenantID string) *Authenticate {
return &Authenticate{
env: env,
clientID: clientID,
clientSecret: clientSecret,
tenantID: tenantID,
}
}
func (a *Authenticate) getServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
return a.getServicePrincipalTokenWithResource(a.env.ResourceManagerEndpoint)
}
func (a *Authenticate) getServicePrincipalTokenWithResource(resource string) (*adal.ServicePrincipalToken, error) {
oauthConfig, err := adal.NewOAuthConfig(a.env.ActiveDirectoryEndpoint, a.tenantID)
if err != nil {
return nil, err
}
spt, err := adal.NewServicePrincipalToken(
*oauthConfig,
a.clientID,
a.clientSecret,
resource)
return spt, err
}
-155
View File
@@ -1,155 +0,0 @@
package arm
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"time"
"github.com/Azure/go-autorest/autorest/azure"
jwt "github.com/dgrijalva/jwt-go"
)
func NewCertOAuthTokenProvider(env azure.Environment, clientID, clientCertPath, tenantID string) (oAuthTokenProvider, error) {
cert, key, err := readCert(clientCertPath)
if err != nil {
return nil, fmt.Errorf("Error reading certificate: %v", err)
}
audience := fmt.Sprintf("%s%s/oauth2/token", env.ActiveDirectoryEndpoint, tenantID)
jwt, err := makeJWT(clientID, audience, cert, key, time.Hour, true)
if err != nil {
return nil, fmt.Errorf("Error generating JWT: %v", err)
}
return NewJWTOAuthTokenProvider(env, clientID, jwt, tenantID), nil
}
// Creates a new JSON Web Token to be used as bearer JWT to authenticate
// to the Azure AD token endpoint to retrieve an access token for `audience`.
// If the full certificate is included in the token, then issuer/subject name
// could be used to authenticate if configured by the identity provider (AAD).
func makeJWT(clientID string, audience string,
cert *x509.Certificate, privatekey interface{},
validFor time.Duration, includeFullCertificate bool) (string, error) {
// The jti (JWT ID) claim provides a unique identifier for the JWT.
// See https://tools.ietf.org/html/rfc7519#section-4.1.7
jti := make([]byte, 20)
_, err := rand.Read(jti)
if err != nil {
return "", err
}
var token *jwt.Token
if cert.PublicKeyAlgorithm == x509.RSA {
token = jwt.New(jwt.SigningMethodRS256)
} else if cert.PublicKeyAlgorithm == x509.ECDSA {
token = jwt.New(jwt.SigningMethodES256)
} else {
return "", fmt.Errorf("Don't know how to handle this type of key algorithm: %v", cert.PublicKeyAlgorithm)
}
hasher := sha1.New()
if _, err := hasher.Write(cert.Raw); err != nil {
return "", err
}
thumbprint := base64.URLEncoding.EncodeToString(hasher.Sum(nil))
// X.509 thumbprint, see https://tools.ietf.org/html/rfc7515#section-4.1.7
token.Header["x5t"] = thumbprint
if includeFullCertificate {
// X.509 certificate (chain), see https://tools.ietf.org/html/rfc7515#section-4.1.6
token.Header["x5c"] = []string{base64.StdEncoding.EncodeToString(cert.Raw)}
}
token.Claims = jwt.MapClaims{
// See https://tools.ietf.org/html/rfc7519#section-4.1
"aud": audience,
"iss": clientID,
"sub": clientID,
"jti": base64.URLEncoding.EncodeToString(jti),
"nbf": time.Now().Unix(),
"exp": time.Now().Add(validFor).Unix(),
}
return token.SignedString(privatekey)
}
func readCert(file string) (cert *x509.Certificate, key interface{}, err error) {
f, err := os.Open(file)
if err != nil {
return nil, nil, err
}
defer f.Close()
d, err := ioutil.ReadAll(f)
if err != nil {
return nil, nil, err
}
blocks := []*pem.Block{}
for len(d) > 0 {
var b *pem.Block
b, d = pem.Decode(d)
if b == nil {
break
}
blocks = append(blocks, b)
}
certs := []*x509.Certificate{}
for _, block := range blocks {
if block.Type == "CERTIFICATE" {
c, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, nil, fmt.Errorf(
"Failed to read certificate block: %v", err)
}
certs = append(certs, c)
} else if block.Type == "PRIVATE KEY" {
key, err = x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, nil, fmt.Errorf(
"Failed to read private key block: %v", err)
}
}
// Don't care about other types of blocks, ignore
}
if key == nil {
return nil, nil, fmt.Errorf("Did not find private key in pem file")
}
// find the certificate that belongs to the private key by comparing the public keys
switch key := key.(type) {
case *rsa.PrivateKey:
for _, c := range certs {
if cp, ok := c.PublicKey.(*rsa.PublicKey); ok &&
(cp.N.Cmp(key.PublicKey.N) == 0) {
cert = c
}
}
case *ecdsa.PrivateKey:
for _, c := range certs {
if cp, ok := c.PublicKey.(*ecdsa.PublicKey); ok &&
(cp.X.Cmp(key.PublicKey.X) == 0) &&
(cp.Y.Cmp(key.PublicKey.Y) == 0) {
cert = c
}
}
}
if cert == nil {
return nil, nil, fmt.Errorf("Did not find certificate belonging to private key in pem file")
}
return cert, key, nil
}
@@ -1,36 +0,0 @@
package arm
import (
"fmt"
"strings"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
packerAzureCommon "github.com/hashicorp/packer/builder/azure/common"
)
func NewDeviceFlowOAuthTokenProvider(env azure.Environment, say func(string), tenantID string) oAuthTokenProvider {
return &deviceflowOauthTokenProvider{}
}
type deviceflowOauthTokenProvider struct {
env azure.Environment
say func(string)
tenantID string
}
func (tp *deviceflowOauthTokenProvider) getServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
return tp.getServicePrincipalTokenWithResource(tp.env.ResourceManagerEndpoint)
}
func (tp *deviceflowOauthTokenProvider) getServicePrincipalTokenWithResource(resource string) (*adal.ServicePrincipalToken, error) {
if resource == tp.env.ServiceManagementEndpoint {
tp.say("Getting auth token for Service management endpoint")
} else if resource == strings.TrimRight(tp.env.KeyVaultEndpoint, "/") {
tp.say("Getting token for Vault resource")
} else {
tp.say(fmt.Sprintf("Getting token for %s", resource))
}
return packerAzureCommon.Authenticate(tp.env, tp.tenantID, tp.say, resource)
}
-43
View File
@@ -1,43 +0,0 @@
package arm
import (
"net/url"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
)
// for clientID/bearer JWT auth
type jwtOAuthTokenProvider struct {
env azure.Environment
clientID, clientJWT, tenantID string
}
func NewJWTOAuthTokenProvider(env azure.Environment, clientID, clientJWT, tenantID string) oAuthTokenProvider {
return &jwtOAuthTokenProvider{env, clientID, clientJWT, tenantID}
}
func (tp *jwtOAuthTokenProvider) getServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
return tp.getServicePrincipalTokenWithResource(tp.env.ResourceManagerEndpoint)
}
func (tp *jwtOAuthTokenProvider) getServicePrincipalTokenWithResource(resource string) (*adal.ServicePrincipalToken, error) {
oauthConfig, err := adal.NewOAuthConfig(tp.env.ActiveDirectoryEndpoint, tp.tenantID)
if err != nil {
return nil, err
}
return adal.NewServicePrincipalTokenWithSecret(
*oauthConfig,
tp.clientID,
resource,
tp)
}
// implements github.com/Azure/go-autorest/autorest/adal.ServicePrincipalSecret
func (tp *jwtOAuthTokenProvider) SetAuthenticationValues(
t *adal.ServicePrincipalToken, v *url.Values) error {
v.Set("client_assertion", tp.clientJWT)
v.Set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
return nil
}
-23
View File
@@ -1,23 +0,0 @@
package arm
import (
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
)
// for managed identity auth
type msiOAuthTokenProvider struct {
env azure.Environment
}
func NewMSIOAuthTokenProvider(env azure.Environment) oAuthTokenProvider {
return &msiOAuthTokenProvider{env}
}
func (tp *msiOAuthTokenProvider) getServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
return tp.getServicePrincipalTokenWithResource(tp.env.ResourceManagerEndpoint)
}
func (tp *msiOAuthTokenProvider) getServicePrincipalTokenWithResource(resource string) (*adal.ServicePrincipalToken, error) {
return adal.NewServicePrincipalTokenFromMSI("http://169.254.169.254/metadata/identity/oauth2/token", resource)
}
-35
View File
@@ -1,35 +0,0 @@
package arm
import (
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
)
// for clientID/secret auth
type secretOAuthTokenProvider struct {
env azure.Environment
clientID, clientSecret, tenantID string
}
func NewSecretOAuthTokenProvider(env azure.Environment, clientID, clientSecret, tenantID string) oAuthTokenProvider {
return &secretOAuthTokenProvider{env, clientID, clientSecret, tenantID}
}
func (tp *secretOAuthTokenProvider) getServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
return tp.getServicePrincipalTokenWithResource(tp.env.ResourceManagerEndpoint)
}
func (tp *secretOAuthTokenProvider) getServicePrincipalTokenWithResource(resource string) (*adal.ServicePrincipalToken, error) {
oauthConfig, err := adal.NewOAuthConfig(tp.env.ActiveDirectoryEndpoint, tp.tenantID)
if err != nil {
return nil, err
}
spt, err := adal.NewServicePrincipalToken(
*oauthConfig,
tp.clientID,
tp.clientSecret,
resource)
return spt, err
}
@@ -9,8 +9,8 @@ import (
// Behavior is the most important thing to assert for ServicePrincipalToken, but
// that cannot be done in a unit test because it involves network access. Instead,
// I assert the expected inertness of this class.
func TestNewSecretOAuthTokenProvider(t *testing.T) {
testSubject := NewSecretOAuthTokenProvider(azure.PublicCloud, "clientID", "clientString", "tenantID")
func TestNewAuthenticate(t *testing.T) {
testSubject := NewAuthenticate(azure.PublicCloud, "clientID", "clientString", "tenantID")
spn, err := testSubject.getServicePrincipalToken()
if err != nil {
t.Fatalf(err.Error())
-7
View File
@@ -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)
+47 -8
View File
@@ -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),
NewStepSnapshotDataDisks(azureClient, ui, b.config),
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),
NewStepSnapshotDataDisks(azureClient, ui, b.config),
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)
}
@@ -385,7 +379,52 @@ func (b *Builder) setImageParameters(stateBag multistep.StateBag) {
}
func (b *Builder) getServicePrincipalTokens(say func(string)) (*adal.ServicePrincipalToken, *adal.ServicePrincipalToken, error) {
return b.config.ClientConfig.getServicePrincipalTokens(say)
var servicePrincipalToken *adal.ServicePrincipalToken
var servicePrincipalTokenVault *adal.ServicePrincipalToken
var err error
if b.config.useDeviceLogin {
say("Getting auth token for Service management endpoint")
servicePrincipalToken, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.TenantID, say, b.config.cloudEnvironment.ServiceManagementEndpoint)
if err != nil {
return nil, nil, err
}
say("Getting token for Vault resource")
servicePrincipalTokenVault, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.TenantID, say, strings.TrimRight(b.config.cloudEnvironment.KeyVaultEndpoint, "/"))
if err != nil {
return nil, nil, err
}
} else {
auth := NewAuthenticate(*b.config.cloudEnvironment, b.config.ClientID, b.config.ClientSecret, b.config.TenantID)
servicePrincipalToken, err = auth.getServicePrincipalToken()
if err != nil {
return nil, nil, err
}
servicePrincipalTokenVault, err = auth.getServicePrincipalTokenWithResource(
strings.TrimRight(b.config.cloudEnvironment.KeyVaultEndpoint, "/"))
if err != nil {
return nil, nil, err
}
}
err = servicePrincipalToken.EnsureFresh()
if err != nil {
return nil, nil, err
}
err = servicePrincipalTokenVault.EnsureFresh()
if err != nil {
return nil, nil, err
}
return servicePrincipalToken, servicePrincipalTokenVault, nil
}
func getObjectIdFromToken(ui packer.Ui, token *adal.ServicePrincipalToken) string {
-223
View File
@@ -1,223 +0,0 @@
package arm
import (
"fmt"
"os"
"strings"
"time"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
jwt "github.com/dgrijalva/jwt-go"
"github.com/hashicorp/packer/packer"
)
// ClientConfig allows for various ways to authenticate Azure clients
type ClientConfig struct {
// Describes where API's are
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
cloudEnvironment *azure.Environment
// Authentication fields
// Client ID
ClientID string `mapstructure:"client_id"`
// Client secret/password
ClientSecret string `mapstructure:"client_secret"`
// Certificate path for client auth
ClientCertPath string `mapstructure:"client_cert_path"`
// JWT bearer token for client auth (RFC 7523, Sec. 2.2)
ClientJWT string `mapstructure:"client_jwt"`
ObjectID string `mapstructure:"object_id"`
TenantID string `mapstructure:"tenant_id"`
SubscriptionID string `mapstructure:"subscription_id"`
}
const DefaultCloudEnvironmentName = "Public"
func (c *ClientConfig) provideDefaultValues() {
if c.CloudEnvironmentName == "" {
c.CloudEnvironmentName = DefaultCloudEnvironmentName
}
}
func (c *ClientConfig) setCloudEnvironment() error {
lookup := map[string]string{
"CHINA": "AzureChinaCloud",
"CHINACLOUD": "AzureChinaCloud",
"AZURECHINACLOUD": "AzureChinaCloud",
"GERMAN": "AzureGermanCloud",
"GERMANCLOUD": "AzureGermanCloud",
"AZUREGERMANCLOUD": "AzureGermanCloud",
"GERMANY": "AzureGermanCloud",
"GERMANYCLOUD": "AzureGermanCloud",
"AZUREGERMANYCLOUD": "AzureGermanCloud",
"PUBLIC": "AzurePublicCloud",
"PUBLICCLOUD": "AzurePublicCloud",
"AZUREPUBLICCLOUD": "AzurePublicCloud",
"USGOVERNMENT": "AzureUSGovernmentCloud",
"USGOVERNMENTCLOUD": "AzureUSGovernmentCloud",
"AZUREUSGOVERNMENTCLOUD": "AzureUSGovernmentCloud",
}
name := strings.ToUpper(c.CloudEnvironmentName)
envName, ok := lookup[name]
if !ok {
return fmt.Errorf("There is no cloud environment matching the name '%s'!", c.CloudEnvironmentName)
}
env, err := azure.EnvironmentFromName(envName)
c.cloudEnvironment = &env
return err
}
func (c ClientConfig) assertRequiredParametersSet(errs *packer.MultiError) {
/////////////////////////////////////////////
// Authentication via OAUTH
// Check if device login is being asked for, and is allowed.
//
// Device login is enabled if the user only defines SubscriptionID and not
// ClientID, ClientSecret, and TenantID.
//
// Device login is not enabled for Windows because the WinRM certificate is
// readable by the ObjectID of the App. There may be another way to handle
// this case, but I am not currently aware of it - send feedback.
if c.useMSI() {
return
}
if c.useDeviceLogin() {
return
}
if c.SubscriptionID != "" && c.ClientID != "" &&
c.ClientSecret != "" &&
c.ClientCertPath == "" &&
c.ClientJWT == "" {
// Service principal using secret
return
}
if c.SubscriptionID != "" && c.ClientID != "" &&
c.ClientSecret == "" &&
c.ClientCertPath != "" &&
c.ClientJWT == "" {
// Service principal using certificate
if _, err := os.Stat(c.ClientCertPath); err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("client_cert_path is not an accessible file: %v", err))
}
return
}
if c.SubscriptionID != "" && c.ClientID != "" &&
c.ClientSecret == "" &&
c.ClientCertPath == "" &&
c.ClientJWT != "" {
// Service principal using JWT
// Check that JWT is valid for at least 5 more minutes
p := jwt.Parser{}
claims := jwt.StandardClaims{}
token, _, err := p.ParseUnverified(c.ClientJWT, &claims)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("client_jwt is not a JWT: %v", err))
} else {
if claims.ExpiresAt < time.Now().Add(5*time.Minute).Unix() {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("client_jwt will expire within 5 minutes, please use a JWT that is valid for at least 5 minutes"))
}
if t, ok := token.Header["x5t"]; !ok || t == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("client_jwt is missing the x5t header value, which is required for bearer JWT client authentication to Azure"))
}
}
return
}
errs = packer.MultiErrorAppend(errs, fmt.Errorf("No valid set of authentication values specified:\n"+
" to use the Managed Identity of the current machine, do not specify any of the fields below\n"+
" to use interactive user authentication, specify only subscription_id\n"+
" to use an Azure Active Directory service principal, specify either:\n"+
" - subscription_id, client_id and client_secret\n"+
" - subscription_id, client_id and client_cert_path\n"+
" - subscription_id, client_id and client_jwt."))
}
func (c ClientConfig) useDeviceLogin() bool {
return c.SubscriptionID != "" &&
c.ClientID == "" &&
c.ClientSecret == "" &&
c.ClientJWT == "" &&
c.ClientCertPath == "" &&
c.TenantID == ""
}
func (c ClientConfig) useMSI() bool {
return c.SubscriptionID == "" &&
c.ClientID == "" &&
c.ClientSecret == "" &&
c.ClientJWT == "" &&
c.ClientCertPath == "" &&
c.TenantID == ""
}
func (c ClientConfig) getServicePrincipalTokens(
say func(string)) (
servicePrincipalToken *adal.ServicePrincipalToken,
servicePrincipalTokenVault *adal.ServicePrincipalToken,
err error) {
tenantID := c.TenantID
var auth oAuthTokenProvider
if c.useDeviceLogin() {
say("Getting tokens using device flow")
auth = NewDeviceFlowOAuthTokenProvider(*c.cloudEnvironment, say, tenantID)
} else if c.useMSI() {
say("Getting tokens using Managed Identity for Azure")
auth = NewMSIOAuthTokenProvider(*c.cloudEnvironment)
} else if c.ClientSecret != "" {
say("Getting tokens using client secret")
auth = NewSecretOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientSecret, tenantID)
} else if c.ClientCertPath != "" {
say("Getting tokens using client certificate")
auth, err = NewCertOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientCertPath, tenantID)
if err != nil {
return nil, nil, err
}
} else {
say("Getting tokens using client bearer JWT")
auth = NewJWTOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientJWT, tenantID)
}
servicePrincipalToken, err = auth.getServicePrincipalToken()
if err != nil {
return nil, nil, err
}
err = servicePrincipalToken.EnsureFresh()
if err != nil {
return nil, nil, err
}
servicePrincipalTokenVault, err = auth.getServicePrincipalTokenWithResource(
strings.TrimRight(c.cloudEnvironment.KeyVaultEndpoint, "/"))
if err != nil {
return nil, nil, err
}
err = servicePrincipalTokenVault.EnsureFresh()
if err != nil {
return nil, nil, err
}
return servicePrincipalToken, servicePrincipalTokenVault, nil
}
-397
View File
@@ -1,397 +0,0 @@
package arm
import (
crand "crypto/rand"
"crypto/rsa"
"encoding/base64"
"encoding/binary"
"fmt"
"io"
mrand "math/rand"
"os"
"testing"
"time"
"github.com/Azure/go-autorest/autorest/azure"
jwt "github.com/dgrijalva/jwt-go"
"github.com/hashicorp/packer/packer"
)
func Test_ClientConfig_RequiredParametersSet(t *testing.T) {
tests := []struct {
name string
config ClientConfig
wantErr bool
}{
{
name: "no client_id, client_secret or subscription_id should enable MSI auth",
config: ClientConfig{},
wantErr: false,
},
{
name: "subscription_id is set will trigger device flow",
config: ClientConfig{
SubscriptionID: "error",
},
wantErr: false,
},
{
name: "client_id without client_secret, client_cert_path or client_jwt should error",
config: ClientConfig{
ClientID: "error",
},
wantErr: true,
},
{
name: "client_secret without client_id should error",
config: ClientConfig{
ClientSecret: "error",
},
wantErr: true,
},
{
name: "client_cert_path without client_id should error",
config: ClientConfig{
ClientCertPath: "/dev/null",
},
wantErr: true,
},
{
name: "client_jwt without client_id should error",
config: ClientConfig{
ClientJWT: "error",
},
wantErr: true,
},
{
name: "missing subscription_id when using secret",
config: ClientConfig{
ClientID: "ok",
ClientSecret: "ok",
},
wantErr: true,
},
{
name: "missing subscription_id when using certificate",
config: ClientConfig{
ClientID: "ok",
ClientCertPath: "ok",
},
wantErr: true,
},
{
name: "missing subscription_id when using JWT",
config: ClientConfig{
ClientID: "ok",
ClientJWT: "ok",
},
wantErr: true,
},
{
name: "too many client_* values",
config: ClientConfig{
SubscriptionID: "ok",
ClientID: "ok",
ClientSecret: "ok",
ClientCertPath: "error",
},
wantErr: true,
},
{
name: "too many client_* values (2)",
config: ClientConfig{
SubscriptionID: "ok",
ClientID: "ok",
ClientSecret: "ok",
ClientJWT: "error",
},
wantErr: true,
},
{
name: "tenant_id alone should fail",
config: ClientConfig{
TenantID: "ok",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
errs := &packer.MultiError{}
tt.config.assertRequiredParametersSet(errs)
if (len(errs.Errors) != 0) != tt.wantErr {
t.Errorf("newConfig() error = %v, wantErr %v", errs, tt.wantErr)
return
}
})
}
}
func Test_ClientConfig_DeviceLogin(t *testing.T) {
getEnvOrSkip(t, "AZURE_DEVICE_LOGIN")
cfg := ClientConfig{
SubscriptionID: getEnvOrSkip(t, "AZURE_SUBSCRIPTION"),
cloudEnvironment: getCloud(),
}
assertValid(t, cfg)
spt, sptkv, err := cfg.getServicePrincipalTokens(
func(s string) { fmt.Printf("SAY: %s\n", s) })
if err != nil {
t.Fatalf("Expected nil err, but got: %v", err)
}
token := spt.Token()
if token.AccessToken == "" {
t.Fatal("Expected management token to have non-nil access token")
}
if token.RefreshToken == "" {
t.Fatal("Expected management token to have non-nil refresh token")
}
kvtoken := sptkv.Token()
if kvtoken.AccessToken == "" {
t.Fatal("Expected keyvault token to have non-nil access token")
}
if kvtoken.RefreshToken == "" {
t.Fatal("Expected keyvault token to have non-nil refresh token")
}
}
func Test_ClientConfig_ClientPassword(t *testing.T) {
cfg := ClientConfig{
SubscriptionID: getEnvOrSkip(t, "AZURE_SUBSCRIPTION"),
ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"),
ClientSecret: getEnvOrSkip(t, "AZURE_CLIENTSECRET"),
TenantID: getEnvOrSkip(t, "AZURE_TENANTID"),
cloudEnvironment: getCloud(),
}
assertValid(t, cfg)
spt, sptkv, err := cfg.getServicePrincipalTokens(func(s string) { fmt.Printf("SAY: %s\n", s) })
if err != nil {
t.Fatalf("Expected nil err, but got: %v", err)
}
token := spt.Token()
if token.AccessToken == "" {
t.Fatal("Expected management token to have non-nil access token")
}
if token.RefreshToken != "" {
t.Fatal("Expected management token to have no refresh token")
}
kvtoken := sptkv.Token()
if kvtoken.AccessToken == "" {
t.Fatal("Expected keyvault token to have non-nil access token")
}
if kvtoken.RefreshToken != "" {
t.Fatal("Expected keyvault token to have no refresh token")
}
}
func Test_ClientConfig_ClientCert(t *testing.T) {
cfg := ClientConfig{
SubscriptionID: getEnvOrSkip(t, "AZURE_SUBSCRIPTION"),
ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"),
ClientCertPath: getEnvOrSkip(t, "AZURE_CLIENTCERT"),
TenantID: getEnvOrSkip(t, "AZURE_TENANTID"),
cloudEnvironment: getCloud(),
}
assertValid(t, cfg)
spt, sptkv, err := cfg.getServicePrincipalTokens(func(s string) { fmt.Printf("SAY: %s\n", s) })
if err != nil {
t.Fatalf("Expected nil err, but got: %v", err)
}
token := spt.Token()
if token.AccessToken == "" {
t.Fatal("Expected management token to have non-nil access token")
}
if token.RefreshToken != "" {
t.Fatal("Expected management token to have no refresh token")
}
kvtoken := sptkv.Token()
if kvtoken.AccessToken == "" {
t.Fatal("Expected keyvault token to have non-nil access token")
}
if kvtoken.RefreshToken != "" {
t.Fatal("Expected keyvault token to have no refresh token")
}
}
func Test_ClientConfig_ClientJWT(t *testing.T) {
cfg := ClientConfig{
SubscriptionID: getEnvOrSkip(t, "AZURE_SUBSCRIPTION"),
ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"),
ClientJWT: getEnvOrSkip(t, "AZURE_CLIENTJWT"),
TenantID: getEnvOrSkip(t, "AZURE_TENANTID"),
cloudEnvironment: getCloud(),
}
assertValid(t, cfg)
spt, sptkv, err := cfg.getServicePrincipalTokens(func(s string) { fmt.Printf("SAY: %s\n", s) })
if err != nil {
t.Fatalf("Expected nil err, but got: %v", err)
}
token := spt.Token()
if token.AccessToken == "" {
t.Fatal("Expected management token to have non-nil access token")
}
if token.RefreshToken != "" {
t.Fatal("Expected management token to have no refresh token")
}
kvtoken := sptkv.Token()
if kvtoken.AccessToken == "" {
t.Fatal("Expected keyvault token to have non-nil access token")
}
if kvtoken.RefreshToken != "" {
t.Fatal("Expected keyvault token to have no refresh token")
}
}
func getEnvOrSkip(t *testing.T, envVar string) string {
v := os.Getenv(envVar)
if v == "" {
t.Skipf("%s is empty, skipping", envVar)
}
return v
}
func getCloud() *azure.Environment {
cloudName := os.Getenv("AZURE_CLOUD")
if cloudName == "" {
cloudName = "AZUREPUBLICCLOUD"
}
c, _ := azure.EnvironmentFromName(cloudName)
return &c
}
// tests for assertRequiredParametersSet
func Test_ClientConfig_CanUseDeviceCode(t *testing.T) {
cfg := emptyClientConfig()
cfg.SubscriptionID = "12345"
// TenantID is optional
assertValid(t, cfg)
}
func assertValid(t *testing.T, cfg ClientConfig) {
errs := &packer.MultiError{}
cfg.assertRequiredParametersSet(errs)
if len(errs.Errors) != 0 {
t.Fatal("Expected errs to be empty: ", errs)
}
}
func assertInvalid(t *testing.T, cfg ClientConfig) {
errs := &packer.MultiError{}
cfg.assertRequiredParametersSet(errs)
if len(errs.Errors) == 0 {
t.Fatal("Expected errs to be non-empty")
}
}
func Test_ClientConfig_CanUseClientSecret(t *testing.T) {
cfg := emptyClientConfig()
cfg.SubscriptionID = "12345"
cfg.ClientID = "12345"
cfg.ClientSecret = "12345"
assertValid(t, cfg)
}
func Test_ClientConfig_CanUseClientSecretWithTenantID(t *testing.T) {
cfg := emptyClientConfig()
cfg.SubscriptionID = "12345"
cfg.ClientID = "12345"
cfg.ClientSecret = "12345"
cfg.TenantID = "12345"
assertValid(t, cfg)
}
func Test_ClientConfig_CanUseClientJWT(t *testing.T) {
cfg := emptyClientConfig()
cfg.SubscriptionID = "12345"
cfg.ClientID = "12345"
cfg.ClientJWT = getJWT(10*time.Minute, true)
assertValid(t, cfg)
}
func Test_ClientConfig_CanUseClientJWTWithTenantID(t *testing.T) {
cfg := emptyClientConfig()
cfg.SubscriptionID = "12345"
cfg.ClientID = "12345"
cfg.ClientJWT = getJWT(10*time.Minute, true)
cfg.TenantID = "12345"
assertValid(t, cfg)
}
func Test_ClientConfig_CannotUseBothClientJWTAndSecret(t *testing.T) {
cfg := emptyClientConfig()
cfg.SubscriptionID = "12345"
cfg.ClientID = "12345"
cfg.ClientSecret = "12345"
cfg.ClientJWT = getJWT(10*time.Minute, true)
assertInvalid(t, cfg)
}
func Test_ClientConfig_ClientJWTShouldBeValidForAtLeast5Minutes(t *testing.T) {
cfg := emptyClientConfig()
cfg.SubscriptionID = "12345"
cfg.ClientID = "12345"
cfg.ClientJWT = getJWT(time.Minute, true)
assertInvalid(t, cfg)
}
func Test_ClientConfig_ClientJWTShouldHaveThumbprint(t *testing.T) {
cfg := emptyClientConfig()
cfg.SubscriptionID = "12345"
cfg.ClientID = "12345"
cfg.ClientJWT = getJWT(10*time.Minute, false)
assertInvalid(t, cfg)
}
func emptyClientConfig() ClientConfig {
cfg := ClientConfig{}
_ = cfg.setCloudEnvironment()
return cfg
}
func Test_getJWT(t *testing.T) {
if getJWT(time.Minute, true) == "" {
t.Fatalf("getJWT is broken")
}
}
func newRandReader() io.Reader {
var seed int64
binary.Read(crand.Reader, binary.LittleEndian, &seed)
return mrand.New(mrand.NewSource(seed))
}
func getJWT(validFor time.Duration, withX5tHeader bool) string {
token := jwt.New(jwt.SigningMethodRS256)
key, _ := rsa.GenerateKey(newRandReader(), 2048)
token.Claims = jwt.MapClaims{
"aud": "https://login.microsoftonline.com/tenant.onmicrosoft.com/oauth2/token?api-version=1.0",
"iss": "355dff10-cd78-11e8-89fe-000d3afd16e3",
"sub": "355dff10-cd78-11e8-89fe-000d3afd16e3",
"jti": base64.URLEncoding.EncodeToString([]byte{0}),
"nbf": time.Now().Unix(),
"exp": time.Now().Add(validFor).Unix(),
}
if withX5tHeader {
token.Header["x5t"] = base64.URLEncoding.EncodeToString([]byte("thumbprint"))
}
jwt, _ := token.SignedString(key)
return jwt
}
+92 -88
View File
@@ -15,6 +15,7 @@ import (
"time"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"github.com/masterzen/winrm"
@@ -31,6 +32,7 @@ import (
)
const (
DefaultCloudEnvironmentName = "Public"
DefaultImageVersion = "latest"
DefaultUserName = "packer"
DefaultPrivateVirtualNetworkWithPublicIp = false
@@ -54,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_]{1,79}$")
reSnapshotPrefix = regexp.MustCompile("^[A-Za-z0-9_]{1,59}$")
)
type PlanInformation struct {
@@ -65,27 +65,20 @@ 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"`
// Authentication via OAUTH
ClientConfig `mapstructure:",squash"`
ClientID string `mapstructure:"client_id"`
ClientSecret string `mapstructure:"client_secret"`
ObjectID string `mapstructure:"object_id"`
TenantID string `mapstructure:"tenant_id"`
SubscriptionID string `mapstructure:"subscription_id"`
// Capture
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"`
@@ -100,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"`
managedImageStorageAccountType compute.StorageAccountTypes
ManagedImageOSDiskSnapshotName string `mapstructure:"managed_image_os_disk_snapshot_name"`
ManagedImageDataDiskSnapshotPrefix string `mapstructure:"managed_image_data_disk_snapshot_prefix"`
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"`
@@ -116,6 +107,8 @@ type Config struct {
TempResourceGroupName string `mapstructure:"temp_resource_group_name"`
BuildResourceGroupName string `mapstructure:"build_resource_group_name"`
storageAccountBlobEndpoint string
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
cloudEnvironment *azure.Environment
PrivateVirtualNetworkWithPublicIp bool `mapstructure:"private_virtual_network_with_public_ip"`
VirtualNetworkName string `mapstructure:"virtual_network_name"`
VirtualNetworkSubnetName string `mapstructure:"virtual_network_subnet_name"`
@@ -130,8 +123,6 @@ type Config struct {
// Additional Disks
AdditionalDiskSize []int32 `mapstructure:"disk_additional_size"`
DiskCachingType string `mapstructure:"disk_caching_type"`
diskCachingType compute.CachingTypes
// Runtime Values
UserName string
@@ -149,6 +140,8 @@ type Config struct {
tmpVirtualNetworkName string
tmpWinRMCertificateUrl string
useDeviceLogin bool
// Authentication with the VM via SSH
sshAuthorizedKey string
@@ -277,7 +270,7 @@ func newConfig(raws ...interface{}) (*Config, []string, error) {
provideDefaultValues(&c)
setRuntimeValues(&c)
setUserNamePassword(&c)
err = c.ClientConfig.setCloudEnvironment()
err = setCloudEnvironment(&c)
if err != nil {
return nil, nil, err
}
@@ -322,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
}
@@ -406,6 +399,40 @@ func setUserNamePassword(c *Config) {
}
}
func setCloudEnvironment(c *Config) error {
lookup := map[string]string{
"CHINA": "AzureChinaCloud",
"CHINACLOUD": "AzureChinaCloud",
"AZURECHINACLOUD": "AzureChinaCloud",
"GERMAN": "AzureGermanCloud",
"GERMANCLOUD": "AzureGermanCloud",
"AZUREGERMANCLOUD": "AzureGermanCloud",
"GERMANY": "AzureGermanCloud",
"GERMANYCLOUD": "AzureGermanCloud",
"AZUREGERMANYCLOUD": "AzureGermanCloud",
"PUBLIC": "AzurePublicCloud",
"PUBLICCLOUD": "AzurePublicCloud",
"AZUREPUBLICCLOUD": "AzurePublicCloud",
"USGOVERNMENT": "AzureUSGovernmentCloud",
"USGOVERNMENTCLOUD": "AzureUSGovernmentCloud",
"AZUREUSGOVERNMENTCLOUD": "AzureUSGovernmentCloud",
}
name := strings.ToUpper(c.CloudEnvironmentName)
envName, ok := lookup[name]
if !ok {
return fmt.Errorf("There is no cloud environment matching the name '%s'!", c.CloudEnvironmentName)
}
env, err := azure.EnvironmentFromName(envName)
c.cloudEnvironment = &env
return err
}
func setCustomData(c *Config) error {
if c.CustomDataFile == "" {
return nil
@@ -429,15 +456,13 @@ func provideDefaultValues(c *Config) {
c.managedImageStorageAccountType = compute.StorageAccountTypesStandardLRS
}
if c.DiskCachingType == "" {
c.diskCachingType = compute.CachingTypesReadWrite
}
if c.ImagePublisher != "" && c.ImageVersion == "" {
c.ImageVersion = DefaultImageVersion
}
c.provideDefaultValues()
if c.CloudEnvironmentName == "" {
c.CloudEnvironmentName = DefaultCloudEnvironmentName
}
}
func assertTagProperties(c *Config, errs *packer.MultiError) {
@@ -456,7 +481,40 @@ func assertTagProperties(c *Config, errs *packer.MultiError) {
}
func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
c.ClientConfig.assertRequiredParametersSet(errs)
/////////////////////////////////////////////
// Authentication via OAUTH
// Check if device login is being asked for, and is allowed.
//
// Device login is enabled if the user only defines SubscriptionID and not
// ClientID, ClientSecret, and TenantID.
//
// Device login is not enabled for Windows because the WinRM certificate is
// readable by the ObjectID of the App. There may be another way to handle
// this case, but I am not currently aware of it - send feedback.
isUseDeviceLogin := func(c *Config) bool {
return c.SubscriptionID != "" &&
c.ClientID == "" &&
c.ClientSecret == "" &&
c.TenantID == ""
}
if isUseDeviceLogin(c) {
c.useDeviceLogin = true
} else {
if c.ClientID == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_id must be specified"))
}
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"))
}
}
/////////////////////////////////////////////
// Capture
@@ -514,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"))
}
@@ -619,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"))
}
@@ -675,17 +704,6 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
default:
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The managed_image_storage_account_type %q is invalid", c.ManagedImageStorageAccountType))
}
switch c.DiskCachingType {
case string(compute.CachingTypesNone):
c.diskCachingType = compute.CachingTypesNone
case string(compute.CachingTypesReadOnly):
c.diskCachingType = compute.CachingTypesReadOnly
case "", string(compute.CachingTypesReadWrite):
c.diskCachingType = compute.CachingTypesReadWrite
default:
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The disk_caching_type %q is invalid", c.DiskCachingType))
}
}
func assertManagedImageName(name, setting string) (bool, error) {
@@ -695,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)
-343
View File
@@ -48,10 +48,6 @@ func TestConfigShouldProvideReasonableDefaultValues(t *testing.T) {
if c.managedImageStorageAccountType == "" {
t.Errorf("Expected 'managedImageStorageAccountType' to be populated, but it was empty!")
}
if c.diskCachingType == "" {
t.Errorf("Expected 'diskCachingType' to be populated, but it was empty!")
}
}
func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) {
@@ -61,7 +57,6 @@ func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) {
builderValues["vm_size"] = "override_vm_size"
builderValues["communicator"] = "ssh"
builderValues["managed_image_storage_account_type"] = "Premium_LRS"
builderValues["disk_caching_type"] = "None"
c, _, err := newConfig(builderValues, getPackerConfiguration())
@@ -92,10 +87,6 @@ func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) {
if c.managedImageStorageAccountType != compute.StorageAccountTypesPremiumLRS {
t.Errorf("Expected 'managed_image_storage_account_type' to be set to 'Premium_LRS', but found %q!", c.managedImageStorageAccountType)
}
if c.diskCachingType != compute.CachingTypesNone {
t.Errorf("Expected 'disk_caching_type' to be set to 'None', but found %q!", c.diskCachingType)
}
}
func TestConfigShouldDefaultVMSizeToStandardA1(t *testing.T) {
@@ -556,105 +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{
"-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{
"-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",
@@ -817,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",
@@ -1091,27 +839,6 @@ func TestConfigShouldRejectMalformedManageImageStorageAccountTypes(t *testing.T)
}
}
func TestConfigShouldRejectMalformedDiskCachingType(t *testing.T) {
config := map[string]interface{}{
"custom_managed_image_resource_group_name": "ignore",
"custom_managed_image_name": "ignore",
"location": "ignore",
"subscription_id": "ignore",
"communicator": "none",
"managed_image_resource_group_name": "ignore",
"managed_image_name": "ignore",
"disk_caching_type": "--invalid--",
// 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 custom and platform input for a managed image build")
}
}
func TestConfigShouldAcceptManagedImageStorageAccountTypes(t *testing.T) {
config := map[string]interface{}{
"custom_managed_image_resource_group_name": "ignore",
@@ -1137,31 +864,6 @@ func TestConfigShouldAcceptManagedImageStorageAccountTypes(t *testing.T) {
}
}
func TestConfigShouldAcceptDiskCachingTypes(t *testing.T) {
config := map[string]interface{}{
"custom_managed_image_resource_group_name": "ignore",
"custom_managed_image_name": "ignore",
"location": "ignore",
"subscription_id": "ignore",
"communicator": "none",
"managed_image_resource_group_name": "ignore",
"managed_image_name": "ignore",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
}
storage_account_types := []string{"None", "ReadOnly", "ReadWrite"}
for _, x := range storage_account_types {
config["disk_caching_type"] = x
_, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatalf("expected config to accept a disk_caching_type of %q", x)
}
}
}
func TestConfigShouldRejectTempAndBuildResourceGroupName(t *testing.T) {
config := map[string]interface{}{
"capture_name_prefix": "ignore",
@@ -1592,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 -1
View File
@@ -28,7 +28,7 @@ func NewStepPowerOffCompute(client *AzureClient, ui packer.Ui) *StepPowerOffComp
}
func (s *StepPowerOffCompute) powerOffCompute(ctx context.Context, resourceGroupName string, computeName string) error {
f, err := s.client.VirtualMachinesClient.Deallocate(ctx, resourceGroupName, computeName)
f, err := s.client.VirtualMachinesClient.PowerOff(ctx, resourceGroupName, computeName)
if err == nil {
err = f.WaitForCompletion(ctx, s.client.VirtualMachinesClient.Client)
}
@@ -1,108 +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)
enable func() bool
}
func NewStepSnapshotDataDisks(client *AzureClient, ui packer.Ui, config *Config) *StepSnapshotDataDisks {
var step = &StepSnapshotDataDisks{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
enable: func() bool { return config.isManagedImage() && config.ManagedImageDataDiskSnapshotPrefix != "" },
}
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(" -> Snapshot ID : '%s'", *(createdSnapshot.ID)))
return nil
}
func (s *StepSnapshotDataDisks) Run(ctx context.Context, stateBag multistep.StateBag) multistep.StepAction {
if !s.enable() {
return multistep.ActionContinue
}
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)
if len(additionalDisks) == 1 {
s.say(fmt.Sprintf("Snapshotting data disk ..."))
} else {
s.say(fmt.Sprintf("Snapshotting data disks ..."))
}
for i, disk := range additionalDisks {
s.say(fmt.Sprintf(" -> Data Disk : '%s'", disk))
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,87 +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) {},
enable: func() bool { return 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 TestStepSnapshotDataDisksShouldNotExecute(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) {},
enable: func() bool { return false },
}
var result = testSubject.Run(context.Background(), nil)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
}
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) {},
enable: func() bool { return 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,98 +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)
enable func() bool
}
func NewStepSnapshotOSDisk(client *AzureClient, ui packer.Ui, config *Config) *StepSnapshotOSDisk {
var step = &StepSnapshotOSDisk{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
enable: func() bool { return config.isManagedImage() && config.ManagedImageOSDiskSnapshotName != "" },
}
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(" -> Snapshot ID : '%s'", *(createdSnapshot.ID)))
return nil
}
func (s *StepSnapshotOSDisk) Run(ctx context.Context, stateBag multistep.StateBag) multistep.StepAction {
if !s.enable() {
return multistep.ActionContinue
}
s.say("Snapshotting 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)
s.say(fmt.Sprintf(" -> OS Disk : '%s'", srcUriVhd))
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,88 +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) {},
enable: func() bool { return 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 TestStepSnapshotOSDiskShouldNotExecute(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) {},
enable: func() bool { return false },
}
var result = testSubject.Run(context.Background(), nil)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
}
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) {},
enable: func() bool { return 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
}
+5 -18
View File
@@ -59,9 +59,9 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
}
if config.ImageUrl != "" {
builder.SetImageUrl(config.ImageUrl, osType, config.diskCachingType)
builder.SetImageUrl(config.ImageUrl, osType)
} else if config.CustomManagedImageName != "" {
builder.SetManagedDiskUrl(config.customManagedImageID, config.managedImageStorageAccountType, config.diskCachingType)
builder.SetManagedDiskUrl(config.customManagedImageID, config.managedImageStorageAccountType)
} else if config.ManagedImageName != "" && config.ImagePublisher != "" {
imageID := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/ArtifactTypes/vmimage/offers/%s/skus/%s/versions/%s",
config.SubscriptionID,
@@ -71,21 +71,9 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
config.ImageSku,
config.ImageVersion)
builder.SetManagedMarketplaceImage(config.Location, config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion, imageID, config.managedImageStorageAccountType, config.diskCachingType)
} 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, config.diskCachingType)
builder.SetManagedMarketplaceImage(config.Location, config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion, imageID, config.managedImageStorageAccountType)
} else {
builder.SetMarketPlaceImage(config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion, config.diskCachingType)
builder.SetMarketPlaceImage(config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion)
}
if config.OSDiskSizeGB > 0 {
@@ -93,8 +81,7 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
}
if len(config.AdditionalDiskSize) > 0 {
isManaged := config.CustomManagedImageName != "" || (config.ManagedImageName != "" && config.ImagePublisher != "")
builder.SetAdditionalDisks(config.AdditionalDiskSize, isManaged, config.diskCachingType)
builder.SetAdditionalDisks(config.AdditionalDiskSize, config.CustomManagedImageName != "" || (config.ManagedImageName != "" && config.ImagePublisher != ""))
}
if config.customData != "" {
+5 -7
View File
@@ -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"
)
+3 -7
View File
@@ -5,7 +5,6 @@ import (
"fmt"
"net/http"
"os"
"os/user"
"path/filepath"
"regexp"
"strings"
@@ -16,6 +15,7 @@ import (
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"github.com/hashicorp/packer/helper/useragent"
"github.com/mitchellh/go-homedir"
)
var (
@@ -148,13 +148,9 @@ func tokenFromDeviceFlow(say func(string), oauthCfg adal.OAuthConfig, clientID,
// tokenCachePath returns the full path the OAuth 2.0 token should be saved at
// for given tenant ID.
func tokenCachePath(tenantID string) string {
var dir string
u, err := user.Current()
if err != nil || u.HomeDir == "" {
dir, err := homedir.Dir()
if err != nil {
dir, _ = filepath.Abs(os.Args[0])
} else {
dir = u.HomeDir
}
return filepath.Join(dir, ".azure", "packer", fmt.Sprintf("oauth-%s.json", tenantID))
@@ -14,6 +14,7 @@ const (
jsonIndent = " "
resourceKeyVaults = "Microsoft.KeyVault/vaults"
resourceManagedDisk = "Microsoft.Compute/images"
resourceNetworkInterfaces = "Microsoft.Network/networkInterfaces"
resourcePublicIPAddresses = "Microsoft.Network/publicIPAddresses"
resourceVirtualMachine = "Microsoft.Compute/virtualMachines"
@@ -100,7 +101,7 @@ func (s *TemplateBuilder) BuildWindows(keyVaultName, winRMCertificateUrl string)
return nil
}
func (s *TemplateBuilder) SetManagedDiskUrl(managedImageId string, storageAccountType compute.StorageAccountTypes, cachingType compute.CachingTypes) error {
func (s *TemplateBuilder) SetManagedDiskUrl(managedImageId string, storageAccountType compute.StorageAccountTypes) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
@@ -113,7 +114,6 @@ func (s *TemplateBuilder) SetManagedDiskUrl(managedImageId string, storageAccoun
profile.OsDisk.OsType = s.osType
profile.OsDisk.CreateOption = compute.DiskCreateOptionTypesFromImage
profile.OsDisk.Vhd = nil
profile.OsDisk.Caching = cachingType
profile.OsDisk.ManagedDisk = &compute.ManagedDiskParameters{
StorageAccountType: storageAccountType,
}
@@ -121,7 +121,7 @@ func (s *TemplateBuilder) SetManagedDiskUrl(managedImageId string, storageAccoun
return nil
}
func (s *TemplateBuilder) SetManagedMarketplaceImage(location, publisher, offer, sku, version, imageID string, storageAccountType compute.StorageAccountTypes, cachingType compute.CachingTypes) error {
func (s *TemplateBuilder) SetManagedMarketplaceImage(location, publisher, offer, sku, version, imageID string, storageAccountType compute.StorageAccountTypes) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
@@ -133,11 +133,11 @@ func (s *TemplateBuilder) SetManagedMarketplaceImage(location, publisher, offer,
Offer: &offer,
Sku: &sku,
Version: &version,
//ID: &imageID,
}
profile.OsDisk.OsType = s.osType
profile.OsDisk.CreateOption = compute.DiskCreateOptionTypesFromImage
profile.OsDisk.Vhd = nil
profile.OsDisk.Caching = cachingType
profile.OsDisk.ManagedDisk = &compute.ManagedDiskParameters{
StorageAccountType: storageAccountType,
}
@@ -145,30 +145,13 @@ func (s *TemplateBuilder) SetManagedMarketplaceImage(location, publisher, offer,
return nil
}
func (s *TemplateBuilder) SetSharedGalleryImage(location, imageID string, cachingType compute.CachingTypes) 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
profile.OsDisk.Caching = cachingType
return nil
}
func (s *TemplateBuilder) SetMarketPlaceImage(publisher, offer, sku, version string, cachingType compute.CachingTypes) error {
func (s *TemplateBuilder) SetMarketPlaceImage(publisher, offer, sku, version string) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.StorageProfile
profile.OsDisk.Caching = cachingType
profile.ImageReference = &compute.ImageReference{
Publisher: to.StringPtr(publisher),
Offer: to.StringPtr(offer),
@@ -179,7 +162,7 @@ func (s *TemplateBuilder) SetMarketPlaceImage(publisher, offer, sku, version str
return nil
}
func (s *TemplateBuilder) SetImageUrl(imageUrl string, osType compute.OperatingSystemTypes, cachingType compute.CachingTypes) error {
func (s *TemplateBuilder) SetImageUrl(imageUrl string, osType compute.OperatingSystemTypes) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
@@ -187,8 +170,6 @@ func (s *TemplateBuilder) SetImageUrl(imageUrl string, osType compute.OperatingS
profile := resource.Properties.StorageProfile
profile.OsDisk.OsType = osType
profile.OsDisk.Caching = cachingType
profile.OsDisk.Image = &compute.VirtualHardDisk{
URI: to.StringPtr(imageUrl),
}
@@ -228,7 +209,7 @@ func (s *TemplateBuilder) SetOSDiskSizeGB(diskSizeGB int32) error {
return nil
}
func (s *TemplateBuilder) SetAdditionalDisks(diskSizeGB []int32, isManaged bool, cachingType compute.CachingTypes) error {
func (s *TemplateBuilder) SetAdditionalDisks(diskSizeGB []int32, isManaged bool) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
@@ -237,12 +218,12 @@ func (s *TemplateBuilder) SetAdditionalDisks(diskSizeGB []int32, isManaged bool,
profile := resource.Properties.StorageProfile
dataDisks := make([]DataDiskUnion, len(diskSizeGB))
for i, additionalSize := range diskSizeGB {
dataDisks[i].DiskSizeGB = to.Int32Ptr(additionalSize)
for i, additionalsize := range diskSizeGB {
dataDisks[i].DiskSizeGB = to.Int32Ptr(additionalsize)
dataDisks[i].Lun = to.IntPtr(i)
dataDisks[i].Name = to.StringPtr(fmt.Sprintf("datadisk-%d", i+1))
dataDisks[i].CreateOption = "Empty"
dataDisks[i].Caching = cachingType
dataDisks[i].Caching = "ReadWrite"
if isManaged {
dataDisks[i].Vhd = nil
dataDisks[i].ManagedDisk = profile.OsDisk.ManagedDisk
@@ -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": "ReadOnly",
"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'))]"
}
}
@@ -20,7 +20,7 @@ func TestBuildLinux00(t *testing.T) {
t.Fatal(err)
}
err = testSubject.SetMarketPlaceImage("Canonical", "UbuntuServer", "16.04", "latest", compute.CachingTypesReadWrite)
err = testSubject.SetMarketPlaceImage("Canonical", "UbuntuServer", "16.04", "latest")
if err != nil {
t.Fatal(err)
}
@@ -48,7 +48,7 @@ func TestBuildLinux01(t *testing.T) {
t.Fatal(err)
}
err = testSubject.SetImageUrl("http://azure/custom.vhd", compute.Linux, compute.CachingTypesReadWrite)
err = testSubject.SetImageUrl("http://azure/custom.vhd", compute.Linux)
if err != nil {
t.Fatal(err)
}
@@ -72,7 +72,7 @@ func TestBuildLinux02(t *testing.T) {
}
testSubject.BuildLinux("--test-ssh-authorized-key--")
testSubject.SetImageUrl("http://azure/custom.vhd", compute.Linux, compute.CachingTypesReadWrite)
testSubject.SetImageUrl("http://azure/custom.vhd", compute.Linux)
testSubject.SetOSDiskSizeGB(100)
err = testSubject.SetVirtualNetwork("--virtual-network-resource-group--", "--virtual-network--", "--subnet-name--")
@@ -105,7 +105,7 @@ func TestBuildWindows00(t *testing.T) {
t.Fatal(err)
}
err = testSubject.SetMarketPlaceImage("MicrosoftWindowsServer", "WindowsServer", "2012-R2-Datacenter", "latest", compute.CachingTypesReadWrite)
err = testSubject.SetMarketPlaceImage("MicrosoftWindowsServer", "WindowsServer", "2012-R2-Datacenter", "latest")
if err != nil {
t.Fatal(err)
}
@@ -133,12 +133,12 @@ func TestBuildWindows01(t *testing.T) {
t.Fatal(err)
}
err = testSubject.SetManagedMarketplaceImage("MicrosoftWindowsServer", "WindowsServer", "2012-R2-Datacenter", "latest", "2015-1", "1", "Premium_LRS", compute.CachingTypesReadWrite)
err = testSubject.SetManagedMarketplaceImage("MicrosoftWindowsServer", "WindowsServer", "2012-R2-Datacenter", "latest", "2015-1", "1", "Premium_LRS")
if err != nil {
t.Fatal(err)
}
err = testSubject.SetAdditionalDisks([]int32{32, 64}, true, compute.CachingTypesReadWrite)
err = testSubject.SetAdditionalDisks([]int32{32, 64}, true)
if err != nil {
t.Fatal(err)
}
@@ -166,36 +166,7 @@ func TestBuildWindows02(t *testing.T) {
t.Fatal(err)
}
err = testSubject.SetAdditionalDisks([]int32{32, 64}, false, compute.CachingTypesReadWrite)
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)
}
}
// 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, compute.CachingTypesReadOnly)
err = testSubject.SetAdditionalDisks([]int32{32, 64}, false)
if err != nil {
t.Fatal(err)
}
+4 -3
View File
@@ -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
}

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