Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fb1be8be30 |
@@ -1,12 +0,0 @@
|
||||
# Golang CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.11
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- checkout
|
||||
- run: make ci
|
||||
+21
-84
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
/src
|
||||
/website/.sass-cache
|
||||
/website/build
|
||||
/website/tmp
|
||||
.DS_Store
|
||||
.vagrant
|
||||
.idea
|
||||
|
||||
+4
-14
@@ -1,10 +1,5 @@
|
||||
env:
|
||||
- USER=travis
|
||||
|
||||
os:
|
||||
- windows
|
||||
- linux
|
||||
- osx
|
||||
- USER=travis
|
||||
|
||||
sudo: false
|
||||
|
||||
@@ -14,15 +9,11 @@ go:
|
||||
- 1.11.x
|
||||
- master
|
||||
|
||||
before_install:
|
||||
- >
|
||||
if [[ "$TRAVIS_OS_NAME" == "windows" ]];
|
||||
then choco install -y make;
|
||||
fi
|
||||
install:
|
||||
- make deps
|
||||
|
||||
script:
|
||||
- df -h
|
||||
- make ci
|
||||
- GOMAXPROCS=2 make ci
|
||||
|
||||
branches:
|
||||
only:
|
||||
@@ -31,5 +22,4 @@ branches:
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
- os: windows
|
||||
fast_finish: true
|
||||
|
||||
-208
@@ -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
@@ -2,7 +2,7 @@
|
||||
|
||||
# builders
|
||||
|
||||
/builder/alicloud/ @chhaj5236
|
||||
/builder/alicloud/ dongxiao.zzh@alibaba-inc.com
|
||||
/builder/amazon/ebssurrogate/ @jen20
|
||||
/builder/amazon/ebsvolume/ @jen20
|
||||
/builder/azure/ @boumenot
|
||||
@@ -14,8 +14,7 @@
|
||||
/builder/profitbricks/ @jasmingacic
|
||||
/builder/triton/ @jen20 @sean-
|
||||
/builder/ncloud/ @YuSungDuk
|
||||
/builder/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
|
||||
/builder/hcloud/ @LKaemmerling
|
||||
/builder/scaleway/ @dimtion @edouardb
|
||||
|
||||
# provisioners
|
||||
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
FROM ubuntu:16.04
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
locales \
|
||||
openssh-server \
|
||||
sudo
|
||||
|
||||
RUN locale-gen en_US.UTF-8
|
||||
|
||||
RUN if ! getent passwd vagrant; then useradd -d /home/vagrant -m -s /bin/bash vagrant; fi \
|
||||
&& echo 'vagrant ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \
|
||||
&& mkdir -p /etc/sudoers.d \
|
||||
&& echo 'vagrant ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/vagrant \
|
||||
&& chmod 0440 /etc/sudoers.d/vagrant
|
||||
|
||||
RUN mkdir -p /home/vagrant/.ssh \
|
||||
&& chmod 0700 /home/vagrant/.ssh \
|
||||
&& wget --no-check-certificate \
|
||||
https://raw.github.com/hashicorp/vagrant/master/keys/vagrant.pub \
|
||||
-O /home/vagrant/.ssh/authorized_keys \
|
||||
&& chmod 0600 /home/vagrant/.ssh/authorized_keys \
|
||||
&& chown -R vagrant /home/vagrant/.ssh
|
||||
|
||||
RUN mkdir -p /run/sshd
|
||||
|
||||
CMD /usr/sbin/sshd -D \
|
||||
-o UseDNS=no \
|
||||
-o PidFile=/tmp/sshd.pid
|
||||
@@ -11,7 +11,7 @@ GOPATH=$(shell go env GOPATH)
|
||||
# gofmt
|
||||
UNFORMATTED_FILES=$(shell find . -not -path "./vendor/*" -name "*.go" | xargs gofmt -s -l)
|
||||
|
||||
EXECUTABLE_FILES=$(shell find . -type f -executable | egrep -v '^\./(website/[vendor|tmp]|vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats|\.git)' | egrep -v './provisioner/ansible/test-fixtures/exit1')
|
||||
EXECUTABLE_FILES=$(shell find . -type f -perm +111 | egrep -v '^\./(website/vendor|vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats|\.git)' | egrep -v './provisioner/ansible/test-fixtures/exit1')
|
||||
|
||||
# Get the git commit
|
||||
GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
||||
@@ -21,11 +21,9 @@ GOLDFLAGS=-X $(GIT_IMPORT).GitCommit=$(GIT_COMMIT)$(GIT_DIRTY)
|
||||
|
||||
export GOLDFLAGS
|
||||
|
||||
.PHONY: bin checkversion ci default deps fmt fmt-docs fmt-examples generate releasebin test testacc testrace updatedeps
|
||||
default: deps generate test dev
|
||||
|
||||
default: deps generate testrace dev releasebin package dev fmt fmt-check mode-check fmt-docs fmt-examples
|
||||
|
||||
ci: testrace
|
||||
ci: deps test
|
||||
|
||||
release: deps test releasebin package ## Build a release build
|
||||
|
||||
@@ -51,6 +49,7 @@ deps:
|
||||
@go get golang.org/x/tools/cmd/stringer
|
||||
@go get -u github.com/mna/pigeon
|
||||
@go get github.com/kardianos/govendor
|
||||
@go get golang.org/x/tools/cmd/goimports
|
||||
|
||||
dev: deps ## Build and install a development build
|
||||
@grep 'const VersionPrerelease = ""' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \
|
||||
@@ -101,25 +100,25 @@ generate: deps ## Generate dynamically generated code
|
||||
goimports -w common/bootcommand/boot_command.go
|
||||
gofmt -w command/plugin.go
|
||||
|
||||
test: fmt-check mode-check vet ## Run unit tests
|
||||
test: deps fmt-check mode-check ## Run unit tests
|
||||
@go test $(TEST) $(TESTARGS) -timeout=2m
|
||||
@go tool vet $(VET) ; if [ $$? -eq 1 ]; then \
|
||||
echo "ERROR: Vet found problems in the code."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# testacc runs acceptance tests
|
||||
testacc: deps generate ## Run acceptance tests
|
||||
@echo "WARN: Acceptance tests will take a long time to run and may cost money. Ctrl-C if you want to cancel."
|
||||
PACKER_ACC=1 go test -v $(TEST) $(TESTARGS) -timeout=45m
|
||||
|
||||
testrace: fmt-check mode-check vet ## Test with race detection enabled
|
||||
@go test -race $(TEST) $(TESTARGS) -timeout=2m -p=8
|
||||
testrace: deps ## Test for race conditions
|
||||
@go test -race $(TEST) $(TESTARGS) -timeout=2m
|
||||
|
||||
updatedeps:
|
||||
@echo "INFO: Packer deps are managed by govendor. See .github/CONTRIBUTING.md"
|
||||
|
||||
vet: ## Vet Go code
|
||||
@go tool vet $(VET) ; if [ $$? -eq 1 ]; then \
|
||||
echo "ERROR: Vet found problems in the code."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
help:
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
.PHONY: bin checkversion ci default deps fmt fmt-docs fmt-examples generate releasebin test testacc testrace updatedeps
|
||||
|
||||
@@ -80,7 +80,7 @@ Packer will build an AMI according to the "quick-start" template. The AMI
|
||||
will be available in your AWS account. To delete the AMI, you must manually
|
||||
delete it using the [AWS console](https://console.aws.amazon.com/). Packer
|
||||
builds your images, it does not manage their lifecycle. Where they go, how
|
||||
they're run, etc., is up to you.
|
||||
they're run, etc. is up to you.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
Vendored
+3
-9
@@ -5,9 +5,9 @@ LINUX_BASE_BOX = "bento/ubuntu-16.04"
|
||||
FREEBSD_BASE_BOX = "jen20/FreeBSD-12.0-CURRENT"
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
if Vagrant.has_plugin?("vagrant-cachier")
|
||||
config.cache.scope = :box
|
||||
end
|
||||
if Vagrant.has_plugin?("vagrant-cachier")
|
||||
config.cache.scope = :box
|
||||
end
|
||||
|
||||
# Compilation and development boxes
|
||||
config.vm.define "linux", autostart: true, primary: true do |vmCfg|
|
||||
@@ -73,12 +73,6 @@ def configureProviders(vmCfg, cpus: "2", memory: "2048")
|
||||
end
|
||||
end
|
||||
|
||||
vmCfg.vm.provider "docker" do |d, override|
|
||||
d.build_dir = "."
|
||||
d.has_ssh = true
|
||||
override.vm.box = nil
|
||||
end
|
||||
|
||||
return vmCfg
|
||||
end
|
||||
|
||||
|
||||
+48
-1
@@ -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' }`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -113,17 +113,6 @@ func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_imageTags(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() {
|
||||
testAccPreCheck(t)
|
||||
},
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccImageTags,
|
||||
Check: checkImageTags(),
|
||||
})
|
||||
}
|
||||
|
||||
func checkSnapshotsDeleted(snapshotIds []string) builderT.TestCheckFunc {
|
||||
return func(artifacts []packer.Artifact) error {
|
||||
// Verify the snapshots are gone
|
||||
@@ -218,51 +207,6 @@ func checkRegionCopy(regions []string) builderT.TestCheckFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func checkImageTags() builderT.TestCheckFunc {
|
||||
return func(artifacts []packer.Artifact) error {
|
||||
if len(artifacts) > 1 {
|
||||
return fmt.Errorf("more than 1 artifact")
|
||||
}
|
||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
||||
artifactRaw := artifacts[0]
|
||||
artifact, ok := artifactRaw.(*Artifact)
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
||||
}
|
||||
// describe the image, get block devices with a snapshot
|
||||
client, _ := testAliyunClient()
|
||||
tags, _, err := client.DescribeTags(
|
||||
&ecs.DescribeTagsArgs{
|
||||
RegionId: "cn-beijing",
|
||||
ResourceType: ecs.TagResourceImage,
|
||||
ResourceId: artifact.AlicloudImages["cn-beijing"],
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error retrieving Image Attributes for ECS Image Artifact (%#v) "+
|
||||
"in ECS Image Tags Test: %s", artifact, err)
|
||||
}
|
||||
failed := false
|
||||
if len(tags) != 2 {
|
||||
failed = true
|
||||
}
|
||||
if !failed {
|
||||
for i := 0; i < len(tags); i++ {
|
||||
if tags[i].TagKey == "TagKey1" && tags[i].TagValue != "TagValue1" {
|
||||
failed = true
|
||||
} else if tags[i].TagKey == "TagKey2" && tags[i].TagValue != "TagValue2" {
|
||||
failed = true
|
||||
} else if tags[i].TagKey != "TagKey1" && tags[i].TagKey != "TagKey2" {
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
return fmt.Errorf("tags is not correctly set %#v", tags)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
if v := os.Getenv("ALICLOUD_ACCESS_KEY"); v == "" {
|
||||
t.Fatal("ALICLOUD_ACCESS_KEY must be set for acceptance tests")
|
||||
@@ -292,7 +236,8 @@ const testBuilderAccBasic = `
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_20G_alibase_20180409.vhd",
|
||||
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
|
||||
"ssh_username": "ubuntu",
|
||||
"io_optimized":"true",
|
||||
"ssh_username":"root",
|
||||
"image_name": "packer-test_{{timestamp}}"
|
||||
@@ -361,22 +306,6 @@ const testBuilderAccSharing = `
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccImageTags = `
|
||||
{ "builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_20G_alibase_20180409.vhd",
|
||||
"ssh_username": "root",
|
||||
"io_optimized":"true",
|
||||
"image_name": "packer-test_{{timestamp}}",
|
||||
"tags": {
|
||||
"TagKey1": "TagValue1",
|
||||
"TagKey2": "TagValue2"
|
||||
}
|
||||
}]
|
||||
}`
|
||||
|
||||
func buildForceDeregisterConfig(val, name string) string {
|
||||
return fmt.Sprintf(testBuilderAccForceDelete, val, name)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -94,141 +93,3 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_Devices(t *testing.T) {
|
||||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
config["system_disk_mapping"] = map[string]interface{}{
|
||||
"disk_category": "cloud",
|
||||
"disk_description": "system disk",
|
||||
"disk_name": "system_disk",
|
||||
"disk_size": 60,
|
||||
}
|
||||
config["image_disk_mappings"] = []map[string]interface{}{
|
||||
{
|
||||
"disk_category": "cloud_efficiency",
|
||||
"disk_name": "data_disk1",
|
||||
"disk_size": 100,
|
||||
"disk_snapshot_id": "s-1",
|
||||
"disk_description": "data disk1",
|
||||
"disk_device": "/dev/xvdb",
|
||||
"disk_delete_with_instance": false,
|
||||
},
|
||||
{
|
||||
"disk_name": "data_disk2",
|
||||
"disk_device": "/dev/xvdc",
|
||||
},
|
||||
}
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(b.config.ECSSystemDiskMapping, AlicloudDiskDevice{
|
||||
DiskCategory: "cloud",
|
||||
Description: "system disk",
|
||||
DiskName: "system_disk",
|
||||
DiskSize: 60,
|
||||
}) {
|
||||
t.Fatalf("system disk is not set properly, actual: %#v", b.config.ECSSystemDiskMapping)
|
||||
}
|
||||
if !reflect.DeepEqual(b.config.ECSImagesDiskMappings, []AlicloudDiskDevice{
|
||||
{
|
||||
DiskCategory: "cloud_efficiency",
|
||||
DiskName: "data_disk1",
|
||||
DiskSize: 100,
|
||||
SnapshotId: "s-1",
|
||||
Description: "data disk1",
|
||||
Device: "/dev/xvdb",
|
||||
DeleteWithInstance: false,
|
||||
},
|
||||
{
|
||||
DiskName: "data_disk2",
|
||||
Device: "/dev/xvdc",
|
||||
},
|
||||
}) {
|
||||
t.Fatalf("data disks are not set properly, actual: %#v", b.config.ECSImagesDiskMappings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_IgnoreDataDisks(t *testing.T) {
|
||||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AlicloudImageIgnoreDataDisks != false {
|
||||
t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", false, b.config.AlicloudImageIgnoreDataDisks)
|
||||
}
|
||||
|
||||
config["image_ignore_data_disks"] = "false"
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AlicloudImageIgnoreDataDisks != false {
|
||||
t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", false, b.config.AlicloudImageIgnoreDataDisks)
|
||||
}
|
||||
|
||||
config["image_ignore_data_disks"] = "true"
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AlicloudImageIgnoreDataDisks != true {
|
||||
t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", true, b.config.AlicloudImageIgnoreDataDisks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_WaitSnapshotReadyTimeout(t *testing.T) {
|
||||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.WaitSnapshotReadyTimeout != 0 {
|
||||
t.Fatalf("wait_snapshot_ready_timeout is not set properly, expect: %d, actual: %d", 0, b.config.WaitSnapshotReadyTimeout)
|
||||
}
|
||||
if b.getSnapshotReadyTimeout() != ALICLOUD_DEFAULT_LONG_TIMEOUT {
|
||||
t.Fatalf("default timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_LONG_TIMEOUT, b.getSnapshotReadyTimeout())
|
||||
}
|
||||
|
||||
config["wait_snapshot_ready_timeout"] = ALICLOUD_DEFAULT_TIMEOUT
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.WaitSnapshotReadyTimeout != ALICLOUD_DEFAULT_TIMEOUT {
|
||||
t.Fatalf("wait_snapshot_ready_timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_TIMEOUT, b.config.WaitSnapshotReadyTimeout)
|
||||
}
|
||||
|
||||
if b.getSnapshotReadyTimeout() != ALICLOUD_DEFAULT_TIMEOUT {
|
||||
t.Fatalf("default timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_TIMEOUT, b.getSnapshotReadyTimeout())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,24 +21,21 @@ type AlicloudDiskDevice struct {
|
||||
}
|
||||
|
||||
type AlicloudDiskDevices struct {
|
||||
ECSSystemDiskMapping AlicloudDiskDevice `mapstructure:"system_disk_mapping"`
|
||||
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings"`
|
||||
}
|
||||
|
||||
type AlicloudImageConfig struct {
|
||||
AlicloudImageName string `mapstructure:"image_name"`
|
||||
AlicloudImageVersion string `mapstructure:"image_version"`
|
||||
AlicloudImageDescription string `mapstructure:"image_description"`
|
||||
AlicloudImageShareAccounts []string `mapstructure:"image_share_account"`
|
||||
AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account"`
|
||||
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions"`
|
||||
AlicloudImageDestinationNames []string `mapstructure:"image_copy_names"`
|
||||
AlicloudImageForceDelete bool `mapstructure:"image_force_delete"`
|
||||
AlicloudImageForceDeleteSnapshots bool `mapstructure:"image_force_delete_snapshots"`
|
||||
AlicloudImageForceDeleteInstances bool `mapstructure:"image_force_delete_instances"`
|
||||
AlicloudImageIgnoreDataDisks bool `mapstructure:"image_ignore_data_disks"`
|
||||
AlicloudImageSkipRegionValidation bool `mapstructure:"skip_region_validation"`
|
||||
AlicloudImageTags map[string]string `mapstructure:"tags"`
|
||||
AlicloudImageName string `mapstructure:"image_name"`
|
||||
AlicloudImageVersion string `mapstructure:"image_version"`
|
||||
AlicloudImageDescription string `mapstructure:"image_description"`
|
||||
AlicloudImageShareAccounts []string `mapstructure:"image_share_account"`
|
||||
AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account"`
|
||||
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions"`
|
||||
AlicloudImageDestinationNames []string `mapstructure:"image_copy_names"`
|
||||
AlicloudImageForceDelete bool `mapstructure:"image_force_delete"`
|
||||
AlicloudImageForceDeleteSnapshots bool `mapstructure:"image_force_delete_snapshots"`
|
||||
AlicloudImageForceDeleteInstances bool `mapstructure:"image_force_delete_instances"`
|
||||
AlicloudImageSkipRegionValidation bool `mapstructure:"skip_region_validation"`
|
||||
AlicloudDiskDevices `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
|
||||
@@ -55,24 +55,6 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestECSImageConfigPrepare_imageTags(t *testing.T) {
|
||||
c := testAlicloudImageConfig()
|
||||
c.AlicloudImageTags = map[string]string{
|
||||
"TagKey1": "TagValue1",
|
||||
"TagKey2": "TagValue2",
|
||||
}
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(c.AlicloudImageTags) != 2 || c.AlicloudImageTags["TagKey1"] != "TagValue1" ||
|
||||
c.AlicloudImageTags["TagKey2"] != "TagValue2" {
|
||||
t.Fatalf("invalid value, expected: %s, actual: %s", map[string]string{
|
||||
"TagKey1": "TagValue1",
|
||||
"TagKey2": "TagValue2",
|
||||
}, c.AlicloudImageTags)
|
||||
}
|
||||
}
|
||||
|
||||
func regionsToString() []string {
|
||||
var regions []string
|
||||
for _, region := range common.ValidRegions {
|
||||
|
||||
@@ -20,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
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ type RunConfig struct {
|
||||
Description string `mapstructure:"description"`
|
||||
AlicloudSourceImage string `mapstructure:"source_image"`
|
||||
ForceStopInstance bool `mapstructure:"force_stop_instance"`
|
||||
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SecurityGroupName string `mapstructure:"security_group_name"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
@@ -32,7 +31,6 @@ type RunConfig struct {
|
||||
InstanceName string `mapstructure:"instance_name"`
|
||||
InternetChargeType string `mapstructure:"internet_charge_type"`
|
||||
InternetMaxBandwidthOut int `mapstructure:"internet_max_bandwidth_out"`
|
||||
WaitSnapshotReadyTimeout int `mapstructure:"wait_snapshot_ready_timeout"`
|
||||
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
@@ -123,54 +123,3 @@ func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
|
||||
t.Fatal("keypair name does not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SSHPrivateIp(t *testing.T) {
|
||||
c := testConfig()
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c.SSHPrivateIp != false {
|
||||
t.Fatalf("invalid value, expected: %t, actul: %t", false, c.SSHPrivateIp)
|
||||
}
|
||||
c.SSHPrivateIp = true
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c.SSHPrivateIp != true {
|
||||
t.Fatalf("invalid value, expected: %t, actul: %t", true, c.SSHPrivateIp)
|
||||
}
|
||||
c.SSHPrivateIp = false
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c.SSHPrivateIp != false {
|
||||
t.Fatalf("invalid value, expected: %t, actul: %t", false, c.SSHPrivateIp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_DisableStopInstance(t *testing.T) {
|
||||
c := testConfig()
|
||||
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c.DisableStopInstance != false {
|
||||
t.Fatalf("invalid value, expected: %t, actul: %t", false, c.DisableStopInstance)
|
||||
}
|
||||
|
||||
c.DisableStopInstance = true
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c.DisableStopInstance != true {
|
||||
t.Fatalf("invalid value, expected: %t, actul: %t", true, c.DisableStopInstance)
|
||||
}
|
||||
|
||||
c.DisableStopInstance = false
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c.DisableStopInstance != false {
|
||||
t.Fatalf("invalid value, expected: %t, actul: %t", false, c.DisableStopInstance)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ type stepAttachKeyPair struct {
|
||||
func (s *stepAttachKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
timeoutPoint := time.Now().Add(120 * time.Second)
|
||||
keyPairName := config.Comm.SSHKeyPairName
|
||||
@@ -52,7 +52,7 @@ func (s *stepAttachKeyPair) Run(_ context.Context, state multistep.StateBag) mul
|
||||
|
||||
func (s *stepAttachKeyPair) Cleanup(state multistep.StateBag) {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
keyPairName := config.Comm.SSHKeyPairName
|
||||
|
||||
@@ -16,7 +16,7 @@ type stepCheckAlicloudSourceImage struct {
|
||||
|
||||
func (s *stepCheckAlicloudSourceImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
args := &ecs.DescribeImagesArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
|
||||
@@ -16,24 +16,12 @@ type stepConfigAlicloudEIP struct {
|
||||
InternetChargeType string
|
||||
InternetMaxBandwidthOut int
|
||||
allocatedId string
|
||||
SSHPrivateIp bool
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudEIP) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
|
||||
if s.SSHPrivateIp {
|
||||
ipaddress := instance.VpcAttributes.PrivateIpAddress.IpAddress
|
||||
if len(ipaddress) == 0 {
|
||||
ui.Say("Failed to get private ip of instance")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("ipaddress", ipaddress[0])
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say("Allocating eip")
|
||||
ipaddress, allocateId, err := client.AllocateEipAddress(&ecs.AllocateEipAddressArgs{
|
||||
RegionId: common.Region(s.RegionId), InternetChargeType: common.InternetChargeType(s.InternetChargeType),
|
||||
|
||||
@@ -3,6 +3,7 @@ package ecs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
@@ -27,9 +28,10 @@ func (s *stepConfigAlicloudKeyPair) Run(_ context.Context, state multistep.State
|
||||
|
||||
if s.Comm.SSHPrivateKeyFile != "" {
|
||||
ui.Say("Using existing SSH private key")
|
||||
privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile()
|
||||
privateKeyBytes, err := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
state.Put("error", fmt.Errorf(
|
||||
"Error loading configured private key file: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
type stepConfigAlicloudPublicIP struct {
|
||||
publicIPAddress string
|
||||
RegionId string
|
||||
SSHPrivateIp bool
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudPublicIP) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
@@ -20,16 +19,6 @@ func (s *stepConfigAlicloudPublicIP) Run(_ context.Context, state multistep.Stat
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
|
||||
if s.SSHPrivateIp {
|
||||
ipaddress := instance.InnerIpAddress.IpAddress
|
||||
if len(ipaddress) == 0 {
|
||||
ui.Say("Failed to get private ip of instance")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("ipaddress", ipaddress[0])
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ipaddress, err := client.AllocatePublicIpAddress(instance.InstanceId)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
|
||||
@@ -20,7 +20,7 @@ type stepConfigAlicloudVPC struct {
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudVPC) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ func (s *stepConfigAlicloudVSwitch) Run(_ context.Context, state multistep.State
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vpcId := state.Get("vpcid").(string)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
|
||||
if len(s.VSwitchId) != 0 {
|
||||
vswitchs, _, err := client.DescribeVSwitches(&ecs.DescribeVSwitchesArgs{
|
||||
|
||||
@@ -11,13 +11,11 @@ import (
|
||||
)
|
||||
|
||||
type stepCreateAlicloudImage struct {
|
||||
AlicloudImageIgnoreDataDisks bool
|
||||
WaitSnapshotReadyTimeout int
|
||||
image *ecs.ImageType
|
||||
image *ecs.ImageType
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
@@ -26,53 +24,48 @@ func (s *stepCreateAlicloudImage) Run(_ context.Context, state multistep.StateBa
|
||||
var imageId string
|
||||
var err error
|
||||
|
||||
if s.AlicloudImageIgnoreDataDisks {
|
||||
snapshotId := state.Get("alicloudsnapshot").(string)
|
||||
imageId, err = client.CreateImage(&ecs.CreateImageArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
SnapshotId: snapshotId,
|
||||
ImageName: config.AlicloudImageName,
|
||||
ImageVersion: config.AlicloudImageVersion,
|
||||
Description: config.AlicloudImageDescription})
|
||||
} else {
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
imageId, err = client.CreateImage(&ecs.CreateImageArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
InstanceId: instance.InstanceId,
|
||||
ImageName: config.AlicloudImageName,
|
||||
ImageVersion: config.AlicloudImageVersion,
|
||||
Description: config.AlicloudImageDescription})
|
||||
}
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
imageId, err = client.CreateImage(&ecs.CreateImageArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
InstanceId: instance.InstanceId,
|
||||
ImageName: config.AlicloudImageName,
|
||||
ImageVersion: config.AlicloudImageVersion,
|
||||
Description: config.AlicloudImageDescription})
|
||||
|
||||
if err != nil {
|
||||
return halt(state, err, "Error creating image")
|
||||
err := fmt.Errorf("Error creating image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
err = client.WaitForImageReady(common.Region(config.AlicloudRegion), imageId, s.WaitSnapshotReadyTimeout)
|
||||
err = client.WaitForImageReady(common.Region(config.AlicloudRegion),
|
||||
imageId, ALICLOUD_DEFAULT_LONG_TIMEOUT)
|
||||
if err != nil {
|
||||
return halt(state, err, "Timeout waiting for image to be created")
|
||||
err := fmt.Errorf("Timeout waiting for image to be created: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
ImageId: imageId})
|
||||
if err != nil {
|
||||
return halt(state, err, "Error querying created imaged")
|
||||
err := fmt.Errorf("Error querying created image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(images) == 0 {
|
||||
return halt(state, err, "Unable to find created image")
|
||||
err := fmt.Errorf("Unable to find created image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.image = &images[0]
|
||||
|
||||
var snapshotIds = []string{}
|
||||
for _, device := range images[0].DiskDeviceMappings.DiskDeviceMapping {
|
||||
snapshotIds = append(snapshotIds, device.SnapshotId)
|
||||
}
|
||||
|
||||
state.Put("alicloudimage", imageId)
|
||||
state.Put("alicloudsnapshots", snapshotIds)
|
||||
|
||||
alicloudImages := make(map[string]string)
|
||||
alicloudImages[config.AlicloudRegion] = images[0].ImageId
|
||||
state.Put("alicloudimages", alicloudImages)
|
||||
@@ -92,7 +85,7 @@ func (s *stepCreateAlicloudImage) Cleanup(state multistep.StateBag) {
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
|
||||
ui.Say("Deleting the image because of cancellation or error...")
|
||||
if err := client.DeleteImage(common.Region(config.AlicloudRegion), s.image.ImageId); err != nil {
|
||||
|
||||
@@ -28,7 +28,7 @@ type stepCreateAlicloudInstance struct {
|
||||
|
||||
func (s *stepCreateAlicloudInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
source_image := state.Get("source_image").(*ecs.ImageType)
|
||||
network_type := state.Get("networktype").(InstanceNetWork)
|
||||
@@ -66,7 +66,6 @@ func (s *stepCreateAlicloudInstance) Run(_ context.Context, state multistep.Stat
|
||||
InstanceName: s.InstanceName,
|
||||
Password: password,
|
||||
ZoneId: s.ZoneId,
|
||||
SystemDisk: systemDeviceToDiskType(config.AlicloudImageConfig.ECSSystemDiskMapping),
|
||||
DataDisk: diskDeviceToDiskType(config.AlicloudImageConfig.ECSImagesDiskMappings),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -148,15 +147,6 @@ func (s *stepCreateAlicloudInstance) getUserData(state multistep.StateBag) (stri
|
||||
|
||||
}
|
||||
|
||||
func systemDeviceToDiskType(systemDisk AlicloudDiskDevice) ecs.SystemDiskType {
|
||||
return ecs.SystemDiskType{
|
||||
DiskName: systemDisk.DiskName,
|
||||
Category: ecs.DiskCategory(systemDisk.DiskCategory),
|
||||
Size: systemDisk.DiskSize,
|
||||
Description: systemDisk.Description,
|
||||
}
|
||||
}
|
||||
|
||||
func diskDeviceToDiskType(diskDevices []AlicloudDiskDevice) []ecs.DataDiskType {
|
||||
result := make([]ecs.DataDiskType, len(diskDevices))
|
||||
for _, diskDevice := range diskDevices {
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepCreateAlicloudSnapshot struct {
|
||||
snapshot *ecs.SnapshotType
|
||||
WaitSnapshotReadyTimeout int
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
disks, _, err := client.DescribeDisks(&ecs.DescribeDisksArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
InstanceId: instance.InstanceId,
|
||||
DiskType: ecs.DiskTypeAllSystem,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return halt(state, err, "Error describe disks")
|
||||
}
|
||||
if len(disks) == 0 {
|
||||
return halt(state, err, "Unable to find system disk of instance")
|
||||
}
|
||||
|
||||
// Create the alicloud snapshot
|
||||
ui.Say(fmt.Sprintf("Creating snapshot from system disk: %s", disks[0].DiskId))
|
||||
|
||||
snapshotId, err := client.CreateSnapshot(&ecs.CreateSnapshotArgs{
|
||||
DiskId: disks[0].DiskId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return halt(state, err, "Error creating snapshot")
|
||||
}
|
||||
|
||||
err = client.WaitForSnapShotReady(common.Region(config.AlicloudRegion), snapshotId, s.WaitSnapshotReadyTimeout)
|
||||
if err != nil {
|
||||
return halt(state, err, "Timeout waiting for snapshot to be created")
|
||||
}
|
||||
|
||||
snapshots, _, err := client.DescribeSnapshots(&ecs.DescribeSnapshotsArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
SnapshotIds: []string{snapshotId},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return halt(state, err, "Error querying created snapshot")
|
||||
}
|
||||
if len(snapshots) == 0 {
|
||||
return halt(state, err, "Unable to find created snapshot")
|
||||
}
|
||||
s.snapshot = &snapshots[0]
|
||||
state.Put("alicloudsnapshot", snapshotId)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudSnapshot) Cleanup(state multistep.StateBag) {
|
||||
if s.snapshot == nil {
|
||||
return
|
||||
}
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if !cancelled && !halted {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Deleting the snapshot because of cancellation or error...")
|
||||
if err := client.DeleteSnapshot(s.snapshot.SnapshotId); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting snapshot, it may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepCreateTags struct {
|
||||
Tags map[string]string
|
||||
}
|
||||
|
||||
func (s *stepCreateTags) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
imageId := state.Get("alicloudimage").(string)
|
||||
snapshotIds := state.Get("alicloudsnapshots").([]string)
|
||||
|
||||
if len(s.Tags) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Adding tags(%s) to image: %s", s.Tags, imageId))
|
||||
err := client.AddTags(&ecs.AddTagsArgs{
|
||||
ResourceId: imageId,
|
||||
ResourceType: ecs.TagResourceImage,
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
Tag: s.Tags,
|
||||
})
|
||||
if err != nil {
|
||||
return halt(state, err, "Error Adding tags to image")
|
||||
}
|
||||
|
||||
for _, snapshotId := range snapshotIds {
|
||||
ui.Say(fmt.Sprintf("Adding tags(%s) to snapshot: %s", s.Tags, snapshotId))
|
||||
err = client.AddTags(&ecs.AddTagsArgs{
|
||||
ResourceId: snapshotId,
|
||||
ResourceType: ecs.TagResourceSnapshot,
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
Tag: s.Tags,
|
||||
})
|
||||
if err != nil {
|
||||
return halt(state, err, "Error Adding tags to snapshot")
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
func (s *stepCreateTags) Cleanup(state multistep.StateBag) {
|
||||
// Nothing need to do, tags will be cleaned when the resource is cleaned
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ type stepMountAlicloudDisk struct {
|
||||
|
||||
func (s *stepMountAlicloudDisk) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
alicloudDiskDevices := config.ECSImagesDiskMappings
|
||||
|
||||
@@ -23,7 +23,7 @@ func (s *stepPreValidate) Run(_ context.Context, state multistep.StateBag) multi
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
ui.Say("Prevalidating image name...")
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
ImageName: s.AlicloudDestImageName,
|
||||
|
||||
@@ -24,9 +24,7 @@ func (s *stepRunAlicloudInstance) Run(_ context.Context, state multistep.StateBa
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Starting instance: %s", instance.InstanceId))
|
||||
|
||||
ui.Say("Starting instance.")
|
||||
err = client.WaitForInstance(instance.InstanceId, ecs.Running, ALICLOUD_DEFAULT_TIMEOUT)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Timeout waiting for instance to start: %s", err)
|
||||
|
||||
@@ -10,8 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type stepStopAlicloudInstance struct {
|
||||
ForceStop bool
|
||||
DisableStop bool
|
||||
ForceStop bool
|
||||
}
|
||||
|
||||
func (s *stepStopAlicloudInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
@@ -19,9 +18,8 @@ func (s *stepStopAlicloudInstance) Run(_ context.Context, state multistep.StateB
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if !s.DisableStop {
|
||||
ui.Say(fmt.Sprintf("Stopping instance: %s", instance.InstanceId))
|
||||
err := client.StopInstance(instance.InstanceId, s.ForceStop)
|
||||
err := client.StopInstance(instance.InstanceId, s.ForceStop)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error stopping alicloud instance: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -30,9 +28,7 @@ func (s *stepStopAlicloudInstance) Run(_ context.Context, state multistep.StateB
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Waiting instance stopped: %s", instance.InstanceId))
|
||||
|
||||
err := client.WaitForInstance(instance.InstanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT)
|
||||
err = client.WaitForInstance(instance.InstanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for alicloud instance to stop: %s", err)
|
||||
state.Put("error", err)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// StepRegisterAMI creates the AMI.
|
||||
type StepRegisterAMI struct {
|
||||
RootVolumeSize int64
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMIENASupport bool
|
||||
EnableAMISriovNetSupport bool
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
|
||||
registerOpts.SriovNetSupport = aws.String("simple")
|
||||
}
|
||||
if s.EnableAMIENASupport != nil && *s.EnableAMIENASupport {
|
||||
if s.EnableAMIENASupport {
|
||||
// Set EnaSupport to true
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
registerOpts.EnaSupport = aws.Bool(true)
|
||||
|
||||
@@ -1,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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -88,10 +88,6 @@ func buildBlockDevices(b []BlockDevice) []*ec2.BlockDeviceMapping {
|
||||
}
|
||||
|
||||
func (b *BlockDevice) Prepare(ctx *interpolate.Context) error {
|
||||
if b.DeviceName == "" {
|
||||
return fmt.Errorf("The `device_name` must be specified " +
|
||||
"for every device in the block device mapping.")
|
||||
}
|
||||
// Warn that encrypted must be true when setting kms_key_id
|
||||
if b.KmsKeyId != "" && b.Encrypted == false {
|
||||
return fmt.Errorf("The device %v, must also have `encrypted: "+
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
)
|
||||
|
||||
// Build a slice of EC2 (AMI/Subnet/VPC) filter options from the filters provided.
|
||||
func buildEc2Filters(input map[*string]*string) []*ec2.Filter {
|
||||
var filters []*ec2.Filter
|
||||
for k, v := range input {
|
||||
filters = append(filters, &ec2.Filter{
|
||||
Name: k,
|
||||
Values: []*string{v},
|
||||
})
|
||||
}
|
||||
return filters
|
||||
}
|
||||
@@ -1,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
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"}),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"sort"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// StepNetworkInfo queries AWS for information about
|
||||
// VPC's and Subnets that is used throughout the AMI creation process.
|
||||
//
|
||||
// Produces (adding them to the state bag):
|
||||
// vpc_id string - the VPC ID
|
||||
// subnet_id string - the Subnet ID
|
||||
// availability_zone string - the AZ name
|
||||
type StepNetworkInfo struct {
|
||||
VpcId string
|
||||
VpcFilter VpcFilterOptions
|
||||
SubnetId string
|
||||
SubnetFilter SubnetFilterOptions
|
||||
AvailabilityZone string
|
||||
SecurityGroupIds []string
|
||||
SecurityGroupFilter SecurityGroupFilterOptions
|
||||
}
|
||||
|
||||
type subnetsSort []*ec2.Subnet
|
||||
|
||||
func (a subnetsSort) Len() int { return len(a) }
|
||||
func (a subnetsSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a subnetsSort) Less(i, j int) bool {
|
||||
return *a[i].AvailableIpAddressCount < *a[j].AvailableIpAddressCount
|
||||
}
|
||||
|
||||
// Returns the most recent AMI out of a slice of images.
|
||||
func mostFreeSubnet(subnets []*ec2.Subnet) *ec2.Subnet {
|
||||
sortedSubnets := subnets
|
||||
sort.Sort(subnetsSort(sortedSubnets))
|
||||
return sortedSubnets[len(sortedSubnets)-1]
|
||||
}
|
||||
|
||||
func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// VPC
|
||||
if s.VpcId == "" && !s.VpcFilter.Empty() {
|
||||
params := &ec2.DescribeVpcsInput{}
|
||||
params.Filters = buildEc2Filters(s.VpcFilter.Filters)
|
||||
s.VpcFilter.Filters[aws.String("state")] = aws.String("available")
|
||||
|
||||
log.Printf("Using VPC Filters %v", params)
|
||||
|
||||
vpcResp, err := ec2conn.DescribeVpcs(params)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying VPCs: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(vpcResp.Vpcs) != 1 {
|
||||
err := fmt.Errorf("Exactly one VPC should match the filter, but %d VPC's was found matching filters: %v", len(vpcResp.Vpcs), params)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.VpcId = *vpcResp.Vpcs[0].VpcId
|
||||
ui.Message(fmt.Sprintf("Found VPC ID: %s", s.VpcId))
|
||||
}
|
||||
|
||||
// Subnet
|
||||
if s.SubnetId == "" && !s.SubnetFilter.Empty() {
|
||||
params := &ec2.DescribeSubnetsInput{}
|
||||
s.SubnetFilter.Filters[aws.String("state")] = aws.String("available")
|
||||
|
||||
if s.VpcId != "" {
|
||||
s.SubnetFilter.Filters[aws.String("vpc-id")] = &s.VpcId
|
||||
}
|
||||
if s.AvailabilityZone != "" {
|
||||
s.SubnetFilter.Filters[aws.String("availability-zone")] = &s.AvailabilityZone
|
||||
}
|
||||
params.Filters = buildEc2Filters(s.SubnetFilter.Filters)
|
||||
log.Printf("Using Subnet Filters %v", params)
|
||||
|
||||
subnetsResp, err := ec2conn.DescribeSubnets(params)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying Subnets: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(subnetsResp.Subnets) == 0 {
|
||||
err := fmt.Errorf("No Subnets was found matching filters: %v", params)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(subnetsResp.Subnets) > 1 && !s.SubnetFilter.Random && !s.SubnetFilter.MostFree {
|
||||
err := fmt.Errorf("Your filter matched %d Subnets. Please try a more specific search, or set random or most_free to true.", len(subnetsResp.Subnets))
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var subnet *ec2.Subnet
|
||||
switch {
|
||||
case s.SubnetFilter.MostFree:
|
||||
subnet = mostFreeSubnet(subnetsResp.Subnets)
|
||||
case s.SubnetFilter.Random:
|
||||
subnet = subnetsResp.Subnets[rand.Intn(len(subnetsResp.Subnets))]
|
||||
default:
|
||||
subnet = subnetsResp.Subnets[0]
|
||||
}
|
||||
s.SubnetId = *subnet.SubnetId
|
||||
ui.Message(fmt.Sprintf("Found Subnet ID: %s", s.SubnetId))
|
||||
}
|
||||
|
||||
// Try to find AZ and VPC Id from Subnet if they are not yet found/given
|
||||
if s.SubnetId != "" && (s.AvailabilityZone == "" || s.VpcId == "") {
|
||||
log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", s.SubnetId)
|
||||
resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&s.SubnetId}})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Describing the subnet: %s returned error: %s.", s.SubnetId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if s.AvailabilityZone == "" {
|
||||
s.AvailabilityZone = *resp.Subnets[0].AvailabilityZone
|
||||
log.Printf("[INFO] AvailabilityZone found: '%s'", s.AvailabilityZone)
|
||||
}
|
||||
if s.VpcId == "" {
|
||||
s.VpcId = *resp.Subnets[0].VpcId
|
||||
log.Printf("[INFO] VpcId found: '%s'", s.VpcId)
|
||||
}
|
||||
}
|
||||
|
||||
state.Put("vpc_id", s.VpcId)
|
||||
state.Put("availability_zone", s.AvailabilityZone)
|
||||
state.Put("subnet_id", s.SubnetId)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepNetworkInfo) Cleanup(multistep.StateBag) {}
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
type StepRunSourceInstance struct {
|
||||
AssociatePublicIpAddress bool
|
||||
AvailabilityZone string
|
||||
BlockDevices BlockDevices
|
||||
Comm *communicator.Config
|
||||
Ctx interpolate.Context
|
||||
@@ -32,6 +33,7 @@ type StepRunSourceInstance struct {
|
||||
InstanceType string
|
||||
IsRestricted bool
|
||||
SourceAMI string
|
||||
SubnetId string
|
||||
Tags TagMap
|
||||
UserData string
|
||||
UserDataFile string
|
||||
@@ -102,7 +104,6 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
az := state.Get("availability_zone").(string)
|
||||
runOpts := &ec2.RunInstancesInput{
|
||||
ImageId: &s.SourceAMI,
|
||||
InstanceType: &s.InstanceType,
|
||||
@@ -111,7 +112,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
MinCount: aws.Int64(1),
|
||||
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
|
||||
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
|
||||
Placement: &ec2.Placement{AvailabilityZone: &az},
|
||||
Placement: &ec2.Placement{AvailabilityZone: &s.AvailabilityZone},
|
||||
EbsOptimized: &s.EbsOptimized,
|
||||
}
|
||||
|
||||
@@ -152,20 +153,18 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
runOpts.KeyName = &s.Comm.SSHKeyPairName
|
||||
}
|
||||
|
||||
subnetId := state.Get("subnet_id").(string)
|
||||
|
||||
if subnetId != "" && s.AssociatePublicIpAddress {
|
||||
if s.SubnetId != "" && s.AssociatePublicIpAddress {
|
||||
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
|
||||
{
|
||||
DeviceIndex: aws.Int64(0),
|
||||
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
|
||||
SubnetId: aws.String(subnetId),
|
||||
SubnetId: &s.SubnetId,
|
||||
Groups: securityGroupIds,
|
||||
DeleteOnTermination: aws.Bool(true),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
runOpts.SubnetId = aws.String(subnetId)
|
||||
runOpts.SubnetId = &s.SubnetId
|
||||
runOpts.SecurityGroupIds = securityGroupIds
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
type StepRunSpotInstance struct {
|
||||
AssociatePublicIpAddress bool
|
||||
AvailabilityZone string
|
||||
BlockDevices BlockDevices
|
||||
BlockDurationMinutes int64
|
||||
Debug bool
|
||||
@@ -35,6 +36,7 @@ type StepRunSpotInstance struct {
|
||||
SpotPrice string
|
||||
SpotPriceProduct string
|
||||
SpotTags TagMap
|
||||
SubnetId string
|
||||
Tags TagMap
|
||||
VolumeTags TagMap
|
||||
UserData string
|
||||
@@ -84,12 +86,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
}
|
||||
|
||||
spotPrice := s.SpotPrice
|
||||
azConfig := ""
|
||||
if azRaw, ok := state.GetOk("availability_zone"); ok {
|
||||
azConfig = azRaw.(string)
|
||||
}
|
||||
az := azConfig
|
||||
|
||||
availabilityZone := s.AvailabilityZone
|
||||
if spotPrice == "auto" {
|
||||
ui.Message(fmt.Sprintf(
|
||||
"Finding spot price for %s %s...",
|
||||
@@ -100,7 +97,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistoryInput{
|
||||
InstanceTypes: []*string{&s.InstanceType},
|
||||
ProductDescriptions: []*string{&s.SpotPriceProduct},
|
||||
AvailabilityZone: &az,
|
||||
AvailabilityZone: &s.AvailabilityZone,
|
||||
StartTime: &startTime,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -120,8 +117,8 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
}
|
||||
if price == 0 || current < price {
|
||||
price = current
|
||||
if azConfig == "" {
|
||||
az = *history.AvailabilityZone
|
||||
if s.AvailabilityZone == "" {
|
||||
availabilityZone = *history.AvailabilityZone
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,26 +162,24 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
UserData: &userData,
|
||||
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
|
||||
Placement: &ec2.SpotPlacement{
|
||||
AvailabilityZone: &az,
|
||||
AvailabilityZone: &availabilityZone,
|
||||
},
|
||||
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
|
||||
EbsOptimized: &s.EbsOptimized,
|
||||
}
|
||||
|
||||
subnetId := state.Get("subnet_id").(string)
|
||||
|
||||
if subnetId != "" && s.AssociatePublicIpAddress {
|
||||
if s.SubnetId != "" && s.AssociatePublicIpAddress {
|
||||
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
|
||||
{
|
||||
DeviceIndex: aws.Int64(0),
|
||||
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
|
||||
SubnetId: &subnetId,
|
||||
SubnetId: &s.SubnetId,
|
||||
Groups: securityGroupIds,
|
||||
DeleteOnTermination: aws.Bool(true),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
runOpts.SubnetId = &subnetId
|
||||
runOpts.SubnetId = &s.SubnetId
|
||||
runOpts.SecurityGroupIds = securityGroupIds
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
@@ -18,8 +17,8 @@ import (
|
||||
|
||||
type StepSecurityGroup struct {
|
||||
CommConfig *communicator.Config
|
||||
SecurityGroupFilter SecurityGroupFilterOptions
|
||||
SecurityGroupIds []string
|
||||
VpcId string
|
||||
TemporarySGSourceCidr string
|
||||
|
||||
createdGroupId string
|
||||
@@ -28,7 +27,6 @@ type StepSecurityGroup struct {
|
||||
func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vpcId := state.Get("vpc_id").(string)
|
||||
|
||||
if len(s.SecurityGroupIds) > 0 {
|
||||
_, err := ec2conn.DescribeSecurityGroups(
|
||||
@@ -47,35 +45,6 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if !s.SecurityGroupFilter.Empty() {
|
||||
|
||||
params := &ec2.DescribeSecurityGroupsInput{}
|
||||
if vpcId != "" {
|
||||
s.SecurityGroupFilter.Filters[aws.String("vpc-id")] = &vpcId
|
||||
}
|
||||
params.Filters = buildEc2Filters(s.SecurityGroupFilter.Filters)
|
||||
|
||||
log.Printf("Using SecurityGroup Filters %v", params)
|
||||
|
||||
sgResp, err := ec2conn.DescribeSecurityGroups(params)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Couldn't find security groups for filter: %s", err)
|
||||
log.Printf("[DEBUG] %s", err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
securityGroupIds := []string{}
|
||||
for _, sg := range sgResp.SecurityGroups {
|
||||
securityGroupIds = append(securityGroupIds, *sg.GroupId)
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Found Security Group(s): %s", strings.Join(securityGroupIds, ", ")))
|
||||
state.Put("securityGroupIds", securityGroupIds)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
port := s.CommConfig.Port()
|
||||
if port == 0 {
|
||||
if s.CommConfig.Type != "none" {
|
||||
@@ -91,7 +60,9 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
|
||||
Description: aws.String("Temporary group for Packer"),
|
||||
}
|
||||
|
||||
group.VpcId = &vpcId
|
||||
if s.VpcId != "" {
|
||||
group.VpcId = &s.VpcId
|
||||
}
|
||||
|
||||
groupResp, err := ec2conn.CreateSecurityGroup(group)
|
||||
if err != nil {
|
||||
@@ -103,7 +74,38 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
|
||||
// Set the group ID so we can delete it later
|
||||
s.createdGroupId = *groupResp.GroupId
|
||||
|
||||
// Wait for the security group become available for authorizing
|
||||
// Authorize the SSH access for the security group
|
||||
req := &ec2.AuthorizeSecurityGroupIngressInput{
|
||||
GroupId: groupResp.GroupId,
|
||||
IpProtocol: aws.String("tcp"),
|
||||
FromPort: aws.Int64(int64(port)),
|
||||
ToPort: aws.Int64(int64(port)),
|
||||
CidrIp: aws.String(s.TemporarySGSourceCidr),
|
||||
}
|
||||
|
||||
// We loop and retry this a few times because sometimes the security
|
||||
// group isn't available immediately because AWS resources are eventually
|
||||
// consistent.
|
||||
ui.Say(fmt.Sprintf(
|
||||
"Authorizing access to port %d from %s in the temporary security group...",
|
||||
port, s.TemporarySGSourceCidr))
|
||||
for i := 0; i < 5; i++ {
|
||||
_, err = ec2conn.AuthorizeSecurityGroupIngress(req)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Error authorizing. Will sleep and retry. %s", err)
|
||||
time.Sleep((time.Duration(i) * time.Second) + 1)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating temporary security group: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Waiting for temporary security group: %s", s.createdGroupId)
|
||||
err = waitUntilSecurityGroupExists(ec2conn,
|
||||
&ec2.DescribeSecurityGroupsInput{
|
||||
@@ -119,34 +121,6 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Authorize the SSH access for the security group
|
||||
groupRules := &ec2.AuthorizeSecurityGroupIngressInput{
|
||||
GroupId: groupResp.GroupId,
|
||||
IpPermissions: []*ec2.IpPermission{
|
||||
{
|
||||
FromPort: aws.Int64(int64(port)),
|
||||
ToPort: aws.Int64(int64(port)),
|
||||
IpRanges: []*ec2.IpRange{
|
||||
{
|
||||
CidrIp: aws.String(s.TemporarySGSourceCidr),
|
||||
},
|
||||
},
|
||||
IpProtocol: aws.String("tcp"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf(
|
||||
"Authorizing access to port %d from %s in the temporary security group...",
|
||||
port, s.TemporarySGSourceCidr))
|
||||
_, err = ec2conn.AuthorizeSecurityGroupIngress(groupRules)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error authorizing temporary security group: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set some state data for use in future steps
|
||||
state.Put("securityGroupIds", []string{s.createdGroupId})
|
||||
|
||||
|
||||
@@ -20,11 +20,23 @@ import (
|
||||
type StepSourceAMIInfo struct {
|
||||
SourceAmi string
|
||||
EnableAMISriovNetSupport bool
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMIENASupport bool
|
||||
AMIVirtType string
|
||||
AmiFilters AmiFilterOptions
|
||||
}
|
||||
|
||||
// Build a slice of AMI filter options from the filters provided.
|
||||
func buildAmiFilters(input map[*string]*string) []*ec2.Filter {
|
||||
var filters []*ec2.Filter
|
||||
for k, v := range input {
|
||||
filters = append(filters, &ec2.Filter{
|
||||
Name: k,
|
||||
Values: []*string{v},
|
||||
})
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
type imageSort []*ec2.Image
|
||||
|
||||
func (a imageSort) Len() int { return len(a) }
|
||||
@@ -54,7 +66,7 @@ func (s *StepSourceAMIInfo) Run(_ context.Context, state multistep.StateBag) mul
|
||||
|
||||
// We have filters to apply
|
||||
if len(s.AmiFilters.Filters) > 0 {
|
||||
params.Filters = buildEc2Filters(s.AmiFilters.Filters)
|
||||
params.Filters = buildAmiFilters(s.AmiFilters.Filters)
|
||||
}
|
||||
if len(s.AmiFilters.Owners) > 0 {
|
||||
params.Owners = s.AmiFilters.Owners
|
||||
@@ -94,7 +106,7 @@ func (s *StepSourceAMIInfo) Run(_ context.Context, state multistep.StateBag) mul
|
||||
|
||||
// Enhanced Networking can only be enabled on HVM AMIs.
|
||||
// See http://goo.gl/icuXh5
|
||||
if (s.EnableAMIENASupport != nil && *s.EnableAMIENASupport) || s.EnableAMISriovNetSupport {
|
||||
if s.EnableAMIENASupport || s.EnableAMISriovNetSupport {
|
||||
err = s.canEnableEnhancedNetworking(image)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -47,7 +47,6 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
|
||||
// Test good
|
||||
config["ami_name"] = "foo"
|
||||
config["skip_region_validation"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
@@ -100,7 +99,6 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
|
||||
|
||||
// Test good
|
||||
config["shutdown_behavior"] = "terminate"
|
||||
config["skip_region_validation"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
|
||||
@@ -17,7 +17,7 @@ type stepCreateAMI struct {
|
||||
}
|
||||
|
||||
func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
config := state.Get("config").(Config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -41,8 +41,6 @@ func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
func TestBuilderPrepare_AccountId(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
@@ -86,7 +84,6 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
|
||||
// Test good
|
||||
config["ami_name"] = "foo"
|
||||
config["skip_region_validation"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
@@ -121,7 +118,6 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
func TestBuilderPrepare_BundleDestination(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
@@ -142,7 +138,6 @@ func TestBuilderPrepare_BundleDestination(t *testing.T) {
|
||||
func TestBuilderPrepare_BundlePrefix(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
@@ -179,7 +174,6 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
func TestBuilderPrepare_S3Bucket(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
@@ -205,7 +199,6 @@ func TestBuilderPrepare_S3Bucket(t *testing.T) {
|
||||
func TestBuilderPrepare_X509CertPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
@@ -247,7 +240,6 @@ func TestBuilderPrepare_X509CertPath(t *testing.T) {
|
||||
func TestBuilderPrepare_X509KeyPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
@@ -289,7 +281,6 @@ func TestBuilderPrepare_X509KeyPath(t *testing.T) {
|
||||
func TestBuilderPrepare_X509UploadPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
type StepRegisterAMI struct {
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMIENASupport bool
|
||||
EnableAMISriovNetSupport bool
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
|
||||
registerOpts.SriovNetSupport = aws.String("simple")
|
||||
}
|
||||
if s.EnableAMIENASupport != nil && *s.EnableAMIENASupport {
|
||||
if s.EnableAMIENASupport {
|
||||
// Set EnaSupport to true
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
registerOpts.EnaSupport = aws.Bool(true)
|
||||
|
||||
@@ -4,7 +4,7 @@ Here's a list of things we like to get done in no particular order:
|
||||
- [ ] Blob/image rename post-processor
|
||||
- [ ] SSH to private ip through subnet
|
||||
- [ ] chroot builder
|
||||
- [ ] support cross-storage account image source (i.e. pre-build blob copy)
|
||||
- [ ] support cross-storage account image source (ie. pre-build blob copy)
|
||||
- [ ] look up object id when using device code (graph api /me ?)
|
||||
- [ ] device flow support for Windows
|
||||
- [x] look up tenant id in all cases (see device flow code)
|
||||
|
||||
@@ -29,26 +29,22 @@ type Artifact struct {
|
||||
TemplateUriReadOnlySas string
|
||||
|
||||
// Managed Image
|
||||
ManagedImageResourceGroupName string
|
||||
ManagedImageName string
|
||||
ManagedImageLocation string
|
||||
ManagedImageId string
|
||||
ManagedImageOSDiskSnapshotName string
|
||||
ManagedImageDataDiskSnapshotPrefix string
|
||||
ManagedImageResourceGroupName string
|
||||
ManagedImageName string
|
||||
ManagedImageLocation string
|
||||
ManagedImageId string
|
||||
|
||||
// Additional Disks
|
||||
AdditionalDisks *[]AdditionalDiskArtifact
|
||||
}
|
||||
|
||||
func NewManagedImageArtifact(osType, resourceGroup, name, location, id, osDiskSnapshotName, dataDiskSnapshotPrefix string) (*Artifact, error) {
|
||||
func NewManagedImageArtifact(osType, resourceGroup, name, location, id string) (*Artifact, error) {
|
||||
return &Artifact{
|
||||
ManagedImageResourceGroupName: resourceGroup,
|
||||
ManagedImageName: name,
|
||||
ManagedImageLocation: location,
|
||||
ManagedImageId: id,
|
||||
OSType: osType,
|
||||
ManagedImageOSDiskSnapshotName: osDiskSnapshotName,
|
||||
ManagedImageDataDiskSnapshotPrefix: dataDiskSnapshotPrefix,
|
||||
ManagedImageResourceGroupName: resourceGroup,
|
||||
ManagedImageName: name,
|
||||
ManagedImageLocation: location,
|
||||
ManagedImageId: id,
|
||||
OSType: osType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -137,10 +133,7 @@ func (*Artifact) Files() []string {
|
||||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
if a.OSDiskUri != "" {
|
||||
return a.OSDiskUri
|
||||
}
|
||||
return a.ManagedImageId
|
||||
return a.OSDiskUri
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
@@ -162,12 +155,6 @@ func (a *Artifact) String() string {
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageName: %s\n", a.ManagedImageName))
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageId: %s\n", a.ManagedImageId))
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageLocation: %s\n", a.ManagedImageLocation))
|
||||
if a.ManagedImageOSDiskSnapshotName != "" {
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageOSDiskSnapshotName: %s\n", a.ManagedImageOSDiskSnapshotName))
|
||||
}
|
||||
if a.ManagedImageDataDiskSnapshotPrefix != "" {
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageDataDiskSnapshotPrefix: %s\n", a.ManagedImageDataDiskSnapshotPrefix))
|
||||
}
|
||||
} else {
|
||||
buf.WriteString(fmt.Sprintf("StorageAccountLocation: %s\n", a.StorageAccountLocation))
|
||||
buf.WriteString(fmt.Sprintf("OSDiskUri: %s\n", a.OSDiskUri))
|
||||
|
||||
@@ -10,7 +10,7 @@ func getFakeSasUrl(name string) string {
|
||||
return fmt.Sprintf("SAS-%s", name)
|
||||
}
|
||||
|
||||
func TestArtifactIdVHD(t *testing.T) {
|
||||
func TestArtifactId(t *testing.T) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
{
|
||||
@@ -41,73 +41,6 @@ func TestArtifactIdVHD(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImage(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "fakeOsDiskSnapshotName", "fakeDataDiskSnapshotPrefix")
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
expected := `Azure.ResourceManagement.VMImage:
|
||||
|
||||
OSType: Linux
|
||||
ManagedImageResourceGroupName: fakeResourceGroup
|
||||
ManagedImageName: fakeName
|
||||
ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageOSDiskSnapshotName: fakeOsDiskSnapshotName
|
||||
ManagedImageDataDiskSnapshotPrefix: fakeDataDiskSnapshotPrefix
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
if result != expected {
|
||||
t.Fatalf("bad: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImageWithoutOSDiskSnapshotName(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "", "fakeDataDiskSnapshotPrefix")
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
expected := `Azure.ResourceManagement.VMImage:
|
||||
|
||||
OSType: Linux
|
||||
ManagedImageResourceGroupName: fakeResourceGroup
|
||||
ManagedImageName: fakeName
|
||||
ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageDataDiskSnapshotPrefix: fakeDataDiskSnapshotPrefix
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
if result != expected {
|
||||
t.Fatalf("bad: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImageWithoutDataDiskSnapshotPrefix(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "fakeOsDiskSnapshotName", "")
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
expected := `Azure.ResourceManagement.VMImage:
|
||||
|
||||
OSType: Linux
|
||||
ManagedImageResourceGroupName: fakeResourceGroup
|
||||
ManagedImageName: fakeName
|
||||
ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageOSDiskSnapshotName: fakeOsDiskSnapshotName
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
if result != expected {
|
||||
t.Fatalf("bad: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactString(t *testing.T) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+2
-2
@@ -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())
|
||||
@@ -40,7 +40,6 @@ type AzureClient struct {
|
||||
common.VaultClient
|
||||
armStorage.AccountsClient
|
||||
compute.DisksClient
|
||||
compute.SnapshotsClient
|
||||
|
||||
InspectorMaxLength int
|
||||
Template *CaptureTemplate
|
||||
@@ -190,12 +189,6 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
|
||||
azureClient.VirtualMachinesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), templateCapture(azureClient), errorCapture(azureClient))
|
||||
azureClient.VirtualMachinesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VirtualMachinesClient.UserAgent)
|
||||
|
||||
azureClient.SnapshotsClient = compute.NewSnapshotsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.SnapshotsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.SnapshotsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.SnapshotsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.SnapshotsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.SnapshotsClient.UserAgent)
|
||||
|
||||
azureClient.AccountsClient = armStorage.NewAccountsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.AccountsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.AccountsClient.RequestInspector = withInspection(maxlen)
|
||||
|
||||
@@ -183,8 +183,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
NewStepGetOSDisk(azureClient, ui),
|
||||
NewStepGetAdditionalDisks(azureClient, ui),
|
||||
NewStepPowerOffCompute(azureClient, ui),
|
||||
NewStepSnapshotOSDisk(azureClient, ui, b.config),
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
-170
@@ -1,170 +0,0 @@
|
||||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminPassword": "[parameters('adminPassword')]",
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "--test-ssh-authorized-key--",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"imageReference": {
|
||||
"id": "/subscriptions/ignore/resourceGroups/ignore/providers/Microsoft.Compute/galleries/ignore/images/ignore"
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "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)
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user