Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e8a3fa3ea | |||
| fb1c968d57 | |||
| 141440147f | |||
| bb92eef95e | |||
| 489c75a363 | |||
| 7bbc29d4b2 | |||
| 8e7bb5ec23 |
@@ -1,12 +0,0 @@
|
||||
# Golang CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.11
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- checkout
|
||||
- run: make ci
|
||||
@@ -1,4 +0,0 @@
|
||||
* text=auto
|
||||
*.go text eol=lf
|
||||
*.sh text eol=lf
|
||||
common/test-fixtures/root/* eol=lf
|
||||
@@ -1,270 +0,0 @@
|
||||
# Contributing to Packer
|
||||
|
||||
**First:** if you're unsure or afraid of _anything_, just ask or submit the
|
||||
issue or pull request anyway. You won't be yelled at for giving your best
|
||||
effort. The worst that can happen is that you'll be politely asked to change
|
||||
something. We appreciate any sort of contributions, and don't want a wall of
|
||||
rules to get in the way of that.
|
||||
|
||||
However, for those individuals who want a bit more guidance on the best way to
|
||||
contribute to the project, read on. This document will cover what we're looking
|
||||
for. By addressing all the points we're looking for, it raises the chances we
|
||||
can quickly merge or address your contributions.
|
||||
|
||||
## Issues
|
||||
|
||||
### Reporting an Issue
|
||||
|
||||
* Make sure you test against the latest released version. It is possible we
|
||||
already fixed the bug you're experiencing.
|
||||
|
||||
* Run the command with debug output with the environment variable `PACKER_LOG`.
|
||||
For example: `PACKER_LOG=1 packer build template.json`. Take the _entire_
|
||||
output and create a [gist](https://gist.github.com) for linking to in your
|
||||
issue. Packer should strip sensitive keys from the output, but take a look
|
||||
through just in case.
|
||||
|
||||
* Provide a reproducible test case. If a contributor can't reproduce an issue,
|
||||
then it dramatically lowers the chances it'll get fixed. And in some cases,
|
||||
the issue will eventually be closed.
|
||||
|
||||
* Respond promptly to any questions made by the Packer team to your issue. Stale
|
||||
issues will be closed.
|
||||
|
||||
### Issue Lifecycle
|
||||
|
||||
1. The issue is reported.
|
||||
|
||||
2. The issue is verified and categorized by a Packer collaborator.
|
||||
Categorization is done via tags. For example, bugs are marked as "bugs" and
|
||||
simple fixes are marked as "good first issue".
|
||||
|
||||
3. Unless it is critical, the issue is left for a period of time (sometimes many
|
||||
weeks), giving outside contributors a chance to address the issue.
|
||||
|
||||
4. The issue is addressed in a pull request or commit. The issue will be
|
||||
referenced in the commit message so that the code that fixes it is clearly
|
||||
linked.
|
||||
|
||||
5. The issue is closed.
|
||||
|
||||
## Setting up Go
|
||||
|
||||
If you have never worked with Go before, you will have to install its
|
||||
runtime in order to build packer.
|
||||
|
||||
1. [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.
|
||||
|
||||
2. Once you believe your pull request is ready to be merged, you can remove any
|
||||
"[WIP]" prefix from the title and a core team member will review.
|
||||
|
||||
3. One of Packer's core team members will look over your contribution and
|
||||
either merge, or provide comments letting you know if there is anything left
|
||||
to do. We do our best to provide feedback in a timely manner, but it may take
|
||||
some time for us to respond. We may also have questions that we need answered
|
||||
about the code, either because something doesn't make sense to us or because
|
||||
we want to understand your thought process.
|
||||
|
||||
4. If we have requested changes, you can either make those changes or, if you
|
||||
disagree with the suggested changes, we can have a conversation about our reasoning and agree on a path forward. This may be a multi-step process. Our view is that pull requests are a chance to collaborate, and we welcome
|
||||
conversations about how to do things better.
|
||||
|
||||
5. Once all outstanding comments and checklist items have been addressed, your
|
||||
contribution will be merged! Merged PRs will be included in the next
|
||||
Packer release. The core team takes care of updating the
|
||||
[CHANGELOG.md](../CHANGELOG.md) as they merge.
|
||||
|
||||
6. In rare cases, we might decide that a PR should be closed without merging.
|
||||
We'll make sure to provide clear reasoning when this happens.
|
||||
|
||||
|
||||
### Tips for Working on Packer
|
||||
|
||||
#### Getting Your Pull Requests Merged Faster
|
||||
|
||||
It is much easier to review pull requests that are:
|
||||
|
||||
1. Well-documented: Try to explain in the pull request comments what your
|
||||
change does, why you have made the change, and provide instructions for how
|
||||
to produce the new behavior introduced in the pull request. If you can,
|
||||
provide screen captures or terminal output to show what the changes look
|
||||
like. This helps the reviewers understand and test the change.
|
||||
|
||||
2. Small: Try to only make one change per pull request. If you found two bugs
|
||||
and want to fix them both, that's _awesome_, but it's still best to submit
|
||||
the fixes as separate pull requests. This makes it much easier for reviewers
|
||||
to keep in their heads all of the implications of individual code changes,
|
||||
and that means the PR takes less effort and energy to merge. In general, the
|
||||
smaller the pull request, the sooner reviewers will be able to make time to
|
||||
review it.
|
||||
|
||||
3. Passing Tests: Based on how much time we have, we may not review pull
|
||||
requests which aren't passing our tests. (Look below for advice on how to
|
||||
run unit tests). If you need help figuring out why tests are failing, please
|
||||
feel free to ask, but while we're happy to give guidance it is generally
|
||||
your responsibility to make sure that tests are passing. If your pull request
|
||||
changes an interface or invalidates an assumption that causes a bunch of
|
||||
tests to fail, then you need to fix those tests before we can merge your PR.
|
||||
|
||||
If we request changes, try to make those changes in a timely manner. Otherwise,
|
||||
PRs can go stale and be a lot more work for all of us to merge in the future.
|
||||
|
||||
Even with everyone making their best effort to be responsive, it can be
|
||||
time-consuming to get a PR merged. It can be frustrating to deal with
|
||||
the back-and-forth as we make sure that we understand the changes fully. Please
|
||||
bear with us, and please know that we appreciate the time and energy you put
|
||||
into the project.
|
||||
|
||||
#### Working on forks
|
||||
|
||||
The easiest way to work on a fork is to set it as a remote of the Packer
|
||||
project. After following the steps in "Setting up Go to work on Packer":
|
||||
|
||||
1. Navigate to the code:
|
||||
|
||||
`cd $GOPATH/src/github.com/hashicorp/packer`
|
||||
|
||||
2. Add the remote by running:
|
||||
|
||||
`git remote add <name of remote> <github url of fork>`
|
||||
|
||||
For example:
|
||||
|
||||
`git remote add mwhooker https://github.com/mwhooker/packer.git`
|
||||
|
||||
3. Checkout a feature branch:
|
||||
|
||||
`git checkout -b new-feature`
|
||||
|
||||
4. Make changes.
|
||||
5. (Optional) Push your changes to the fork:
|
||||
|
||||
`git push -u <name of remote> new-feature`
|
||||
|
||||
This way you can push to your fork to create a PR, but the code on disk still
|
||||
lives in the spot where the go cli tools are expecting to find it.
|
||||
|
||||
#### 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.
|
||||
|
||||
@@ -17,10 +12,9 @@ FOR BUGS:
|
||||
|
||||
Describe the problem and include the following information:
|
||||
|
||||
- Packer version from `packer version`
|
||||
- Packer Version
|
||||
- Host platform
|
||||
- Debug log output from `PACKER_LOG=1 packer build template.json`.
|
||||
Please paste this in a gist https://gist.github.com
|
||||
- The _simplest example template and scripts_ needed to reproduce the bug.
|
||||
Include these in your gist https://gist.github.com
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
-12
@@ -4,25 +4,13 @@
|
||||
/src
|
||||
/website/.sass-cache
|
||||
/website/build
|
||||
/website/tmp
|
||||
.DS_Store
|
||||
.vagrant
|
||||
.idea
|
||||
test/.env
|
||||
*~
|
||||
*.received.*
|
||||
*.swp
|
||||
|
||||
website/.bundle
|
||||
website/vendor
|
||||
|
||||
packer-test*.log
|
||||
|
||||
*.received.txt
|
||||
*.received.json
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
Thumbs.db
|
||||
/packer.exe
|
||||
.project
|
||||
|
||||
+6
-20
@@ -1,35 +1,21 @@
|
||||
env:
|
||||
- USER=travis
|
||||
|
||||
os:
|
||||
- windows
|
||||
- linux
|
||||
- osx
|
||||
|
||||
sudo: false
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
- master
|
||||
- 1.6
|
||||
|
||||
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
|
||||
|
||||
+941
-2384
File diff suppressed because it is too large
Load Diff
-29
@@ -1,29 +0,0 @@
|
||||
* @hashicorp/packer
|
||||
|
||||
# builders
|
||||
|
||||
/builder/alicloud/ @chhaj5236
|
||||
/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/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
|
||||
/builder/hcloud/ @LKaemmerling
|
||||
|
||||
# provisioners
|
||||
|
||||
/provisioner/ansible/ @bhcleek
|
||||
/provisioner/converge/ @stevendborrelli
|
||||
|
||||
# post-processors
|
||||
/post-processor/alicloud-import/ dongxiao.zzh@alibaba-inc.com
|
||||
/post-processor/checksum/ v.tolstov@selfip.ru
|
||||
/post-processor/googlecompute-export/ crunkleton@google.com
|
||||
/post-processor/vsphere-template/ nelson@bennu.cl
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
# Contributing to Packer
|
||||
|
||||
**First:** if you're unsure or afraid of _anything_, just ask
|
||||
or submit the issue or pull request anyways. You won't be yelled at for
|
||||
giving your best effort. The worst that can happen is that you'll be
|
||||
politely asked to change something. We appreciate any sort of contributions,
|
||||
and don't want a wall of rules to get in the way of that.
|
||||
|
||||
However, for those individuals who want a bit more guidance on the
|
||||
best way to contribute to the project, read on. This document will cover
|
||||
what we're looking for. By addressing all the points we're looking for,
|
||||
it raises the chances we can quickly merge or address your contributions.
|
||||
|
||||
## Issues
|
||||
|
||||
### Reporting an Issue
|
||||
|
||||
* Make sure you test against the latest released version. It is possible
|
||||
we already fixed the bug you're experiencing.
|
||||
|
||||
* Run the command with debug ouput with the environment variable
|
||||
`PACKER_LOG`. For example: `PACKER_LOG=1 packer build template.json`. Take
|
||||
the *entire* output and create a [gist](https://gist.github.com) for linking
|
||||
to in your issue. Packer should strip sensitive keys from the output,
|
||||
but take a look through just in case.
|
||||
|
||||
* Provide a reproducible test case. If a contributor can't reproduce an
|
||||
issue, then it dramatically lowers the chances it'll get fixed. And in
|
||||
some cases, the issue will eventually be closed.
|
||||
|
||||
* Respond promptly to any questions made by the Packer team to your issue.
|
||||
Stale issues will be closed.
|
||||
|
||||
### Issue Lifecycle
|
||||
|
||||
1. The issue is reported.
|
||||
|
||||
2. The issue is verified and categorized by a Packer collaborator.
|
||||
Categorization is done via tags. For example, bugs are marked as "bugs"
|
||||
and easy fixes are marked as "easy".
|
||||
|
||||
3. Unless it is critical, the issue is left for a period of time (sometimes
|
||||
many weeks), giving outside contributors a chance to address the issue.
|
||||
|
||||
4. The issue is addressed in a pull request or commit. The issue will be
|
||||
referenced in the commit message so that the code that fixes it is clearly
|
||||
linked.
|
||||
|
||||
5. The issue is closed.
|
||||
|
||||
## Setting up Go to work on Packer
|
||||
|
||||
If you have never worked with Go before, you will have to complete the
|
||||
following steps in order to be able to compile and test Packer. These instructions target POSIX-like environments (Mac OS X, Linux, Cygwin, etc.) so you may need to adjust them for Windows or other shells.
|
||||
|
||||
1. [Download](https://golang.org/dl) and install Go. The instructions below
|
||||
are for go 1.6. Earlier versions of Go are no longer supported.
|
||||
|
||||
2. Set and export the `GOPATH` environment variable and update your `PATH`. For
|
||||
example, you can add to your `.bash_profile`.
|
||||
|
||||
```
|
||||
export GOPATH=$HOME/go
|
||||
export PATH=$PATH:$GOPATH/bin
|
||||
```
|
||||
|
||||
3. Download the Packer source (and its dependencies) by running `go get
|
||||
github.com/mitchellh/packer`. This will download the Packer source to
|
||||
`$GOPATH/src/github.com/mitchellh/packer`.
|
||||
|
||||
4. When working on packer `cd $GOPATH/src/github.com/mitchellh/packer` so you
|
||||
can run `make` and easily access other files.
|
||||
|
||||
5. Make your changes to the Packer source. You can run `make` in
|
||||
`$GOPATH/src/github.com/mitchellh/packer` to run tests and build the packer
|
||||
binary. Any compilation errors will be shown when the binaries are
|
||||
rebuilding. If you don't have `make` you can simply run `go build -o bin/packer .` from the project root.
|
||||
|
||||
6. After running building packer successfully, use
|
||||
`$GOPATH/src/github.com/mitchellh/packer/bin/packer` to build a machine and
|
||||
verify your changes work. For instance: `$GOPATH/src/github.com/mitchellh/packer/bin/packer build template.json`.
|
||||
|
||||
7. If everything works well and the tests pass, run `go fmt` on your code
|
||||
before submitting a pull-request.
|
||||
|
||||
### Tips for Working on Packer
|
||||
|
||||
#### Godeps
|
||||
|
||||
If you are submitting a change that requires a change in dependencies, DO NOT update the `vendor/` folder. This keeps the PR smaller and easier to review. Instead, please indicate which upstream has changed and which version we should be using. You _may_ do this using `Godeps/Godeps.json` but this is not required.
|
||||
|
||||
#### Running Unit Tests
|
||||
|
||||
You can run tests for individual packages using commands like this:
|
||||
|
||||
$ make test TEST=./builder/amazon/...
|
||||
|
||||
#### Running Acceptance Tests
|
||||
|
||||
Packer has [acceptance tests](https://en.wikipedia.org/wiki/Acceptance_testing)
|
||||
for various builders. These typically require an API key (AWS, GCE), or
|
||||
additional software to be installed on your computer (VirtualBox, VMware).
|
||||
|
||||
If you're working on a feature of a builder or a new builder and want verify it
|
||||
is functioning (and also hasn't broken anything else), we recommend running the
|
||||
acceptance tests.
|
||||
|
||||
**Warning:** The acceptance tests create/destroy/modify *real resources*, which
|
||||
may incur real costs in some cases. In the presence of a bug, it is technically
|
||||
possible that broken backends could leave dangling data behind. Therefore,
|
||||
please run the acceptance tests at your own risk. At the very least, we
|
||||
recommend running them in their own private account for whatever builder you're
|
||||
testing.
|
||||
|
||||
To run the acceptance tests, invoke `make testacc`:
|
||||
|
||||
$ make testacc TEST=./builder/amazon/ebs
|
||||
...
|
||||
|
||||
The `TEST` variable lets you narrow the scope of the acceptance tests to a
|
||||
specific package / folder. The `TESTARGS` variable is recommended to filter
|
||||
down to a specific resource to test, since testing all of them at once can
|
||||
sometimes take a very long time.
|
||||
|
||||
Acceptance tests typically require other environment variables to be set for
|
||||
things such as access keys. The test itself should error early and tell you
|
||||
what to set, so it is not documented here.
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
FROM ubuntu:16.04
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
locales \
|
||||
openssh-server \
|
||||
sudo
|
||||
|
||||
RUN locale-gen en_US.UTF-8
|
||||
|
||||
RUN if ! getent passwd vagrant; then useradd -d /home/vagrant -m -s /bin/bash vagrant; fi \
|
||||
&& echo 'vagrant ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \
|
||||
&& mkdir -p /etc/sudoers.d \
|
||||
&& echo 'vagrant ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/vagrant \
|
||||
&& chmod 0440 /etc/sudoers.d/vagrant
|
||||
|
||||
RUN mkdir -p /home/vagrant/.ssh \
|
||||
&& chmod 0700 /home/vagrant/.ssh \
|
||||
&& wget --no-check-certificate \
|
||||
https://raw.github.com/hashicorp/vagrant/master/keys/vagrant.pub \
|
||||
-O /home/vagrant/.ssh/authorized_keys \
|
||||
&& chmod 0600 /home/vagrant/.ssh/authorized_keys \
|
||||
&& chown -R vagrant /home/vagrant/.ssh
|
||||
|
||||
RUN mkdir -p /run/sshd
|
||||
|
||||
CMD /usr/sbin/sshd -D \
|
||||
-o UseDNS=no \
|
||||
-o PidFile=/tmp/sshd.pid
|
||||
Generated
+633
@@ -0,0 +1,633 @@
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/packer",
|
||||
"GoVersion": "go1.6",
|
||||
"GodepVersion": "v62",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/ActiveState/tail",
|
||||
"Comment": "v0-41-g1a0242e",
|
||||
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/compute",
|
||||
"Comment": "v2.1.1-beta",
|
||||
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/network",
|
||||
"Comment": "v2.1.1-beta",
|
||||
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/resources/resources",
|
||||
"Comment": "v2.1.1-beta",
|
||||
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/resources/subscriptions",
|
||||
"Comment": "v2.1.1-beta",
|
||||
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/storage",
|
||||
"Comment": "v2.1.1-beta",
|
||||
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/azure-sdk-for-go/storage",
|
||||
"Comment": "v2.1.1-beta",
|
||||
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/go-autorest/autorest",
|
||||
"Comment": "v7.0.4",
|
||||
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/go-autorest/autorest/azure",
|
||||
"Comment": "v7.0.4",
|
||||
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/go-autorest/autorest/date",
|
||||
"Comment": "v7.0.4",
|
||||
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/go-autorest/autorest/to",
|
||||
"Comment": "v7.0.4",
|
||||
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/go-ntlmssp",
|
||||
"Rev": "e0b63eb299a769ea4b04dadfe530f6074b277afb"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/armon/go-radix",
|
||||
"Rev": "4239b77079c7b5d1243b7b4736304ce8ddb6f0f2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/aws",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/awserr",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/awsutil",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/client",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/client/metadata",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/corehandlers",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/defaults",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/ec2metadata",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/request",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/session",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/endpoints",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/restxml",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/signer/v4",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/waiter",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/service/ec2",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/service/s3",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3iface",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3manager",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/service/sts",
|
||||
"Comment": "v1.1.2",
|
||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/bgentry/speakeasy",
|
||||
"Rev": "36e9cfdd690967f4f690c6edcc9ffacd006014a0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/dgrijalva/jwt-go",
|
||||
"Comment": "v2.4.0-11-gf219341",
|
||||
"Rev": "f2193411bd642f7db03249fd79d5292c9b34916a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/digitalocean/godo",
|
||||
"Comment": "v0.9.0-24-g6ca5b77",
|
||||
"Rev": "6ca5b770f203b82a0fca68d0941736458efa8a4f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/dylanmei/iso8601",
|
||||
"Rev": "2075bf119b58e5576c6ed9f867b8f3d17f2e54d4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/dylanmei/winrmtest",
|
||||
"Rev": "025617847eb2cf9bd1d851bc3b22ed28e6245ce5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-ini/ini",
|
||||
"Comment": "v1.8.6",
|
||||
"Rev": "afbd495e5aaea13597b5e14fe514ddeaa4d76fc3"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/golang/protobuf/proto",
|
||||
"Rev": "b982704f8bb716bb608144408cff30e15fbde841"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/go-querystring/query",
|
||||
"Rev": "2a60fc2ba6c19de80291203597d752e9ba58e4c0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/atlas-go/archive",
|
||||
"Comment": "20141209094003-92-g95fa852",
|
||||
"Rev": "95fa852edca41c06c4ce526af4bb7dec4eaad434"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/atlas-go/v1",
|
||||
"Comment": "20141209094003-92-g95fa852",
|
||||
"Rev": "95fa852edca41c06c4ce526af4bb7dec4eaad434"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/errwrap",
|
||||
"Rev": "7554cd9344cec97297fa6649b055a8c98c2a1e55"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/go-checkpoint",
|
||||
"Rev": "e4b2dc34c0f698ee04750bf2035d8b9384233e1b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/go-cleanhttp",
|
||||
"Rev": "875fb671b3ddc66f8e2f0acc33829c8cb989a38d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/go-multierror",
|
||||
"Rev": "d30f09973e19c1dfcd120b2d9c4f168e68d6b5d5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/go-rootcerts",
|
||||
"Rev": "6bb64b370b90e7ef1fa532be9e591a81c3493e00"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/go-version",
|
||||
"Rev": "7e3c02b30806fa5779d3bdfc152ce4c6f40e7b38"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/yamux",
|
||||
"Rev": "df949784da9ed028ee76df44652e42d37a09d7e4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hpcloud/tail/ratelimiter",
|
||||
"Comment": "v0-41-g1a0242e",
|
||||
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hpcloud/tail/util",
|
||||
"Comment": "v0-41-g1a0242e",
|
||||
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hpcloud/tail/watch",
|
||||
"Comment": "v0-41-g1a0242e",
|
||||
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hpcloud/tail/winfile",
|
||||
"Comment": "v0-41-g1a0242e",
|
||||
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jmespath/go-jmespath",
|
||||
"Comment": "0.2.2-2-gc01cf91",
|
||||
"Rev": "c01cf91b011868172fdcd9f41838e80c9d716264"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kardianos/osext",
|
||||
"Rev": "29ae4ffbc9a6fe9fb2bc5029050ce6996ea1d3bc"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/klauspost/compress/flate",
|
||||
"Rev": "f86d2e6d8a77c6a2c4e42a87ded21c6422f7557e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/klauspost/cpuid",
|
||||
"Rev": "349c675778172472f5e8f3a3e0fe187e302e5a10"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/klauspost/crc32",
|
||||
"Rev": "999f3125931f6557b991b2f8472172bdfa578d38"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/klauspost/pgzip",
|
||||
"Rev": "47f36e165cecae5382ecf1ec28ebf7d4679e307d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kr/fs",
|
||||
"Rev": "2788f0dbd16903de03cb8186e5c7d97b69ad387b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/masterzen/simplexml/dom",
|
||||
"Rev": "95ba30457eb1121fa27753627c774c7cd4e90083"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/masterzen/winrm/soap",
|
||||
"Rev": "54ea5d01478cfc2afccec1504bd0dfcd8c260cfa"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/masterzen/winrm/winrm",
|
||||
"Rev": "54ea5d01478cfc2afccec1504bd0dfcd8c260cfa"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/masterzen/xmlpath",
|
||||
"Rev": "13f4951698adc0fa9c1dda3e275d489a24201161"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mattn/go-isatty",
|
||||
"Rev": "56b76bdf51f7708750eac80fa38b952bb9f32639"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/cli",
|
||||
"Rev": "5c87c51cedf76a1737bf5ca3979e8644871598a6"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/go-fs",
|
||||
"Rev": "a34c1b9334e86165685a9449b782f20465eb8c69"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/go-fs/fat",
|
||||
"Rev": "a34c1b9334e86165685a9449b782f20465eb8c69"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/go-homedir",
|
||||
"Rev": "d682a8f0cf139663a984ff12528da460ca963de9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/go-vnc",
|
||||
"Rev": "723ed9867aed0f3209a81151e52ddc61681f0b01"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/iochan",
|
||||
"Rev": "87b45ffd0e9581375c491fef3d32130bb15c5bd7"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/mapstructure",
|
||||
"Rev": "281073eb9eb092240d33ef253c404f1cca550309"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/multistep",
|
||||
"Rev": "162146fc57112954184d90266f4733e900ed05a5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/panicwrap",
|
||||
"Rev": "a1e50bc201f387747a45ffff020f1af2d8759e88"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/prefixedio",
|
||||
"Rev": "6e6954073784f7ee67b28f2d22749d6479151ed7"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/reflectwalk",
|
||||
"Rev": "eecf4c70c626c7cfbb95c90195bc34d386c74ac6"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/nu7hatch/gouuid",
|
||||
"Rev": "179d4d0c4d8d407a32af483c2354df1d2c91e6c3"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/packer-community/winrmcp/winrmcp",
|
||||
"Rev": "f1bcf36a69fa2945e65dd099eee11b560fbd3346"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pierrec/lz4",
|
||||
"Rev": "383c0d87b5dd7c090d3cddefe6ff0c2ffbb88470"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pierrec/xxHash/xxHash32",
|
||||
"Rev": "5a004441f897722c627870a981d02b29924215fa"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pkg/sftp",
|
||||
"Rev": "e84cc8c755ca39b7b64f510fe1fffc1b51f210a5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/openstack",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/openstack/common/extensions",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/flavors",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/images",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/servers",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tenants",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tokens",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v3/tokens",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/openstack/utils",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/pagination",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/testhelper",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud/testhelper/client",
|
||||
"Comment": "v1.0.0-810-g53d1dc4",
|
||||
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/satori/go.uuid",
|
||||
"Rev": "d41af8bb6a7704f00bc3b7cba9355ae6a5a80048"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/tent/http-link-go",
|
||||
"Rev": "ac974c61c2f990f4115b119354b5e0b47550e888"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ugorji/go/codec",
|
||||
"Rev": "646ae4a518c1c3be0739df898118d9bccf993858"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/curve25519",
|
||||
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/md4",
|
||||
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/ssh",
|
||||
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/ssh/agent",
|
||||
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/context",
|
||||
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/context/ctxhttp",
|
||||
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/html",
|
||||
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/html/atom",
|
||||
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2",
|
||||
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2/google",
|
||||
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2/internal",
|
||||
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2/jws",
|
||||
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2/jwt",
|
||||
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/sys/unix",
|
||||
"Rev": "50c6bc5e4292a1d4e65c6e9be5f53be28bcbe28e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/api/compute/v1",
|
||||
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/api/gensupport",
|
||||
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/api/googleapi",
|
||||
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/api/googleapi/internal/uritemplates",
|
||||
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/appengine",
|
||||
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/appengine/internal",
|
||||
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/appengine/internal/app_identity",
|
||||
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/appengine/internal/base",
|
||||
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/appengine/internal/datastore",
|
||||
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/appengine/internal/log",
|
||||
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/appengine/internal/modules",
|
||||
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/appengine/internal/remote_api",
|
||||
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/cloud/compute/metadata",
|
||||
"Rev": "5a3b06f8b5da3b7c3a93da43163b872c86c509ef"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/cloud/internal",
|
||||
"Rev": "5a3b06f8b5da3b7c3a93da43163b872c86c509ef"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/fsnotify.v1",
|
||||
"Comment": "v1.2.9",
|
||||
"Rev": "8611c35ab31c1c28aa903d33cf8b6e44a399b09e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/tomb.v1",
|
||||
"Rev": "dd632973f1e7218eb1089048e0798ec9ae7dceb8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/xmlpath.v2",
|
||||
"Rev": "860cbeca3ebcc600db0b213c0e83ad6ce91f5739"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -4,90 +4,46 @@ 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
|
||||
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')
|
||||
ci: deps test
|
||||
|
||||
# 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)
|
||||
release: deps test releasebin package
|
||||
|
||||
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
|
||||
|
||||
release: deps test releasebin package ## Build a release build
|
||||
|
||||
bin: deps ## Build debug/test build
|
||||
@go get github.com/mitchellh/gox
|
||||
bin: deps
|
||||
@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 version | grep 1.4 ; if [ $$? -eq 0 ]; then \
|
||||
echo "Installing godep and restoring dependencies"; \
|
||||
go get github.com/tools/godep; \
|
||||
godep restore; \
|
||||
fi
|
||||
|
||||
dev: deps ## Build and install a development build
|
||||
dev: deps
|
||||
@grep 'const VersionPrerelease = ""' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \
|
||||
echo "ERROR: You must add prerelease tags to version/version.go prior to making a dev build."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@mkdir -p pkg/$(GOOS)_$(GOARCH)
|
||||
@mkdir -p bin
|
||||
@go install -ldflags '$(GOLDFLAGS)'
|
||||
@cp $(GOPATH)/bin/packer bin/packer
|
||||
@cp $(GOPATH)/bin/packer pkg/$(GOOS)_$(GOARCH)
|
||||
@PACKER_DEV=1 GO15VENDOREXPERIMENT=1 sh -c "$(CURDIR)/scripts/build.sh"
|
||||
|
||||
fmt: ## Format Go code
|
||||
@gofmt -w -s main.go $(UNFORMATTED_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 {} \;
|
||||
fmt:
|
||||
go fmt `go list ./... | grep -v vendor`
|
||||
|
||||
# Install js-beautify with npm install -g js-beautify
|
||||
fmt-examples:
|
||||
@@ -95,31 +51,34 @@ fmt-examples:
|
||||
|
||||
# generate runs `go generate` to build the dynamically generated
|
||||
# source files.
|
||||
generate: deps ## Generate dynamically generated code
|
||||
generate: deps
|
||||
go generate .
|
||||
gofmt -w common/bootcommand/boot_command.go
|
||||
goimports -w common/bootcommand/boot_command.go
|
||||
gofmt -w command/plugin.go
|
||||
go fmt command/plugin.go
|
||||
|
||||
test: fmt-check mode-check vet ## Run unit tests
|
||||
test: deps
|
||||
@go test $(TEST) $(TESTARGS) -timeout=2m
|
||||
|
||||
# testacc runs acceptance tests
|
||||
testacc: deps generate ## Run acceptance tests
|
||||
@echo "WARN: Acceptance tests will take a long time to run and may cost money. Ctrl-C if you want to cancel."
|
||||
PACKER_ACC=1 go test -v $(TEST) $(TESTARGS) -timeout=45m
|
||||
|
||||
testrace: fmt-check mode-check vet ## Test with race detection enabled
|
||||
@go test -race $(TEST) $(TESTARGS) -timeout=2m -p=8
|
||||
|
||||
updatedeps:
|
||||
@echo "INFO: Packer deps are managed by govendor. See .github/CONTRIBUTING.md"
|
||||
|
||||
vet: ## Vet Go code
|
||||
@go tool vet $(VET) ; if [ $$? -eq 1 ]; then \
|
||||
echo "ERROR: Vet found problems in the code."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
help:
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
# testacc runs acceptance tests
|
||||
testacc: deps generate
|
||||
@echo "WARN: Acceptance tests will take a long time to run and may cost money. Ctrl-C if you want to cancel."
|
||||
PACKER_ACC=1 go test -v $(TEST) $(TESTARGS) -timeout=45m
|
||||
|
||||
testrace: deps
|
||||
@go test -race $(TEST) $(TESTARGS) -timeout=2m
|
||||
|
||||
updatedeps:
|
||||
go get -u github.com/mitchellh/gox
|
||||
go get -u golang.org/x/tools/cmd/stringer
|
||||
@echo "INFO: Packer deps are managed by godep. See CONTRIBUTING.md"
|
||||
|
||||
# This is used to add new dependencies to packer. If you are submitting a PR
|
||||
# that includes new dependencies you will need to run this.
|
||||
vendor:
|
||||
godep restore
|
||||
godep save
|
||||
|
||||
.PHONY: bin checkversion ci default deps fmt fmt-examples generate releasebin test testacc testrace updatedeps
|
||||
|
||||
@@ -1,30 +1,28 @@
|
||||
# Packer
|
||||
|
||||
[![Build Status][travis-badge]][travis]
|
||||
[![Windows Build Status][appveyor-badge]][appveyor]
|
||||
[![GoDoc][godoc-badge]][godoc]
|
||||
[![GoReportCard][report-badge]][report]
|
||||
[](https://travis-ci.org/mitchellh/packer)
|
||||
[](https://ci.appveyor.com/project/hashicorp/packer)
|
||||
|
||||
[travis-badge]: https://travis-ci.org/hashicorp/packer.svg?branch=master
|
||||
[travis]: https://travis-ci.org/hashicorp/packer
|
||||
[appveyor-badge]: https://ci.appveyor.com/api/projects/status/miavlgnp989e5obc/branch/master?svg=true
|
||||
[appveyor]: https://ci.appveyor.com/project/hashicorp/packer
|
||||
[godoc-badge]: https://godoc.org/github.com/hashicorp/packer?status.svg
|
||||
[godoc]: https://godoc.org/github.com/hashicorp/packer
|
||||
[report-badge]: https://goreportcard.com/badge/github.com/hashicorp/packer
|
||||
[report]: https://goreportcard.com/report/github.com/hashicorp/packer
|
||||
|
||||
* Website: https://www.packer.io
|
||||
* Website: http://www.packer.io
|
||||
* IRC: `#packer-tool` on Freenode
|
||||
* Mailing list: [Google Groups](https://groups.google.com/forum/#!forum/packer-tool)
|
||||
* Mailing list: [Google Groups](http://groups.google.com/group/packer-tool)
|
||||
|
||||
Packer is a tool for building identical machine images for multiple platforms
|
||||
from a single source configuration.
|
||||
|
||||
Packer is lightweight, runs on every major operating system, and is highly
|
||||
performant, creating machine images for multiple platforms in parallel. Packer
|
||||
comes out of the box with support for many platforms, the full list of which can
|
||||
be found at https://www.packer.io/docs/builders/index.html.
|
||||
comes out of the box with support for the following platforms:
|
||||
|
||||
* Amazon EC2 (AMI). Both EBS-backed and instance-store AMIs
|
||||
* DigitalOcean
|
||||
* Docker
|
||||
* Google Compute Engine
|
||||
* OpenStack
|
||||
* Parallels
|
||||
* QEMU. Both KVM and Xen images.
|
||||
* VirtualBox
|
||||
* VMware
|
||||
|
||||
Support for other platforms can be added via plugins.
|
||||
|
||||
@@ -34,15 +32,13 @@ The images that Packer creates can easily be turned into
|
||||
## Quick Start
|
||||
|
||||
**Note:** There is a great
|
||||
[introduction and getting started guide](https://www.packer.io/intro)
|
||||
[introduction and getting started guide](http://www.packer.io/intro)
|
||||
for those with a bit more patience. Otherwise, the quick start below
|
||||
will get you up and running quickly, at the sacrifice of not explaining some
|
||||
key points.
|
||||
|
||||
First, [download a pre-built Packer
|
||||
binary](https://www.packer.io/downloads.html) for your operating system or
|
||||
[compile Packer
|
||||
yourself](https://github.com/hashicorp/packer/blob/master/.github/CONTRIBUTING.md#setting-up-go-to-work-on-packer).
|
||||
First, [download a pre-built Packer binary](http://www.packer.io/downloads.html)
|
||||
for your operating system or [compile Packer yourself](#developing-packer).
|
||||
|
||||
After Packer is installed, create your first template, which tells Packer
|
||||
what platforms to build images for and how you want to build them. In our
|
||||
@@ -61,8 +57,8 @@ file as `quick-start.json`. Export your AWS credentials as the
|
||||
"access_key": "{{user `access_key`}}",
|
||||
"secret_key": "{{user `secret_key`}}",
|
||||
"region": "us-east-1",
|
||||
"source_ami": "ami-af22d9b9",
|
||||
"instance_type": "t2.micro",
|
||||
"source_ami": "ami-de0d9eb7",
|
||||
"instance_type": "t1.micro",
|
||||
"ssh_username": "ubuntu",
|
||||
"ami_name": "packer-example {{timestamp}}"
|
||||
}]
|
||||
@@ -80,17 +76,14 @@ Packer will build an AMI according to the "quick-start" template. The AMI
|
||||
will be available in your AWS account. To delete the AMI, you must manually
|
||||
delete it using the [AWS console](https://console.aws.amazon.com/). Packer
|
||||
builds your images, it does not manage their lifecycle. Where they go, how
|
||||
they're run, etc., is up to you.
|
||||
they're run, etc. is up to you.
|
||||
|
||||
## Documentation
|
||||
|
||||
Comprehensive documentation is viewable on the Packer website:
|
||||
|
||||
https://www.packer.io/docs
|
||||
http://www.packer.io/docs
|
||||
|
||||
## Developing Packer
|
||||
|
||||
See
|
||||
[CONTRIBUTING.md](https://github.com/hashicorp/packer/blob/master/.github/CONTRIBUTING.md)
|
||||
for best practices and instructions on setting up your development environment
|
||||
to work on Packer.
|
||||
See [CONTRIBUTING.md](https://github.com/mitchellh/packer/blob/master/CONTRIBUTING.md) for best practices and instructions on setting up your development environment to work on Packer.
|
||||
|
||||
Vendored
+41
-85
@@ -1,94 +1,50 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
LINUX_BASE_BOX = "bento/ubuntu-16.04"
|
||||
FREEBSD_BASE_BOX = "jen20/FreeBSD-12.0-CURRENT"
|
||||
$script = <<SCRIPT
|
||||
# Fetch from https://golang.org/dl
|
||||
TARBALL="https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz"
|
||||
|
||||
UNTARPATH="/opt"
|
||||
GOROOT="${UNTARPATH}/go"
|
||||
GOPATH="${UNTARPATH}/gopath"
|
||||
|
||||
# Install Go
|
||||
if [ ! -d ${GOROOT} ]; then
|
||||
sudo wget --progress=bar:force --output-document - ${TARBALL} |\
|
||||
tar xfz - -C ${UNTARPATH}
|
||||
fi
|
||||
|
||||
# Setup the GOPATH
|
||||
sudo mkdir -p ${GOPATH}
|
||||
cat <<EOF >/tmp/gopath.sh
|
||||
export GOROOT="${GOROOT}"
|
||||
export GOPATH="${GOPATH}"
|
||||
export PATH="${GOROOT}/bin:${GOPATH}/bin:\$PATH"
|
||||
EOF
|
||||
sudo mv /tmp/gopath.sh /etc/profile.d/gopath.sh
|
||||
|
||||
# Make sure the GOPATH is usable by vagrant
|
||||
sudo chown -R vagrant:vagrant ${GOROOT}
|
||||
sudo chown -R vagrant:vagrant ${GOPATH}
|
||||
|
||||
# Install some other stuff we need
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y curl make git mercurial bzr zip
|
||||
SCRIPT
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
if Vagrant.has_plugin?("vagrant-cachier")
|
||||
config.cache.scope = :box
|
||||
end
|
||||
config.vm.box = "bento/ubuntu-14.04"
|
||||
|
||||
# Compilation and development boxes
|
||||
config.vm.define "linux", autostart: true, primary: true do |vmCfg|
|
||||
vmCfg.vm.box = LINUX_BASE_BOX
|
||||
vmCfg.vm.hostname = "linux"
|
||||
vmCfg = configureProviders vmCfg,
|
||||
cpus: suggestedCPUCores()
|
||||
config.vm.provision "shell", inline: $script
|
||||
|
||||
vmCfg.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
vmCfg.vm.synced_folder '.',
|
||||
'/opt/gopath/src/github.com/hashicorp/packer'
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
|
||||
vmCfg.vm.provision "shell",
|
||||
privileged: true,
|
||||
inline: 'rm -f /home/vagrant/linux.iso'
|
||||
|
||||
vmCfg.vm.provision "shell",
|
||||
privileged: true,
|
||||
path: './scripts/vagrant-linux-priv-go.sh'
|
||||
|
||||
vmCfg.vm.provision "shell",
|
||||
privileged: true,
|
||||
path: './scripts/vagrant-linux-priv-config.sh'
|
||||
|
||||
vmCfg.vm.provision "shell",
|
||||
privileged: false,
|
||||
path: './scripts/vagrant-linux-unpriv-bootstrap.sh'
|
||||
end
|
||||
|
||||
config.vm.define "freebsd", autostart: false, primary: false do |vmCfg|
|
||||
vmCfg.vm.box = FREEBSD_BASE_BOX
|
||||
vmCfg.vm.hostname = "freebsd"
|
||||
vmCfg = configureProviders vmCfg,
|
||||
cpus: suggestedCPUCores()
|
||||
|
||||
vmCfg.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
vmCfg.vm.synced_folder '.',
|
||||
'/opt/gopath/src/github.com/hashicorp/packer',
|
||||
type: "nfs",
|
||||
bsd__nfs_options: ['noatime']
|
||||
|
||||
vmCfg.vm.provision "shell",
|
||||
privileged: true,
|
||||
path: './scripts/vagrant-freebsd-priv-config.sh'
|
||||
|
||||
vmCfg.vm.provision "shell",
|
||||
privileged: false,
|
||||
path: './scripts/vagrant-freebsd-unpriv-bootstrap.sh'
|
||||
end
|
||||
end
|
||||
|
||||
def configureProviders(vmCfg, cpus: "2", memory: "2048")
|
||||
vmCfg.vm.provider "virtualbox" do |v|
|
||||
v.memory = memory
|
||||
v.cpus = cpus
|
||||
end
|
||||
|
||||
["vmware_fusion", "vmware_workstation"].each do |p|
|
||||
vmCfg.vm.provider p do |v|
|
||||
v.enable_vmrun_ip_lookup = false
|
||||
v.vmx["memsize"] = memory
|
||||
v.vmx["numvcpus"] = cpus
|
||||
end
|
||||
end
|
||||
|
||||
vmCfg.vm.provider "docker" do |d, override|
|
||||
d.build_dir = "."
|
||||
d.has_ssh = true
|
||||
override.vm.box = nil
|
||||
end
|
||||
|
||||
return vmCfg
|
||||
end
|
||||
|
||||
def suggestedCPUCores()
|
||||
case RbConfig::CONFIG['host_os']
|
||||
when /darwin/
|
||||
Integer(`sysctl -n hw.ncpu`) / 2
|
||||
when /linux/
|
||||
Integer(`cat /proc/cpuinfo | grep processor | wc -l`) / 2
|
||||
else
|
||||
2
|
||||
end
|
||||
["vmware_fusion", "vmware_workstation"].each do |p|
|
||||
config.vm.provider "p" do |v|
|
||||
v.vmx["memsize"] = "2048"
|
||||
v.vmx["numvcpus"] = "2"
|
||||
v.vmx["cpuid.coresPerSocket"] = "1"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+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,266 +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,
|
||||
})
|
||||
|
||||
if b.config.AlicloudImageIgnoreDataDisks {
|
||||
steps = append(steps, &stepCreateAlicloudSnapshot{
|
||||
WaitSnapshotReadyTimeout: b.getSnapshotReadyTimeout(),
|
||||
})
|
||||
}
|
||||
|
||||
steps = append(steps,
|
||||
&stepCreateAlicloudImage{
|
||||
AlicloudImageIgnoreDataDisks: b.config.AlicloudImageIgnoreDataDisks,
|
||||
WaitSnapshotReadyTimeout: b.getSnapshotReadyTimeout(),
|
||||
},
|
||||
&stepCreateTags{
|
||||
Tags: b.config.AlicloudImageTags,
|
||||
},
|
||||
&stepRegionCopyAlicloudImage{
|
||||
AlicloudImageDestinationRegions: b.config.AlicloudImageDestinationRegions,
|
||||
AlicloudImageDestinationNames: b.config.AlicloudImageDestinationNames,
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
},
|
||||
&stepShareAlicloudImage{
|
||||
AlicloudImageShareAccounts: b.config.AlicloudImageShareAccounts,
|
||||
AlicloudImageUNShareAccounts: b.config.AlicloudImageUNShareAccounts,
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
})
|
||||
|
||||
// Run!
|
||||
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(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 != ""
|
||||
}
|
||||
|
||||
func (b *Builder) getSnapshotReadyTimeout() int {
|
||||
if b.config.WaitSnapshotReadyTimeout > 0 {
|
||||
return b.config.WaitSnapshotReadyTimeout
|
||||
}
|
||||
|
||||
return ALICLOUD_DEFAULT_LONG_TIMEOUT
|
||||
}
|
||||
@@ -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,234 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func testBuilderConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"access_key": "foo",
|
||||
"secret_key": "bar",
|
||||
"source_image": "foo",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"region": "cn-beijing",
|
||||
"ssh_username": "root",
|
||||
"image_name": "foo",
|
||||
"io_optimized": true,
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
if _, ok := raw.(packer.Builder); !ok {
|
||||
t.Fatalf("Builder should be a builder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_Prepare_BadType(t *testing.T) {
|
||||
b := &Builder{}
|
||||
c := map[string]interface{}{
|
||||
"access_key": []string{},
|
||||
}
|
||||
|
||||
warnings, err := b.Prepare(c)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("prepare should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ECSImageName(t *testing.T) {
|
||||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
|
||||
// Test good
|
||||
config["image_name"] = "ecs.n1.tiny"
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
config["ecs_image_name"] = "foo {{"
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test bad
|
||||
delete(config, "image_name")
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_Devices(t *testing.T) {
|
||||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
config["system_disk_mapping"] = map[string]interface{}{
|
||||
"disk_category": "cloud",
|
||||
"disk_description": "system disk",
|
||||
"disk_name": "system_disk",
|
||||
"disk_size": 60,
|
||||
}
|
||||
config["image_disk_mappings"] = []map[string]interface{}{
|
||||
{
|
||||
"disk_category": "cloud_efficiency",
|
||||
"disk_name": "data_disk1",
|
||||
"disk_size": 100,
|
||||
"disk_snapshot_id": "s-1",
|
||||
"disk_description": "data disk1",
|
||||
"disk_device": "/dev/xvdb",
|
||||
"disk_delete_with_instance": false,
|
||||
},
|
||||
{
|
||||
"disk_name": "data_disk2",
|
||||
"disk_device": "/dev/xvdc",
|
||||
},
|
||||
}
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(b.config.ECSSystemDiskMapping, AlicloudDiskDevice{
|
||||
DiskCategory: "cloud",
|
||||
Description: "system disk",
|
||||
DiskName: "system_disk",
|
||||
DiskSize: 60,
|
||||
}) {
|
||||
t.Fatalf("system disk is not set properly, actual: %#v", b.config.ECSSystemDiskMapping)
|
||||
}
|
||||
if !reflect.DeepEqual(b.config.ECSImagesDiskMappings, []AlicloudDiskDevice{
|
||||
{
|
||||
DiskCategory: "cloud_efficiency",
|
||||
DiskName: "data_disk1",
|
||||
DiskSize: 100,
|
||||
SnapshotId: "s-1",
|
||||
Description: "data disk1",
|
||||
Device: "/dev/xvdb",
|
||||
DeleteWithInstance: false,
|
||||
},
|
||||
{
|
||||
DiskName: "data_disk2",
|
||||
Device: "/dev/xvdc",
|
||||
},
|
||||
}) {
|
||||
t.Fatalf("data disks are not set properly, actual: %#v", b.config.ECSImagesDiskMappings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_IgnoreDataDisks(t *testing.T) {
|
||||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AlicloudImageIgnoreDataDisks != false {
|
||||
t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", false, b.config.AlicloudImageIgnoreDataDisks)
|
||||
}
|
||||
|
||||
config["image_ignore_data_disks"] = "false"
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AlicloudImageIgnoreDataDisks != false {
|
||||
t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", false, b.config.AlicloudImageIgnoreDataDisks)
|
||||
}
|
||||
|
||||
config["image_ignore_data_disks"] = "true"
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AlicloudImageIgnoreDataDisks != true {
|
||||
t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", true, b.config.AlicloudImageIgnoreDataDisks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_WaitSnapshotReadyTimeout(t *testing.T) {
|
||||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.WaitSnapshotReadyTimeout != 0 {
|
||||
t.Fatalf("wait_snapshot_ready_timeout is not set properly, expect: %d, actual: %d", 0, b.config.WaitSnapshotReadyTimeout)
|
||||
}
|
||||
if b.getSnapshotReadyTimeout() != ALICLOUD_DEFAULT_LONG_TIMEOUT {
|
||||
t.Fatalf("default timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_LONG_TIMEOUT, b.getSnapshotReadyTimeout())
|
||||
}
|
||||
|
||||
config["wait_snapshot_ready_timeout"] = ALICLOUD_DEFAULT_TIMEOUT
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.WaitSnapshotReadyTimeout != ALICLOUD_DEFAULT_TIMEOUT {
|
||||
t.Fatalf("wait_snapshot_ready_timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_TIMEOUT, b.config.WaitSnapshotReadyTimeout)
|
||||
}
|
||||
|
||||
if b.getSnapshotReadyTimeout() != ALICLOUD_DEFAULT_TIMEOUT {
|
||||
t.Fatalf("default timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_TIMEOUT, b.getSnapshotReadyTimeout())
|
||||
}
|
||||
}
|
||||
@@ -1,103 +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 {
|
||||
ECSSystemDiskMapping AlicloudDiskDevice `mapstructure:"system_disk_mapping"`
|
||||
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings"`
|
||||
}
|
||||
|
||||
type AlicloudImageConfig struct {
|
||||
AlicloudImageName string `mapstructure:"image_name"`
|
||||
AlicloudImageVersion string `mapstructure:"image_version"`
|
||||
AlicloudImageDescription string `mapstructure:"image_description"`
|
||||
AlicloudImageShareAccounts []string `mapstructure:"image_share_account"`
|
||||
AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account"`
|
||||
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions"`
|
||||
AlicloudImageDestinationNames []string `mapstructure:"image_copy_names"`
|
||||
AlicloudImageForceDelete bool `mapstructure:"image_force_delete"`
|
||||
AlicloudImageForceDeleteSnapshots bool `mapstructure:"image_force_delete_snapshots"`
|
||||
AlicloudImageForceDeleteInstances bool `mapstructure:"image_force_delete_instances"`
|
||||
AlicloudImageIgnoreDataDisks bool `mapstructure:"image_ignore_data_disks"`
|
||||
AlicloudImageSkipRegionValidation bool `mapstructure:"skip_region_validation"`
|
||||
AlicloudImageTags map[string]string `mapstructure:"tags"`
|
||||
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,31 +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))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func halt(state multistep.StateBag, err error, prefix string) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
err = fmt.Errorf("%s: %s", prefix, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/common/uuid"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type RunConfig struct {
|
||||
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
|
||||
ZoneId string `mapstructure:"zone_id"`
|
||||
IOOptimized bool `mapstructure:"io_optimized"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
Description string `mapstructure:"description"`
|
||||
AlicloudSourceImage string `mapstructure:"source_image"`
|
||||
ForceStopInstance bool `mapstructure:"force_stop_instance"`
|
||||
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SecurityGroupName string `mapstructure:"security_group_name"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
UserDataFile string `mapstructure:"user_data_file"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
VpcName string `mapstructure:"vpc_name"`
|
||||
CidrBlock string `mapstructure:"vpc_cidr_block"`
|
||||
VSwitchId string `mapstructure:"vswitch_id"`
|
||||
VSwitchName string `mapstructure:"vswitch_id"`
|
||||
InstanceName string `mapstructure:"instance_name"`
|
||||
InternetChargeType string `mapstructure:"internet_charge_type"`
|
||||
InternetMaxBandwidthOut int `mapstructure:"internet_max_bandwidth_out"`
|
||||
WaitSnapshotReadyTimeout int `mapstructure:"wait_snapshot_ready_timeout"`
|
||||
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
if c.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" &&
|
||||
c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" && c.Comm.WinRMPassword == "" {
|
||||
|
||||
c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
// Validation
|
||||
errs := c.Comm.Prepare(ctx)
|
||||
if c.AlicloudSourceImage == "" {
|
||||
errs = append(errs, errors.New("A source_image must be specified"))
|
||||
}
|
||||
|
||||
if strings.TrimSpace(c.AlicloudSourceImage) != c.AlicloudSourceImage {
|
||||
errs = append(errs, errors.New("The source_image can't include spaces"))
|
||||
}
|
||||
|
||||
if c.InstanceType == "" {
|
||||
errs = append(errs, errors.New("An alicloud_instance_type must be specified"))
|
||||
}
|
||||
|
||||
if c.UserData != "" && c.UserDataFile != "" {
|
||||
errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
|
||||
} else if c.UserDataFile != "" {
|
||||
if _, err := os.Stat(c.UserDataFile); err != nil {
|
||||
errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
@@ -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,134 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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 := s.Comm.ReadSSHPrivateKeyFile()
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.Comm.SSHPrivateKey = privateKeyBytes
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName == "" {
|
||||
ui.Say("Using SSH Agent with key pair in source image")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName != "" {
|
||||
ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.Comm.SSHKeyPairName))
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHTemporaryKeyPairName == "" {
|
||||
ui.Say("Not using temporary keypair")
|
||||
s.Comm.SSHKeyPairName = ""
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
client := state.Get("client").(*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,102 +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 {
|
||||
AlicloudImageIgnoreDataDisks bool
|
||||
WaitSnapshotReadyTimeout int
|
||||
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
|
||||
|
||||
if s.AlicloudImageIgnoreDataDisks {
|
||||
snapshotId := state.Get("alicloudsnapshot").(string)
|
||||
imageId, err = client.CreateImage(&ecs.CreateImageArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
SnapshotId: snapshotId,
|
||||
ImageName: config.AlicloudImageName,
|
||||
ImageVersion: config.AlicloudImageVersion,
|
||||
Description: config.AlicloudImageDescription})
|
||||
} else {
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
imageId, err = client.CreateImage(&ecs.CreateImageArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
InstanceId: instance.InstanceId,
|
||||
ImageName: config.AlicloudImageName,
|
||||
ImageVersion: config.AlicloudImageVersion,
|
||||
Description: config.AlicloudImageDescription})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return halt(state, err, "Error creating image")
|
||||
}
|
||||
err = client.WaitForImageReady(common.Region(config.AlicloudRegion), imageId, s.WaitSnapshotReadyTimeout)
|
||||
if err != nil {
|
||||
return halt(state, err, "Timeout waiting for image to be created")
|
||||
}
|
||||
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
ImageId: imageId})
|
||||
if err != nil {
|
||||
return halt(state, err, "Error querying created imaged")
|
||||
}
|
||||
|
||||
if len(images) == 0 {
|
||||
return halt(state, err, "Unable to find created image")
|
||||
}
|
||||
|
||||
s.image = &images[0]
|
||||
|
||||
var snapshotIds = []string{}
|
||||
for _, device := range images[0].DiskDeviceMappings.DiskDeviceMapping {
|
||||
snapshotIds = append(snapshotIds, device.SnapshotId)
|
||||
}
|
||||
|
||||
state.Put("alicloudimage", imageId)
|
||||
state.Put("alicloudsnapshots", snapshotIds)
|
||||
|
||||
alicloudImages := make(map[string]string)
|
||||
alicloudImages[config.AlicloudRegion] = images[0].ImageId
|
||||
state.Put("alicloudimages", alicloudImages)
|
||||
|
||||
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,174 +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,
|
||||
SystemDisk: systemDeviceToDiskType(config.AlicloudImageConfig.ECSSystemDiskMapping),
|
||||
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 systemDeviceToDiskType(systemDisk AlicloudDiskDevice) ecs.SystemDiskType {
|
||||
return ecs.SystemDiskType{
|
||||
DiskName: systemDisk.DiskName,
|
||||
Category: ecs.DiskCategory(systemDisk.DiskCategory),
|
||||
Size: systemDisk.DiskSize,
|
||||
Description: systemDisk.Description,
|
||||
}
|
||||
}
|
||||
|
||||
func diskDeviceToDiskType(diskDevices []AlicloudDiskDevice) []ecs.DataDiskType {
|
||||
result := make([]ecs.DataDiskType, len(diskDevices))
|
||||
for _, diskDevice := range diskDevices {
|
||||
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,88 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepCreateAlicloudSnapshot struct {
|
||||
snapshot *ecs.SnapshotType
|
||||
WaitSnapshotReadyTimeout int
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
disks, _, err := client.DescribeDisks(&ecs.DescribeDisksArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
InstanceId: instance.InstanceId,
|
||||
DiskType: ecs.DiskTypeAllSystem,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return halt(state, err, "Error describe disks")
|
||||
}
|
||||
if len(disks) == 0 {
|
||||
return halt(state, err, "Unable to find system disk of instance")
|
||||
}
|
||||
|
||||
// Create the alicloud snapshot
|
||||
ui.Say(fmt.Sprintf("Creating snapshot from system disk: %s", disks[0].DiskId))
|
||||
|
||||
snapshotId, err := client.CreateSnapshot(&ecs.CreateSnapshotArgs{
|
||||
DiskId: disks[0].DiskId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return halt(state, err, "Error creating snapshot")
|
||||
}
|
||||
|
||||
err = client.WaitForSnapShotReady(common.Region(config.AlicloudRegion), snapshotId, s.WaitSnapshotReadyTimeout)
|
||||
if err != nil {
|
||||
return halt(state, err, "Timeout waiting for snapshot to be created")
|
||||
}
|
||||
|
||||
snapshots, _, err := client.DescribeSnapshots(&ecs.DescribeSnapshotsArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
SnapshotIds: []string{snapshotId},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return halt(state, err, "Error querying created snapshot")
|
||||
}
|
||||
if len(snapshots) == 0 {
|
||||
return halt(state, err, "Unable to find created snapshot")
|
||||
}
|
||||
s.snapshot = &snapshots[0]
|
||||
state.Put("alicloudsnapshot", snapshotId)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudSnapshot) Cleanup(state multistep.StateBag) {
|
||||
if s.snapshot == nil {
|
||||
return
|
||||
}
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if !cancelled && !halted {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Deleting the snapshot because of cancellation or error...")
|
||||
if err := client.DeleteSnapshot(s.snapshot.SnapshotId); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting snapshot, it may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepCreateTags struct {
|
||||
Tags map[string]string
|
||||
}
|
||||
|
||||
func (s *stepCreateTags) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
imageId := state.Get("alicloudimage").(string)
|
||||
snapshotIds := state.Get("alicloudsnapshots").([]string)
|
||||
|
||||
if len(s.Tags) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Adding tags(%s) to image: %s", s.Tags, imageId))
|
||||
err := client.AddTags(&ecs.AddTagsArgs{
|
||||
ResourceId: imageId,
|
||||
ResourceType: ecs.TagResourceImage,
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
Tag: s.Tags,
|
||||
})
|
||||
if err != nil {
|
||||
return halt(state, err, "Error Adding tags to image")
|
||||
}
|
||||
|
||||
for _, snapshotId := range snapshotIds {
|
||||
ui.Say(fmt.Sprintf("Adding tags(%s) to snapshot: %s", s.Tags, snapshotId))
|
||||
err = client.AddTags(&ecs.AddTagsArgs{
|
||||
ResourceId: snapshotId,
|
||||
ResourceType: ecs.TagResourceSnapshot,
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
Tag: s.Tags,
|
||||
})
|
||||
if err != nil {
|
||||
return halt(state, err, "Error Adding tags to snapshot")
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
func (s *stepCreateTags) Cleanup(state multistep.StateBag) {
|
||||
// Nothing need to do, tags will be cleaned when the resource is cleaned
|
||||
}
|
||||
@@ -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
|
||||
@@ -24,28 +25,19 @@ const BuilderId = "mitchellh.amazon.chroot"
|
||||
// Config is the configuration that is chained through the steps and
|
||||
// settable from the template.
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AMIBlockDevices `mapstructure:",squash"`
|
||||
awscommon.AMIConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
awscommon.AMIConfig `mapstructure:",squash"`
|
||||
|
||||
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
|
||||
CommandWrapper string `mapstructure:"command_wrapper"`
|
||||
CopyFiles []string `mapstructure:"copy_files"`
|
||||
DevicePath string `mapstructure:"device_path"`
|
||||
NVMEDevicePath string `mapstructure:"nvme_device_path"`
|
||||
FromScratch bool `mapstructure:"from_scratch"`
|
||||
MountOptions []string `mapstructure:"mount_options"`
|
||||
MountPartition string `mapstructure:"mount_partition"`
|
||||
MountPath string `mapstructure:"mount_path"`
|
||||
PostMountCommands []string `mapstructure:"post_mount_commands"`
|
||||
PreMountCommands []string `mapstructure:"pre_mount_commands"`
|
||||
RootDeviceName string `mapstructure:"root_device_name"`
|
||||
RootVolumeSize int64 `mapstructure:"root_volume_size"`
|
||||
RootVolumeType string `mapstructure:"root_volume_type"`
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
SourceAmiFilter awscommon.AmiFilterOptions `mapstructure:"source_ami_filter"`
|
||||
RootVolumeTags awscommon.TagMap `mapstructure:"root_volume_tags"`
|
||||
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
|
||||
CommandWrapper string `mapstructure:"command_wrapper"`
|
||||
CopyFiles []string `mapstructure:"copy_files"`
|
||||
DevicePath string `mapstructure:"device_path"`
|
||||
MountPath string `mapstructure:"mount_path"`
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
RootVolumeSize int64 `mapstructure:"root_volume_size"`
|
||||
MountOptions []string `mapstructure:"mount_options"`
|
||||
MountPartition int `mapstructure:"mount_partition"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
@@ -66,13 +58,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"ami_description",
|
||||
"snapshot_tags",
|
||||
"tags",
|
||||
"root_volume_tags",
|
||||
"command_wrapper",
|
||||
"post_mount_commands",
|
||||
"pre_mount_commands",
|
||||
"mount_path",
|
||||
},
|
||||
},
|
||||
@@ -81,31 +67,27 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if b.config.PackerConfig.PackerForce {
|
||||
b.config.AMIForceDeregister = true
|
||||
}
|
||||
|
||||
// Defaults
|
||||
if b.config.ChrootMounts == nil {
|
||||
b.config.ChrootMounts = make([][]string, 0)
|
||||
}
|
||||
|
||||
if b.config.CopyFiles == nil {
|
||||
b.config.CopyFiles = make([]string, 0)
|
||||
}
|
||||
|
||||
if len(b.config.ChrootMounts) == 0 {
|
||||
b.config.ChrootMounts = [][]string{
|
||||
{"proc", "proc", "/proc"},
|
||||
{"sysfs", "sysfs", "/sys"},
|
||||
{"bind", "/dev", "/dev"},
|
||||
{"devpts", "devpts", "/dev/pts"},
|
||||
{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
|
||||
[]string{"proc", "proc", "/proc"},
|
||||
[]string{"sysfs", "sysfs", "/sys"},
|
||||
[]string{"bind", "/dev", "/dev"},
|
||||
[]string{"devpts", "devpts", "/dev/pts"},
|
||||
[]string{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
|
||||
}
|
||||
}
|
||||
|
||||
// set default copy file if we're not giving our own
|
||||
if b.config.CopyFiles == nil {
|
||||
b.config.CopyFiles = make([]string, 0)
|
||||
if !b.config.FromScratch {
|
||||
b.config.CopyFiles = []string{"/etc/resolv.conf"}
|
||||
}
|
||||
if len(b.config.CopyFiles) == 0 {
|
||||
b.config.CopyFiles = []string{"/etc/resolv.conf"}
|
||||
}
|
||||
|
||||
if b.config.CommandWrapper == "" {
|
||||
@@ -116,17 +98,14 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
b.config.MountPath = "/mnt/packer-amazon-chroot-volumes/{{.Device}}"
|
||||
}
|
||||
|
||||
if b.config.MountPartition == "" {
|
||||
b.config.MountPartition = "1"
|
||||
if b.config.MountPartition == 0 {
|
||||
b.config.MountPartition = 1
|
||||
}
|
||||
|
||||
// Accumulate any errors or warnings
|
||||
// Accumulate any errors
|
||||
var errs *packer.MultiError
|
||||
var warns []string
|
||||
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
b.config.AMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...)
|
||||
|
||||
for _, mounts := range b.config.ChrootMounts {
|
||||
if len(mounts) != 3 {
|
||||
@@ -136,49 +115,16 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.FromScratch {
|
||||
if b.config.SourceAmi != "" || !b.config.SourceAmiFilter.Empty() {
|
||||
warns = append(warns, "source_ami and source_ami_filter are unused when from_scratch is true")
|
||||
}
|
||||
if b.config.RootVolumeSize == 0 {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("root_volume_size is required with from_scratch."))
|
||||
}
|
||||
if len(b.config.PreMountCommands) == 0 {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("pre_mount_commands is required with from_scratch."))
|
||||
}
|
||||
if b.config.AMIVirtType == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("ami_virtualization_type is required with from_scratch."))
|
||||
}
|
||||
if b.config.RootDeviceName == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("root_device_name is required with from_scratch."))
|
||||
}
|
||||
if len(b.config.AMIMappings) == 0 {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("ami_block_device_mappings is required with from_scratch."))
|
||||
}
|
||||
} else {
|
||||
if b.config.SourceAmi == "" && b.config.SourceAmiFilter.Empty() {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("source_ami or source_ami_filter is required."))
|
||||
}
|
||||
if len(b.config.AMIMappings) != 0 {
|
||||
warns = append(warns, "ami_block_device_mappings are unused when from_scratch is false")
|
||||
}
|
||||
if b.config.RootDeviceName != "" {
|
||||
warns = append(warns, "root_device_name is unused when from_scratch is false")
|
||||
}
|
||||
if b.config.SourceAmi == "" {
|
||||
errs = packer.MultiErrorAppend(errs, errors.New("source_ami is required."))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return warns, errs
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
return warns, nil
|
||||
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
@@ -186,10 +132,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 := session.New(config)
|
||||
ec2conn := ec2.New(session)
|
||||
|
||||
wrappedCommand := func(command string) (string, error) {
|
||||
@@ -202,7 +150,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))
|
||||
@@ -214,90 +161,60 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
ForceDeregister: b.config.AMIForceDeregister,
|
||||
},
|
||||
&StepInstanceInfo{},
|
||||
}
|
||||
|
||||
if !b.config.FromScratch {
|
||||
steps = append(steps,
|
||||
&awscommon.StepSourceAMIInfo{
|
||||
SourceAmi: b.config.SourceAmi,
|
||||
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
||||
EnableAMIENASupport: b.config.AMIENASupport,
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
AMIVirtType: b.config.AMIVirtType,
|
||||
},
|
||||
&StepCheckRootDevice{},
|
||||
)
|
||||
}
|
||||
|
||||
steps = append(steps,
|
||||
&awscommon.StepSourceAMIInfo{
|
||||
SourceAmi: b.config.SourceAmi,
|
||||
EnhancedNetworking: b.config.AMIEnhancedNetworking,
|
||||
},
|
||||
&StepCheckRootDevice{},
|
||||
&StepFlock{},
|
||||
&StepPrepareDevice{},
|
||||
&StepCreateVolume{
|
||||
RootVolumeType: b.config.RootVolumeType,
|
||||
RootVolumeSize: b.config.RootVolumeSize,
|
||||
RootVolumeTags: b.config.RootVolumeTags,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
&StepAttachVolume{},
|
||||
&StepEarlyUnflock{},
|
||||
&StepPreMountCommands{
|
||||
Commands: b.config.PreMountCommands,
|
||||
},
|
||||
&StepMountDevice{
|
||||
MountOptions: b.config.MountOptions,
|
||||
MountPartition: b.config.MountPartition,
|
||||
},
|
||||
&StepPostMountCommands{
|
||||
Commands: b.config.PostMountCommands,
|
||||
},
|
||||
&StepMountExtra{},
|
||||
&StepCopyFiles{},
|
||||
&StepChrootProvision{},
|
||||
&StepEarlyCleanup{},
|
||||
&StepSnapshot{},
|
||||
&awscommon.StepDeregisterAMI{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
ForceDeregister: b.config.AMIForceDeregister,
|
||||
ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot,
|
||||
AMIName: b.config.AMIName,
|
||||
Regions: b.config.AMIRegions,
|
||||
ForceDeregister: b.config.AMIForceDeregister,
|
||||
AMIName: b.config.AMIName,
|
||||
},
|
||||
&StepRegisterAMI{
|
||||
RootVolumeSize: b.config.RootVolumeSize,
|
||||
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
||||
EnableAMIENASupport: b.config.AMIENASupport,
|
||||
},
|
||||
&awscommon.StepCreateEncryptedAMICopy{
|
||||
KeyID: b.config.AMIKmsKeyId,
|
||||
EncryptBootVolume: b.config.AMIEncryptBootVolume,
|
||||
Name: b.config.AMIName,
|
||||
AMIMappings: b.config.AMIBlockDevices.AMIMappings,
|
||||
RootVolumeSize: b.config.RootVolumeSize,
|
||||
},
|
||||
&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,
|
||||
Users: b.config.AMIUsers,
|
||||
Groups: b.config.AMIGroups,
|
||||
ProductCodes: b.config.AMIProductCodes,
|
||||
SnapshotUsers: b.config.SnapshotUsers,
|
||||
SnapshotGroups: b.config.SnapshotGroups,
|
||||
Ctx: b.config.ctx,
|
||||
Description: b.config.AMIDescription,
|
||||
Users: b.config.AMIUsers,
|
||||
Groups: b.config.AMIGroups,
|
||||
ProductCodes: b.config.AMIProductCodes,
|
||||
},
|
||||
&awscommon.StepCreateTags{
|
||||
Tags: b.config.AMITags,
|
||||
SnapshotTags: b.config.SnapshotTags,
|
||||
Ctx: b.config.ctx,
|
||||
Tags: b.config.AMITags,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Run!
|
||||
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
if b.config.PackerDebug {
|
||||
b.runner = &multistep.DebugRunner{
|
||||
Steps: steps,
|
||||
PauseFn: common.MultistepDebugFn(ui),
|
||||
}
|
||||
} else {
|
||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
||||
}
|
||||
|
||||
b.runner.Run(state)
|
||||
|
||||
// If there was an error, return that
|
||||
@@ -314,7 +231,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
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"ami_name": "foo",
|
||||
"source_ami": "foo",
|
||||
"region": "us-east-1",
|
||||
// region validation logic is checked in ami_config_test
|
||||
"skip_region_validation": true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +26,6 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
|
||||
// Test good
|
||||
config["ami_name"] = "foo"
|
||||
config["skip_region_validation"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
@@ -74,16 +69,11 @@ func TestBuilderPrepare_ChrootMounts(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ChrootMountsBadDefaults(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
config["chroot_mounts"] = [][]string{
|
||||
{"bad"},
|
||||
[]string{"bad"},
|
||||
}
|
||||
warnings, err := b.Prepare(config)
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
@@ -127,38 +117,3 @@ func TestBuilderPrepare_CommandWrapper(t *testing.T) {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_CopyFiles(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(b.config.CopyFiles) != 1 && b.config.CopyFiles[0] != "/etc/resolv.conf" {
|
||||
t.Errorf("Was expecting default value for copy_files.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_CopyFilesNoDefault(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
config["copy_files"] = []string{}
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(b.config.CopyFiles) > 0 {
|
||||
t.Errorf("Was expecting no default value for copy_files.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -72,10 +69,7 @@ func (c *Communicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
|
||||
return fmt.Errorf("Error preparing shell script: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
if _, err := io.Copy(tf, r); err != nil {
|
||||
return err
|
||||
}
|
||||
io.Copy(tf, r)
|
||||
|
||||
cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp %s %s", tf.Name(), dst))
|
||||
if err != nil {
|
||||
|
||||
@@ -1,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)
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sl "github.com/hashicorp/packer/common/shell-local"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
func RunLocalCommands(commands []string, wrappedCommand CommandWrapper, ctx interpolate.Context, ui packer.Ui) error {
|
||||
for _, rawCmd := range commands {
|
||||
intCmd, err := interpolate.Render(rawCmd, &ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error interpolating: %s", err)
|
||||
}
|
||||
|
||||
command, err := wrappedCommand(intCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error wrapping command: %s", err)
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Executing command: %s", command))
|
||||
comm := &sl.Communicator{
|
||||
ExecuteCommand: []string{"sh", "-c", command},
|
||||
}
|
||||
cmd := &packer.RemoteCmd{Command: command}
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
return fmt.Errorf("Error executing command: %s", err)
|
||||
}
|
||||
if cmd.ExitStatus != 0 {
|
||||
return fmt.Errorf(
|
||||
"Received non-zero exit code %d from command: %s",
|
||||
cmd.ExitStatus,
|
||||
command)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -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"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// StepChrootProvision provisions the instance within a chroot.
|
||||
type StepChrootProvision struct {
|
||||
mounts []string
|
||||
}
|
||||
|
||||
func (s *StepChrootProvision) Run(_ 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,78 +19,42 @@ import (
|
||||
type StepCreateVolume struct {
|
||||
volumeId string
|
||||
RootVolumeSize int64
|
||||
RootVolumeType string
|
||||
RootVolumeTags awscommon.TagMap
|
||||
Ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
image := state.Get("source_image").(*ec2.Image)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
volTags, err := s.RootVolumeTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error tagging volumes: %s", err)
|
||||
// Determine the root device snapshot
|
||||
log.Printf("Searching for root device of the image (%s)", *image.RootDeviceName)
|
||||
var rootDevice *ec2.BlockDeviceMapping
|
||||
for _, device := range image.BlockDeviceMappings {
|
||||
if *device.DeviceName == *image.RootDeviceName {
|
||||
rootDevice = device
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if rootDevice == nil {
|
||||
err := fmt.Errorf("Couldn't find root device!")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Collect tags for tagging on resource creation
|
||||
var tagSpecs []*ec2.TagSpecification
|
||||
|
||||
if len(volTags) > 0 {
|
||||
runVolTags := &ec2.TagSpecification{
|
||||
ResourceType: aws.String("volume"),
|
||||
Tags: volTags,
|
||||
}
|
||||
|
||||
tagSpecs = append(tagSpecs, runVolTags)
|
||||
ui.Say("Creating the root volume...")
|
||||
vs := *rootDevice.Ebs.VolumeSize
|
||||
if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize {
|
||||
vs = s.RootVolumeSize
|
||||
}
|
||||
|
||||
var createVolume *ec2.CreateVolumeInput
|
||||
if config.FromScratch {
|
||||
rootVolumeType := ec2.VolumeTypeGp2
|
||||
if s.RootVolumeType == "io1" {
|
||||
err := errors.New("Cannot use io1 volume when building from scratch")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
} else if s.RootVolumeType != "" {
|
||||
rootVolumeType = s.RootVolumeType
|
||||
}
|
||||
createVolume = &ec2.CreateVolumeInput{
|
||||
AvailabilityZone: instance.Placement.AvailabilityZone,
|
||||
Size: aws.Int64(s.RootVolumeSize),
|
||||
VolumeType: aws.String(rootVolumeType),
|
||||
}
|
||||
|
||||
} else {
|
||||
// Determine the root device snapshot
|
||||
image := state.Get("source_image").(*ec2.Image)
|
||||
log.Printf("Searching for root device of the image (%s)", *image.RootDeviceName)
|
||||
var rootDevice *ec2.BlockDeviceMapping
|
||||
for _, device := range image.BlockDeviceMappings {
|
||||
if *device.DeviceName == *image.RootDeviceName {
|
||||
rootDevice = device
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say("Creating the root volume...")
|
||||
createVolume, err = s.buildCreateVolumeInput(*instance.Placement.AvailabilityZone, rootDevice)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
if len(tagSpecs) > 0 {
|
||||
createVolume.SetTagSpecifications(tagSpecs)
|
||||
volTags.Report(ui)
|
||||
createVolume := &ec2.CreateVolumeInput{
|
||||
AvailabilityZone: instance.Placement.AvailabilityZone,
|
||||
Size: aws.Int64(vs),
|
||||
SnapshotId: rootDevice.Ebs.SnapshotId,
|
||||
VolumeType: rootDevice.Ebs.VolumeType,
|
||||
Iops: rootDevice.Ebs.Iops,
|
||||
}
|
||||
log.Printf("Create args: %+v", createVolume)
|
||||
|
||||
@@ -110,7 +71,22 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
log.Printf("Volume ID: %s", s.volumeId)
|
||||
|
||||
// Wait for the volume to become ready
|
||||
err = awscommon.WaitUntilVolumeAvailable(ctx, ec2conn, s.volumeId)
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"creating"},
|
||||
StepState: state,
|
||||
Target: "available",
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
resp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesInput{VolumeIds: []*string{&s.volumeId}})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
v := resp.Volumes[0]
|
||||
return v, *v.State, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for volume: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -136,33 +112,3 @@ func (s *StepCreateVolume) Cleanup(state multistep.StateBag) {
|
||||
ui.Error(fmt.Sprintf("Error deleting EBS volume: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepCreateVolume) buildCreateVolumeInput(az string, rootDevice *ec2.BlockDeviceMapping) (*ec2.CreateVolumeInput, error) {
|
||||
if rootDevice == nil {
|
||||
return nil, fmt.Errorf("Couldn't find root device!")
|
||||
}
|
||||
createVolumeInput := &ec2.CreateVolumeInput{
|
||||
AvailabilityZone: aws.String(az),
|
||||
Size: rootDevice.Ebs.VolumeSize,
|
||||
SnapshotId: rootDevice.Ebs.SnapshotId,
|
||||
VolumeType: rootDevice.Ebs.VolumeType,
|
||||
Iops: rootDevice.Ebs.Iops,
|
||||
}
|
||||
if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize {
|
||||
createVolumeInput.Size = aws.Int64(s.RootVolumeSize)
|
||||
}
|
||||
|
||||
if s.RootVolumeType == "" || s.RootVolumeType == *rootDevice.Ebs.VolumeType {
|
||||
return createVolumeInput, nil
|
||||
}
|
||||
|
||||
if s.RootVolumeType == "io1" {
|
||||
return nil, fmt.Errorf("Root volume type cannot be io1, because existing root volume type was %s", *rootDevice.Ebs.VolumeType)
|
||||
}
|
||||
|
||||
createVolumeInput.VolumeType = aws.String(s.RootVolumeType)
|
||||
// non io1 cannot set iops
|
||||
createVolumeInput.Iops = nil
|
||||
|
||||
return createVolumeInput, nil
|
||||
}
|
||||
|
||||
@@ -1,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,32 +25,19 @@ 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)
|
||||
image := state.Get("source_image").(*ec2.Image)
|
||||
device := state.Get("device").(string)
|
||||
if config.NVMEDevicePath != "" {
|
||||
// customizable device path for mounting NVME block devices on c5 and m5 HVM
|
||||
device = config.NVMEDevicePath
|
||||
}
|
||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||
|
||||
var virtualizationType string
|
||||
if config.FromScratch {
|
||||
virtualizationType = config.AMIVirtType
|
||||
} else {
|
||||
image := state.Get("source_image").(*ec2.Image)
|
||||
virtualizationType = *image.VirtualizationType
|
||||
log.Printf("Source image virtualization type is: %s", virtualizationType)
|
||||
}
|
||||
|
||||
ctx := config.ctx
|
||||
|
||||
ctx.Data = &mountPathData{Device: filepath.Base(device)}
|
||||
mountPath, err := interpolate.Render(config.MountPath, &ctx)
|
||||
|
||||
@@ -79,17 +65,17 @@ func (s *StepMountDevice) Run(_ context.Context, state multistep.StateBag) multi
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Source image virtualization type is: %s", *image.VirtualizationType)
|
||||
deviceMount := device
|
||||
|
||||
if virtualizationType == "hvm" && s.MountPartition != "0" {
|
||||
deviceMount = fmt.Sprintf("%s%s", device, s.MountPartition)
|
||||
if *image.VirtualizationType == "hvm" {
|
||||
deviceMount = fmt.Sprintf("%s%d", device, s.MountPartition)
|
||||
}
|
||||
state.Put("deviceMount", deviceMount)
|
||||
|
||||
ui.Say("Mounting the root device...")
|
||||
stderr := new(bytes.Buffer)
|
||||
|
||||
// build mount options from mount_options config, useful for nouuid options
|
||||
// build mount options from mount_options config, usefull for nouuid options
|
||||
// or other specific device type settings for mount
|
||||
opts := ""
|
||||
if len(s.MountOptions) > 0 {
|
||||
@@ -103,7 +89,7 @@ func (s *StepMountDevice) Run(_ 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,47 +0,0 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type postMountCommandsData struct {
|
||||
Device string
|
||||
MountPath string
|
||||
}
|
||||
|
||||
// StepPostMountCommands allows running arbitrary commands after mounting the
|
||||
// device, but prior to the bind mount and copy steps.
|
||||
type StepPostMountCommands struct {
|
||||
Commands []string
|
||||
}
|
||||
|
||||
func (s *StepPostMountCommands) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
device := state.Get("device").(string)
|
||||
mountPath := state.Get("mount_path").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||
|
||||
if len(s.Commands) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ctx := config.ctx
|
||||
ctx.Data = &postMountCommandsData{
|
||||
Device: device,
|
||||
MountPath: mountPath,
|
||||
}
|
||||
|
||||
ui.Say("Running post-mount commands...")
|
||||
if err := RunLocalCommands(s.Commands, wrappedCommand, ctx, ui); err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepPostMountCommands) Cleanup(state multistep.StateBag) {}
|
||||
@@ -1,41 +0,0 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type preMountCommandsData struct {
|
||||
Device string
|
||||
}
|
||||
|
||||
// StepPreMountCommands sets up the a new block device when building from scratch
|
||||
type StepPreMountCommands struct {
|
||||
Commands []string
|
||||
}
|
||||
|
||||
func (s *StepPreMountCommands) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
device := state.Get("device").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||
|
||||
if len(s.Commands) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ctx := config.ctx
|
||||
ctx.Data = &preMountCommandsData{Device: device}
|
||||
|
||||
ui.Say("Running device setup commands...")
|
||||
if err := RunLocalCommands(s.Commands, wrappedCommand, ctx, ui); err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepPreMountCommands) Cleanup(state multistep.StateBag) {}
|
||||
@@ -1,20 +1,19 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// StepPrepareDevice finds an available device and sets it.
|
||||
type StepPrepareDevice struct {
|
||||
mounts []string
|
||||
}
|
||||
|
||||
func (s *StepPrepareDevice) Run(_ 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,58 +1,39 @@
|
||||
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)
|
||||
image := state.Get("source_image").(*ec2.Image)
|
||||
snapshotId := state.Get("snapshot_id").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Registering the AMI...")
|
||||
|
||||
var (
|
||||
registerOpts *ec2.RegisterImageInput
|
||||
mappings []*ec2.BlockDeviceMapping
|
||||
image *ec2.Image
|
||||
rootDeviceName string
|
||||
)
|
||||
|
||||
if config.FromScratch {
|
||||
mappings = config.AMIBlockDevices.BuildAMIDevices()
|
||||
rootDeviceName = config.RootDeviceName
|
||||
} else {
|
||||
image = state.Get("source_image").(*ec2.Image)
|
||||
mappings = image.BlockDeviceMappings
|
||||
rootDeviceName = *image.RootDeviceName
|
||||
}
|
||||
|
||||
newMappings := make([]*ec2.BlockDeviceMapping, len(mappings))
|
||||
for i, device := range mappings {
|
||||
blockDevices := make([]*ec2.BlockDeviceMapping, len(image.BlockDeviceMappings))
|
||||
for i, device := range image.BlockDeviceMappings {
|
||||
newDevice := device
|
||||
if *newDevice.DeviceName == rootDeviceName {
|
||||
if *newDevice.DeviceName == *image.RootDeviceName {
|
||||
if newDevice.Ebs != nil {
|
||||
newDevice.Ebs.SnapshotId = aws.String(snapshotId)
|
||||
} else {
|
||||
newDevice.Ebs = &ec2.EbsBlockDevice{SnapshotId: aws.String(snapshotId)}
|
||||
}
|
||||
|
||||
if config.FromScratch || s.RootVolumeSize > *newDevice.Ebs.VolumeSize {
|
||||
if s.RootVolumeSize > *newDevice.Ebs.VolumeSize {
|
||||
newDevice.Ebs.VolumeSize = aws.Int64(s.RootVolumeSize)
|
||||
}
|
||||
}
|
||||
@@ -63,31 +44,15 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
newDevice.Ebs.Encrypted = nil
|
||||
}
|
||||
|
||||
newMappings[i] = newDevice
|
||||
blockDevices[i] = newDevice
|
||||
}
|
||||
|
||||
if config.FromScratch {
|
||||
registerOpts = &ec2.RegisterImageInput{
|
||||
Name: &config.AMIName,
|
||||
Architecture: aws.String(ec2.ArchitectureValuesX8664),
|
||||
RootDeviceName: aws.String(rootDeviceName),
|
||||
VirtualizationType: aws.String(config.AMIVirtType),
|
||||
BlockDeviceMappings: newMappings,
|
||||
}
|
||||
} else {
|
||||
registerOpts = buildRegisterOpts(config, image, newMappings)
|
||||
}
|
||||
registerOpts := buildRegisterOpts(config, image, blockDevices)
|
||||
|
||||
if s.EnableAMISriovNetSupport {
|
||||
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
|
||||
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
|
||||
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
|
||||
if config.AMIEnhancedNetworking {
|
||||
registerOpts.SriovNetSupport = aws.String("simple")
|
||||
}
|
||||
if s.EnableAMIENASupport != nil && *s.EnableAMIENASupport {
|
||||
// Set EnaSupport to true
|
||||
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
|
||||
registerOpts.EnaSupport = aws.Bool(true)
|
||||
}
|
||||
|
||||
registerResp, err := ec2conn.RegisterImage(registerOpts)
|
||||
if err != nil {
|
||||
@@ -102,8 +67,16 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
amis[*ec2conn.Config.Region] = *registerResp.ImageId
|
||||
state.Put("amis", amis)
|
||||
|
||||
// Wait for the image to become ready
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
Target: "available",
|
||||
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *registerResp.ImageId),
|
||||
StepState: state,
|
||||
}
|
||||
|
||||
ui.Say("Waiting for AMI to become ready...")
|
||||
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
|
||||
if _, err := awscommon.WaitForState(&stateChange); err != nil {
|
||||
err := fmt.Errorf("Error waiting for AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
@@ -115,12 +88,12 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
|
||||
func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {}
|
||||
|
||||
func buildRegisterOpts(config *Config, image *ec2.Image, mappings []*ec2.BlockDeviceMapping) *ec2.RegisterImageInput {
|
||||
func buildRegisterOpts(config *Config, image *ec2.Image, blockDevices []*ec2.BlockDeviceMapping) *ec2.RegisterImageInput {
|
||||
registerOpts := &ec2.RegisterImageInput{
|
||||
Name: &config.AMIName,
|
||||
Architecture: image.Architecture,
|
||||
RootDeviceName: image.RootDeviceName,
|
||||
BlockDeviceMappings: mappings,
|
||||
BlockDeviceMappings: blockDevices,
|
||||
VirtualizationType: image.VirtualizationType,
|
||||
}
|
||||
|
||||
@@ -132,5 +105,6 @@ func buildRegisterOpts(config *Config, image *ec2.Image, mappings []*ec2.BlockDe
|
||||
registerOpts.KernelId = image.KernelId
|
||||
registerOpts.RamdiskId = image.RamdiskId
|
||||
}
|
||||
|
||||
return registerOpts
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepSnapshot creates a snapshot of the created volume.
|
||||
@@ -19,7 +19,7 @@ type StepSnapshot struct {
|
||||
snapshotId string
|
||||
}
|
||||
|
||||
func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
volumeId := state.Get("volume_id").(string)
|
||||
@@ -43,7 +43,26 @@ func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
|
||||
ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId))
|
||||
|
||||
// Wait for the snapshot to be ready
|
||||
err = awscommon.WaitUntilSnapshotDone(ctx, ec2conn, s.snapshotId)
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
StepState: state,
|
||||
Target: "completed",
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{SnapshotIds: []*string{&s.snapshotId}})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(resp.Snapshots) == 0 {
|
||||
return nil, "", errors.New("No snapshots found.")
|
||||
}
|
||||
|
||||
s := resp.Snapshots[0]
|
||||
return s, *s.State, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for snapshot: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -52,12 +71,6 @@ func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
|
||||
}
|
||||
|
||||
state.Put("snapshot_id", s.snapshotId)
|
||||
|
||||
snapshots := map[string][]string{
|
||||
*ec2conn.Config.Region: {s.snapshotId},
|
||||
}
|
||||
state.Put("snapshots", snapshots)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
@@ -1,177 +1,117 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"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"`
|
||||
InsecureSkipTLSVerify bool `mapstructure:"insecure_skip_tls_verify"`
|
||||
MFACode string `mapstructure:"mfa_code"`
|
||||
ProfileName string `mapstructure:"profile"`
|
||||
RawRegion string `mapstructure:"region"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
SkipValidation bool `mapstructure:"skip_region_validation"`
|
||||
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
|
||||
Token string `mapstructure:"token"`
|
||||
session *session.Session
|
||||
|
||||
getEC2Connection func() ec2iface.EC2API
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
RawRegion string `mapstructure:"region"`
|
||||
Token string `mapstructure:"token"`
|
||||
ProfileName string `mapstructure:"profile"`
|
||||
}
|
||||
|
||||
// Config returns a valid aws.Config object for access to AWS services, or
|
||||
// an error if the authentication and region couldn't be resolved
|
||||
func (c *AccessConfig) Session() (*session.Session, error) {
|
||||
if c.session != nil {
|
||||
return c.session, nil
|
||||
}
|
||||
func (c *AccessConfig) Config() (*aws.Config, error) {
|
||||
var creds *credentials.Credentials
|
||||
|
||||
config := aws.NewConfig().WithCredentialsChainVerboseErrors(true)
|
||||
staticCreds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
|
||||
if _, err := staticCreds.Get(); err != credentials.ErrStaticCredentialsEmpty {
|
||||
config.WithCredentials(staticCreds)
|
||||
}
|
||||
|
||||
// default is 3, and when it was causing failures for users being throttled
|
||||
// retries are exponentially backed off.
|
||||
config = config.WithMaxRetries(8)
|
||||
|
||||
region, err := c.region()
|
||||
region, err := c.Region()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not get region, "+
|
||||
"probably because it's not set or we're not running on AWS. %s", err)
|
||||
}
|
||||
config = config.WithRegion(region)
|
||||
|
||||
if c.CustomEndpointEc2 != "" {
|
||||
config = config.WithEndpoint(c.CustomEndpointEc2)
|
||||
}
|
||||
|
||||
if c.InsecureSkipTLSVerify {
|
||||
config := config.WithHTTPClient(cleanhttp.DefaultClient())
|
||||
transport := config.HTTPClient.Transport.(*http.Transport)
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
|
||||
opts := session.Options{
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
Config: *config,
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
} 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
|
||||
}
|
||||
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
|
||||
creds, err = profile.CredentialsFromProfile(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
sess := session.New(config)
|
||||
creds = credentials.NewChainCredentials([]credentials.Provider{
|
||||
&credentials.StaticProvider{Value: credentials.Value{
|
||||
AccessKeyID: c.AccessKey,
|
||||
SecretAccessKey: c.SecretKey,
|
||||
SessionToken: c.Token,
|
||||
}},
|
||||
&credentials.EnvProvider{},
|
||||
&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
|
||||
&ec2rolecreds.EC2RoleProvider{
|
||||
Client: ec2metadata.New(sess),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if c.DecodeAuthZMessages {
|
||||
DecodeAuthZMessages(c.session)
|
||||
}
|
||||
|
||||
return c.session, nil
|
||||
return config.WithCredentials(creds), 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, error) {
|
||||
|
||||
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,
|
||||
})
|
||||
return ec2meta.Region()
|
||||
}
|
||||
|
||||
func (c *AccessConfig) region() (string, error) {
|
||||
// Region returns the aws.Region object for access to AWS services, requesting
|
||||
// the region from the instance metadata if possible.
|
||||
func (c *AccessConfig) Region() (string, error) {
|
||||
if c.RawRegion != "" {
|
||||
if valid := ValidateRegion(c.RawRegion); valid == false {
|
||||
return "", fmt.Errorf("Not a valid region: %s", c.RawRegion)
|
||||
}
|
||||
return c.RawRegion, nil
|
||||
}
|
||||
return c.metadataRegion()
|
||||
|
||||
md, err := GetInstanceMetaData("placement/availability-zone")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
region := strings.TrimRightFunc(string(md), unicode.IsLetter)
|
||||
return region, nil
|
||||
}
|
||||
|
||||
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
|
||||
if c.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 != "" {
|
||||
if valid := ValidateRegion(c.RawRegion); valid == false {
|
||||
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 []byte(body), err
|
||||
}
|
||||
|
||||
@@ -2,69 +2,26 @@ package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
)
|
||||
|
||||
func testAccessConfig() *AccessConfig {
|
||||
return &AccessConfig{
|
||||
getEC2Connection: func() ec2iface.EC2API {
|
||||
return &mockEC2Client{}
|
||||
},
|
||||
}
|
||||
return &AccessConfig{}
|
||||
}
|
||||
|
||||
func TestAccessConfigPrepare_Region(t *testing.T) {
|
||||
c := testAccessConfig()
|
||||
|
||||
c.RawRegion = "us-east-12"
|
||||
err := c.ValidateRegion(c.RawRegion)
|
||||
if err == nil {
|
||||
t.Fatalf("should have region validation err: %s", c.RawRegion)
|
||||
}
|
||||
|
||||
c.RawRegion = "us-east-1"
|
||||
err = c.ValidateRegion(c.RawRegion)
|
||||
if err != nil {
|
||||
t.Fatalf("shouldn't have region validation err: %s", c.RawRegion)
|
||||
}
|
||||
|
||||
c.RawRegion = "custom"
|
||||
err = c.ValidateRegion(c.RawRegion)
|
||||
if err == nil {
|
||||
t.Fatalf("should have region validation err: %s", c.RawRegion)
|
||||
}
|
||||
|
||||
c.RawRegion = "custom"
|
||||
c.SkipValidation = true
|
||||
// testing whole prepare func here; this is checking that validation is
|
||||
// skipped, so we don't need a mock connection
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.SkipValidation = false
|
||||
c.RawRegion = ""
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccessConfigPrepare_RegionRestricted(t *testing.T) {
|
||||
c := testAccessConfig()
|
||||
|
||||
// Create a Session with a custom region
|
||||
c.session = session.Must(session.NewSession(&aws.Config{
|
||||
Region: aws.String("us-gov-west-1"),
|
||||
}))
|
||||
c.RawRegion = "us-east-12"
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
c.RawRegion = "us-east-1"
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
if !c.IsGovCloud() {
|
||||
t.Fatal("We should be in gov region.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,99 +2,30 @@ 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.
|
||||
type AMIConfig struct {
|
||||
AMIName string `mapstructure:"ami_name"`
|
||||
AMIDescription string `mapstructure:"ami_description"`
|
||||
AMIVirtType string `mapstructure:"ami_virtualization_type"`
|
||||
AMIUsers []string `mapstructure:"ami_users"`
|
||||
AMIGroups []string `mapstructure:"ami_groups"`
|
||||
AMIProductCodes []string `mapstructure:"ami_product_codes"`
|
||||
AMIRegions []string `mapstructure:"ami_regions"`
|
||||
AMISkipRegionValidation bool `mapstructure:"skip_region_validation"`
|
||||
AMITags TagMap `mapstructure:"tags"`
|
||||
AMIENASupport *bool `mapstructure:"ena_support"`
|
||||
AMISriovNetSupport bool `mapstructure:"sriov_support"`
|
||||
AMIForceDeregister bool `mapstructure:"force_deregister"`
|
||||
AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot"`
|
||||
AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"`
|
||||
AMIKmsKeyId string `mapstructure:"kms_key_id"`
|
||||
AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids"`
|
||||
SnapshotTags TagMap `mapstructure:"snapshot_tags"`
|
||||
SnapshotUsers []string `mapstructure:"snapshot_users"`
|
||||
SnapshotGroups []string `mapstructure:"snapshot_groups"`
|
||||
AMIName string `mapstructure:"ami_name"`
|
||||
AMIDescription string `mapstructure:"ami_description"`
|
||||
AMIVirtType string `mapstructure:"ami_virtualization_type"`
|
||||
AMIUsers []string `mapstructure:"ami_users"`
|
||||
AMIGroups []string `mapstructure:"ami_groups"`
|
||||
AMIProductCodes []string `mapstructure:"ami_product_codes"`
|
||||
AMIRegions []string `mapstructure:"ami_regions"`
|
||||
AMITags map[string]string `mapstructure:"tags"`
|
||||
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
|
||||
AMIForceDeregister bool `mapstructure:"force_deregister"`
|
||||
}
|
||||
|
||||
func stringInSlice(s []string, searchstr string) bool {
|
||||
for _, item := range s {
|
||||
if item == searchstr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context) []error {
|
||||
func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
|
||||
if c.AMIName == "" {
|
||||
errs = append(errs, fmt.Errorf("ami_name must be specified"))
|
||||
}
|
||||
|
||||
// Make sure that if we have region_kms_key_ids defined,
|
||||
// the regions in region_kms_key_ids are also in ami_regions
|
||||
if len(c.AMIRegionKMSKeyIDs) > 0 {
|
||||
for kmsKeyRegion := range c.AMIRegionKMSKeyIDs {
|
||||
if !stringInSlice(c.AMIRegions, kmsKeyRegion) {
|
||||
errs = append(errs, fmt.Errorf("Region %s is in region_kms_key_ids but not in ami_regions", kmsKeyRegion))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errs = append(errs, c.prepareRegions(accessConfig)...)
|
||||
|
||||
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 {
|
||||
regionSet := make(map[string]struct{})
|
||||
regions := make([]string, 0, len(c.AMIRegions))
|
||||
@@ -108,23 +39,21 @@ func (c *AMIConfig) prepareRegions(accessConfig *AccessConfig) (errs []error) {
|
||||
// Mark that we saw the region
|
||||
regionSet[region] = struct{}{}
|
||||
|
||||
// Make sure that if we have region_kms_key_ids defined,
|
||||
// the regions in ami_regions are also in region_kms_key_ids
|
||||
if len(c.AMIRegionKMSKeyIDs) > 0 {
|
||||
if _, ok := c.AMIRegionKMSKeyIDs[region]; !ok {
|
||||
errs = append(errs, fmt.Errorf("Region %s is in ami_regions but not in region_kms_key_ids", region))
|
||||
}
|
||||
}
|
||||
if (accessConfig != nil) && (region == accessConfig.RawRegion) {
|
||||
// make sure we don't try to copy to the region we originally
|
||||
// create the AMI in.
|
||||
log.Printf("Cannot copy AMI to AWS session region '%s', deleting it from `ami_regions`.", region)
|
||||
// Verify the region is real
|
||||
if valid := ValidateRegion(region); valid == false {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
|
||||
continue
|
||||
}
|
||||
|
||||
regions = append(regions, region)
|
||||
}
|
||||
|
||||
c.AMIRegions = regions
|
||||
}
|
||||
return errs
|
||||
|
||||
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,198 +11,42 @@ func testAMIConfig() *AMIConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func getFakeAccessConfig(region string) *AccessConfig {
|
||||
c := testAccessConfig()
|
||||
c.RawRegion = region
|
||||
return c
|
||||
}
|
||||
|
||||
func TestAMIConfigPrepare_name(t *testing.T) {
|
||||
c := testAMIConfig()
|
||||
accessConf := testAccessConfig()
|
||||
if err := c.Prepare(accessConf, nil); err != nil {
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AMIName = ""
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
type mockEC2Client struct {
|
||||
ec2iface.EC2API
|
||||
}
|
||||
|
||||
func (m *mockEC2Client) DescribeRegions(*ec2.DescribeRegionsInput) (*ec2.DescribeRegionsOutput, error) {
|
||||
return &ec2.DescribeRegionsOutput{
|
||||
Regions: []*ec2.Region{
|
||||
{RegionName: aws.String("us-east-1")},
|
||||
{RegionName: aws.String("us-east-2")},
|
||||
{RegionName: aws.String("us-west-1")},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
c := testAMIConfig()
|
||||
c.AMIRegions = nil
|
||||
|
||||
var errs []error
|
||||
var err error
|
||||
accessConf := testAccessConfig()
|
||||
mockConn := &mockEC2Client{}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatalf("shouldn't have err: %#v", errs)
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AMIRegions, err = listEC2Regions(mockConn)
|
||||
if err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err.Error())
|
||||
c.AMIRegions = listEC2Regions()
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatalf("shouldn't have err: %#v", errs)
|
||||
|
||||
c.AMIRegions = []string{"foo"}
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
errs = errs[:0]
|
||||
|
||||
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-1"}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatalf("bad: %s", errs[0])
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
|
||||
expected := []string{"us-east-1", "us-west-1"}
|
||||
if !reflect.DeepEqual(c.AMIRegions, expected) {
|
||||
t.Fatalf("bad: %#v", c.AMIRegions)
|
||||
}
|
||||
|
||||
c.AMIRegions = []string{"custom"}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatal("shouldn't have error")
|
||||
}
|
||||
|
||||
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
|
||||
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||
"us-east-1": "123-456-7890",
|
||||
"us-west-1": "789-012-3456",
|
||||
"us-east-2": "456-789-0123",
|
||||
}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatal(fmt.Sprintf("shouldn't have error: %s", errs[0]))
|
||||
}
|
||||
|
||||
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
|
||||
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||
"us-east-1": "123-456-7890",
|
||||
"us-west-1": "789-012-3456",
|
||||
"us-east-2": "",
|
||||
}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatal("should have passed; we are able to use default KMS key if not sharing")
|
||||
}
|
||||
|
||||
c.SnapshotUsers = []string{"user-foo", "user-bar"}
|
||||
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
|
||||
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||
"us-east-1": "123-456-7890",
|
||||
"us-west-1": "789-012-3456",
|
||||
"us-east-2": "",
|
||||
}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatal("should have an error b/c can't use default KMS key if sharing")
|
||||
}
|
||||
|
||||
c.AMIRegions = []string{"us-east-1", "us-west-1"}
|
||||
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||
"us-east-1": "123-456-7890",
|
||||
"us-west-1": "789-012-3456",
|
||||
"us-east-2": "456-789-0123",
|
||||
}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatal("should have error b/c theres a region in the key map that isn't in ami_regions")
|
||||
}
|
||||
|
||||
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
|
||||
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||
"us-east-1": "123-456-7890",
|
||||
"us-west-1": "789-012-3456",
|
||||
}
|
||||
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
|
||||
}
|
||||
|
||||
c.SnapshotUsers = []string{"foo", "bar"}
|
||||
c.AMIKmsKeyId = "123-abc-456"
|
||||
c.AMIEncryptBootVolume = 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.AMIUsers = []string{"testAccountID"}
|
||||
c.AMIEncryptBootVolume = true
|
||||
|
||||
accessConf := testAccessConfig()
|
||||
|
||||
c.AMIKmsKeyId = ""
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
|
||||
}
|
||||
|
||||
c.AMIKmsKeyId = "89c3fb9a-de87-4f2a-aedc-fddc5138193c"
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAMINameValidation(t *testing.T) {
|
||||
c := testAMIConfig()
|
||||
|
||||
accessConf := testAccessConfig()
|
||||
|
||||
c.AMIName = "aa"
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
t.Fatal("shouldn't be able to have an ami name with less than 3 characters")
|
||||
}
|
||||
|
||||
var longAmiName string
|
||||
for i := 0; i < 129; i++ {
|
||||
longAmiName += "a"
|
||||
}
|
||||
c.AMIName = longAmiName
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
t.Fatal("shouldn't be able to have an ami name with great than 128 characters")
|
||||
}
|
||||
|
||||
c.AMIName = "+aaa"
|
||||
if err := c.Prepare(accessConf, nil); err == nil {
|
||||
t.Fatal("shouldn't be able to have an ami name with invalid characters")
|
||||
}
|
||||
|
||||
c.AMIName = "fooBAR1()[] ./-'@_"
|
||||
if err := c.Prepare(accessConf, nil); err != nil {
|
||||
t.Fatal("should be able to use all of the allowed AMI characters")
|
||||
}
|
||||
|
||||
c.AMIName = `xyz-base-2017-04-05-1934`
|
||||
if err := c.Prepare(accessConf, nil); err != nil {
|
||||
t.Fatalf("expected `xyz-base-2017-04-05-1934` to pass validation.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// Artifact is an artifact implementation that contains built AMIs.
|
||||
@@ -21,7 +21,7 @@ type Artifact struct {
|
||||
BuilderIdValue string
|
||||
|
||||
// EC2 connection for performing API stuff.
|
||||
Session *session.Session
|
||||
Conn *ec2.EC2
|
||||
}
|
||||
|
||||
func (a *Artifact) BuilderId() string {
|
||||
@@ -51,7 +51,7 @@ func (a *Artifact) String() string {
|
||||
}
|
||||
|
||||
sort.Strings(amiStrings)
|
||||
return fmt.Sprintf("AMIs were created:\n%s\n", strings.Join(amiStrings, "\n"))
|
||||
return fmt.Sprintf("AMIs were created:\n\n%s", strings.Join(amiStrings, "\n"))
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
@@ -69,23 +69,13 @@ func (a *Artifact) Destroy() error {
|
||||
for region, imageId := range a.Amis {
|
||||
log.Printf("Deregistering image ID (%s) from region (%s)", imageId, region)
|
||||
|
||||
regionConn := ec2.New(a.Session, &aws.Config{
|
||||
Region: aws.String(region),
|
||||
})
|
||||
|
||||
// Get image metadata
|
||||
imageResp, err := regionConn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{&imageId},
|
||||
})
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
if len(imageResp.Images) == 0 {
|
||||
err := fmt.Errorf("Error retrieving details for AMI (%s), no images found", imageId)
|
||||
errors = append(errors, err)
|
||||
regionConfig := &aws.Config{
|
||||
Credentials: a.Conn.Config.Credentials,
|
||||
Region: aws.String(region),
|
||||
}
|
||||
sess := session.New(regionConfig)
|
||||
regionConn := ec2.New(sess)
|
||||
|
||||
// Deregister ami
|
||||
input := &ec2.DeregisterImageInput{
|
||||
ImageId: &imageId,
|
||||
}
|
||||
|
||||
@@ -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,19 +19,10 @@ 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 {
|
||||
AMIBlockDevices `mapstructure:",squash"`
|
||||
LaunchBlockDevices `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
type AMIBlockDevices struct {
|
||||
AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings"`
|
||||
}
|
||||
|
||||
type LaunchBlockDevices struct {
|
||||
AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings"`
|
||||
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings"`
|
||||
}
|
||||
|
||||
@@ -75,10 +65,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,37 +73,14 @@ func buildBlockDevices(b []BlockDevice) []*ec2.BlockDeviceMapping {
|
||||
return blockDevices
|
||||
}
|
||||
|
||||
func (b *BlockDevice) Prepare(ctx *interpolate.Context) error {
|
||||
if b.DeviceName == "" {
|
||||
return fmt.Errorf("The `device_name` must be specified " +
|
||||
"for every device in the block device mapping.")
|
||||
}
|
||||
// Warn that encrypted must be true when setting kms_key_id
|
||||
if b.KmsKeyId != "" && b.Encrypted == false {
|
||||
return fmt.Errorf("The device %v, must also have `encrypted: "+
|
||||
"true` when setting a kms_key_id.", b.DeviceName)
|
||||
}
|
||||
func (b *BlockDevices) Prepare(ctx *interpolate.Context) []error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
|
||||
for _, d := range b.AMIMappings {
|
||||
if err := d.Prepare(ctx); err != nil {
|
||||
errs = append(errs, fmt.Errorf("AMIMapping: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
for _, d := range b.LaunchMappings {
|
||||
if err := d.Prepare(ctx); err != nil {
|
||||
errs = append(errs, fmt.Errorf("LaunchMapping: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func (b *AMIBlockDevices) BuildAMIDevices() []*ec2.BlockDeviceMapping {
|
||||
func (b *BlockDevices) BuildAMIDevices() []*ec2.BlockDeviceMapping {
|
||||
return buildBlockDevices(b.AMIMappings)
|
||||
}
|
||||
|
||||
func (b *LaunchBlockDevices) BuildLaunchDevices() []*ec2.BlockDeviceMapping {
|
||||
func (b *BlockDevices) BuildLaunchDevices() []*ec2.BlockDeviceMapping {
|
||||
return buildBlockDevices(b.LaunchMappings)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
@@ -145,26 +124,22 @@ func TestBlockDevice(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
amiBlockDevices := AMIBlockDevices{
|
||||
AMIMappings: []BlockDevice{*tc.Config},
|
||||
}
|
||||
|
||||
launchBlockDevices := LaunchBlockDevices{
|
||||
blockDevices := BlockDevices{
|
||||
AMIMappings: []BlockDevice{*tc.Config},
|
||||
LaunchMappings: []BlockDevice{*tc.Config},
|
||||
}
|
||||
|
||||
expected := []*ec2.BlockDeviceMapping{tc.Result}
|
||||
|
||||
amiResults := amiBlockDevices.BuildAMIDevices()
|
||||
if !reflect.DeepEqual(expected, amiResults) {
|
||||
got := blockDevices.BuildAMIDevices()
|
||||
if !reflect.DeepEqual(expected, got) {
|
||||
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
|
||||
expected, amiResults)
|
||||
expected, got)
|
||||
}
|
||||
|
||||
launchResults := launchBlockDevices.BuildLaunchDevices()
|
||||
if !reflect.DeepEqual(expected, launchResults) {
|
||||
if !reflect.DeepEqual(expected, blockDevices.BuildLaunchDevices()) {
|
||||
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
|
||||
expected, launchResults)
|
||||
expected,
|
||||
blockDevices.BuildLaunchDevices())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,155 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/sts"
|
||||
"github.com/go-ini/ini"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
type CLIConfig struct {
|
||||
ProfileName string
|
||||
SourceProfile string
|
||||
|
||||
AssumeRoleInput *sts.AssumeRoleInput
|
||||
SourceCredentials *credentials.Credentials
|
||||
|
||||
profileCfg *ini.Section
|
||||
profileCred *ini.Section
|
||||
}
|
||||
|
||||
// Return a new CLIConfig with stored profile settings
|
||||
func NewFromProfile(name string) (*CLIConfig, error) {
|
||||
c := &CLIConfig{}
|
||||
c.AssumeRoleInput = new(sts.AssumeRoleInput)
|
||||
err := c.Prepare(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sessName, err := c.getSessionName(c.profileCfg.Key("role_session_name").Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.AssumeRoleInput.RoleSessionName = aws.String(sessName)
|
||||
arn := c.profileCfg.Key("role_arn").Value()
|
||||
if arn != "" {
|
||||
c.AssumeRoleInput.RoleArn = aws.String(arn)
|
||||
}
|
||||
id := c.profileCfg.Key("external_id").Value()
|
||||
if id != "" {
|
||||
c.AssumeRoleInput.ExternalId = aws.String(id)
|
||||
}
|
||||
c.SourceCredentials = credentials.NewStaticCredentials(
|
||||
c.profileCred.Key("aws_access_key_id").Value(),
|
||||
c.profileCred.Key("aws_secret_access_key").Value(),
|
||||
c.profileCred.Key("aws_session_token").Value(),
|
||||
)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Return AWS Credentials using current profile. Must supply source config.
|
||||
func (c *CLIConfig) CredentialsFromProfile(conf *aws.Config) (*credentials.Credentials, error) {
|
||||
// If the profile name is equal to the source profile, there is no role to assume so return
|
||||
// the source credentials as they were captured.
|
||||
if c.ProfileName == c.SourceProfile {
|
||||
return c.SourceCredentials, nil
|
||||
}
|
||||
srcCfg := aws.NewConfig().Copy(conf).WithCredentials(c.SourceCredentials)
|
||||
svc := sts.New(session.New(), srcCfg)
|
||||
res, err := svc.AssumeRole(c.AssumeRoleInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return credentials.NewStaticCredentials(
|
||||
*res.Credentials.AccessKeyId,
|
||||
*res.Credentials.SecretAccessKey,
|
||||
*res.Credentials.SessionToken,
|
||||
), nil
|
||||
}
|
||||
|
||||
// Sets params in the struct based on the file section
|
||||
func (c *CLIConfig) Prepare(name string) error {
|
||||
var err error
|
||||
c.ProfileName = name
|
||||
c.profileCfg, err = configFromName(c.ProfileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.SourceProfile = c.profileCfg.Key("source_profile").Value()
|
||||
if c.SourceProfile == "" {
|
||||
c.SourceProfile = c.ProfileName
|
||||
}
|
||||
c.profileCred, err = credsFromName(c.SourceProfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CLIConfig) getSessionName(rawName string) (string, error) {
|
||||
if rawName == "" {
|
||||
name := "packer-"
|
||||
host, err := os.Hostname()
|
||||
if err != nil {
|
||||
return name, err
|
||||
}
|
||||
return fmt.Sprintf("%s%s", name, host), nil
|
||||
} else {
|
||||
return rawName, nil
|
||||
}
|
||||
}
|
||||
|
||||
func configFromName(name string) (*ini.Section, error) {
|
||||
filePath := os.Getenv("AWS_CONFIG_FILE")
|
||||
if filePath == "" {
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filePath = path.Join(home, ".aws", "config")
|
||||
}
|
||||
file, err := readFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
profileName := fmt.Sprintf("profile %s", name)
|
||||
cfg, err := file.GetSection(profileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func credsFromName(name string) (*ini.Section, error) {
|
||||
filePath := os.Getenv("AWS_SHARED_CREDENTIALS_FILE")
|
||||
if filePath == "" {
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filePath = path.Join(home, ".aws", "credentials")
|
||||
}
|
||||
file, err := readFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg, err := file.GetSection(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func readFile(path string) (*ini.File, error) {
|
||||
cfg, err := ini.Load(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
@@ -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 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
type BuildInfoTemplate struct {
|
||||
BuildRegion string
|
||||
SourceAMI string
|
||||
SourceAMIName string
|
||||
SourceAMITags map[string]string
|
||||
}
|
||||
|
||||
func extractBuildInfo(region string, state multistep.StateBag) *BuildInfoTemplate {
|
||||
rawSourceAMI, hasSourceAMI := state.GetOk("source_image")
|
||||
if !hasSourceAMI {
|
||||
return &BuildInfoTemplate{
|
||||
BuildRegion: region,
|
||||
}
|
||||
}
|
||||
|
||||
sourceAMI := rawSourceAMI.(*ec2.Image)
|
||||
sourceAMITags := make(map[string]string, len(sourceAMI.Tags))
|
||||
for _, tag := range sourceAMI.Tags {
|
||||
sourceAMITags[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value)
|
||||
}
|
||||
|
||||
return &BuildInfoTemplate{
|
||||
BuildRegion: region,
|
||||
SourceAMI: aws.StringValue(sourceAMI.ImageId),
|
||||
SourceAMIName: aws.StringValue(sourceAMI.Name),
|
||||
SourceAMITags: sourceAMITags,
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
func testImage() *ec2.Image {
|
||||
return &ec2.Image{
|
||||
ImageId: aws.String("ami-abcd1234"),
|
||||
Name: aws.String("ami_test_name"),
|
||||
Tags: []*ec2.Tag{
|
||||
{
|
||||
Key: aws.String("key-1"),
|
||||
Value: aws.String("value-1"),
|
||||
},
|
||||
{
|
||||
Key: aws.String("key-2"),
|
||||
Value: aws.String("value-2"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testState() multistep.StateBag {
|
||||
state := new(multistep.BasicStateBag)
|
||||
return state
|
||||
}
|
||||
|
||||
func TestInterpolateBuildInfo_extractBuildInfo_noSourceImage(t *testing.T) {
|
||||
state := testState()
|
||||
buildInfo := extractBuildInfo("foo", state)
|
||||
|
||||
expected := BuildInfoTemplate{
|
||||
BuildRegion: "foo",
|
||||
}
|
||||
if !reflect.DeepEqual(*buildInfo, expected) {
|
||||
t.Fatalf("Unexpected BuildInfoTemplate: expected %#v got %#v\n", expected, *buildInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpolateBuildInfo_extractBuildInfo_withSourceImage(t *testing.T) {
|
||||
state := testState()
|
||||
state.Put("source_image", testImage())
|
||||
buildInfo := extractBuildInfo("foo", state)
|
||||
|
||||
expected := BuildInfoTemplate{
|
||||
BuildRegion: "foo",
|
||||
SourceAMI: "ami-abcd1234",
|
||||
SourceAMIName: "ami_test_name",
|
||||
SourceAMITags: map[string]string{
|
||||
"key-1": "value-1",
|
||||
"key-2": "value-2",
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(*buildInfo, expected) {
|
||||
t.Fatalf("Unexpected BuildInfoTemplate: expected %#v got %#v\n", expected, *buildInfo)
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,29 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
)
|
||||
|
||||
func listEC2Regions(ec2conn ec2iface.EC2API) ([]string, error) {
|
||||
var regions []string
|
||||
resultRegions, err := ec2conn.DescribeRegions(nil)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
func listEC2Regions() []string {
|
||||
return []string{
|
||||
"ap-northeast-1",
|
||||
"ap-northeast-2",
|
||||
"ap-southeast-1",
|
||||
"ap-southeast-2",
|
||||
"cn-north-1",
|
||||
"eu-central-1",
|
||||
"eu-west-1",
|
||||
"sa-east-1",
|
||||
"us-east-1",
|
||||
"us-gov-west-1",
|
||||
"us-west-1",
|
||||
"us-west-2",
|
||||
}
|
||||
for _, region := range resultRegions.Regions {
|
||||
regions = append(regions, *region.RegionName)
|
||||
}
|
||||
|
||||
return regions, nil
|
||||
}
|
||||
|
||||
// ValidateRegion returns 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,172 +1,71 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/common/uuid"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/mitchellh/packer/common/uuid"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
var reShutdownBehavior = regexp.MustCompile("^(stop|terminate)$")
|
||||
|
||||
type AmiFilterOptions struct {
|
||||
Filters map[*string]*string
|
||||
Owners []*string
|
||||
MostRecent bool `mapstructure:"most_recent"`
|
||||
}
|
||||
|
||||
func (d *AmiFilterOptions) Empty() bool {
|
||||
return len(d.Owners) == 0 && len(d.Filters) == 0
|
||||
}
|
||||
|
||||
func (d *AmiFilterOptions) NoOwner() bool {
|
||||
return len(d.Owners) == 0
|
||||
}
|
||||
|
||||
type SubnetFilterOptions struct {
|
||||
Filters map[*string]*string
|
||||
MostFree bool `mapstructure:"most_free"`
|
||||
Random bool `mapstructure:"random"`
|
||||
}
|
||||
|
||||
func (d *SubnetFilterOptions) Empty() bool {
|
||||
return len(d.Filters) == 0
|
||||
}
|
||||
|
||||
type VpcFilterOptions struct {
|
||||
Filters map[*string]*string
|
||||
}
|
||||
|
||||
func (d *VpcFilterOptions) Empty() bool {
|
||||
return len(d.Filters) == 0
|
||||
}
|
||||
|
||||
type SecurityGroupFilterOptions struct {
|
||||
Filters map[*string]*string
|
||||
}
|
||||
|
||||
func (d *SecurityGroupFilterOptions) Empty() bool {
|
||||
return len(d.Filters) == 0
|
||||
}
|
||||
|
||||
// RunConfig contains configuration for running an instance from a source
|
||||
// AMI and details on how to access that launched image.
|
||||
type RunConfig struct {
|
||||
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
|
||||
AvailabilityZone string `mapstructure:"availability_zone"`
|
||||
BlockDurationMinutes int64 `mapstructure:"block_duration_minutes"`
|
||||
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
|
||||
EbsOptimized bool `mapstructure:"ebs_optimized"`
|
||||
EnableT2Unlimited bool `mapstructure:"enable_t2_unlimited"`
|
||||
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
|
||||
InstanceInitiatedShutdownBehavior string `mapstructure:"shutdown_behavior"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
SecurityGroupFilter SecurityGroupFilterOptions `mapstructure:"security_group_filter"`
|
||||
RunTags map[string]string `mapstructure:"run_tags"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SecurityGroupIds []string `mapstructure:"security_group_ids"`
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
SourceAmiFilter AmiFilterOptions `mapstructure:"source_ami_filter"`
|
||||
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"`
|
||||
SpotPrice string `mapstructure:"spot_price"`
|
||||
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SecurityGroupIds []string `mapstructure:"security_group_ids"`
|
||||
SubnetId string `mapstructure:"subnet_id"`
|
||||
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
UserDataFile string `mapstructure:"user_data_file"`
|
||||
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHKeyPairName string `mapstructure:"ssh_keypair_name"`
|
||||
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
// If we are not given an explicit ssh_keypair_name or
|
||||
// ssh_private_key_file, then create a temporary one, but only if the
|
||||
// temporary_key_pair_name has not been provided and we are not using
|
||||
// ssh_password.
|
||||
if c.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" &&
|
||||
c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" {
|
||||
|
||||
c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
// if we are not given an explicit keypairname, create a temporary one
|
||||
if c.SSHKeyPairName == "" {
|
||||
c.TemporaryKeyPairName = fmt.Sprintf(
|
||||
"packer %s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
if c.WindowsPasswordTimeout == 0 {
|
||||
c.WindowsPasswordTimeout = 20 * time.Minute
|
||||
}
|
||||
|
||||
if c.RunTags == nil {
|
||||
c.RunTags = make(map[string]string)
|
||||
c.WindowsPasswordTimeout = 10 * time.Minute
|
||||
}
|
||||
|
||||
// Validation
|
||||
errs := c.Comm.Prepare(ctx)
|
||||
|
||||
// Validating ssh_interface
|
||||
if c.Comm.SSHInterface != "public_ip" &&
|
||||
c.Comm.SSHInterface != "private_ip" &&
|
||||
c.Comm.SSHInterface != "public_dns" &&
|
||||
c.Comm.SSHInterface != "private_dns" &&
|
||||
c.Comm.SSHInterface != "" {
|
||||
errs = append(errs, fmt.Errorf("Unknown interface type: %s", c.Comm.SSHInterface))
|
||||
}
|
||||
|
||||
if c.Comm.SSHKeyPairName != "" {
|
||||
if c.Comm.Type == "winrm" && c.Comm.WinRMPassword == "" && c.Comm.SSHPrivateKeyFile == "" {
|
||||
errs = append(errs, fmt.Errorf("ssh_private_key_file must be provided to retrieve the winrm password when using ssh_keypair_name."))
|
||||
} else if c.Comm.SSHPrivateKeyFile == "" && !c.Comm.SSHAgentAuth {
|
||||
errs = append(errs, fmt.Errorf("ssh_private_key_file must be provided or ssh_agent_auth enabled when ssh_keypair_name is specified."))
|
||||
}
|
||||
}
|
||||
|
||||
if c.SourceAmi == "" && c.SourceAmiFilter.Empty() {
|
||||
errs = append(errs, fmt.Errorf("A source_ami or source_ami_filter must be specified"))
|
||||
}
|
||||
|
||||
if c.SourceAmi == "" && c.SourceAmiFilter.NoOwner() {
|
||||
errs = append(errs, fmt.Errorf("For security reasons, your source AMI filter must declare an owner."))
|
||||
if c.SourceAmi == "" {
|
||||
errs = append(errs, errors.New("A source_ami 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 +83,5 @@ 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"
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@ package common
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -29,13 +28,6 @@ func testConfig() *RunConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func testConfigFilter() *RunConfig {
|
||||
config := testConfig()
|
||||
config.SourceAmi = ""
|
||||
config.SourceAmiFilter = AmiFilterOptions{}
|
||||
return config
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare(t *testing.T) {
|
||||
c := testConfig()
|
||||
err := c.Prepare(nil)
|
||||
@@ -48,98 +40,28 @@ func TestRunConfigPrepare_InstanceType(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.InstanceType = ""
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if an instance_type is not specified")
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceAmi(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.SourceAmi = ""
|
||||
if err := c.Prepare(nil); len(err) != 2 {
|
||||
t.Fatalf("Should error if a source_ami (or source_ami_filter) is not specified")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceAmiFilterBlank(t *testing.T) {
|
||||
c := testConfigFilter()
|
||||
if err := c.Prepare(nil); len(err) != 2 {
|
||||
t.Fatalf("Should error if source_ami_filter is empty or not specified (and source_ami is not specified)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceAmiFilterOwnersBlank(t *testing.T) {
|
||||
c := testConfigFilter()
|
||||
filter_key := "name"
|
||||
filter_value := "foo"
|
||||
c.SourceAmiFilter = AmiFilterOptions{Filters: map[*string]*string{&filter_key: &filter_value}}
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if Owners is not specified)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceAmiFilterGood(t *testing.T) {
|
||||
c := testConfigFilter()
|
||||
owner := "123"
|
||||
filter_key := "name"
|
||||
filter_value := "foo"
|
||||
goodFilter := AmiFilterOptions{Owners: []*string{&owner}, Filters: map[*string]*string{&filter_key: &filter_value}}
|
||||
c.SourceAmiFilter = goodFilter
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_EnableT2UnlimitedGood(t *testing.T) {
|
||||
c := testConfig()
|
||||
// Must have a T2 instance type if T2 Unlimited is enabled
|
||||
c.InstanceType = "t2.micro"
|
||||
c.EnableT2Unlimited = true
|
||||
err := c.Prepare(nil)
|
||||
if len(err) > 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_EnableT2UnlimitedBadInstanceType(t *testing.T) {
|
||||
c := testConfig()
|
||||
// T2 Unlimited cannot be used with instance types other than T2
|
||||
c.InstanceType = "m5.large"
|
||||
c.EnableT2Unlimited = true
|
||||
err := c.Prepare(nil)
|
||||
if len(err) != 1 {
|
||||
t.Fatalf("Should error if T2 Unlimited is enabled with non-T2 instance_type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_EnableT2UnlimitedBadWithSpotInstanceRequest(t *testing.T) {
|
||||
c := testConfig()
|
||||
// T2 Unlimited cannot be used with Spot Instances
|
||||
c.InstanceType = "t2.micro"
|
||||
c.EnableT2Unlimited = true
|
||||
c.SpotPrice = "auto"
|
||||
c.SpotPriceAutoProduct = "Linux/UNIX"
|
||||
err := c.Prepare(nil)
|
||||
if len(err) != 1 {
|
||||
t.Fatalf("Should error if T2 Unlimited has been used in conjuntion with a Spot Price request")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SpotAuto(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.SpotPrice = "auto"
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if spot_price_auto_product is not set and spot_price is set to auto")
|
||||
}
|
||||
|
||||
// Good - SpotPrice and SpotPriceAutoProduct are correctly set
|
||||
c.SpotPriceAutoProduct = "foo"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c.SpotPrice = ""
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if spot_price is not set to auto and spot_price_auto_product is set")
|
||||
c.SpotPriceAutoProduct = "foo"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,19 +86,26 @@ func TestRunConfigPrepare_SSHPort(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SSHUsername(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.Comm.SSHUsername = ""
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_UserData(t *testing.T) {
|
||||
c := testConfig()
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
c.UserData = "foo"
|
||||
c.UserDataFile = tf.Name()
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if user_data string and user_data_file have both been specified")
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,14 +117,13 @@ func TestRunConfigPrepare_UserDataFile(t *testing.T) {
|
||||
|
||||
c.UserDataFile = "idontexistidontthink"
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if the file specified by user_data_file does not exist")
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
c.UserDataFile = tf.Name()
|
||||
@@ -206,27 +134,12 @@ func TestRunConfigPrepare_UserDataFile(t *testing.T) {
|
||||
|
||||
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.Comm.SSHTemporaryKeyPairName = ""
|
||||
c.TemporaryKeyPairName = ""
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.Comm.SSHTemporaryKeyPairName == "" {
|
||||
t.Fatal("keypair name is empty")
|
||||
}
|
||||
|
||||
// Match prefix and UUID, e.g. "packer_5790d491-a0b8-c84c-c9d2-2aea55086550".
|
||||
r := regexp.MustCompile(`\Apacker_(?:(?i)[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}?)\z`)
|
||||
if !r.MatchString(c.Comm.SSHTemporaryKeyPairName) {
|
||||
t.Fatal("keypair name is not valid")
|
||||
}
|
||||
|
||||
c.Comm.SSHTemporaryKeyPairName = "ssh-key-123"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.Comm.SSHTemporaryKeyPairName != "ssh-key-123" {
|
||||
t.Fatal("keypair name does not match")
|
||||
if c.TemporaryKeyPairName == "" {
|
||||
t.Fatal("keypair empty")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,52 +6,21 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
type ec2Describer interface {
|
||||
DescribeInstances(*ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
|
||||
}
|
||||
|
||||
var (
|
||||
// modified in tests
|
||||
sshHostSleepDuration = time.Second
|
||||
"github.com/mitchellh/multistep"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// SSHHost returns a function that can be given to the SSH communicator
|
||||
// for determining the SSH address based on the instance DNS name.
|
||||
func SSHHost(e ec2Describer, sshInterface string) func(multistep.StateBag) (string, error) {
|
||||
func SSHHost(e *ec2.EC2, private bool) func(multistep.StateBag) (string, error) {
|
||||
return func(state multistep.StateBag) (string, error) {
|
||||
const tries = 2
|
||||
// <= with current structure to check result of describing `tries` times
|
||||
for j := 0; j <= tries; j++ {
|
||||
for j := 0; j < 2; j++ {
|
||||
var host string
|
||||
i := state.Get("instance").(*ec2.Instance)
|
||||
if sshInterface != "" {
|
||||
switch sshInterface {
|
||||
case "public_ip":
|
||||
if i.PublicIpAddress != nil {
|
||||
host = *i.PublicIpAddress
|
||||
}
|
||||
case "private_ip":
|
||||
if i.PrivateIpAddress != nil {
|
||||
host = *i.PrivateIpAddress
|
||||
}
|
||||
case "public_dns":
|
||||
if i.PublicDnsName != nil {
|
||||
host = *i.PublicDnsName
|
||||
}
|
||||
case "private_dns":
|
||||
if i.PrivateDnsName != nil {
|
||||
host = *i.PrivateDnsName
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown interface type: %s", sshInterface))
|
||||
}
|
||||
} else if i.VpcId != nil && *i.VpcId != "" {
|
||||
if i.PublicIpAddress != nil && *i.PublicIpAddress != "" {
|
||||
if i.VpcId != nil && *i.VpcId != "" {
|
||||
if i.PublicIpAddress != nil && *i.PublicIpAddress != "" && !private {
|
||||
host = *i.PublicIpAddress
|
||||
} else if i.PrivateIpAddress != nil && *i.PrivateIpAddress != "" {
|
||||
} else {
|
||||
host = *i.PrivateIpAddress
|
||||
}
|
||||
} else if i.PublicDnsName != nil && *i.PublicDnsName != "" {
|
||||
@@ -73,10 +42,31 @@ func SSHHost(e ec2Describer, sshInterface string) func(multistep.StateBag) (stri
|
||||
return "", fmt.Errorf("instance not found: %s", *i.InstanceId)
|
||||
}
|
||||
|
||||
state.Put("instance", r.Reservations[0].Instances[0])
|
||||
time.Sleep(sshHostSleepDuration)
|
||||
state.Put("instance", &r.Reservations[0].Instances[0])
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
return "", errors.New("couldn't determine address for instance")
|
||||
return "", errors.New("couldn't determine IP address for instance")
|
||||
}
|
||||
}
|
||||
|
||||
// SSHConfig returns a function that can be used for the SSH communicator
|
||||
// config for connecting to the instance created over SSH using the generated
|
||||
// private key.
|
||||
func SSHConfig(username string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
privateKey := state.Get("privateKey").(string)
|
||||
|
||||
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
const (
|
||||
privateIP = "10.0.0.1"
|
||||
publicIP = "192.168.1.1"
|
||||
privateDNS = "private.dns.test"
|
||||
publicDNS = "public.dns.test"
|
||||
)
|
||||
|
||||
func TestSSHHost(t *testing.T) {
|
||||
origSshHostSleepDuration := sshHostSleepDuration
|
||||
defer func() { sshHostSleepDuration = origSshHostSleepDuration }()
|
||||
sshHostSleepDuration = 0
|
||||
|
||||
var cases = []struct {
|
||||
allowTries int
|
||||
vpcId string
|
||||
sshInterface string
|
||||
|
||||
ok bool
|
||||
wantHost string
|
||||
}{
|
||||
{1, "", "", true, publicDNS},
|
||||
{1, "", "private_ip", true, privateIP},
|
||||
{1, "vpc-id", "", true, publicIP},
|
||||
{1, "vpc-id", "private_ip", true, privateIP},
|
||||
{1, "vpc-id", "private_dns", true, privateDNS},
|
||||
{1, "vpc-id", "public_dns", true, publicDNS},
|
||||
{1, "vpc-id", "public_ip", true, publicIP},
|
||||
{2, "", "", true, publicDNS},
|
||||
{2, "", "private_ip", true, privateIP},
|
||||
{2, "vpc-id", "", true, publicIP},
|
||||
{2, "vpc-id", "private_ip", true, privateIP},
|
||||
{2, "vpc-id", "private_dns", true, privateDNS},
|
||||
{2, "vpc-id", "public_dns", true, publicDNS},
|
||||
{2, "vpc-id", "public_ip", true, publicIP},
|
||||
{3, "", "", false, ""},
|
||||
{3, "", "private_ip", false, ""},
|
||||
{3, "vpc-id", "", false, ""},
|
||||
{3, "vpc-id", "private_ip", false, ""},
|
||||
{3, "vpc-id", "private_dns", false, ""},
|
||||
{3, "vpc-id", "public_dns", false, ""},
|
||||
{3, "vpc-id", "public_ip", false, ""},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
testSSHHost(t, c.allowTries, c.vpcId, c.sshInterface, c.ok, c.wantHost)
|
||||
}
|
||||
}
|
||||
|
||||
func testSSHHost(t *testing.T, allowTries int, vpcId string, sshInterface string, ok bool, wantHost string) {
|
||||
t.Logf("allowTries=%d vpcId=%s sshInterface=%s ok=%t wantHost=%q", allowTries, vpcId, sshInterface, ok, wantHost)
|
||||
|
||||
e := &fakeEC2Describer{
|
||||
allowTries: allowTries,
|
||||
vpcId: vpcId,
|
||||
privateIP: privateIP,
|
||||
publicIP: publicIP,
|
||||
privateDNS: privateDNS,
|
||||
publicDNS: publicDNS,
|
||||
}
|
||||
|
||||
f := SSHHost(e, sshInterface)
|
||||
st := &multistep.BasicStateBag{}
|
||||
st.Put("instance", &ec2.Instance{
|
||||
InstanceId: aws.String("instance-id"),
|
||||
})
|
||||
|
||||
host, err := f(st)
|
||||
|
||||
if e.tries > allowTries {
|
||||
t.Fatalf("got %d ec2 DescribeInstances tries, want %d", e.tries, allowTries)
|
||||
}
|
||||
|
||||
switch {
|
||||
case ok && err != nil:
|
||||
t.Fatalf("expected no error, got %+v", err)
|
||||
case !ok && err == nil:
|
||||
t.Fatalf("expected error, got none and host %s", host)
|
||||
}
|
||||
|
||||
if host != wantHost {
|
||||
t.Fatalf("got host %s, want %s", host, wantHost)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeEC2Describer struct {
|
||||
allowTries int
|
||||
tries int
|
||||
|
||||
vpcId string
|
||||
privateIP, publicIP, privateDNS, publicDNS string
|
||||
}
|
||||
|
||||
func (d *fakeEC2Describer) DescribeInstances(in *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
|
||||
d.tries++
|
||||
|
||||
instance := &ec2.Instance{
|
||||
InstanceId: aws.String("instance-id"),
|
||||
}
|
||||
|
||||
if d.vpcId != "" {
|
||||
instance.VpcId = aws.String(d.vpcId)
|
||||
}
|
||||
|
||||
if d.tries >= d.allowTries {
|
||||
instance.PublicIpAddress = aws.String(d.publicIP)
|
||||
instance.PrivateIpAddress = aws.String(d.privateIP)
|
||||
instance.PublicDnsName = aws.String(d.publicDNS)
|
||||
instance.PrivateDnsName = aws.String(d.privateDNS)
|
||||
}
|
||||
|
||||
out := &ec2.DescribeInstancesOutput{
|
||||
Reservations: []*ec2.Reservation{
|
||||
{
|
||||
Instances: []*ec2.Instance{instance},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
+182
-294
@@ -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,207 @@ type StateChangeConf struct {
|
||||
Target string
|
||||
}
|
||||
|
||||
// Following are wrapper functions that use Packer's environment-variables to
|
||||
// determing retry logic, then call the AWS SDK's built-in waiters.
|
||||
|
||||
func WaitUntilAMIAvailable(ctx aws.Context, conn *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 := 2
|
||||
maxTicks := int(TimeoutSeconds()/sleepSeconds) + 1
|
||||
notfoundTick := 0
|
||||
|
||||
for {
|
||||
var currentState string
|
||||
i, currentState, err = conf.Refresh()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if i == nil {
|
||||
// If we didn't find the resource, check if we have been
|
||||
// not finding it for awhile, and if so, report an error.
|
||||
notfoundTick += 1
|
||||
if notfoundTick > maxTicks {
|
||||
return nil, errors.New("couldn't find resource")
|
||||
}
|
||||
} else {
|
||||
// Reset the counter for when a resource isn't found
|
||||
notfoundTick = 0
|
||||
|
||||
if currentState == conf.Target {
|
||||
return
|
||||
}
|
||||
|
||||
if conf.StepState != nil {
|
||||
if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
|
||||
return nil, errors.New("interrupted")
|
||||
}
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, allowed := range conf.Pending {
|
||||
if currentState == allowed {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
err := fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(sleepSeconds) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func getWaiterOptions() []request.WaiterOption {
|
||||
envOverrides := getEnvOverrides()
|
||||
waitOpts := applyEnvOverrides(envOverrides)
|
||||
return waitOpts
|
||||
func isTransientNetworkError(err error) bool {
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getOverride(varInfo envInfo) envInfo {
|
||||
override := os.Getenv(varInfo.envKey)
|
||||
// Returns 300 seconds (5 minutes) by default
|
||||
// Some AWS operations, like copying an AMI to a distant region, take a very long time
|
||||
// Allow user to override with AWS_TIMEOUT_SECONDS environment variable
|
||||
func TimeoutSeconds() (seconds int) {
|
||||
seconds = 300
|
||||
|
||||
override := os.Getenv("AWS_TIMEOUT_SECONDS")
|
||||
if override != "" {
|
||||
n, err := strconv.Atoi(override)
|
||||
if err != nil {
|
||||
log.Printf("Invalid %s '%s', using default", varInfo.envKey, override)
|
||||
log.Printf("Invalid timeout seconds '%s', using default", override)
|
||||
} else {
|
||||
varInfo.overridden = true
|
||||
varInfo.Val = n
|
||||
seconds = n
|
||||
}
|
||||
}
|
||||
|
||||
return varInfo
|
||||
}
|
||||
func getEnvOverrides() overridableWaitVars {
|
||||
// Load env vars from environment.
|
||||
envValues := overridableWaitVars{
|
||||
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
|
||||
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
|
||||
envInfo{"AWS_TIMEOUT_SECONDS", 0, false},
|
||||
}
|
||||
|
||||
envValues.awsMaxAttempts = getOverride(envValues.awsMaxAttempts)
|
||||
envValues.awsPollDelaySeconds = getOverride(envValues.awsPollDelaySeconds)
|
||||
envValues.awsTimeoutSeconds = getOverride(envValues.awsTimeoutSeconds)
|
||||
|
||||
return envValues
|
||||
}
|
||||
|
||||
func applyEnvOverrides(envOverrides overridableWaitVars) []request.WaiterOption {
|
||||
waitOpts := make([]request.WaiterOption, 0)
|
||||
// If user has set poll delay seconds, overwrite it. If user has NOT,
|
||||
// default to a poll delay of 2 seconds
|
||||
if envOverrides.awsPollDelaySeconds.overridden {
|
||||
delaySeconds := request.ConstantWaiterDelay(time.Duration(envOverrides.awsPollDelaySeconds.Val) * time.Second)
|
||||
waitOpts = append(waitOpts, request.WithWaiterDelay(delaySeconds))
|
||||
}
|
||||
|
||||
// If user has set max attempts, overwrite it. If user hasn't set max
|
||||
// attempts, default to whatever the waiter has set as a default.
|
||||
if envOverrides.awsMaxAttempts.overridden {
|
||||
waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(envOverrides.awsMaxAttempts.Val))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
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("Allowing %ds to complete (change with AWS_TIMEOUT_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,30 +1,28 @@
|
||||
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)
|
||||
snapshots := state.Get("snapshots").(map[string][]string)
|
||||
ami := amis[*ec2conn.Config.Region]
|
||||
|
||||
if len(s.Regions) == 0 {
|
||||
@@ -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,17 +44,13 @@ 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, err := amiRegionCopy(state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region)
|
||||
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
amis[region] = id
|
||||
snapshots[region] = snapshotIds
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
@@ -84,56 +77,42 @@ 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) {
|
||||
snapshotIds := []string{}
|
||||
isEncrypted := false
|
||||
// returns the resulting ID or error.
|
||||
func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string, imageId string,
|
||||
target string, source string) (string, error) {
|
||||
|
||||
// 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
|
||||
return "", err
|
||||
}
|
||||
// if we've provided a map of key ids to regions, use those keys.
|
||||
if len(keyID) > 0 {
|
||||
isEncrypted = true
|
||||
}
|
||||
regionconn := ec2.New(session.Copy(&aws.Config{
|
||||
Region: aws.String(target)},
|
||||
))
|
||||
awsConfig.Region = aws.String(target)
|
||||
|
||||
sess := session.New(awsConfig)
|
||||
regionconn := ec2.New(sess)
|
||||
|
||||
resp, err := regionconn.CopyImage(&ec2.CopyImageInput{
|
||||
SourceRegion: &source,
|
||||
SourceImageId: &imageId,
|
||||
Name: &name,
|
||||
Encrypted: aws.Bool(isEncrypted),
|
||||
KmsKeyId: aws.String(keyID),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", snapshotIds, fmt.Errorf("Error Copying AMI (%s) to region (%s): %s",
|
||||
return "", fmt.Errorf("Error Copying AMI (%s) to region (%s): %s",
|
||||
imageId, target, err)
|
||||
}
|
||||
|
||||
// Wait for the image to become ready
|
||||
if err := WaitUntilAMIAvailable(ctx, regionconn, *resp.ImageId); err != nil {
|
||||
return "", snapshotIds, fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s",
|
||||
stateChange := StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
Target: "available",
|
||||
Refresh: AMIStateRefreshFunc(regionconn, *resp.ImageId),
|
||||
StepState: state,
|
||||
}
|
||||
|
||||
if _, err := WaitForState(&stateChange); err != nil {
|
||||
return "", fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s",
|
||||
*resp.ImageId, target, err)
|
||||
}
|
||||
|
||||
// Getting snapshot IDs out of the copied AMI
|
||||
describeImageResp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{resp.ImageId}})
|
||||
if err != nil {
|
||||
return "", snapshotIds, fmt.Errorf("Error describing copied AMI (%s) in region (%s): %s",
|
||||
imageId, target, err)
|
||||
}
|
||||
|
||||
for _, blockDeviceMapping := range describeImageResp.Images[0].BlockDeviceMappings {
|
||||
if blockDeviceMapping.Ebs != nil && blockDeviceMapping.Ebs.SnapshotId != nil {
|
||||
snapshotIds = append(snapshotIds, *blockDeviceMapping.Ebs.SnapshotId)
|
||||
}
|
||||
}
|
||||
|
||||
return *resp.ImageId, snapshotIds, nil
|
||||
return *resp.ImageId, nil
|
||||
}
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// stepCleanupVolumes cleans up any orphaned volumes that were not designated to
|
||||
// remain after termination of the instance. These volumes are typically ones
|
||||
// that are marked as "delete on terminate:false" in the source_ami of a build.
|
||||
type StepCleanupVolumes struct {
|
||||
BlockDevices BlockDevices
|
||||
}
|
||||
|
||||
func (s *StepCleanupVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
// stepCleanupVolumes is for Cleanup only
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instanceRaw := state.Get("instance")
|
||||
var instance *ec2.Instance
|
||||
if instanceRaw != nil {
|
||||
instance = instanceRaw.(*ec2.Instance)
|
||||
}
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
if instance == nil {
|
||||
ui.Say("No volumes to clean up, skipping")
|
||||
return
|
||||
}
|
||||
|
||||
ui.Say("Cleaning up any extra volumes...")
|
||||
|
||||
// Collect Volume information from the cached Instance as a map of volume-id
|
||||
// to device name, to compare with save list below
|
||||
var vl []*string
|
||||
volList := make(map[string]string)
|
||||
for _, bdm := range instance.BlockDeviceMappings {
|
||||
if bdm.Ebs != nil {
|
||||
vl = append(vl, bdm.Ebs.VolumeId)
|
||||
volList[*bdm.Ebs.VolumeId] = *bdm.DeviceName
|
||||
}
|
||||
}
|
||||
|
||||
// Using the volume list from the cached Instance, check with AWS for up to
|
||||
// date information on them
|
||||
resp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesInput{
|
||||
Filters: []*ec2.Filter{
|
||||
{
|
||||
Name: aws.String("volume-id"),
|
||||
Values: vl,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Error describing volumes: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
// If any of the returned volumes are in a "deleting" stage or otherwise not
|
||||
// available, remove them from the list of volumes
|
||||
for _, v := range resp.Volumes {
|
||||
if v.State != nil && *v.State != "available" {
|
||||
delete(volList, *v.VolumeId)
|
||||
}
|
||||
}
|
||||
|
||||
if len(resp.Volumes) == 0 {
|
||||
ui.Say("No volumes to clean up, skipping")
|
||||
return
|
||||
}
|
||||
|
||||
// Filter out any devices created as part of the launch mappings, since
|
||||
// we'll let amazon follow the `delete_on_termination` setting.
|
||||
for _, b := range s.BlockDevices.LaunchMappings {
|
||||
for volKey, volName := range volList {
|
||||
if volName == b.DeviceName {
|
||||
delete(volList, volKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy remaining volumes
|
||||
for k := range volList {
|
||||
ui.Say(fmt.Sprintf("Destroying volume (%s)...", k))
|
||||
_, err := ec2conn.DeleteVolume(&ec2.DeleteVolumeInput{VolumeId: aws.String(k)})
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Error deleting volume: %s", err))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,131 +1,87 @@
|
||||
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"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepCreateTags struct {
|
||||
Tags TagMap
|
||||
SnapshotTags TagMap
|
||||
Ctx interpolate.Context
|
||||
Tags map[string]string
|
||||
}
|
||||
|
||||
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() {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
if len(s.Tags) > 0 {
|
||||
for region, ami := range amis {
|
||||
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami))
|
||||
|
||||
// Adds tags to AMIs and snapshots
|
||||
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),
|
||||
})
|
||||
|
||||
// Retrieve image list for given AMI
|
||||
resourceIds := []*string{&ami}
|
||||
imageResp, err := regionConn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: resourceIds,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error retrieving details for AMI (%s): %s", ami, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(imageResp.Images) == 0 {
|
||||
err := fmt.Errorf("Error retrieving details for AMI (%s), no images found", ami)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
image := imageResp.Images[0]
|
||||
snapshotIds := []*string{}
|
||||
|
||||
// Add only those with a Snapshot ID, i.e. not Ephemeral
|
||||
for _, device := range image.BlockDeviceMappings {
|
||||
if device.Ebs != nil && device.Ebs.SnapshotId != nil {
|
||||
ui.Say(fmt.Sprintf("Tagging snapshot: %s", *device.Ebs.SnapshotId))
|
||||
resourceIds = append(resourceIds, device.Ebs.SnapshotId)
|
||||
snapshotIds = append(snapshotIds, device.Ebs.SnapshotId)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert tags to ec2.Tag format
|
||||
ui.Say("Creating AMI tags")
|
||||
amiTags, err := s.Tags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
amiTags.Report(ui)
|
||||
|
||||
ui.Say("Creating snapshot tags")
|
||||
snapshotTags, err := s.SnapshotTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
snapshotTags.Report(ui)
|
||||
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
|
||||
// Tag images and snapshots
|
||||
_, err := regionConn.CreateTags(&ec2.CreateTagsInput{
|
||||
Resources: resourceIds,
|
||||
Tags: amiTags,
|
||||
})
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
if awsErr.Code() == "InvalidAMIID.NotFound" ||
|
||||
awsErr.Code() == "InvalidSnapshot.NotFound" {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Override tags on snapshots
|
||||
if len(snapshotTags) > 0 {
|
||||
_, err = regionConn.CreateTags(&ec2.CreateTagsInput{
|
||||
Resources: snapshotIds,
|
||||
Tags: snapshotTags,
|
||||
var ec2Tags []*ec2.Tag
|
||||
for key, value := range s.Tags {
|
||||
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value))
|
||||
ec2Tags = append(ec2Tags, &ec2.Tag{
|
||||
Key: aws.String(key),
|
||||
Value: aws.String(value),
|
||||
})
|
||||
}
|
||||
if err == nil {
|
||||
return true, nil
|
||||
|
||||
// Declare list of resources to tag
|
||||
resourceIds := []*string{&ami}
|
||||
awsConfig := aws.Config{
|
||||
Credentials: ec2conn.Config.Credentials,
|
||||
Region: aws.String(region),
|
||||
}
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
if awsErr.Code() == "InvalidSnapshot.NotFound" {
|
||||
return false, nil
|
||||
session := session.New(&awsConfig)
|
||||
|
||||
regionconn := ec2.New(session)
|
||||
|
||||
// Retrieve image list for given AMI
|
||||
imageResp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: resourceIds,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error retrieving details for AMI (%s): %s", ami, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(imageResp.Images) == 0 {
|
||||
err := fmt.Errorf("Error retrieving details for AMI (%s), no images found", ami)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
image := imageResp.Images[0]
|
||||
|
||||
// Add only those with a Snapshot ID, i.e. not Ephemeral
|
||||
for _, device := range image.BlockDeviceMappings {
|
||||
if device.Ebs != nil && device.Ebs.SnapshotId != nil {
|
||||
ui.Say(fmt.Sprintf("Tagging snapshot: %s", *device.Ebs.SnapshotId))
|
||||
resourceIds = append(resourceIds, device.Ebs.SnapshotId)
|
||||
}
|
||||
}
|
||||
return true, err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error adding tags to Resources (%#v): %s", resourceIds, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
_, err = regionconn.CreateTags(&ec2.CreateTagsInput{
|
||||
Resources: resourceIds,
|
||||
Tags: ec2Tags,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error adding tags to Resources (%#v): %s", resourceIds, 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