Compare commits
90 Commits
v1.3.2
..
v1.0.0-rc2
| Author | SHA1 | Date | |
|---|---|---|---|
| 7866d7df14 | |||
| 6169a7c07f | |||
| a129d539d4 | |||
| 8a214a9dbe | |||
| b2775b0ca3 | |||
| 1ea6b439e9 | |||
| f91f24f164 | |||
| 27b5b276b0 | |||
| 9131c8cea8 | |||
| 793c42d6a2 | |||
| 9962fd486b | |||
| 1392f4dd09 | |||
| 81472bdbab | |||
| 9b8ab21c15 | |||
| 5781c27fef | |||
| bb153b519e | |||
| f985522e7c | |||
| 1f30646cab | |||
| 6bebcd89f5 | |||
| f1b5a78737 | |||
| b1b95fbf3a | |||
| 30792f5638 | |||
| 36608e5f9b | |||
| 8a80a735e7 | |||
| 6d884c171f | |||
| 06d5fbadc4 | |||
| 8b30c230ac | |||
| a8e864ac25 | |||
| d4d655503d | |||
| 05cb1cf012 | |||
| e42127d305 | |||
| 4c351d0ad9 | |||
| a05546e93c | |||
| 5807847cc4 | |||
| 3e9cf70fc9 | |||
| 0fb55177e4 | |||
| 7e98343b8f | |||
| 77d20776a9 | |||
| 67a81afac4 | |||
| 669dcae766 | |||
| 09a32d4793 | |||
| fcc53d3639 | |||
| 816eebe516 | |||
| 20a6ad651a | |||
| de3e2312c8 | |||
| 01ae8c728d | |||
| af3163d6cb | |||
| d14004eaf2 | |||
| 85b5b00b9a | |||
| 648590ba25 | |||
| eeeb09579f | |||
| da145b960b | |||
| 0216debe72 | |||
| e0eecb6829 | |||
| e6c8c547b2 | |||
| 43dec47de8 | |||
| 080a296e3a | |||
| 3a75f04c12 | |||
| e264c8aebb | |||
| ace3f2c04a | |||
| c4d8cfc090 | |||
| be324ac0d9 | |||
| 2f936cd6b7 | |||
| 22cdc76644 | |||
| b8429a2e58 | |||
| b2356bfe7a | |||
| e67994b174 | |||
| 7f608fd0d7 | |||
| 2c1740ab3c | |||
| f851475df8 | |||
| 7f679001c8 | |||
| 6fcf5a3223 | |||
| 1d4e3228f8 | |||
| a8dec623a1 | |||
| 876df8ed0f | |||
| 8cea6b69d9 | |||
| a819b04140 | |||
| 5d797565c1 | |||
| dbe08e5f18 | |||
| b5a27c23fe | |||
| 2a7eef5737 | |||
| 7ce2b7cf9f | |||
| 1ea024a38e | |||
| b6d0f08162 | |||
| c243b9a713 | |||
| 9d872dda56 | |||
| 408a15fe13 | |||
| 3767fe41f1 | |||
| edc3f8f39b | |||
| 116b7c738e |
+2
-4
@@ -1,4 +1,2 @@
|
||||
* text=auto
|
||||
*.go text eol=lf
|
||||
*.sh text eol=lf
|
||||
common/test-fixtures/root/* eol=lf
|
||||
|
||||
common/test-fixtures/root/* eol=lf
|
||||
@@ -1,228 +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
|
||||
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
|
||||
|
||||
If you have never worked with Go before, you will have to install its
|
||||
runtime in order to build packer.
|
||||
|
||||
1. [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.
|
||||
|
||||
### 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 on forks](#Working on forks) for a better way to use `git push ...`.
|
||||
|
||||
### Pull Request Lifecycle
|
||||
|
||||
1. You are welcome to submit your pull request for commentary or review before
|
||||
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.
|
||||
|
||||
1. Once you believe your pull request is ready to be merged, you can remove any
|
||||
"[WIP]" prefix from the title and a core team member will review.
|
||||
|
||||
1. One of Packer's core team members will look over your contribution and
|
||||
either provide comments letting you know if there is anything left to do. We
|
||||
do our best to provide feedback in a timely manner, but it may take some time
|
||||
for us to respond.
|
||||
|
||||
1. Once all outstanding comments and checklist items have been addressed, your
|
||||
contribution will be merged! Merged PRs will be included in the next
|
||||
Packer release. The core team takes care of updating the
|
||||
[CHANGELOG.md](../CHANGELOG.md) as they merge.
|
||||
|
||||
1. 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
|
||||
|
||||
#### 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.
|
||||
|
||||
#### Govendor
|
||||
|
||||
If you are submitting a change that requires new or updated dependencies, please
|
||||
include them in `vendor/vendor.json` and in the `vendor/` folder. This helps
|
||||
everything get tested properly in CI.
|
||||
|
||||
Note that you will need to use [govendor](https://github.com/kardianos/govendor)
|
||||
to do this. This step is recommended but not required; if you don't use govendor
|
||||
please indicate in your PR which dependencies have changed and to what versions.
|
||||
|
||||
Use `govendor fetch <project>` to add dependencies to the project. See
|
||||
[govendor quick start](https://github.com/kardianos/govendor#quick-start-also-see-the-faq)
|
||||
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.
|
||||
@@ -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.
|
||||
|
||||
@@ -23,4 +18,3 @@ Describe the problem and include the following information:
|
||||
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
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ 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:
|
||||
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
/src
|
||||
/website/.sass-cache
|
||||
/website/build
|
||||
/website/tmp
|
||||
.DS_Store
|
||||
.vagrant
|
||||
.idea
|
||||
test/.env
|
||||
*~
|
||||
*.received.*
|
||||
*.swp
|
||||
|
||||
website/.bundle
|
||||
website/vendor
|
||||
@@ -23,6 +21,3 @@ packer-test*.log
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
Thumbs.db
|
||||
/packer.exe
|
||||
.project
|
||||
|
||||
+8
-18
@@ -1,35 +1,25 @@
|
||||
env:
|
||||
- USER=travis
|
||||
|
||||
os:
|
||||
- windows
|
||||
- linux
|
||||
- osx
|
||||
- USER=travis
|
||||
|
||||
sudo: false
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
- master
|
||||
- 1.7.4
|
||||
- 1.8
|
||||
|
||||
before_install:
|
||||
- >
|
||||
if [[ "$TRAVIS_OS_NAME" == "windows" ]];
|
||||
then choco install -y make;
|
||||
fi
|
||||
install:
|
||||
- make deps
|
||||
|
||||
script:
|
||||
- df -h
|
||||
- make ci
|
||||
- GOMAXPROCS=2 make ci
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
- os: windows
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- go: 1.4.3
|
||||
|
||||
+68
-1005
File diff suppressed because it is too large
Load Diff
-28
@@ -1,28 +0,0 @@
|
||||
* @hashicorp/packer
|
||||
|
||||
# builders
|
||||
|
||||
/builder/alicloud/ dongxiao.zzh@alibaba-inc.com
|
||||
/builder/amazon/ebssurrogate/ @jen20
|
||||
/builder/amazon/ebsvolume/ @jen20
|
||||
/builder/azure/ @boumenot
|
||||
/builder/hyperv/ @taliesins
|
||||
/builder/lxc/ @ChrisLundquist
|
||||
/builder/lxd/ @ChrisLundquist
|
||||
/builder/oneandone/ @jasmingacic
|
||||
/builder/oracle/ @prydie @owainlewis
|
||||
/builder/profitbricks/ @jasmingacic
|
||||
/builder/triton/ @jen20 @sean-
|
||||
/builder/ncloud/ @YuSungDuk
|
||||
/builder/scaleway/ @dimtion @edouardb
|
||||
|
||||
# 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
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
# 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. Run `make help` to get
|
||||
information about make targets.
|
||||
|
||||
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.
|
||||
|
||||
### Opening an Pull Request
|
||||
|
||||
When you are ready to open a pull-request, you will need to [fork packer](https://github.com/mitchellh/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 filepaths. This means `go build` will break if you `git clone` your fork instead of using `go get` on the main packer project.
|
||||
|
||||
### Tips for Working on Packer
|
||||
|
||||
#### 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 $GOPATH/src/github.com/mitchellh/packer
|
||||
2. Add the remote `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.
|
||||
|
||||
#### Govendor
|
||||
|
||||
If you are submitting a change that requires new or updated dependencies, please include them in `vendor/vendor.json` and in the `vendor/` folder. This helps everything get tested properly in CI.
|
||||
|
||||
Note that you will need to use [govendor](https://github.com/kardianos/govendor) to do this. This step is recommended but not required; if you don't use govendor please indicate in your PR which dependencies have changed and to what versions.
|
||||
|
||||
Use `govendor fetch <project>` to add dependencies to the project. See
|
||||
[govendor quick
|
||||
start](https://github.com/kardianos/govendor#quick-start-also-see-the-faq) 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 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.
|
||||
|
||||
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.
|
||||
@@ -4,90 +4,47 @@ VET?=$(shell ls -d */ | grep -v vendor | grep -v website)
|
||||
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)
|
||||
GOFMT_FILES?=$$(find . -not -path "./vendor/*" -name "*.go")
|
||||
|
||||
# gofmt
|
||||
UNFORMATTED_FILES=$(shell find . -not -path "./vendor/*" -name "*.go" | xargs gofmt -s -l)
|
||||
default: deps generate test dev
|
||||
|
||||
EXECUTABLE_FILES=$(shell find . -type f -executable | egrep -v '^\./(website/[vendor|tmp]|vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats|\.git)' | egrep -v './provisioner/ansible/test-fixtures/exit1')
|
||||
|
||||
# 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)
|
||||
|
||||
export GOLDFLAGS
|
||||
|
||||
.PHONY: bin checkversion ci default deps fmt fmt-docs fmt-examples generate releasebin test testacc testrace updatedeps
|
||||
|
||||
default: deps generate testrace dev releasebin package dev fmt fmt-check mode-check fmt-docs fmt-examples
|
||||
|
||||
ci: testrace
|
||||
ci: deps test
|
||||
|
||||
release: deps test releasebin package ## Build a release build
|
||||
|
||||
bin: deps ## Build debug/test build
|
||||
@go get github.com/mitchellh/gox
|
||||
@echo "WARN: 'make bin' is for debug / test builds only. Use 'make release' for release builds."
|
||||
@sh -c "$(CURDIR)/scripts/build.sh"
|
||||
@GO15VENDOREXPERIMENT=1 sh -c "$(CURDIR)/scripts/build.sh"
|
||||
|
||||
releasebin: deps
|
||||
@go get github.com/mitchellh/gox
|
||||
@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
|
||||
@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)"
|
||||
|
||||
deps:
|
||||
@go get golang.org/x/tools/cmd/goimports
|
||||
@go get golang.org/x/tools/cmd/stringer
|
||||
@go get -u github.com/mna/pigeon
|
||||
@go get github.com/kardianos/govendor
|
||||
go get github.com/mitchellh/gox
|
||||
go get golang.org/x/tools/cmd/stringer
|
||||
go get github.com/kardianos/govendor
|
||||
govendor sync
|
||||
|
||||
dev: deps ## Build and install a development build
|
||||
@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
|
||||
@gofmt -w -s main.go $(UNFORMATTED_FILES)
|
||||
@gofmt -w -s $(GOFMT_FILES)
|
||||
|
||||
fmt-check: ## Check go code formatting
|
||||
@echo "==> Checking that code complies with gofmt requirements..."
|
||||
@if [ ! -z "$(UNFORMATTED_FILES)" ]; then \
|
||||
echo "gofmt needs to be run on the following files:"; \
|
||||
echo "$(UNFORMATTED_FILES)" | xargs -n1; \
|
||||
echo "You can use the command: \`make fmt\` to reformat code."; \
|
||||
exit 1; \
|
||||
else \
|
||||
echo "Check passed."; \
|
||||
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 {} \;
|
||||
$(CURDIR)/scripts/gofmtcheck.sh $(GOFMT_FILES)
|
||||
|
||||
# Install js-beautify with npm install -g js-beautify
|
||||
fmt-examples:
|
||||
@@ -97,29 +54,29 @@ fmt-examples:
|
||||
# source files.
|
||||
generate: deps ## Generate dynamically generated code
|
||||
go generate .
|
||||
gofmt -w common/bootcommand/boot_command.go
|
||||
goimports -w common/bootcommand/boot_command.go
|
||||
gofmt -w command/plugin.go
|
||||
|
||||
test: fmt-check mode-check vet ## Run unit tests
|
||||
test: deps fmt-check ## Run unit tests
|
||||
@go test $(TEST) $(TESTARGS) -timeout=2m
|
||||
@go tool vet $(VET) ; if [ $$? -eq 1 ]; then \
|
||||
echo "ERROR: Vet found problems in the code."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# testacc runs acceptance tests
|
||||
testacc: deps generate ## Run acceptance tests
|
||||
@echo "WARN: Acceptance tests will take a long time to run and may cost money. Ctrl-C if you want to cancel."
|
||||
PACKER_ACC=1 go test -v $(TEST) $(TESTARGS) -timeout=45m
|
||||
|
||||
testrace: fmt-check mode-check vet ## Test with race detection enabled
|
||||
testrace: deps ## Test for race conditions
|
||||
@go test -race $(TEST) $(TESTARGS) -timeout=2m
|
||||
|
||||
updatedeps:
|
||||
@echo "INFO: Packer deps are managed by govendor. See .github/CONTRIBUTING.md"
|
||||
|
||||
vet: ## Vet Go code
|
||||
@go tool vet $(VET) ; if [ $$? -eq 1 ]; then \
|
||||
echo "ERROR: Vet found problems in the code."; \
|
||||
exit 1; \
|
||||
fi
|
||||
go get -u github.com/mitchellh/gox
|
||||
go get -u golang.org/x/tools/cmd/stringer
|
||||
@echo "INFO: Packer deps are managed by govendor. See CONTRIBUTING.md"
|
||||
|
||||
help:
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
.PHONY: bin checkversion ci default deps fmt fmt-examples generate releasebin test testacc testrace updatedeps
|
||||
|
||||
@@ -5,26 +5,41 @@
|
||||
[![GoDoc][godoc-badge]][godoc]
|
||||
[![GoReportCard][report-badge]][report]
|
||||
|
||||
[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
|
||||
[travis-badge]: https://travis-ci.org/mitchellh/packer.svg?branch=master
|
||||
[travis]: https://travis-ci.org/mitchellh/packer
|
||||
[appveyor-badge]: https://ci.appveyor.com/api/projects/status/github/mitchellh/packer?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
|
||||
[godoc-badge]: https://godoc.org/github.com/mitchellh/packer?status.svg
|
||||
[godoc]: https://godoc.org/github.com/mitchellh/packer
|
||||
[report-badge]: https://goreportcard.com/badge/github.com/mitchellh/packer
|
||||
[report]: https://goreportcard.com/report/github.com/mitchellh/packer
|
||||
|
||||
* Website: https://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
|
||||
* Azure
|
||||
* CloudStack
|
||||
* DigitalOcean
|
||||
* Docker
|
||||
* Google Compute Engine
|
||||
* Hyper-V
|
||||
* 1&1
|
||||
* OpenStack
|
||||
* Parallels
|
||||
* ProfitBricks
|
||||
* QEMU. Both KVM and Xen images.
|
||||
* Triton (Joyent Public Cloud)
|
||||
* VMware
|
||||
* VirtualBox
|
||||
|
||||
Support for other platforms can be added via plugins.
|
||||
|
||||
@@ -32,17 +47,19 @@ The images that Packer creates can easily be turned into
|
||||
[Vagrant](http://www.vagrantup.com) boxes.
|
||||
|
||||
## Quick Start
|
||||
Download and install packages and dependencies
|
||||
```
|
||||
go get github.com/mitchellh/packer
|
||||
```
|
||||
|
||||
**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](CONTRIBUTING.md#setting-up-go-to-work-on-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
|
||||
@@ -80,17 +97,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
-79
@@ -1,88 +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
|
||||
|
||||
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
|
||||
|
||||
+4
-4
@@ -13,7 +13,7 @@ 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 GO15VENDOREXPERIMENT=1
|
||||
@@ -29,9 +29,9 @@ build_script:
|
||||
- ps: |
|
||||
go.exe test (go.exe list ./... `
|
||||
|? { -not $_.Contains('/vendor/') } `
|
||||
|? { $_ -ne 'github.com/hashicorp/packer/builder/parallels/common' } `
|
||||
|? { $_ -ne 'github.com/hashicorp/packer/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
@@ -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 ''
|
||||
@@ -1,89 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// 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 for AlicloudClient
|
||||
func (c *AlicloudAccessConfig) Client() (*ecs.Client, error) {
|
||||
if err := c.loadAndValidate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.SecurityToken == "" {
|
||||
c.SecurityToken = os.Getenv("SECURITY_TOKEN")
|
||||
}
|
||||
client := ecs.NewECSClientWithSecurityToken(c.AlicloudAccessKey, c.AlicloudSecretKey,
|
||||
c.SecurityToken, common.Region(c.AlicloudRegion))
|
||||
|
||||
client.SetBusinessInfo("Packer")
|
||||
if _, err := client.DescribeRegions(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return 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.AlicloudSkipValidation {
|
||||
if c.validateRegion() != nil {
|
||||
errs = append(errs, fmt.Errorf("Unknown alicloud region: %s", c.AlicloudRegion))
|
||||
}
|
||||
}
|
||||
|
||||
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) loadAndValidate() error {
|
||||
if err := c.validateRegion(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AlicloudAccessConfig) validateRegion() error {
|
||||
|
||||
for _, valid := range common.ValidRegions {
|
||||
if c.AlicloudRegion == string(valid) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Not a valid alicloud region: %s", c.AlicloudRegion)
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"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("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AlicloudRegion = "cn-beijing-3"
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
c.AlicloudRegion = "cn-beijing"
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AlicloudRegion = "unknown"
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatalf("should have err")
|
||||
}
|
||||
|
||||
c.AlicloudRegion = "unknown"
|
||||
c.AlicloudSkipValidation = true
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
c.AlicloudSkipValidation = false
|
||||
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/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 *ecs.Client
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
for region, imageId := range a.AlicloudImages {
|
||||
log.Printf("Delete alicloud image ID (%s) from region (%s)", imageId, region)
|
||||
|
||||
// Get alicloud image metadata
|
||||
images, _, err := a.Client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
RegionId: common.Region(region),
|
||||
ImageId: imageId})
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
if len(images) == 0 {
|
||||
err := fmt.Errorf("Error retrieving details for alicloud image(%s), no alicloud images found", imageId)
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
//Unshared the shared account before destroy
|
||||
sharePermissions, err := a.Client.DescribeImageSharePermission(&ecs.ModifyImageSharePermissionArgs{RegionId: common.Region(region), ImageId: imageId})
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
accountsNumber := len(sharePermissions.Accounts.Account)
|
||||
if accountsNumber > 0 {
|
||||
accounts := make([]string, accountsNumber)
|
||||
for index, account := range sharePermissions.Accounts.Account {
|
||||
accounts[index] = account.AliyunId
|
||||
}
|
||||
err := a.Client.ModifyImageSharePermission(&ecs.ModifyImageSharePermissionArgs{
|
||||
|
||||
RegionId: common.Region(region),
|
||||
ImageId: imageId,
|
||||
RemoveAccount: accounts,
|
||||
})
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
// Delete alicloud images
|
||||
if err := a.Client.DeleteImage(common.Region(region), imageId); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
//Delete the snapshot of this images
|
||||
for _, diskDevices := range images[0].DiskDeviceMappings.DiskDeviceMapping {
|
||||
if err := a.Client.DeleteSnapshot(diskDevices.SnapshotId); 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) stateAtlasMetadata() interface{} {
|
||||
metadata := make(map[string]string)
|
||||
for region, imageId := range a.AlicloudImages {
|
||||
k := fmt.Sprintf("region.%s", region)
|
||||
metadata[k] = imageId
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
// The alicloud contains a packer.Builder implementation that
|
||||
// builds ecs images for alicloud.
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"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 (
|
||||
ClassicNet = InstanceNetWork("classic")
|
||||
VpcNet = InstanceNetWork("vpc")
|
||||
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
|
||||
}
|
||||
|
||||
// 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(ui packer.Ui, hook packer.Hook, cache packer.Cache) (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() == VpcNet {
|
||||
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() == VpcNet {
|
||||
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{},
|
||||
&stepMountAlicloudDisk{},
|
||||
&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,
|
||||
},
|
||||
&stepCreateAlicloudImage{},
|
||||
&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(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) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) chooseNetworkType() InstanceNetWork {
|
||||
if b.isVpcNetRequired() {
|
||||
return VpcNet
|
||||
} else {
|
||||
return ClassicNet
|
||||
}
|
||||
}
|
||||
|
||||
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 != ""
|
||||
}
|
||||
@@ -1,402 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
builderT "github.com/hashicorp/packer/helper/builder/testing"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() {
|
||||
testAccPreCheck(t)
|
||||
},
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccBasic,
|
||||
})
|
||||
}
|
||||
|
||||
//func TestBuilderAcc_windows(t *testing.T) {
|
||||
// builderT.Test(t, builderT.TestCase{
|
||||
// PreCheck: func() {
|
||||
// testAccPreCheck(t)
|
||||
// },
|
||||
// Builder: &Builder{},
|
||||
// Template: testBuilderAccWindows,
|
||||
// })
|
||||
//}
|
||||
|
||||
//func TestBuilderAcc_regionCopy(t *testing.T) {
|
||||
// builderT.Test(t, builderT.TestCase{
|
||||
// PreCheck: func() {
|
||||
// testAccPreCheck(t)
|
||||
// },
|
||||
// Builder: &Builder{},
|
||||
// Template: testBuilderAccRegionCopy,
|
||||
// Check: checkRegionCopy([]string{"cn-hangzhou", "cn-shenzhen"}),
|
||||
// })
|
||||
//}
|
||||
|
||||
func TestBuilderAcc_forceDelete(t *testing.T) {
|
||||
// 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 TestBuilderAcc_ECSImageSharing(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() {
|
||||
testAccPreCheck(t)
|
||||
},
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccSharing,
|
||||
Check: checkECSImageSharing("1309208528360047"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) {
|
||||
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()
|
||||
images, _, _ := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
ImageName: "packer-test-" + destImageName,
|
||||
RegionId: common.Region("cn-beijing")})
|
||||
|
||||
image := images[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 TestBuilderAcc_imageTags(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() {
|
||||
testAccPreCheck(t)
|
||||
},
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccImageTags,
|
||||
Check: checkImageTags(),
|
||||
})
|
||||
}
|
||||
|
||||
func checkSnapshotsDeleted(snapshotIds []string) builderT.TestCheckFunc {
|
||||
return func(artifacts []packer.Artifact) error {
|
||||
// Verify the snapshots are gone
|
||||
client, _ := testAliyunClient()
|
||||
snapshotResp, _, err := client.DescribeSnapshots(
|
||||
&ecs.DescribeSnapshotsArgs{RegionId: common.Region("cn-beijing"), SnapshotIds: snapshotIds},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Query snapshot failed %v", err)
|
||||
}
|
||||
if len(snapshotResp) > 0 {
|
||||
return fmt.Errorf("Snapshots weren't successfully deleted by " +
|
||||
"`ecs_image_force_delete_snapshots`")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
imageSharePermissionResponse, err := client.DescribeImageSharePermission(
|
||||
&ecs.ModifyImageSharePermissionArgs{
|
||||
RegionId: "cn-beijing",
|
||||
ImageId: artifact.AlicloudImages["cn-beijing"],
|
||||
})
|
||||
|
||||
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(imageSharePermissionResponse.Accounts.Account) != 1 &&
|
||||
imageSharePermissionResponse.Accounts.Account[0].AliyunId != uid {
|
||||
return fmt.Errorf("share account is incorrect %d",
|
||||
len(imageSharePermissionResponse.Accounts.Account))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
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("unknown region: %s", r)
|
||||
}
|
||||
|
||||
delete(regionSet, r)
|
||||
}
|
||||
if len(regionSet) > 0 {
|
||||
return fmt.Errorf("didn't copy to: %#v", regionSet)
|
||||
}
|
||||
client, _ := testAliyunClient()
|
||||
for key, value := range artifact.AlicloudImages {
|
||||
client.WaitForImageReady(common.Region(key), value, 1800)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkImageTags() builderT.TestCheckFunc {
|
||||
return func(artifacts []packer.Artifact) error {
|
||||
if len(artifacts) > 1 {
|
||||
return fmt.Errorf("more than 1 artifact")
|
||||
}
|
||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
||||
artifactRaw := artifacts[0]
|
||||
artifact, ok := artifactRaw.(*Artifact)
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
||||
}
|
||||
// describe the image, get block devices with a snapshot
|
||||
client, _ := testAliyunClient()
|
||||
tags, _, err := client.DescribeTags(
|
||||
&ecs.DescribeTagsArgs{
|
||||
RegionId: "cn-beijing",
|
||||
ResourceType: ecs.TagResourceImage,
|
||||
ResourceId: artifact.AlicloudImages["cn-beijing"],
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error retrieving Image Attributes for ECS Image Artifact (%#v) "+
|
||||
"in ECS Image Tags Test: %s", artifact, err)
|
||||
}
|
||||
failed := false
|
||||
if len(tags) != 2 {
|
||||
failed = true
|
||||
}
|
||||
if !failed {
|
||||
for i := 0; i < len(tags); i++ {
|
||||
if tags[i].TagKey == "TagKey1" && tags[i].TagValue != "TagValue1" {
|
||||
failed = true
|
||||
} else if tags[i].TagKey == "TagKey2" && tags[i].TagValue != "TagValue2" {
|
||||
failed = true
|
||||
} else if tags[i].TagKey != "TagKey1" && tags[i].TagKey != "TagKey2" {
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
return fmt.Errorf("tags is not correctly set %#v", tags)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
if v := os.Getenv("ALICLOUD_ACCESS_KEY"); v == "" {
|
||||
t.Fatal("ALICLOUD_ACCESS_KEY must be set for acceptance tests")
|
||||
}
|
||||
|
||||
if v := os.Getenv("ALICLOUD_SECRET_KEY"); v == "" {
|
||||
t.Fatal("ALICLOUD_SECRET_KEY must be set for acceptance tests")
|
||||
}
|
||||
}
|
||||
|
||||
func testAliyunClient() (*ecs.Client, 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
|
||||
}
|
||||
|
||||
const testBuilderAccBasic = `
|
||||
{ "builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_20G_alibase_20180409.vhd",
|
||||
"io_optimized":"true",
|
||||
"ssh_username":"root",
|
||||
"image_name": "packer-test_{{timestamp}}"
|
||||
}]
|
||||
}`
|
||||
|
||||
const testBuilderAccRegionCopy = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
|
||||
"io_optimized":"true",
|
||||
"ssh_username":"root",
|
||||
"image_name": "packer-test_{{timestamp}}",
|
||||
"image_copy_regions": ["cn-hangzhou", "cn-shenzhen"]
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccForceDelete = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
|
||||
"io_optimized":"true",
|
||||
"ssh_username":"root",
|
||||
"image_force_delete": "%s",
|
||||
"image_name": "packer-test_%s"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccForceDeleteSnapshot = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
|
||||
"io_optimized":"true",
|
||||
"ssh_username":"root",
|
||||
"image_force_delete_snapshots": "%s",
|
||||
"image_force_delete": "%s",
|
||||
"image_name": "packer-test-%s"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
// share with catsby
|
||||
const testBuilderAccSharing = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
|
||||
"io_optimized":"true",
|
||||
"ssh_username":"root",
|
||||
"image_name": "packer-test_{{timestamp}}",
|
||||
"image_share_account":["1309208528360047"]
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccImageTags = `
|
||||
{ "builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_20G_alibase_20180409.vhd",
|
||||
"ssh_username": "root",
|
||||
"io_optimized":"true",
|
||||
"image_name": "packer-test_{{timestamp}}",
|
||||
"tags": {
|
||||
"TagKey1": "TagValue1",
|
||||
"TagKey2": "TagValue2"
|
||||
}
|
||||
}]
|
||||
}`
|
||||
|
||||
func buildForceDeregisterConfig(val, name string) string {
|
||||
return fmt.Sprintf(testBuilderAccForceDelete, val, name)
|
||||
}
|
||||
|
||||
func buildForceDeleteSnapshotConfig(val, name string) string {
|
||||
return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name)
|
||||
}
|
||||
|
||||
const testBuilderAccWindows = `
|
||||
{ "builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"win2008_64_ent_r2_zh-cn_40G_alibase_20170301.vhd",
|
||||
"io_optimized":"true",
|
||||
"image_force_delete":"true",
|
||||
"communicator": "winrm",
|
||||
"winrm_port": 5985,
|
||||
"winrm_username": "Administrator",
|
||||
"winrm_password": "Test1234",
|
||||
"image_name": "packer-test_{{timestamp}}"
|
||||
}]
|
||||
}`
|
||||
@@ -1,95 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"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")
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"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"`
|
||||
}
|
||||
|
||||
type AlicloudDiskDevices struct {
|
||||
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings"`
|
||||
}
|
||||
|
||||
type AlicloudImageConfig struct {
|
||||
AlicloudImageName string `mapstructure:"image_name"`
|
||||
AlicloudImageVersion string `mapstructure:"image_version"`
|
||||
AlicloudImageDescription string `mapstructure:"image_description"`
|
||||
AlicloudImageShareAccounts []string `mapstructure:"image_share_account"`
|
||||
AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account"`
|
||||
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions"`
|
||||
AlicloudImageDestinationNames []string `mapstructure:"image_copy_names"`
|
||||
AlicloudImageForceDelete bool `mapstructure:"image_force_delete"`
|
||||
AlicloudImageForceDeleteSnapshots bool `mapstructure:"image_force_delete_snapshots"`
|
||||
AlicloudImageForceDeleteInstances bool `mapstructure:"image_force_delete_instances"`
|
||||
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{}{}
|
||||
|
||||
if !c.AlicloudImageSkipRegionValidation {
|
||||
// Verify the region is real
|
||||
if valid := validateRegion(region); valid != nil {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
regions = append(regions, region)
|
||||
}
|
||||
|
||||
c.AlicloudImageDestinationRegions = regions
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateRegion(region string) error {
|
||||
|
||||
for _, valid := range common.ValidRegions {
|
||||
if region == string(valid) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Not a valid alicloud region: %s", region)
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
)
|
||||
|
||||
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 = regionsToString()
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AlicloudImageDestinationRegions = []string{"foo"}
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
c.AlicloudImageDestinationRegions = []string{"cn-beijing", "cn-hangzhou", "eu-central-1"}
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
|
||||
c.AlicloudImageDestinationRegions = []string{"unknow"}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func regionsToString() []string {
|
||||
var regions []string
|
||||
for _, region := range common.ValidRegions {
|
||||
regions = append(regions, string(region))
|
||||
}
|
||||
return regions
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func message(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))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,71 +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"`
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,75 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepAttachKeyPair struct {
|
||||
}
|
||||
|
||||
func (s *stepAttachKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
timeoutPoint := time.Now().Add(120 * time.Second)
|
||||
keyPairName := config.Comm.SSHKeyPairName
|
||||
if keyPairName == "" {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
for {
|
||||
err := client.AttachKeyPair(&ecs.AttachKeyPairArgs{RegionId: common.Region(config.AlicloudRegion),
|
||||
KeyPairName: keyPairName, InstanceIds: "[\"" + instance.InstanceId + "\"]"})
|
||||
if err != nil {
|
||||
e, _ := err.(*common.Error)
|
||||
if (!(e.Code == "MissingParameter" || e.Code == "DependencyViolation.WindowsInstance" ||
|
||||
e.Code == "InvalidKeyPairName.NotFound" || e.Code == "InvalidRegionId.NotFound")) &&
|
||||
time.Now().Before(timeoutPoint) {
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
err := fmt.Errorf("Error attaching keypair %s to instance %s : %s",
|
||||
keyPairName, instance.InstanceId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
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").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
keyPairName := config.Comm.SSHKeyPairName
|
||||
if keyPairName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
err := client.DetachKeyPair(&ecs.DetachKeyPairArgs{RegionId: common.Region(config.AlicloudRegion),
|
||||
KeyPairName: keyPairName, InstanceIds: "[\"" + instance.InstanceId + "\"]"})
|
||||
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,60 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepCheckAlicloudSourceImage struct {
|
||||
SourceECSImageId string
|
||||
}
|
||||
|
||||
func (s *stepCheckAlicloudSourceImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
args := &ecs.DescribeImagesArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
ImageId: config.AlicloudSourceImage,
|
||||
}
|
||||
args.PageSize = 50
|
||||
images, _, err := client.DescribeImages(args)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying alicloud image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Describe markerplace image
|
||||
args.ImageOwnerAlias = ecs.ImageOwnerMarketplace
|
||||
imageMarkets, _, err := client.DescribeImages(args)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying alicloud marketplace image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if len(imageMarkets) > 0 {
|
||||
images = append(images, imageMarkets...)
|
||||
}
|
||||
|
||||
if len(images) == 0 {
|
||||
err := fmt.Errorf("No alicloud image was found matching filters: %v", config.AlicloudSourceImage)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
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) {}
|
||||
@@ -1,94 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepConfigAlicloudEIP struct {
|
||||
AssociatePublicIpAddress bool
|
||||
RegionId string
|
||||
InternetChargeType string
|
||||
InternetMaxBandwidthOut int
|
||||
allocatedId string
|
||||
SSHPrivateIp bool
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudEIP) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
|
||||
if s.SSHPrivateIp {
|
||||
ipaddress := instance.VpcAttributes.PrivateIpAddress.IpAddress
|
||||
if len(ipaddress) == 0 {
|
||||
ui.Say("Failed to get private ip of instance")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("ipaddress", ipaddress[0])
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say("Allocating eip")
|
||||
ipaddress, allocateId, err := client.AllocateEipAddress(&ecs.AllocateEipAddressArgs{
|
||||
RegionId: common.Region(s.RegionId), InternetChargeType: common.InternetChargeType(s.InternetChargeType),
|
||||
Bandwidth: s.InternetMaxBandwidthOut,
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Error allocating eip: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.allocatedId = allocateId
|
||||
if err = client.WaitForEip(common.Region(s.RegionId), allocateId,
|
||||
ecs.EipStatusAvailable, ALICLOUD_DEFAULT_SHORT_TIMEOUT); err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Error allocating eip: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if err = client.AssociateEipAddress(allocateId, instance.InstanceId); err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Error binding eip: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if err = client.WaitForEip(common.Region(s.RegionId), allocateId,
|
||||
ecs.EipStatusInUse, ALICLOUD_DEFAULT_SHORT_TIMEOUT); err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Error associating eip: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Allocated eip %s", ipaddress))
|
||||
state.Put("ipaddress", ipaddress)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudEIP) Cleanup(state multistep.StateBag) {
|
||||
if len(s.allocatedId) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
message(state, "EIP")
|
||||
|
||||
if err := client.UnassociateEipAddress(s.allocatedId, instance.InstanceId); err != nil {
|
||||
ui.Say(fmt.Sprintf("Failed to unassociate eip."))
|
||||
}
|
||||
|
||||
if err := client.WaitForEip(common.Region(s.RegionId), s.allocatedId, ecs.EipStatusAvailable, ALICLOUD_DEFAULT_SHORT_TIMEOUT); err != nil {
|
||||
ui.Say(fmt.Sprintf("Timeout while unassociating eip."))
|
||||
}
|
||||
if err := client.ReleaseEipAddress(s.allocatedId); err != nil {
|
||||
ui.Say(fmt.Sprintf("Failed to release eip."))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/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(_ 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 := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf(
|
||||
"Error loading configured private key file: %s", 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").(*ecs.Client)
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
|
||||
keyResp, err := client.CreateKeyPair(&ecs.CreateKeyPairArgs{
|
||||
KeyPairName: s.Comm.SSHTemporaryKeyPairName,
|
||||
RegionId: common.Region(s.RegionId),
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// 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").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Remove the keypair
|
||||
ui.Say("Deleting temporary keypair...")
|
||||
err := client.DeleteKeyPairs(&ecs.DeleteKeyPairsArgs{
|
||||
RegionId: common.Region(s.RegionId),
|
||||
KeyPairNames: "[\"" + s.keyName + "\"]",
|
||||
})
|
||||
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,47 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/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(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
|
||||
if s.SSHPrivateIp {
|
||||
ipaddress := instance.InnerIpAddress.IpAddress
|
||||
if len(ipaddress) == 0 {
|
||||
ui.Say("Failed to get private ip of instance")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("ipaddress", ipaddress[0])
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ipaddress, err := client.AllocatePublicIpAddress(instance.InstanceId)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Error allocating public ip: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.publicIPAddress = ipaddress
|
||||
ui.Say(fmt.Sprintf("Allocated public ip address %s.", ipaddress))
|
||||
state.Put("ipaddress", ipaddress)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudPublicIP) Cleanup(state multistep.StateBag) {
|
||||
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"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
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudSecurityGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
networkType := state.Get("networktype").(InstanceNetWork)
|
||||
|
||||
var securityGroupItems []ecs.SecurityGroupItemType
|
||||
var err error
|
||||
if len(s.SecurityGroupId) != 0 {
|
||||
if networkType == VpcNet {
|
||||
vpcId := state.Get("vpcid").(string)
|
||||
securityGroupItems, _, err = client.DescribeSecurityGroups(&ecs.DescribeSecurityGroupsArgs{
|
||||
VpcId: vpcId,
|
||||
RegionId: common.Region(s.RegionId),
|
||||
})
|
||||
} else {
|
||||
securityGroupItems, _, err = client.DescribeSecurityGroups(&ecs.DescribeSecurityGroupsArgs{
|
||||
RegionId: common.Region(s.RegionId),
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Failed querying security group: %s", err))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
for _, securityGroupItem := range securityGroupItems {
|
||||
if securityGroupItem.SecurityGroupId == s.SecurityGroupId {
|
||||
state.Put("securitygroupid", s.SecurityGroupId)
|
||||
s.isCreate = false
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
}
|
||||
s.isCreate = false
|
||||
message := fmt.Sprintf("The specified security group {%s} doesn't exist.", s.SecurityGroupId)
|
||||
state.Put("error", errors.New(message))
|
||||
ui.Say(message)
|
||||
return multistep.ActionHalt
|
||||
|
||||
}
|
||||
var securityGroupId string
|
||||
ui.Say("Creating security groups...")
|
||||
if networkType == VpcNet {
|
||||
vpcId := state.Get("vpcid").(string)
|
||||
securityGroupId, err = client.CreateSecurityGroup(&ecs.CreateSecurityGroupArgs{
|
||||
RegionId: common.Region(s.RegionId),
|
||||
SecurityGroupName: s.SecurityGroupName,
|
||||
VpcId: vpcId,
|
||||
})
|
||||
} else {
|
||||
securityGroupId, err = client.CreateSecurityGroup(&ecs.CreateSecurityGroupArgs{
|
||||
RegionId: common.Region(s.RegionId),
|
||||
SecurityGroupName: s.SecurityGroupName,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Failed creating security group %s.", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("securitygroupid", securityGroupId)
|
||||
s.isCreate = true
|
||||
s.SecurityGroupId = securityGroupId
|
||||
err = client.AuthorizeSecurityGroupEgress(&ecs.AuthorizeSecurityGroupEgressArgs{
|
||||
SecurityGroupId: securityGroupId,
|
||||
RegionId: common.Region(s.RegionId),
|
||||
IpProtocol: ecs.IpProtocolAll,
|
||||
PortRange: "-1/-1",
|
||||
NicType: ecs.NicTypeInternet,
|
||||
DestCidrIp: "0.0.0.0/0", //The input parameter "DestGroupId" or "DestCidrIp" cannot be both blank.
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Failed authorizing security group: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
err = client.AuthorizeSecurityGroup(&ecs.AuthorizeSecurityGroupArgs{
|
||||
SecurityGroupId: securityGroupId,
|
||||
RegionId: common.Region(s.RegionId),
|
||||
IpProtocol: ecs.IpProtocolAll,
|
||||
PortRange: "-1/-1",
|
||||
NicType: ecs.NicTypeInternet,
|
||||
SourceCidrIp: "0.0.0.0/0", //The input parameter "SourceGroupId" or "SourceCidrIp" cannot be both blank.
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Failed authorizing security group: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudSecurityGroup) Cleanup(state multistep.StateBag) {
|
||||
if !s.isCreate {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
message(state, "security group")
|
||||
timeoutPoint := time.Now().Add(120 * time.Second)
|
||||
for {
|
||||
if err := client.DeleteSecurityGroup(common.Region(s.RegionId), s.SecurityGroupId); err != nil {
|
||||
e, _ := err.(*common.Error)
|
||||
if e.Code == "DependencyViolation" && time.Now().Before(timeoutPoint) {
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
ui.Error(fmt.Sprintf("Failed to delete security group, it may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"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
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudVPC) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if len(s.VpcId) != 0 {
|
||||
vpcs, _, err := client.DescribeVpcs(&ecs.DescribeVpcsArgs{
|
||||
VpcId: s.VpcId,
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
})
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Failed querying vpcs: %s", err))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if len(vpcs) > 0 {
|
||||
vpc := vpcs[0]
|
||||
state.Put("vpcid", vpc.VpcId)
|
||||
s.isCreate = false
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
message := fmt.Sprintf("The specified vpc {%s} doesn't exist.", s.VpcId)
|
||||
state.Put("error", errors.New(message))
|
||||
ui.Say(message)
|
||||
return multistep.ActionHalt
|
||||
|
||||
}
|
||||
ui.Say("Creating vpc")
|
||||
vpc, err := client.CreateVpc(&ecs.CreateVpcArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
CidrBlock: s.CidrBlock,
|
||||
VpcName: s.VpcName,
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Failed creating vpc: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
err = client.WaitForVpcAvailable(common.Region(config.AlicloudRegion), vpc.VpcId, ALICLOUD_DEFAULT_SHORT_TIMEOUT)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Failed waiting for vpc to become available: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("vpcid", vpc.VpcId)
|
||||
s.isCreate = true
|
||||
s.VpcId = vpc.VpcId
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudVPC) Cleanup(state multistep.StateBag) {
|
||||
if !s.isCreate {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
message(state, "VPC")
|
||||
timeoutPoint := time.Now().Add(60 * time.Second)
|
||||
for {
|
||||
if err := client.DeleteVpc(s.VpcId); err != nil {
|
||||
e, _ := err.(*common.Error)
|
||||
if (e.Code == "DependencyViolation.Instance" || e.Code == "DependencyViolation.RouteEntry" ||
|
||||
e.Code == "DependencyViolation.VSwitch" ||
|
||||
e.Code == "DependencyViolation.SecurityGroup" ||
|
||||
e.Code == "Forbbiden") && time.Now().Before(timeoutPoint) {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
ui.Error(fmt.Sprintf("Error deleting vpc, it may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepConfigAlicloudVSwitch struct {
|
||||
VSwitchId string
|
||||
ZoneId string
|
||||
isCreate bool
|
||||
CidrBlock string
|
||||
VSwitchName string
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudVSwitch) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vpcId := state.Get("vpcid").(string)
|
||||
config := state.Get("config").(*Config)
|
||||
|
||||
if len(s.VSwitchId) != 0 {
|
||||
vswitchs, _, err := client.DescribeVSwitches(&ecs.DescribeVSwitchesArgs{
|
||||
VpcId: vpcId,
|
||||
VSwitchId: s.VSwitchId,
|
||||
ZoneId: s.ZoneId,
|
||||
})
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Failed querying vswitch: %s", err))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if len(vswitchs) > 0 {
|
||||
vswitch := vswitchs[0]
|
||||
state.Put("vswitchid", vswitch.VSwitchId)
|
||||
s.isCreate = false
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
s.isCreate = false
|
||||
message := fmt.Sprintf("The specified vswitch {%s} doesn't exist.", s.VSwitchId)
|
||||
state.Put("error", errors.New(message))
|
||||
ui.Say(message)
|
||||
return multistep.ActionHalt
|
||||
|
||||
}
|
||||
if s.ZoneId == "" {
|
||||
|
||||
zones, err := client.DescribeZones(common.Region(config.AlicloudRegion))
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Query for available zones failed: %s", err))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
var instanceTypes []string
|
||||
for _, zone := range zones {
|
||||
isVSwitchSupported := false
|
||||
for _, resourceType := range zone.AvailableResourceCreation.ResourceTypes {
|
||||
if resourceType == ecs.ResourceTypeVSwitch {
|
||||
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 = "172.16.0.0/24" //use the default CirdBlock
|
||||
}
|
||||
ui.Say("Creating vswitch...")
|
||||
vswitchId, err := client.CreateVSwitch(&ecs.CreateVSwitchArgs{
|
||||
CidrBlock: s.CidrBlock,
|
||||
ZoneId: s.ZoneId,
|
||||
VpcId: vpcId,
|
||||
VSwitchName: s.VSwitchName,
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Create vswitch failed %v", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if err := client.WaitForVSwitchAvailable(vpcId, s.VSwitchId, ALICLOUD_DEFAULT_TIMEOUT); err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(fmt.Sprintf("Timeout waiting for vswitch to become available: %v", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("vswitchid", vswitchId)
|
||||
s.isCreate = true
|
||||
s.VSwitchId = vswitchId
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudVSwitch) Cleanup(state multistep.StateBag) {
|
||||
if !s.isCreate {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
message(state, "vSwitch")
|
||||
timeoutPoint := time.Now().Add(10 * time.Second)
|
||||
for {
|
||||
if err := client.DeleteVSwitch(s.VSwitchId); err != nil {
|
||||
e, _ := err.(*common.Error)
|
||||
if (e.Code == "IncorrectVSwitchStatus" || e.Code == "DependencyViolation" ||
|
||||
e.Code == "DependencyViolation.HaVip" ||
|
||||
e.Code == "IncorrectRouteEntryStatus") && time.Now().Before(timeoutPoint) {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
ui.Error(fmt.Sprintf("Error deleting vswitch, it may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepCreateAlicloudImage struct {
|
||||
image *ecs.ImageType
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Create the alicloud image
|
||||
ui.Say(fmt.Sprintf("Creating image: %s", config.AlicloudImageName))
|
||||
var imageId string
|
||||
var err error
|
||||
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
imageId, err = client.CreateImage(&ecs.CreateImageArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
InstanceId: instance.InstanceId,
|
||||
ImageName: config.AlicloudImageName,
|
||||
ImageVersion: config.AlicloudImageVersion,
|
||||
Description: config.AlicloudImageDescription})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
err = client.WaitForImageReady(common.Region(config.AlicloudRegion),
|
||||
imageId, ALICLOUD_DEFAULT_LONG_TIMEOUT)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Timeout waiting for image to be created: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
ImageId: imageId})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying created image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(images) == 0 {
|
||||
err := fmt.Errorf("Unable to find created image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.image = &images[0]
|
||||
|
||||
state.Put("alicloudimage", imageId)
|
||||
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
|
||||
}
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if !cancelled && !halted {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
config := state.Get("config").(*Config)
|
||||
|
||||
ui.Say("Deleting the image because of cancellation or error...")
|
||||
if err := client.DeleteImage(common.Region(config.AlicloudRegion), s.image.ImageId); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting image, it may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/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.InstanceAttributesType
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
source_image := state.Get("source_image").(*ecs.ImageType)
|
||||
network_type := state.Get("networktype").(InstanceNetWork)
|
||||
securityGroupId := state.Get("securitygroupid").(string)
|
||||
var instanceId string
|
||||
var err error
|
||||
|
||||
ioOptimized := ecs.IoOptimizedNone
|
||||
if s.IOOptimized {
|
||||
ioOptimized = ecs.IoOptimizedOptimized
|
||||
}
|
||||
password := config.Comm.SSHPassword
|
||||
if password == "" && config.Comm.WinRMPassword != "" {
|
||||
password = config.Comm.WinRMPassword
|
||||
}
|
||||
ui.Say("Creating instance.")
|
||||
if network_type == VpcNet {
|
||||
userData, err := s.getUserData(state)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
vswitchId := state.Get("vswitchid").(string)
|
||||
instanceId, err = client.CreateInstance(&ecs.CreateInstanceArgs{
|
||||
RegionId: common.Region(s.RegionId),
|
||||
ImageId: source_image.ImageId,
|
||||
InstanceType: s.InstanceType,
|
||||
InternetChargeType: common.InternetChargeType(s.InternetChargeType), //"PayByTraffic",
|
||||
InternetMaxBandwidthOut: s.InternetMaxBandwidthOut,
|
||||
UserData: userData,
|
||||
IoOptimized: ioOptimized,
|
||||
VSwitchId: vswitchId,
|
||||
SecurityGroupId: securityGroupId,
|
||||
InstanceName: s.InstanceName,
|
||||
Password: password,
|
||||
ZoneId: s.ZoneId,
|
||||
DataDisk: diskDeviceToDiskType(config.AlicloudImageConfig.ECSImagesDiskMappings),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
} else {
|
||||
if s.InstanceType == "" {
|
||||
s.InstanceType = "PayByTraffic"
|
||||
}
|
||||
if s.InternetMaxBandwidthOut == 0 {
|
||||
s.InternetMaxBandwidthOut = 5
|
||||
}
|
||||
instanceId, err = client.CreateInstance(&ecs.CreateInstanceArgs{
|
||||
RegionId: common.Region(s.RegionId),
|
||||
ImageId: source_image.ImageId,
|
||||
InstanceType: s.InstanceType,
|
||||
InternetChargeType: common.InternetChargeType(s.InternetChargeType), //"PayByTraffic",
|
||||
InternetMaxBandwidthOut: s.InternetMaxBandwidthOut,
|
||||
IoOptimized: ioOptimized,
|
||||
SecurityGroupId: securityGroupId,
|
||||
InstanceName: s.InstanceName,
|
||||
Password: password,
|
||||
ZoneId: s.ZoneId,
|
||||
DataDisk: diskDeviceToDiskType(config.AlicloudImageConfig.ECSImagesDiskMappings),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
err = client.WaitForInstance(instanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
instance, err := client.DescribeInstanceAttribute(instanceId)
|
||||
if err != nil {
|
||||
ui.Say(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.instance = instance
|
||||
state.Put("instance", instance)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudInstance) Cleanup(state multistep.StateBag) {
|
||||
if s.instance == nil {
|
||||
return
|
||||
}
|
||||
message(state, "instance")
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
err := client.DeleteInstance(s.instance.InstanceId)
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Failed to clean up instance %s: %v", s.instance.InstanceId, err.Error()))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
log.Printf(userData)
|
||||
return userData, nil
|
||||
|
||||
}
|
||||
|
||||
func diskDeviceToDiskType(diskDevices []AlicloudDiskDevice) []ecs.DataDiskType {
|
||||
result := make([]ecs.DataDiskType, len(diskDevices))
|
||||
for _, diskDevice := range diskDevices {
|
||||
result = append(result, ecs.DataDiskType{
|
||||
DiskName: diskDevice.DiskName,
|
||||
Category: ecs.DiskCategory(diskDevice.DiskCategory),
|
||||
Size: diskDevice.DiskSize,
|
||||
SnapshotId: diskDevice.SnapshotId,
|
||||
Description: diskDevice.Description,
|
||||
DeleteWithInstance: diskDevice.DeleteWithInstance,
|
||||
Device: diskDevice.Device,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepCreateTags struct {
|
||||
Tags map[string]string
|
||||
}
|
||||
|
||||
func (s *stepCreateTags) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
imageId := state.Get("alicloudimage").(string)
|
||||
if len(s.Tags) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Adding tags(%s) to image: %s", s.Tags, imageId))
|
||||
err := client.AddTags(&ecs.AddTagsArgs{
|
||||
ResourceId: imageId,
|
||||
ResourceType: ecs.TagResourceImage,
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
Tag: s.Tags,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error Adding tags to image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Say(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
func (s *stepCreateTags) Cleanup(state multistep.StateBag) {
|
||||
// Nothing need to do, tags will be cleaned when the resource is cleaned
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepDeleteAlicloudImageSnapshots struct {
|
||||
AlicloudImageForceDelete bool
|
||||
AlicloudImageForceDeleteSnapshots bool
|
||||
AlicloudImageName string
|
||||
}
|
||||
|
||||
func (s *stepDeleteAlicloudImageSnapshots) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
config := state.Get("config").(*Config)
|
||||
|
||||
// Check for force delete
|
||||
if s.AlicloudImageForceDelete {
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
ImageName: s.AlicloudImageName,
|
||||
})
|
||||
if len(images) < 1 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Deleting duplicated image and snapshot: %s", s.AlicloudImageName))
|
||||
|
||||
for _, image := range images {
|
||||
if image.ImageOwnerAlias != string(ecs.ImageOwnerSelf) {
|
||||
log.Printf("You can only delete instances based on customized images %s ", image.ImageId)
|
||||
continue
|
||||
}
|
||||
err = client.DeleteImage(common.Region(config.AlicloudRegion), image.ImageId)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Failed to delete image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if s.AlicloudImageForceDeleteSnapshots {
|
||||
for _, diskDevice := range image.DiskDeviceMappings.DiskDeviceMapping {
|
||||
if err := client.DeleteSnapshot(diskDevice.SnapshotId); err != nil {
|
||||
err := fmt.Errorf("Deleting ECS snapshot failed: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepDeleteAlicloudImageSnapshots) Cleanup(state multistep.StateBag) {
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepMountAlicloudDisk struct {
|
||||
}
|
||||
|
||||
func (s *stepMountAlicloudDisk) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
alicloudDiskDevices := config.ECSImagesDiskMappings
|
||||
if len(config.ECSImagesDiskMappings) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
ui.Say("Mounting disks.")
|
||||
disks, _, err := client.DescribeDisks(&ecs.DescribeDisksArgs{InstanceId: instance.InstanceId,
|
||||
RegionId: instance.RegionId})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying disks: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
for _, disk := range disks {
|
||||
if disk.Status == ecs.DiskStatusAvailable {
|
||||
if err := client.AttachDisk(&ecs.AttachDiskArgs{DiskId: disk.DiskId,
|
||||
InstanceId: instance.InstanceId,
|
||||
Device: getDevice(&disk, alicloudDiskDevices),
|
||||
}); err != nil {
|
||||
err := fmt.Errorf("Error mounting disks: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, disk := range disks {
|
||||
if err := client.WaitForDisk(instance.RegionId, disk.DiskId, ecs.DiskStatusInUse, ALICLOUD_DEFAULT_SHORT_TIMEOUT); err != nil {
|
||||
err := fmt.Errorf("Timeout waiting for mount: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
ui.Say("Finished mounting disks.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepMountAlicloudDisk) Cleanup(state multistep.StateBag) {
|
||||
|
||||
}
|
||||
|
||||
func getDevice(disk *ecs.DiskItemType, diskDevices []AlicloudDiskDevice) string {
|
||||
if disk.Device != "" {
|
||||
return disk.Device
|
||||
}
|
||||
for _, alicloudDiskDevice := range diskDevices {
|
||||
if alicloudDiskDevice.DiskName == disk.DiskName || alicloudDiskDevice.SnapshotId == disk.SourceSnapshotId {
|
||||
return alicloudDiskDevice.Device
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepPreValidate struct {
|
||||
AlicloudDestImageName string
|
||||
ForceDelete bool
|
||||
}
|
||||
|
||||
func (s *stepPreValidate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
if s.ForceDelete {
|
||||
ui.Say("Force delete flag found, skipping prevalidating image name.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
ui.Say("Prevalidating image name...")
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
ImageName: s.AlicloudDestImageName,
|
||||
RegionId: common.Region(config.AlicloudRegion)})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying alicloud image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(images) > 0 {
|
||||
err := fmt.Errorf("Error: name conflicts with an existing alicloud image: %s", images[0].ImageId)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepPreValidate) Cleanup(multistep.StateBag) {}
|
||||
@@ -1,72 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepRegionCopyAlicloudImage struct {
|
||||
AlicloudImageDestinationRegions []string
|
||||
AlicloudImageDestinationNames []string
|
||||
RegionId string
|
||||
}
|
||||
|
||||
func (s *stepRegionCopyAlicloudImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
if len(s.AlicloudImageDestinationRegions) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
imageId := state.Get("alicloudimage").(string)
|
||||
alicloudImages := state.Get("alicloudimages").(map[string]string)
|
||||
region := common.Region(s.RegionId)
|
||||
|
||||
numberOfName := len(s.AlicloudImageDestinationNames)
|
||||
for index, destinationRegion := range s.AlicloudImageDestinationRegions {
|
||||
if destinationRegion == s.RegionId {
|
||||
continue
|
||||
}
|
||||
ecsImageName := ""
|
||||
if numberOfName > 0 && index < numberOfName {
|
||||
ecsImageName = s.AlicloudImageDestinationNames[index]
|
||||
}
|
||||
imageId, err := client.CopyImage(
|
||||
&ecs.CopyImageArgs{
|
||||
RegionId: region,
|
||||
ImageId: imageId,
|
||||
DestinationRegionId: common.Region(destinationRegion),
|
||||
DestinationImageName: ecsImageName,
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Error copying images: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
alicloudImages[destinationRegion] = imageId
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepRegionCopyAlicloudImage) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if cancelled || halted {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
alicloudImages := state.Get("alicloudimages").(map[string]string)
|
||||
ui.Say(fmt.Sprintf("Stopping copy image because cancellation or error..."))
|
||||
for copiedRegionId, copiedImageId := range alicloudImages {
|
||||
if copiedRegionId == s.RegionId {
|
||||
continue
|
||||
}
|
||||
if err := client.CancelCopyImage(common.Region(copiedRegionId), copiedImageId); err != nil {
|
||||
ui.Say(fmt.Sprintf("Error cancelling copy image: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepRunAlicloudInstance struct {
|
||||
}
|
||||
|
||||
func (s *stepRunAlicloudInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
|
||||
err := client.StartInstance(instance.InstanceId)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error starting instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Starting instance: %s", instance.InstanceId))
|
||||
|
||||
err = client.WaitForInstance(instance.InstanceId, ecs.Running, ALICLOUD_DEFAULT_TIMEOUT)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Timeout waiting for instance to start: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepRunAlicloudInstance) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if cancelled || halted {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
instanceAttribute, _ := client.DescribeInstanceAttribute(instance.InstanceId)
|
||||
if instanceAttribute.Status == ecs.Starting || instanceAttribute.Status == ecs.Running {
|
||||
if err := client.StopInstance(instance.InstanceId, true); err != nil {
|
||||
ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err))
|
||||
return
|
||||
}
|
||||
if err := client.WaitForInstance(instance.InstanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT); err != nil {
|
||||
ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepShareAlicloudImage struct {
|
||||
AlicloudImageShareAccounts []string
|
||||
AlicloudImageUNShareAccounts []string
|
||||
RegionId string
|
||||
}
|
||||
|
||||
func (s *stepShareAlicloudImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
alicloudImages := state.Get("alicloudimages").(map[string]string)
|
||||
for copiedRegion, copiedImageId := range alicloudImages {
|
||||
err := client.ModifyImageSharePermission(
|
||||
&ecs.ModifyImageSharePermissionArgs{
|
||||
RegionId: common.Region(copiedRegion),
|
||||
ImageId: copiedImageId,
|
||||
AddAccount: s.AlicloudImageShareAccounts,
|
||||
RemoveAccount: s.AlicloudImageUNShareAccounts,
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Failed modifying image share permissions: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepShareAlicloudImage) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if cancelled || halted {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
alicloudImages := state.Get("alicloudimages").(map[string]string)
|
||||
ui.Say("Restoring image share permission because cancellations or error...")
|
||||
for copiedRegion, copiedImageId := range alicloudImages {
|
||||
err := client.ModifyImageSharePermission(
|
||||
&ecs.ModifyImageSharePermissionArgs{
|
||||
RegionId: common.Region(copiedRegion),
|
||||
ImageId: copiedImageId,
|
||||
AddAccount: s.AlicloudImageUNShareAccounts,
|
||||
RemoveAccount: s.AlicloudImageShareAccounts,
|
||||
})
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Restoring image share permission failed: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepStopAlicloudInstance struct {
|
||||
ForceStop bool
|
||||
DisableStop bool
|
||||
}
|
||||
|
||||
func (s *stepStopAlicloudInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if !s.DisableStop {
|
||||
ui.Say(fmt.Sprintf("Stopping instance: %s", instance.InstanceId))
|
||||
err := client.StopInstance(instance.InstanceId, s.ForceStop)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error stopping alicloud instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Waiting instance stopped: %s", instance.InstanceId))
|
||||
|
||||
err := client.WaitForInstance(instance.InstanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for alicloud instance to stop: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepStopAlicloudInstance) Cleanup(multistep.StateBag) {
|
||||
// No cleanup...
|
||||
}
|
||||
@@ -9,13 +9,14 @@ import (
|
||||
"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
|
||||
@@ -33,19 +34,16 @@ type Config struct {
|
||||
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"`
|
||||
MountPartition int `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"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
@@ -69,7 +67,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
"ami_description",
|
||||
"snapshot_tags",
|
||||
"tags",
|
||||
"root_volume_tags",
|
||||
"command_wrapper",
|
||||
"post_mount_commands",
|
||||
"pre_mount_commands",
|
||||
@@ -116,8 +113,8 @@ 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
|
||||
@@ -125,8 +122,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
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 {
|
||||
@@ -177,7 +173,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
return warns, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
|
||||
return warns, nil
|
||||
}
|
||||
|
||||
@@ -186,7 +182,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
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, err := session.NewSession(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -202,7 +203,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("awsSession", session)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
|
||||
@@ -219,11 +219,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
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,
|
||||
SourceAmi: b.config.SourceAmi,
|
||||
EnhancedNetworking: b.config.AMIEnhancedNetworking,
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
},
|
||||
&StepCheckRootDevice{},
|
||||
)
|
||||
@@ -233,10 +231,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
&StepFlock{},
|
||||
&StepPrepareDevice{},
|
||||
&StepCreateVolume{
|
||||
RootVolumeType: b.config.RootVolumeType,
|
||||
RootVolumeSize: b.config.RootVolumeSize,
|
||||
RootVolumeTags: b.config.RootVolumeTags,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
&StepAttachVolume{},
|
||||
&StepEarlyUnflock{},
|
||||
@@ -256,29 +251,22 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
&StepEarlyCleanup{},
|
||||
&StepSnapshot{},
|
||||
&awscommon.StepDeregisterAMI{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
ForceDeregister: b.config.AMIForceDeregister,
|
||||
ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot,
|
||||
AMIName: b.config.AMIName,
|
||||
Regions: b.config.AMIRegions,
|
||||
},
|
||||
&StepRegisterAMI{
|
||||
RootVolumeSize: b.config.RootVolumeSize,
|
||||
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
||||
EnableAMIENASupport: b.config.AMIENASupport,
|
||||
RootVolumeSize: b.config.RootVolumeSize,
|
||||
},
|
||||
&awscommon.StepCreateEncryptedAMICopy{
|
||||
KeyID: b.config.AMIKmsKeyId,
|
||||
EncryptBootVolume: b.config.AMIEncryptBootVolume,
|
||||
Name: b.config.AMIName,
|
||||
AMIMappings: b.config.AMIBlockDevices.AMIMappings,
|
||||
},
|
||||
&awscommon.StepAMIRegionCopy{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
Regions: b.config.AMIRegions,
|
||||
RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
|
||||
EncryptBootVolume: b.config.AMIEncryptBootVolume,
|
||||
Name: b.config.AMIName,
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
Regions: b.config.AMIRegions,
|
||||
Name: b.config.AMIName,
|
||||
},
|
||||
&awscommon.StepModifyAMIAttributes{
|
||||
Description: b.config.AMIDescription,
|
||||
@@ -314,7 +302,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
artifact := &awscommon.Artifact{
|
||||
Amis: state.Get("amis").(map[string]string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Session: session,
|
||||
Conn: ec2conn,
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
|
||||
@@ -3,16 +3,13 @@ package chroot
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/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 +27,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 +70,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"},
|
||||
}
|
||||
warnings, err := b.Prepare(config)
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
@@ -143,14 +134,9 @@ func TestBuilderPrepare_CopyFiles(t *testing.T) {
|
||||
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)
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -9,11 +9,10 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// Communicator is a special communicator that works by executing
|
||||
@@ -24,10 +23,8 @@ type Communicator struct {
|
||||
}
|
||||
|
||||
func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
|
||||
// need extra escapes for the command since we're wrapping it in quotes
|
||||
cmd.Command = strconv.Quote(cmd.Command)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestCommunicator_ImplementsCommunicator(t *testing.T) {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package chroot
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDevicePrefixMatch(t *testing.T) {
|
||||
/*
|
||||
if devicePrefixMatch("nvme0n1") != "" {
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ package chroot
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sl "github.com/hashicorp/packer/common/shell-local"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/post-processor/shell-local"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
func RunLocalCommands(commands []string, wrappedCommand CommandWrapper, ctx interpolate.Context, ui packer.Ui) error {
|
||||
@@ -21,9 +21,7 @@ func RunLocalCommands(commands []string, wrappedCommand CommandWrapper, ctx inte
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Executing command: %s", command))
|
||||
comm := &sl.Communicator{
|
||||
ExecuteCommand: []string{"sh", "-c", command},
|
||||
}
|
||||
comm := &shell_local.Communicator{}
|
||||
cmd := &packer.RemoteCmd{Command: command}
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
return fmt.Errorf("Error executing command: %s", err)
|
||||
|
||||
@@ -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(_ 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"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepChrootProvision provisions the instance within a chroot.
|
||||
type StepChrootProvision struct {
|
||||
}
|
||||
|
||||
func (s *StepChrootProvision) Run(_ 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)
|
||||
|
||||
@@ -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(_ 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)
|
||||
|
||||
@@ -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,54 +19,21 @@ 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 {
|
||||
func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
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),
|
||||
VolumeType: aws.String(ec2.VolumeTypeGp2),
|
||||
}
|
||||
|
||||
} else {
|
||||
// Determine the root device snapshot
|
||||
image := state.Get("source_image").(*ec2.Image)
|
||||
@@ -82,19 +46,28 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say("Creating the root volume...")
|
||||
createVolume, err = s.buildCreateVolumeInput(*instance.Placement.AvailabilityZone, rootDevice)
|
||||
if err != nil {
|
||||
if rootDevice == nil {
|
||||
err := fmt.Errorf("Couldn't find root device!")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Creating the root volume...")
|
||||
vs := *rootDevice.Ebs.VolumeSize
|
||||
if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize {
|
||||
vs = s.RootVolumeSize
|
||||
}
|
||||
|
||||
createVolume = &ec2.CreateVolumeInput{
|
||||
AvailabilityZone: instance.Placement.AvailabilityZone,
|
||||
Size: aws.Int64(vs),
|
||||
SnapshotId: rootDevice.Ebs.SnapshotId,
|
||||
VolumeType: rootDevice.Ebs.VolumeType,
|
||||
Iops: rootDevice.Ebs.Iops,
|
||||
}
|
||||
}
|
||||
|
||||
if len(tagSpecs) > 0 {
|
||||
createVolume.SetTagSpecifications(tagSpecs)
|
||||
volTags.Report(ui)
|
||||
}
|
||||
log.Printf("Create args: %+v", createVolume)
|
||||
|
||||
createVolumeResp, err := ec2conn.CreateVolume(createVolume)
|
||||
@@ -110,7 +83,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 +124,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,73 +0,0 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -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(_ 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",
|
||||
|
||||
@@ -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(_ 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)
|
||||
|
||||
|
||||
@@ -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(_ 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"
|
||||
|
||||
@@ -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(_ 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(_ context.Context, state multistep.StateBag) mult
|
||||
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)
|
||||
|
||||
@@ -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,19 +25,15 @@ 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(_ 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)
|
||||
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
|
||||
@@ -51,7 +46,6 @@ func (s *StepMountDevice) Run(_ context.Context, state multistep.StateBag) multi
|
||||
}
|
||||
|
||||
ctx := config.ctx
|
||||
|
||||
ctx.Data = &mountPathData{Device: filepath.Base(device)}
|
||||
mountPath, err := interpolate.Render(config.MountPath, &ctx)
|
||||
|
||||
@@ -80,9 +74,8 @@ func (s *StepMountDevice) Run(_ context.Context, state multistep.StateBag) multi
|
||||
}
|
||||
|
||||
deviceMount := device
|
||||
|
||||
if virtualizationType == "hvm" && s.MountPartition != "0" {
|
||||
deviceMount = fmt.Sprintf("%s%s", device, s.MountPartition)
|
||||
if virtualizationType == "hvm" {
|
||||
deviceMount = fmt.Sprintf("%s%d", device, s.MountPartition)
|
||||
}
|
||||
state.Put("deviceMount", deviceMount)
|
||||
|
||||
@@ -103,7 +96,7 @@ func (s *StepMountDevice) Run(_ context.Context, state multistep.StateBag) multi
|
||||
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 {
|
||||
|
||||
@@ -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(_ 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,10 +1,8 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type postMountCommandsData struct {
|
||||
@@ -18,7 +16,7 @@ type StepPostMountCommands struct {
|
||||
Commands []string
|
||||
}
|
||||
|
||||
func (s *StepPostMountCommands) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepPostMountCommands) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
device := state.Get("device").(string)
|
||||
mountPath := state.Get("mount_path").(string)
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type preMountCommandsData struct {
|
||||
@@ -16,7 +14,7 @@ type StepPreMountCommands struct {
|
||||
Commands []string
|
||||
}
|
||||
|
||||
func (s *StepPreMountCommands) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepPreMountCommands) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
device := state.Get("device").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepPrepareDevice finds an available device and sets it.
|
||||
type StepPrepareDevice struct {
|
||||
}
|
||||
|
||||
func (s *StepPrepareDevice) Run(_ 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)
|
||||
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
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)
|
||||
@@ -78,12 +75,11 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
registerOpts = buildRegisterOpts(config, image, newMappings)
|
||||
}
|
||||
|
||||
if s.EnableAMISriovNetSupport {
|
||||
if config.AMIEnhancedNetworking {
|
||||
// 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.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)
|
||||
@@ -102,8 +98,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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -2,171 +2,118 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"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"
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// AccessConfig is for common configuration related to AWS access
|
||||
type AccessConfig struct {
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2"`
|
||||
DecodeAuthZMessages bool `mapstructure:"decode_authorization_messages"`
|
||||
MFACode string `mapstructure:"mfa_code"`
|
||||
ProfileName string `mapstructure:"profile"`
|
||||
RawRegion string `mapstructure:"region"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
SkipValidation bool `mapstructure:"skip_region_validation"`
|
||||
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
|
||||
Token string `mapstructure:"token"`
|
||||
session *session.Session
|
||||
|
||||
getEC2Connection func() ec2iface.EC2API
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
RawRegion string `mapstructure:"region"`
|
||||
SkipValidation bool `mapstructure:"skip_region_validation"`
|
||||
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)
|
||||
}
|
||||
|
||||
// default is 3, and when it was causing failures for users being throttled
|
||||
config = config.WithMaxRetries(20)
|
||||
|
||||
if c.RawRegion != "" {
|
||||
config = config.WithRegion(c.RawRegion)
|
||||
} else if region := c.metadataRegion(); region != "" {
|
||||
config = config.WithRegion(region)
|
||||
}
|
||||
|
||||
if c.CustomEndpointEc2 != "" {
|
||||
config = config.WithEndpoint(c.CustomEndpointEc2)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if sess, err := session.NewSessionWithOptions(opts); err != nil {
|
||||
region, err := c.Region()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if *sess.Config.Region == "" {
|
||||
return nil, fmt.Errorf("Could not find AWS region, make sure it's set.")
|
||||
} else {
|
||||
log.Printf("Found region %s", *sess.Config.Region)
|
||||
c.session = sess
|
||||
|
||||
cp, err := c.session.Config.Credentials.Get()
|
||||
}
|
||||
config := aws.NewConfig().WithRegion(region).WithMaxRetries(11)
|
||||
if c.ProfileName != "" {
|
||||
profile, err := NewFromProfile(c.ProfileName)
|
||||
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 nil, err
|
||||
}
|
||||
creds, err = profile.CredentialsFromProfile(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
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(session.New(config)),
|
||||
},
|
||||
})
|
||||
}
|
||||
return config.WithCredentials(creds), nil
|
||||
}
|
||||
|
||||
// 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 !c.SkipValidation {
|
||||
if valid := ValidateRegion(c.RawRegion); !valid {
|
||||
return "", fmt.Errorf("Not a valid region: %s", c.RawRegion)
|
||||
}
|
||||
}
|
||||
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
|
||||
return c.RawRegion, nil
|
||||
}
|
||||
|
||||
if c.DecodeAuthZMessages {
|
||||
DecodeAuthZMessages(c.session)
|
||||
}
|
||||
|
||||
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-")
|
||||
}
|
||||
|
||||
// metadataRegion returns the region from the metadata service
|
||||
func (c *AccessConfig) metadataRegion() string {
|
||||
|
||||
client := cleanhttp.DefaultClient()
|
||||
|
||||
// Keep the default timeout (100ms) low as we don't want to wait in non-EC2 environments
|
||||
client.Timeout = 100 * time.Millisecond
|
||||
ec2meta := ec2metadata.New(session.New(), &aws.Config{
|
||||
HTTPClient: client,
|
||||
})
|
||||
region, err := ec2meta.Region()
|
||||
md, err := GetInstanceMetaData("placement/availability-zone")
|
||||
if err != nil {
|
||||
log.Println("Error getting region from metadata service, "+
|
||||
"probably because we're not running on AWS.", err)
|
||||
return ""
|
||||
return "", err
|
||||
}
|
||||
return region
|
||||
|
||||
region := strings.TrimRightFunc(string(md), unicode.IsLetter)
|
||||
return region, nil
|
||||
}
|
||||
|
||||
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
|
||||
if c.SkipMetadataApiCheck {
|
||||
log.Println("(WARN) skip_metadata_api_check ignored.")
|
||||
}
|
||||
// Either both access and secret key must be set or neither of them should
|
||||
// be.
|
||||
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."))
|
||||
}
|
||||
|
||||
if c.RawRegion != "" && !c.SkipValidation {
|
||||
err := c.ValidateRegion(c.RawRegion)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("error validating region: %s", err.Error()))
|
||||
if valid := ValidateRegion(c.RawRegion); !valid {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", c.RawRegion))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AccessConfig) NewEC2Connection() (ec2iface.EC2API, error) {
|
||||
if c.getEC2Connection != nil {
|
||||
return c.getEC2Connection(), nil
|
||||
}
|
||||
sess, err := c.Session()
|
||||
func GetInstanceMetaData(path string) (contents []byte, err error) {
|
||||
url := "http://169.254.169.254/latest/meta-data/" + path
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
return ec2.New(sess), nil
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
err = fmt.Errorf("Code %d returned for url %s", resp.StatusCode, url)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return body, err
|
||||
}
|
||||
|
||||
@@ -2,69 +2,39 @@ 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.")
|
||||
c.RawRegion = "custom"
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatalf("should have err")
|
||||
}
|
||||
|
||||
c.RawRegion = "custom"
|
||||
c.SkipValidation = true
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
c.SkipValidation = false
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// AMIConfig is for common configuration related to creating AMIs.
|
||||
@@ -17,93 +16,24 @@ type AMIConfig struct {
|
||||
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"`
|
||||
AMITags map[string]string `mapstructure:"tags"`
|
||||
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
|
||||
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"`
|
||||
SnapshotTags map[string]string `mapstructure:"snapshot_tags"`
|
||||
SnapshotUsers []string `mapstructure:"snapshot_users"`
|
||||
SnapshotGroups []string `mapstructure:"snapshot_groups"`
|
||||
}
|
||||
|
||||
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)...)
|
||||
|
||||
if len(c.AMIUsers) > 0 && c.AMIEncryptBootVolume {
|
||||
errs = append(errs, fmt.Errorf("Cannot share AMI with encrypted boot volume"))
|
||||
}
|
||||
|
||||
if len(c.SnapshotUsers) > 0 {
|
||||
if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume {
|
||||
errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
|
||||
}
|
||||
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_ami_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 {
|
||||
if !c.AMISkipRegionValidation {
|
||||
// Verify the regions are real
|
||||
err := accessConfig.ValidateRegion(c.AMIRegions...)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("error validating regions: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
regionSet := make(map[string]struct{})
|
||||
regions := make([]string, 0, len(c.AMIRegions))
|
||||
|
||||
@@ -116,23 +46,31 @@ 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 !c.AMISkipRegionValidation {
|
||||
// Verify the region is real
|
||||
if valid := ValidateRegion(region); !valid {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (accessConfig != nil) && (region == accessConfig.RawRegion) {
|
||||
// make sure we don't try to copy to the region we originally
|
||||
// create the AMI in.
|
||||
log.Printf("Cannot copy AMI to AWS session region '%s', deleting it from `ami_regions`.", region)
|
||||
continue
|
||||
}
|
||||
|
||||
regions = append(regions, region)
|
||||
}
|
||||
|
||||
c.AMIRegions = regions
|
||||
}
|
||||
return errs
|
||||
|
||||
if len(c.AMIUsers) > 0 && c.AMIEncryptBootVolume {
|
||||
errs = append(errs, fmt.Errorf("Cannot share AMI with encrypted boot volume"))
|
||||
}
|
||||
|
||||
if len(c.SnapshotUsers) > 0 && len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume {
|
||||
errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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,71 +11,38 @@ 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()
|
||||
c.AMISkipRegionValidation = true
|
||||
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
|
||||
c.AMISkipRegionValidation = true
|
||||
|
||||
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.AMISkipRegionValidation = false
|
||||
c.AMIRegions, err = listEC2Regions(mockConn)
|
||||
if err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err.Error())
|
||||
}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatalf("shouldn't have err: %#v", errs)
|
||||
c.AMIRegions = listEC2Regions()
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AMIRegions = []string{"foo"}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) == 0 {
|
||||
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"}
|
||||
@@ -90,138 +52,25 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
|
||||
c.AMIRegions = []string{"custom"}
|
||||
c.AMISkipRegionValidation = true
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatal("shouldn't have error")
|
||||
}
|
||||
c.AMISkipRegionValidation = false
|
||||
|
||||
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
|
||||
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||
"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",
|
||||
}
|
||||
|
||||
c.AMISkipRegionValidation = true
|
||||
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.AMISkipRegionValidation = false
|
||||
|
||||
c.SnapshotUsers = []string{"foo", "bar"}
|
||||
c.AMIKmsKeyId = "123-abc-456"
|
||||
c.AMIEncryptBootVolume = true
|
||||
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.AMISkipRegionValidation = true
|
||||
c.AMIUsers = []string{"testAccountID"}
|
||||
c.AMIEncryptBootVolume = true
|
||||
|
||||
accessConf := testAccessConfig()
|
||||
|
||||
c.AMIKmsKeyId = ""
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
|
||||
}
|
||||
|
||||
c.AMIKmsKeyId = "89c3fb9a-de87-4f2a-aedc-fddc5138193c"
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAMINameValidation(t *testing.T) {
|
||||
c := testAMIConfig()
|
||||
c.AMISkipRegionValidation = true
|
||||
|
||||
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.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,9 +69,15 @@ 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),
|
||||
})
|
||||
regionConfig := &aws.Config{
|
||||
Credentials: a.Conn.Config.Credentials,
|
||||
Region: aws.String(region),
|
||||
}
|
||||
session, err := session.NewSession(regionConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
regionConn := ec2.New(session)
|
||||
|
||||
// Get image metadata
|
||||
imageResp, err := regionConn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
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
|
||||
@@ -20,7 +19,6 @@ type BlockDevice struct {
|
||||
VirtualName string `mapstructure:"virtual_name"`
|
||||
VolumeType string `mapstructure:"volume_type"`
|
||||
VolumeSize int64 `mapstructure:"volume_size"`
|
||||
KmsKeyId string `mapstructure:"kms_key_id"`
|
||||
}
|
||||
|
||||
type BlockDevices struct {
|
||||
@@ -75,10 +73,6 @@ func buildBlockDevices(b []BlockDevice) []*ec2.BlockDeviceMapping {
|
||||
ebsBlockDevice.Encrypted = aws.Bool(blockDevice.Encrypted)
|
||||
}
|
||||
|
||||
if blockDevice.KmsKeyId != "" {
|
||||
ebsBlockDevice.KmsKeyId = aws.String(blockDevice.KmsKeyId)
|
||||
}
|
||||
|
||||
mapping.Ebs = ebsBlockDevice
|
||||
}
|
||||
|
||||
@@ -87,33 +81,10 @@ 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 == 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 {
|
||||
return buildBlockDevices(b.AMIMappings)
|
||||
}
|
||||
|
||||
@@ -84,27 +84,6 @@ func TestBlockDevice(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Config: &BlockDevice{
|
||||
DeviceName: "/dev/sdb",
|
||||
VolumeType: "gp2",
|
||||
VolumeSize: 8,
|
||||
DeleteOnTermination: true,
|
||||
Encrypted: 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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
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)
|
||||
session, err := session.NewSession(srcCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
svc := sts.New(session)
|
||||
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)
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 +1,6 @@
|
||||
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,
|
||||
}
|
||||
SourceAMI string
|
||||
BuildRegion string
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,35 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
)
|
||||
|
||||
func listEC2Regions(ec2conn ec2iface.EC2API) ([]string, error) {
|
||||
var regions []string
|
||||
resultRegions, err := ec2conn.DescribeRegions(nil)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
// aws ec2 describe-regions --query 'Regions[].{Name:RegionName}' --output text | sed 's/\(.*\)/"\1",/' | sort
|
||||
func listEC2Regions() []string {
|
||||
return []string{
|
||||
"ap-northeast-1",
|
||||
"ap-northeast-2",
|
||||
"ap-south-1",
|
||||
"ap-southeast-1",
|
||||
"ap-southeast-2",
|
||||
"ca-central-1",
|
||||
"eu-central-1",
|
||||
"eu-west-1",
|
||||
"eu-west-2",
|
||||
"sa-east-1",
|
||||
"us-east-1",
|
||||
"us-east-2",
|
||||
"us-west-1",
|
||||
"us-west-2",
|
||||
// not part of autogenerated list
|
||||
"us-gov-west-1",
|
||||
"cn-north-1",
|
||||
}
|
||||
for _, region := range resultRegions.Regions {
|
||||
regions = append(regions, *region.RegionName)
|
||||
}
|
||||
|
||||
return regions, nil
|
||||
}
|
||||
|
||||
// ValidateRegion returns true if the supplied region is a valid AWS
|
||||
// region and false if it's not.
|
||||
func (c *AccessConfig) ValidateRegion(regions ...string) error {
|
||||
ec2conn, err := c.NewEC2Connection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
validRegions, err := listEC2Regions(ec2conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var invalidRegions []string
|
||||
for _, region := range regions {
|
||||
found := false
|
||||
for _, validRegion := range validRegions {
|
||||
if region == validRegion {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
invalidRegions = append(invalidRegions, region)
|
||||
func ValidateRegion(region string) bool {
|
||||
for _, valid := range listEC2Regions() {
|
||||
if region == valid {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalidRegions) > 0 {
|
||||
return fmt.Errorf("Invalid region(s): %v", invalidRegions)
|
||||
}
|
||||
return nil
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
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)$")
|
||||
@@ -25,69 +24,34 @@ 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"`
|
||||
SpotPrice string `mapstructure:"spot_price"`
|
||||
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
|
||||
SpotTags map[string]string `mapstructure:"spot_tags"`
|
||||
SubnetFilter SubnetFilterOptions `mapstructure:"subnet_filter"`
|
||||
SubnetId string `mapstructure:"subnet_id"`
|
||||
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
|
||||
TemporarySGSourceCidr string `mapstructure:"temporary_security_group_source_cidr"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
UserDataFile string `mapstructure:"user_data_file"`
|
||||
VpcFilter VpcFilterOptions `mapstructure:"vpc_filter"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"`
|
||||
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
|
||||
AvailabilityZone string `mapstructure:"availability_zone"`
|
||||
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"`
|
||||
SourceAmiFilter AmiFilterOptions `mapstructure:"source_ami_filter"`
|
||||
SpotPrice string `mapstructure:"spot_price"`
|
||||
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
|
||||
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
|
||||
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"`
|
||||
InstanceInitiatedShutdownBehavior string `mapstructure:"shutdown_behavior"`
|
||||
|
||||
// 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 {
|
||||
@@ -95,10 +59,10 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
// 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 == "" {
|
||||
if c.SSHKeyPairName == "" && c.TemporaryKeyPairName == "" &&
|
||||
c.Comm.SSHPrivateKey == "" && c.Comm.SSHPassword == "" {
|
||||
|
||||
c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
c.TemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
if c.WindowsPasswordTimeout == 0 {
|
||||
@@ -111,62 +75,29 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
|
||||
// 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.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.SSHKeyPairName != "" {
|
||||
if c.Comm.Type == "winrm" && c.Comm.WinRMPassword == "" && c.Comm.SSHPrivateKey == "" {
|
||||
errs = append(errs, errors.New("A private_key_file must be provided to retrieve the winrm password when using ssh_keypair_name."))
|
||||
} else if c.Comm.SSHPrivateKey == "" && !c.Comm.SSHAgentAuth {
|
||||
errs = append(errs, errors.New("A 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."))
|
||||
errs = append(errs, errors.New("A source_ami or source_ami_filter must be specified"))
|
||||
}
|
||||
|
||||
if c.InstanceType == "" {
|
||||
errs = append(errs, fmt.Errorf("An instance_type must be specified"))
|
||||
}
|
||||
|
||||
if c.BlockDurationMinutes%60 != 0 {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"block_duration_minutes must be multiple of 60"))
|
||||
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 != "" {
|
||||
@@ -184,35 +115,11 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
}
|
||||
}
|
||||
|
||||
if c.TemporarySGSourceCidr == "" {
|
||||
c.TemporarySGSourceCidr = "0.0.0.0/0"
|
||||
} else {
|
||||
if _, _, err := net.ParseCIDR(c.TemporarySGSourceCidr); err != nil {
|
||||
errs = append(errs, fmt.Errorf("Error parsing temporary_security_group_source_cidr: %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"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -48,32 +48,22 @@ 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")
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
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)")
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,57 +79,16 @@ func TestRunConfigPrepare_SourceAmiFilterGood(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,13 +119,12 @@ func TestRunConfigPrepare_UserData(t *testing.T) {
|
||||
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 +136,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 +153,27 @@ 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 == "" {
|
||||
if c.TemporaryKeyPairName == "" {
|
||||
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) {
|
||||
if !r.MatchString(c.TemporaryKeyPairName) {
|
||||
t.Fatal("keypair name is not valid")
|
||||
}
|
||||
|
||||
c.Comm.SSHTemporaryKeyPairName = "ssh-key-123"
|
||||
c.TemporaryKeyPairName = "ssh-key-123"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.Comm.SSHTemporaryKeyPairName != "ssh-key-123" {
|
||||
if c.TemporaryKeyPairName != "ssh-key-123" {
|
||||
t.Fatal("keypair name does not match")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,15 @@ package common
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/mitchellh/multistep"
|
||||
packerssh "github.com/mitchellh/packer/communicator/ssh"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
type ec2Describer interface {
|
||||
@@ -20,40 +25,21 @@ var (
|
||||
|
||||
// SSHHost returns a function that can be given to the SSH communicator
|
||||
// for determining the SSH address based on the instance DNS name.
|
||||
func SSHHost(e ec2Describer, sshInterface string) func(multistep.StateBag) (string, error) {
|
||||
func SSHHost(e ec2Describer, 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++ {
|
||||
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 != "" {
|
||||
host = *i.PrivateIpAddress
|
||||
}
|
||||
} else if private && i.PrivateIpAddress != nil && *i.PrivateIpAddress != "" {
|
||||
host = *i.PrivateIpAddress
|
||||
} else if i.PublicDnsName != nil && *i.PublicDnsName != "" {
|
||||
host = *i.PublicDnsName
|
||||
}
|
||||
@@ -77,6 +63,56 @@ func SSHHost(e ec2Describer, sshInterface string) func(multistep.StateBag) (stri
|
||||
time.Sleep(sshHostSleepDuration)
|
||||
}
|
||||
|
||||
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 private key
|
||||
// or password.
|
||||
func SSHConfig(useAgent bool, username, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
if useAgent {
|
||||
authSock := os.Getenv("SSH_AUTH_SOCK")
|
||||
if authSock == "" {
|
||||
return nil, fmt.Errorf("SSH_AUTH_SOCK is not set")
|
||||
}
|
||||
|
||||
sshAgent, err := net.Dial("unix", authSock)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot connect to SSH Agent socket %q: %s", authSock, err)
|
||||
}
|
||||
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
privateKey, hasKey := state.GetOk("privateKey")
|
||||
if hasKey {
|
||||
|
||||
signer, err := ssh.ParsePrivateKey([]byte(privateKey.(string)))
|
||||
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
|
||||
|
||||
} else {
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password(password),
|
||||
ssh.KeyboardInteractive(
|
||||
packerssh.PasswordKeyboardInteractive(password)),
|
||||
}}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,13 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
const (
|
||||
privateIP = "10.0.0.1"
|
||||
publicIP = "192.168.1.1"
|
||||
privateDNS = "private.dns.test"
|
||||
publicDNS = "public.dns.test"
|
||||
privateIP = "10.0.0.1"
|
||||
publicIP = "192.168.1.1"
|
||||
publicDNS = "public.dns.test"
|
||||
)
|
||||
|
||||
func TestSSHHost(t *testing.T) {
|
||||
@@ -21,54 +20,44 @@ func TestSSHHost(t *testing.T) {
|
||||
sshHostSleepDuration = 0
|
||||
|
||||
var cases = []struct {
|
||||
allowTries int
|
||||
vpcId string
|
||||
sshInterface string
|
||||
allowTries int
|
||||
vpcId string
|
||||
private bool
|
||||
|
||||
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, ""},
|
||||
{1, "", false, true, publicDNS},
|
||||
{1, "", true, true, privateIP},
|
||||
{1, "vpc-id", false, true, publicIP},
|
||||
{1, "vpc-id", true, true, privateIP},
|
||||
{2, "", false, true, publicDNS},
|
||||
{2, "", true, true, privateIP},
|
||||
{2, "vpc-id", false, true, publicIP},
|
||||
{2, "vpc-id", true, true, privateIP},
|
||||
{3, "", false, false, ""},
|
||||
{3, "", true, false, ""},
|
||||
{3, "vpc-id", false, false, ""},
|
||||
{3, "vpc-id", true, false, ""},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
testSSHHost(t, c.allowTries, c.vpcId, c.sshInterface, c.ok, c.wantHost)
|
||||
testSSHHost(t, c.allowTries, c.vpcId, c.private, 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)
|
||||
func testSSHHost(t *testing.T, allowTries int, vpcId string, private, ok bool, wantHost string) {
|
||||
t.Logf("allowTries=%d vpcId=%s private=%t ok=%t wantHost=%q", allowTries, vpcId, private, ok, wantHost)
|
||||
|
||||
e := &fakeEC2Describer{
|
||||
allowTries: allowTries,
|
||||
vpcId: vpcId,
|
||||
privateIP: privateIP,
|
||||
publicIP: publicIP,
|
||||
privateDNS: privateDNS,
|
||||
publicDNS: publicDNS,
|
||||
}
|
||||
|
||||
f := SSHHost(e, sshInterface)
|
||||
f := SSHHost(e, private)
|
||||
st := &multistep.BasicStateBag{}
|
||||
st.Put("instance", &ec2.Instance{
|
||||
InstanceId: aws.String("instance-id"),
|
||||
@@ -96,8 +85,8 @@ type fakeEC2Describer struct {
|
||||
allowTries int
|
||||
tries int
|
||||
|
||||
vpcId string
|
||||
privateIP, publicIP, privateDNS, publicDNS string
|
||||
vpcId string
|
||||
privateIP, publicIP, publicDNS string
|
||||
}
|
||||
|
||||
func (d *fakeEC2Describer) DescribeInstances(in *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
|
||||
@@ -115,7 +104,6 @@ func (d *fakeEC2Describer) DescribeInstances(in *ec2.DescribeInstancesInput) (*e
|
||||
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{
|
||||
|
||||
+197
-288
@@ -1,15 +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/hashicorp/packer/helper/multistep"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
// StateRefreshFunc is a function type used for StateChangeConf that is
|
||||
@@ -32,322 +35,228 @@ 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 *ec2.EC2, 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 := SleepSeconds()
|
||||
maxTicks := 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
|
||||
log.Printf("Allowing %ds to complete (change with AWS_TIMEOUT_SECONDS)", seconds)
|
||||
return seconds
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
// Returns 2 seconds by default
|
||||
// AWS async operations sometimes takes long times, if there are multiple parallel builds,
|
||||
// polling at 2 second frequency will exceed the request limit. Allow 2 seconds to be
|
||||
// overwritten with AWS_POLL_DELAY_SECONDS
|
||||
func SleepSeconds() (seconds int) {
|
||||
seconds = 2
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
if envOverrides.awsMaxAttempts.overridden && envOverrides.awsTimeoutSeconds.overridden {
|
||||
log.Printf("WARNING: AWS_MAX_ATTEMPTS and AWS_TIMEOUT_SECONDS are" +
|
||||
" both set. Packer will be using AWS_MAX_ATTEMPTS and discarding " +
|
||||
"AWS_TIMEOUT_SECONDS. If you have not set AWS_POLL_DELAY_SECONDS, " +
|
||||
"Packer will default to a 2 second poll delay.")
|
||||
} else if envOverrides.awsTimeoutSeconds.overridden {
|
||||
log.Printf("DEPRECATION WARNING: env var AWS_TIMEOUT_SECONDS is " +
|
||||
"deprecated in favor of AWS_MAX_ATTEMPTS. If you have not " +
|
||||
"explicitly set AWS_POLL_DELAY_SECONDS, we are defaulting to a " +
|
||||
"poll delay of 2 seconds, regardless of the AWS waiter's default.")
|
||||
maxAttempts := envOverrides.awsTimeoutSeconds.Val / envOverrides.awsPollDelaySeconds.Val
|
||||
// override the delay so we can get the timeout right
|
||||
if !envOverrides.awsPollDelaySeconds.overridden {
|
||||
delaySeconds := request.ConstantWaiterDelay(time.Duration(envOverrides.awsPollDelaySeconds.Val) * time.Second)
|
||||
waitOpts = append(waitOpts, request.WithWaiterDelay(delaySeconds))
|
||||
override := os.Getenv("AWS_POLL_DELAY_SECONDS")
|
||||
if override != "" {
|
||||
n, err := strconv.Atoi(override)
|
||||
if err != nil {
|
||||
log.Printf("Invalid sleep seconds '%s', using default", override)
|
||||
} else {
|
||||
seconds = n
|
||||
}
|
||||
waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(maxAttempts))
|
||||
}
|
||||
if len(waitOpts) == 0 {
|
||||
log.Printf("No AWS timeout and polling overrides have been set. " +
|
||||
"Packer will default to waiter-specific delays and timeouts. If you would " +
|
||||
"like to customize the length of time between retries and max " +
|
||||
"number of retries you may do so by setting the environment " +
|
||||
"variables AWS_POLL_DELAY_SECONDS and AWS_MAX_ATTEMPTS to your " +
|
||||
"desired values.")
|
||||
}
|
||||
|
||||
return waitOpts
|
||||
log.Printf("Using %ds as polling delay (change with AWS_POLL_DELAY_SECONDS)", seconds)
|
||||
return seconds
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,24 @@
|
||||
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/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
|
||||
RegionKeyIds map[string]string
|
||||
EncryptBootVolume bool
|
||||
Name string
|
||||
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)
|
||||
@@ -35,7 +33,6 @@ func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) m
|
||||
|
||||
var lock sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
var regKeyID string
|
||||
errs := new(packer.MultiError)
|
||||
for _, region := range s.Regions {
|
||||
if region == *ec2conn.Config.Region {
|
||||
@@ -47,13 +44,10 @@ func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) m
|
||||
wg.Add(1)
|
||||
ui.Message(fmt.Sprintf("Copying to: %s", region))
|
||||
|
||||
if s.EncryptBootVolume {
|
||||
regKeyID = s.RegionKeyIds[region]
|
||||
}
|
||||
|
||||
go func(region string) {
|
||||
defer wg.Done()
|
||||
id, snapshotIds, err := amiRegionCopy(ctx, state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region, regKeyID)
|
||||
id, snapshotIds, err := amiRegionCopy(state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region)
|
||||
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
amis[region] = id
|
||||
@@ -85,30 +79,27 @@ func (s *StepAMIRegionCopy) Cleanup(state multistep.StateBag) {
|
||||
|
||||
// amiRegionCopy does a copy for the given AMI to the target region and
|
||||
// returns the resulting ID and snapshot IDs, or error.
|
||||
func amiRegionCopy(ctx context.Context, state multistep.StateBag, config *AccessConfig, name string, imageId string,
|
||||
target string, source string, keyID string) (string, []string, error) {
|
||||
func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string, imageId string,
|
||||
target string, source string) (string, []string, error) {
|
||||
snapshotIds := []string{}
|
||||
isEncrypted := false
|
||||
|
||||
// Connect to the region where the AMI will be copied to
|
||||
session, err := config.Session()
|
||||
awsConfig, err := config.Config()
|
||||
if err != nil {
|
||||
return "", snapshotIds, err
|
||||
}
|
||||
// if we've provided a map of key ids to regions, use those keys.
|
||||
if len(keyID) > 0 {
|
||||
isEncrypted = true
|
||||
awsConfig.Region = aws.String(target)
|
||||
|
||||
session, err := session.NewSession(awsConfig)
|
||||
if err != nil {
|
||||
return "", snapshotIds, err
|
||||
}
|
||||
regionconn := ec2.New(session.Copy(&aws.Config{
|
||||
Region: aws.String(target)},
|
||||
))
|
||||
regionconn := ec2.New(session)
|
||||
|
||||
resp, err := regionconn.CopyImage(&ec2.CopyImageInput{
|
||||
SourceRegion: &source,
|
||||
SourceImageId: &imageId,
|
||||
Name: &name,
|
||||
Encrypted: aws.Bool(isEncrypted),
|
||||
KmsKeyId: aws.String(keyID),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -116,8 +107,14 @@ func amiRegionCopy(ctx context.Context, state multistep.StateBag, config *Access
|
||||
imageId, target, err)
|
||||
}
|
||||
|
||||
// Wait for the image to become ready
|
||||
if err := WaitUntilAMIAvailable(ctx, regionconn, *resp.ImageId); err != nil {
|
||||
stateChange := StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
Target: "available",
|
||||
Refresh: AMIStateRefreshFunc(regionconn, *resp.ImageId),
|
||||
StepState: state,
|
||||
}
|
||||
|
||||
if _, err := WaitForState(&stateChange); err != nil {
|
||||
return "", snapshotIds, fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s",
|
||||
*resp.ImageId, target, err)
|
||||
}
|
||||
|
||||
@@ -1,32 +1,37 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
retry "github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/mitchellh/multistep"
|
||||
retry "github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type StepCreateTags struct {
|
||||
Tags TagMap
|
||||
SnapshotTags TagMap
|
||||
Tags map[string]string
|
||||
SnapshotTags map[string]string
|
||||
Ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
session := state.Get("awsSession").(*session.Session)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
amis := state.Get("amis").(map[string]string)
|
||||
|
||||
if !s.Tags.IsSet() && !s.SnapshotTags.IsSet() {
|
||||
var sourceAMI string
|
||||
if rawSourceAMI, hasSourceAMI := state.GetOk("source_image"); hasSourceAMI {
|
||||
sourceAMI = *rawSourceAMI.(*ec2.Image).ImageId
|
||||
} else {
|
||||
sourceAMI = ""
|
||||
}
|
||||
|
||||
if len(s.Tags) == 0 && len(s.SnapshotTags) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
@@ -34,13 +39,23 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
||||
for region, ami := range amis {
|
||||
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami))
|
||||
|
||||
regionConn := ec2.New(session, &aws.Config{
|
||||
Region: aws.String(region),
|
||||
})
|
||||
// Declare list of resources to tag
|
||||
awsConfig := aws.Config{
|
||||
Credentials: ec2conn.Config.Credentials,
|
||||
Region: aws.String(region),
|
||||
}
|
||||
session, err := session.NewSession(&awsConfig)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating AWS session: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
regionconn := ec2.New(session)
|
||||
|
||||
// Retrieve image list for given AMI
|
||||
resourceIds := []*string{&ami}
|
||||
imageResp, err := regionConn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
imageResp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: resourceIds,
|
||||
})
|
||||
|
||||
@@ -72,27 +87,27 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
||||
|
||||
// Convert tags to ec2.Tag format
|
||||
ui.Say("Creating AMI tags")
|
||||
amiTags, err := s.Tags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||
amiTags, err := ConvertToEC2Tags(s.Tags, *ec2conn.Config.Region, sourceAMI, s.Ctx)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
amiTags.Report(ui)
|
||||
ReportTags(ui, amiTags)
|
||||
|
||||
ui.Say("Creating snapshot tags")
|
||||
snapshotTags, err := s.SnapshotTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||
snapshotTags, err := ConvertToEC2Tags(s.SnapshotTags, *ec2conn.Config.Region, sourceAMI, s.Ctx)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
snapshotTags.Report(ui)
|
||||
ReportTags(ui, snapshotTags)
|
||||
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
|
||||
err = retry.Retry(0.2, 30, 11, func() (bool, error) {
|
||||
// Tag images and snapshots
|
||||
_, err := regionConn.CreateTags(&ec2.CreateTagsInput{
|
||||
_, err := regionconn.CreateTags(&ec2.CreateTagsInput{
|
||||
Resources: resourceIds,
|
||||
Tags: amiTags,
|
||||
})
|
||||
@@ -105,7 +120,7 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
||||
|
||||
// Override tags on snapshots
|
||||
if len(snapshotTags) > 0 {
|
||||
_, err = regionConn.CreateTags(&ec2.CreateTagsInput{
|
||||
_, err = regionconn.CreateTags(&ec2.CreateTagsInput{
|
||||
Resources: snapshotIds,
|
||||
Tags: snapshotTags,
|
||||
})
|
||||
@@ -135,3 +150,32 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
||||
func (s *StepCreateTags) Cleanup(state multistep.StateBag) {
|
||||
// No cleanup...
|
||||
}
|
||||
|
||||
func ReportTags(ui packer.Ui, tags []*ec2.Tag) {
|
||||
for _, tag := range tags {
|
||||
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"",
|
||||
aws.StringValue(tag.Key), aws.StringValue(tag.Value)))
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertToEC2Tags(tags map[string]string, region, sourceAmiId string, ctx interpolate.Context) ([]*ec2.Tag, error) {
|
||||
var ec2Tags []*ec2.Tag
|
||||
for key, value := range tags {
|
||||
|
||||
ctx.Data = &BuildInfoTemplate{
|
||||
SourceAMI: sourceAmiId,
|
||||
BuildRegion: region,
|
||||
}
|
||||
interpolatedValue, err := interpolate.Render(value, &ctx)
|
||||
if err != nil {
|
||||
return ec2Tags, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err)
|
||||
}
|
||||
|
||||
ec2Tags = append(ec2Tags, &ec2.Tag{
|
||||
Key: aws.String(key),
|
||||
Value: aws.String(interpolatedValue),
|
||||
})
|
||||
}
|
||||
|
||||
return ec2Tags, nil
|
||||
}
|
||||
|
||||
@@ -1,50 +1,30 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepDeregisterAMI struct {
|
||||
AccessConfig *AccessConfig
|
||||
ForceDeregister bool
|
||||
ForceDeleteSnapshot bool
|
||||
AMIName string
|
||||
Regions []string
|
||||
}
|
||||
|
||||
func (s *StepDeregisterAMI) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
// Check for force deregister
|
||||
if !s.ForceDeregister {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
func (s *StepDeregisterAMI) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
// Add the session region to list of regions will will deregister AMIs in
|
||||
regions := append(s.Regions, *ec2conn.Config.Region)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
for _, region := range regions {
|
||||
// get new connection for each region in which we need to deregister vms
|
||||
session, err := s.AccessConfig.Session()
|
||||
if err != nil {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
regionconn := ec2.New(session.Copy(&aws.Config{
|
||||
Region: aws.String(region)},
|
||||
))
|
||||
|
||||
resp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
Owners: aws.StringSlice([]string{"self"}),
|
||||
// Check for force deregister
|
||||
if s.ForceDeregister {
|
||||
resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
Filters: []*ec2.Filter{{
|
||||
Name: aws.String("name"),
|
||||
Values: aws.StringSlice([]string{s.AMIName}),
|
||||
Values: []*string{aws.String(s.AMIName)},
|
||||
}}})
|
||||
|
||||
if err != nil {
|
||||
@@ -56,7 +36,7 @@ func (s *StepDeregisterAMI) Run(_ context.Context, state multistep.StateBag) mul
|
||||
|
||||
// Deregister image(s) by name
|
||||
for _, i := range resp.Images {
|
||||
_, err := regionconn.DeregisterImage(&ec2.DeregisterImageInput{
|
||||
_, err := ec2conn.DeregisterImage(&ec2.DeregisterImageInput{
|
||||
ImageId: i.ImageId,
|
||||
})
|
||||
|
||||
@@ -71,8 +51,8 @@ func (s *StepDeregisterAMI) Run(_ context.Context, state multistep.StateBag) mul
|
||||
// Delete snapshot(s) by image
|
||||
if s.ForceDeleteSnapshot {
|
||||
for _, b := range i.BlockDeviceMappings {
|
||||
if b.Ebs != nil && aws.StringValue(b.Ebs.SnapshotId) != "" {
|
||||
_, err := regionconn.DeleteSnapshot(&ec2.DeleteSnapshotInput{
|
||||
if b.Ebs != nil {
|
||||
_, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{
|
||||
SnapshotId: b.Ebs.SnapshotId,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepCreateEncryptedAMICopy struct {
|
||||
@@ -16,10 +15,9 @@ type StepCreateEncryptedAMICopy struct {
|
||||
KeyID string
|
||||
EncryptBootVolume bool
|
||||
Name string
|
||||
AMIMappings []BlockDevice
|
||||
}
|
||||
|
||||
func (s *StepCreateEncryptedAMICopy) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
kmsKeyId := s.KeyID
|
||||
@@ -38,14 +36,14 @@ func (s *StepCreateEncryptedAMICopy) Run(ctx context.Context, state multistep.St
|
||||
var region, id string
|
||||
if amis != nil {
|
||||
for region, id = range amis {
|
||||
break // There is only ever one region:ami pair in this map
|
||||
break // Only get the first
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Copying AMI: %s(%s)", region, id))
|
||||
|
||||
if kmsKeyId != "" {
|
||||
ui.Say(fmt.Sprintf("Encrypting with KMS Key ID: %s", kmsKeyId))
|
||||
ui.Say(fmt.Sprintf("Encypting with KMS Key ID: %s", kmsKeyId))
|
||||
}
|
||||
|
||||
copyOpts := &ec2.CopyImageInput{
|
||||
@@ -65,8 +63,15 @@ func (s *StepCreateEncryptedAMICopy) Run(ctx context.Context, state multistep.St
|
||||
}
|
||||
|
||||
// Wait for the copy to become ready
|
||||
stateChange := StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
Target: "available",
|
||||
Refresh: AMIStateRefreshFunc(ec2conn, *copyResp.ImageId),
|
||||
StepState: state,
|
||||
}
|
||||
|
||||
ui.Say("Waiting for AMI copy to become ready...")
|
||||
if err := WaitUntilAMIAvailable(ctx, ec2conn, *copyResp.ImageId); err != nil {
|
||||
if _, err := WaitForState(&stateChange); err != nil {
|
||||
err := fmt.Errorf("Error waiting for AMI Copy: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
@@ -111,18 +116,9 @@ func (s *StepCreateEncryptedAMICopy) Run(ctx context.Context, state multistep.St
|
||||
ui.Say("Deleting unencrypted snapshots")
|
||||
snapshots := state.Get("snapshots").(map[string][]string)
|
||||
|
||||
OuterLoop:
|
||||
for _, blockDevice := range unencImage.BlockDeviceMappings {
|
||||
if blockDevice.Ebs != nil && blockDevice.Ebs.SnapshotId != nil {
|
||||
// If this packer run didn't create it, then don't delete it
|
||||
for _, origDevice := range s.AMIMappings {
|
||||
if origDevice.SnapshotId == *blockDevice.Ebs.SnapshotId {
|
||||
ui.Message(fmt.Sprintf("Keeping Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
|
||||
continue OuterLoop
|
||||
}
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Deleting Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
|
||||
ui.Message(fmt.Sprintf("Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
|
||||
deleteSnapOpts := &ec2.DeleteSnapshotInput{
|
||||
SnapshotId: aws.String(*blockDevice.Ebs.SnapshotId),
|
||||
}
|
||||
@@ -165,7 +161,7 @@ func (s *StepCreateEncryptedAMICopy) Cleanup(state multistep.StateBag) {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Deregistering the AMI because cancellation or error...")
|
||||
ui.Say("Deregistering the AMI because cancelation or error...")
|
||||
deregisterOpts := &ec2.DeregisterImageInput{ImageId: s.image.ImageId}
|
||||
if _, err := ec2conn.DeregisterImage(deregisterOpts); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deregistering AMI, may still be around: %s", err))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
@@ -12,22 +11,20 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepGetPassword reads the password from a Windows server and sets it
|
||||
// on the WinRM config.
|
||||
type StepGetPassword struct {
|
||||
Debug bool
|
||||
Comm *communicator.Config
|
||||
Timeout time.Duration
|
||||
BuildName string
|
||||
Debug bool
|
||||
Comm *communicator.Config
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func (s *StepGetPassword) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepGetPassword) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Skip if we're not using winrm
|
||||
@@ -94,22 +91,16 @@ WaitLoop:
|
||||
ui.Message(fmt.Sprintf(
|
||||
"Password (since debug is enabled): %s", s.Comm.WinRMPassword))
|
||||
}
|
||||
// store so that we can access this later during provisioning
|
||||
|
||||
commonhelper.SetSharedState("winrm_password", s.Comm.WinRMPassword, s.BuildName)
|
||||
packer.LogSecretFilter.Set(s.Comm.WinRMPassword)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepGetPassword) Cleanup(multistep.StateBag) {
|
||||
commonhelper.RemoveSharedStateFile("winrm_password", s.BuildName)
|
||||
}
|
||||
func (s *StepGetPassword) Cleanup(multistep.StateBag) {}
|
||||
|
||||
func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
privateKey := s.Comm.SSHPrivateKey
|
||||
privateKey := state.Get("privateKey").(string)
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -1,74 +1,78 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepKeyPair struct {
|
||||
Debug bool
|
||||
Comm *communicator.Config
|
||||
DebugKeyPath string
|
||||
Debug bool
|
||||
SSHAgentAuth bool
|
||||
DebugKeyPath string
|
||||
TemporaryKeyPairName string
|
||||
KeyPairName string
|
||||
PrivateKeyFile string
|
||||
|
||||
doCleanup bool
|
||||
keyName string
|
||||
}
|
||||
|
||||
func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if s.Comm.SSHPrivateKeyFile != "" {
|
||||
if s.PrivateKeyFile != "" {
|
||||
ui.Say("Using existing SSH private key")
|
||||
privateKeyBytes, err := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile)
|
||||
privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf(
|
||||
"Error loading configured private key file: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.Comm.SSHPrivateKey = privateKeyBytes
|
||||
state.Put("keyPair", s.KeyPairName)
|
||||
state.Put("privateKey", string(privateKeyBytes))
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName == "" {
|
||||
if s.SSHAgentAuth && s.KeyPairName == "" {
|
||||
ui.Say("Using SSH Agent with key pair in Source AMI")
|
||||
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))
|
||||
if s.SSHAgentAuth && s.KeyPairName != "" {
|
||||
ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.KeyPairName))
|
||||
state.Put("keyPair", s.KeyPairName)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHTemporaryKeyPairName == "" {
|
||||
if s.TemporaryKeyPairName == "" {
|
||||
ui.Say("Not using temporary keypair")
|
||||
s.Comm.SSHKeyPairName = ""
|
||||
state.Put("keyPair", "")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
|
||||
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.TemporaryKeyPairName))
|
||||
keyResp, err := ec2conn.CreateKeyPair(&ec2.CreateKeyPairInput{
|
||||
KeyName: &s.Comm.SSHTemporaryKeyPairName})
|
||||
KeyName: &s.TemporaryKeyPairName})
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.doCleanup = true
|
||||
// Set the keyname so we know to delete it later
|
||||
s.keyName = s.TemporaryKeyPairName
|
||||
|
||||
// Set some data for use in future steps
|
||||
s.Comm.SSHKeyPairName = s.Comm.SSHTemporaryKeyPairName
|
||||
s.Comm.SSHPrivateKey = []byte(*keyResp.KeyMaterial)
|
||||
// Set some state data for use in future steps
|
||||
state.Put("keyPair", s.keyName)
|
||||
state.Put("privateKey", *keyResp.KeyMaterial)
|
||||
|
||||
// If we're in debug mode, output the private key to the working
|
||||
// directory.
|
||||
@@ -100,7 +104,10 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
|
||||
}
|
||||
|
||||
func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
|
||||
if !s.doCleanup {
|
||||
// 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.PrivateKeyFile != "" || (s.KeyPairName == "" && s.keyName == "") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -109,10 +116,10 @@ func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
|
||||
|
||||
// Remove the keypair
|
||||
ui.Say("Deleting temporary keypair...")
|
||||
_, err := ec2conn.DeleteKeyPair(&ec2.DeleteKeyPairInput{KeyName: &s.Comm.SSHTemporaryKeyPairName})
|
||||
_, err := ec2conn.DeleteKeyPair(&ec2.DeleteKeyPairInput{KeyName: &s.keyName})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error cleaning up keypair. Please delete the key manually: %s", s.Comm.SSHTemporaryKeyPairName))
|
||||
"Error cleaning up keypair. Please delete the key manually: %s", s.keyName))
|
||||
}
|
||||
|
||||
// Also remove the physical key if we're debugging.
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/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 StepModifyAMIAttributes struct {
|
||||
@@ -22,11 +21,17 @@ type StepModifyAMIAttributes struct {
|
||||
Ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (s *StepModifyAMIAttributes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
session := state.Get("awsSession").(*session.Session)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
amis := state.Get("amis").(map[string]string)
|
||||
|
||||
var sourceAMI string
|
||||
if rawSourceAMI, hasSourceAMI := state.GetOk("source_image"); hasSourceAMI {
|
||||
sourceAMI = *rawSourceAMI.(*ec2.Image).ImageId
|
||||
} else {
|
||||
sourceAMI = ""
|
||||
}
|
||||
snapshots := state.Get("snapshots").(map[string][]string)
|
||||
|
||||
// Determine if there is any work to do.
|
||||
@@ -43,7 +48,10 @@ func (s *StepModifyAMIAttributes) Run(_ context.Context, state multistep.StateBa
|
||||
}
|
||||
|
||||
var err error
|
||||
s.Ctx.Data = extractBuildInfo(*ec2conn.Config.Region, state)
|
||||
s.Ctx.Data = &BuildInfoTemplate{
|
||||
SourceAMI: sourceAMI,
|
||||
BuildRegion: *ec2conn.Config.Region,
|
||||
}
|
||||
s.Description, err = interpolate.Render(s.Description, &s.Ctx)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error interpolating AMI description: %s", err)
|
||||
@@ -144,13 +152,22 @@ func (s *StepModifyAMIAttributes) Run(_ context.Context, state multistep.StateBa
|
||||
// Modifying image attributes
|
||||
for region, ami := range amis {
|
||||
ui.Say(fmt.Sprintf("Modifying attributes on AMI (%s)...", ami))
|
||||
regionConn := ec2.New(session, &aws.Config{
|
||||
Region: aws.String(region),
|
||||
})
|
||||
awsConfig := aws.Config{
|
||||
Credentials: ec2conn.Config.Credentials,
|
||||
Region: aws.String(region),
|
||||
}
|
||||
session, err := session.NewSession(&awsConfig)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating AWS session: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
regionconn := ec2.New(session)
|
||||
for name, input := range options {
|
||||
ui.Message(fmt.Sprintf("Modifying: %s", name))
|
||||
input.ImageId = &ami
|
||||
_, err := regionConn.ModifyImageAttribute(input)
|
||||
_, err := regionconn.ModifyImageAttribute(input)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error modify AMI attributes: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -164,13 +181,16 @@ func (s *StepModifyAMIAttributes) Run(_ context.Context, state multistep.StateBa
|
||||
for region, region_snapshots := range snapshots {
|
||||
for _, snapshot := range region_snapshots {
|
||||
ui.Say(fmt.Sprintf("Modifying attributes on snapshot (%s)...", snapshot))
|
||||
regionConn := ec2.New(session, &aws.Config{
|
||||
Region: aws.String(region),
|
||||
})
|
||||
awsConfig := aws.Config{
|
||||
Credentials: ec2conn.Config.Credentials,
|
||||
Region: aws.String(region),
|
||||
}
|
||||
session := session.New(&awsConfig)
|
||||
regionconn := ec2.New(session)
|
||||
for name, input := range snapshotOptions {
|
||||
ui.Message(fmt.Sprintf("Modifying: %s", name))
|
||||
input.SnapshotId = &snapshot
|
||||
_, err := regionConn.ModifySnapshotAttribute(input)
|
||||
_, err := regionconn.ModifySnapshotAttribute(input)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error modify snapshot attributes: %s", err)
|
||||
state.Put("error", err)
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepModifyEBSBackedInstance struct {
|
||||
EnableAMIENASupport *bool
|
||||
EnableAMISriovNetSupport bool
|
||||
EnableEnhancedNetworking bool
|
||||
}
|
||||
|
||||
func (s *StepModifyEBSBackedInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepModifyEBSBackedInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
|
||||
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
|
||||
if s.EnableAMISriovNetSupport {
|
||||
if s.EnableEnhancedNetworking {
|
||||
// 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)
|
||||
ui.Say("Enabling Enhanced Networking (SR-IOV)...")
|
||||
simple := "simple"
|
||||
_, err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
|
||||
@@ -36,24 +33,16 @@ func (s *StepModifyEBSBackedInstance) Run(_ context.Context, state multistep.Sta
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
// Handle EnaSupport flag.
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
if s.EnableAMIENASupport != nil {
|
||||
var prefix string
|
||||
if *s.EnableAMIENASupport {
|
||||
prefix = "En"
|
||||
} else {
|
||||
prefix = "Dis"
|
||||
}
|
||||
ui.Say(fmt.Sprintf("%sabling Enhanced Networking (ENA)...", prefix))
|
||||
_, err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
|
||||
// Set EnaSupport to true.
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
ui.Say("Enabling Enhanced Networking (ENA)...")
|
||||
_, err = ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
|
||||
InstanceId: instance.InstanceId,
|
||||
EnaSupport: &ec2.AttributeBooleanValue{Value: aws.Bool(*s.EnableAMIENASupport)},
|
||||
EnaSupport: &ec2.AttributeBooleanValue{Value: aws.Bool(true)},
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error %sabling Enhanced Networking (ENA) on %s: %s", strings.ToLower(prefix), *instance.InstanceId, err)
|
||||
err := fmt.Errorf("Error enabling Enhanced Networking (ENA) on %s: %s", *instance.InstanceId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user