Compare commits

..

7 Commits

Author SHA1 Message Date
Matthew Hooker 9e8a3fa3ea bump gem deps and www version 2016-09-29 20:21:23 -07:00
Matthew Hooker fb1c968d57 Cut version 0.10.2 2016-09-20 16:42:06 -07:00
Matthew Hooker 141440147f Cut version 0.10.2 2016-09-20 16:38:01 -07:00
Matthew Hooker bb92eef95e Cut version 0.10.2 2016-09-20 16:34:31 -07:00
Sean Chittenden 489c75a363 Go's -X linker flag now requires only one argument (#3540)
This fixes building `packer` with Go >1.6.  From https://golang.org/cmd/link/:

```
-X importpath.name=value
	Set the value of the string variable in importpath named name to value.
	Note that before Go 1.5 this option took two separate arguments.
	Now it takes one argument split on the first = sign.
```
2016-09-20 16:34:30 -07:00
Chris Bednarski 7bbc29d4b2 Fix go vet casing issue 2016-09-20 16:28:55 -07:00
Chris Bednarski 8e7bb5ec23 Change version.go to 0.10.1 release 2016-09-20 16:28:55 -07:00
7852 changed files with 191585 additions and 1447531 deletions
-133
View File
@@ -1,133 +0,0 @@
defaults: &golang
docker:
- image: circleci/golang:1.12
working_directory: /go/src/github.com/hashicorp/packer
steps:
- checkout
- run: go build -ldflags="-s -w" -o ./pkg/packer_$(go env GOOS)_$(go env GOARCH) .
- run: zip ./pkg/packer_$(go env GOOS)_$(go env GOARCH).zip ./pkg/packer_$(go env GOOS)_$(go env GOARCH)
- run: rm ./pkg/packer_$(go env GOOS)_$(go env GOARCH)
- persist_to_workspace:
root: .
paths:
- ./pkg/
# Golang CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-go/ for more details
version: 2
jobs:
build:
<<: *golang
steps:
- checkout
- run: make ci
check-vendor-vs-mod:
<<: *golang
steps:
- checkout
- run: GO111MODULE=on go run . --help
- run: make check-vendor-vs-mod
check-fmt:
<<: *golang
steps:
- checkout
- run: make fmt-check
check-generate:
<<: *golang
steps:
- checkout
- run: make generate-check
build_linux:
<<: *golang
environment:
GOOS: linux
build_windows:
<<: *golang
environment:
GOOS: windows
build_darwin:
<<: *golang
environment:
GOOS: darwin
build_freebsd:
<<: *golang
environment:
GOOS: freebsd
build_solaris:
<<: *golang
environment:
GOOS: solaris
build_openbsd:
<<: *golang
environment:
GOOS: openbsd
store_artifacts:
<<: *golang
steps:
- attach_workspace:
at: .
- store_artifacts:
path: ./pkg/
destination: /
publish-github-tag-release:
docker:
- image: cibuilds/github:0.10
working_directory: /go/src/github.com/hashicorp/packer
steps:
- attach_workspace:
at: .
- run: |
ghr -prerelease -t ${GITHUB_TOKEN_AZR} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${CIRCLE_TAG} ./pkg/
workflows:
version: 2
build_and_check_vendor_vs_module:
jobs:
- build
- check-vendor-vs-mod
- check-fmt
- check-generate
- build_linux:
filters:
tags:
only: /.*/
- build_darwin:
filters:
tags:
only: /.*/
- build_windows:
filters:
tags:
only: /.*/
- build_freebsd:
filters:
tags:
only: /.*/
- build_openbsd:
filters:
tags:
only: /.*/
- build_solaris:
filters:
tags:
only: /.*/
- store_artifacts:
requires:
- build_linux
- build_darwin
- build_windows
- build_freebsd
- build_openbsd
- build_solaris
- publish-github-tag-release:
requires:
- build_linux
- build_darwin
- build_windows
- build_freebsd
- build_openbsd
- build_solaris
filters:
branches:
ignore: /.*/
tags:
only: nightly
-7
View File
@@ -1,7 +0,0 @@
* text=auto
*.go text eol=lf
*.sh text eol=lf
*.json text eol=lf
*.md text eol=lf
*.ps1 text eol=lf
common/test-fixtures/root/* eol=lf
-294
View File
@@ -1,294 +0,0 @@
# 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
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.
However, for those individuals who want a bit more guidance on the best way to
contribute to the project, read on. This document will cover what we're looking
for. By addressing all the points we're looking for, it raises the chances we
can quickly merge or address your contributions.
## Issues
### Reporting an Issue
* Make sure you test against the latest released version. It is possible we
already fixed the bug you're experiencing.
* Run the command with debug output with the environment variable `PACKER_LOG`.
For example: `PACKER_LOG=1 packer build template.json`. Take the _entire_
output and create a [gist](https://gist.github.com) for linking to in your
issue. Packer should strip sensitive keys from the output, but take a look
through just in case.
* Provide a reproducible test case. If a contributor can't reproduce an issue,
then it dramatically lowers the chances it'll get fixed. And in some cases,
the issue will eventually be closed.
* Respond promptly to any questions made by the Packer team to your issue. Stale
issues will be closed.
### Issue Lifecycle
1. The issue is reported.
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".
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.
4. The issue is addressed in a pull request or commit. The issue will be
referenced in the commit message so that the code that fixes it is clearly
linked.
5. The issue is closed.
## Setting up Go
If you have never worked with Go before, you will have to install its
runtime in order to build packer.
1. This project always releases from the latest version of golang. [Install go](https://golang.org/doc/install#install)
## Setting up Packer for dev
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
adjust them for Windows or other shells.
The instructions below are for go 1.7. or later.
1. Download the Packer source (and its dependencies) by running
`go get github.com/hashicorp/packer`. This will download the Packer source to
`$GOPATH/src/github.com/hashicorp/packer`.
2. When working on Packer, first `cd $GOPATH/src/github.com/hashicorp/packer`
so you can run `make` and easily access other files. Run `make help` to get
information about make targets.
3. Make your changes to the Packer source. You can run `make` in
`$GOPATH/src/github.com/hashicorp/packer` to run tests and build the Packer
binary. Any compilation errors will be shown when the binaries are
rebuilding. If you don't have `make` you can simply run
`go build -o bin/packer .` from the project root.
4. After running building Packer successfully, use
`$GOPATH/src/github.com/hashicorp/packer/bin/packer` to build a machine and
verify your changes work. For instance:
`$GOPATH/src/github.com/hashicorp/packer/bin/packer build template.json`.
5. If everything works well and the tests pass, run `go fmt` on your code before
submitting a pull-request.
### Windows Systems
On windows systems you need at least the [MinGW Tools](http://www.mingw.org/), e.g. install via [choco](https://chocolatey.org/):
```
choco install mingw -y
```
This installs the GCC compiler, as well as a ```mingw32-make``` which can be used wherever
this documentation mentions ```make```
when building using ```go``` you also need to mention the windows
executable extension
```
go build -o bin/packer.exe
```
### Opening an Pull Request
Thank you for contributing! When you are ready to open a pull-request, you will
need to [fork
Packer](https://github.com/hashicorp/packer#fork-destination-box), push your
changes to your fork, and then open a pull-request.
For example, my github username is `cbednarski`, so I would do the following:
```
git checkout -b f-my-feature
# Develop a patch.
git push https://github.com/cbednarski/Packer f-my-feature
```
From there, open your fork in your browser to open a new pull-request.
**Note:** Go infers package names from their file paths. This means `go build`
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
it is fully completed. Please prefix the title of your pull request with
"[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.
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.
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
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.
### 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.
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
lives in the spot where the go cli tools are expecting to find it.
#### Go modules & go vendor
If you are submitting a change that requires new or updated dependencies,
please include them in `go.mod`/`go.sum` and in the `vendor/` folder. This
helps everything get tested properly in CI.
Note that you will need to use [go
mod](https://github.com/golang/go/wiki/Modules) to do this. This step is
recommended but not required.
Use `go get <project>` to add dependencies to the project and `go mod vendor`
to make vendored copy of dependencies. See [go mod quick
start](https://github.com/golang/go/wiki/Modules#quick-start) for examples.
Please only apply the minimal vendor changes to get your PR to work. Packer
does not attempt to track the latest version for each dependency.
#### Running Unit Tests
You can run tests for individual packages using commands like this:
```
make test TEST=./builder/amazon/...
```
#### Running Acceptance Tests
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
functioning (and also hasn't broken anything else), we recommend running the
acceptance tests.
**Warning:** The acceptance tests create/destroy/modify _real resources_, which
may incur costs for real money. In the presence of a bug, it is possible that
resources may be left behind, which can cost money even though you were not
using them. We recommend running tests in an account used only for that purpose
so it is easy to see if there are any dangling resources, and so production
resources are not accidentally destroyed or overwritten during testing.
To run the acceptance tests, invoke `make testacc`:
```
make testacc TEST=./builder/amazon/ebs
...
```
The `TEST` variable lets you narrow the scope of the acceptance tests to a
specific package / folder. The `TESTARGS` variable is recommended to filter down
to a specific resource to test, since testing all of them at once can sometimes
take a very long time.
To run only a specific test, use the `-run` argument:
```
make testacc TEST=./builder/amazon/ebs TESTARGS="-run TestBuilderAcc_forceDeleteSnapshot"
```
Acceptance tests typically require other environment variables to be set for
things such as API tokens and keys. Each test should error and tell you which
credentials are missing, so those are not documented here.
+2 -8
View File
@@ -1,11 +1,6 @@
_Please read these instructions before submitting_
**DELETE THIS TEMPLATE BEFORE SUBMITTING**
_Only use GitHub issues to report bugs or feature requests, see
https://www.packer.io/community.html_
For example, _Timeouts waiting for SSH/WinRM_ are generally not bugs within packer and are better addressed by the mailing list. Ask on the mailing list if you are unsure.
_Only use Github issues to report bugs or feature requests, see https://www.packer.io/community/_
If you are planning to open a pull-request just open the pull-request instead of making an issue first.
@@ -17,10 +12,9 @@ FOR BUGS:
Describe the problem and include the following information:
- Packer version from `packer version`
- Packer Version
- Host platform
- Debug log output from `PACKER_LOG=1 packer build template.json`.
Please paste this in a gist https://gist.github.com
- The _simplest example template and scripts_ needed to reproduce the bug.
Include these in your gist https://gist.github.com
+2 -8
View File
@@ -1,17 +1,11 @@
**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:
- https://github.com/hashicorp/packer/blob/master/builder/virtualbox/common/ssh_config_test.go#L19-L37
- https://github.com/hashicorp/packer/blob/master/post-processor/compress/post-processor_test.go#L153-L182
- https://github.com/mitchellh/packer/blob/master/builder/virtualbox/common/ssh_config_test.go#L19-L37
- https://github.com/mitchellh/packer/blob/master/post-processor/compress/post-processor_test.go#L153-L182
If your PR resolves any open issue(s), please indicate them like this so they will be closed when your PR is merged:
-12
View File
@@ -4,25 +4,13 @@
/src
/website/.sass-cache
/website/build
/website/tmp
.DS_Store
.vagrant
.idea
test/.env
*~
*.received.*
*.swp
website/.bundle
website/vendor
packer-test*.log
*.received.txt
*.received.json
.idea/
*.iml
Thumbs.db
/packer.exe
.project
+7 -9
View File
@@ -1,19 +1,15 @@
env:
- USER=travis GO111MODULE=off
os:
- osx
sudo: false
language: go
go:
- 1.12.x
- 1.6
install:
- make deps
script:
- df -h
- travis_wait make ci
- GOMAXPROCS=2 make ci
branches:
only:
@@ -21,3 +17,5 @@ branches:
matrix:
fast_finish: true
allow_failures:
- go: 1.4.3
+941 -2702
View File
File diff suppressed because it is too large Load Diff
-31
View File
@@ -1,31 +0,0 @@
* @hashicorp/packer
# builders
/builder/alicloud/ @chhaj5236
/builder/azure/ @paulmey
/builder/hyperv/ @taliesins
/builder/linode/ @displague @ctreatma @stvnjacobs
/builder/lxc/ @ChrisLundquist
/builder/lxd/ @ChrisLundquist
/builder/oneandone/ @jasmingacic
/builder/oracle/ @prydie @owainlewis
/builder/profitbricks/ @jasmingacic
/builder/triton/ @sean-
/builder/ncloud/ @YuSungDuk
/builder/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
/builder/hcloud/ @LKaemmerling
/builder/hyperone @m110 @gregorybrzeski @ad-m
/builder/yandex @GennadySpb @alexanderKhaustov @seukyaso
# provisioners
/provisioner/ansible/ @bhcleek
/provisioner/converge/ @stevendborrelli
# post-processors
/post-processor/alicloud-import/ dongxiao.zzh@alibaba-inc.com
/post-processor/checksum/ v.tolstov@selfip.ru
/post-processor/googlecompute-export/ crunkleton@google.com
/post-processor/vsphere-template/ nelson@bennu.cl
+127
View File
@@ -0,0 +1,127 @@
# Contributing to Packer
**First:** if you're unsure or afraid of _anything_, just ask
or submit the 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.
However, for those individuals who want a bit more guidance on the
best way to contribute to the project, read on. This document will cover
what we're looking for. By addressing all the points we're looking for,
it raises the chances we can quickly merge or address your contributions.
## Issues
### Reporting an Issue
* Make sure you test against the latest released version. It is possible
we already fixed the bug you're experiencing.
* Run the command with debug ouput with the environment variable
`PACKER_LOG`. For example: `PACKER_LOG=1 packer build template.json`. Take
the *entire* output and create a [gist](https://gist.github.com) for linking
to in your issue. Packer should strip sensitive keys from the output,
but take a look through just in case.
* Provide a reproducible test case. If a contributor can't reproduce an
issue, then it dramatically lowers the chances it'll get fixed. And in
some cases, the issue will eventually be closed.
* Respond promptly to any questions made by the Packer team to your issue.
Stale issues will be closed.
### Issue Lifecycle
1. The issue is reported.
2. The issue is verified and categorized by a Packer collaborator.
Categorization is done via tags. For example, bugs are marked as "bugs"
and 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.
4. The issue is addressed in a pull request or commit. The issue will be
referenced in the commit message so that the code that fixes it is clearly
linked.
5. The issue is closed.
## Setting up Go to work on Packer
If you have never worked with Go before, you will have to complete the
following steps in order to be able to compile and test Packer. These instructions target POSIX-like environments (Mac OS X, Linux, Cygwin, etc.) so you may need to adjust them for Windows or other shells.
1. [Download](https://golang.org/dl) and install Go. The instructions below
are for go 1.6. Earlier versions of Go are no longer supported.
2. Set and export the `GOPATH` environment variable and update your `PATH`. For
example, you can add to your `.bash_profile`.
```
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
```
3. Download the Packer source (and its dependencies) by running `go get
github.com/mitchellh/packer`. This will download the Packer source to
`$GOPATH/src/github.com/mitchellh/packer`.
4. When working on packer `cd $GOPATH/src/github.com/mitchellh/packer` so you
can run `make` and easily access other files.
5. Make your changes to the Packer source. You can run `make` in
`$GOPATH/src/github.com/mitchellh/packer` to run tests and build the packer
binary. Any compilation errors will be shown when the binaries are
rebuilding. If you don't have `make` you can simply run `go build -o bin/packer .` from the project root.
6. After running building packer successfully, use
`$GOPATH/src/github.com/mitchellh/packer/bin/packer` to build a machine and
verify your changes work. For instance: `$GOPATH/src/github.com/mitchellh/packer/bin/packer build template.json`.
7. If everything works well and the tests pass, run `go fmt` on your code
before submitting a pull-request.
### Tips for Working on Packer
#### Godeps
If you are submitting a change that requires a change in dependencies, DO NOT update the `vendor/` folder. This keeps the PR smaller and easier to review. Instead, please indicate which upstream has changed and which version we should be using. You _may_ do this using `Godeps/Godeps.json` but this is not required.
#### Running Unit Tests
You can run tests for individual packages using commands like this:
$ make test TEST=./builder/amazon/...
#### Running Acceptance Tests
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 feature of a builder or a new builder and want verify it
is functioning (and also hasn't broken anything else), we recommend running the
acceptance tests.
**Warning:** The acceptance tests create/destroy/modify *real resources*, which
may incur real costs in some cases. In the presence of a bug, it is technically
possible that broken backends could leave dangling data behind. Therefore,
please run the acceptance tests at your own risk. At the very least, we
recommend running them in their own private account for whatever builder you're
testing.
To run the acceptance tests, invoke `make testacc`:
$ make testacc TEST=./builder/amazon/ebs
...
The `TEST` variable lets you narrow the scope of the acceptance tests to a
specific package / folder. The `TESTARGS` variable is recommended to filter
down to a specific resource to test, since testing all of them at once can
sometimes take a very long time.
Acceptance tests typically require other environment variables to be set for
things such as access keys. The test itself should error early and tell you
what to set, so it is not documented here.
-30
View File
@@ -1,30 +0,0 @@
FROM ubuntu:16.04
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && apt-get install -y \
locales \
openssh-server \
sudo
RUN locale-gen en_US.UTF-8
RUN if ! getent passwd vagrant; then useradd -d /home/vagrant -m -s /bin/bash vagrant; fi \
&& echo 'vagrant ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \
&& mkdir -p /etc/sudoers.d \
&& echo 'vagrant ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/vagrant \
&& chmod 0440 /etc/sudoers.d/vagrant
RUN mkdir -p /home/vagrant/.ssh \
&& chmod 0700 /home/vagrant/.ssh \
&& wget --no-check-certificate \
https://raw.github.com/hashicorp/vagrant/master/keys/vagrant.pub \
-O /home/vagrant/.ssh/authorized_keys \
&& chmod 0600 /home/vagrant/.ssh/authorized_keys \
&& chown -R vagrant /home/vagrant/.ssh
RUN mkdir -p /run/sshd
CMD /usr/sbin/sshd -D \
-o UseDNS=no \
-o PidFile=/tmp/sshd.pid
+633
View File
@@ -0,0 +1,633 @@
{
"ImportPath": "github.com/mitchellh/packer",
"GoVersion": "go1.6",
"GodepVersion": "v62",
"Deps": [
{
"ImportPath": "github.com/ActiveState/tail",
"Comment": "v0-41-g1a0242e",
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/compute",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/network",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/resources/resources",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/resources/subscriptions",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/storage",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/storage",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest",
"Comment": "v7.0.4",
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/azure",
"Comment": "v7.0.4",
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/date",
"Comment": "v7.0.4",
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/to",
"Comment": "v7.0.4",
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
},
{
"ImportPath": "github.com/Azure/go-ntlmssp",
"Rev": "e0b63eb299a769ea4b04dadfe530f6074b277afb"
},
{
"ImportPath": "github.com/armon/go-radix",
"Rev": "4239b77079c7b5d1243b7b4736304ce8ddb6f0f2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/awserr",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/awsutil",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/client",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/client/metadata",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/corehandlers",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/defaults",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/ec2metadata",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/request",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/session",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/endpoints",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/restxml",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/signer/v4",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/waiter",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/ec2",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/s3",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3iface",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3manager",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/sts",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/bgentry/speakeasy",
"Rev": "36e9cfdd690967f4f690c6edcc9ffacd006014a0"
},
{
"ImportPath": "github.com/dgrijalva/jwt-go",
"Comment": "v2.4.0-11-gf219341",
"Rev": "f2193411bd642f7db03249fd79d5292c9b34916a"
},
{
"ImportPath": "github.com/digitalocean/godo",
"Comment": "v0.9.0-24-g6ca5b77",
"Rev": "6ca5b770f203b82a0fca68d0941736458efa8a4f"
},
{
"ImportPath": "github.com/dylanmei/iso8601",
"Rev": "2075bf119b58e5576c6ed9f867b8f3d17f2e54d4"
},
{
"ImportPath": "github.com/dylanmei/winrmtest",
"Rev": "025617847eb2cf9bd1d851bc3b22ed28e6245ce5"
},
{
"ImportPath": "github.com/go-ini/ini",
"Comment": "v1.8.6",
"Rev": "afbd495e5aaea13597b5e14fe514ddeaa4d76fc3"
},
{
"ImportPath": "github.com/golang/protobuf/proto",
"Rev": "b982704f8bb716bb608144408cff30e15fbde841"
},
{
"ImportPath": "github.com/google/go-querystring/query",
"Rev": "2a60fc2ba6c19de80291203597d752e9ba58e4c0"
},
{
"ImportPath": "github.com/hashicorp/atlas-go/archive",
"Comment": "20141209094003-92-g95fa852",
"Rev": "95fa852edca41c06c4ce526af4bb7dec4eaad434"
},
{
"ImportPath": "github.com/hashicorp/atlas-go/v1",
"Comment": "20141209094003-92-g95fa852",
"Rev": "95fa852edca41c06c4ce526af4bb7dec4eaad434"
},
{
"ImportPath": "github.com/hashicorp/errwrap",
"Rev": "7554cd9344cec97297fa6649b055a8c98c2a1e55"
},
{
"ImportPath": "github.com/hashicorp/go-checkpoint",
"Rev": "e4b2dc34c0f698ee04750bf2035d8b9384233e1b"
},
{
"ImportPath": "github.com/hashicorp/go-cleanhttp",
"Rev": "875fb671b3ddc66f8e2f0acc33829c8cb989a38d"
},
{
"ImportPath": "github.com/hashicorp/go-multierror",
"Rev": "d30f09973e19c1dfcd120b2d9c4f168e68d6b5d5"
},
{
"ImportPath": "github.com/hashicorp/go-rootcerts",
"Rev": "6bb64b370b90e7ef1fa532be9e591a81c3493e00"
},
{
"ImportPath": "github.com/hashicorp/go-version",
"Rev": "7e3c02b30806fa5779d3bdfc152ce4c6f40e7b38"
},
{
"ImportPath": "github.com/hashicorp/yamux",
"Rev": "df949784da9ed028ee76df44652e42d37a09d7e4"
},
{
"ImportPath": "github.com/hpcloud/tail/ratelimiter",
"Comment": "v0-41-g1a0242e",
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
},
{
"ImportPath": "github.com/hpcloud/tail/util",
"Comment": "v0-41-g1a0242e",
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
},
{
"ImportPath": "github.com/hpcloud/tail/watch",
"Comment": "v0-41-g1a0242e",
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
},
{
"ImportPath": "github.com/hpcloud/tail/winfile",
"Comment": "v0-41-g1a0242e",
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
},
{
"ImportPath": "github.com/jmespath/go-jmespath",
"Comment": "0.2.2-2-gc01cf91",
"Rev": "c01cf91b011868172fdcd9f41838e80c9d716264"
},
{
"ImportPath": "github.com/kardianos/osext",
"Rev": "29ae4ffbc9a6fe9fb2bc5029050ce6996ea1d3bc"
},
{
"ImportPath": "github.com/klauspost/compress/flate",
"Rev": "f86d2e6d8a77c6a2c4e42a87ded21c6422f7557e"
},
{
"ImportPath": "github.com/klauspost/cpuid",
"Rev": "349c675778172472f5e8f3a3e0fe187e302e5a10"
},
{
"ImportPath": "github.com/klauspost/crc32",
"Rev": "999f3125931f6557b991b2f8472172bdfa578d38"
},
{
"ImportPath": "github.com/klauspost/pgzip",
"Rev": "47f36e165cecae5382ecf1ec28ebf7d4679e307d"
},
{
"ImportPath": "github.com/kr/fs",
"Rev": "2788f0dbd16903de03cb8186e5c7d97b69ad387b"
},
{
"ImportPath": "github.com/masterzen/simplexml/dom",
"Rev": "95ba30457eb1121fa27753627c774c7cd4e90083"
},
{
"ImportPath": "github.com/masterzen/winrm/soap",
"Rev": "54ea5d01478cfc2afccec1504bd0dfcd8c260cfa"
},
{
"ImportPath": "github.com/masterzen/winrm/winrm",
"Rev": "54ea5d01478cfc2afccec1504bd0dfcd8c260cfa"
},
{
"ImportPath": "github.com/masterzen/xmlpath",
"Rev": "13f4951698adc0fa9c1dda3e275d489a24201161"
},
{
"ImportPath": "github.com/mattn/go-isatty",
"Rev": "56b76bdf51f7708750eac80fa38b952bb9f32639"
},
{
"ImportPath": "github.com/mitchellh/cli",
"Rev": "5c87c51cedf76a1737bf5ca3979e8644871598a6"
},
{
"ImportPath": "github.com/mitchellh/go-fs",
"Rev": "a34c1b9334e86165685a9449b782f20465eb8c69"
},
{
"ImportPath": "github.com/mitchellh/go-fs/fat",
"Rev": "a34c1b9334e86165685a9449b782f20465eb8c69"
},
{
"ImportPath": "github.com/mitchellh/go-homedir",
"Rev": "d682a8f0cf139663a984ff12528da460ca963de9"
},
{
"ImportPath": "github.com/mitchellh/go-vnc",
"Rev": "723ed9867aed0f3209a81151e52ddc61681f0b01"
},
{
"ImportPath": "github.com/mitchellh/iochan",
"Rev": "87b45ffd0e9581375c491fef3d32130bb15c5bd7"
},
{
"ImportPath": "github.com/mitchellh/mapstructure",
"Rev": "281073eb9eb092240d33ef253c404f1cca550309"
},
{
"ImportPath": "github.com/mitchellh/multistep",
"Rev": "162146fc57112954184d90266f4733e900ed05a5"
},
{
"ImportPath": "github.com/mitchellh/panicwrap",
"Rev": "a1e50bc201f387747a45ffff020f1af2d8759e88"
},
{
"ImportPath": "github.com/mitchellh/prefixedio",
"Rev": "6e6954073784f7ee67b28f2d22749d6479151ed7"
},
{
"ImportPath": "github.com/mitchellh/reflectwalk",
"Rev": "eecf4c70c626c7cfbb95c90195bc34d386c74ac6"
},
{
"ImportPath": "github.com/nu7hatch/gouuid",
"Rev": "179d4d0c4d8d407a32af483c2354df1d2c91e6c3"
},
{
"ImportPath": "github.com/packer-community/winrmcp/winrmcp",
"Rev": "f1bcf36a69fa2945e65dd099eee11b560fbd3346"
},
{
"ImportPath": "github.com/pierrec/lz4",
"Rev": "383c0d87b5dd7c090d3cddefe6ff0c2ffbb88470"
},
{
"ImportPath": "github.com/pierrec/xxHash/xxHash32",
"Rev": "5a004441f897722c627870a981d02b29924215fa"
},
{
"ImportPath": "github.com/pkg/sftp",
"Rev": "e84cc8c755ca39b7b64f510fe1fffc1b51f210a5"
},
{
"ImportPath": "github.com/rackspace/gophercloud",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/common/extensions",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/flavors",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/images",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/servers",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tenants",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tokens",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v3/tokens",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/utils",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/pagination",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/testhelper",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/testhelper/client",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/satori/go.uuid",
"Rev": "d41af8bb6a7704f00bc3b7cba9355ae6a5a80048"
},
{
"ImportPath": "github.com/tent/http-link-go",
"Rev": "ac974c61c2f990f4115b119354b5e0b47550e888"
},
{
"ImportPath": "github.com/ugorji/go/codec",
"Rev": "646ae4a518c1c3be0739df898118d9bccf993858"
},
{
"ImportPath": "golang.org/x/crypto/curve25519",
"Rev": "1f22c0103821b9390939b6776727195525381532"
},
{
"ImportPath": "golang.org/x/crypto/md4",
"Rev": "1f22c0103821b9390939b6776727195525381532"
},
{
"ImportPath": "golang.org/x/crypto/ssh",
"Rev": "1f22c0103821b9390939b6776727195525381532"
},
{
"ImportPath": "golang.org/x/crypto/ssh/agent",
"Rev": "1f22c0103821b9390939b6776727195525381532"
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
},
{
"ImportPath": "golang.org/x/net/context/ctxhttp",
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
},
{
"ImportPath": "golang.org/x/net/html",
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
},
{
"ImportPath": "golang.org/x/net/html/atom",
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
},
{
"ImportPath": "golang.org/x/oauth2",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/oauth2/google",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/oauth2/internal",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/oauth2/jws",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/oauth2/jwt",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/sys/unix",
"Rev": "50c6bc5e4292a1d4e65c6e9be5f53be28bcbe28e"
},
{
"ImportPath": "google.golang.org/api/compute/v1",
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
},
{
"ImportPath": "google.golang.org/api/gensupport",
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
},
{
"ImportPath": "google.golang.org/api/googleapi",
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
},
{
"ImportPath": "google.golang.org/api/googleapi/internal/uritemplates",
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
},
{
"ImportPath": "google.golang.org/appengine",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/app_identity",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/base",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/datastore",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/log",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/modules",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/remote_api",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/cloud/compute/metadata",
"Rev": "5a3b06f8b5da3b7c3a93da43163b872c86c509ef"
},
{
"ImportPath": "google.golang.org/cloud/internal",
"Rev": "5a3b06f8b5da3b7c3a93da43163b872c86c509ef"
},
{
"ImportPath": "gopkg.in/fsnotify.v1",
"Comment": "v1.2.9",
"Rev": "8611c35ab31c1c28aa903d33cf8b6e44a399b09e"
},
{
"ImportPath": "gopkg.in/tomb.v1",
"Rev": "dd632973f1e7218eb1089048e0798ec9ae7dceb8"
},
{
"ImportPath": "gopkg.in/xmlpath.v2",
"Rev": "860cbeca3ebcc600db0b213c0e83ad6ce91f5739"
}
]
}
Generated
+5
View File
@@ -0,0 +1,5 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.
+46 -93
View File
@@ -1,87 +1,49 @@
TEST?=$(shell go list ./...)
VET?=$(shell go list ./...)
TEST?=$(shell go list ./... | grep -v vendor)
VET?=$(shell ls -d */ | grep -v vendor | grep -v website)
# Get the current full sha from git
GITSHA:=$(shell git rev-parse HEAD)
# Get the current local branch name from git (if we can, this may be blank)
GITBRANCH:=$(shell git symbolic-ref --short HEAD 2>/dev/null)
GOOS=$(shell go env GOOS)
GOARCH=$(shell go env GOARCH)
GOPATH=$(shell go env GOPATH)
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|inspec)/test-fixtures/exit1')
default: deps generate test dev
# Get the git commit
GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)
GIT_COMMIT=$(shell git rev-parse --short HEAD)
GIT_IMPORT=github.com/hashicorp/packer/version
GOLDFLAGS=-X $(GIT_IMPORT).GitCommit=$(GIT_COMMIT)$(GIT_DIRTY)
ci: deps test
export GOLDFLAGS
release: deps test releasebin package
.PHONY: bin checkversion ci default install-build-deps install-gen-deps fmt fmt-docs fmt-examples generate releasebin test testacc testrace
default: install-build-deps install-gen-deps generate testrace dev releasebin package dev fmt fmt-check mode-check fmt-docs fmt-examples
ci: testrace ## Test in continuous integration
release: install-build-deps test releasebin package ## Build a release build
bin: install-build-deps ## Build debug/test build
bin: deps
@echo "WARN: 'make bin' is for debug / test builds only. Use 'make release' for release builds."
@GO111MODULE=auto sh -c "$(CURDIR)/scripts/build.sh"
@GO15VENDOREXPERIMENT=1 sh -c "$(CURDIR)/scripts/build.sh"
releasebin: install-build-deps
releasebin: deps
@grep 'const VersionPrerelease = "dev"' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \
echo "ERROR: You must remove prerelease tags from version/version.go prior to release."; \
exit 1; \
fi
@GO111MODULE=auto sh -c "$(CURDIR)/scripts/build.sh"
@GO15VENDOREXPERIMENT=1 sh -c "$(CURDIR)/scripts/build.sh"
package:
$(if $(VERSION),,@echo 'VERSION= needed to release; Use make package skip compilation'; exit 1)
@sh -c "$(CURDIR)/scripts/dist.sh $(VERSION)"
install-build-deps: ## Install dependencies for bin build
@go get github.com/mitchellh/gox
deps:
go get github.com/mitchellh/gox
go get golang.org/x/tools/cmd/stringer
@go version | grep 1.4 ; if [ $$? -eq 0 ]; then \
echo "Installing godep and restoring dependencies"; \
go get github.com/tools/godep; \
godep restore; \
fi
install-gen-deps: ## Install dependencies for code generation
@go get golang.org/x/tools/cmd/goimports
@go get -u github.com/mna/pigeon
@go get github.com/alvaroloes/enumer
dev: ## Build and install a development build
dev: deps
@grep 'const VersionPrerelease = ""' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \
echo "ERROR: You must add prerelease tags to version/version.go prior to making a dev build."; \
exit 1; \
fi
@mkdir -p pkg/$(GOOS)_$(GOARCH)
@mkdir -p bin
@go install -ldflags '$(GOLDFLAGS)'
@cp $(GOPATH)/bin/packer bin/packer
@cp $(GOPATH)/bin/packer pkg/$(GOOS)_$(GOARCH)
@PACKER_DEV=1 GO15VENDOREXPERIMENT=1 sh -c "$(CURDIR)/scripts/build.sh"
fmt: ## Format Go code
@go fmt ./...
fmt-check: fmt ## Check go code formatting
@echo "==> Checking that code complies with go fmt requirements..."
@git diff --exit-code; if [ $$? -eq 1 ]; then \
echo "Found files that are not fmt'ed."; \
echo "You can use the command: \`make fmt\` to reformat code."; \
exit 1; \
fi
mode-check: ## Check that only certain files are executable
@echo "==> Checking that only certain files are executable..."
@if [ ! -z "$(EXECUTABLE_FILES)" ]; then \
echo "These files should not be executable or they must be white listed in the Makefile:"; \
echo "$(EXECUTABLE_FILES)" | xargs -n1; \
exit 1; \
else \
echo "Check passed."; \
fi
fmt-docs:
@find ./website/source/docs -name "*.md" -exec pandoc --wrap auto --columns 79 --atx-headers -s -f "markdown_github+yaml_metadata_block" -t "markdown_github+yaml_metadata_block" {} -o {} \;
fmt:
go fmt `go list ./... | grep -v vendor`
# Install js-beautify with npm install -g js-beautify
fmt-examples:
@@ -89,43 +51,34 @@ fmt-examples:
# generate runs `go generate` to build the dynamically generated
# source files.
generate: install-gen-deps ## Generate dynamically generated code
go generate ./...
go fmt common/bootcommand/boot_command.go
goimports -w common/bootcommand/boot_command.go
generate: deps
go generate .
go fmt command/plugin.go
generate-check: generate ## Check go code generation is on par
@echo "==> Checking that auto-generated code is not changed..."
@git diff --exit-code; if [ $$? -eq 1 ]; then \
echo "Found diffs in go generated code."; \
echo "You can use the command: \`make generate\` to reformat code."; \
exit 1; \
fi
test: mode-check vet ## Run unit tests
@go test $(TEST) $(TESTARGS) -timeout=3m
# testacc runs acceptance tests
testacc: install-build-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: mode-check vet ## Test with race detection enabled
@GO111MODULE=off go test -race $(TEST) $(TESTARGS) -timeout=3m -p=8
check-vendor-vs-mod: ## Check that go modules and vendored code are on par
@GO111MODULE=on go mod vendor
@git diff --exit-code --ignore-space-change --ignore-space-at-eol -- vendor ; if [ $$? -eq 1 ]; then \
echo "ERROR: vendor dir is not on par with go modules definition." && \
exit 1; \
fi
vet: ## Vet Go code
@go vet $(VET) ; if [ $$? -eq 1 ]; then \
test: deps
@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
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
# testacc runs acceptance tests
testacc: deps generate
@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: deps
@go test -race $(TEST) $(TESTARGS) -timeout=2m
updatedeps:
go get -u github.com/mitchellh/gox
go get -u golang.org/x/tools/cmd/stringer
@echo "INFO: Packer deps are managed by godep. See CONTRIBUTING.md"
# This is used to add new dependencies to packer. If you are submitting a PR
# that includes new dependencies you will need to run this.
vendor:
godep restore
godep save
.PHONY: bin checkversion ci default deps fmt fmt-examples generate releasebin test testacc testrace updatedeps
+23 -30
View File
@@ -1,30 +1,28 @@
# Packer
[![Build Status][travis-badge]][travis]
[![Windows Build Status][appveyor-badge]][appveyor]
[![GoDoc][godoc-badge]][godoc]
[![GoReportCard][report-badge]][report]
[![Build Status](https://travis-ci.org/mitchellh/packer.svg?branch=master)](https://travis-ci.org/mitchellh/packer)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/mitchellh/packer?branch=master&svg=true)](https://ci.appveyor.com/project/hashicorp/packer)
[travis-badge]: https://travis-ci.org/hashicorp/packer.svg?branch=master
[travis]: https://travis-ci.org/hashicorp/packer
[appveyor-badge]: https://ci.appveyor.com/api/projects/status/miavlgnp989e5obc/branch/master?svg=true
[appveyor]: https://ci.appveyor.com/project/hashicorp/packer
[godoc-badge]: https://godoc.org/github.com/hashicorp/packer?status.svg
[godoc]: https://godoc.org/github.com/hashicorp/packer
[report-badge]: https://goreportcard.com/badge/github.com/hashicorp/packer
[report]: https://goreportcard.com/report/github.com/hashicorp/packer
* Website: https://www.packer.io
* Website: http://www.packer.io
* IRC: `#packer-tool` on Freenode
* Mailing list: [Google Groups](https://groups.google.com/forum/#!forum/packer-tool)
* Mailing list: [Google Groups](http://groups.google.com/group/packer-tool)
Packer is a tool for building identical machine images for multiple platforms
from a single source configuration.
Packer is lightweight, runs on every major operating system, and is highly
performant, creating machine images for multiple platforms in parallel. Packer
comes out of the box with support for many platforms, the full list of which can
be found at https://www.packer.io/docs/builders/index.html.
comes out of the box with support for the following platforms:
* Amazon EC2 (AMI). Both EBS-backed and instance-store AMIs
* DigitalOcean
* Docker
* Google Compute Engine
* OpenStack
* Parallels
* QEMU. Both KVM and Xen images.
* VirtualBox
* VMware
Support for other platforms can be added via plugins.
@@ -34,15 +32,13 @@ The images that Packer creates can easily be turned into
## Quick Start
**Note:** There is a great
[introduction and getting started guide](https://www.packer.io/intro)
[introduction and getting started guide](http://www.packer.io/intro)
for those with a bit more patience. Otherwise, the quick start below
will get you up and running quickly, at the sacrifice of not explaining some
key points.
First, [download a pre-built Packer
binary](https://www.packer.io/downloads.html) for your operating system or
[compile Packer
yourself](https://github.com/hashicorp/packer/blob/master/.github/CONTRIBUTING.md#setting-up-go-to-work-on-packer).
First, [download a pre-built Packer binary](http://www.packer.io/downloads.html)
for your operating system or [compile Packer yourself](#developing-packer).
After Packer is installed, create your first template, which tells Packer
what platforms to build images for and how you want to build them. In our
@@ -61,8 +57,8 @@ file as `quick-start.json`. Export your AWS credentials as the
"access_key": "{{user `access_key`}}",
"secret_key": "{{user `secret_key`}}",
"region": "us-east-1",
"source_ami": "ami-af22d9b9",
"instance_type": "t2.micro",
"source_ami": "ami-de0d9eb7",
"instance_type": "t1.micro",
"ssh_username": "ubuntu",
"ami_name": "packer-example {{timestamp}}"
}]
@@ -80,17 +76,14 @@ 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
Comprehensive documentation is viewable on the Packer website:
https://www.packer.io/docs
http://www.packer.io/docs
## Developing Packer
See
[CONTRIBUTING.md](https://github.com/hashicorp/packer/blob/master/.github/CONTRIBUTING.md)
for best practices and instructions on setting up your development environment
to work on Packer.
See [CONTRIBUTING.md](https://github.com/mitchellh/packer/blob/master/CONTRIBUTING.md) for best practices and instructions on setting up your development environment to work on Packer.
Vendored
+41 -85
View File
@@ -1,94 +1,50 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
LINUX_BASE_BOX = "bento/ubuntu-16.04"
FREEBSD_BASE_BOX = "jen20/FreeBSD-12.0-CURRENT"
$script = <<SCRIPT
# Fetch from https://golang.org/dl
TARBALL="https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz"
UNTARPATH="/opt"
GOROOT="${UNTARPATH}/go"
GOPATH="${UNTARPATH}/gopath"
# Install Go
if [ ! -d ${GOROOT} ]; then
sudo wget --progress=bar:force --output-document - ${TARBALL} |\
tar xfz - -C ${UNTARPATH}
fi
# Setup the GOPATH
sudo mkdir -p ${GOPATH}
cat <<EOF >/tmp/gopath.sh
export GOROOT="${GOROOT}"
export GOPATH="${GOPATH}"
export PATH="${GOROOT}/bin:${GOPATH}/bin:\$PATH"
EOF
sudo mv /tmp/gopath.sh /etc/profile.d/gopath.sh
# Make sure the GOPATH is usable by vagrant
sudo chown -R vagrant:vagrant ${GOROOT}
sudo chown -R vagrant:vagrant ${GOPATH}
# Install some other stuff we need
sudo apt-get update
sudo apt-get install -y curl make git mercurial bzr zip
SCRIPT
Vagrant.configure(2) do |config|
if Vagrant.has_plugin?("vagrant-cachier")
config.cache.scope = :box
end
config.vm.box = "bento/ubuntu-14.04"
# Compilation and development boxes
config.vm.define "linux", autostart: true, primary: true do |vmCfg|
vmCfg.vm.box = LINUX_BASE_BOX
vmCfg.vm.hostname = "linux"
vmCfg = configureProviders vmCfg,
cpus: suggestedCPUCores()
config.vm.provision "shell", inline: $script
vmCfg.vm.synced_folder ".", "/vagrant", disabled: true
vmCfg.vm.synced_folder '.',
'/opt/gopath/src/github.com/hashicorp/packer'
config.vm.synced_folder ".", "/vagrant", disabled: true
vmCfg.vm.provision "shell",
privileged: true,
inline: 'rm -f /home/vagrant/linux.iso'
vmCfg.vm.provision "shell",
privileged: true,
path: './scripts/vagrant-linux-priv-go.sh'
vmCfg.vm.provision "shell",
privileged: true,
path: './scripts/vagrant-linux-priv-config.sh'
vmCfg.vm.provision "shell",
privileged: false,
path: './scripts/vagrant-linux-unpriv-bootstrap.sh'
end
config.vm.define "freebsd", autostart: false, primary: false do |vmCfg|
vmCfg.vm.box = FREEBSD_BASE_BOX
vmCfg.vm.hostname = "freebsd"
vmCfg = configureProviders vmCfg,
cpus: suggestedCPUCores()
vmCfg.vm.synced_folder ".", "/vagrant", disabled: true
vmCfg.vm.synced_folder '.',
'/opt/gopath/src/github.com/hashicorp/packer',
type: "nfs",
bsd__nfs_options: ['noatime']
vmCfg.vm.provision "shell",
privileged: true,
path: './scripts/vagrant-freebsd-priv-config.sh'
vmCfg.vm.provision "shell",
privileged: false,
path: './scripts/vagrant-freebsd-unpriv-bootstrap.sh'
end
end
def configureProviders(vmCfg, cpus: "2", memory: "2048")
vmCfg.vm.provider "virtualbox" do |v|
v.memory = memory
v.cpus = cpus
end
["vmware_fusion", "vmware_workstation"].each do |p|
vmCfg.vm.provider p do |v|
v.enable_vmrun_ip_lookup = false
v.vmx["memsize"] = memory
v.vmx["numvcpus"] = cpus
end
end
vmCfg.vm.provider "docker" do |d, override|
d.build_dir = "."
d.has_ssh = true
override.vm.box = nil
end
return vmCfg
end
def suggestedCPUCores()
case RbConfig::CONFIG['host_os']
when /darwin/
Integer(`sysctl -n hw.ncpu`) / 2
when /linux/
Integer(`cat /proc/cpuinfo | grep processor | wc -l`) / 2
else
2
end
["vmware_fusion", "vmware_workstation"].each do |p|
config.vm.provider "p" do |v|
v.vmx["memsize"] = "2048"
v.vmx["numvcpus"] = "2"
v.vmx["cpuid.coresPerSocket"] = "1"
end
end
end
+6 -5
View File
@@ -13,10 +13,10 @@ os: Windows Server 2012 R2
environment:
GOPATH: c:\gopath
clone_folder: c:\gopath\src\github.com\hashicorp\packer
clone_folder: c:\gopath\src\github.com\mitchellh\packer
install:
- set GO111MODULE=off
- set GO15VENDOREXPERIMENT=1
- echo %Path%
- go version
- go env
@@ -27,10 +27,11 @@ 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/provisioner/ansible' })
|? { $_ -ne 'github.com/mitchellh/packer/builder/parallels/common' } `
|? { $_ -ne 'github.com/mitchellh/packer/common' }`
|? { $_ -ne 'github.com/mitchellh/packer/provisioner/ansible' })
test: off
Executable
+18
View File
@@ -0,0 +1,18 @@
PACKER=$GOPATH/src/github.com/mitchellh/packer
AZURE=/tmp/packer-azure
ls $AZURE >/dev/null || git clone https://github.com/Azure/packer-azure /tmp/packer-azure
PWD=`pwd`
cd $AZURE && git pull
cd $PWD
# copy things
cp -r $AZURE/packer/builder/azure $PACKER/builder/
cp -r $AZURE/packer/communicator/* $PACKER/communicator/
cp -r $AZURE/packer/provisioner/azureVmCustomScriptExtension $PACKER/provisioner/
# remove legacy API client
rm -rf $PACKER/builder/azure/smapi
# fix imports
find $PACKER/builder/azure/ -type f | grep ".go" | xargs sed -e 's/Azure\/packer-azure\/packer\/builder\/azure/mitchellh\/packer\/builder\/azure/g' -i ''
-118
View File
@@ -1,118 +0,0 @@
package ecs
import (
"fmt"
"os"
"time"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/template/interpolate"
"github.com/hashicorp/packer/version"
)
// Config of alicloud
type AlicloudAccessConfig struct {
AlicloudAccessKey string `mapstructure:"access_key"`
AlicloudSecretKey string `mapstructure:"secret_key"`
AlicloudRegion string `mapstructure:"region"`
AlicloudSkipValidation bool `mapstructure:"skip_region_validation"`
SecurityToken string `mapstructure:"security_token"`
client *ClientWrapper
}
const Packer = "HashiCorp-Packer"
const DefaultRequestReadTimeout = 10 * time.Second
// Client for AlicloudClient
func (c *AlicloudAccessConfig) Client() (*ClientWrapper, error) {
if c.client != nil {
return c.client, nil
}
if c.SecurityToken == "" {
c.SecurityToken = os.Getenv("SECURITY_TOKEN")
}
client, err := ecs.NewClientWithStsToken(c.AlicloudRegion, c.AlicloudAccessKey,
c.AlicloudSecretKey, c.SecurityToken)
if err != nil {
return nil, err
}
client.AppendUserAgent(Packer, version.FormattedVersion())
client.SetReadTimeout(DefaultRequestReadTimeout)
c.client = &ClientWrapper{client}
return c.client, nil
}
func (c *AlicloudAccessConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if err := c.Config(); err != nil {
errs = append(errs, err)
}
if c.AlicloudRegion == "" {
c.AlicloudRegion = os.Getenv("ALICLOUD_REGION")
}
if c.AlicloudRegion == "" {
errs = append(errs, fmt.Errorf("region option or ALICLOUD_REGION must be provided in template file or environment variables."))
}
if len(errs) > 0 {
return errs
}
return nil
}
func (c *AlicloudAccessConfig) Config() error {
if c.AlicloudAccessKey == "" {
c.AlicloudAccessKey = os.Getenv("ALICLOUD_ACCESS_KEY")
}
if c.AlicloudSecretKey == "" {
c.AlicloudSecretKey = os.Getenv("ALICLOUD_SECRET_KEY")
}
if c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "" {
return fmt.Errorf("ALICLOUD_ACCESS_KEY and ALICLOUD_SECRET_KEY must be set in template file or environment variables.")
}
return nil
}
func (c *AlicloudAccessConfig) ValidateRegion(region string) error {
supportedRegions, err := c.getSupportedRegions()
if err != nil {
return err
}
for _, supportedRegion := range supportedRegions {
if region == supportedRegion {
return nil
}
}
return fmt.Errorf("Not a valid alicloud region: %s", region)
}
func (c *AlicloudAccessConfig) getSupportedRegions() ([]string, error) {
client, err := c.Client()
if err != nil {
return nil, err
}
regionsRequest := ecs.CreateDescribeRegionsRequest()
regionsResponse, err := client.DescribeRegions(regionsRequest)
if err != nil {
return nil, err
}
validRegions := make([]string, len(regionsResponse.Regions.Region))
for _, valid := range regionsResponse.Regions.Region {
validRegions = append(validRegions, valid.RegionId)
}
return validRegions, nil
}
@@ -1,36 +0,0 @@
package ecs
import (
"os"
"testing"
)
func testAlicloudAccessConfig() *AlicloudAccessConfig {
return &AlicloudAccessConfig{
AlicloudAccessKey: "ak",
AlicloudSecretKey: "acs",
}
}
func TestAlicloudAccessConfigPrepareRegion(t *testing.T) {
c := testAlicloudAccessConfig()
c.AlicloudRegion = ""
if err := c.Prepare(nil); err == nil {
t.Fatalf("should have err")
}
c.AlicloudRegion = "cn-beijing"
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
os.Setenv("ALICLOUD_REGION", "cn-hangzhou")
c.AlicloudRegion = ""
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AlicloudSkipValidation = false
}
-186
View File
@@ -1,186 +0,0 @@
package ecs
import (
"fmt"
"log"
"sort"
"strings"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/packer"
)
type Artifact struct {
// A map of regions to alicloud image IDs.
AlicloudImages map[string]string
// BuilderId is the unique ID for the builder that created this alicloud image
BuilderIdValue string
// Alcloud connection for performing API stuff.
Client *ClientWrapper
}
func (a *Artifact) BuilderId() string {
return a.BuilderIdValue
}
func (*Artifact) Files() []string {
// We have no files
return nil
}
func (a *Artifact) Id() string {
parts := make([]string, 0, len(a.AlicloudImages))
for region, ecsImageId := range a.AlicloudImages {
parts = append(parts, fmt.Sprintf("%s:%s", region, ecsImageId))
}
sort.Strings(parts)
return strings.Join(parts, ",")
}
func (a *Artifact) String() string {
alicloudImageStrings := make([]string, 0, len(a.AlicloudImages))
for region, id := range a.AlicloudImages {
single := fmt.Sprintf("%s: %s", region, id)
alicloudImageStrings = append(alicloudImageStrings, single)
}
sort.Strings(alicloudImageStrings)
return fmt.Sprintf("Alicloud images were created:\n\n%s", strings.Join(alicloudImageStrings, "\n"))
}
func (a *Artifact) State(name string) interface{} {
switch name {
case "atlas.artifact.metadata":
return a.stateAtlasMetadata()
default:
return nil
}
}
func (a *Artifact) Destroy() error {
errors := make([]error, 0)
copyingImages := make(map[string]string, len(a.AlicloudImages))
sourceImage := make(map[string]*ecs.Image, 1)
for regionId, imageId := range a.AlicloudImages {
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = regionId
describeImagesRequest.ImageId = imageId
describeImagesRequest.Status = ImageStatusQueried
imagesResponse, err := a.Client.DescribeImages(describeImagesRequest)
if err != nil {
errors = append(errors, err)
}
images := imagesResponse.Images.Image
if len(images) == 0 {
err := fmt.Errorf("Error retrieving details for alicloud image(%s), no alicloud images found", imageId)
errors = append(errors, err)
continue
}
if images[0].IsCopied && images[0].Status != ImageStatusAvailable {
copyingImages[regionId] = imageId
} else {
sourceImage[regionId] = &images[0]
}
}
for regionId, imageId := range copyingImages {
log.Printf("Cancel copying alicloud image (%s) from region (%s)", imageId, regionId)
errs := a.unsharedAccountsOnImages(regionId, imageId)
if errs != nil {
errors = append(errors, errs...)
}
cancelImageCopyRequest := ecs.CreateCancelCopyImageRequest()
cancelImageCopyRequest.RegionId = regionId
cancelImageCopyRequest.ImageId = imageId
if _, err := a.Client.CancelCopyImage(cancelImageCopyRequest); err != nil {
errors = append(errors, err)
}
}
for regionId, image := range sourceImage {
imageId := image.ImageId
log.Printf("Delete alicloud image (%s) from region (%s)", imageId, regionId)
errs := a.unsharedAccountsOnImages(regionId, imageId)
if errs != nil {
errors = append(errors, errs...)
}
deleteImageRequest := ecs.CreateDeleteImageRequest()
deleteImageRequest.RegionId = regionId
deleteImageRequest.ImageId = imageId
if _, err := a.Client.DeleteImage(deleteImageRequest); err != nil {
errors = append(errors, err)
}
//Delete the snapshot of this images
for _, diskDevices := range image.DiskDeviceMappings.DiskDeviceMapping {
deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest()
deleteSnapshotRequest.SnapshotId = diskDevices.SnapshotId
_, err := a.Client.DeleteSnapshot(deleteSnapshotRequest)
if err != nil {
errors = append(errors, err)
}
}
}
if len(errors) > 0 {
if len(errors) == 1 {
return errors[0]
} else {
return &packer.MultiError{Errors: errors}
}
}
return nil
}
func (a *Artifact) unsharedAccountsOnImages(regionId string, imageId string) []error {
var errors []error
describeImageShareRequest := ecs.CreateDescribeImageSharePermissionRequest()
describeImageShareRequest.RegionId = regionId
describeImageShareRequest.ImageId = imageId
imageShareResponse, err := a.Client.DescribeImageSharePermission(describeImageShareRequest)
if err != nil {
errors = append(errors, err)
return errors
}
accountsNumber := len(imageShareResponse.Accounts.Account)
if accountsNumber > 0 {
accounts := make([]string, accountsNumber)
for index, account := range imageShareResponse.Accounts.Account {
accounts[index] = account.AliyunId
}
modifyImageShareRequest := ecs.CreateModifyImageSharePermissionRequest()
modifyImageShareRequest.RegionId = regionId
modifyImageShareRequest.ImageId = imageId
modifyImageShareRequest.RemoveAccount = &accounts
_, err := a.Client.ModifyImageSharePermission(modifyImageShareRequest)
if err != nil {
errors = append(errors, err)
}
}
return errors
}
func (a *Artifact) stateAtlasMetadata() interface{} {
metadata := make(map[string]string)
for region, imageId := range a.AlicloudImages {
k := fmt.Sprintf("region.%s", region)
metadata[k] = imageId
}
return metadata
}
-47
View File
@@ -1,47 +0,0 @@
package ecs
import (
"reflect"
"testing"
"github.com/hashicorp/packer/packer"
)
func TestArtifact_Impl(t *testing.T) {
var _ packer.Artifact = new(Artifact)
}
func TestArtifactId(t *testing.T) {
expected := `east:foo,west:bar`
ecsImages := make(map[string]string)
ecsImages["east"] = "foo"
ecsImages["west"] = "bar"
a := &Artifact{
AlicloudImages: ecsImages,
}
result := a.Id()
if result != expected {
t.Fatalf("bad: %s", result)
}
}
func TestArtifactState_atlasMetadata(t *testing.T) {
a := &Artifact{
AlicloudImages: map[string]string{
"east": "foo",
"west": "bar",
},
}
actual := a.State("atlas.artifact.metadata")
expected := map[string]string{
"region.east": "foo",
"region.west": "bar",
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
-262
View File
@@ -1,262 +0,0 @@
// The alicloud contains a packer.Builder implementation that
// builds ecs images for alicloud.
package ecs
import (
"context"
"fmt"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
// The unique ID for this builder
const BuilderId = "alibaba.alicloud"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
AlicloudAccessConfig `mapstructure:",squash"`
AlicloudImageConfig `mapstructure:",squash"`
RunConfig `mapstructure:",squash"`
ctx interpolate.Context
}
type Builder struct {
config Config
runner multistep.Runner
}
type InstanceNetWork string
const (
ALICLOUD_DEFAULT_SHORT_TIMEOUT = 180
ALICLOUD_DEFAULT_TIMEOUT = 1800
ALICLOUD_DEFAULT_LONG_TIMEOUT = 3600
)
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"run_command",
},
},
}, raws...)
b.config.ctx.EnableEnv = true
if err != nil {
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)...)
errs = packer.MultiErrorAppend(errs, b.config.AlicloudImageConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
}
packer.LogSecretFilter.Set(b.config.AlicloudAccessKey, b.config.AlicloudSecretKey)
return nil, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
client, err := b.config.Client()
if err != nil {
return nil, err
}
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("client", client)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("networktype", b.chooseNetworkType())
var steps []multistep.Step
// Build the steps
steps = []multistep.Step{
&stepPreValidate{
AlicloudDestImageName: b.config.AlicloudImageName,
ForceDelete: b.config.AlicloudImageForceDelete,
},
&stepCheckAlicloudSourceImage{
SourceECSImageId: b.config.AlicloudSourceImage,
},
&stepConfigAlicloudKeyPair{
Debug: b.config.PackerDebug,
Comm: &b.config.Comm,
DebugKeyPath: fmt.Sprintf("ecs_%s.pem", b.config.PackerBuildName),
RegionId: b.config.AlicloudRegion,
},
}
if b.chooseNetworkType() == InstanceNetworkVpc {
steps = append(steps,
&stepConfigAlicloudVPC{
VpcId: b.config.VpcId,
CidrBlock: b.config.CidrBlock,
VpcName: b.config.VpcName,
},
&stepConfigAlicloudVSwitch{
VSwitchId: b.config.VSwitchId,
ZoneId: b.config.ZoneId,
CidrBlock: b.config.CidrBlock,
VSwitchName: b.config.VSwitchName,
})
}
steps = append(steps,
&stepConfigAlicloudSecurityGroup{
SecurityGroupId: b.config.SecurityGroupId,
SecurityGroupName: b.config.SecurityGroupId,
RegionId: b.config.AlicloudRegion,
},
&stepCreateAlicloudInstance{
IOOptimized: b.config.IOOptimized,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
RegionId: b.config.AlicloudRegion,
InternetChargeType: b.config.InternetChargeType,
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
InstanceName: b.config.InstanceName,
ZoneId: b.config.ZoneId,
})
if b.chooseNetworkType() == InstanceNetworkVpc {
steps = append(steps, &stepConfigAlicloudEIP{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
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,
})
}
steps = append(steps,
&stepAttachKeyPair{},
&stepRunAlicloudInstance{},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: SSHHost(
client,
b.config.SSHPrivateIp),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.RunConfig.Comm,
},
&stepStopAlicloudInstance{
ForceStop: b.config.ForceStopInstance,
DisableStop: b.config.DisableStopInstance,
},
&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,
},
&stepRegionCopyAlicloudImage{
AlicloudImageDestinationRegions: b.config.AlicloudImageDestinationRegions,
AlicloudImageDestinationNames: b.config.AlicloudImageDestinationNames,
RegionId: b.config.AlicloudRegion,
},
&stepShareAlicloudImage{
AlicloudImageShareAccounts: b.config.AlicloudImageShareAccounts,
AlicloudImageUNShareAccounts: b.config.AlicloudImageUNShareAccounts,
RegionId: b.config.AlicloudRegion,
})
// Run!
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If there are no ECS images, then just return
if _, ok := state.GetOk("alicloudimages"); !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &Artifact{
AlicloudImages: state.Get("alicloudimages").(map[string]string),
BuilderIdValue: BuilderId,
Client: client,
}
return artifact, nil
}
func (b *Builder) chooseNetworkType() InstanceNetWork {
if b.isVpcNetRequired() {
return InstanceNetworkVpc
} else {
return InstanceNetworkClassic
}
}
func (b *Builder) isVpcNetRequired() bool {
// UserData and KeyPair only works in VPC
return b.isVpcSpecified() || b.isUserDataNeeded() || b.isKeyPairNeeded()
}
func (b *Builder) isVpcSpecified() bool {
return b.config.VpcId != "" || b.config.VSwitchId != ""
}
func (b *Builder) isUserDataNeeded() bool {
// Public key setup requires userdata
if b.config.RunConfig.Comm.SSHPrivateKeyFile != "" {
return true
}
return b.config.UserData != "" || b.config.UserDataFile != ""
}
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
}
-892
View File
@@ -1,892 +0,0 @@
package ecs
import (
"encoding/json"
"fmt"
"os"
"strings"
"testing"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
builderT "github.com/hashicorp/packer/helper/builder/testing"
"github.com/hashicorp/packer/packer"
)
const defaultTestRegion = "cn-beijing"
func TestBuilderAcc_validateRegion(t *testing.T) {
t.Parallel()
if os.Getenv(builderT.TestEnvVar) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", builderT.TestEnvVar))
return
}
testAccPreCheck(t)
access := &AlicloudAccessConfig{AlicloudRegion: "cn-beijing"}
err := access.Config()
if err != nil {
t.Fatalf("init AlicloudAccessConfig failed: %s", err)
}
err = access.ValidateRegion("cn-hangzhou")
if err != nil {
t.Fatalf("Expect pass with valid region id but failed: %s", err)
}
err = access.ValidateRegion("invalidRegionId")
if err == nil {
t.Fatal("Expect failure due to invalid region id but passed")
}
}
func TestBuilderAcc_basic(t *testing.T) {
t.Parallel()
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccBasic,
})
}
const testBuilderAccBasic = `
{ "builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
"io_optimized":"true",
"ssh_username":"root",
"image_name": "packer-test-basic_{{timestamp}}"
}]
}`
func TestBuilderAcc_withDiskSettings(t *testing.T) {
t.Parallel()
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccWithDiskSettings,
Check: checkImageDisksSettings(),
})
}
const testBuilderAccWithDiskSettings = `
{ "builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
"io_optimized":"true",
"ssh_username":"root",
"image_name": "packer-test-withDiskSettings_{{timestamp}}",
"system_disk_mapping": {
"disk_size": 60
},
"image_disk_mappings": [
{
"disk_name": "datadisk1",
"disk_size": 25,
"disk_delete_with_instance": true
},
{
"disk_name": "datadisk2",
"disk_size": 25,
"disk_delete_with_instance": true
}
]
}]
}`
func checkImageDisksSettings() 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)
}
imageId := artifact.AlicloudImages[defaultTestRegion]
// describe the image, get block devices with a snapshot
client, _ := testAliyunClient()
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = defaultTestRegion
describeImagesRequest.ImageId = imageId
imagesResponse, err := client.DescribeImages(describeImagesRequest)
if err != nil {
return fmt.Errorf("describe images failed due to %s", err)
}
if len(imagesResponse.Images.Image) == 0 {
return fmt.Errorf("image %s generated can not be found", imageId)
}
image := imagesResponse.Images.Image[0]
if image.Size != 60 {
return fmt.Errorf("the size of image %s should be equal to 60G but got %dG", imageId, image.Size)
}
if len(image.DiskDeviceMappings.DiskDeviceMapping) != 3 {
return fmt.Errorf("image %s should contains 3 disks", imageId)
}
var snapshotIds []string
for _, mapping := range image.DiskDeviceMappings.DiskDeviceMapping {
if mapping.Type == DiskTypeSystem {
if mapping.Size != "60" {
return fmt.Errorf("the system snapshot size of image %s should be equal to 60G but got %sG", imageId, mapping.Size)
}
} else {
if mapping.Size != "25" {
return fmt.Errorf("the data disk size of image %s should be equal to 25G but got %sG", imageId, mapping.Size)
}
snapshotIds = append(snapshotIds, mapping.SnapshotId)
}
}
data, _ := json.Marshal(snapshotIds)
describeSnapshotRequest := ecs.CreateDescribeSnapshotsRequest()
describeSnapshotRequest.RegionId = defaultTestRegion
describeSnapshotRequest.SnapshotIds = string(data)
describeSnapshotsResponse, err := client.DescribeSnapshots(describeSnapshotRequest)
if err != nil {
return fmt.Errorf("describe data snapshots failed due to %s", err)
}
if len(describeSnapshotsResponse.Snapshots.Snapshot) != 2 {
return fmt.Errorf("expect %d data snapshots but got %d", len(snapshotIds), len(describeSnapshotsResponse.Snapshots.Snapshot))
}
var dataDiskIds []string
for _, snapshot := range describeSnapshotsResponse.Snapshots.Snapshot {
dataDiskIds = append(dataDiskIds, snapshot.SourceDiskId)
}
data, _ = json.Marshal(dataDiskIds)
describeDisksRequest := ecs.CreateDescribeDisksRequest()
describeDisksRequest.RegionId = defaultTestRegion
describeDisksRequest.DiskIds = string(data)
describeDisksResponse, err := client.DescribeDisks(describeDisksRequest)
if err != nil {
return fmt.Errorf("describe snapshots failed due to %s", err)
}
if len(describeDisksResponse.Disks.Disk) != 0 {
return fmt.Errorf("data disks should be deleted but %d left", len(describeDisksResponse.Disks.Disk))
}
return nil
}
}
func TestBuilderAcc_withIgnoreDataDisks(t *testing.T) {
t.Parallel()
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccIgnoreDataDisks,
Check: checkIgnoreDataDisks(),
})
}
const testBuilderAccIgnoreDataDisks = `
{ "builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.gn5-c8g1.2xlarge",
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
"io_optimized":"true",
"ssh_username":"root",
"image_name": "packer-test-ignoreDataDisks_{{timestamp}}",
"image_ignore_data_disks": true
}]
}`
func checkIgnoreDataDisks() 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)
}
imageId := artifact.AlicloudImages[defaultTestRegion]
// describe the image, get block devices with a snapshot
client, _ := testAliyunClient()
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = defaultTestRegion
describeImagesRequest.ImageId = imageId
imagesResponse, err := client.DescribeImages(describeImagesRequest)
if err != nil {
return fmt.Errorf("describe images failed due to %s", err)
}
if len(imagesResponse.Images.Image) == 0 {
return fmt.Errorf("image %s generated can not be found", imageId)
}
image := imagesResponse.Images.Image[0]
if len(image.DiskDeviceMappings.DiskDeviceMapping) != 1 {
return fmt.Errorf("image %s should only contain one disks", imageId)
}
return nil
}
}
func TestBuilderAcc_windows(t *testing.T) {
t.Parallel()
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccWindows,
})
}
const testBuilderAccWindows = `
{ "builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"winsvr_64_dtcC_1809_en-us_40G_alibase_20190318.vhd",
"io_optimized":"true",
"communicator": "winrm",
"winrm_port": 5985,
"winrm_username": "Administrator",
"winrm_password": "Test1234",
"image_name": "packer-test-windows_{{timestamp}}",
"user_data_file": "../../../examples/alicloud/basic/winrm_enable_userdata.ps1"
}]
}`
func TestBuilderAcc_regionCopy(t *testing.T) {
t.Parallel()
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccRegionCopy,
Check: checkRegionCopy([]string{"cn-hangzhou", "cn-shenzhen"}),
})
}
const testBuilderAccRegionCopy = `
{
"builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
"io_optimized":"true",
"ssh_username":"root",
"image_name": "packer-test-regionCopy_{{timestamp}}",
"image_copy_regions": ["cn-hangzhou", "cn-shenzhen"],
"image_copy_names": ["packer-copy-test-hz_{{timestamp}}", "packer-copy-test-sz_{{timestamp}}"]
}]
}
`
func checkRegionCopy(regions []string) 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)
}
// Verify that we copied to only the regions given
regionSet := make(map[string]struct{})
for _, r := range regions {
regionSet[r] = struct{}{}
}
for r := range artifact.AlicloudImages {
if r == "cn-beijing" {
delete(regionSet, r)
continue
}
if _, ok := regionSet[r]; !ok {
return fmt.Errorf("region %s is not the target region but found in artifacts", r)
}
delete(regionSet, r)
}
if len(regionSet) > 0 {
return fmt.Errorf("following region(s) should be the copying targets but corresponding artifact(s) not found: %#v", regionSet)
}
client, _ := testAliyunClient()
for regionId, imageId := range artifact.AlicloudImages {
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = regionId
describeImagesRequest.ImageId = imageId
describeImagesRequest.Status = ImageStatusQueried
describeImagesResponse, err := client.DescribeImages(describeImagesRequest)
if err != nil {
return fmt.Errorf("describe generated image %s failed due to %s", imageId, err)
}
if len(describeImagesResponse.Images.Image) == 0 {
return fmt.Errorf("image %s in artifacts can not be found", imageId)
}
image := describeImagesResponse.Images.Image[0]
if image.IsCopied && regionId == "cn-hangzhou" && !strings.HasPrefix(image.ImageName, "packer-copy-test-hz") {
return fmt.Errorf("the name of image %s in artifacts should begin with %s but got %s", imageId, "packer-copy-test-hz", image.ImageName)
}
if image.IsCopied && regionId == "cn-shenzhen" && !strings.HasPrefix(image.ImageName, "packer-copy-test-sz") {
return fmt.Errorf("the name of image %s in artifacts should begin with %s but got %s", imageId, "packer-copy-test-sz", image.ImageName)
}
}
return nil
}
}
func TestBuilderAcc_forceDelete(t *testing.T) {
t.Parallel()
// Build the same alicloud image twice, with ecs_image_force_delete on the second run
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: buildForceDeregisterConfig("false", "delete"),
SkipArtifactTeardown: true,
})
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: buildForceDeregisterConfig("true", "delete"),
})
}
func buildForceDeregisterConfig(val, name string) string {
return fmt.Sprintf(testBuilderAccForceDelete, val, name)
}
const testBuilderAccForceDelete = `
{
"builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
"io_optimized":"true",
"ssh_username":"root",
"image_force_delete": "%s",
"image_name": "packer-test-forceDelete_%s"
}]
}
`
func TestBuilderAcc_ECSImageSharing(t *testing.T) {
t.Parallel()
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccSharing,
Check: checkECSImageSharing("1309208528360047"),
})
}
// share with catsby
const testBuilderAccSharing = `
{
"builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
"io_optimized":"true",
"ssh_username":"root",
"image_name": "packer-test-ECSImageSharing_{{timestamp}}",
"image_share_account":["1309208528360047"]
}]
}
`
func checkECSImageSharing(uid string) 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()
describeImageShareRequest := ecs.CreateDescribeImageSharePermissionRequest()
describeImageShareRequest.RegionId = "cn-beijing"
describeImageShareRequest.ImageId = artifact.AlicloudImages["cn-beijing"]
imageShareResponse, err := client.DescribeImageSharePermission(describeImageShareRequest)
if err != nil {
return fmt.Errorf("Error retrieving Image Attributes for ECS Image Artifact (%#v) "+
"in ECS Image Sharing Test: %s", artifact, err)
}
if len(imageShareResponse.Accounts.Account) != 1 && imageShareResponse.Accounts.Account[0].AliyunId != uid {
return fmt.Errorf("share account is incorrect %d", len(imageShareResponse.Accounts.Account))
}
return nil
}
}
func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) {
t.Parallel()
destImageName := "delete"
// Build the same alicloud image name twice, with force_delete_snapshot on the second run
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: buildForceDeleteSnapshotConfig("false", destImageName),
SkipArtifactTeardown: true,
})
// Get image data by image image name
client, _ := testAliyunClient()
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = "cn-beijing"
describeImagesRequest.ImageName = "packer-test-" + destImageName
images, _ := client.DescribeImages(describeImagesRequest)
image := images.Images.Image[0]
// Get snapshot ids for image
snapshotIds := []string{}
for _, device := range image.DiskDeviceMappings.DiskDeviceMapping {
if device.Device != "" && device.SnapshotId != "" {
snapshotIds = append(snapshotIds, device.SnapshotId)
}
}
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: buildForceDeleteSnapshotConfig("true", destImageName),
Check: checkSnapshotsDeleted(snapshotIds),
})
}
func buildForceDeleteSnapshotConfig(val, name string) string {
return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name)
}
const testBuilderAccForceDeleteSnapshot = `
{
"builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
"io_optimized":"true",
"ssh_username":"root",
"image_force_delete_snapshots": "%s",
"image_force_delete": "%s",
"image_name": "packer-test-%s"
}]
}
`
func checkSnapshotsDeleted(snapshotIds []string) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
// Verify the snapshots are gone
client, _ := testAliyunClient()
data, err := json.Marshal(snapshotIds)
if err != nil {
return fmt.Errorf("Marshal snapshotIds array failed %v", err)
}
describeSnapshotsRequest := ecs.CreateDescribeSnapshotsRequest()
describeSnapshotsRequest.RegionId = "cn-beijing"
describeSnapshotsRequest.SnapshotIds = string(data)
snapshotResp, err := client.DescribeSnapshots(describeSnapshotsRequest)
if err != nil {
return fmt.Errorf("Query snapshot failed %v", err)
}
snapshots := snapshotResp.Snapshots.Snapshot
if len(snapshots) > 0 {
return fmt.Errorf("Snapshots weren't successfully deleted by " +
"`ecs_image_force_delete_snapshots`")
}
return nil
}
}
func TestBuilderAcc_imageTags(t *testing.T) {
t.Parallel()
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccImageTags,
Check: checkImageTags(),
})
}
const testBuilderAccImageTags = `
{ "builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
"ssh_username": "root",
"io_optimized":"true",
"image_name": "packer-test-imageTags_{{timestamp}}",
"tags": {
"TagKey1": "TagValue1",
"TagKey2": "TagValue2"
}
}]
}`
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)
}
imageId := artifact.AlicloudImages[defaultTestRegion]
// describe the image, get block devices with a snapshot
client, _ := testAliyunClient()
describeImageTagsRequest := ecs.CreateDescribeTagsRequest()
describeImageTagsRequest.RegionId = defaultTestRegion
describeImageTagsRequest.ResourceType = TagResourceImage
describeImageTagsRequest.ResourceId = imageId
imageTagsResponse, err := client.DescribeTags(describeImageTagsRequest)
if err != nil {
return fmt.Errorf("Error retrieving Image Attributes for ECS Image Artifact (%#v) "+
"in ECS Image Tags Test: %s", artifact, err)
}
if len(imageTagsResponse.Tags.Tag) != 2 {
return fmt.Errorf("expect 2 tags set on image %s but got %d", imageId, len(imageTagsResponse.Tags.Tag))
}
for _, tag := range imageTagsResponse.Tags.Tag {
if tag.TagKey != "TagKey1" && tag.TagKey != "TagKey2" {
return fmt.Errorf("tags on image %s should be within the list of TagKey1 and TagKey2 but got %s", imageId, tag.TagKey)
}
if tag.TagKey == "TagKey1" && tag.TagValue != "TagValue1" {
return fmt.Errorf("the value for tag %s on image %s should be TagValue1 but got %s", tag.TagKey, imageId, tag.TagValue)
} else if tag.TagKey == "TagKey2" && tag.TagValue != "TagValue2" {
return fmt.Errorf("the value for tag %s on image %s should be TagValue2 but got %s", tag.TagKey, imageId, tag.TagValue)
}
}
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = defaultTestRegion
describeImagesRequest.ImageId = imageId
imagesResponse, err := client.DescribeImages(describeImagesRequest)
if err != nil {
return fmt.Errorf("describe images failed due to %s", err)
}
if len(imagesResponse.Images.Image) == 0 {
return fmt.Errorf("image %s generated can not be found", imageId)
}
image := imagesResponse.Images.Image[0]
for _, mapping := range image.DiskDeviceMappings.DiskDeviceMapping {
describeSnapshotTagsRequest := ecs.CreateDescribeTagsRequest()
describeSnapshotTagsRequest.RegionId = defaultTestRegion
describeSnapshotTagsRequest.ResourceType = TagResourceSnapshot
describeSnapshotTagsRequest.ResourceId = mapping.SnapshotId
snapshotTagsResponse, err := client.DescribeTags(describeSnapshotTagsRequest)
if err != nil {
return fmt.Errorf("failed to get snapshot tags due to %s", err)
}
if len(snapshotTagsResponse.Tags.Tag) != 2 {
return fmt.Errorf("expect 2 tags set on snapshot %s but got %d", mapping.SnapshotId, len(snapshotTagsResponse.Tags.Tag))
}
for _, tag := range snapshotTagsResponse.Tags.Tag {
if tag.TagKey != "TagKey1" && tag.TagKey != "TagKey2" {
return fmt.Errorf("tags on snapshot %s should be within the list of TagKey1 and TagKey2 but got %s", mapping.SnapshotId, tag.TagKey)
}
if tag.TagKey == "TagKey1" && tag.TagValue != "TagValue1" {
return fmt.Errorf("the value for tag %s on snapshot %s should be TagValue1 but got %s", tag.TagKey, mapping.SnapshotId, tag.TagValue)
} else if tag.TagKey == "TagKey2" && tag.TagValue != "TagValue2" {
return fmt.Errorf("the value for tag %s on snapshot %s should be TagValue2 but got %s", tag.TagKey, mapping.SnapshotId, tag.TagValue)
}
}
}
return nil
}
}
func TestBuilderAcc_dataDiskEncrypted(t *testing.T) {
t.Parallel()
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccDataDiskEncrypted,
Check: checkDataDiskEncrypted(),
})
}
const testBuilderAccDataDiskEncrypted = `
{ "builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
"io_optimized":"true",
"ssh_username":"root",
"image_name": "packer-test-dataDiskEncrypted_{{timestamp}}",
"image_disk_mappings": [
{
"disk_name": "data_disk1",
"disk_size": 25,
"disk_encrypted": true,
"disk_delete_with_instance": true
},
{
"disk_name": "data_disk2",
"disk_size": 35,
"disk_encrypted": false,
"disk_delete_with_instance": true
},
{
"disk_name": "data_disk3",
"disk_size": 45,
"disk_delete_with_instance": true
}
]
}]
}`
func checkDataDiskEncrypted() 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)
}
imageId := artifact.AlicloudImages[defaultTestRegion]
// describe the image, get block devices with a snapshot
client, _ := testAliyunClient()
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = defaultTestRegion
describeImagesRequest.ImageId = imageId
imagesResponse, err := client.DescribeImages(describeImagesRequest)
if err != nil {
return fmt.Errorf("describe images failed due to %s", err)
}
if len(imagesResponse.Images.Image) == 0 {
return fmt.Errorf("image %s generated can not be found", imageId)
}
image := imagesResponse.Images.Image[0]
var snapshotIds []string
for _, mapping := range image.DiskDeviceMappings.DiskDeviceMapping {
snapshotIds = append(snapshotIds, mapping.SnapshotId)
}
data, _ := json.Marshal(snapshotIds)
describeSnapshotRequest := ecs.CreateDescribeSnapshotsRequest()
describeSnapshotRequest.RegionId = defaultTestRegion
describeSnapshotRequest.SnapshotIds = string(data)
describeSnapshotsResponse, err := client.DescribeSnapshots(describeSnapshotRequest)
if err != nil {
return fmt.Errorf("describe data snapshots failed due to %s", err)
}
if len(describeSnapshotsResponse.Snapshots.Snapshot) != 4 {
return fmt.Errorf("expect %d data snapshots but got %d", len(snapshotIds), len(describeSnapshotsResponse.Snapshots.Snapshot))
}
snapshots := describeSnapshotsResponse.Snapshots.Snapshot
for _, snapshot := range snapshots {
if snapshot.SourceDiskType == DiskTypeSystem {
if snapshot.Encrypted != false {
return fmt.Errorf("the system snapshot expected to be non-encrypted but got true")
}
continue
}
if snapshot.SourceDiskSize == "25" && snapshot.Encrypted != true {
return fmt.Errorf("the first snapshot expected to be encrypted but got false")
}
if snapshot.SourceDiskSize == "35" && snapshot.Encrypted != false {
return fmt.Errorf("the second snapshot expected to be non-encrypted but got true")
}
if snapshot.SourceDiskSize == "45" && snapshot.Encrypted != false {
return fmt.Errorf("the third snapshot expected to be non-encrypted but got true")
}
}
return nil
}
}
func TestBuilderAcc_systemDiskEncrypted(t *testing.T) {
t.Parallel()
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccSystemDiskEncrypted,
Check: checkSystemDiskEncrypted(),
})
}
const testBuilderAccSystemDiskEncrypted = `
{
"builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
"io_optimized":"true",
"ssh_username":"root",
"image_name": "packer-test_{{timestamp}}",
"image_encrypted": "true"
}]
}`
func checkSystemDiskEncrypted() 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()
imageId := artifact.AlicloudImages[defaultTestRegion]
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = defaultTestRegion
describeImagesRequest.ImageId = imageId
describeImagesRequest.Status = ImageStatusQueried
imagesResponse, err := client.DescribeImages(describeImagesRequest)
if err != nil {
return fmt.Errorf("describe images failed due to %s", err)
}
if len(imagesResponse.Images.Image) == 0 {
return fmt.Errorf("image %s generated can not be found", imageId)
}
image := imagesResponse.Images.Image[0]
if image.IsCopied == false {
return fmt.Errorf("image %s generated expexted to be copied but false", image.ImageId)
}
describeSnapshotRequest := ecs.CreateDescribeSnapshotsRequest()
describeSnapshotRequest.RegionId = defaultTestRegion
describeSnapshotRequest.SnapshotIds = fmt.Sprintf("[\"%s\"]", image.DiskDeviceMappings.DiskDeviceMapping[0].SnapshotId)
describeSnapshotsResponse, err := client.DescribeSnapshots(describeSnapshotRequest)
if err != nil {
return fmt.Errorf("describe system snapshots failed due to %s", err)
}
snapshots := describeSnapshotsResponse.Snapshots.Snapshot[0]
if snapshots.Encrypted != true {
return fmt.Errorf("system snapshot of image %s expected to be encrypted but got false", imageId)
}
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")
}
if v := os.Getenv("ALICLOUD_SECRET_KEY"); v == "" {
t.Fatal("ALICLOUD_SECRET_KEY must be set for acceptance tests")
}
}
func testAliyunClient() (*ClientWrapper, error) {
access := &AlicloudAccessConfig{AlicloudRegion: "cn-beijing"}
err := access.Config()
if err != nil {
return nil, err
}
client, err := access.Client()
if err != nil {
return nil, err
}
return client, nil
}
-234
View File
@@ -1,234 +0,0 @@
package ecs
import (
"reflect"
"testing"
"github.com/hashicorp/packer/packer"
)
func testBuilderConfig() map[string]interface{} {
return map[string]interface{}{
"access_key": "foo",
"secret_key": "bar",
"source_image": "foo",
"instance_type": "ecs.n1.tiny",
"region": "cn-beijing",
"ssh_username": "root",
"image_name": "foo",
"io_optimized": true,
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilder_Prepare_BadType(t *testing.T) {
b := &Builder{}
c := map[string]interface{}{
"access_key": []string{},
}
warnings, err := b.Prepare(c)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("prepare should fail")
}
}
func TestBuilderPrepare_ECSImageName(t *testing.T) {
var b Builder
config := testBuilderConfig()
// Test good
config["image_name"] = "ecs.n1.tiny"
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)
}
// Test bad
config["ecs_image_name"] = "foo {{"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
// Test bad
delete(config, "image_name")
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testBuilderConfig()
// Add a random key
config["i_should_not_be_valid"] = true
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
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())
}
}
-310
View File
@@ -1,310 +0,0 @@
package ecs
import (
"fmt"
"time"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
)
type ClientWrapper struct {
*ecs.Client
}
const (
InstanceStatusRunning = "Running"
InstanceStatusStarting = "Starting"
InstanceStatusStopped = "Stopped"
InstanceStatusStopping = "Stopping"
)
const (
ImageStatusWaiting = "Waiting"
ImageStatusCreating = "Creating"
ImageStatusCreateFailed = "CreateFailed"
ImageStatusAvailable = "Available"
)
var ImageStatusQueried = fmt.Sprintf("%s,%s,%s,%s", ImageStatusWaiting, ImageStatusCreating, ImageStatusCreateFailed, ImageStatusAvailable)
const (
SnapshotStatusAll = "all"
SnapshotStatusProgressing = "progressing"
SnapshotStatusAccomplished = "accomplished"
SnapshotStatusFailed = "failed"
)
const (
DiskStatusInUse = "In_use"
DiskStatusAvailable = "Available"
DiskStatusAttaching = "Attaching"
DiskStatusDetaching = "Detaching"
DiskStatusCreating = "Creating"
DiskStatusReIniting = "ReIniting"
)
const (
VpcStatusPending = "Pending"
VpcStatusAvailable = "Available"
)
const (
VSwitchStatusPending = "Pending"
VSwitchStatusAvailable = "Available"
)
const (
EipStatusAssociating = "Associating"
EipStatusUnassociating = "Unassociating"
EipStatusInUse = "InUse"
EipStatusAvailable = "Available"
)
const (
ImageOwnerSystem = "system"
ImageOwnerSelf = "self"
ImageOwnerOthers = "others"
ImageOwnerMarketplace = "marketplace"
)
const (
IOOptimizedNone = "none"
IOOptimizedOptimized = "optimized"
)
const (
InstanceNetworkClassic = "classic"
InstanceNetworkVpc = "vpc"
)
const (
DiskTypeSystem = "system"
DiskTypeData = "data"
)
const (
TagResourceImage = "image"
TagResourceInstance = "instance"
TagResourceSnapshot = "snapshot"
TagResourceDisk = "disk"
)
const (
IpProtocolAll = "all"
IpProtocolTCP = "tcp"
IpProtocolUDP = "udp"
IpProtocolICMP = "icmp"
IpProtocolGRE = "gre"
)
const (
NicTypeInternet = "internet"
NicTypeIntranet = "intranet"
)
const (
DefaultPortRange = "-1/-1"
DefaultCidrIp = "0.0.0.0/0"
DefaultCidrBlock = "172.16.0.0/24"
)
const (
defaultRetryInterval = 5 * time.Second
defaultRetryTimes = 12
shortRetryTimes = 36
mediumRetryTimes = 360
longRetryTimes = 720
)
type WaitForExpectEvalResult struct {
evalPass bool
stopRetry bool
}
var (
WaitForExpectSuccess = WaitForExpectEvalResult{
evalPass: true,
stopRetry: true,
}
WaitForExpectToRetry = WaitForExpectEvalResult{
evalPass: false,
stopRetry: false,
}
WaitForExpectFailToStop = WaitForExpectEvalResult{
evalPass: false,
stopRetry: true,
}
)
type WaitForExpectArgs struct {
RequestFunc func() (responses.AcsResponse, error)
EvalFunc func(response responses.AcsResponse, err error) WaitForExpectEvalResult
RetryInterval time.Duration
RetryTimes int
RetryTimeout time.Duration
}
func (c *ClientWrapper) WaitForExpected(args *WaitForExpectArgs) (responses.AcsResponse, error) {
if args.RetryInterval <= 0 {
args.RetryInterval = defaultRetryInterval
}
if args.RetryTimes <= 0 {
args.RetryTimes = defaultRetryTimes
}
var timeoutPoint time.Time
if args.RetryTimeout > 0 {
timeoutPoint = time.Now().Add(args.RetryTimeout)
}
var lastResponse responses.AcsResponse
var lastError error
for i := 0; ; i++ {
if args.RetryTimeout > 0 && time.Now().After(timeoutPoint) {
break
}
if args.RetryTimeout <= 0 && i >= args.RetryTimes {
break
}
response, err := args.RequestFunc()
lastResponse = response
lastError = err
evalResult := args.EvalFunc(response, err)
if evalResult.evalPass {
return response, nil
}
if evalResult.stopRetry {
return response, err
}
time.Sleep(args.RetryInterval)
}
if lastError == nil {
lastError = fmt.Errorf("<no error>")
}
if args.RetryTimeout > 0 {
return lastResponse, fmt.Errorf("evaluate failed after %d seconds timeout with %d seconds retry interval: %s", int(args.RetryTimeout.Seconds()), int(args.RetryInterval.Seconds()), lastError)
}
return lastResponse, fmt.Errorf("evaluate failed after %d times retry with %d seconds retry interval: %s", args.RetryTimes, int(args.RetryInterval.Seconds()), lastError)
}
func (c *ClientWrapper) WaitForInstanceStatus(regionId string, instanceId string, expectedStatus string) (responses.AcsResponse, error) {
return c.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDescribeInstancesRequest()
request.RegionId = regionId
request.InstanceIds = fmt.Sprintf("[\"%s\"]", instanceId)
return c.DescribeInstances(request)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
return WaitForExpectToRetry
}
instancesResponse := response.(*ecs.DescribeInstancesResponse)
instances := instancesResponse.Instances.Instance
for _, instance := range instances {
if instance.Status == expectedStatus {
return WaitForExpectSuccess
}
}
return WaitForExpectToRetry
},
RetryTimes: mediumRetryTimes,
})
}
func (c *ClientWrapper) WaitForImageStatus(regionId string, imageId string, expectedStatus string, timeout time.Duration) (responses.AcsResponse, error) {
return c.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDescribeImagesRequest()
request.RegionId = regionId
request.ImageId = imageId
request.Status = ImageStatusQueried
return c.DescribeImages(request)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
return WaitForExpectToRetry
}
imagesResponse := response.(*ecs.DescribeImagesResponse)
images := imagesResponse.Images.Image
for _, image := range images {
if image.Status == expectedStatus {
return WaitForExpectSuccess
}
}
return WaitForExpectToRetry
},
RetryTimeout: timeout,
})
}
func (c *ClientWrapper) WaitForSnapshotStatus(regionId string, snapshotId string, expectedStatus string, timeout time.Duration) (responses.AcsResponse, error) {
return c.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDescribeSnapshotsRequest()
request.RegionId = regionId
request.SnapshotIds = fmt.Sprintf("[\"%s\"]", snapshotId)
return c.DescribeSnapshots(request)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
return WaitForExpectToRetry
}
snapshotsResponse := response.(*ecs.DescribeSnapshotsResponse)
snapshots := snapshotsResponse.Snapshots.Snapshot
for _, snapshot := range snapshots {
if snapshot.Status == expectedStatus {
return WaitForExpectSuccess
}
}
return WaitForExpectToRetry
},
RetryTimeout: timeout,
})
}
type EvalErrorType bool
const (
EvalRetryErrorType = EvalErrorType(true)
EvalNotRetryErrorType = EvalErrorType(false)
)
func (c *ClientWrapper) EvalCouldRetryResponse(evalErrors []string, evalErrorType EvalErrorType) func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
return func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err == nil {
return WaitForExpectSuccess
}
e, ok := err.(errors.Error)
if !ok {
return WaitForExpectToRetry
}
if evalErrorType == EvalRetryErrorType && !ContainsInArray(evalErrors, e.ErrorCode()) {
return WaitForExpectFailToStop
}
if evalErrorType == EvalNotRetryErrorType && ContainsInArray(evalErrors, e.ErrorCode()) {
return WaitForExpectFailToStop
}
return WaitForExpectToRetry
}
}
-83
View File
@@ -1,83 +0,0 @@
package ecs
import (
"fmt"
"testing"
"time"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
)
func TestWaitForExpectedExceedRetryTimes(t *testing.T) {
c := ClientWrapper{}
iter := 0
waitDone := make(chan bool, 1)
go func() {
_, _ = c.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
iter++
return nil, fmt.Errorf("test: let iteration %d failed", iter)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
fmt.Printf("need retry: %s\n", err)
return WaitForExpectToRetry
}
return WaitForExpectSuccess
},
})
waitDone <- true
}()
timeTolerance := 1 * time.Second
select {
case <-waitDone:
if iter != defaultRetryTimes {
t.Fatalf("WaitForExpected should terminate at the %d iterations", defaultRetryTimes)
}
case <-time.After(defaultRetryTimes*defaultRetryInterval + timeTolerance):
t.Fatalf("WaitForExpected should terminate within %f seconds", (defaultRetryTimes*defaultRetryInterval + timeTolerance).Seconds())
}
}
func TestWaitForExpectedExceedRetryTimeout(t *testing.T) {
c := ClientWrapper{}
expectTimeout := 10 * time.Second
iter := 0
waitDone := make(chan bool, 1)
go func() {
_, _ = c.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
iter++
return nil, fmt.Errorf("test: let iteration %d failed", iter)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
fmt.Printf("need retry: %s\n", err)
return WaitForExpectToRetry
}
return WaitForExpectSuccess
},
RetryTimeout: expectTimeout,
})
waitDone <- true
}()
timeTolerance := 1 * time.Second
select {
case <-waitDone:
if iter > int(expectTimeout/defaultRetryInterval) {
t.Fatalf("WaitForExpected should terminate before the %d iterations", int(expectTimeout/defaultRetryInterval))
}
case <-time.After(expectTimeout + timeTolerance):
t.Fatalf("WaitForExpected should terminate within %f seconds", (expectTimeout + timeTolerance).Seconds())
}
}
-83
View File
@@ -1,83 +0,0 @@
package ecs
import (
"fmt"
"regexp"
"strings"
"github.com/hashicorp/packer/template/interpolate"
)
type AlicloudDiskDevice struct {
DiskName string `mapstructure:"disk_name"`
DiskCategory string `mapstructure:"disk_category"`
DiskSize int `mapstructure:"disk_size"`
SnapshotId string `mapstructure:"disk_snapshot_id"`
Description string `mapstructure:"disk_description"`
DeleteWithInstance bool `mapstructure:"disk_delete_with_instance"`
Device string `mapstructure:"disk_device"`
Encrypted *bool `mapstructure:"disk_encrypted"`
}
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"`
ImageEncrypted *bool `mapstructure:"image_encrypted"`
AlicloudImageForceDelete bool `mapstructure:"image_force_delete"`
AlicloudImageForceDeleteSnapshots bool `mapstructure:"image_force_delete_snapshots"`
AlicloudImageForceDeleteInstances bool `mapstructure:"image_force_delete_instances"`
AlicloudImageIgnoreDataDisks bool `mapstructure:"image_ignore_data_disks"`
AlicloudImageSkipRegionValidation bool `mapstructure:"skip_region_validation"`
AlicloudImageTags map[string]string `mapstructure:"tags"`
AlicloudDiskDevices `mapstructure:",squash"`
}
func (c *AlicloudImageConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if c.AlicloudImageName == "" {
errs = append(errs, fmt.Errorf("image_name must be specified"))
} else if len(c.AlicloudImageName) < 2 || len(c.AlicloudImageName) > 128 {
errs = append(errs, fmt.Errorf("image_name must less than 128 letters and more than 1 letters"))
} else if strings.HasPrefix(c.AlicloudImageName, "http://") ||
strings.HasPrefix(c.AlicloudImageName, "https://") {
errs = append(errs, fmt.Errorf("image_name can't start with 'http://' or 'https://'"))
}
reg := regexp.MustCompile("\\s+")
if reg.FindString(c.AlicloudImageName) != "" {
errs = append(errs, fmt.Errorf("image_name can't include spaces"))
}
if len(c.AlicloudImageDestinationRegions) > 0 {
regionSet := make(map[string]struct{})
regions := make([]string, 0, len(c.AlicloudImageDestinationRegions))
for _, region := range c.AlicloudImageDestinationRegions {
// 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{}{}
regions = append(regions, region)
}
c.AlicloudImageDestinationRegions = regions
}
if len(errs) > 0 {
return errs
}
return nil
}
-61
View File
@@ -1,61 +0,0 @@
package ecs
import (
"testing"
)
func testAlicloudImageConfig() *AlicloudImageConfig {
return &AlicloudImageConfig{
AlicloudImageName: "foo",
}
}
func TestECSImageConfigPrepare_name(t *testing.T) {
c := testAlicloudImageConfig()
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AlicloudImageName = ""
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
}
func TestAMIConfigPrepare_regions(t *testing.T) {
c := testAlicloudImageConfig()
c.AlicloudImageDestinationRegions = nil
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AlicloudImageDestinationRegions = []string{"cn-beijing", "cn-hangzhou", "eu-central-1"}
if err := c.Prepare(nil); err != nil {
t.Fatalf("bad: %s", err)
}
c.AlicloudImageDestinationRegions = nil
c.AlicloudImageSkipRegionValidation = true
if err := c.Prepare(nil); err != nil {
t.Fatal("shouldn't have error")
}
c.AlicloudImageSkipRegionValidation = false
}
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)
}
}
-52
View File
@@ -1,52 +0,0 @@
package ecs
import (
"fmt"
"strconv"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func cleanUpMessage(state multistep.StateBag, module string) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
ui := state.Get("ui").(packer.Ui)
if cancelled || halted {
ui.Say(fmt.Sprintf("Deleting %s because of cancellation or error...", module))
} else {
ui.Say(fmt.Sprintf("Cleaning up '%s'", module))
}
}
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
}
func convertNumber(value int) string {
if value <= 0 {
return ""
}
return strconv.Itoa(value)
}
func ContainsInArray(arr []string, value string) bool {
for _, item := range arr {
if item == value {
return true
}
}
return false
}
-72
View File
@@ -1,72 +0,0 @@
package ecs
import (
"errors"
"fmt"
"os"
"strings"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/template/interpolate"
)
type RunConfig struct {
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
ZoneId string `mapstructure:"zone_id"`
IOOptimized *bool `mapstructure:"io_optimized"`
InstanceType string `mapstructure:"instance_type"`
Description string `mapstructure:"description"`
AlicloudSourceImage string `mapstructure:"source_image"`
ForceStopInstance bool `mapstructure:"force_stop_instance"`
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupName string `mapstructure:"security_group_name"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
VpcId string `mapstructure:"vpc_id"`
VpcName string `mapstructure:"vpc_name"`
CidrBlock string `mapstructure:"vpc_cidr_block"`
VSwitchId string `mapstructure:"vswitch_id"`
VSwitchName string `mapstructure:"vswitch_id"`
InstanceName string `mapstructure:"instance_name"`
InternetChargeType string `mapstructure:"internet_charge_type"`
InternetMaxBandwidthOut int `mapstructure:"internet_max_bandwidth_out"`
WaitSnapshotReadyTimeout int `mapstructure:"wait_snapshot_ready_timeout"`
// Communicator settings
Comm communicator.Config `mapstructure:",squash"`
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
}
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
if c.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" &&
c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" && c.Comm.WinRMPassword == "" {
c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
}
// Validation
errs := c.Comm.Prepare(ctx)
if c.AlicloudSourceImage == "" {
errs = append(errs, errors.New("A source_image must be specified"))
}
if strings.TrimSpace(c.AlicloudSourceImage) != c.AlicloudSourceImage {
errs = append(errs, errors.New("The source_image can't include spaces"))
}
if c.InstanceType == "" {
errs = append(errs, errors.New("An alicloud_instance_type must be specified"))
}
if c.UserData != "" && c.UserDataFile != "" {
errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
} else if c.UserDataFile != "" {
if _, err := os.Stat(c.UserDataFile); err != nil {
errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
}
}
return errs
}
-176
View File
@@ -1,176 +0,0 @@
package ecs
import (
"io/ioutil"
"os"
"testing"
"github.com/hashicorp/packer/helper/communicator"
)
func testConfig() *RunConfig {
return &RunConfig{
AlicloudSourceImage: "alicloud_images",
InstanceType: "ecs.n1.tiny",
Comm: communicator.Config{
SSHUsername: "alicloud",
},
}
}
func TestRunConfigPrepare(t *testing.T) {
c := testConfig()
err := c.Prepare(nil)
if len(err) > 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_InstanceType(t *testing.T) {
c := testConfig()
c.InstanceType = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SourceECSImage(t *testing.T) {
c := testConfig()
c.AlicloudSourceImage = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SSHPort(t *testing.T) {
c := testConfig()
c.Comm.SSHPort = 0
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHPort != 22 {
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
}
c.Comm.SSHPort = 44
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHPort != 44 {
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
}
}
func TestRunConfigPrepare_UserData(t *testing.T) {
c := testConfig()
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
c.UserData = "foo"
c.UserDataFile = tf.Name()
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_UserDataFile(t *testing.T) {
c := testConfig()
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
c.UserDataFile = "idontexistidontthink"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
c.UserDataFile = tf.Name()
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
c := testConfig()
c.Comm.SSHTemporaryKeyPairName = ""
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHTemporaryKeyPairName == "" {
t.Fatal("keypair name is empty")
}
c.Comm.SSHTemporaryKeyPairName = "ssh-key-123"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHTemporaryKeyPairName != "ssh-key-123" {
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)
}
}
-23
View File
@@ -1,23 +0,0 @@
package ecs
import (
"time"
"github.com/hashicorp/packer/helper/multistep"
)
var (
// modified in tests
sshHostSleepDuration = time.Second
)
type alicloudSSHHelper interface {
}
// SSHHost returns a function that can be given to the SSH communicator
func SSHHost(e alicloudSSHHelper, private bool) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
ipAddress := state.Get("ipaddress").(string)
return ipAddress, nil
}
}
@@ -1,77 +0,0 @@
package ecs
import (
"context"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepAttachKeyPair struct {
}
var attachKeyPairNotRetryErrors = []string{
"MissingParameter",
"DependencyViolation.WindowsInstance",
"InvalidKeyPairName.NotFound",
"InvalidRegionId.NotFound",
}
func (s *stepAttachKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*ClientWrapper)
config := state.Get("config").(*Config)
instance := state.Get("instance").(*ecs.Instance)
keyPairName := config.Comm.SSHKeyPairName
if keyPairName == "" {
return multistep.ActionContinue
}
_, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateAttachKeyPairRequest()
request.RegionId = config.AlicloudRegion
request.KeyPairName = keyPairName
request.InstanceIds = "[\"" + instance.InstanceId + "\"]"
return client.AttachKeyPair(request)
},
EvalFunc: client.EvalCouldRetryResponse(attachKeyPairNotRetryErrors, EvalNotRetryErrorType),
})
if err != nil {
return halt(state, err, fmt.Sprintf("Error attaching keypair %s to instance %s", keyPairName, instance.InstanceId))
}
ui.Message(fmt.Sprintf("Attach keypair %s to instance: %s", keyPairName, instance.InstanceId))
return multistep.ActionContinue
}
func (s *stepAttachKeyPair) Cleanup(state multistep.StateBag) {
client := state.Get("client").(*ClientWrapper)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.Instance)
keyPairName := config.Comm.SSHKeyPairName
if keyPairName == "" {
return
}
detachKeyPairRequest := ecs.CreateDetachKeyPairRequest()
detachKeyPairRequest.RegionId = config.AlicloudRegion
detachKeyPairRequest.KeyPairName = keyPairName
detachKeyPairRequest.InstanceIds = fmt.Sprintf("[\"%s\"]", instance.InstanceId)
_, err := client.DetachKeyPair(detachKeyPairRequest)
if err != nil {
err := fmt.Errorf("Error Detaching keypair %s to instance %s : %s", keyPairName,
instance.InstanceId, err)
state.Put("error", err)
ui.Error(err.Error())
return
}
ui.Message(fmt.Sprintf("Detach keypair %s from instance: %s", keyPairName, instance.InstanceId))
}
@@ -1,54 +0,0 @@
package ecs
import (
"context"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepCheckAlicloudSourceImage struct {
SourceECSImageId string
}
func (s *stepCheckAlicloudSourceImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ClientWrapper)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = config.AlicloudRegion
describeImagesRequest.ImageId = config.AlicloudSourceImage
imagesResponse, err := client.DescribeImages(describeImagesRequest)
if err != nil {
return halt(state, err, "Error querying alicloud image")
}
images := imagesResponse.Images.Image
// Describe markerplace image
describeImagesRequest.ImageOwnerAlias = "marketplace"
marketImagesResponse, err := client.DescribeImages(describeImagesRequest)
if err != nil {
return halt(state, err, "Error querying alicloud marketplace image")
}
marketImages := marketImagesResponse.Images.Image
if len(marketImages) > 0 {
images = append(images, marketImages...)
}
if len(images) == 0 {
err := fmt.Errorf("No alicloud image was found matching filters: %v", config.AlicloudSourceImage)
return halt(state, err, "")
}
ui.Message(fmt.Sprintf("Found image ID: %s", images[0].ImageId))
state.Put("source_image", &images[0])
return multistep.ActionContinue
}
func (s *stepCheckAlicloudSourceImage) Cleanup(multistep.StateBag) {}
-165
View File
@@ -1,165 +0,0 @@
package ecs
import (
"context"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
"github.com/hashicorp/packer/common/uuid"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepConfigAlicloudEIP struct {
AssociatePublicIpAddress bool
RegionId string
InternetChargeType string
InternetMaxBandwidthOut int
allocatedId string
SSHPrivateIp bool
}
var allocateEipAddressRetryErrors = []string{
"LastTokenProcessing",
}
func (s *stepConfigAlicloudEIP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.Instance)
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...")
allocateEipAddressRequest := s.buildAllocateEipAddressRequest(state)
allocateEipAddressResponse, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.AllocateEipAddress(allocateEipAddressRequest)
},
EvalFunc: client.EvalCouldRetryResponse(allocateEipAddressRetryErrors, EvalRetryErrorType),
})
if err != nil {
return halt(state, err, "Error allocating eip")
}
ipaddress := allocateEipAddressResponse.(*ecs.AllocateEipAddressResponse).EipAddress
ui.Message(fmt.Sprintf("Allocated eip: %s", ipaddress))
allocateId := allocateEipAddressResponse.(*ecs.AllocateEipAddressResponse).AllocationId
s.allocatedId = allocateId
err = s.waitForEipStatus(client, instance.RegionId, s.allocatedId, EipStatusAvailable)
if err != nil {
return halt(state, err, "Error wait eip available timeout")
}
associateEipAddressRequest := ecs.CreateAssociateEipAddressRequest()
associateEipAddressRequest.AllocationId = allocateId
associateEipAddressRequest.InstanceId = instance.InstanceId
if _, err := client.AssociateEipAddress(associateEipAddressRequest); err != nil {
e, ok := err.(errors.Error)
if !ok || e.ErrorCode() != "TaskConflict" {
return halt(state, err, "Error associating eip")
}
ui.Error(fmt.Sprintf("Error associate eip: %s", err))
}
err = s.waitForEipStatus(client, instance.RegionId, s.allocatedId, EipStatusInUse)
if err != nil {
return halt(state, err, "Error wait eip associated timeout")
}
state.Put("ipaddress", ipaddress)
return multistep.ActionContinue
}
func (s *stepConfigAlicloudEIP) Cleanup(state multistep.StateBag) {
if len(s.allocatedId) == 0 {
return
}
cleanUpMessage(state, "EIP")
client := state.Get("client").(*ClientWrapper)
instance := state.Get("instance").(*ecs.Instance)
ui := state.Get("ui").(packer.Ui)
unassociateEipAddressRequest := ecs.CreateUnassociateEipAddressRequest()
unassociateEipAddressRequest.AllocationId = s.allocatedId
unassociateEipAddressRequest.InstanceId = instance.InstanceId
if _, err := client.UnassociateEipAddress(unassociateEipAddressRequest); err != nil {
ui.Say(fmt.Sprintf("Failed to unassociate eip: %s", err))
}
if err := s.waitForEipStatus(client, instance.RegionId, s.allocatedId, EipStatusAvailable); err != nil {
ui.Say(fmt.Sprintf("Timeout while unassociating eip: %s", err))
}
releaseEipAddressRequest := ecs.CreateReleaseEipAddressRequest()
releaseEipAddressRequest.AllocationId = s.allocatedId
if _, err := client.ReleaseEipAddress(releaseEipAddressRequest); err != nil {
ui.Say(fmt.Sprintf("Failed to release eip: %s", err))
}
}
func (s *stepConfigAlicloudEIP) waitForEipStatus(client *ClientWrapper, regionId string, allocationId string, expectedStatus string) error {
describeEipAddressesRequest := ecs.CreateDescribeEipAddressesRequest()
describeEipAddressesRequest.RegionId = regionId
describeEipAddressesRequest.AllocationId = s.allocatedId
_, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
response, err := client.DescribeEipAddresses(describeEipAddressesRequest)
if err == nil && len(response.EipAddresses.EipAddress) == 0 {
err = fmt.Errorf("eip allocated is not find")
}
return response, err
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
return WaitForExpectToRetry
}
eipAddressesResponse := response.(*ecs.DescribeEipAddressesResponse)
eipAddresses := eipAddressesResponse.EipAddresses.EipAddress
for _, eipAddress := range eipAddresses {
if eipAddress.Status == expectedStatus {
return WaitForExpectSuccess
}
}
return WaitForExpectToRetry
},
RetryTimes: shortRetryTimes,
})
return err
}
func (s *stepConfigAlicloudEIP) buildAllocateEipAddressRequest(state multistep.StateBag) *ecs.AllocateEipAddressRequest {
instance := state.Get("instance").(*ecs.Instance)
request := ecs.CreateAllocateEipAddressRequest()
request.ClientToken = uuid.TimeOrderedUUID()
request.RegionId = instance.RegionId
request.InternetChargeType = s.InternetChargeType
request.Bandwidth = string(convertNumber(s.InternetMaxBandwidthOut))
return request
}
@@ -1,132 +0,0 @@
package ecs
import (
"context"
"fmt"
"os"
"runtime"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepConfigAlicloudKeyPair struct {
Debug bool
Comm *communicator.Config
DebugKeyPath string
RegionId string
keyName string
}
func (s *stepConfigAlicloudKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if s.Comm.SSHPrivateKeyFile != "" {
ui.Say("Using existing SSH private key")
privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
s.Comm.SSHPrivateKey = privateKeyBytes
return multistep.ActionContinue
}
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName == "" {
ui.Say("Using SSH Agent with key pair in source image")
return multistep.ActionContinue
}
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName != "" {
ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.Comm.SSHKeyPairName))
return multistep.ActionContinue
}
if s.Comm.SSHTemporaryKeyPairName == "" {
ui.Say("Not using temporary keypair")
s.Comm.SSHKeyPairName = ""
return multistep.ActionContinue
}
client := state.Get("client").(*ClientWrapper)
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
createKeyPairRequest := ecs.CreateCreateKeyPairRequest()
createKeyPairRequest.RegionId = s.RegionId
createKeyPairRequest.KeyPairName = s.Comm.SSHTemporaryKeyPairName
keyResp, err := client.CreateKeyPair(createKeyPairRequest)
if err != nil {
return halt(state, err, "Error creating temporary keypair")
}
// Set the keyname so we know to delete it later
s.keyName = s.Comm.SSHTemporaryKeyPairName
// Set some state data for use in future steps
s.Comm.SSHKeyPairName = s.keyName
s.Comm.SSHPrivateKey = []byte(keyResp.PrivateKeyBody)
// If we're in debug mode, output the private key to the working
// directory.
if s.Debug {
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
f, err := os.Create(s.DebugKeyPath)
if err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
defer f.Close()
// Write the key out
if _, err := f.Write([]byte(keyResp.PrivateKeyBody)); err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
// Chmod it so that it is SSH ready
if runtime.GOOS != "windows" {
if err := f.Chmod(0600); err != nil {
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *stepConfigAlicloudKeyPair) Cleanup(state multistep.StateBag) {
// If no key name is set, then we never created it, so just return
// If we used an SSH private key file, do not go about deleting
// keypairs
if s.Comm.SSHPrivateKeyFile != "" || (s.Comm.SSHKeyPairName == "" && s.keyName == "") {
return
}
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
// Remove the keypair
ui.Say("Deleting temporary keypair...")
deleteKeyPairsRequest := ecs.CreateDeleteKeyPairsRequest()
deleteKeyPairsRequest.RegionId = s.RegionId
deleteKeyPairsRequest.KeyPairNames = fmt.Sprintf("[\"%s\"]", s.keyName)
_, err := client.DeleteKeyPairs(deleteKeyPairsRequest)
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up keypair. Please delete the key manually: %s", s.keyName))
}
// Also remove the physical key if we're debugging.
if s.Debug {
if err := os.Remove(s.DebugKeyPath); err != nil {
ui.Error(fmt.Sprintf(
"Error removing debug key '%s': %s", s.DebugKeyPath, err))
}
}
}
@@ -1,48 +0,0 @@
package ecs
import (
"context"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepConfigAlicloudPublicIP struct {
publicIPAddress string
RegionId string
SSHPrivateIp bool
}
func (s *stepConfigAlicloudPublicIP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.Instance)
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
}
allocatePublicIpAddressRequest := ecs.CreateAllocatePublicIpAddressRequest()
allocatePublicIpAddressRequest.InstanceId = instance.InstanceId
ipaddress, err := client.AllocatePublicIpAddress(allocatePublicIpAddressRequest)
if err != nil {
return halt(state, err, "Error allocating public ip")
}
s.publicIPAddress = ipaddress.IpAddress
ui.Say(fmt.Sprintf("Allocated public ip address %s.", ipaddress.IpAddress))
state.Put("ipaddress", ipaddress.IpAddress)
return multistep.ActionContinue
}
func (s *stepConfigAlicloudPublicIP) Cleanup(state multistep.StateBag) {
}
@@ -1,152 +0,0 @@
package ecs
import (
"context"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepConfigAlicloudSecurityGroup struct {
SecurityGroupId string
SecurityGroupName string
Description string
VpcId string
RegionId string
isCreate bool
}
var createSecurityGroupRetryErrors = []string{
"IdempotentProcessing",
}
var deleteSecurityGroupRetryErrors = []string{
"DependencyViolation",
}
func (s *stepConfigAlicloudSecurityGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
networkType := state.Get("networktype").(InstanceNetWork)
if len(s.SecurityGroupId) != 0 {
describeSecurityGroupsRequest := ecs.CreateDescribeSecurityGroupsRequest()
describeSecurityGroupsRequest.RegionId = s.RegionId
if networkType == InstanceNetworkVpc {
vpcId := state.Get("vpcid").(string)
describeSecurityGroupsRequest.VpcId = vpcId
}
securityGroupsResponse, err := client.DescribeSecurityGroups(describeSecurityGroupsRequest)
if err != nil {
return halt(state, err, "Failed querying security group")
}
securityGroupItems := securityGroupsResponse.SecurityGroups.SecurityGroup
for _, securityGroupItem := range securityGroupItems {
if securityGroupItem.SecurityGroupId == s.SecurityGroupId {
state.Put("securitygroupid", s.SecurityGroupId)
s.isCreate = false
return multistep.ActionContinue
}
}
s.isCreate = false
err = fmt.Errorf("The specified security group {%s} doesn't exist.", s.SecurityGroupId)
return halt(state, err, "")
}
ui.Say("Creating security group...")
createSecurityGroupRequest := s.buildCreateSecurityGroupRequest(state)
securityGroupResponse, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.CreateSecurityGroup(createSecurityGroupRequest)
},
EvalFunc: client.EvalCouldRetryResponse(createSecurityGroupRetryErrors, EvalRetryErrorType),
})
if err != nil {
return halt(state, err, "Failed creating security group")
}
securityGroupId := securityGroupResponse.(*ecs.CreateSecurityGroupResponse).SecurityGroupId
ui.Message(fmt.Sprintf("Created security group: %s", securityGroupId))
state.Put("securitygroupid", securityGroupId)
s.isCreate = true
s.SecurityGroupId = securityGroupId
authorizeSecurityGroupEgressRequest := ecs.CreateAuthorizeSecurityGroupEgressRequest()
authorizeSecurityGroupEgressRequest.SecurityGroupId = securityGroupId
authorizeSecurityGroupEgressRequest.RegionId = s.RegionId
authorizeSecurityGroupEgressRequest.IpProtocol = IpProtocolAll
authorizeSecurityGroupEgressRequest.PortRange = DefaultPortRange
authorizeSecurityGroupEgressRequest.NicType = NicTypeInternet
authorizeSecurityGroupEgressRequest.DestCidrIp = DefaultCidrIp
if _, err := client.AuthorizeSecurityGroupEgress(authorizeSecurityGroupEgressRequest); err != nil {
return halt(state, err, "Failed authorizing security group")
}
authorizeSecurityGroupRequest := ecs.CreateAuthorizeSecurityGroupRequest()
authorizeSecurityGroupRequest.SecurityGroupId = securityGroupId
authorizeSecurityGroupRequest.RegionId = s.RegionId
authorizeSecurityGroupRequest.IpProtocol = IpProtocolAll
authorizeSecurityGroupRequest.PortRange = DefaultPortRange
authorizeSecurityGroupRequest.NicType = NicTypeInternet
authorizeSecurityGroupRequest.SourceCidrIp = DefaultCidrIp
if _, err := client.AuthorizeSecurityGroup(authorizeSecurityGroupRequest); err != nil {
return halt(state, err, "Failed authorizing security group")
}
return multistep.ActionContinue
}
func (s *stepConfigAlicloudSecurityGroup) Cleanup(state multistep.StateBag) {
if !s.isCreate {
return
}
cleanUpMessage(state, "security group")
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
_, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDeleteSecurityGroupRequest()
request.RegionId = s.RegionId
request.SecurityGroupId = s.SecurityGroupId
return client.DeleteSecurityGroup(request)
},
EvalFunc: client.EvalCouldRetryResponse(deleteSecurityGroupRetryErrors, EvalRetryErrorType),
RetryTimes: shortRetryTimes,
})
if err != nil {
ui.Error(fmt.Sprintf("Failed to delete security group, it may still be around: %s", err))
}
}
func (s *stepConfigAlicloudSecurityGroup) buildCreateSecurityGroupRequest(state multistep.StateBag) *ecs.CreateSecurityGroupRequest {
networkType := state.Get("networktype").(InstanceNetWork)
request := ecs.CreateCreateSecurityGroupRequest()
request.ClientToken = uuid.TimeOrderedUUID()
request.RegionId = s.RegionId
request.SecurityGroupName = s.SecurityGroupName
if networkType == InstanceNetworkVpc {
vpcId := state.Get("vpcid").(string)
request.VpcId = vpcId
}
return request
}
-148
View File
@@ -1,148 +0,0 @@
package ecs
import (
"context"
errorsNew "errors"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepConfigAlicloudVPC struct {
VpcId string
CidrBlock string //192.168.0.0/16 or 172.16.0.0/16 (default)
VpcName string
isCreate bool
}
var createVpcRetryErrors = []string{
"TOKEN_PROCESSING",
}
var deleteVpcRetryErrors = []string{
"DependencyViolation.Instance",
"DependencyViolation.RouteEntry",
"DependencyViolation.VSwitch",
"DependencyViolation.SecurityGroup",
"Forbbiden",
"TaskConflict",
}
func (s *stepConfigAlicloudVPC) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
if len(s.VpcId) != 0 {
describeVpcsRequest := ecs.CreateDescribeVpcsRequest()
describeVpcsRequest.VpcId = s.VpcId
describeVpcsRequest.RegionId = config.AlicloudRegion
vpcsResponse, err := client.DescribeVpcs(describeVpcsRequest)
if err != nil {
return halt(state, err, "Failed querying vpcs")
}
vpcs := vpcsResponse.Vpcs.Vpc
if len(vpcs) > 0 {
state.Put("vpcid", vpcs[0].VpcId)
s.isCreate = false
return multistep.ActionContinue
}
message := fmt.Sprintf("The specified vpc {%s} doesn't exist.", s.VpcId)
return halt(state, errorsNew.New(message), "")
}
ui.Say("Creating vpc...")
createVpcRequest := s.buildCreateVpcRequest(state)
createVpcResponse, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.CreateVpc(createVpcRequest)
},
EvalFunc: client.EvalCouldRetryResponse(createVpcRetryErrors, EvalRetryErrorType),
})
if err != nil {
return halt(state, err, "Failed creating vpc")
}
vpcId := createVpcResponse.(*ecs.CreateVpcResponse).VpcId
_, err = client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDescribeVpcsRequest()
request.RegionId = config.AlicloudRegion
request.VpcId = vpcId
return client.DescribeVpcs(request)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
return WaitForExpectToRetry
}
vpcsResponse := response.(*ecs.DescribeVpcsResponse)
vpcs := vpcsResponse.Vpcs.Vpc
if len(vpcs) > 0 {
for _, vpc := range vpcs {
if vpc.Status == VpcStatusAvailable {
return WaitForExpectSuccess
}
}
}
return WaitForExpectToRetry
},
RetryTimes: shortRetryTimes,
})
if err != nil {
return halt(state, err, "Failed waiting for vpc to become available")
}
ui.Message(fmt.Sprintf("Created vpc: %s", vpcId))
state.Put("vpcid", vpcId)
s.isCreate = true
s.VpcId = vpcId
return multistep.ActionContinue
}
func (s *stepConfigAlicloudVPC) Cleanup(state multistep.StateBag) {
if !s.isCreate {
return
}
cleanUpMessage(state, "VPC")
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
_, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDeleteVpcRequest()
request.VpcId = s.VpcId
return client.DeleteVpc(request)
},
EvalFunc: client.EvalCouldRetryResponse(deleteVpcRetryErrors, EvalRetryErrorType),
RetryTimes: shortRetryTimes,
})
if err != nil {
ui.Error(fmt.Sprintf("Error deleting vpc, it may still be around: %s", err))
}
}
func (s *stepConfigAlicloudVPC) buildCreateVpcRequest(state multistep.StateBag) *ecs.CreateVpcRequest {
config := state.Get("config").(*Config)
request := ecs.CreateCreateVpcRequest()
request.ClientToken = uuid.TimeOrderedUUID()
request.RegionId = config.AlicloudRegion
request.CidrBlock = s.CidrBlock
request.VpcName = s.VpcName
return request
}
-207
View File
@@ -1,207 +0,0 @@
package ecs
import (
"context"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepConfigAlicloudVSwitch struct {
VSwitchId string
ZoneId string
isCreate bool
CidrBlock string
VSwitchName string
}
var createVSwitchRetryErrors = []string{
"TOKEN_PROCESSING",
}
var deleteVSwitchRetryErrors = []string{
"IncorrectVSwitchStatus",
"DependencyViolation",
"DependencyViolation.HaVip",
"IncorrectRouteEntryStatus",
"TaskConflict",
}
func (s *stepConfigAlicloudVSwitch) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
vpcId := state.Get("vpcid").(string)
config := state.Get("config").(*Config)
if len(s.VSwitchId) != 0 {
describeVSwitchesRequest := ecs.CreateDescribeVSwitchesRequest()
describeVSwitchesRequest.VpcId = vpcId
describeVSwitchesRequest.VSwitchId = s.VSwitchId
describeVSwitchesRequest.ZoneId = s.ZoneId
vswitchesResponse, err := client.DescribeVSwitches(describeVSwitchesRequest)
if err != nil {
return halt(state, err, "Failed querying vswitch")
}
vswitch := vswitchesResponse.VSwitches.VSwitch
if len(vswitch) > 0 {
state.Put("vswitchid", vswitch[0].VSwitchId)
s.isCreate = false
return multistep.ActionContinue
}
s.isCreate = false
return halt(state, fmt.Errorf("The specified vswitch {%s} doesn't exist.", s.VSwitchId), "")
}
if s.ZoneId == "" {
describeZonesRequest := ecs.CreateDescribeZonesRequest()
describeZonesRequest.RegionId = config.AlicloudRegion
zonesResponse, err := client.DescribeZones(describeZonesRequest)
if err != nil {
return halt(state, err, "Query for available zones failed")
}
var instanceTypes []string
zones := zonesResponse.Zones.Zone
for _, zone := range zones {
isVSwitchSupported := false
for _, resourceType := range zone.AvailableResourceCreation.ResourceTypes {
if resourceType == "VSwitch" {
isVSwitchSupported = true
}
}
if isVSwitchSupported {
for _, instanceType := range zone.AvailableInstanceTypes.InstanceTypes {
if instanceType == config.InstanceType {
s.ZoneId = zone.ZoneId
break
}
instanceTypes = append(instanceTypes, instanceType)
}
}
}
if s.ZoneId == "" {
if len(instanceTypes) > 0 {
ui.Say(fmt.Sprintf("The instance type %s isn't available in this region."+
"\n You can either change the instance to one of following: %v \n"+
"or choose another region.", config.InstanceType, instanceTypes))
state.Put("error", fmt.Errorf("The instance type %s isn't available in this region."+
"\n You can either change the instance to one of following: %v \n"+
"or choose another region.", config.InstanceType, instanceTypes))
return multistep.ActionHalt
} else {
ui.Say(fmt.Sprintf("The instance type %s isn't available in this region."+
"\n You can change to other regions.", config.InstanceType))
state.Put("error", fmt.Errorf("The instance type %s isn't available in this region."+
"\n You can change to other regions.", config.InstanceType))
return multistep.ActionHalt
}
}
}
if config.CidrBlock == "" {
s.CidrBlock = DefaultCidrBlock //use the default CirdBlock
}
ui.Say("Creating vswitch...")
createVSwitchRequest := s.buildCreateVSwitchRequest(state)
createVSwitchResponse, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.CreateVSwitch(createVSwitchRequest)
},
EvalFunc: client.EvalCouldRetryResponse(createVSwitchRetryErrors, EvalRetryErrorType),
})
if err != nil {
return halt(state, err, "Error Creating vswitch")
}
vSwitchId := createVSwitchResponse.(*ecs.CreateVSwitchResponse).VSwitchId
describeVSwitchesRequest := ecs.CreateDescribeVSwitchesRequest()
describeVSwitchesRequest.VpcId = vpcId
describeVSwitchesRequest.VSwitchId = vSwitchId
_, err = client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.DescribeVSwitches(describeVSwitchesRequest)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
return WaitForExpectToRetry
}
vSwitchesResponse := response.(*ecs.DescribeVSwitchesResponse)
vSwitches := vSwitchesResponse.VSwitches.VSwitch
if len(vSwitches) > 0 {
for _, vSwitch := range vSwitches {
if vSwitch.Status == VSwitchStatusAvailable {
return WaitForExpectSuccess
}
}
}
return WaitForExpectToRetry
},
RetryTimes: shortRetryTimes,
})
if err != nil {
return halt(state, err, "Timeout waiting for vswitch to become available")
}
ui.Message(fmt.Sprintf("Created vswitch: %s", vSwitchId))
state.Put("vswitchid", vSwitchId)
s.isCreate = true
s.VSwitchId = vSwitchId
return multistep.ActionContinue
}
func (s *stepConfigAlicloudVSwitch) Cleanup(state multistep.StateBag) {
if !s.isCreate {
return
}
cleanUpMessage(state, "vSwitch")
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
_, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDeleteVSwitchRequest()
request.VSwitchId = s.VSwitchId
return client.DeleteVSwitch(request)
},
EvalFunc: client.EvalCouldRetryResponse(deleteVSwitchRetryErrors, EvalRetryErrorType),
RetryTimes: shortRetryTimes,
})
if err != nil {
ui.Error(fmt.Sprintf("Error deleting vswitch, it may still be around: %s", err))
}
}
func (s *stepConfigAlicloudVSwitch) buildCreateVSwitchRequest(state multistep.StateBag) *ecs.CreateVSwitchRequest {
vpcId := state.Get("vpcid").(string)
request := ecs.CreateCreateVSwitchRequest()
request.ClientToken = uuid.TimeOrderedUUID()
request.CidrBlock = s.CidrBlock
request.ZoneId = s.ZoneId
request.VpcId = vpcId
request.VSwitchName = s.VSwitchName
return request
}
-144
View File
@@ -1,144 +0,0 @@
package ecs
import (
"context"
"fmt"
"time"
"github.com/hashicorp/packer/common/random"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepCreateAlicloudImage struct {
AlicloudImageIgnoreDataDisks bool
WaitSnapshotReadyTimeout int
image *ecs.Image
}
var createImageRetryErrors = []string{
"IdempotentProcessing",
}
func (s *stepCreateAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
tempImageName := config.AlicloudImageName
if config.ImageEncrypted != nil && *config.ImageEncrypted {
tempImageName = fmt.Sprintf("packer_%s", random.AlphaNum(7))
ui.Say(fmt.Sprintf("Creating temporary image for encryption: %s", tempImageName))
} else {
ui.Say(fmt.Sprintf("Creating image: %s", tempImageName))
}
createImageRequest := s.buildCreateImageRequest(state, tempImageName)
createImageResponse, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.CreateImage(createImageRequest)
},
EvalFunc: client.EvalCouldRetryResponse(createImageRetryErrors, EvalRetryErrorType),
})
if err != nil {
return halt(state, err, "Error creating image")
}
imageId := createImageResponse.(*ecs.CreateImageResponse).ImageId
imagesResponse, err := client.WaitForImageStatus(config.AlicloudRegion, imageId, ImageStatusAvailable, time.Duration(s.WaitSnapshotReadyTimeout)*time.Second)
// save image first for cleaning up if timeout
images := imagesResponse.(*ecs.DescribeImagesResponse).Images.Image
if len(images) == 0 {
return halt(state, err, "Unable to find created image")
}
s.image = &images[0]
if err != nil {
return halt(state, err, "Timeout waiting for image to be created")
}
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)
return multistep.ActionContinue
}
func (s *stepCreateAlicloudImage) Cleanup(state multistep.StateBag) {
if s.image == nil {
return
}
config := state.Get("config").(*Config)
encryptedSet := config.ImageEncrypted != nil && *config.ImageEncrypted
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted && !encryptedSet {
return
}
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
if !cancelled && !halted && encryptedSet {
ui.Say(fmt.Sprintf("Deleting temporary image %s(%s) and related snapshots after finishing encryption...", s.image.ImageId, s.image.ImageName))
} else {
ui.Say("Deleting the image and related snapshots because of cancellation or error...")
}
deleteImageRequest := ecs.CreateDeleteImageRequest()
deleteImageRequest.RegionId = config.AlicloudRegion
deleteImageRequest.ImageId = s.image.ImageId
if _, err := client.DeleteImage(deleteImageRequest); err != nil {
ui.Error(fmt.Sprintf("Error deleting image, it may still be around: %s", err))
return
}
//Delete the snapshot of this image
for _, diskDevices := range s.image.DiskDeviceMappings.DiskDeviceMapping {
deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest()
deleteSnapshotRequest.SnapshotId = diskDevices.SnapshotId
if _, err := client.DeleteSnapshot(deleteSnapshotRequest); err != nil {
ui.Error(fmt.Sprintf("Error deleting snapshot, it may still be around: %s", err))
return
}
}
}
func (s *stepCreateAlicloudImage) buildCreateImageRequest(state multistep.StateBag, imageName string) *ecs.CreateImageRequest {
config := state.Get("config").(*Config)
request := ecs.CreateCreateImageRequest()
request.ClientToken = uuid.TimeOrderedUUID()
request.RegionId = config.AlicloudRegion
request.ImageName = imageName
request.ImageVersion = config.AlicloudImageVersion
request.Description = config.AlicloudImageDescription
if s.AlicloudImageIgnoreDataDisks {
snapshotId := state.Get("alicloudsnapshot").(string)
request.SnapshotId = snapshotId
} else {
instance := state.Get("instance").(*ecs.Instance)
request.InstanceId = instance.InstanceId
}
return request
}
@@ -1,206 +0,0 @@
package ecs
import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"strconv"
"github.com/hashicorp/packer/common/uuid"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepCreateAlicloudInstance struct {
IOOptimized *bool
InstanceType string
UserData string
UserDataFile string
instanceId string
RegionId string
InternetChargeType string
InternetMaxBandwidthOut int
InstanceName string
ZoneId string
instance *ecs.Instance
}
var createInstanceRetryErrors = []string{
"IdempotentProcessing",
}
var deleteInstanceRetryErrors = []string{
"IncorrectInstanceStatus.Initializing",
}
func (s *stepCreateAlicloudInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating instance...")
createInstanceRequest, err := s.buildCreateInstanceRequest(state)
if err != nil {
return halt(state, err, "")
}
createInstanceResponse, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.CreateInstance(createInstanceRequest)
},
EvalFunc: client.EvalCouldRetryResponse(createInstanceRetryErrors, EvalRetryErrorType),
})
if err != nil {
return halt(state, err, "Error creating instance")
}
instanceId := createInstanceResponse.(*ecs.CreateInstanceResponse).InstanceId
_, err = client.WaitForInstanceStatus(s.RegionId, instanceId, InstanceStatusStopped)
if err != nil {
return halt(state, err, "Error waiting create instance")
}
describeInstancesRequest := ecs.CreateDescribeInstancesRequest()
describeInstancesRequest.InstanceIds = fmt.Sprintf("[\"%s\"]", instanceId)
instances, err := client.DescribeInstances(describeInstancesRequest)
if err != nil {
return halt(state, err, "")
}
ui.Message(fmt.Sprintf("Created instance: %s", instanceId))
s.instance = &instances.Instances.Instance[0]
state.Put("instance", s.instance)
return multistep.ActionContinue
}
func (s *stepCreateAlicloudInstance) Cleanup(state multistep.StateBag) {
if s.instance == nil {
return
}
cleanUpMessage(state, "instance")
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
_, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDeleteInstanceRequest()
request.InstanceId = s.instance.InstanceId
request.Force = requests.NewBoolean(true)
return client.DeleteInstance(request)
},
EvalFunc: client.EvalCouldRetryResponse(deleteInstanceRetryErrors, EvalRetryErrorType),
RetryTimes: shortRetryTimes,
})
if err != nil {
ui.Say(fmt.Sprintf("Failed to clean up instance %s: %s", s.instance.InstanceId, err))
}
}
func (s *stepCreateAlicloudInstance) buildCreateInstanceRequest(state multistep.StateBag) (*ecs.CreateInstanceRequest, error) {
request := ecs.CreateCreateInstanceRequest()
request.ClientToken = uuid.TimeOrderedUUID()
request.RegionId = s.RegionId
request.InstanceType = s.InstanceType
request.InstanceName = s.InstanceName
request.ZoneId = s.ZoneId
sourceImage := state.Get("source_image").(*ecs.Image)
request.ImageId = sourceImage.ImageId
securityGroupId := state.Get("securitygroupid").(string)
request.SecurityGroupId = securityGroupId
networkType := state.Get("networktype").(InstanceNetWork)
if networkType == InstanceNetworkVpc {
vswitchId := state.Get("vswitchid").(string)
request.VSwitchId = vswitchId
userData, err := s.getUserData(state)
if err != nil {
return nil, err
}
request.UserData = userData
} else {
if s.InternetChargeType == "" {
s.InternetChargeType = "PayByTraffic"
}
if s.InternetMaxBandwidthOut == 0 {
s.InternetMaxBandwidthOut = 5
}
}
request.InternetChargeType = s.InternetChargeType
request.InternetMaxBandwidthOut = requests.Integer(convertNumber(s.InternetMaxBandwidthOut))
if s.IOOptimized != nil {
if *s.IOOptimized {
request.IoOptimized = IOOptimizedOptimized
} else {
request.IoOptimized = IOOptimizedNone
}
}
config := state.Get("config").(*Config)
password := config.Comm.SSHPassword
if password == "" && config.Comm.WinRMPassword != "" {
password = config.Comm.WinRMPassword
}
request.Password = password
systemDisk := config.AlicloudImageConfig.ECSSystemDiskMapping
request.SystemDiskDiskName = systemDisk.DiskName
request.SystemDiskCategory = systemDisk.DiskCategory
request.SystemDiskSize = requests.Integer(convertNumber(systemDisk.DiskSize))
request.SystemDiskDescription = systemDisk.Description
imageDisks := config.AlicloudImageConfig.ECSImagesDiskMappings
var dataDisks []ecs.CreateInstanceDataDisk
for _, imageDisk := range imageDisks {
var dataDisk ecs.CreateInstanceDataDisk
dataDisk.DiskName = imageDisk.DiskName
dataDisk.Category = imageDisk.DiskCategory
dataDisk.Size = string(convertNumber(imageDisk.DiskSize))
dataDisk.SnapshotId = imageDisk.SnapshotId
dataDisk.Description = imageDisk.Description
dataDisk.DeleteWithInstance = strconv.FormatBool(imageDisk.DeleteWithInstance)
dataDisk.Device = imageDisk.Device
if imageDisk.Encrypted != nil {
dataDisk.Encrypted = strconv.FormatBool(*imageDisk.Encrypted)
}
dataDisks = append(dataDisks, dataDisk)
}
request.DataDisk = &dataDisks
return request, nil
}
func (s *stepCreateAlicloudInstance) getUserData(state multistep.StateBag) (string, error) {
userData := s.UserData
if s.UserDataFile != "" {
data, err := ioutil.ReadFile(s.UserDataFile)
if err != nil {
return "", err
}
userData = string(data)
}
if userData != "" {
userData = base64.StdEncoding.EncodeToString([]byte(userData))
}
return userData, nil
}
@@ -1,90 +0,0 @@
package ecs
import (
"context"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
"time"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepCreateAlicloudSnapshot struct {
snapshot *ecs.Snapshot
WaitSnapshotReadyTimeout int
}
func (s *stepCreateAlicloudSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.Instance)
describeDisksRequest := ecs.CreateDescribeDisksRequest()
describeDisksRequest.RegionId = config.AlicloudRegion
describeDisksRequest.InstanceId = instance.InstanceId
describeDisksRequest.DiskType = DiskTypeSystem
disksResponse, err := client.DescribeDisks(describeDisksRequest)
if err != nil {
return halt(state, err, "Error describe disks")
}
disks := disksResponse.Disks.Disk
if len(disks) == 0 {
return halt(state, err, "Unable to find system disk of instance")
}
createSnapshotRequest := ecs.CreateCreateSnapshotRequest()
createSnapshotRequest.DiskId = disks[0].DiskId
snapshot, err := client.CreateSnapshot(createSnapshotRequest)
if err != nil {
return halt(state, err, "Error creating snapshot")
}
// Create the alicloud snapshot
ui.Say(fmt.Sprintf("Creating snapshot from system disk %s: %s", disks[0].DiskId, snapshot.SnapshotId))
snapshotsResponse, err := client.WaitForSnapshotStatus(config.AlicloudRegion, snapshot.SnapshotId, SnapshotStatusAccomplished, time.Duration(s.WaitSnapshotReadyTimeout)*time.Second)
if err != nil {
_, ok := err.(errors.Error)
if ok {
return halt(state, err, "Error querying created snapshot")
}
return halt(state, err, "Timeout waiting for snapshot to be created")
}
snapshots := snapshotsResponse.(*ecs.DescribeSnapshotsResponse).Snapshots.Snapshot
if len(snapshots) == 0 {
return halt(state, err, "Unable to find created snapshot")
}
s.snapshot = &snapshots[0]
state.Put("alicloudsnapshot", snapshot.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").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting the snapshot because of cancellation or error...")
deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest()
deleteSnapshotRequest.SnapshotId = s.snapshot.SnapshotId
if _, err := client.DeleteSnapshot(deleteSnapshotRequest); err != nil {
ui.Error(fmt.Sprintf("Error deleting snapshot, it may still be around: %s", err))
return
}
}
-65
View File
@@ -1,65 +0,0 @@
package ecs
import (
"context"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepCreateTags struct {
Tags map[string]string
}
func (s *stepCreateTags) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
client := state.Get("client").(*ClientWrapper)
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))
var tags []ecs.AddTagsTag
for key, value := range s.Tags {
var tag ecs.AddTagsTag
tag.Key = key
tag.Value = value
tags = append(tags, tag)
}
addTagsRequest := ecs.CreateAddTagsRequest()
addTagsRequest.RegionId = config.AlicloudRegion
addTagsRequest.ResourceId = imageId
addTagsRequest.ResourceType = TagResourceImage
addTagsRequest.Tag = &tags
if _, err := client.AddTags(addTagsRequest); 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))
addTagsRequest := ecs.CreateAddTagsRequest()
addTagsRequest.RegionId = config.AlicloudRegion
addTagsRequest.ResourceId = snapshotId
addTagsRequest.ResourceType = TagResourceSnapshot
addTagsRequest.Tag = &tags
if _, err := client.AddTags(addTagsRequest); 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
}
@@ -1,101 +0,0 @@
package ecs
import (
"context"
"fmt"
"log"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepDeleteAlicloudImageSnapshots struct {
AlicloudImageForceDelete bool
AlicloudImageForceDeleteSnapshots bool
AlicloudImageName string
AlicloudImageDestinationRegions []string
AlicloudImageDestinationNames []string
}
func (s *stepDeleteAlicloudImageSnapshots) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
// 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 {
return multistep.ActionContinue
}
for index, destinationRegion := range s.AlicloudImageDestinationRegions {
if destinationRegion == config.AlicloudRegion {
continue
}
if index < numberOfName {
err = s.deleteImageAndSnapshots(state, s.AlicloudImageDestinationNames[index], destinationRegion)
if err != nil {
return halt(state, err, "")
}
} else {
break
}
}
}
return multistep.ActionContinue
}
func (s *stepDeleteAlicloudImageSnapshots) deleteImageAndSnapshots(state multistep.StateBag, imageName string, region string) error {
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = region
describeImagesRequest.ImageName = imageName
describeImagesRequest.Status = ImageStatusQueried
imageResponse, _ := client.DescribeImages(describeImagesRequest)
images := imageResponse.Images.Image
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 != ImageOwnerSelf {
log.Printf("You can not delete non-customized images: %s ", image.ImageId)
continue
}
deleteImageRequest := ecs.CreateDeleteImageRequest()
deleteImageRequest.RegionId = region
deleteImageRequest.ImageId = image.ImageId
if _, err := client.DeleteImage(deleteImageRequest); err != nil {
err := fmt.Errorf("Failed to delete image: %s", err)
return err
}
if s.AlicloudImageForceDeleteSnapshots {
for _, diskDevice := range image.DiskDeviceMappings.DiskDeviceMapping {
deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest()
deleteSnapshotRequest.SnapshotId = diskDevice.SnapshotId
if _, err := client.DeleteSnapshot(deleteSnapshotRequest); err != nil {
err := fmt.Errorf("Deleting ECS snapshot failed: %s", err)
return err
}
}
}
}
return nil
}
func (s *stepDeleteAlicloudImageSnapshots) Cleanup(state multistep.StateBag) {
}
-87
View File
@@ -1,87 +0,0 @@
package ecs
import (
"context"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepPreValidate struct {
AlicloudDestImageName string
ForceDelete bool
}
func (s *stepPreValidate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
if err := s.validateRegions(state); err != nil {
return halt(state, err, "")
}
if err := s.validateDestImageName(state); err != nil {
return halt(state, err, "")
}
return multistep.ActionContinue
}
func (s *stepPreValidate) validateRegions(state multistep.StateBag) error {
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
if config.AlicloudSkipValidation {
ui.Say("Skip region validation flag found, skipping prevalidating source region and copied regions.")
return nil
}
ui.Say("Prevalidating source region and copied regions...")
var errs *packer.MultiError
if err := config.ValidateRegion(config.AlicloudRegion); err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
for _, region := range config.AlicloudImageDestinationRegions {
if err := config.ValidateRegion(region); err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}
func (s *stepPreValidate) validateDestImageName(state multistep.StateBag) error {
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*ClientWrapper)
config := state.Get("config").(*Config)
if s.ForceDelete {
ui.Say("Force delete flag found, skipping prevalidating image name.")
return nil
}
ui.Say("Prevalidating image name...")
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = config.AlicloudRegion
describeImagesRequest.ImageName = s.AlicloudDestImageName
describeImagesRequest.Status = ImageStatusQueried
imagesResponse, err := client.DescribeImages(describeImagesRequest)
if err != nil {
return fmt.Errorf("Error querying alicloud image: %s", err)
}
images := imagesResponse.Images.Image
if len(images) > 0 {
return fmt.Errorf("Error: Image Name: '%s' is used by an existing alicloud image: %s", images[0].ImageName, images[0].ImageId)
}
return nil
}
func (s *stepPreValidate) Cleanup(multistep.StateBag) {}
@@ -1,105 +0,0 @@
package ecs
import (
"context"
"fmt"
"time"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepRegionCopyAlicloudImage struct {
AlicloudImageDestinationRegions []string
AlicloudImageDestinationNames []string
RegionId string
}
func (s *stepRegionCopyAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
if config.ImageEncrypted != nil {
s.AlicloudImageDestinationRegions = append(s.AlicloudImageDestinationRegions, s.RegionId)
s.AlicloudImageDestinationNames = append(s.AlicloudImageDestinationNames, config.AlicloudImageName)
}
if len(s.AlicloudImageDestinationRegions) == 0 {
return multistep.ActionContinue
}
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
srcImageId := state.Get("alicloudimage").(string)
alicloudImages := state.Get("alicloudimages").(map[string]string)
numberOfName := len(s.AlicloudImageDestinationNames)
ui.Say(fmt.Sprintf("Coping image %s from %s...", srcImageId, s.RegionId))
for index, destinationRegion := range s.AlicloudImageDestinationRegions {
if destinationRegion == s.RegionId && config.ImageEncrypted == nil {
continue
}
ecsImageName := ""
if numberOfName > 0 && index < numberOfName {
ecsImageName = s.AlicloudImageDestinationNames[index]
}
copyImageRequest := ecs.CreateCopyImageRequest()
copyImageRequest.RegionId = s.RegionId
copyImageRequest.ImageId = srcImageId
copyImageRequest.DestinationRegionId = destinationRegion
copyImageRequest.DestinationImageName = ecsImageName
if config.ImageEncrypted != nil {
copyImageRequest.Encrypted = requests.NewBoolean(*config.ImageEncrypted)
}
imageResponse, err := client.CopyImage(copyImageRequest)
if err != nil {
return halt(state, err, "Error copying images")
}
alicloudImages[destinationRegion] = imageResponse.ImageId
ui.Message(fmt.Sprintf("Copy image from %s(%s) to %s(%s)", s.RegionId, srcImageId, destinationRegion, imageResponse.ImageId))
}
if config.ImageEncrypted != nil {
if _, err := client.WaitForImageStatus(s.RegionId, alicloudImages[s.RegionId], ImageStatusAvailable, time.Duration(ALICLOUD_DEFAULT_LONG_TIMEOUT)*time.Second); err != nil {
return halt(state, err, fmt.Sprintf("Timeout waiting image %s finish copying", alicloudImages[s.RegionId]))
}
}
return multistep.ActionContinue
}
func (s *stepRegionCopyAlicloudImage) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Stopping copy image because cancellation or error..."))
client := state.Get("client").(*ClientWrapper)
alicloudImages := state.Get("alicloudimages").(map[string]string)
srcImageId := state.Get("alicloudimage").(string)
for copiedRegionId, copiedImageId := range alicloudImages {
if copiedImageId == srcImageId {
continue
}
cancelCopyImageRequest := ecs.CreateCancelCopyImageRequest()
cancelCopyImageRequest.RegionId = copiedRegionId
cancelCopyImageRequest.ImageId = copiedImageId
if _, err := client.CancelCopyImage(cancelCopyImageRequest); err != nil {
ui.Error(fmt.Sprintf("Error cancelling copy image: %v", err))
}
}
}
-72
View File
@@ -1,72 +0,0 @@
package ecs
import (
"context"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepRunAlicloudInstance struct {
}
func (s *stepRunAlicloudInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.Instance)
startInstanceRequest := ecs.CreateStartInstanceRequest()
startInstanceRequest.InstanceId = instance.InstanceId
if _, err := client.StartInstance(startInstanceRequest); err != nil {
return halt(state, err, "Error starting instance")
}
ui.Say(fmt.Sprintf("Starting instance: %s", instance.InstanceId))
_, err := client.WaitForInstanceStatus(instance.RegionId, instance.InstanceId, InstanceStatusRunning)
if err != nil {
return halt(state, err, "Timeout waiting for instance to start")
}
return multistep.ActionContinue
}
func (s *stepRunAlicloudInstance) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*ClientWrapper)
instance := state.Get("instance").(*ecs.Instance)
describeInstancesRequest := ecs.CreateDescribeInstancesRequest()
describeInstancesRequest.InstanceIds = fmt.Sprintf("[\"%s\"]", instance.InstanceId)
instancesResponse, _ := client.DescribeInstances(describeInstancesRequest)
if len(instancesResponse.Instances.Instance) == 0 {
return
}
instanceAttribute := instancesResponse.Instances.Instance[0]
if instanceAttribute.Status == InstanceStatusStarting || instanceAttribute.Status == InstanceStatusRunning {
stopInstanceRequest := ecs.CreateStopInstanceRequest()
stopInstanceRequest.InstanceId = instance.InstanceId
stopInstanceRequest.ForceStop = requests.NewBoolean(true)
if _, err := client.StopInstance(stopInstanceRequest); err != nil {
ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err))
return
}
_, err := client.WaitForInstanceStatus(instance.RegionId, instance.InstanceId, InstanceStatusStopped)
if err != nil {
ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err))
}
}
}
-60
View File
@@ -1,60 +0,0 @@
package ecs
import (
"context"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepShareAlicloudImage struct {
AlicloudImageShareAccounts []string
AlicloudImageUNShareAccounts []string
RegionId string
}
func (s *stepShareAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ClientWrapper)
alicloudImages := state.Get("alicloudimages").(map[string]string)
for regionId, imageId := range alicloudImages {
modifyImageShareRequest := ecs.CreateModifyImageSharePermissionRequest()
modifyImageShareRequest.RegionId = regionId
modifyImageShareRequest.ImageId = imageId
modifyImageShareRequest.AddAccount = &s.AlicloudImageShareAccounts
modifyImageShareRequest.RemoveAccount = &s.AlicloudImageUNShareAccounts
if _, err := client.ModifyImageSharePermission(modifyImageShareRequest); err != nil {
return halt(state, err, "Failed modifying image share permissions")
}
}
return multistep.ActionContinue
}
func (s *stepShareAlicloudImage) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*ClientWrapper)
alicloudImages := state.Get("alicloudimages").(map[string]string)
ui.Say("Restoring image share permission because cancellations or error...")
for regionId, imageId := range alicloudImages {
modifyImageShareRequest := ecs.CreateModifyImageSharePermissionRequest()
modifyImageShareRequest.RegionId = regionId
modifyImageShareRequest.ImageId = imageId
modifyImageShareRequest.AddAccount = &s.AlicloudImageUNShareAccounts
modifyImageShareRequest.RemoveAccount = &s.AlicloudImageShareAccounts
if _, err := client.ModifyImageSharePermission(modifyImageShareRequest); err != nil {
ui.Say(fmt.Sprintf("Restoring image share permission failed: %s", err))
}
}
}
@@ -1,48 +0,0 @@
package ecs
import (
"context"
"fmt"
"strconv"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepStopAlicloudInstance struct {
ForceStop bool
DisableStop bool
}
func (s *stepStopAlicloudInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ClientWrapper)
instance := state.Get("instance").(*ecs.Instance)
ui := state.Get("ui").(packer.Ui)
if !s.DisableStop {
ui.Say(fmt.Sprintf("Stopping instance: %s", instance.InstanceId))
stopInstanceRequest := ecs.CreateStopInstanceRequest()
stopInstanceRequest.InstanceId = instance.InstanceId
stopInstanceRequest.ForceStop = requests.Boolean(strconv.FormatBool(s.ForceStop))
if _, err := client.StopInstance(stopInstanceRequest); err != nil {
return halt(state, err, "Error stopping alicloud instance")
}
}
ui.Say(fmt.Sprintf("Waiting instance stopped: %s", instance.InstanceId))
_, err := client.WaitForInstanceStatus(instance.RegionId, instance.InstanceId, InstanceStatusStopped)
if err != nil {
return halt(state, err, "Error waiting for alicloud instance to stop")
}
return multistep.ActionContinue
}
func (s *stepStopAlicloudInstance) Cleanup(multistep.StateBag) {
// No cleanup...
}
+82 -179
View File
@@ -5,17 +5,18 @@
package chroot
import (
"context"
"errors"
"log"
"runtime"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
)
// The unique ID for this builder
@@ -24,29 +25,19 @@ const BuilderId = "mitchellh.amazon.chroot"
// Config is the configuration that is chained through the steps and
// settable from the template.
type Config struct {
common.PackerConfig `mapstructure:",squash"`
awscommon.AMIBlockDevices `mapstructure:",squash"`
awscommon.AMIConfig `mapstructure:",squash"`
awscommon.AccessConfig `mapstructure:",squash"`
common.PackerConfig `mapstructure:",squash"`
awscommon.AccessConfig `mapstructure:",squash"`
awscommon.AMIConfig `mapstructure:",squash"`
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
CommandWrapper string `mapstructure:"command_wrapper"`
CopyFiles []string `mapstructure:"copy_files"`
DevicePath string `mapstructure:"device_path"`
NVMEDevicePath string `mapstructure:"nvme_device_path"`
FromScratch bool `mapstructure:"from_scratch"`
MountOptions []string `mapstructure:"mount_options"`
MountPartition string `mapstructure:"mount_partition"`
MountPath string `mapstructure:"mount_path"`
PostMountCommands []string `mapstructure:"post_mount_commands"`
PreMountCommands []string `mapstructure:"pre_mount_commands"`
RootDeviceName string `mapstructure:"root_device_name"`
RootVolumeSize int64 `mapstructure:"root_volume_size"`
RootVolumeType string `mapstructure:"root_volume_type"`
SourceAmi string `mapstructure:"source_ami"`
SourceAmiFilter awscommon.AmiFilterOptions `mapstructure:"source_ami_filter"`
RootVolumeTags awscommon.TagMap `mapstructure:"root_volume_tags"`
Architecture string `mapstructure:"ami_architecture"`
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
CommandWrapper string `mapstructure:"command_wrapper"`
CopyFiles []string `mapstructure:"copy_files"`
DevicePath string `mapstructure:"device_path"`
MountPath string `mapstructure:"mount_path"`
SourceAmi string `mapstructure:"source_ami"`
RootVolumeSize int64 `mapstructure:"root_volume_size"`
MountOptions []string `mapstructure:"mount_options"`
MountPartition int `mapstructure:"mount_partition"`
ctx interpolate.Context
}
@@ -67,13 +58,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"ami_description",
"snapshot_tags",
"tags",
"root_volume_tags",
"command_wrapper",
"post_mount_commands",
"pre_mount_commands",
"mount_path",
},
},
@@ -82,34 +67,27 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
return nil, err
}
if b.config.Architecture == "" {
b.config.Architecture = "x86_64"
}
if b.config.PackerConfig.PackerForce {
b.config.AMIForceDeregister = true
}
// Defaults
if b.config.ChrootMounts == nil {
b.config.ChrootMounts = make([][]string, 0)
}
if b.config.CopyFiles == nil {
b.config.CopyFiles = make([]string, 0)
}
if len(b.config.ChrootMounts) == 0 {
b.config.ChrootMounts = [][]string{
{"proc", "proc", "/proc"},
{"sysfs", "sysfs", "/sys"},
{"bind", "/dev", "/dev"},
{"devpts", "devpts", "/dev/pts"},
{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
[]string{"proc", "proc", "/proc"},
[]string{"sysfs", "sysfs", "/sys"},
[]string{"bind", "/dev", "/dev"},
[]string{"devpts", "devpts", "/dev/pts"},
[]string{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
}
}
// set default copy file if we're not giving our own
if b.config.CopyFiles == nil {
if !b.config.FromScratch {
b.config.CopyFiles = []string{"/etc/resolv.conf"}
}
if len(b.config.CopyFiles) == 0 {
b.config.CopyFiles = []string{"/etc/resolv.conf"}
}
if b.config.CommandWrapper == "" {
@@ -120,17 +98,14 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.MountPath = "/mnt/packer-amazon-chroot-volumes/{{.Device}}"
}
if b.config.MountPartition == "" {
b.config.MountPartition = "1"
if b.config.MountPartition == 0 {
b.config.MountPartition = 1
}
// Accumulate any errors or warnings
// Accumulate any errors
var errs *packer.MultiError
var warns []string
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs,
b.config.AMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...)
for _, mounts := range b.config.ChrootMounts {
if len(mounts) != 3 {
@@ -140,94 +115,41 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
}
if b.config.FromScratch {
if b.config.SourceAmi != "" || !b.config.SourceAmiFilter.Empty() {
warns = append(warns, "source_ami and source_ami_filter are unused when from_scratch is true")
}
if b.config.RootVolumeSize == 0 {
errs = packer.MultiErrorAppend(
errs, errors.New("root_volume_size is required with from_scratch."))
}
if len(b.config.PreMountCommands) == 0 {
errs = packer.MultiErrorAppend(
errs, errors.New("pre_mount_commands is required with from_scratch."))
}
if b.config.AMIVirtType == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("ami_virtualization_type is required with from_scratch."))
}
if b.config.RootDeviceName == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("root_device_name is required with from_scratch."))
}
if len(b.config.AMIMappings) == 0 {
errs = packer.MultiErrorAppend(
errs, errors.New("ami_block_device_mappings is required with from_scratch."))
}
} else {
if b.config.SourceAmi == "" && b.config.SourceAmiFilter.Empty() {
errs = packer.MultiErrorAppend(
errs, errors.New("source_ami or source_ami_filter is required."))
}
if len(b.config.AMIMappings) > 0 && b.config.RootDeviceName != "" {
if b.config.RootVolumeSize == 0 {
// Although, they can specify the device size in the block device mapping, it's easier to
// be specific here.
errs = packer.MultiErrorAppend(
errs, errors.New("root_volume_size is required if ami_block_device_mappings is specified"))
}
warns = append(warns, "ami_block_device_mappings from source image will be completely overwritten")
} else if len(b.config.AMIMappings) > 0 {
errs = packer.MultiErrorAppend(
errs, errors.New("If ami_block_device_mappings is specified, root_device_name must be specified"))
} else if b.config.RootDeviceName != "" {
errs = packer.MultiErrorAppend(
errs, errors.New("If root_device_name is specified, ami_block_device_mappings must be specified"))
}
}
valid := false
for _, validArch := range []string{"x86_64", "arm64"} {
if validArch == b.config.Architecture {
valid = true
break
}
}
if !valid {
errs = packer.MultiErrorAppend(errs, errors.New(`The only valid ami_architecture values are "x86_64" and "arm64"`))
if b.config.SourceAmi == "" {
errs = packer.MultiErrorAppend(errs, errors.New("source_ami is required."))
}
if errs != nil && len(errs.Errors) > 0 {
return warns, errs
return nil, errs
}
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
return warns, nil
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
return nil, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
if runtime.GOOS != "linux" {
return nil, errors.New("The amazon-chroot builder only works on Linux environments.")
}
session, err := b.config.Session()
config, err := b.config.Config()
if err != nil {
return nil, err
}
session := session.New(config)
ec2conn := ec2.New(session)
wrappedCommand := func(command string) (string, error) {
ictx := b.config.ctx
ictx.Data = &wrappedCommandTemplate{Command: command}
return interpolate.Render(b.config.CommandWrapper, &ictx)
ctx := b.config.ctx
ctx.Data = &wrappedCommandTemplate{Command: command}
return interpolate.Render(b.config.CommandWrapper, &ctx)
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("access_config", &b.config.AccessConfig)
state.Put("ami_config", &b.config.AMIConfig)
state.Put("ec2", ec2conn)
state.Put("awsSession", session)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
@@ -239,87 +161,61 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
ForceDeregister: b.config.AMIForceDeregister,
},
&StepInstanceInfo{},
}
if !b.config.FromScratch {
steps = append(steps,
&awscommon.StepSourceAMIInfo{
SourceAmi: b.config.SourceAmi,
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
AmiFilters: b.config.SourceAmiFilter,
AMIVirtType: b.config.AMIVirtType,
},
&StepCheckRootDevice{},
)
}
steps = append(steps,
&awscommon.StepSourceAMIInfo{
SourceAmi: b.config.SourceAmi,
EnhancedNetworking: b.config.AMIEnhancedNetworking,
},
&StepCheckRootDevice{},
&StepFlock{},
&StepPrepareDevice{},
&StepCreateVolume{
RootVolumeType: b.config.RootVolumeType,
RootVolumeSize: b.config.RootVolumeSize,
RootVolumeTags: b.config.RootVolumeTags,
Ctx: b.config.ctx,
},
&StepAttachVolume{},
&StepEarlyUnflock{},
&StepPreMountCommands{
Commands: b.config.PreMountCommands,
},
&StepMountDevice{
MountOptions: b.config.MountOptions,
MountPartition: b.config.MountPartition,
},
&StepPostMountCommands{
Commands: b.config.PostMountCommands,
},
&StepMountExtra{},
&StepCopyFiles{},
&StepChrootProvision{},
&StepEarlyCleanup{},
&StepSnapshot{},
&awscommon.StepDeregisterAMI{
AccessConfig: &b.config.AccessConfig,
ForceDeregister: b.config.AMIForceDeregister,
ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot,
AMIName: b.config.AMIName,
Regions: b.config.AMIRegions,
ForceDeregister: b.config.AMIForceDeregister,
AMIName: b.config.AMIName,
},
&StepRegisterAMI{
RootVolumeSize: b.config.RootVolumeSize,
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
RootVolumeSize: b.config.RootVolumeSize,
},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,
AMIKmsKeyId: b.config.AMIKmsKeyId,
RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
OriginalRegion: *ec2conn.Config.Region,
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,
Name: b.config.AMIName,
},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
SnapshotUsers: b.config.SnapshotUsers,
SnapshotGroups: b.config.SnapshotGroups,
Ctx: b.config.ctx,
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
SnapshotTags: b.config.SnapshotTags,
Ctx: b.config.ctx,
Tags: b.config.AMITags,
},
)
}
// Run!
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
@@ -335,8 +231,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
artifact := &awscommon.Artifact{
Amis: state.Get("amis").(map[string]string),
BuilderIdValue: BuilderId,
Session: session,
Conn: ec2conn,
}
return artifact, nil
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}
+3 -95
View File
@@ -1,18 +1,14 @@
package chroot
import (
"github.com/mitchellh/packer/packer"
"testing"
"github.com/hashicorp/packer/packer"
)
func testConfig() map[string]interface{} {
return 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 +26,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)
@@ -74,16 +69,11 @@ func TestBuilderPrepare_ChrootMounts(t *testing.T) {
if err != nil {
t.Errorf("err: %s", err)
}
}
func TestBuilderPrepare_ChrootMountsBadDefaults(t *testing.T) {
b := &Builder{}
config := testConfig()
config["chroot_mounts"] = [][]string{
{"bad"},
[]string{"bad"},
}
warnings, err := b.Prepare(config)
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
@@ -127,85 +117,3 @@ func TestBuilderPrepare_CommandWrapper(t *testing.T) {
t.Errorf("err: %s", err)
}
}
func TestBuilderPrepare_CopyFiles(t *testing.T) {
b := &Builder{}
config := testConfig()
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
if len(b.config.CopyFiles) != 1 && b.config.CopyFiles[0] != "/etc/resolv.conf" {
t.Errorf("Was expecting default value for copy_files.")
}
}
func TestBuilderPrepare_CopyFilesNoDefault(t *testing.T) {
b := &Builder{}
config := testConfig()
config["copy_files"] = []string{}
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
if len(b.config.CopyFiles) > 0 {
t.Errorf("Was expecting no default value for copy_files. Found %v",
b.config.CopyFiles)
}
}
func TestBuilderPrepare_RootDeviceNameAndAMIMappings(t *testing.T) {
var b Builder
config := testConfig()
config["root_device_name"] = "/dev/sda"
config["ami_block_device_mappings"] = []interface{}{map[string]string{}}
config["root_volume_size"] = 15
warnings, err := b.Prepare(config)
if len(warnings) == 0 {
t.Fatal("Missing warning, stating block device mappings will be overwritten")
} else if len(warnings) > 1 {
t.Fatalf("excessive warnings: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_AMIMappingsNoRootDeviceName(t *testing.T) {
var b Builder
config := testConfig()
config["ami_block_device_mappings"] = []interface{}{map[string]string{}}
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("should have error")
}
}
func TestBuilderPrepare_RootDeviceNameNoAMIMappings(t *testing.T) {
var b Builder
config := testConfig()
config["root_device_name"] = "/dev/sda"
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("should have error")
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
package chroot
import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/mitchellh/multistep"
)
// Cleanup is an interface that some steps implement for early cleanup.
+6 -13
View File
@@ -2,19 +2,17 @@ package chroot
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer/tmp"
"github.com/mitchellh/packer/packer"
)
// Communicator is a special communicator that works by executing
@@ -24,11 +22,9 @@ type Communicator struct {
CmdWrapper CommandWrapper
}
func (c *Communicator) Start(ctx context.Context, cmd *packer.RemoteCmd) error {
// need extra escapes for the command since we're wrapping it in quotes
cmd.Command = strconv.Quote(cmd.Command)
func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
command, err := c.CmdWrapper(
fmt.Sprintf("chroot %s /bin/sh -c %s", c.Chroot, cmd.Command))
fmt.Sprintf("chroot %s /bin/sh -c \"%s\"", c.Chroot, cmd.Command))
if err != nil {
return err
}
@@ -68,15 +64,12 @@ func (c *Communicator) Start(ctx context.Context, cmd *packer.RemoteCmd) error {
func (c *Communicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
dst = filepath.Join(c.Chroot, dst)
log.Printf("Uploading to chroot dir: %s", dst)
tf, err := tmp.File("packer-amazon-chroot")
tf, err := ioutil.TempFile("", "packer-amazon-chroot")
if err != nil {
return fmt.Errorf("Error preparing shell script: %s", err)
}
defer os.Remove(tf.Name())
if _, err := io.Copy(tf, r); err != nil {
return err
}
io.Copy(tf, r)
cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp %s %s", tf.Name(), dst))
if err != nil {
+1 -2
View File
@@ -1,9 +1,8 @@
package chroot
import (
"github.com/mitchellh/packer/packer"
"testing"
"github.com/hashicorp/packer/packer"
)
func TestCommunicator_ImplementsCommunicator(t *testing.T) {
-10
View File
@@ -1,10 +0,0 @@
package chroot
import "testing"
func TestDevicePrefixMatch(t *testing.T) {
/*
if devicePrefixMatch("nvme0n1") != "" {
}
*/
}
+3 -4
View File
@@ -4,8 +4,7 @@ package chroot
import (
"os"
"golang.org/x/sys/unix"
"syscall"
)
// See: http://linux.die.net/include/sys/file.h
@@ -14,7 +13,7 @@ const LOCK_NB = 4
const LOCK_UN = 8
func lockFile(f *os.File) error {
err := unix.Flock(int(f.Fd()), LOCK_EX)
err := syscall.Flock(int(f.Fd()), LOCK_EX)
if err != nil {
return err
}
@@ -23,5 +22,5 @@ func lockFile(f *os.File) error {
}
func unlockFile(f *os.File) error {
return unix.Flock(int(f.Fd()), LOCK_UN)
return syscall.Flock(int(f.Fd()), LOCK_UN)
}
@@ -1,41 +0,0 @@
package chroot
import (
"context"
"fmt"
sl "github.com/hashicorp/packer/common/shell-local"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
func RunLocalCommands(commands []string, wrappedCommand CommandWrapper, ictx interpolate.Context, ui packer.Ui) error {
ctx := context.TODO()
for _, rawCmd := range commands {
intCmd, err := interpolate.Render(rawCmd, &ictx)
if err != nil {
return fmt.Errorf("Error interpolating: %s", err)
}
command, err := wrappedCommand(intCmd)
if err != nil {
return fmt.Errorf("Error wrapping command: %s", err)
}
ui.Say(fmt.Sprintf("Executing command: %s", command))
comm := &sl.Communicator{
ExecuteCommand: []string{"sh", "-c", command},
}
cmd := &packer.RemoteCmd{Command: command}
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return fmt.Errorf("Error executing command: %s", err)
}
if cmd.ExitStatus() != 0 {
return fmt.Errorf(
"Received non-zero exit code %d from command: %s",
cmd.ExitStatus(),
command)
}
}
return nil
}
+55 -8
View File
@@ -1,15 +1,15 @@
package chroot
import (
"context"
"errors"
"fmt"
"strings"
"time"
"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/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
)
// StepAttachVolume attaches the previously created volume to an
@@ -23,7 +23,7 @@ type StepAttachVolume struct {
volumeId string
}
func (s *StepAttachVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepAttachVolume) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
device := state.Get("device").(string)
instance := state.Get("instance").(*ec2.Instance)
@@ -51,7 +51,35 @@ func (s *StepAttachVolume) Run(ctx context.Context, state multistep.StateBag) mu
s.volumeId = volumeId
// Wait for the volume to become attached
err = awscommon.WaitUntilVolumeAttached(ctx, ec2conn, s.volumeId)
stateChange := awscommon.StateChangeConf{
Pending: []string{"attaching"},
StepState: state,
Target: "attached",
Refresh: func() (interface{}, string, error) {
attempts := 0
for attempts < 30 {
resp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesInput{VolumeIds: []*string{&volumeId}})
if err != nil {
return nil, "", err
}
if len(resp.Volumes[0].Attachments) > 0 {
a := resp.Volumes[0].Attachments[0]
return a, *a.State, nil
}
// When Attachment on volume is not present sleep for 2s and retry
attempts += 1
ui.Say(fmt.Sprintf(
"Volume %s show no attachments. Attempt %d/30. Sleeping for 2s and will retry.",
volumeId, attempts))
time.Sleep(2 * time.Second)
}
// Attachment on volume is not present after all attempts
return nil, "", errors.New("No attachments on volume.")
},
}
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
@@ -87,7 +115,26 @@ func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error {
s.attached = false
// Wait for the volume to detach
err = awscommon.WaitUntilVolumeDetached(aws.BackgroundContext(), ec2conn, s.volumeId)
stateChange := awscommon.StateChangeConf{
Pending: []string{"attaching", "attached", "detaching"},
StepState: state,
Target: "detached",
Refresh: func() (interface{}, string, error) {
resp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesInput{VolumeIds: []*string{&s.volumeId}})
if err != nil {
return nil, "", err
}
v := resp.Volumes[0]
if len(v.Attachments) > 0 {
return v, *v.Attachments[0].State, nil
} else {
return v, "detached", nil
}
},
}
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
return fmt.Errorf("Error waiting for volume: %s", err)
}
@@ -1,18 +1,17 @@
package chroot
import (
"context"
"fmt"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
// StepCheckRootDevice makes sure the root device on the AMI is EBS-backed.
type StepCheckRootDevice struct{}
func (s *StepCheckRootDevice) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepCheckRootDevice) Run(state multistep.StateBag) multistep.StepAction {
image := state.Get("source_image").(*ec2.Image)
ui := state.Get("ui").(packer.Ui)
@@ -1,18 +1,17 @@
package chroot
import (
"context"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepChrootProvision provisions the instance within a chroot.
type StepChrootProvision struct {
mounts []string
}
func (s *StepChrootProvision) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction {
hook := state.Get("hook").(packer.Hook)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
@@ -26,7 +25,7 @@ func (s *StepChrootProvision) Run(ctx context.Context, state multistep.StateBag)
// Provision
log.Println("Running the provision hook")
if err := hook.Run(ctx, packer.HookProvision, ui, comm, nil); err != nil {
if err := hook.Run(packer.HookProvision, ui, comm, nil); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
+3 -5
View File
@@ -2,13 +2,11 @@ package chroot
import (
"bytes"
"context"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"path/filepath"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepCopyFiles copies some files from the host into the chroot environment.
@@ -20,7 +18,7 @@ type StepCopyFiles struct {
files []string
}
func (s *StepCopyFiles) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
+43 -97
View File
@@ -1,17 +1,14 @@
package chroot
import (
"context"
"errors"
"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/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
)
// StepCreateVolume creates a new volume from the snapshot of the root
@@ -22,78 +19,42 @@ import (
type StepCreateVolume struct {
volumeId string
RootVolumeSize int64
RootVolumeType string
RootVolumeTags awscommon.TagMap
Ctx interpolate.Context
}
func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
image := state.Get("source_image").(*ec2.Image)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
volTags, err := s.RootVolumeTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
if err != nil {
err := fmt.Errorf("Error tagging volumes: %s", err)
// Determine the root device snapshot
log.Printf("Searching for root device of the image (%s)", *image.RootDeviceName)
var rootDevice *ec2.BlockDeviceMapping
for _, device := range image.BlockDeviceMappings {
if *device.DeviceName == *image.RootDeviceName {
rootDevice = device
break
}
}
if rootDevice == nil {
err := fmt.Errorf("Couldn't find root device!")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Collect tags for tagging on resource creation
var tagSpecs []*ec2.TagSpecification
if len(volTags) > 0 {
runVolTags := &ec2.TagSpecification{
ResourceType: aws.String("volume"),
Tags: volTags,
}
tagSpecs = append(tagSpecs, runVolTags)
ui.Say("Creating the root volume...")
vs := *rootDevice.Ebs.VolumeSize
if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize {
vs = s.RootVolumeSize
}
var createVolume *ec2.CreateVolumeInput
if config.FromScratch {
rootVolumeType := ec2.VolumeTypeGp2
if s.RootVolumeType == "io1" {
err := errors.New("Cannot use io1 volume when building from scratch")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
} else if s.RootVolumeType != "" {
rootVolumeType = s.RootVolumeType
}
createVolume = &ec2.CreateVolumeInput{
AvailabilityZone: instance.Placement.AvailabilityZone,
Size: aws.Int64(s.RootVolumeSize),
VolumeType: aws.String(rootVolumeType),
}
} else {
// Determine the root device snapshot
image := state.Get("source_image").(*ec2.Image)
log.Printf("Searching for root device of the image (%s)", *image.RootDeviceName)
var rootDevice *ec2.BlockDeviceMapping
for _, device := range image.BlockDeviceMappings {
if *device.DeviceName == *image.RootDeviceName {
rootDevice = device
break
}
}
ui.Say("Creating the root volume...")
createVolume, err = s.buildCreateVolumeInput(*instance.Placement.AvailabilityZone, rootDevice)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if len(tagSpecs) > 0 {
createVolume.SetTagSpecifications(tagSpecs)
volTags.Report(ui)
createVolume := &ec2.CreateVolumeInput{
AvailabilityZone: instance.Placement.AvailabilityZone,
Size: aws.Int64(vs),
SnapshotId: rootDevice.Ebs.SnapshotId,
VolumeType: rootDevice.Ebs.VolumeType,
Iops: rootDevice.Ebs.Iops,
}
log.Printf("Create args: %+v", createVolume)
@@ -110,7 +71,22 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
log.Printf("Volume ID: %s", s.volumeId)
// Wait for the volume to become ready
err = awscommon.WaitUntilVolumeAvailable(ctx, ec2conn, s.volumeId)
stateChange := awscommon.StateChangeConf{
Pending: []string{"creating"},
StepState: state,
Target: "available",
Refresh: func() (interface{}, string, error) {
resp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesInput{VolumeIds: []*string{&s.volumeId}})
if err != nil {
return nil, "", err
}
v := resp.Volumes[0]
return v, *v.State, nil
},
}
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
@@ -136,33 +112,3 @@ func (s *StepCreateVolume) Cleanup(state multistep.StateBag) {
ui.Error(fmt.Sprintf("Error deleting EBS volume: %s", err))
}
}
func (s *StepCreateVolume) buildCreateVolumeInput(az string, rootDevice *ec2.BlockDeviceMapping) (*ec2.CreateVolumeInput, error) {
if rootDevice == nil {
return nil, fmt.Errorf("Couldn't find root device!")
}
createVolumeInput := &ec2.CreateVolumeInput{
AvailabilityZone: aws.String(az),
Size: rootDevice.Ebs.VolumeSize,
SnapshotId: rootDevice.Ebs.SnapshotId,
VolumeType: rootDevice.Ebs.VolumeType,
Iops: rootDevice.Ebs.Iops,
}
if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize {
createVolumeInput.Size = aws.Int64(s.RootVolumeSize)
}
if s.RootVolumeType == "" || s.RootVolumeType == *rootDevice.Ebs.VolumeType {
return createVolumeInput, nil
}
if s.RootVolumeType == "io1" {
return nil, fmt.Errorf("Root volume type cannot be io1, because existing root volume type was %s", *rootDevice.Ebs.VolumeType)
}
createVolumeInput.VolumeType = aws.String(s.RootVolumeType)
// non io1 cannot set iops
createVolumeInput.Iops = nil
return createVolumeInput, nil
}
@@ -1,74 +0,0 @@
package chroot
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/stretchr/testify/assert"
)
func buildTestRootDevice() *ec2.BlockDeviceMapping {
return &ec2.BlockDeviceMapping{
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(10),
SnapshotId: aws.String("snap-1234"),
VolumeType: aws.String("gp2"),
},
}
}
func TestCreateVolume_Default(t *testing.T) {
stepCreateVolume := new(StepCreateVolume)
_, err := stepCreateVolume.buildCreateVolumeInput("test-az", buildTestRootDevice())
assert.NoError(t, err)
}
func TestCreateVolume_Shrink(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeSize: 1}
testRootDevice := buildTestRootDevice()
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
// Ensure that the new value is equal to the size of the old root device
assert.Equal(t, *ret.Size, *testRootDevice.Ebs.VolumeSize)
}
func TestCreateVolume_Expand(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeSize: 25}
testRootDevice := buildTestRootDevice()
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
// Ensure that the new value is equal to the size of the value passed in
assert.Equal(t, *ret.Size, stepCreateVolume.RootVolumeSize)
}
func TestCreateVolume_io1_to_io1(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeType: "io1"}
testRootDevice := buildTestRootDevice()
testRootDevice.Ebs.VolumeType = aws.String("io1")
testRootDevice.Ebs.Iops = aws.Int64(1000)
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
assert.Equal(t, *ret.VolumeType, stepCreateVolume.RootVolumeType)
assert.Equal(t, *ret.Iops, *testRootDevice.Ebs.Iops)
}
func TestCreateVolume_io1_to_gp2(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeType: "gp2"}
testRootDevice := buildTestRootDevice()
testRootDevice.Ebs.VolumeType = aws.String("io1")
testRootDevice.Ebs.Iops = aws.Int64(1000)
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
assert.Equal(t, *ret.VolumeType, stepCreateVolume.RootVolumeType)
assert.Nil(t, ret.Iops)
}
func TestCreateVolume_gp2_to_io1(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeType: "io1"}
testRootDevice := buildTestRootDevice()
_, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.Error(t, err)
}
+3 -5
View File
@@ -1,19 +1,17 @@
package chroot
import (
"context"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepEarlyCleanup performs some of the cleanup steps early in order to
// prepare for snapshotting and creating an AMI.
type StepEarlyCleanup struct{}
func (s *StepEarlyCleanup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepEarlyCleanup) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
cleanupKeys := []string{
"copy_files_cleanup",
+3 -5
View File
@@ -1,18 +1,16 @@
package chroot
import (
"context"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepEarlyUnflock unlocks the flock.
type StepEarlyUnflock struct{}
func (s *StepEarlyUnflock) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepEarlyUnflock) Run(state multistep.StateBag) multistep.StepAction {
cleanup := state.Get("flock_cleanup").(Cleanup)
ui := state.Get("ui").(packer.Ui)
+3 -5
View File
@@ -1,14 +1,12 @@
package chroot
import (
"context"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"os"
"path/filepath"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepFlock provisions the instance within a chroot.
@@ -19,7 +17,7 @@ type StepFlock struct {
fh *os.File
}
func (s *StepFlock) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepFlock) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
lockfile := "/var/lock/packer-chroot/lock"
+10 -12
View File
@@ -1,31 +1,27 @@
package chroot
import (
"context"
"fmt"
"log"
"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/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
)
// StepInstanceInfo verifies that this builder is running on an EC2 instance.
type StepInstanceInfo struct{}
func (s *StepInstanceInfo) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
session := state.Get("awsSession").(*session.Session)
ui := state.Get("ui").(packer.Ui)
// Get our own instance ID
ui.Say("Gathering information about this EC2 instance...")
ec2meta := ec2metadata.New(session)
identity, err := ec2meta.GetInstanceIdentityDocument()
instanceIdBytes, err := common.GetInstanceMetaData("instance-id")
if err != nil {
log.Printf("Error: %s", err)
err := fmt.Errorf(
"Error retrieving the ID of the instance Packer is running on.\n" +
"Please verify Packer is running on a proper AWS EC2 instance.")
@@ -33,10 +29,12 @@ func (s *StepInstanceInfo) Run(ctx context.Context, state multistep.StateBag) mu
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("Instance ID: %s", identity.InstanceID)
instanceId := string(instanceIdBytes)
log.Printf("Instance ID: %s", instanceId)
// Query the entire instance metadata
instancesResp, err := ec2conn.DescribeInstances(&ec2.DescribeInstancesInput{InstanceIds: []*string{&identity.InstanceID}})
instancesResp, err := ec2conn.DescribeInstances(&ec2.DescribeInstancesInput{InstanceIds: []*string{&instanceId}})
if err != nil {
err := fmt.Errorf("Error getting instance data: %s", err)
state.Put("error", err)
+14 -28
View File
@@ -2,7 +2,6 @@ package chroot
import (
"bytes"
"context"
"fmt"
"log"
"os"
@@ -10,9 +9,9 @@ import (
"strings"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
)
type mountPathData struct {
@@ -26,34 +25,21 @@ type mountPathData struct {
// mount_device_cleanup CleanupFunc - To perform early cleanup
type StepMountDevice struct {
MountOptions []string
MountPartition string
MountPartition int
mountPath string
}
func (s *StepMountDevice) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
image := state.Get("source_image").(*ec2.Image)
device := state.Get("device").(string)
if config.NVMEDevicePath != "" {
// customizable device path for mounting NVME block devices on c5 and m5 HVM
device = config.NVMEDevicePath
}
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
var virtualizationType string
if config.FromScratch || config.AMIVirtType != "" {
virtualizationType = config.AMIVirtType
} else {
image := state.Get("source_image").(*ec2.Image)
virtualizationType = *image.VirtualizationType
log.Printf("Source image virtualization type is: %s", virtualizationType)
}
ictx := config.ctx
ictx.Data = &mountPathData{Device: filepath.Base(device)}
mountPath, err := interpolate.Render(config.MountPath, &ictx)
ctx := config.ctx
ctx.Data = &mountPathData{Device: filepath.Base(device)}
mountPath, err := interpolate.Render(config.MountPath, &ctx)
if err != nil {
err := fmt.Errorf("Error preparing mount directory: %s", err)
@@ -79,17 +65,17 @@ func (s *StepMountDevice) Run(ctx context.Context, state multistep.StateBag) mul
return multistep.ActionHalt
}
log.Printf("Source image virtualization type is: %s", *image.VirtualizationType)
deviceMount := device
if virtualizationType == "hvm" && s.MountPartition != "0" {
deviceMount = fmt.Sprintf("%s%s", device, s.MountPartition)
if *image.VirtualizationType == "hvm" {
deviceMount = fmt.Sprintf("%s%d", device, s.MountPartition)
}
state.Put("deviceMount", deviceMount)
ui.Say("Mounting the root device...")
stderr := new(bytes.Buffer)
// build mount options from mount_options config, useful for nouuid options
// build mount options from mount_options config, usefull for nouuid options
// or other specific device type settings for mount
opts := ""
if len(s.MountOptions) > 0 {
@@ -103,7 +89,7 @@ func (s *StepMountDevice) Run(ctx context.Context, state multistep.StateBag) mul
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("[DEBUG] (step mount) mount command is %s", mountCommand)
cmd := ShellCommand(mountCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
+3 -5
View File
@@ -2,14 +2,12 @@ package chroot
import (
"bytes"
"context"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
"os/exec"
"syscall"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepMountExtra mounts the attached device.
@@ -20,7 +18,7 @@ type StepMountExtra struct {
mounts []string
}
func (s *StepMountExtra) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
@@ -1,47 +0,0 @@
package chroot
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type postMountCommandsData struct {
Device string
MountPath string
}
// StepPostMountCommands allows running arbitrary commands after mounting the
// device, but prior to the bind mount and copy steps.
type StepPostMountCommands struct {
Commands []string
}
func (s *StepPostMountCommands) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
device := state.Get("device").(string)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
if len(s.Commands) == 0 {
return multistep.ActionContinue
}
ictx := config.ctx
ictx.Data = &postMountCommandsData{
Device: device,
MountPath: mountPath,
}
ui.Say("Running post-mount commands...")
if err := RunLocalCommands(s.Commands, wrappedCommand, ictx, ui); err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepPostMountCommands) Cleanup(state multistep.StateBag) {}
@@ -1,41 +0,0 @@
package chroot
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type preMountCommandsData struct {
Device string
}
// StepPreMountCommands sets up the a new block device when building from scratch
type StepPreMountCommands struct {
Commands []string
}
func (s *StepPreMountCommands) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
device := state.Get("device").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
if len(s.Commands) == 0 {
return multistep.ActionContinue
}
ictx := config.ctx
ictx.Data = &preMountCommandsData{Device: device}
ui.Say("Running device setup commands...")
if err := RunLocalCommands(s.Commands, wrappedCommand, ictx, ui); err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepPreMountCommands) Cleanup(state multistep.StateBag) {}
+4 -5
View File
@@ -1,20 +1,19 @@
package chroot
import (
"context"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"os"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepPrepareDevice finds an available device and sets it.
type StepPrepareDevice struct {
mounts []string
}
func (s *StepPrepareDevice) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepPrepareDevice) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
+44 -82
View File
@@ -1,51 +1,58 @@
package chroot
import (
"context"
"fmt"
"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/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
)
// StepRegisterAMI creates the AMI.
type StepRegisterAMI struct {
RootVolumeSize int64
EnableAMIENASupport *bool
EnableAMISriovNetSupport bool
RootVolumeSize int64
}
func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
snapshotID := state.Get("snapshot_id").(string)
image := state.Get("source_image").(*ec2.Image)
snapshotId := state.Get("snapshot_id").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Registering the AMI...")
blockDevices := make([]*ec2.BlockDeviceMapping, len(image.BlockDeviceMappings))
for i, device := range image.BlockDeviceMappings {
newDevice := device
if *newDevice.DeviceName == *image.RootDeviceName {
if newDevice.Ebs != nil {
newDevice.Ebs.SnapshotId = aws.String(snapshotId)
} else {
newDevice.Ebs = &ec2.EbsBlockDevice{SnapshotId: aws.String(snapshotId)}
}
var registerOpts *ec2.RegisterImageInput
if s.RootVolumeSize > *newDevice.Ebs.VolumeSize {
newDevice.Ebs.VolumeSize = aws.Int64(s.RootVolumeSize)
}
}
// Source Image is only required to be passed if the image is not from scratch
if config.FromScratch {
registerOpts = buildBaseRegisterOpts(config, nil, s.RootVolumeSize, snapshotID)
} else {
image := state.Get("source_image").(*ec2.Image)
registerOpts = buildBaseRegisterOpts(config, image, s.RootVolumeSize, snapshotID)
// assume working from a snapshot, so we unset the Encrypted field if set,
// otherwise AWS API will return InvalidParameter
if newDevice.Ebs != nil && newDevice.Ebs.Encrypted != nil {
newDevice.Ebs.Encrypted = nil
}
blockDevices[i] = newDevice
}
if s.EnableAMISriovNetSupport {
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
registerOpts := buildRegisterOpts(config, image, blockDevices)
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
if config.AMIEnhancedNetworking {
registerOpts.SriovNetSupport = aws.String("simple")
}
if s.EnableAMIENASupport != nil && *s.EnableAMIENASupport {
// Set EnaSupport to true
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
registerOpts.EnaSupport = aws.Bool(true)
}
registerResp, err := ec2conn.RegisterImage(registerOpts)
if err != nil {
@@ -60,8 +67,16 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
amis[*ec2conn.Config.Region] = *registerResp.ImageId
state.Put("amis", amis)
// Wait for the image to become ready
stateChange := awscommon.StateChangeConf{
Pending: []string{"pending"},
Target: "available",
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *registerResp.ImageId),
StepState: state,
}
ui.Say("Waiting for AMI to become ready...")
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
if _, err := awscommon.WaitForState(&stateChange); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@@ -73,66 +88,12 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {}
// Builds the base register opts with architecture, name, root block device, mappings, virtualizationtype
func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSize int64, snapshotID string) *ec2.RegisterImageInput {
var (
mappings []*ec2.BlockDeviceMapping
rootDeviceName string
)
generatingNewBlockDeviceMappings := config.FromScratch || len(config.AMIMappings) > 0
if generatingNewBlockDeviceMappings {
mappings = config.AMIBlockDevices.BuildAMIDevices()
rootDeviceName = config.RootDeviceName
} else {
// If config.FromScratch is false, source image must be set
mappings = sourceImage.BlockDeviceMappings
rootDeviceName = *sourceImage.RootDeviceName
}
newMappings := make([]*ec2.BlockDeviceMapping, len(mappings))
for i, device := range mappings {
newDevice := device
if *newDevice.DeviceName == rootDeviceName {
if newDevice.Ebs != nil {
newDevice.Ebs.SnapshotId = aws.String(snapshotID)
} else {
newDevice.Ebs = &ec2.EbsBlockDevice{SnapshotId: aws.String(snapshotID)}
}
if generatingNewBlockDeviceMappings || rootVolumeSize > *newDevice.Ebs.VolumeSize {
newDevice.Ebs.VolumeSize = aws.Int64(rootVolumeSize)
}
}
// assume working from a snapshot, so we unset the Encrypted field if set,
// otherwise AWS API will return InvalidParameter
if newDevice.Ebs != nil && newDevice.Ebs.Encrypted != nil {
newDevice.Ebs.Encrypted = nil
}
newMappings[i] = newDevice
}
if config.FromScratch {
return &ec2.RegisterImageInput{
Name: &config.AMIName,
Architecture: aws.String(config.Architecture),
RootDeviceName: aws.String(rootDeviceName),
VirtualizationType: aws.String(config.AMIVirtType),
BlockDeviceMappings: newMappings,
}
}
return buildRegisterOptsFromExistingImage(config, sourceImage, newMappings, rootDeviceName)
}
func buildRegisterOptsFromExistingImage(config *Config, image *ec2.Image, mappings []*ec2.BlockDeviceMapping, rootDeviceName string) *ec2.RegisterImageInput {
func buildRegisterOpts(config *Config, image *ec2.Image, blockDevices []*ec2.BlockDeviceMapping) *ec2.RegisterImageInput {
registerOpts := &ec2.RegisterImageInput{
Name: &config.AMIName,
Architecture: image.Architecture,
RootDeviceName: &rootDeviceName,
BlockDeviceMappings: mappings,
RootDeviceName: image.RootDeviceName,
BlockDeviceMappings: blockDevices,
VirtualizationType: image.VirtualizationType,
}
@@ -144,5 +105,6 @@ func buildRegisterOptsFromExistingImage(config *Config, image *ec2.Image, mappin
registerOpts.KernelId = image.KernelId
registerOpts.RamdiskId = image.RamdiskId
}
return registerOpts
}
+2 -149
View File
@@ -3,9 +3,6 @@ package chroot
import (
"testing"
amazon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
)
@@ -24,13 +21,12 @@ func TestStepRegisterAmi_buildRegisterOpts_pv(t *testing.T) {
config.AMIName = "test_ami_name"
config.AMIDescription = "test_ami_description"
config.AMIVirtType = "paravirtual"
rootDeviceName := "foo"
image := testImage()
blockDevices := []*ec2.BlockDeviceMapping{}
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName)
opts := buildRegisterOpts(&config, &image, blockDevices)
expected := config.AMIVirtType
if *opts.VirtualizationType != expected {
@@ -47,10 +43,6 @@ func TestStepRegisterAmi_buildRegisterOpts_pv(t *testing.T) {
t.Fatalf("Unexpected KernelId value: expected %s got %s\n", expected, *opts.KernelId)
}
expected = rootDeviceName
if *opts.RootDeviceName != expected {
t.Fatalf("Unexpected RootDeviceName value: expected %s got %s\n", expected, *opts.RootDeviceName)
}
}
func TestStepRegisterAmi_buildRegisterOpts_hvm(t *testing.T) {
@@ -58,13 +50,12 @@ func TestStepRegisterAmi_buildRegisterOpts_hvm(t *testing.T) {
config.AMIName = "test_ami_name"
config.AMIDescription = "test_ami_description"
config.AMIVirtType = "hvm"
rootDeviceName := "foo"
image := testImage()
blockDevices := []*ec2.BlockDeviceMapping{}
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName)
opts := buildRegisterOpts(&config, &image, blockDevices)
expected := config.AMIVirtType
if *opts.VirtualizationType != expected {
@@ -79,142 +70,4 @@ func TestStepRegisterAmi_buildRegisterOpts_hvm(t *testing.T) {
if opts.KernelId != nil {
t.Fatalf("Unexpected KernelId value: expected nil got %s\n", *opts.KernelId)
}
expected = rootDeviceName
if *opts.RootDeviceName != expected {
t.Fatalf("Unexpected RootDeviceName value: expected %s got %s\n", expected, *opts.RootDeviceName)
}
}
func TestStepRegisterAmi_buildRegisterOptsFromScratch(t *testing.T) {
rootDeviceName := "/dev/sda"
snapshotID := "foo"
config := Config{
FromScratch: true,
PackerConfig: common.PackerConfig{},
AMIBlockDevices: amazon.AMIBlockDevices{
AMIMappings: []amazon.BlockDevice{
{
DeviceName: rootDeviceName,
},
},
},
RootDeviceName: rootDeviceName,
}
registerOpts := buildBaseRegisterOpts(&config, nil, 10, snapshotID)
if len(registerOpts.BlockDeviceMappings) != 1 {
t.Fatal("Expected block device mapping of length 1")
}
if *registerOpts.BlockDeviceMappings[0].Ebs.SnapshotId != snapshotID {
t.Fatalf("Snapshot ID of root disk not set to snapshot id %s", rootDeviceName)
}
}
func TestStepRegisterAmi_buildRegisterOptFromExistingImage(t *testing.T) {
rootDeviceName := "/dev/sda"
snapshotID := "foo"
config := Config{
FromScratch: false,
PackerConfig: common.PackerConfig{},
}
sourceImage := ec2.Image{
RootDeviceName: &rootDeviceName,
BlockDeviceMappings: []*ec2.BlockDeviceMapping{
{
DeviceName: &rootDeviceName,
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(10),
},
},
// Throw in an ephemeral device, it seems like all devices in the return struct in a source AMI have
// a size, even if it's for ephemeral
{
DeviceName: aws.String("/dev/sdb"),
VirtualName: aws.String("ephemeral0"),
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(0),
},
},
},
}
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotID)
if len(registerOpts.BlockDeviceMappings) != 2 {
t.Fatal("Expected block device mapping of length 2")
}
for _, dev := range registerOpts.BlockDeviceMappings {
if dev.Ebs.SnapshotId != nil && *dev.Ebs.SnapshotId == snapshotID {
// Even though root volume size is in config, it isn't used, instead we use the root volume size
// that's derived when we build the step
if *dev.Ebs.VolumeSize != 15 {
t.Fatalf("Root volume size not 15 GB instead %d", *dev.Ebs.VolumeSize)
}
return
}
}
t.Fatalf("Could not find device with snapshot ID %s", snapshotID)
}
func TestStepRegisterAmi_buildRegisterOptFromExistingImageWithBlockDeviceMappings(t *testing.T) {
const (
rootDeviceName = "/dev/xvda"
oldRootDevice = "/dev/sda1"
)
snapshotId := "foo"
config := Config{
FromScratch: false,
PackerConfig: common.PackerConfig{},
AMIBlockDevices: amazon.AMIBlockDevices{
AMIMappings: []amazon.BlockDevice{
{
DeviceName: rootDeviceName,
},
},
},
RootDeviceName: rootDeviceName,
}
// Intentionally try to use a different root devicename
sourceImage := ec2.Image{
RootDeviceName: aws.String(oldRootDevice),
BlockDeviceMappings: []*ec2.BlockDeviceMapping{
{
DeviceName: aws.String(oldRootDevice),
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(10),
},
},
// Throw in an ephemeral device, it seems like all devices in the return struct in a source AMI have
// a size, even if it's for ephemeral
{
DeviceName: aws.String("/dev/sdb"),
VirtualName: aws.String("ephemeral0"),
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(0),
},
},
},
}
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotId)
if len(registerOpts.BlockDeviceMappings) != 1 {
t.Fatal("Expected block device mapping of length 1")
}
if *registerOpts.BlockDeviceMappings[0].Ebs.SnapshotId != snapshotId {
t.Fatalf("Snapshot ID of root disk set to '%s' expected '%s'", *registerOpts.BlockDeviceMappings[0].Ebs.SnapshotId, rootDeviceName)
}
if *registerOpts.RootDeviceName != rootDeviceName {
t.Fatalf("Root device set to '%s' expected %s", *registerOpts.RootDeviceName, rootDeviceName)
}
if *registerOpts.BlockDeviceMappings[0].Ebs.VolumeSize != 15 {
t.Fatalf("Size of root disk not set to 15 GB, instead %d", *registerOpts.BlockDeviceMappings[0].Ebs.VolumeSize)
}
}
+25 -12
View File
@@ -1,14 +1,14 @@
package chroot
import (
"context"
"errors"
"fmt"
"time"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
)
// StepSnapshot creates a snapshot of the created volume.
@@ -19,7 +19,7 @@ type StepSnapshot struct {
snapshotId string
}
func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
volumeId := state.Get("volume_id").(string)
@@ -43,7 +43,26 @@ func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId))
// Wait for the snapshot to be ready
err = awscommon.WaitUntilSnapshotDone(ctx, ec2conn, s.snapshotId)
stateChange := awscommon.StateChangeConf{
Pending: []string{"pending"},
StepState: state,
Target: "completed",
Refresh: func() (interface{}, string, error) {
resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{SnapshotIds: []*string{&s.snapshotId}})
if err != nil {
return nil, "", err
}
if len(resp.Snapshots) == 0 {
return nil, "", errors.New("No snapshots found.")
}
s := resp.Snapshots[0]
return s, *s.State, nil
},
}
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for snapshot: %s", err)
state.Put("error", err)
@@ -52,12 +71,6 @@ func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
}
state.Put("snapshot_id", s.snapshotId)
snapshots := map[string][]string{
*ec2conn.Config.Region: {s.snapshotId},
}
state.Put("snapshots", snapshots)
return multistep.ActionContinue
}
+80 -179
View File
@@ -1,216 +1,117 @@
package common
import (
"crypto/tls"
"fmt"
"log"
"io/ioutil"
"net/http"
"strings"
"unicode"
"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/credentials/ec2rolecreds"
"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"
"github.com/hashicorp/packer/template/interpolate"
vaultapi "github.com/hashicorp/vault/api"
"github.com/mitchellh/packer/template/interpolate"
)
type VaultAWSEngineOptions struct {
Name string `mapstructure:"name"`
RoleARN string `mapstructure:"role_arn"`
TTL string `mapstructure:"ttl"`
EngineName string `mapstructure:"engine_name"`
}
func (v *VaultAWSEngineOptions) Empty() bool {
return len(v.Name) == 0 && len(v.RoleARN) == 0 &&
len(v.EngineName) == 0 && len(v.TTL) == 0
}
// AccessConfig is for common configuration related to AWS access
type AccessConfig struct {
AccessKey string `mapstructure:"access_key"`
CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2"`
DecodeAuthZMessages bool `mapstructure:"decode_authorization_messages"`
InsecureSkipTLSVerify bool `mapstructure:"insecure_skip_tls_verify"`
MFACode string `mapstructure:"mfa_code"`
ProfileName string `mapstructure:"profile"`
RawRegion string `mapstructure:"region"`
SecretKey string `mapstructure:"secret_key"`
SkipValidation bool `mapstructure:"skip_region_validation"`
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
Token string `mapstructure:"token"`
session *session.Session
VaultAWSEngine VaultAWSEngineOptions `mapstructure:"vault_aws_engine"`
getEC2Connection func() ec2iface.EC2API
AccessKey string `mapstructure:"access_key"`
SecretKey string `mapstructure:"secret_key"`
RawRegion string `mapstructure:"region"`
Token string `mapstructure:"token"`
ProfileName string `mapstructure:"profile"`
}
// Config returns a valid aws.Config object for access to AWS services, or
// an error if the authentication and region couldn't be resolved
func (c *AccessConfig) Session() (*session.Session, error) {
if c.session != nil {
return c.session, nil
}
func (c *AccessConfig) Config() (*aws.Config, error) {
var creds *credentials.Credentials
config := aws.NewConfig().WithCredentialsChainVerboseErrors(true)
staticCreds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
if _, err := staticCreds.Get(); err != credentials.ErrStaticCredentialsEmpty {
config.WithCredentials(staticCreds)
}
if c.RawRegion != "" {
config = config.WithRegion(c.RawRegion)
}
if c.CustomEndpointEc2 != "" {
config = config.WithEndpoint(c.CustomEndpointEc2)
}
config = config.WithHTTPClient(cleanhttp.DefaultClient())
transport := config.HTTPClient.Transport.(*http.Transport)
if c.InsecureSkipTLSVerify {
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
transport.Proxy = http.ProxyFromEnvironment
opts := session.Options{
SharedConfigState: session.SharedConfigEnable,
Config: *config,
}
if c.ProfileName != "" {
opts.Profile = c.ProfileName
}
if c.MFACode != "" {
opts.AssumeRoleTokenProvider = func() (string, error) {
return c.MFACode, nil
}
}
sess, err := session.NewSessionWithOptions(opts)
region, err := c.Region()
if err != nil {
return nil, err
}
log.Printf("Found region %s", *sess.Config.Region)
c.session = sess
config := aws.NewConfig().WithRegion(region).WithMaxRetries(11)
if c.ProfileName != "" {
profile, err := NewFromProfile(c.ProfileName)
if err != nil {
return nil, err
}
creds, err = profile.CredentialsFromProfile(config)
if err != nil {
return nil, err
}
} else {
sess := session.New(config)
creds = credentials.NewChainCredentials([]credentials.Provider{
&credentials.StaticProvider{Value: credentials.Value{
AccessKeyID: c.AccessKey,
SecretAccessKey: c.SecretKey,
SessionToken: c.Token,
}},
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
&ec2rolecreds.EC2RoleProvider{
Client: ec2metadata.New(sess),
},
})
}
return config.WithCredentials(creds), nil
}
cp, err := c.session.Config.Credentials.Get()
// Region returns the aws.Region object for access to AWS services, requesting
// the region from the instance metadata if possible.
func (c *AccessConfig) Region() (string, error) {
if c.RawRegion != "" {
if valid := ValidateRegion(c.RawRegion); valid == false {
return "", fmt.Errorf("Not a valid region: %s", c.RawRegion)
}
return c.RawRegion, nil
}
md, err := GetInstanceMetaData("placement/availability-zone")
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)
return "", err
}
region := strings.TrimRightFunc(string(md), unicode.IsLetter)
return region, nil
}
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if c.RawRegion != "" {
if valid := ValidateRegion(c.RawRegion); valid == false {
errs = append(errs, fmt.Errorf("Unknown region: %s", c.RawRegion))
}
}
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
if c.DecodeAuthZMessages {
DecodeAuthZMessages(c.session)
}
LogEnvOverrideWarnings()
return c.session, nil
}
func (c *AccessConfig) SessionRegion() string {
if c.session == nil {
panic("access config session should be set.")
}
return aws.StringValue(c.session.Config.Region)
}
func (c *AccessConfig) IsGovCloud() bool {
return strings.HasPrefix(c.SessionRegion(), "us-gov-")
}
func (c *AccessConfig) IsChinaCloud() bool {
return strings.HasPrefix(c.SessionRegion(), "cn-")
}
func (c *AccessConfig) GetCredsFromVault() error {
// const EnvVaultAddress = "VAULT_ADDR"
// const EnvVaultToken = "VAULT_TOKEN"
vaultConfig := vaultapi.DefaultConfig()
cli, err := vaultapi.NewClient(vaultConfig)
if err != nil {
return fmt.Errorf("Error getting Vault client: %s", err)
}
if c.VaultAWSEngine.EngineName == "" {
c.VaultAWSEngine.EngineName = "aws"
}
path := fmt.Sprintf("/%s/creds/%s", c.VaultAWSEngine.EngineName,
c.VaultAWSEngine.Name)
secret, err := cli.Logical().Read(path)
if err != nil {
return fmt.Errorf("Error reading vault secret: %s", err)
}
if secret == nil {
return fmt.Errorf("Vault Secret does not exist at the given path.")
}
c.AccessKey = secret.Data["access_key"].(string)
c.SecretKey = secret.Data["secret_key"].(string)
token := secret.Data["security_token"]
if token != nil {
c.Token = token.(string)
} else {
c.Token = ""
if len(errs) > 0 {
return errs
}
return nil
}
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
func GetInstanceMetaData(path string) (contents []byte, err error) {
url := "http://169.254.169.254/latest/meta-data/" + path
if c.SkipMetadataApiCheck {
log.Println("(WARN) skip_metadata_api_check ignored.")
}
// Make sure it's obvious from the config how we're getting credentials:
// Vault, Packer config, or environemnt.
if !c.VaultAWSEngine.Empty() {
if len(c.AccessKey) > 0 {
errs = append(errs,
fmt.Errorf("If you have set vault_aws_engine, you must not set"+
" the access_key or secret_key."))
}
// Go ahead and grab those credentials from Vault now, so we can set
// the keys and token now.
err := c.GetCredsFromVault()
if err != nil {
errs = append(errs, err)
}
}
if (len(c.AccessKey) > 0) != (len(c.SecretKey) > 0) {
errs = append(errs,
fmt.Errorf("`access_key` and `secret_key` must both be either set or not set."))
}
return errs
}
func (c *AccessConfig) NewEC2Connection() (ec2iface.EC2API, error) {
if c.getEC2Connection != nil {
return c.getEC2Connection(), nil
}
sess, err := c.Session()
resp, err := http.Get(url)
if err != nil {
return nil, err
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
err = fmt.Errorf("Code %d returned for url %s", resp.StatusCode, url)
return
}
ec2conn := ec2.New(sess)
return ec2conn, nil
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
return []byte(body), err
}
+6 -49
View File
@@ -2,69 +2,26 @@ package common
import (
"testing"
"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)
}
}
func TestAccessConfigPrepare_RegionRestricted(t *testing.T) {
c := testAccessConfig()
// Create a Session with a custom region
c.session = session.Must(session.NewSession(&aws.Config{
Region: aws.String("us-gov-west-1"),
}))
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)
}
if !c.IsGovCloud() {
t.Fatal("We should be in gov region.")
}
}
+20 -140
View File
@@ -2,128 +2,30 @@ package common
import (
"fmt"
"log"
"regexp"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/packer/template/interpolate"
)
// AMIConfig is for common configuration related to creating AMIs.
type AMIConfig struct {
AMIName string `mapstructure:"ami_name"`
AMIDescription string `mapstructure:"ami_description"`
AMIVirtType string `mapstructure:"ami_virtualization_type"`
AMIUsers []string `mapstructure:"ami_users"`
AMIGroups []string `mapstructure:"ami_groups"`
AMIProductCodes []string `mapstructure:"ami_product_codes"`
AMIRegions []string `mapstructure:"ami_regions"`
AMISkipRegionValidation bool `mapstructure:"skip_region_validation"`
AMITags TagMap `mapstructure:"tags"`
AMIENASupport *bool `mapstructure:"ena_support"`
AMISriovNetSupport bool `mapstructure:"sriov_support"`
AMIForceDeregister bool `mapstructure:"force_deregister"`
AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot"`
AMIEncryptBootVolume *bool `mapstructure:"encrypt_boot"`
AMIKmsKeyId string `mapstructure:"kms_key_id"`
AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids"`
SnapshotTags TagMap `mapstructure:"snapshot_tags"`
SnapshotUsers []string `mapstructure:"snapshot_users"`
SnapshotGroups []string `mapstructure:"snapshot_groups"`
AMISkipBuildRegion bool `mapstructure:"skip_save_build_region"`
AMIName string `mapstructure:"ami_name"`
AMIDescription string `mapstructure:"ami_description"`
AMIVirtType string `mapstructure:"ami_virtualization_type"`
AMIUsers []string `mapstructure:"ami_users"`
AMIGroups []string `mapstructure:"ami_groups"`
AMIProductCodes []string `mapstructure:"ami_product_codes"`
AMIRegions []string `mapstructure:"ami_regions"`
AMITags map[string]string `mapstructure:"tags"`
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
AMIForceDeregister bool `mapstructure:"force_deregister"`
}
func stringInSlice(s []string, searchstr string) bool {
for _, item := range s {
if item == searchstr {
return true
}
}
return false
}
func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context) []error {
func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if c.AMIName == "" {
errs = append(errs, fmt.Errorf("ami_name must be specified"))
}
// Make sure that if we have region_kms_key_ids defined,
// the regions in region_kms_key_ids are also in ami_regions
if len(c.AMIRegionKMSKeyIDs) > 0 {
for kmsKeyRegion := range c.AMIRegionKMSKeyIDs {
if !stringInSlice(c.AMIRegions, kmsKeyRegion) {
errs = append(errs, fmt.Errorf("Region %s is in region_kms_key_ids but not in ami_regions", kmsKeyRegion))
}
}
}
errs = append(errs, c.prepareRegions(accessConfig)...)
// Prevent sharing of default KMS key encrypted volumes with other aws users
if len(c.AMIUsers) > 0 {
if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume != nil && *c.AMIEncryptBootVolume {
errs = append(errs, fmt.Errorf("Cannot share AMI encrypted with default KMS key"))
}
if len(c.AMIRegionKMSKeyIDs) > 0 {
for _, kmsKey := range c.AMIRegionKMSKeyIDs {
if len(kmsKey) == 0 {
errs = append(errs, fmt.Errorf("Cannot share AMI encrypted with default KMS key for other regions"))
}
}
}
}
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 != nil && *c.AMIEncryptBootVolume {
errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
}
if len(c.AMIRegionKMSKeyIDs) > 0 {
for _, kmsKey := range c.AMIRegionKMSKeyIDs {
if len(kmsKey) == 0 {
errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
}
}
}
}
if len(c.AMIName) < 3 || len(c.AMIName) > 128 {
errs = append(errs, fmt.Errorf("ami_name must be between 3 and 128 characters long"))
}
if c.AMIName != templateCleanAMIName(c.AMIName) {
errs = append(errs, fmt.Errorf("AMIName should only contain "+
"alphanumeric characters, parentheses (()), square brackets ([]), spaces "+
"( ), periods (.), slashes (/), dashes (-), single quotes ('), at-signs "+
"(@), or underscores(_). You can use the `clean_resource_name` template "+
"filter to automatically clean your ami name."))
}
if len(errs) > 0 {
return errs
}
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))
@@ -137,43 +39,21 @@ func (c *AMIConfig) prepareRegions(accessConfig *AccessConfig) (errs []error) {
// 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)
// Verify the region is real
if valid := ValidateRegion(region); valid == false {
errs = append(errs, fmt.Errorf("Unknown region: %s", 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(-us-gov)?:kms:([a-z]{2}-(gov-)?[a-z]+-\d{1})?:(\d{12}):`
if regexp.MustCompile(fmt.Sprintf("^%s", kmsKeyIdPattern)).MatchString(kmsKey) {
return true
if len(errs) > 0 {
return errs
}
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
return nil
}
+13 -210
View File
@@ -1,13 +1,8 @@
package common
import (
"fmt"
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
)
func testAMIConfig() *AMIConfig {
@@ -16,234 +11,42 @@ func testAMIConfig() *AMIConfig {
}
}
func getFakeAccessConfig(region string) *AccessConfig {
c := testAccessConfig()
c.RawRegion = region
return c
}
func TestAMIConfigPrepare_name(t *testing.T) {
c := testAMIConfig()
accessConf := testAccessConfig()
if err := c.Prepare(accessConf, nil); err != nil {
if err := c.Prepare(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); 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); 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); 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); 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); err != nil {
t.Fatalf("bad: %s", err)
}
expected := []string{"us-east-1", "us-west-1"}
if !reflect.DeepEqual(c.AMIRegions, expected) {
t.Fatalf("bad: %#v", c.AMIRegions)
}
c.AMIRegions = []string{"custom"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("shouldn't have error")
}
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"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]))
}
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have passed; we are able to use default KMS key if not sharing")
}
c.SnapshotUsers = []string{"user-foo", "user-bar"}
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have an error b/c can't use default KMS key if sharing")
}
c.AMIRegions = []string{"us-east-1", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "456-789-0123",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have error b/c theres a region in the key map that isn't in ami_regions")
}
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
}
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
}
c.SnapshotUsers = []string{"foo", "bar"}
c.AMIKmsKeyId = "123-abc-456"
c.AMIEncryptBootVolume = &[]bool{true}[0]
c.AMIRegions = []string{"us-east-1", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
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")
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
c.AMIRegionKMSKeyIDs = nil
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should allow user to have the raw region in ami_regions")
}
}
func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
c := testAMIConfig()
c.AMIUsers = []string{"testAccountID"}
c.AMIEncryptBootVolume = &[]bool{true}[0]
accessConf := testAccessConfig()
c.AMIKmsKeyId = ""
if err := c.Prepare(accessConf, 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 {
t.Fatal("should be able to share ami with encrypted boot volume")
}
}
func TestAMIConfigPrepare_ValidateKmsKey(t *testing.T) {
c := testAMIConfig()
c.AMIEncryptBootVolume = aws.Bool(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",
"arn:aws-us-gov:kms:us-gov-east-1:123456789012:key/12345678-1234-abcd-0000-123456789012",
}
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",
"arn:foo:kms:us-east-1:012345678910:key/abcd1234-a123-456a-a12b-a123b4cd56ef",
}
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 {
t.Fatal("shouldn't be able to have an ami name with less than 3 characters")
}
var longAmiName string
for i := 0; i < 129; i++ {
longAmiName += "a"
}
c.AMIName = longAmiName
if err := c.Prepare(accessConf, 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 {
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 {
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 {
t.Fatalf("expected `xyz-base-2017-04-05-1934` to pass validation.")
}
}
+8 -18
View File
@@ -9,7 +9,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/packer/packer"
)
// Artifact is an artifact implementation that contains built AMIs.
@@ -21,7 +21,7 @@ type Artifact struct {
BuilderIdValue string
// EC2 connection for performing API stuff.
Session *session.Session
Conn *ec2.EC2
}
func (a *Artifact) BuilderId() string {
@@ -51,7 +51,7 @@ func (a *Artifact) String() string {
}
sort.Strings(amiStrings)
return fmt.Sprintf("AMIs were created:\n%s\n", strings.Join(amiStrings, "\n"))
return fmt.Sprintf("AMIs were created:\n\n%s", strings.Join(amiStrings, "\n"))
}
func (a *Artifact) State(name string) interface{} {
@@ -69,23 +69,13 @@ func (a *Artifact) Destroy() error {
for region, imageId := range a.Amis {
log.Printf("Deregistering image ID (%s) from region (%s)", imageId, region)
regionConn := ec2.New(a.Session, &aws.Config{
Region: aws.String(region),
})
// Get image metadata
imageResp, err := regionConn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: []*string{&imageId},
})
if err != nil {
errors = append(errors, err)
}
if len(imageResp.Images) == 0 {
err := fmt.Errorf("Error retrieving details for AMI (%s), no images found", imageId)
errors = append(errors, err)
regionConfig := &aws.Config{
Credentials: a.Conn.Config.Credentials,
Region: aws.String(region),
}
sess := session.New(regionConfig)
regionConn := ec2.New(sess)
// Deregister ami
input := &ec2.DeregisterImageInput{
ImageId: &imageId,
}
+3 -3
View File
@@ -4,7 +4,7 @@ import (
"reflect"
"testing"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/packer/packer"
)
func TestArtifact_Impl(t *testing.T) {
@@ -48,9 +48,9 @@ func TestArtifactState_atlasMetadata(t *testing.T) {
func TestArtifactString(t *testing.T) {
expected := `AMIs were created:
east: foo
west: bar
`
west: bar`
amis := make(map[string]string)
amis["east"] = "foo"
+8 -57
View File
@@ -1,40 +1,28 @@
package common
import (
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/packer/template/interpolate"
)
// BlockDevice
type BlockDevice struct {
DeleteOnTermination bool `mapstructure:"delete_on_termination"`
DeviceName string `mapstructure:"device_name"`
Encrypted *bool `mapstructure:"encrypted"`
Encrypted bool `mapstructure:"encrypted"`
IOPS int64 `mapstructure:"iops"`
NoDevice bool `mapstructure:"no_device"`
SnapshotId string `mapstructure:"snapshot_id"`
VirtualName string `mapstructure:"virtual_name"`
VolumeType string `mapstructure:"volume_type"`
VolumeSize int64 `mapstructure:"volume_size"`
KmsKeyId string `mapstructure:"kms_key_id"`
// ebssurrogate only
OmitFromArtifact bool `mapstructure:"omit_from_artifact"`
}
type BlockDevices struct {
AMIBlockDevices `mapstructure:",squash"`
LaunchBlockDevices `mapstructure:",squash"`
}
type AMIBlockDevices struct {
AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings"`
}
type LaunchBlockDevices struct {
AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings"`
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings"`
}
@@ -73,11 +61,8 @@ func buildBlockDevices(b []BlockDevice) []*ec2.BlockDeviceMapping {
// You cannot specify Encrypted if you specify a Snapshot ID
if blockDevice.SnapshotId != "" {
ebsBlockDevice.SnapshotId = aws.String(blockDevice.SnapshotId)
}
ebsBlockDevice.Encrypted = blockDevice.Encrypted
if blockDevice.KmsKeyId != "" {
ebsBlockDevice.KmsKeyId = aws.String(blockDevice.KmsKeyId)
} else if blockDevice.Encrypted {
ebsBlockDevice.Encrypted = aws.Bool(blockDevice.Encrypted)
}
mapping.Ebs = ebsBlockDevice
@@ -88,48 +73,14 @@ func buildBlockDevices(b []BlockDevice) []*ec2.BlockDeviceMapping {
return blockDevices
}
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 != nil && *b.Encrypted == false {
return fmt.Errorf("The device %v, must also have `encrypted: "+
"true` when setting a kms_key_id.", b.DeviceName)
}
func (b *BlockDevices) Prepare(ctx *interpolate.Context) []error {
return nil
}
func (b *BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
for _, d := range b.AMIMappings {
if err := d.Prepare(ctx); err != nil {
errs = append(errs, fmt.Errorf("AMIMapping: %s", err.Error()))
}
}
for _, d := range b.LaunchMappings {
if err := d.Prepare(ctx); err != nil {
errs = append(errs, fmt.Errorf("LaunchMapping: %s", err.Error()))
}
}
return errs
}
func (b *AMIBlockDevices) BuildAMIDevices() []*ec2.BlockDeviceMapping {
func (b *BlockDevices) BuildAMIDevices() []*ec2.BlockDeviceMapping {
return buildBlockDevices(b.AMIMappings)
}
func (b *LaunchBlockDevices) BuildLaunchDevices() []*ec2.BlockDeviceMapping {
func (b *BlockDevices) BuildLaunchDevices() []*ec2.BlockDeviceMapping {
return buildBlockDevices(b.LaunchMappings)
}
func (b *LaunchBlockDevices) GetOmissions() map[string]bool {
omitMap := make(map[string]bool)
for _, blockDevice := range b.LaunchMappings {
omitMap[blockDevice.DeviceName] = blockDevice.OmitFromArtifact
}
return omitMap
}
+9 -34
View File
@@ -71,7 +71,7 @@ func TestBlockDevice(t *testing.T) {
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnTermination: true,
Encrypted: aws.Bool(true),
Encrypted: true,
},
Result: &ec2.BlockDeviceMapping{
@@ -84,27 +84,6 @@ func TestBlockDevice(t *testing.T) {
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnTermination: true,
Encrypted: aws.Bool(true),
KmsKeyId: "2Fa48a521f-3aff-4b34-a159-376ac5d37812",
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("gp2"),
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(true),
Encrypted: aws.Bool(true),
KmsKeyId: aws.String("2Fa48a521f-3aff-4b34-a159-376ac5d37812"),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
@@ -145,26 +124,22 @@ func TestBlockDevice(t *testing.T) {
}
for _, tc := range cases {
amiBlockDevices := AMIBlockDevices{
AMIMappings: []BlockDevice{*tc.Config},
}
launchBlockDevices := LaunchBlockDevices{
blockDevices := BlockDevices{
AMIMappings: []BlockDevice{*tc.Config},
LaunchMappings: []BlockDevice{*tc.Config},
}
expected := []*ec2.BlockDeviceMapping{tc.Result}
amiResults := amiBlockDevices.BuildAMIDevices()
if !reflect.DeepEqual(expected, amiResults) {
got := blockDevices.BuildAMIDevices()
if !reflect.DeepEqual(expected, got) {
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
expected, amiResults)
expected, got)
}
launchResults := launchBlockDevices.BuildLaunchDevices()
if !reflect.DeepEqual(expected, launchResults) {
if !reflect.DeepEqual(expected, blockDevices.BuildLaunchDevices()) {
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
expected, launchResults)
expected,
blockDevices.BuildLaunchDevices())
}
}
}
-17
View File
@@ -1,17 +0,0 @@
package common
import (
"github.com/aws/aws-sdk-go/service/ec2"
)
// Build a slice of EC2 (AMI/Subnet/VPC) filter options from the filters provided.
func buildEc2Filters(input map[*string]*string) []*ec2.Filter {
var filters []*ec2.Filter
for k, v := range input {
filters = append(filters, &ec2.Filter{
Name: k,
Values: []*string{v},
})
}
return filters
}
+155
View File
@@ -0,0 +1,155 @@
package common
import (
"fmt"
"os"
"path"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/go-ini/ini"
"github.com/mitchellh/go-homedir"
)
type CLIConfig struct {
ProfileName string
SourceProfile string
AssumeRoleInput *sts.AssumeRoleInput
SourceCredentials *credentials.Credentials
profileCfg *ini.Section
profileCred *ini.Section
}
// Return a new CLIConfig with stored profile settings
func NewFromProfile(name string) (*CLIConfig, error) {
c := &CLIConfig{}
c.AssumeRoleInput = new(sts.AssumeRoleInput)
err := c.Prepare(name)
if err != nil {
return nil, err
}
sessName, err := c.getSessionName(c.profileCfg.Key("role_session_name").Value())
if err != nil {
return nil, err
}
c.AssumeRoleInput.RoleSessionName = aws.String(sessName)
arn := c.profileCfg.Key("role_arn").Value()
if arn != "" {
c.AssumeRoleInput.RoleArn = aws.String(arn)
}
id := c.profileCfg.Key("external_id").Value()
if id != "" {
c.AssumeRoleInput.ExternalId = aws.String(id)
}
c.SourceCredentials = credentials.NewStaticCredentials(
c.profileCred.Key("aws_access_key_id").Value(),
c.profileCred.Key("aws_secret_access_key").Value(),
c.profileCred.Key("aws_session_token").Value(),
)
return c, nil
}
// Return AWS Credentials using current profile. Must supply source config.
func (c *CLIConfig) CredentialsFromProfile(conf *aws.Config) (*credentials.Credentials, error) {
// If the profile name is equal to the source profile, there is no role to assume so return
// the source credentials as they were captured.
if c.ProfileName == c.SourceProfile {
return c.SourceCredentials, nil
}
srcCfg := aws.NewConfig().Copy(conf).WithCredentials(c.SourceCredentials)
svc := sts.New(session.New(), srcCfg)
res, err := svc.AssumeRole(c.AssumeRoleInput)
if err != nil {
return nil, err
}
return credentials.NewStaticCredentials(
*res.Credentials.AccessKeyId,
*res.Credentials.SecretAccessKey,
*res.Credentials.SessionToken,
), nil
}
// Sets params in the struct based on the file section
func (c *CLIConfig) Prepare(name string) error {
var err error
c.ProfileName = name
c.profileCfg, err = configFromName(c.ProfileName)
if err != nil {
return err
}
c.SourceProfile = c.profileCfg.Key("source_profile").Value()
if c.SourceProfile == "" {
c.SourceProfile = c.ProfileName
}
c.profileCred, err = credsFromName(c.SourceProfile)
if err != nil {
return err
}
return nil
}
func (c *CLIConfig) getSessionName(rawName string) (string, error) {
if rawName == "" {
name := "packer-"
host, err := os.Hostname()
if err != nil {
return name, err
}
return fmt.Sprintf("%s%s", name, host), nil
} else {
return rawName, nil
}
}
func configFromName(name string) (*ini.Section, error) {
filePath := os.Getenv("AWS_CONFIG_FILE")
if filePath == "" {
home, err := homedir.Dir()
if err != nil {
return nil, err
}
filePath = path.Join(home, ".aws", "config")
}
file, err := readFile(filePath)
if err != nil {
return nil, err
}
profileName := fmt.Sprintf("profile %s", name)
cfg, err := file.GetSection(profileName)
if err != nil {
return nil, err
}
return cfg, nil
}
func credsFromName(name string) (*ini.Section, error) {
filePath := os.Getenv("AWS_SHARED_CREDENTIALS_FILE")
if filePath == "" {
home, err := homedir.Dir()
if err != nil {
return nil, err
}
filePath = path.Join(home, ".aws", "credentials")
}
file, err := readFile(filePath)
if err != nil {
return nil, err
}
cfg, err := file.GetSection(name)
if err != nil {
return nil, err
}
return cfg, nil
}
func readFile(path string) (*ini.File, error) {
cfg, err := ini.Load(path)
if err != nil {
return nil, err
}
return cfg, nil
}
+118
View File
@@ -0,0 +1,118 @@
package common
import (
"io/ioutil"
"os"
"path"
"strconv"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
)
func init() {
os.Setenv("AWS_ACCESS_KEY_ID", "")
os.Setenv("AWS_ACCESS_KEY", "")
os.Setenv("AWS_SECRET_ACCESS_KEY", "")
os.Setenv("AWS_SECRET_KEY", "")
os.Setenv("AWS_CONFIG_FILE", "")
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "")
}
func testCLIConfig() *CLIConfig {
return &CLIConfig{}
}
func TestCLIConfigNewFromProfile(t *testing.T) {
tmpDir := mockConfig(t)
c, err := NewFromProfile("testing2")
if err != nil {
t.Error(err)
}
if c.AssumeRoleInput.RoleArn != nil {
t.Errorf("RoleArn should be nil. Instead %p", c.AssumeRoleInput.RoleArn)
}
if c.AssumeRoleInput.ExternalId != nil {
t.Errorf("ExternalId should be nil. Instead %p", c.AssumeRoleInput.ExternalId)
}
mockConfigClose(t, tmpDir)
}
func TestAssumeRole(t *testing.T) {
tmpDir := mockConfig(t)
c, err := NewFromProfile("testing1")
if err != nil {
t.Error(err)
}
// Role
e := "arn:aws:iam::123456789011:role/rolename"
a := *c.AssumeRoleInput.RoleArn
if e != a {
t.Errorf("RoleArn value should be %s. Instead %s", e, a)
}
// Session
a = *c.AssumeRoleInput.RoleSessionName
e = "testsession"
if e != a {
t.Errorf("RoleSessionName value should be %s. Instead %s", e, a)
}
config := aws.NewConfig()
_, err = c.CredentialsFromProfile(config)
if err == nil {
t.Error("Should have errored")
}
mockConfigClose(t, tmpDir)
}
func mockConfig(t *testing.T) string {
time := time.Now().UnixNano()
dir, err := ioutil.TempDir("", strconv.FormatInt(time, 10))
if err != nil {
t.Error(err)
}
cfg := []byte(`[profile testing1]
region=us-west-2
source_profile=testingcredentials
role_arn = arn:aws:iam::123456789011:role/rolename
role_session_name = testsession
[profile testing2]
region=us-west-2
`)
cfgFile := path.Join(dir, "config")
err = ioutil.WriteFile(cfgFile, cfg, 0644)
if err != nil {
t.Error(err)
}
os.Setenv("AWS_CONFIG_FILE", cfgFile)
crd := []byte(`[testingcredentials]
aws_access_key_id = foo
aws_secret_access_key = bar
[testing2]
aws_access_key_id = baz
aws_secret_access_key = qux
`)
crdFile := path.Join(dir, "credentials")
err = ioutil.WriteFile(crdFile, crd, 0644)
if err != nil {
t.Error(err)
}
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", crdFile)
return dir
}
func mockConfigClose(t *testing.T, dir string) {
err := os.RemoveAll(dir)
if err != nil {
t.Error(err)
}
}
-58
View File
@@ -1,58 +0,0 @@
package common
import (
"fmt"
"log"
"regexp"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
)
var encodedFailureMessagePattern = regexp.MustCompile(`(?i)(.*) Encoded authorization failure message: ([\w-]+) ?( .*)?`)
type stsDecoder interface {
DecodeAuthorizationMessage(input *sts.DecodeAuthorizationMessageInput) (*sts.DecodeAuthorizationMessageOutput, error)
}
// decodeError replaces encoded authorization messages with the
// decoded results
func decodeAWSError(decoder stsDecoder, err error) error {
groups := encodedFailureMessagePattern.FindStringSubmatch(err.Error())
if groups != nil && len(groups) > 1 {
result, decodeErr := decoder.DecodeAuthorizationMessage(&sts.DecodeAuthorizationMessageInput{
EncodedMessage: aws.String(groups[2]),
})
if decodeErr == nil {
msg := aws.StringValue(result.DecodedMessage)
return fmt.Errorf("%s Authorization failure message: '%s'%s", groups[1], msg, groups[3])
}
log.Printf("[WARN] Attempted to decode authorization message, but received: %v", decodeErr)
}
return err
}
// DecodeAuthZMessages enables automatic decoding of any
// encoded authorization messages
func DecodeAuthZMessages(sess *session.Session) {
azd := &authZMessageDecoder{
Decoder: sts.New(sess),
}
sess.Handlers.UnmarshalError.AfterEachFn = azd.afterEachFn
}
type authZMessageDecoder struct {
Decoder stsDecoder
}
func (a *authZMessageDecoder) afterEachFn(item request.HandlerListRunItem) bool {
if err, ok := item.Request.Error.(awserr.Error); ok && err.Code() == "UnauthorizedOperation" {
item.Request.Error = decodeAWSError(a.Decoder, err)
}
return true
}
-70
View File
@@ -1,70 +0,0 @@
package common
import (
"fmt"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/aws/aws-sdk-go/aws/awserr"
)
type mockSTS struct {
}
func (m *mockSTS) DecodeAuthorizationMessage(input *sts.DecodeAuthorizationMessageInput) (*sts.DecodeAuthorizationMessageOutput, error) {
return &sts.DecodeAuthorizationMessageOutput{
DecodedMessage: aws.String(`{
"allowed": false,
"explicitDeny": true,
"matchedStatements": {}
}`),
}, nil
}
func TestErrorsParsing_RequestFailure(t *testing.T) {
ae := awserr.New("UnauthorizedOperation",
`You are not authorized to perform this operation. Encoded authorization failure message: D9Q7oicjOMr9l2CC-NPP1FiZXK9Ijia1k-3l0siBFCcrK3oSuMFMkBIO5TNj0HdXE-WfwnAcdycFOohfKroNO6toPJEns8RFVfy_M_IjNGmrEFJ6E62pnmBW0OLrMsXxR9FQE4gB4gJzSM0AD6cV6S3FOfqYzWBRX-sQdOT4HryGkFNRoFBr9Xbp-tRwiadwkbdHdfnV9fbRkXmnwCdULml16NBSofC4ZPepLMKmIB5rKjwk-m179UUh2XA-J5no0si6XcRo5GbHQB5QfCIwSHL4vsro2wLZUd16-8OWKyr3tVlTbQe0ERZskqRqRQ5E28QuiBCVV6XstUyo-T4lBSr75Fgnyr3wCO-dS3b_5Ns3WzA2JD4E2AJOAStXIU8IH5YuKkAg7C-dJMuBMPpmKCBEXhNoHDwCyOo5PsV3xMlc0jSb0qYGpfst_TDDtejcZfn7NssUjxVq9qkdH-OXz2gPoQB-hX8ycmZCL5UZwKc3TCLUr7TGnudHjmnMrE9cUo-yTCWfyHPLprhiYhTCKW18EikJ0O1EKI3FJ_b4F19_jFBPARjSwQc7Ut6MNCVzrPdZGYSF6acj5gPaxdy9uSkVQwWXK7Pd5MFP7EBDE1_DgYbzodgwDO2PXeVFUbSLBHKWo_ebZS9ZX2nYPcGss_sYaly0ZVSIJXp7G58B5BoFVhvVH6jYnF9XiAOjMltuP_ycu1pQP1lki500RY3baLvfeYeAsB38XZHKEgWZzq7Fei-uh89q0cjJTmlVyrfRU3q6`,
fmt.Errorf("You can't do it!!"))
rf := awserr.NewRequestFailure(ae, 400, "abc-def-123-456")
result := decodeAWSError(&mockSTS{}, rf)
if result == nil {
t.Error("Expected resulting error")
}
if !strings.Contains(result.Error(), "Authorization failure message:") {
t.Error("Expected authorization failure message")
}
}
func TestErrorsParsing_NonAuthorizationFailure(t *testing.T) {
ae := awserr.New("BadRequest",
`You did something wrong. Try again`,
fmt.Errorf("Request was no good."))
rf := awserr.NewRequestFailure(ae, 400, "abc-def-123-456")
result := decodeAWSError(&mockSTS{}, rf)
if result == nil {
t.Error("Expected resulting error")
}
if result != rf {
t.Error("Expected original error to be returned unchanged")
}
}
func TestErrorsParsing_NonAWSError(t *testing.T) {
err := fmt.Errorf("Random error occurred")
result := decodeAWSError(&mockSTS{}, err)
if result == nil {
t.Error("Expected resulting error")
}
if result != err {
t.Error("Expected original error to be returned unchanged")
}
}
@@ -1,36 +0,0 @@
package common
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/helper/multistep"
)
type BuildInfoTemplate struct {
BuildRegion string
SourceAMI string
SourceAMIName string
SourceAMITags map[string]string
}
func extractBuildInfo(region string, state multistep.StateBag) *BuildInfoTemplate {
rawSourceAMI, hasSourceAMI := state.GetOk("source_image")
if !hasSourceAMI {
return &BuildInfoTemplate{
BuildRegion: region,
}
}
sourceAMI := rawSourceAMI.(*ec2.Image)
sourceAMITags := make(map[string]string, len(sourceAMI.Tags))
for _, tag := range sourceAMI.Tags {
sourceAMITags[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value)
}
return &BuildInfoTemplate{
BuildRegion: region,
SourceAMI: aws.StringValue(sourceAMI.ImageId),
SourceAMIName: aws.StringValue(sourceAMI.Name),
SourceAMITags: sourceAMITags,
}
}
@@ -1,63 +0,0 @@
package common
import (
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/helper/multistep"
)
func testImage() *ec2.Image {
return &ec2.Image{
ImageId: aws.String("ami-abcd1234"),
Name: aws.String("ami_test_name"),
Tags: []*ec2.Tag{
{
Key: aws.String("key-1"),
Value: aws.String("value-1"),
},
{
Key: aws.String("key-2"),
Value: aws.String("value-2"),
},
},
}
}
func testState() multistep.StateBag {
state := new(multistep.BasicStateBag)
return state
}
func TestInterpolateBuildInfo_extractBuildInfo_noSourceImage(t *testing.T) {
state := testState()
buildInfo := extractBuildInfo("foo", state)
expected := BuildInfoTemplate{
BuildRegion: "foo",
}
if !reflect.DeepEqual(*buildInfo, expected) {
t.Fatalf("Unexpected BuildInfoTemplate: expected %#v got %#v\n", expected, *buildInfo)
}
}
func TestInterpolateBuildInfo_extractBuildInfo_withSourceImage(t *testing.T) {
state := testState()
state.Put("source_image", testImage())
buildInfo := extractBuildInfo("foo", state)
expected := BuildInfoTemplate{
BuildRegion: "foo",
SourceAMI: "ami-abcd1234",
SourceAMIName: "ami_test_name",
SourceAMITags: map[string]string{
"key-1": "value-1",
"key-2": "value-2",
},
}
if !reflect.DeepEqual(*buildInfo, expected) {
t.Fatalf("Unexpected BuildInfoTemplate: expected %#v got %#v\n", expected, *buildInfo)
}
}
+21 -50
View File
@@ -1,58 +1,29 @@
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
func listEC2Regions() []string {
return []string{
"ap-northeast-1",
"ap-northeast-2",
"ap-southeast-1",
"ap-southeast-2",
"cn-north-1",
"eu-central-1",
"eu-west-1",
"sa-east-1",
"us-east-1",
"us-gov-west-1",
"us-west-1",
"us-west-2",
}
for _, region := range resultRegions.Regions {
regions = append(regions, *region.RegionName)
}
return regions, nil
}
// ValidateRegion returns an nil if the regions are valid
// and exists; otherwise an error.
// ValidateRegion calls ec2conn.DescribeRegions to get the list of
// regions available to this account.
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)
// ValidateRegion returns true if the supplied region is a valid AWS
// region and false if it's not.
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, available regions: %v", invalidRegions, validRegions)
}
return nil
return false
}
+34 -174
View File
@@ -1,179 +1,71 @@
package common
import (
"errors"
"fmt"
"net"
"os"
"regexp"
"strings"
"time"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/template/interpolate"
)
var reShutdownBehavior = regexp.MustCompile("^(stop|terminate)$")
type AmiFilterOptions struct {
Filters map[*string]*string
Owners []*string
MostRecent bool `mapstructure:"most_recent"`
}
func (d *AmiFilterOptions) Empty() bool {
return len(d.Owners) == 0 && len(d.Filters) == 0
}
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"`
SpotInstanceTypes []string `mapstructure:"spot_instance_types"`
SpotPrice string `mapstructure:"spot_price"`
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
SpotTags map[string]string `mapstructure:"spot_tags"`
SubnetFilter SubnetFilterOptions `mapstructure:"subnet_filter"`
SubnetId string `mapstructure:"subnet_id"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
VpcFilter VpcFilterOptions `mapstructure:"vpc_filter"`
VpcId string `mapstructure:"vpc_id"`
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"`
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
AvailabilityZone string `mapstructure:"availability_zone"`
EbsOptimized bool `mapstructure:"ebs_optimized"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceType string `mapstructure:"instance_type"`
RunTags map[string]string `mapstructure:"run_tags"`
SourceAmi string `mapstructure:"source_ami"`
SpotPrice string `mapstructure:"spot_price"`
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids"`
SubnetId string `mapstructure:"subnet_id"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"`
VpcId string `mapstructure:"vpc_id"`
// Communicator settings
Comm communicator.Config `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
SSHKeyPairName string `mapstructure:"ssh_keypair_name"`
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
}
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
// If we are not given an explicit ssh_keypair_name or
// ssh_private_key_file, then create a temporary one, but only if the
// temporary_key_pair_name has not been provided and we are not using
// ssh_password.
if c.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" &&
c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" {
c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
// if we are not given an explicit keypairname, create a temporary one
if c.SSHKeyPairName == "" {
c.TemporaryKeyPairName = fmt.Sprintf(
"packer %s", uuid.TimeOrderedUUID())
}
if c.WindowsPasswordTimeout == 0 {
c.WindowsPasswordTimeout = 20 * time.Minute
}
if c.RunTags == nil {
c.RunTags = make(map[string]string)
c.WindowsPasswordTimeout = 10 * time.Minute
}
// Validation
errs := c.Comm.Prepare(ctx)
// Validating ssh_interface
if c.Comm.SSHInterface != "public_ip" &&
c.Comm.SSHInterface != "private_ip" &&
c.Comm.SSHInterface != "public_dns" &&
c.Comm.SSHInterface != "private_dns" &&
c.Comm.SSHInterface != "" {
errs = append(errs, fmt.Errorf("Unknown interface type: %s", c.Comm.SSHInterface))
if c.SourceAmi == "" {
errs = append(errs, errors.New("A source_ami must be specified"))
}
if c.Comm.SSHKeyPairName != "" {
if c.Comm.Type == "winrm" && c.Comm.WinRMPassword == "" && c.Comm.SSHPrivateKeyFile == "" {
errs = append(errs, fmt.Errorf("ssh_private_key_file must be provided to retrieve the winrm password when using ssh_keypair_name."))
} else if c.Comm.SSHPrivateKeyFile == "" && !c.Comm.SSHAgentAuth {
errs = append(errs, fmt.Errorf("ssh_private_key_file must be provided or ssh_agent_auth enabled when ssh_keypair_name is specified."))
}
}
if c.SourceAmi == "" && c.SourceAmiFilter.Empty() {
errs = append(errs, fmt.Errorf("A source_ami or source_ami_filter must be specified"))
}
if c.SourceAmi == "" && c.SourceAmiFilter.NoOwner() {
errs = append(errs, fmt.Errorf("For security reasons, your source AMI filter must declare an owner."))
}
if c.InstanceType == "" && len(c.SpotInstanceTypes) == 0 {
errs = append(errs, fmt.Errorf("either instance_type or "+
"spot_instance_types must be specified"))
}
if c.InstanceType != "" && len(c.SpotInstanceTypes) > 0 {
errs = append(errs, fmt.Errorf("either instance_type or "+
"spot_instance_types must be specified, not both"))
}
if c.BlockDurationMinutes%60 != 0 {
errs = append(errs, fmt.Errorf(
"block_duration_minutes must be multiple of 60"))
if c.InstanceType == "" {
errs = append(errs, errors.New("An instance_type must be specified"))
}
if c.SpotPrice == "auto" {
if c.SpotPriceAutoProduct == "" {
errs = append(errs, fmt.Errorf(
errs = append(errs, errors.New(
"spot_price_auto_product must be specified when spot_price is auto"))
}
}
if c.SpotPriceAutoProduct != "" {
if c.SpotPrice != "auto" {
errs = append(errs, fmt.Errorf(
"spot_price should be set to auto when spot_price_auto_product is specified"))
}
}
if c.SpotTags != nil {
if c.SpotPrice == "" || c.SpotPrice == "0" {
errs = append(errs, fmt.Errorf(
"spot_tags should not be set when not requesting a spot instance"))
}
}
if c.UserData != "" && c.UserDataFile != "" {
errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
} else if c.UserDataFile != "" {
@@ -191,37 +83,5 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
}
}
if len(c.TemporarySGSourceCidrs) == 0 {
c.TemporarySGSourceCidrs = []string{"0.0.0.0/0"}
} else {
for _, cidr := range c.TemporarySGSourceCidrs {
if _, _, err := net.ParseCIDR(cidr); err != nil {
errs = append(errs, fmt.Errorf("Error parsing CIDR in temporary_security_group_source_cidrs: %s", err.Error()))
}
}
}
if c.InstanceInitiatedShutdownBehavior == "" {
c.InstanceInitiatedShutdownBehavior = "stop"
} else if !reShutdownBehavior.MatchString(c.InstanceInitiatedShutdownBehavior) {
errs = append(errs, fmt.Errorf("shutdown_behavior only accepts 'stop' or 'terminate' values."))
}
if c.EnableT2Unlimited {
if c.SpotPrice != "" {
errs = append(errs, fmt.Errorf("Error: T2 Unlimited cannot be used in conjuction with Spot Instances"))
}
firstDotIndex := strings.Index(c.InstanceType, ".")
if firstDotIndex == -1 {
errs = append(errs, fmt.Errorf("Error determining main Instance Type from: %s", c.InstanceType))
} else if c.InstanceType[0:firstDotIndex] != "t2" {
errs = append(errs, fmt.Errorf("Error: T2 Unlimited enabled with a non-T2 Instance Type: %s", c.InstanceType))
}
}
return errs
}
func (c *RunConfig) IsSpotInstance() bool {
return c.SpotPrice != "" && c.SpotPrice != "0"
}
+18 -105
View File
@@ -3,10 +3,9 @@ package common
import (
"io/ioutil"
"os"
"regexp"
"testing"
"github.com/hashicorp/packer/helper/communicator"
"github.com/mitchellh/packer/helper/communicator"
)
func init() {
@@ -29,13 +28,6 @@ func testConfig() *RunConfig {
}
}
func testConfigFilter() *RunConfig {
config := testConfig()
config.SourceAmi = ""
config.SourceAmiFilter = AmiFilterOptions{}
return config
}
func TestRunConfigPrepare(t *testing.T) {
c := testConfig()
err := c.Prepare(nil)
@@ -48,98 +40,28 @@ func TestRunConfigPrepare_InstanceType(t *testing.T) {
c := testConfig()
c.InstanceType = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if an instance_type is not specified")
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SourceAmi(t *testing.T) {
c := testConfig()
c.SourceAmi = ""
if err := c.Prepare(nil); len(err) != 2 {
t.Fatalf("Should error if a source_ami (or source_ami_filter) is not specified")
}
}
func TestRunConfigPrepare_SourceAmiFilterBlank(t *testing.T) {
c := testConfigFilter()
if err := c.Prepare(nil); len(err) != 2 {
t.Fatalf("Should error if source_ami_filter is empty or not specified (and source_ami is not specified)")
}
}
func TestRunConfigPrepare_SourceAmiFilterOwnersBlank(t *testing.T) {
c := testConfigFilter()
filter_key := "name"
filter_value := "foo"
c.SourceAmiFilter = AmiFilterOptions{Filters: map[*string]*string{&filter_key: &filter_value}}
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if Owners is not specified)")
}
}
func TestRunConfigPrepare_SourceAmiFilterGood(t *testing.T) {
c := testConfigFilter()
owner := "123"
filter_key := "name"
filter_value := "foo"
goodFilter := AmiFilterOptions{Owners: []*string{&owner}, Filters: map[*string]*string{&filter_key: &filter_value}}
c.SourceAmiFilter = goodFilter
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_EnableT2UnlimitedGood(t *testing.T) {
c := testConfig()
// Must have a T2 instance type if T2 Unlimited is enabled
c.InstanceType = "t2.micro"
c.EnableT2Unlimited = true
err := c.Prepare(nil)
if len(err) > 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_EnableT2UnlimitedBadInstanceType(t *testing.T) {
c := testConfig()
// T2 Unlimited cannot be used with instance types other than T2
c.InstanceType = "m5.large"
c.EnableT2Unlimited = true
err := c.Prepare(nil)
if len(err) != 1 {
t.Fatalf("Should error if T2 Unlimited is enabled with non-T2 instance_type")
}
}
func TestRunConfigPrepare_EnableT2UnlimitedBadWithSpotInstanceRequest(t *testing.T) {
c := testConfig()
// T2 Unlimited cannot be used with Spot Instances
c.InstanceType = "t2.micro"
c.EnableT2Unlimited = true
c.SpotPrice = "auto"
c.SpotPriceAutoProduct = "Linux/UNIX"
err := c.Prepare(nil)
if len(err) != 1 {
t.Fatalf("Should error if T2 Unlimited has been used in conjuntion with a Spot Price request")
}
}
func TestRunConfigPrepare_SpotAuto(t *testing.T) {
c := testConfig()
c.SpotPrice = "auto"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if spot_price_auto_product is not set and spot_price is set to auto")
}
// Good - SpotPrice and SpotPriceAutoProduct are correctly set
c.SpotPriceAutoProduct = "foo"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
c.SpotPrice = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if spot_price is not set to auto and spot_price_auto_product is set")
c.SpotPriceAutoProduct = "foo"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
@@ -164,19 +86,26 @@ func TestRunConfigPrepare_SSHPort(t *testing.T) {
}
}
func TestRunConfigPrepare_SSHUsername(t *testing.T) {
c := testConfig()
c.Comm.SSHUsername = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_UserData(t *testing.T) {
c := testConfig()
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
c.UserData = "foo"
c.UserDataFile = tf.Name()
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if user_data string and user_data_file have both been specified")
t.Fatalf("err: %s", err)
}
}
@@ -188,14 +117,13 @@ func TestRunConfigPrepare_UserDataFile(t *testing.T) {
c.UserDataFile = "idontexistidontthink"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if the file specified by user_data_file does not exist")
t.Fatalf("err: %s", err)
}
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
c.UserDataFile = tf.Name()
@@ -206,27 +134,12 @@ func TestRunConfigPrepare_UserDataFile(t *testing.T) {
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
c := testConfig()
c.Comm.SSHTemporaryKeyPairName = ""
c.TemporaryKeyPairName = ""
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHTemporaryKeyPairName == "" {
t.Fatal("keypair name is empty")
}
// Match prefix and UUID, e.g. "packer_5790d491-a0b8-c84c-c9d2-2aea55086550".
r := regexp.MustCompile(`\Apacker_(?:(?i)[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}?)\z`)
if !r.MatchString(c.Comm.SSHTemporaryKeyPairName) {
t.Fatal("keypair name is not valid")
}
c.Comm.SSHTemporaryKeyPairName = "ssh-key-123"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHTemporaryKeyPairName != "ssh-key-123" {
t.Fatal("keypair name does not match")
if c.TemporaryKeyPairName == "" {
t.Fatal("keypair empty")
}
}
+31 -41
View File
@@ -6,52 +6,21 @@ import (
"time"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/helper/multistep"
)
type ec2Describer interface {
DescribeInstances(*ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
}
var (
// modified in tests
sshHostSleepDuration = time.Second
"github.com/mitchellh/multistep"
"golang.org/x/crypto/ssh"
)
// SSHHost returns a function that can be given to the SSH communicator
// for determining the SSH address based on the instance DNS name.
func SSHHost(e ec2Describer, sshInterface string) func(multistep.StateBag) (string, error) {
func SSHHost(e *ec2.EC2, private bool) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
const tries = 2
// <= with current structure to check result of describing `tries` times
for j := 0; j <= tries; j++ {
for j := 0; j < 2; j++ {
var host string
i := state.Get("instance").(*ec2.Instance)
if sshInterface != "" {
switch sshInterface {
case "public_ip":
if i.PublicIpAddress != nil {
host = *i.PublicIpAddress
}
case "private_ip":
if i.PrivateIpAddress != nil {
host = *i.PrivateIpAddress
}
case "public_dns":
if i.PublicDnsName != nil {
host = *i.PublicDnsName
}
case "private_dns":
if i.PrivateDnsName != nil {
host = *i.PrivateDnsName
}
default:
panic(fmt.Sprintf("Unknown interface type: %s", sshInterface))
}
} else if i.VpcId != nil && *i.VpcId != "" {
if i.PublicIpAddress != nil && *i.PublicIpAddress != "" {
if i.VpcId != nil && *i.VpcId != "" {
if i.PublicIpAddress != nil && *i.PublicIpAddress != "" && !private {
host = *i.PublicIpAddress
} else if i.PrivateIpAddress != nil && *i.PrivateIpAddress != "" {
} else {
host = *i.PrivateIpAddress
}
} else if i.PublicDnsName != nil && *i.PublicDnsName != "" {
@@ -73,10 +42,31 @@ func SSHHost(e ec2Describer, sshInterface string) func(multistep.StateBag) (stri
return "", fmt.Errorf("instance not found: %s", *i.InstanceId)
}
state.Put("instance", r.Reservations[0].Instances[0])
time.Sleep(sshHostSleepDuration)
state.Put("instance", &r.Reservations[0].Instances[0])
time.Sleep(1 * time.Second)
}
return "", errors.New("couldn't determine address for instance")
return "", errors.New("couldn't determine IP address for instance")
}
}
// SSHConfig returns a function that can be used for the SSH communicator
// config for connecting to the instance created over SSH using the generated
// private key.
func SSHConfig(username string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
privateKey := state.Get("privateKey").(string)
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
}
return &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
}, nil
}
}
-130
View File
@@ -1,130 +0,0 @@
package common
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/helper/multistep"
)
const (
privateIP = "10.0.0.1"
publicIP = "192.168.1.1"
privateDNS = "private.dns.test"
publicDNS = "public.dns.test"
)
func TestSSHHost(t *testing.T) {
origSshHostSleepDuration := sshHostSleepDuration
defer func() { sshHostSleepDuration = origSshHostSleepDuration }()
sshHostSleepDuration = 0
var cases = []struct {
allowTries int
vpcId string
sshInterface string
ok bool
wantHost string
}{
{1, "", "", true, publicDNS},
{1, "", "private_ip", true, privateIP},
{1, "vpc-id", "", true, publicIP},
{1, "vpc-id", "private_ip", true, privateIP},
{1, "vpc-id", "private_dns", true, privateDNS},
{1, "vpc-id", "public_dns", true, publicDNS},
{1, "vpc-id", "public_ip", true, publicIP},
{2, "", "", true, publicDNS},
{2, "", "private_ip", true, privateIP},
{2, "vpc-id", "", true, publicIP},
{2, "vpc-id", "private_ip", true, privateIP},
{2, "vpc-id", "private_dns", true, privateDNS},
{2, "vpc-id", "public_dns", true, publicDNS},
{2, "vpc-id", "public_ip", true, publicIP},
{3, "", "", false, ""},
{3, "", "private_ip", false, ""},
{3, "vpc-id", "", false, ""},
{3, "vpc-id", "private_ip", false, ""},
{3, "vpc-id", "private_dns", false, ""},
{3, "vpc-id", "public_dns", false, ""},
{3, "vpc-id", "public_ip", false, ""},
}
for _, c := range cases {
testSSHHost(t, c.allowTries, c.vpcId, c.sshInterface, c.ok, c.wantHost)
}
}
func testSSHHost(t *testing.T, allowTries int, vpcId string, sshInterface string, ok bool, wantHost string) {
t.Logf("allowTries=%d vpcId=%s sshInterface=%s ok=%t wantHost=%q", allowTries, vpcId, sshInterface, ok, wantHost)
e := &fakeEC2Describer{
allowTries: allowTries,
vpcId: vpcId,
privateIP: privateIP,
publicIP: publicIP,
privateDNS: privateDNS,
publicDNS: publicDNS,
}
f := SSHHost(e, sshInterface)
st := &multistep.BasicStateBag{}
st.Put("instance", &ec2.Instance{
InstanceId: aws.String("instance-id"),
})
host, err := f(st)
if e.tries > allowTries {
t.Fatalf("got %d ec2 DescribeInstances tries, want %d", e.tries, allowTries)
}
switch {
case ok && err != nil:
t.Fatalf("expected no error, got %+v", err)
case !ok && err == nil:
t.Fatalf("expected error, got none and host %s", host)
}
if host != wantHost {
t.Fatalf("got host %s, want %s", host, wantHost)
}
}
type fakeEC2Describer struct {
allowTries int
tries int
vpcId string
privateIP, publicIP, privateDNS, publicDNS string
}
func (d *fakeEC2Describer) DescribeInstances(in *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
d.tries++
instance := &ec2.Instance{
InstanceId: aws.String("instance-id"),
}
if d.vpcId != "" {
instance.VpcId = aws.String(d.vpcId)
}
if d.tries >= d.allowTries {
instance.PublicIpAddress = aws.String(d.publicIP)
instance.PrivateIpAddress = aws.String(d.privateIP)
instance.PublicDnsName = aws.String(d.publicDNS)
instance.PrivateDnsName = aws.String(d.privateDNS)
}
out := &ec2.DescribeInstancesOutput{
Reservations: []*ec2.Reservation{
{
Instances: []*ec2.Instance{instance},
},
},
}
return out, nil
}
+181 -306
View File
@@ -1,17 +1,18 @@
package common
import (
"errors"
"fmt"
"log"
"net"
"os"
"strconv"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer/helper/multistep"
"github.com/mitchellh/multistep"
)
// StateRefreshFunc is a function type used for StateChangeConf that is
@@ -34,333 +35,207 @@ type StateChangeConf struct {
Target string
}
// Following are wrapper functions that use Packer's environment-variables to
// determing retry logic, then call the AWS SDK's built-in waiters.
func WaitUntilAMIAvailable(ctx aws.Context, conn ec2iface.EC2API, imageId string) error {
imageInput := ec2.DescribeImagesInput{
ImageIds: []*string{&imageId},
}
waitOpts := getWaiterOptions()
if len(waitOpts) == 0 {
// Bump this default to 30 minutes because the aws default
// of ten minutes doesn't work for some of our long-running copies.
waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(120))
}
err := conn.WaitUntilImageAvailableWithContext(
ctx,
&imageInput,
waitOpts...)
return err
}
func WaitUntilInstanceTerminated(ctx aws.Context, conn *ec2.EC2, instanceId string) error {
instanceInput := ec2.DescribeInstancesInput{
InstanceIds: []*string{&instanceId},
}
err := conn.WaitUntilInstanceTerminatedWithContext(
ctx,
&instanceInput,
getWaiterOptions()...)
return err
}
// This function works for both requesting and cancelling spot instances.
func WaitUntilSpotRequestFulfilled(ctx aws.Context, conn *ec2.EC2, spotRequestId string) error {
spotRequestInput := ec2.DescribeSpotInstanceRequestsInput{
SpotInstanceRequestIds: []*string{&spotRequestId},
}
err := conn.WaitUntilSpotInstanceRequestFulfilledWithContext(
ctx,
&spotRequestInput,
getWaiterOptions()...)
return err
}
func WaitUntilVolumeAvailable(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
volumeInput := ec2.DescribeVolumesInput{
VolumeIds: []*string{&volumeId},
}
err := conn.WaitUntilVolumeAvailableWithContext(
ctx,
&volumeInput,
getWaiterOptions()...)
return err
}
func WaitUntilSnapshotDone(ctx aws.Context, conn *ec2.EC2, snapshotID string) error {
snapInput := ec2.DescribeSnapshotsInput{
SnapshotIds: []*string{&snapshotID},
}
err := conn.WaitUntilSnapshotCompletedWithContext(
ctx,
&snapInput,
getWaiterOptions()...)
return err
}
// Wrappers for our custom AWS waiters
func WaitUntilVolumeAttached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
volumeInput := ec2.DescribeVolumesInput{
VolumeIds: []*string{&volumeId},
}
err := WaitForVolumeToBeAttached(conn,
ctx,
&volumeInput,
getWaiterOptions()...)
return err
}
func WaitUntilVolumeDetached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
volumeInput := ec2.DescribeVolumesInput{
VolumeIds: []*string{&volumeId},
}
err := WaitForVolumeToBeDetached(conn,
ctx,
&volumeInput,
getWaiterOptions()...)
return err
}
func WaitUntilImageImported(ctx aws.Context, conn *ec2.EC2, taskID string) error {
importInput := ec2.DescribeImportImageTasksInput{
ImportTaskIds: []*string{&taskID},
}
err := WaitForImageToBeImported(conn,
ctx,
&importInput,
getWaiterOptions()...)
return err
}
// Custom waiters using AWS's request.Waiter
func WaitForVolumeToBeAttached(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeVolumesInput, opts ...request.WaiterOption) error {
w := request.Waiter{
Name: "DescribeVolumes",
MaxAttempts: 40,
Delay: request.ConstantWaiterDelay(5 * time.Second),
Acceptors: []request.WaiterAcceptor{
{
State: request.SuccessWaiterState,
Matcher: request.PathAllWaiterMatch,
Argument: "Volumes[].Attachments[].State",
Expected: "attached",
},
},
Logger: c.Config.Logger,
NewRequest: func(opts []request.Option) (*request.Request, error) {
var inCpy *ec2.DescribeVolumesInput
if input != nil {
tmp := *input
inCpy = &tmp
// AMIStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an AMI for state changes.
func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: []*string{&imageId},
})
if err != nil {
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidAMIID.NotFound" {
// Set this to nil as if we didn't find anything.
resp = nil
} else if isTransientNetworkError(err) {
// Transient network error, treat it as if we didn't find anything
resp = nil
} else {
log.Printf("Error on AMIStateRefresh: %s", err)
return nil, "", err
}
req, _ := c.DescribeVolumesRequest(inCpy)
req.SetContext(ctx)
req.ApplyOptions(opts...)
return req, nil
},
}
w.ApplyOptions(opts...)
}
return w.WaitWithContext(ctx)
if resp == nil || len(resp.Images) == 0 {
// Sometimes AWS has consistency issues and doesn't see the
// AMI. Return an empty state.
return nil, "", nil
}
i := resp.Images[0]
return i, *i.State, nil
}
}
func WaitForVolumeToBeDetached(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeVolumesInput, opts ...request.WaiterOption) error {
w := request.Waiter{
Name: "DescribeVolumes",
MaxAttempts: 40,
Delay: request.ConstantWaiterDelay(5 * time.Second),
Acceptors: []request.WaiterAcceptor{
{
State: request.SuccessWaiterState,
Matcher: request.PathAllWaiterMatch,
Argument: "length(Volumes[].Attachments[]) == `0`",
Expected: true,
},
},
Logger: c.Config.Logger,
NewRequest: func(opts []request.Option) (*request.Request, error) {
var inCpy *ec2.DescribeVolumesInput
if input != nil {
tmp := *input
inCpy = &tmp
// InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an EC2 instance.
func InstanceStateRefreshFunc(conn *ec2.EC2, instanceId string) StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{
InstanceIds: []*string{&instanceId},
})
if err != nil {
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" {
// Set this to nil as if we didn't find anything.
resp = nil
} else if isTransientNetworkError(err) {
// Transient network error, treat it as if we didn't find anything
resp = nil
} else {
log.Printf("Error on InstanceStateRefresh: %s", err)
return nil, "", err
}
req, _ := c.DescribeVolumesRequest(inCpy)
req.SetContext(ctx)
req.ApplyOptions(opts...)
return req, nil
},
}
w.ApplyOptions(opts...)
}
return w.WaitWithContext(ctx)
if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
// Sometimes AWS just has consistency issues and doesn't see
// our instance yet. Return an empty state.
return nil, "", nil
}
i := resp.Reservations[0].Instances[0]
return i, *i.State.Name, nil
}
}
func WaitForImageToBeImported(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeImportImageTasksInput, opts ...request.WaiterOption) error {
w := request.Waiter{
Name: "DescribeImages",
MaxAttempts: 720,
Delay: request.ConstantWaiterDelay(5 * time.Second),
Acceptors: []request.WaiterAcceptor{
{
State: request.SuccessWaiterState,
Matcher: request.PathAllWaiterMatch,
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) {
var inCpy *ec2.DescribeImportImageTasksInput
if input != nil {
tmp := *input
inCpy = &tmp
// SpotRequestStateRefreshFunc returns a StateRefreshFunc that is used to watch
// a spot request for state changes.
func SpotRequestStateRefreshFunc(conn *ec2.EC2, spotRequestId string) StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{
SpotInstanceRequestIds: []*string{&spotRequestId},
})
if err != nil {
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSpotInstanceRequestID.NotFound" {
// Set this to nil as if we didn't find anything.
resp = nil
} else if isTransientNetworkError(err) {
// Transient network error, treat it as if we didn't find anything
resp = nil
} else {
log.Printf("Error on SpotRequestStateRefresh: %s", err)
return nil, "", err
}
req, _ := c.DescribeImportImageTasksRequest(inCpy)
req.SetContext(ctx)
req.ApplyOptions(opts...)
return req, nil
},
}
if resp == nil || len(resp.SpotInstanceRequests) == 0 {
// Sometimes AWS has consistency issues and doesn't see the
// SpotRequest. Return an empty state.
return nil, "", nil
}
i := resp.SpotInstanceRequests[0]
return i, *i.State, nil
}
w.ApplyOptions(opts...)
return w.WaitWithContext(ctx)
}
// This helper function uses the environment variables AWS_TIMEOUT_SECONDS and
// AWS_POLL_DELAY_SECONDS to generate waiter options that can be passed into any
// request.Waiter function. These options will control how many times the waiter
// will retry the request, as well as how long to wait between the retries.
func ImportImageRefreshFunc(conn *ec2.EC2, importTaskId string) StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{
ImportTaskIds: []*string{
&importTaskId,
},
},
)
if err != nil {
if ec2err, ok := err.(awserr.Error); ok && strings.HasPrefix(ec2err.Code(), "InvalidConversionTaskId") {
resp = nil
} else if isTransientNetworkError(err) {
resp = nil
} else {
log.Printf("Error on ImportImageRefresh: %s", err)
return nil, "", err
}
}
// DEFAULTING BEHAVIOR:
// if AWS_POLL_DELAY_SECONDS is set but the others are not, Packer will set this
// poll delay and use the waiter-specific default
if resp == nil || len(resp.ImportImageTasks) == 0 {
return nil, "", nil
}
// if AWS_TIMEOUT_SECONDS is set but AWS_MAX_ATTEMPTS is not, Packer will use
// AWS_TIMEOUT_SECONDS and _either_ AWS_POLL_DELAY_SECONDS _or_ 2 if the user has not set AWS_POLL_DELAY_SECONDS, to determine a max number of attempts to make.
// if AWS_TIMEOUT_SECONDS, _and_ AWS_MAX_ATTEMPTS are both set,
// AWS_TIMEOUT_SECONDS will be ignored.
// if AWS_MAX_ATTEMPTS is set but AWS_POLL_DELAY_SECONDS is not, then we will
// use waiter-specific defaults.
type envInfo struct {
envKey string
Val int
overridden bool
i := resp.ImportImageTasks[0]
return i, *i.Status, nil
}
}
type overridableWaitVars struct {
awsPollDelaySeconds envInfo
awsMaxAttempts envInfo
awsTimeoutSeconds envInfo
// WaitForState watches an object and waits for it to achieve a certain
// state.
func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
log.Printf("Waiting for state to become: %s", conf.Target)
sleepSeconds := 2
maxTicks := int(TimeoutSeconds()/sleepSeconds) + 1
notfoundTick := 0
for {
var currentState string
i, currentState, err = conf.Refresh()
if err != nil {
return
}
if i == nil {
// If we didn't find the resource, check if we have been
// not finding it for awhile, and if so, report an error.
notfoundTick += 1
if notfoundTick > maxTicks {
return nil, errors.New("couldn't find resource")
}
} else {
// Reset the counter for when a resource isn't found
notfoundTick = 0
if currentState == conf.Target {
return
}
if conf.StepState != nil {
if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("interrupted")
}
}
found := false
for _, allowed := range conf.Pending {
if currentState == allowed {
found = true
break
}
}
if !found {
err := fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
return nil, err
}
}
time.Sleep(time.Duration(sleepSeconds) * time.Second)
}
}
func getWaiterOptions() []request.WaiterOption {
envOverrides := getEnvOverrides()
waitOpts := applyEnvOverrides(envOverrides)
return waitOpts
func isTransientNetworkError(err error) bool {
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
return true
}
return false
}
func getOverride(varInfo envInfo) envInfo {
override := os.Getenv(varInfo.envKey)
// Returns 300 seconds (5 minutes) by default
// Some AWS operations, like copying an AMI to a distant region, take a very long time
// Allow user to override with AWS_TIMEOUT_SECONDS environment variable
func TimeoutSeconds() (seconds int) {
seconds = 300
override := os.Getenv("AWS_TIMEOUT_SECONDS")
if override != "" {
n, err := strconv.Atoi(override)
if err != nil {
log.Printf("Invalid %s '%s', using default", varInfo.envKey, override)
log.Printf("Invalid timeout seconds '%s', using default", override)
} else {
varInfo.overridden = true
varInfo.Val = n
seconds = n
}
}
return varInfo
}
func getEnvOverrides() overridableWaitVars {
// Load env vars from environment.
envValues := overridableWaitVars{
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
envInfo{"AWS_TIMEOUT_SECONDS", 0, false},
}
envValues.awsMaxAttempts = getOverride(envValues.awsMaxAttempts)
envValues.awsPollDelaySeconds = getOverride(envValues.awsPollDelaySeconds)
envValues.awsTimeoutSeconds = getOverride(envValues.awsTimeoutSeconds)
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,
// default to a poll delay of 2 seconds
if envOverrides.awsPollDelaySeconds.overridden {
delaySeconds := request.ConstantWaiterDelay(time.Duration(envOverrides.awsPollDelaySeconds.Val) * time.Second)
waitOpts = append(waitOpts, request.WithWaiterDelay(delaySeconds))
}
// If user has set max attempts, overwrite it. If user hasn't set max
// attempts, default to whatever the waiter has set as a default.
if envOverrides.awsMaxAttempts.overridden {
waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(envOverrides.awsMaxAttempts.Val))
} else if envOverrides.awsTimeoutSeconds.overridden {
maxAttempts := envOverrides.awsTimeoutSeconds.Val / envOverrides.awsPollDelaySeconds.Val
// override the delay so we can get the timeout right
if !envOverrides.awsPollDelaySeconds.overridden {
delaySeconds := request.ConstantWaiterDelay(time.Duration(envOverrides.awsPollDelaySeconds.Val) * time.Second)
waitOpts = append(waitOpts, request.WithWaiterDelay(delaySeconds))
}
waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(maxAttempts))
}
return waitOpts
log.Printf("Allowing %ds to complete (change with AWS_TIMEOUT_SECONDS)", seconds)
return seconds
}
-66
View File
@@ -1,66 +0,0 @@
package common
import (
"reflect"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws/request"
)
func testGetWaiterOptions(t *testing.T) {
// no vars are set
envValues := overridableWaitVars{
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
envInfo{"AWS_TIMEOUT_SECONDS", 300, false},
}
options := applyEnvOverrides(envValues)
if len(options) > 0 {
t.Fatalf("Did not expect any waiter options to be generated; actual: %#v", options)
}
// all vars are set
envValues = overridableWaitVars{
envInfo{"AWS_POLL_DELAY_SECONDS", 1, true},
envInfo{"AWS_MAX_ATTEMPTS", 800, true},
envInfo{"AWS_TIMEOUT_SECONDS", 20, true},
}
options = applyEnvOverrides(envValues)
expected := []request.WaiterOption{
request.WithWaiterDelay(request.ConstantWaiterDelay(time.Duration(1) * time.Second)),
request.WithWaiterMaxAttempts(800),
}
if !reflect.DeepEqual(options, expected) {
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
}
// poll delay is not set
envValues = overridableWaitVars{
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
envInfo{"AWS_MAX_ATTEMPTS", 800, true},
envInfo{"AWS_TIMEOUT_SECONDS", 300, false},
}
options = applyEnvOverrides(envValues)
expected = []request.WaiterOption{
request.WithWaiterMaxAttempts(800),
}
if !reflect.DeepEqual(options, expected) {
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
}
// poll delay is not set but timeout seconds is
envValues = overridableWaitVars{
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
envInfo{"AWS_TIMEOUT_SECONDS", 20, true},
}
options = applyEnvOverrides(envValues)
expected = []request.WaiterOption{
request.WithWaiterDelay(request.ConstantWaiterDelay(time.Duration(2) * time.Second)),
request.WithWaiterMaxAttempts(10),
}
if !reflect.DeepEqual(options, expected) {
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
}
}
+44 -147
View File
@@ -1,85 +1,56 @@
package common
import (
"context"
"fmt"
"sync"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepAMIRegionCopy struct {
AccessConfig *AccessConfig
Regions []string
AMIKmsKeyId string
RegionKeyIds map[string]string
EncryptBootVolume *bool // nil means preserve
Name string
OriginalRegion string
toDelete string
getRegionConn func(*AccessConfig, string) (ec2iface.EC2API, error)
AMISkipBuildRegion bool
AccessConfig *AccessConfig
Regions []string
Name string
}
func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
snapshots := state.Get("snapshots").(map[string][]string)
ami := amis[s.OriginalRegion]
// Always copy back into original region to preserve the ami name
if s.EncryptBootVolume != nil || s.AMISkipBuildRegion {
// if we haven't specificed encryption and we aren't skipping the save
// to the build region, we don't have anything to delete
s.toDelete = ami
}
if s.EncryptBootVolume != nil {
if !s.AMISkipBuildRegion {
s.Regions = append(s.Regions, s.OriginalRegion)
}
if *s.EncryptBootVolume {
// encrypt_boot is true, so we have to copy the temporary
// AMI with required encryption setting.
// temp image was created by stepCreateAMI.
if s.RegionKeyIds == nil {
s.RegionKeyIds = make(map[string]string)
}
s.RegionKeyIds[s.OriginalRegion] = s.AMIKmsKeyId
}
}
ami := amis[*ec2conn.Config.Region]
if len(s.Regions) == 0 {
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Copying/Encrypting AMI (%s) to other regions...", ami))
ui.Say(fmt.Sprintf("Copying AMI (%s) to other regions...", ami))
var lock sync.Mutex
var wg sync.WaitGroup
var regKeyID string
errs := new(packer.MultiError)
wg.Add(len(s.Regions))
for _, region := range s.Regions {
ui.Message(fmt.Sprintf("Copying to: %s", region))
if s.EncryptBootVolume != nil && *s.EncryptBootVolume {
regKeyID = s.RegionKeyIds[region]
if region == *ec2conn.Config.Region {
ui.Message(fmt.Sprintf(
"Avoiding copying AMI to duplicate region %s", region))
continue
}
wg.Add(1)
ui.Message(fmt.Sprintf("Copying to: %s", region))
go func(region string) {
defer wg.Done()
id, snapshotIds, err := s.amiRegionCopy(ctx, state, s.AccessConfig, s.Name, ami, region, s.OriginalRegion, regKeyID, s.EncryptBootVolume)
id, err := amiRegionCopy(state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region)
lock.Lock()
defer lock.Unlock()
amis[region] = id
snapshots[region] = snapshotIds
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
@@ -102,120 +73,46 @@ func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) m
}
func (s *StepAMIRegionCopy) Cleanup(state multistep.StateBag) {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
if len(s.toDelete) == 0 {
return
}
// Delete the unencrypted amis and snapshots
ui.Say("Deregistering the AMI and deleting unencrypted temporary " +
"AMIs and snapshots")
resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: []*string{&s.toDelete},
})
if err != nil {
err := fmt.Errorf("Error describing AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
// Deregister image by name.
for _, i := range resp.Images {
_, err := ec2conn.DeregisterImage(&ec2.DeregisterImageInput{
ImageId: i.ImageId,
})
if err != nil {
err := fmt.Errorf("Error deregistering existing AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
ui.Say(fmt.Sprintf("Deregistered AMI id: %s", *i.ImageId))
// Delete snapshot(s) by image
for _, b := range i.BlockDeviceMappings {
if b.Ebs != nil && aws.StringValue(b.Ebs.SnapshotId) != "" {
_, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{
SnapshotId: b.Ebs.SnapshotId,
})
if err != nil {
err := fmt.Errorf("Error deleting existing snapshot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
ui.Say(fmt.Sprintf("Deleted snapshot: %s", *b.Ebs.SnapshotId))
}
}
}
}
func getRegionConn(config *AccessConfig, target string) (ec2iface.EC2API, error) {
// Connect to the region where the AMI will be copied to
session, err := config.Session()
if err != nil {
return nil, fmt.Errorf("Error getting region connection for copy: %s", err)
}
regionconn := ec2.New(session.Copy(&aws.Config{
Region: aws.String(target),
}))
return regionconn, nil
// No cleanup...
}
// amiRegionCopy does a copy for the given AMI to the target region and
// returns the resulting ID and snapshot IDs, or error.
func (s *StepAMIRegionCopy) amiRegionCopy(ctx context.Context, state multistep.StateBag, config *AccessConfig, name, imageId,
target, source, keyId string, encrypt *bool) (string, []string, error) {
snapshotIds := []string{}
// returns the resulting ID or error.
func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string, imageId string,
target string, source string) (string, error) {
if s.getRegionConn == nil {
s.getRegionConn = getRegionConn
}
regionconn, err := s.getRegionConn(config, target)
// Connect to the region where the AMI will be copied to
awsConfig, err := config.Config()
if err != nil {
return "", snapshotIds, err
return "", err
}
awsConfig.Region = aws.String(target)
sess := session.New(awsConfig)
regionconn := ec2.New(sess)
resp, err := regionconn.CopyImage(&ec2.CopyImageInput{
SourceRegion: &source,
SourceImageId: &imageId,
Name: &name,
Encrypted: encrypt,
KmsKeyId: aws.String(keyId),
})
if err != nil {
return "", snapshotIds, fmt.Errorf("Error Copying AMI (%s) to region (%s): %s",
return "", fmt.Errorf("Error Copying AMI (%s) to region (%s): %s",
imageId, target, err)
}
// Wait for the image to become ready
if err := WaitUntilAMIAvailable(ctx, regionconn, *resp.ImageId); err != nil {
return "", snapshotIds, fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s",
stateChange := StateChangeConf{
Pending: []string{"pending"},
Target: "available",
Refresh: AMIStateRefreshFunc(regionconn, *resp.ImageId),
StepState: state,
}
if _, err := WaitForState(&stateChange); err != nil {
return "", fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s",
*resp.ImageId, target, err)
}
// Getting snapshot IDs out of the copied AMI
describeImageResp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{resp.ImageId}})
if err != nil {
return "", snapshotIds, fmt.Errorf("Error describing copied AMI (%s) in region (%s): %s",
imageId, target, err)
}
for _, blockDeviceMapping := range describeImageResp.Images[0].BlockDeviceMappings {
if blockDeviceMapping.Ebs != nil && blockDeviceMapping.Ebs.SnapshotId != nil {
snapshotIds = append(snapshotIds, *blockDeviceMapping.Ebs.SnapshotId)
}
}
return *resp.ImageId, snapshotIds, nil
return *resp.ImageId, nil
}

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