Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e8a3fa3ea | |||
| fb1c968d57 | |||
| 141440147f | |||
| bb92eef95e | |||
| 489c75a363 | |||
| 7bbc29d4b2 | |||
| 8e7bb5ec23 |
@@ -1,2 +0,0 @@
|
||||
|
||||
common/test-fixtures/root/* eol=lf
|
||||
@@ -12,7 +12,7 @@ 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
|
||||
|
||||
@@ -9,15 +9,8 @@
|
||||
.idea
|
||||
test/.env
|
||||
*~
|
||||
*.received.*
|
||||
|
||||
website/.bundle
|
||||
website/vendor
|
||||
|
||||
packer-test*.log
|
||||
|
||||
*.received.txt
|
||||
*.received.json
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
+1
-5
@@ -1,13 +1,9 @@
|
||||
env:
|
||||
- USER=travis
|
||||
|
||||
sudo: false
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.7.4
|
||||
- 1.8beta1
|
||||
- 1.6
|
||||
|
||||
install:
|
||||
- make deps
|
||||
|
||||
+196
-555
File diff suppressed because it is too large
Load Diff
+12
-31
@@ -69,8 +69,7 @@ following steps in order to be able to compile and test Packer. These instructio
|
||||
`$GOPATH/src/github.com/mitchellh/packer`.
|
||||
|
||||
4. When working on packer `cd $GOPATH/src/github.com/mitchellh/packer` so you
|
||||
can run `make` and easily access other files. Run `make help` to get
|
||||
information about make targets.
|
||||
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
|
||||
@@ -84,34 +83,11 @@ following steps in order to be able to compile and test Packer. These instructio
|
||||
7. If everything works well and the tests pass, run `go fmt` on your code
|
||||
before submitting a pull-request.
|
||||
|
||||
### Opening an Pull Request
|
||||
|
||||
When you are ready to open a pull-request, you will need to [fork packer](https://github.com/mitchellh/packer#fork-destination-box), push your changes to your fork, and then open a pull-request.
|
||||
|
||||
For example, my github username is `cbednarski` so I would do the following:
|
||||
|
||||
git checkout -b f-my-feature
|
||||
// develop a patch
|
||||
git push https://github.com/cbednarski/packer f-my-feature
|
||||
|
||||
From there, open your fork in your browser to open a new pull-request.
|
||||
|
||||
**Note** Go infers package names from their filepaths. This means `go build` will break if you `git clone` your fork instead of using `go get` on the main packer project.
|
||||
|
||||
### Tips for Working on Packer
|
||||
|
||||
#### Govendor
|
||||
#### Godeps
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -125,11 +101,16 @@ Packer has [acceptance tests](https://en.wikipedia.org/wiki/Acceptance_testing)
|
||||
for various builders. These typically require an API key (AWS, GCE), or
|
||||
additional software to be installed on your computer (VirtualBox, VMware).
|
||||
|
||||
If you're working on a new builder or builder feature and want verify it is functioning (and also hasn't broken anything else), we recommend running the
|
||||
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 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.
|
||||
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`:
|
||||
|
||||
@@ -142,5 +123,5 @@ down to a specific resource to test, since testing all of them at once can
|
||||
sometimes take a very long time.
|
||||
|
||||
Acceptance tests typically require other environment variables to be set for
|
||||
things such as API tokens and keys. Each test should error and tell you which
|
||||
credentials are missing, so those are not documented here.
|
||||
things such as access keys. The test itself should error early and tell you
|
||||
what to set, so it is not documented here.
|
||||
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,15 +4,14 @@ 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)
|
||||
GOFMT_FILES?=$$(find . -not -path "./vendor/*" -name "*.go")
|
||||
|
||||
default: deps generate test dev
|
||||
|
||||
ci: deps test
|
||||
|
||||
release: deps test releasebin package ## Build a release build
|
||||
release: deps test releasebin package
|
||||
|
||||
bin: deps ## Build debug/test build
|
||||
bin: deps
|
||||
@echo "WARN: 'make bin' is for debug / test builds only. Use 'make release' for release builds."
|
||||
@GO15VENDOREXPERIMENT=1 sh -c "$(CURDIR)/scripts/build.sh"
|
||||
|
||||
@@ -30,21 +29,21 @@ package:
|
||||
deps:
|
||||
go get github.com/mitchellh/gox
|
||||
go get golang.org/x/tools/cmd/stringer
|
||||
go get github.com/kardianos/govendor
|
||||
govendor sync
|
||||
@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
|
||||
@PACKER_DEV=1 GO15VENDOREXPERIMENT=1 sh -c "$(CURDIR)/scripts/build.sh"
|
||||
|
||||
fmt: ## Format Go code
|
||||
@gofmt -w -s $(GOFMT_FILES)
|
||||
|
||||
fmt-check: ## Check go code formatting
|
||||
$(CURDIR)/scripts/gofmtcheck.sh $(GOFMT_FILES)
|
||||
fmt:
|
||||
go fmt `go list ./... | grep -v vendor`
|
||||
|
||||
# Install js-beautify with npm install -g js-beautify
|
||||
fmt-examples:
|
||||
@@ -52,11 +51,11 @@ 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 command/plugin.go
|
||||
go fmt command/plugin.go
|
||||
|
||||
test: deps fmt-check ## Run unit tests
|
||||
test: deps
|
||||
@go test $(TEST) $(TESTARGS) -timeout=2m
|
||||
@go tool vet $(VET) ; if [ $$? -eq 1 ]; then \
|
||||
echo "ERROR: Vet found problems in the code."; \
|
||||
@@ -64,19 +63,22 @@ test: deps fmt-check ## Run unit tests
|
||||
fi
|
||||
|
||||
# testacc runs acceptance tests
|
||||
testacc: deps generate ## Run 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 ## Test for race conditions
|
||||
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 govendor. See CONTRIBUTING.md"
|
||||
@echo "INFO: Packer deps are managed by godep. See CONTRIBUTING.md"
|
||||
|
||||
help:
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
# 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,18 +1,7 @@
|
||||
# Packer
|
||||
|
||||
[![Build Status][travis-badge]][travis]
|
||||
[![Windows Build Status][appveyor-badge]][appveyor]
|
||||
[![GoDoc][godoc-badge]][godoc]
|
||||
[![GoReportCard][report-badge]][report]
|
||||
|
||||
[travis-badge]: https://travis-ci.org/mitchellh/packer.svg?branch=master
|
||||
[travis]: https://travis-ci.org/mitchellh/packer
|
||||
[appveyor-badge]: https://ci.appveyor.com/api/projects/status/github/mitchellh/packer?branch=master&svg=true
|
||||
[appveyor]: https://ci.appveyor.com/project/hashicorp/packer
|
||||
[godoc-badge]: https://godoc.org/github.com/mitchellh/packer?status.svg
|
||||
[godoc]: https://godoc.org/github.com/mitchellh/packer
|
||||
[report-badge]: https://goreportcard.com/badge/github.com/mitchellh/packer
|
||||
[report]: https://goreportcard.com/report/github.com/mitchellh/packer
|
||||
[](https://travis-ci.org/mitchellh/packer)
|
||||
[](https://ci.appveyor.com/project/hashicorp/packer)
|
||||
|
||||
* Website: http://www.packer.io
|
||||
* IRC: `#packer-tool` on Freenode
|
||||
@@ -26,7 +15,6 @@ performant, creating machine images for multiple platforms in parallel. Packer
|
||||
comes out of the box with support for the following platforms:
|
||||
|
||||
* Amazon EC2 (AMI). Both EBS-backed and instance-store AMIs
|
||||
* Azure
|
||||
* DigitalOcean
|
||||
* Docker
|
||||
* Google Compute Engine
|
||||
@@ -42,10 +30,6 @@ The images that Packer creates can easily be turned into
|
||||
[Vagrant](http://www.vagrantup.com) boxes.
|
||||
|
||||
## Quick Start
|
||||
Download and install packages and dependencies
|
||||
```
|
||||
go get github.com/mitchellh/packer
|
||||
```
|
||||
|
||||
**Note:** There is a great
|
||||
[introduction and getting started guide](http://www.packer.io/intro)
|
||||
@@ -54,7 +38,7 @@ will get you up and running quickly, at the sacrifice of not explaining some
|
||||
key points.
|
||||
|
||||
First, [download a pre-built Packer binary](http://www.packer.io/downloads.html)
|
||||
for your operating system or [compile Packer yourself](CONTRIBUTING.md#setting-up-go-to-work-on-packer).
|
||||
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
|
||||
|
||||
@@ -25,25 +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"`
|
||||
FromScratch bool `mapstructure:"from_scratch"`
|
||||
MountOptions []string `mapstructure:"mount_options"`
|
||||
MountPartition int `mapstructure:"mount_partition"`
|
||||
MountPath string `mapstructure:"mount_path"`
|
||||
PostMountCommands []string `mapstructure:"post_mount_commands"`
|
||||
PreMountCommands []string `mapstructure:"pre_mount_commands"`
|
||||
RootDeviceName string `mapstructure:"root_device_name"`
|
||||
RootVolumeSize int64 `mapstructure:"root_volume_size"`
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
SourceAmiFilter awscommon.AmiFilterOptions `mapstructure:"source_ami_filter"`
|
||||
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
|
||||
}
|
||||
@@ -65,8 +59,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"command_wrapper",
|
||||
"post_mount_commands",
|
||||
"pre_mount_commands",
|
||||
"mount_path",
|
||||
},
|
||||
},
|
||||
@@ -86,15 +78,15 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
|
||||
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"},
|
||||
}
|
||||
}
|
||||
|
||||
if len(b.config.CopyFiles) == 0 && !b.config.FromScratch {
|
||||
if len(b.config.CopyFiles) == 0 {
|
||||
b.config.CopyFiles = []string{"/etc/resolv.conf"}
|
||||
}
|
||||
|
||||
@@ -110,10 +102,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
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.ctx)...)
|
||||
|
||||
@@ -125,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
|
||||
}
|
||||
|
||||
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
|
||||
return warns, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
@@ -180,10 +137,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := session.NewSession(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
session := session.New(config)
|
||||
ec2conn := ec2.New(session)
|
||||
|
||||
wrappedCommand := func(command string) (string, error) {
|
||||
@@ -207,20 +161,11 @@ 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,
|
||||
EnhancedNetworking: b.config.AMIEnhancedNetworking,
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
},
|
||||
&StepCheckRootDevice{},
|
||||
)
|
||||
}
|
||||
|
||||
steps = append(steps,
|
||||
&awscommon.StepSourceAMIInfo{
|
||||
SourceAmi: b.config.SourceAmi,
|
||||
EnhancedNetworking: b.config.AMIEnhancedNetworking,
|
||||
},
|
||||
&StepCheckRootDevice{},
|
||||
&StepFlock{},
|
||||
&StepPrepareDevice{},
|
||||
&StepCreateVolume{
|
||||
@@ -228,25 +173,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
},
|
||||
&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{
|
||||
ForceDeregister: b.config.AMIForceDeregister,
|
||||
ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot,
|
||||
AMIName: b.config.AMIName,
|
||||
ForceDeregister: b.config.AMIForceDeregister,
|
||||
AMIName: b.config.AMIName,
|
||||
},
|
||||
&StepRegisterAMI{
|
||||
RootVolumeSize: b.config.RootVolumeSize,
|
||||
@@ -257,21 +195,26 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
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,
|
||||
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,
|
||||
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
|
||||
|
||||
@@ -71,7 +71,7 @@ func TestBuilderPrepare_ChrootMounts(t *testing.T) {
|
||||
}
|
||||
|
||||
config["chroot_mounts"] = [][]string{
|
||||
{"bad"},
|
||||
[]string{"bad"},
|
||||
}
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/post-processor/shell-local"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
func RunLocalCommands(commands []string, wrappedCommand CommandWrapper, ctx interpolate.Context, ui packer.Ui) error {
|
||||
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 := &shell_local.Communicator{}
|
||||
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
|
||||
}
|
||||
@@ -22,52 +22,40 @@ type StepCreateVolume struct {
|
||||
}
|
||||
|
||||
func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
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)
|
||||
|
||||
var createVolume *ec2.CreateVolumeInput
|
||||
if config.FromScratch {
|
||||
createVolume = &ec2.CreateVolumeInput{
|
||||
AvailabilityZone: instance.Placement.AvailabilityZone,
|
||||
Size: aws.Int64(s.RootVolumeSize),
|
||||
VolumeType: aws.String(ec2.VolumeTypeGp2),
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
if rootDevice == nil {
|
||||
err := fmt.Errorf("Couldn't find root device!")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Creating the root volume...")
|
||||
vs := *rootDevice.Ebs.VolumeSize
|
||||
if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize {
|
||||
vs = s.RootVolumeSize
|
||||
}
|
||||
|
||||
createVolume = &ec2.CreateVolumeInput{
|
||||
AvailabilityZone: instance.Placement.AvailabilityZone,
|
||||
Size: aws.Int64(vs),
|
||||
SnapshotId: rootDevice.Ebs.SnapshotId,
|
||||
VolumeType: rootDevice.Ebs.VolumeType,
|
||||
Iops: rootDevice.Ebs.Iops,
|
||||
// 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
|
||||
}
|
||||
|
||||
ui.Say("Creating the root volume...")
|
||||
vs := *rootDevice.Ebs.VolumeSize
|
||||
if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize {
|
||||
vs = s.RootVolumeSize
|
||||
}
|
||||
createVolume := &ec2.CreateVolumeInput{
|
||||
AvailabilityZone: instance.Placement.AvailabilityZone,
|
||||
Size: aws.Int64(vs),
|
||||
SnapshotId: rootDevice.Ebs.SnapshotId,
|
||||
VolumeType: rootDevice.Ebs.VolumeType,
|
||||
Iops: rootDevice.Ebs.Iops,
|
||||
}
|
||||
log.Printf("Create args: %+v", createVolume)
|
||||
|
||||
createVolumeResp, err := ec2conn.CreateVolume(createVolume)
|
||||
|
||||
@@ -33,18 +33,10 @@ type StepMountDevice struct {
|
||||
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)
|
||||
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)
|
||||
@@ -73,8 +65,9 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Source image virtualization type is: %s", *image.VirtualizationType)
|
||||
deviceMount := device
|
||||
if virtualizationType == "hvm" {
|
||||
if *image.VirtualizationType == "hvm" {
|
||||
deviceMount = fmt.Sprintf("%s%d", device, s.MountPartition)
|
||||
}
|
||||
state.Put("deviceMount", deviceMount)
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/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(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,39 +0,0 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/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(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) {}
|
||||
@@ -18,38 +18,22 @@ type StepRegisterAMI struct {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -60,20 +44,10 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
|
||||
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)
|
||||
|
||||
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
|
||||
if config.AMIEnhancedNetworking {
|
||||
@@ -114,12 +88,12 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -131,5 +105,6 @@ func buildRegisterOpts(config *Config, image *ec2.Image, mappings []*ec2.BlockDe
|
||||
registerOpts.KernelId = image.KernelId
|
||||
registerOpts.RamdiskId = image.RamdiskId
|
||||
}
|
||||
|
||||
return registerOpts
|
||||
}
|
||||
|
||||
@@ -71,12 +71,6 @@ func (s *StepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
|
||||
}
|
||||
|
||||
state.Put("snapshot_id", s.snapshotId)
|
||||
|
||||
snapshots := map[string][]string{
|
||||
*ec2conn.Config.Region: {s.snapshotId},
|
||||
}
|
||||
state.Put("snapshots", snapshots)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,11 @@ import (
|
||||
|
||||
// AccessConfig is for common configuration related to AWS access
|
||||
type AccessConfig struct {
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
RawRegion string `mapstructure:"region"`
|
||||
SkipValidation bool `mapstructure:"skip_region_validation"`
|
||||
Token string `mapstructure:"token"`
|
||||
ProfileName string `mapstructure:"profile"`
|
||||
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
|
||||
@@ -45,6 +44,7 @@ func (c *AccessConfig) Config() (*aws.Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
sess := session.New(config)
|
||||
creds = credentials.NewChainCredentials([]credentials.Provider{
|
||||
&credentials.StaticProvider{Value: credentials.Value{
|
||||
AccessKeyID: c.AccessKey,
|
||||
@@ -54,7 +54,7 @@ func (c *AccessConfig) Config() (*aws.Config, error) {
|
||||
&credentials.EnvProvider{},
|
||||
&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
|
||||
&ec2rolecreds.EC2RoleProvider{
|
||||
Client: ec2metadata.New(session.New(config)),
|
||||
Client: ec2metadata.New(sess),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -65,10 +65,8 @@ func (c *AccessConfig) Config() (*aws.Config, error) {
|
||||
// the region from the instance metadata if possible.
|
||||
func (c *AccessConfig) Region() (string, error) {
|
||||
if c.RawRegion != "" {
|
||||
if !c.SkipValidation {
|
||||
if valid := ValidateRegion(c.RawRegion); valid == false {
|
||||
return "", fmt.Errorf("Not a valid region: %s", c.RawRegion)
|
||||
}
|
||||
if valid := ValidateRegion(c.RawRegion); valid == false {
|
||||
return "", fmt.Errorf("Not a valid region: %s", c.RawRegion)
|
||||
}
|
||||
return c.RawRegion, nil
|
||||
}
|
||||
@@ -84,7 +82,7 @@ func (c *AccessConfig) Region() (string, error) {
|
||||
|
||||
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
if c.RawRegion != "" && !c.SkipValidation {
|
||||
if c.RawRegion != "" {
|
||||
if valid := ValidateRegion(c.RawRegion); valid == false {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", c.RawRegion))
|
||||
}
|
||||
|
||||
@@ -24,17 +24,4 @@ func TestAccessConfigPrepare_Region(t *testing.T) {
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.RawRegion = "custom"
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatalf("should have err")
|
||||
}
|
||||
|
||||
c.RawRegion = "custom"
|
||||
c.SkipValidation = true
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
c.SkipValidation = false
|
||||
|
||||
}
|
||||
|
||||
@@ -8,23 +8,16 @@ import (
|
||||
|
||||
// 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 map[string]string `mapstructure:"tags"`
|
||||
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
|
||||
AMIForceDeregister bool `mapstructure:"force_deregister"`
|
||||
AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot"`
|
||||
AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"`
|
||||
AMIKmsKeyId string `mapstructure:"kms_key_id"`
|
||||
SnapshotTags map[string]string `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 (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
@@ -46,12 +39,10 @@ func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
// Mark that we saw the region
|
||||
regionSet[region] = struct{}{}
|
||||
|
||||
if !c.AMISkipRegionValidation {
|
||||
// Verify the region is real
|
||||
if valid := ValidateRegion(region); valid == false {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
|
||||
continue
|
||||
}
|
||||
// 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)
|
||||
@@ -60,14 +51,6 @@ func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
c.AMIRegions = regions
|
||||
}
|
||||
|
||||
if len(c.AMIUsers) > 0 && c.AMIEncryptBootVolume {
|
||||
errs = append(errs, fmt.Errorf("Cannot share AMI with encrypted boot volume"))
|
||||
}
|
||||
|
||||
if len(c.SnapshotUsers) > 0 && len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume {
|
||||
errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -49,28 +49,4 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
if !reflect.DeepEqual(c.AMIRegions, expected) {
|
||||
t.Fatalf("bad: %#v", c.AMIRegions)
|
||||
}
|
||||
|
||||
c.AMIRegions = []string{"custom"}
|
||||
c.AMISkipRegionValidation = true
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatal("shouldn't have error")
|
||||
}
|
||||
c.AMISkipRegionValidation = false
|
||||
|
||||
}
|
||||
|
||||
func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
|
||||
c := testAMIConfig()
|
||||
c.AMIUsers = []string{"testAccountID"}
|
||||
c.AMIEncryptBootVolume = true
|
||||
|
||||
c.AMIKmsKeyId = ""
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
|
||||
}
|
||||
|
||||
c.AMIKmsKeyId = "89c3fb9a-de87-4f2a-aedc-fddc5138193c"
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,25 +73,9 @@ func (a *Artifact) Destroy() error {
|
||||
Credentials: a.Conn.Config.Credentials,
|
||||
Region: aws.String(region),
|
||||
}
|
||||
session, err := session.NewSession(regionConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
regionConn := ec2.New(session)
|
||||
sess := session.New(regionConfig)
|
||||
regionConn := ec2.New(sess)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Deregister ami
|
||||
input := &ec2.DeregisterImageInput{
|
||||
ImageId: &imageId,
|
||||
}
|
||||
|
||||
@@ -22,15 +22,7 @@ type BlockDevice struct {
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -85,10 +77,10 @@ func (b *BlockDevices) Prepare(ctx *interpolate.Context) []error {
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -124,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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,11 +61,7 @@ func (c *CLIConfig) CredentialsFromProfile(conf *aws.Config) (*credentials.Crede
|
||||
return c.SourceCredentials, nil
|
||||
}
|
||||
srcCfg := aws.NewConfig().Copy(conf).WithCredentials(c.SourceCredentials)
|
||||
session, err := session.NewSession(srcCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
svc := sts.New(session)
|
||||
svc := sts.New(session.New(), srcCfg)
|
||||
res, err := svc.AssumeRole(c.AssumeRoleInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
package common
|
||||
|
||||
// aws ec2 describe-regions --query 'Regions[].{Name:RegionName}' --output text | sed 's/\(.*\)/"\1",/' | sort
|
||||
func listEC2Regions() []string {
|
||||
return []string{
|
||||
"ap-northeast-1",
|
||||
"ap-northeast-2",
|
||||
"ap-south-1",
|
||||
"ap-southeast-1",
|
||||
"ap-southeast-2",
|
||||
"ca-central-1",
|
||||
"cn-north-1",
|
||||
"eu-central-1",
|
||||
"eu-west-1",
|
||||
"eu-west-2",
|
||||
"sa-east-1",
|
||||
"us-east-1",
|
||||
"us-east-2",
|
||||
"us-gov-west-1",
|
||||
"us-west-1",
|
||||
"us-west-2",
|
||||
// not part of autogenerated list
|
||||
"us-gov-west-1",
|
||||
"cn-north-1",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/packer/common/uuid"
|
||||
@@ -12,41 +11,26 @@ import (
|
||||
"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
|
||||
}
|
||||
|
||||
// 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"`
|
||||
EbsOptimized bool `mapstructure:"ebs_optimized"`
|
||||
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
RunTags map[string]string `mapstructure:"run_tags"`
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
SourceAmiFilter AmiFilterOptions `mapstructure:"source_ami_filter"`
|
||||
SpotPrice string `mapstructure:"spot_price"`
|
||||
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
|
||||
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SecurityGroupIds []string `mapstructure:"security_group_ids"`
|
||||
SubnetId string `mapstructure:"subnet_id"`
|
||||
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
UserDataFile string `mapstructure:"user_data_file"`
|
||||
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
InstanceInitiatedShutdownBehavior string `mapstructure:"shutdown_behavior"`
|
||||
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"`
|
||||
@@ -55,28 +39,20 @@ type RunConfig struct {
|
||||
}
|
||||
|
||||
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.SSHKeyPairName == "" && c.TemporaryKeyPairName == "" &&
|
||||
c.Comm.SSHPrivateKey == "" && c.Comm.SSHPassword == "" {
|
||||
|
||||
c.TemporaryKeyPairName = 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 = 10 * time.Minute
|
||||
}
|
||||
|
||||
if c.RunTags == nil {
|
||||
c.RunTags = make(map[string]string)
|
||||
}
|
||||
|
||||
// Validation
|
||||
errs := c.Comm.Prepare(ctx)
|
||||
if c.SourceAmi == "" && c.SourceAmiFilter.Empty() {
|
||||
errs = append(errs, errors.New("A source_ami or source_ami_filter must be specified"))
|
||||
if c.SourceAmi == "" {
|
||||
errs = append(errs, errors.New("A source_ami must be specified"))
|
||||
}
|
||||
|
||||
if c.InstanceType == "" {
|
||||
@@ -107,11 +83,5 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []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."))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package common
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
@@ -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)
|
||||
@@ -60,25 +52,6 @@ func TestRunConfigPrepare_SourceAmi(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceAmiFilterBlank(t *testing.T) {
|
||||
c := testConfigFilter()
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
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_SpotAuto(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.SpotPrice = "auto"
|
||||
@@ -113,6 +86,14 @@ 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")
|
||||
@@ -159,21 +140,6 @@ func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
|
||||
}
|
||||
|
||||
if c.TemporaryKeyPairName == "" {
|
||||
t.Fatal("keypair name is empty")
|
||||
}
|
||||
|
||||
// Match prefix and UUID, e.g. "packer_5790d491-a0b8-c84c-c9d2-2aea55086550".
|
||||
r := regexp.MustCompile(`\Apacker_(?:(?i)[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}?)\z`)
|
||||
if !r.MatchString(c.TemporaryKeyPairName) {
|
||||
t.Fatal("keypair name is not valid")
|
||||
}
|
||||
|
||||
c.TemporaryKeyPairName = "ssh-key-123"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.TemporaryKeyPairName != "ssh-key-123" {
|
||||
t.Fatal("keypair name does not match")
|
||||
t.Fatal("keypair empty")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,43 +3,26 @@ package common
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
packerssh "github.com/mitchellh/packer/communicator/ssh"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
type ec2Describer interface {
|
||||
DescribeInstances(*ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
|
||||
}
|
||||
|
||||
var (
|
||||
// modified in tests
|
||||
sshHostSleepDuration = time.Second
|
||||
)
|
||||
|
||||
// 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, private bool) 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 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 private && i.PrivateIpAddress != nil && *i.PrivateIpAddress != "" {
|
||||
host = *i.PrivateIpAddress
|
||||
} else if i.PublicDnsName != nil && *i.PublicDnsName != "" {
|
||||
host = *i.PublicDnsName
|
||||
}
|
||||
@@ -59,8 +42,8 @@ func SSHHost(e ec2Describer, private bool) func(multistep.StateBag) (string, err
|
||||
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 IP address for instance")
|
||||
@@ -68,51 +51,22 @@ func SSHHost(e ec2Describer, private bool) func(multistep.StateBag) (string, err
|
||||
}
|
||||
|
||||
// SSHConfig returns a function that can be used for the SSH communicator
|
||||
// config for connecting to the instance created over SSH using the private key
|
||||
// or password.
|
||||
func SSHConfig(useAgent bool, username, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
// 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) {
|
||||
if useAgent {
|
||||
authSock := os.Getenv("SSH_AUTH_SOCK")
|
||||
if authSock == "" {
|
||||
return nil, fmt.Errorf("SSH_AUTH_SOCK is not set")
|
||||
}
|
||||
privateKey := state.Get("privateKey").(string)
|
||||
|
||||
sshAgent, err := net.Dial("unix", authSock)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot connect to SSH Agent socket %q: %s", authSock, err)
|
||||
}
|
||||
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers),
|
||||
},
|
||||
}, nil
|
||||
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
|
||||
privateKey, hasKey := state.GetOk("privateKey")
|
||||
if hasKey {
|
||||
|
||||
signer, err := ssh.ParsePrivateKey([]byte(privateKey.(string)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
}, nil
|
||||
|
||||
} else {
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password(password),
|
||||
ssh.KeyboardInteractive(
|
||||
packerssh.PasswordKeyboardInteractive(password)),
|
||||
}}, nil
|
||||
}
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
const (
|
||||
privateIP = "10.0.0.1"
|
||||
publicIP = "192.168.1.1"
|
||||
publicDNS = "public.dns.test"
|
||||
)
|
||||
|
||||
func TestSSHHost(t *testing.T) {
|
||||
origSshHostSleepDuration := sshHostSleepDuration
|
||||
defer func() { sshHostSleepDuration = origSshHostSleepDuration }()
|
||||
sshHostSleepDuration = 0
|
||||
|
||||
var cases = []struct {
|
||||
allowTries int
|
||||
vpcId string
|
||||
private bool
|
||||
|
||||
ok bool
|
||||
wantHost string
|
||||
}{
|
||||
{1, "", false, true, publicDNS},
|
||||
{1, "", true, true, privateIP},
|
||||
{1, "vpc-id", false, true, publicIP},
|
||||
{1, "vpc-id", true, true, privateIP},
|
||||
{2, "", false, true, publicDNS},
|
||||
{2, "", true, true, privateIP},
|
||||
{2, "vpc-id", false, true, publicIP},
|
||||
{2, "vpc-id", true, true, privateIP},
|
||||
{3, "", false, false, ""},
|
||||
{3, "", true, false, ""},
|
||||
{3, "vpc-id", false, false, ""},
|
||||
{3, "vpc-id", true, false, ""},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
testSSHHost(t, c.allowTries, c.vpcId, c.private, c.ok, c.wantHost)
|
||||
}
|
||||
}
|
||||
|
||||
func testSSHHost(t *testing.T, allowTries int, vpcId string, private, ok bool, wantHost string) {
|
||||
t.Logf("allowTries=%d vpcId=%s private=%t ok=%t wantHost=%q", allowTries, vpcId, private, ok, wantHost)
|
||||
|
||||
e := &fakeEC2Describer{
|
||||
allowTries: allowTries,
|
||||
vpcId: vpcId,
|
||||
privateIP: privateIP,
|
||||
publicIP: publicIP,
|
||||
publicDNS: publicDNS,
|
||||
}
|
||||
|
||||
f := SSHHost(e, private)
|
||||
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, 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)
|
||||
}
|
||||
|
||||
out := &ec2.DescribeInstancesOutput{
|
||||
Reservations: []*ec2.Reservation{
|
||||
{
|
||||
Instances: []*ec2.Instance{instance},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
@@ -162,7 +162,7 @@ func ImportImageRefreshFunc(conn *ec2.EC2, importTaskId string) StateRefreshFunc
|
||||
func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
|
||||
log.Printf("Waiting for state to become: %s", conf.Target)
|
||||
|
||||
sleepSeconds := SleepSeconds()
|
||||
sleepSeconds := 2
|
||||
maxTicks := int(TimeoutSeconds()/sleepSeconds) + 1
|
||||
notfoundTick := 0
|
||||
|
||||
@@ -239,24 +239,3 @@ func TimeoutSeconds() (seconds int) {
|
||||
log.Printf("Allowing %ds to complete (change with AWS_TIMEOUT_SECONDS)", seconds)
|
||||
return seconds
|
||||
}
|
||||
|
||||
// Returns 2 seconds by default
|
||||
// AWS async operations sometimes takes long times, if there are multiple parallel builds,
|
||||
// polling at 2 second frequency will exceed the request limit. Allow 2 seconds to be
|
||||
// overwritten with AWS_POLL_DELAY_SECONDS
|
||||
func SleepSeconds() (seconds int) {
|
||||
seconds = 2
|
||||
|
||||
override := os.Getenv("AWS_POLL_DELAY_SECONDS")
|
||||
if override != "" {
|
||||
n, err := strconv.Atoi(override)
|
||||
if err != nil {
|
||||
log.Printf("Invalid sleep seconds '%s', using default", override)
|
||||
} else {
|
||||
seconds = n
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Using %ds as polling delay (change with AWS_POLL_DELAY_SECONDS)", seconds)
|
||||
return seconds
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ 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 {
|
||||
@@ -47,12 +46,11 @@ func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction {
|
||||
|
||||
go func(region string) {
|
||||
defer wg.Done()
|
||||
id, snapshotIds, err := amiRegionCopy(state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region)
|
||||
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)
|
||||
}
|
||||
@@ -79,23 +77,19 @@ 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.
|
||||
// returns the resulting ID or error.
|
||||
func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string, imageId string,
|
||||
target string, source string) (string, []string, error) {
|
||||
snapshotIds := []string{}
|
||||
target string, source string) (string, error) {
|
||||
|
||||
// Connect to the region where the AMI will be copied to
|
||||
awsConfig, err := config.Config()
|
||||
if err != nil {
|
||||
return "", snapshotIds, err
|
||||
return "", err
|
||||
}
|
||||
awsConfig.Region = aws.String(target)
|
||||
|
||||
session, err := session.NewSession(awsConfig)
|
||||
if err != nil {
|
||||
return "", snapshotIds, err
|
||||
}
|
||||
regionconn := ec2.New(session)
|
||||
sess := session.New(awsConfig)
|
||||
regionconn := ec2.New(sess)
|
||||
|
||||
resp, err := regionconn.CopyImage(&ec2.CopyImageInput{
|
||||
SourceRegion: &source,
|
||||
@@ -104,7 +98,7 @@ func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string,
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -116,22 +110,9 @@ func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string,
|
||||
}
|
||||
|
||||
if _, err := WaitForState(&stateChange); err != nil {
|
||||
return "", snapshotIds, fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s",
|
||||
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 {
|
||||
snapshotIds = append(snapshotIds, *blockDeviceMapping.Ebs.SnapshotId)
|
||||
}
|
||||
}
|
||||
|
||||
return *resp.ImageId, snapshotIds, nil
|
||||
return *resp.ImageId, nil
|
||||
}
|
||||
|
||||
@@ -2,20 +2,16 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"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"
|
||||
"github.com/mitchellh/multistep"
|
||||
retry "github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepCreateTags struct {
|
||||
Tags map[string]string
|
||||
SnapshotTags map[string]string
|
||||
Tags map[string]string
|
||||
}
|
||||
|
||||
func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
|
||||
@@ -23,103 +19,69 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
amis := state.Get("amis").(map[string]string)
|
||||
|
||||
if len(s.Tags) == 0 && len(s.SnapshotTags) == 0 {
|
||||
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))
|
||||
|
||||
// Declare list of resources to tag
|
||||
awsConfig := aws.Config{
|
||||
Credentials: ec2conn.Config.Credentials,
|
||||
Region: aws.String(region),
|
||||
}
|
||||
session, err := session.NewSession(&awsConfig)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating AWS session: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
regionconn := ec2.New(session)
|
||||
|
||||
// Retrieve image list for given AMI
|
||||
resourceIds := []*string{&ami}
|
||||
imageResp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
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 := ConvertToEC2Tags(s.Tags)
|
||||
ui.Say("Creating snapshot tags")
|
||||
snapshotTags := ConvertToEC2Tags(s.SnapshotTags)
|
||||
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
err = retry.Retry(0.2, 30, 11, func() (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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,15 +91,3 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepCreateTags) Cleanup(state multistep.StateBag) {
|
||||
// No cleanup...
|
||||
}
|
||||
|
||||
func ConvertToEC2Tags(tags map[string]string) []*ec2.Tag {
|
||||
var ec2tags []*ec2.Tag
|
||||
for key, value := range tags {
|
||||
log.Printf("[DEBUG] Creating tag %s=%s", key, value)
|
||||
ec2tags = append(ec2tags, &ec2.Tag{
|
||||
Key: aws.String(key),
|
||||
Value: aws.String(value),
|
||||
})
|
||||
}
|
||||
return ec2tags
|
||||
}
|
||||
|
||||
@@ -10,31 +10,30 @@ import (
|
||||
)
|
||||
|
||||
type StepDeregisterAMI struct {
|
||||
ForceDeregister bool
|
||||
ForceDeleteSnapshot bool
|
||||
AMIName string
|
||||
ForceDeregister bool
|
||||
AMIName string
|
||||
}
|
||||
|
||||
func (s *StepDeregisterAMI) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Check for force deregister
|
||||
// check for force deregister
|
||||
if s.ForceDeregister {
|
||||
resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
Filters: []*ec2.Filter{{
|
||||
Filters: []*ec2.Filter{&ec2.Filter{
|
||||
Name: aws.String("name"),
|
||||
Values: []*string{aws.String(s.AMIName)},
|
||||
}}})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error describing AMI: %s", err)
|
||||
err := fmt.Errorf("Error creating AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Deregister image(s) by name
|
||||
// deregister image(s) by that name
|
||||
for _, i := range resp.Images {
|
||||
_, err := ec2conn.DeregisterImage(&ec2.DeregisterImageInput{
|
||||
ImageId: i.ImageId,
|
||||
@@ -47,25 +46,6 @@ func (s *StepDeregisterAMI) Run(state multistep.StateBag) multistep.StepAction {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Deregistered AMI %s, id: %s", s.AMIName, *i.ImageId))
|
||||
|
||||
// Delete snapshot(s) by image
|
||||
if s.ForceDeleteSnapshot {
|
||||
for _, b := range i.BlockDeviceMappings {
|
||||
if b.Ebs != nil {
|
||||
_, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{
|
||||
SnapshotId: b.Ebs.SnapshotId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error deleting existing snapshot: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Deleted snapshot: %s", *b.Ebs.SnapshotId))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
type StepKeyPair struct {
|
||||
Debug bool
|
||||
SSHAgentAuth bool
|
||||
DebugKeyPath string
|
||||
TemporaryKeyPairName string
|
||||
KeyPairName string
|
||||
@@ -23,10 +22,7 @@ type StepKeyPair struct {
|
||||
}
|
||||
|
||||
func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if s.PrivateKeyFile != "" {
|
||||
ui.Say("Using existing SSH private key")
|
||||
privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf(
|
||||
@@ -40,24 +36,8 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.SSHAgentAuth && s.KeyPairName == "" {
|
||||
ui.Say("Using SSH Agent with key pair in Source AMI")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.SSHAgentAuth && s.KeyPairName != "" {
|
||||
ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.KeyPairName))
|
||||
state.Put("keyPair", s.KeyPairName)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.TemporaryKeyPairName == "" {
|
||||
ui.Say("Not using temporary keypair")
|
||||
state.Put("keyPair", "")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.TemporaryKeyPairName))
|
||||
keyResp, err := ec2conn.CreateKeyPair(&ec2.CreateKeyPairInput{
|
||||
@@ -107,7 +87,7 @@ func (s *StepKeyPair) 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.PrivateKeyFile != "" || s.KeyPairName != "" {
|
||||
if s.PrivateKeyFile != "" {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -11,19 +11,16 @@ import (
|
||||
)
|
||||
|
||||
type StepModifyAMIAttributes struct {
|
||||
Users []string
|
||||
Groups []string
|
||||
SnapshotUsers []string
|
||||
SnapshotGroups []string
|
||||
ProductCodes []string
|
||||
Description string
|
||||
Users []string
|
||||
Groups []string
|
||||
ProductCodes []string
|
||||
Description string
|
||||
}
|
||||
|
||||
func (s *StepModifyAMIAttributes) 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)
|
||||
|
||||
// Determine if there is any work to do.
|
||||
valid := false
|
||||
@@ -31,89 +28,51 @@ func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAc
|
||||
valid = valid || (s.Users != nil && len(s.Users) > 0)
|
||||
valid = valid || (s.Groups != nil && len(s.Groups) > 0)
|
||||
valid = valid || (s.ProductCodes != nil && len(s.ProductCodes) > 0)
|
||||
valid = valid || (s.SnapshotUsers != nil && len(s.SnapshotUsers) > 0)
|
||||
valid = valid || (s.SnapshotGroups != nil && len(s.SnapshotGroups) > 0)
|
||||
|
||||
if !valid {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Construct the modify image and snapshot attribute requests we're going
|
||||
// to make. We need to make each separately since the EC2 API only allows
|
||||
// changing one type at a kind currently.
|
||||
// Construct the modify image attribute requests we're going to make.
|
||||
// We need to make each separately since the EC2 API only allows changing
|
||||
// one type at a kind currently.
|
||||
options := make(map[string]*ec2.ModifyImageAttributeInput)
|
||||
if s.Description != "" {
|
||||
options["description"] = &ec2.ModifyImageAttributeInput{
|
||||
Description: &ec2.AttributeValue{Value: &s.Description},
|
||||
}
|
||||
}
|
||||
snapshotOptions := make(map[string]*ec2.ModifySnapshotAttributeInput)
|
||||
|
||||
if len(s.Groups) > 0 {
|
||||
groups := make([]*string, len(s.Groups))
|
||||
addsImage := make([]*ec2.LaunchPermission, len(s.Groups))
|
||||
adds := make([]*ec2.LaunchPermission, len(s.Groups))
|
||||
addGroups := &ec2.ModifyImageAttributeInput{
|
||||
LaunchPermission: &ec2.LaunchPermissionModifications{},
|
||||
}
|
||||
|
||||
for i, g := range s.Groups {
|
||||
groups[i] = aws.String(g)
|
||||
addsImage[i] = &ec2.LaunchPermission{
|
||||
adds[i] = &ec2.LaunchPermission{
|
||||
Group: aws.String(g),
|
||||
}
|
||||
}
|
||||
|
||||
addGroups.UserGroups = groups
|
||||
addGroups.LaunchPermission.Add = addsImage
|
||||
addGroups.LaunchPermission.Add = adds
|
||||
|
||||
options["groups"] = addGroups
|
||||
}
|
||||
|
||||
if len(s.SnapshotGroups) > 0 {
|
||||
groups := make([]*string, len(s.SnapshotGroups))
|
||||
addsSnapshot := make([]*ec2.CreateVolumePermission, len(s.SnapshotGroups))
|
||||
addSnapshotGroups := &ec2.ModifySnapshotAttributeInput{
|
||||
CreateVolumePermission: &ec2.CreateVolumePermissionModifications{},
|
||||
}
|
||||
|
||||
for i, g := range s.SnapshotGroups {
|
||||
groups[i] = aws.String(g)
|
||||
addsSnapshot[i] = &ec2.CreateVolumePermission{
|
||||
Group: aws.String(g),
|
||||
}
|
||||
}
|
||||
addSnapshotGroups.GroupNames = groups
|
||||
addSnapshotGroups.CreateVolumePermission.Add = addsSnapshot
|
||||
snapshotOptions["groups"] = addSnapshotGroups
|
||||
}
|
||||
|
||||
if len(s.Users) > 0 {
|
||||
users := make([]*string, len(s.Users))
|
||||
addsImage := make([]*ec2.LaunchPermission, len(s.Users))
|
||||
adds := make([]*ec2.LaunchPermission, len(s.Users))
|
||||
for i, u := range s.Users {
|
||||
users[i] = aws.String(u)
|
||||
addsImage[i] = &ec2.LaunchPermission{UserId: aws.String(u)}
|
||||
adds[i] = &ec2.LaunchPermission{UserId: aws.String(u)}
|
||||
}
|
||||
|
||||
options["users"] = &ec2.ModifyImageAttributeInput{
|
||||
UserIds: users,
|
||||
LaunchPermission: &ec2.LaunchPermissionModifications{
|
||||
Add: addsImage,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.SnapshotUsers) > 0 {
|
||||
users := make([]*string, len(s.SnapshotUsers))
|
||||
addsSnapshot := make([]*ec2.CreateVolumePermission, len(s.SnapshotUsers))
|
||||
for i, u := range s.SnapshotUsers {
|
||||
users[i] = aws.String(u)
|
||||
addsSnapshot[i] = &ec2.CreateVolumePermission{UserId: aws.String(u)}
|
||||
}
|
||||
|
||||
snapshotOptions["users"] = &ec2.ModifySnapshotAttributeInput{
|
||||
UserIds: users,
|
||||
CreateVolumePermission: &ec2.CreateVolumePermissionModifications{
|
||||
Add: addsSnapshot,
|
||||
Add: adds,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -128,20 +87,13 @@ func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAc
|
||||
}
|
||||
}
|
||||
|
||||
// Modifying image attributes
|
||||
for region, ami := range amis {
|
||||
ui.Say(fmt.Sprintf("Modifying attributes on AMI (%s)...", ami))
|
||||
awsConfig := aws.Config{
|
||||
Credentials: ec2conn.Config.Credentials,
|
||||
Region: aws.String(region),
|
||||
}
|
||||
session, err := session.NewSession(&awsConfig)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating AWS session: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
session := session.New(&awsConfig)
|
||||
regionconn := ec2.New(session)
|
||||
for name, input := range options {
|
||||
ui.Message(fmt.Sprintf("Modifying: %s", name))
|
||||
@@ -156,30 +108,6 @@ func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAc
|
||||
}
|
||||
}
|
||||
|
||||
// Modifying snapshot attributes
|
||||
for region, region_snapshots := range snapshots {
|
||||
for _, snapshot := range region_snapshots {
|
||||
ui.Say(fmt.Sprintf("Modifying attributes on snapshot (%s)...", snapshot))
|
||||
awsConfig := aws.Config{
|
||||
Credentials: ec2conn.Config.Credentials,
|
||||
Region: aws.String(region),
|
||||
}
|
||||
session := session.New(&awsConfig)
|
||||
regionconn := ec2.New(session)
|
||||
for name, input := range snapshotOptions {
|
||||
ui.Message(fmt.Sprintf("Modifying: %s", name))
|
||||
input.SnapshotId = &snapshot
|
||||
_, err := regionconn.ModifySnapshotAttribute(input)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error modify snapshot attributes: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ func (s *StepPreValidate) Run(state multistep.StateBag) multistep.StepAction {
|
||||
|
||||
ui.Say("Prevalidating AMI Name...")
|
||||
resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
Filters: []*ec2.Filter{{
|
||||
Filters: []*ec2.Filter{&ec2.Filter{
|
||||
Name: aws.String("name"),
|
||||
Values: []*string{aws.String(s.DestAmiName)},
|
||||
}}})
|
||||
|
||||
@@ -16,22 +16,21 @@ import (
|
||||
)
|
||||
|
||||
type StepRunSourceInstance struct {
|
||||
AssociatePublicIpAddress bool
|
||||
AvailabilityZone string
|
||||
BlockDevices BlockDevices
|
||||
Debug bool
|
||||
EbsOptimized bool
|
||||
ExpectedRootDevice string
|
||||
IamInstanceProfile string
|
||||
InstanceInitiatedShutdownBehavior string
|
||||
InstanceType string
|
||||
SourceAMI string
|
||||
SpotPrice string
|
||||
SpotPriceProduct string
|
||||
SubnetId string
|
||||
Tags map[string]string
|
||||
UserData string
|
||||
UserDataFile string
|
||||
AssociatePublicIpAddress bool
|
||||
AvailabilityZone string
|
||||
BlockDevices BlockDevices
|
||||
Debug bool
|
||||
EbsOptimized bool
|
||||
ExpectedRootDevice string
|
||||
InstanceType string
|
||||
IamInstanceProfile string
|
||||
SourceAMI string
|
||||
SpotPrice string
|
||||
SpotPriceProduct string
|
||||
SubnetId string
|
||||
Tags map[string]string
|
||||
UserData string
|
||||
UserDataFile string
|
||||
|
||||
instanceId string
|
||||
spotRequest *ec2.SpotInstanceRequest
|
||||
@@ -40,9 +39,14 @@ type StepRunSourceInstance struct {
|
||||
func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
keyName := state.Get("keyPair").(string)
|
||||
securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string))
|
||||
tempSecurityGroupIds := state.Get("securityGroupIds").([]string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
securityGroupIds := make([]*string, len(tempSecurityGroupIds))
|
||||
for i, sg := range tempSecurityGroupIds {
|
||||
securityGroupIds[i] = aws.String(sg)
|
||||
}
|
||||
|
||||
userData := s.UserData
|
||||
if s.UserDataFile != "" {
|
||||
contents, err := ioutil.ReadFile(s.UserDataFile)
|
||||
@@ -61,18 +65,24 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
|
||||
}
|
||||
|
||||
ui.Say("Launching a source AWS instance...")
|
||||
image, ok := state.Get("source_image").(*ec2.Image)
|
||||
if !ok {
|
||||
state.Put("error", fmt.Errorf("source_image type assertion failed"))
|
||||
imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{&s.SourceAMI},
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("There was a problem with the source AMI: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.SourceAMI = *image.ImageId
|
||||
|
||||
if s.ExpectedRootDevice != "" && *image.RootDeviceType != s.ExpectedRootDevice {
|
||||
if len(imageResp.Images) != 1 {
|
||||
state.Put("error", fmt.Errorf("The source AMI '%s' could not be found.", s.SourceAMI))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if s.ExpectedRootDevice != "" && *imageResp.Images[0].RootDeviceType != s.ExpectedRootDevice {
|
||||
state.Put("error", fmt.Errorf(
|
||||
"The provided source AMI has an invalid root device type.\n"+
|
||||
"Expected '%s', got '%s'.",
|
||||
s.ExpectedRootDevice, *image.RootDeviceType))
|
||||
s.ExpectedRootDevice, *imageResp.Images[0].RootDeviceType))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
@@ -118,10 +128,6 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
} else {
|
||||
// Add 0.5 cents to minimum spot bid to ensure capacity will be available
|
||||
// Avoids price-too-low error in active markets which can fluctuate
|
||||
price = price + 0.005
|
||||
}
|
||||
|
||||
spotPrice = strconv.FormatFloat(price, 'f', -1, 64)
|
||||
@@ -131,6 +137,7 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
|
||||
|
||||
if spotPrice == "" || spotPrice == "0" {
|
||||
runOpts := &ec2.RunInstancesInput{
|
||||
KeyName: &keyName,
|
||||
ImageId: &s.SourceAMI,
|
||||
InstanceType: &s.InstanceType,
|
||||
UserData: &userData,
|
||||
@@ -142,13 +149,9 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
|
||||
EbsOptimized: &s.EbsOptimized,
|
||||
}
|
||||
|
||||
if keyName != "" {
|
||||
runOpts.KeyName = &keyName
|
||||
}
|
||||
|
||||
if s.SubnetId != "" && s.AssociatePublicIpAddress {
|
||||
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
|
||||
{
|
||||
&ec2.InstanceNetworkInterfaceSpecification{
|
||||
DeviceIndex: aws.Int64(0),
|
||||
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
|
||||
SubnetId: &s.SubnetId,
|
||||
@@ -161,10 +164,6 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
|
||||
runOpts.SecurityGroupIds = securityGroupIds
|
||||
}
|
||||
|
||||
if s.ExpectedRootDevice == "ebs" {
|
||||
runOpts.InstanceInitiatedShutdownBehavior = &s.InstanceInitiatedShutdownBehavior
|
||||
}
|
||||
|
||||
runResp, err := ec2conn.RunInstances(runOpts)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error launching source instance: %s", err)
|
||||
@@ -177,41 +176,29 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
|
||||
ui.Message(fmt.Sprintf(
|
||||
"Requesting spot instance '%s' for: %s",
|
||||
s.InstanceType, spotPrice))
|
||||
|
||||
runOpts := &ec2.RequestSpotLaunchSpecification{
|
||||
ImageId: &s.SourceAMI,
|
||||
InstanceType: &s.InstanceType,
|
||||
UserData: &userData,
|
||||
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
|
||||
Placement: &ec2.SpotPlacement{
|
||||
AvailabilityZone: &availabilityZone,
|
||||
},
|
||||
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
|
||||
EbsOptimized: &s.EbsOptimized,
|
||||
}
|
||||
|
||||
if s.SubnetId != "" && s.AssociatePublicIpAddress {
|
||||
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
|
||||
{
|
||||
DeviceIndex: aws.Int64(0),
|
||||
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
|
||||
SubnetId: &s.SubnetId,
|
||||
Groups: securityGroupIds,
|
||||
DeleteOnTermination: aws.Bool(true),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
runOpts.SubnetId = &s.SubnetId
|
||||
runOpts.SecurityGroupIds = securityGroupIds
|
||||
}
|
||||
|
||||
if keyName != "" {
|
||||
runOpts.KeyName = &keyName
|
||||
}
|
||||
|
||||
runSpotResp, err := ec2conn.RequestSpotInstances(&ec2.RequestSpotInstancesInput{
|
||||
SpotPrice: &spotPrice,
|
||||
LaunchSpecification: runOpts,
|
||||
SpotPrice: &spotPrice,
|
||||
LaunchSpecification: &ec2.RequestSpotLaunchSpecification{
|
||||
KeyName: &keyName,
|
||||
ImageId: &s.SourceAMI,
|
||||
InstanceType: &s.InstanceType,
|
||||
UserData: &userData,
|
||||
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
|
||||
NetworkInterfaces: []*ec2.InstanceNetworkInterfaceSpecification{
|
||||
&ec2.InstanceNetworkInterfaceSpecification{
|
||||
DeviceIndex: aws.Int64(0),
|
||||
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
|
||||
SubnetId: &s.SubnetId,
|
||||
Groups: securityGroupIds,
|
||||
DeleteOnTermination: aws.Bool(true),
|
||||
},
|
||||
},
|
||||
Placement: &ec2.SpotPlacement{
|
||||
AvailabilityZone: &availabilityZone,
|
||||
},
|
||||
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
|
||||
EbsOptimized: &s.EbsOptimized,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error launching source spot instance: %s", err)
|
||||
@@ -271,21 +258,19 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
|
||||
|
||||
instance := latestInstance.(*ec2.Instance)
|
||||
|
||||
ui.Say("Adding tags to source instance")
|
||||
if _, exists := s.Tags["Name"]; !exists {
|
||||
s.Tags["Name"] = "Packer Builder"
|
||||
ec2Tags := make([]*ec2.Tag, 1, len(s.Tags)+1)
|
||||
ec2Tags[0] = &ec2.Tag{Key: aws.String("Name"), Value: aws.String("Packer Builder")}
|
||||
for k, v := range s.Tags {
|
||||
ec2Tags = append(ec2Tags, &ec2.Tag{Key: aws.String(k), Value: aws.String(v)})
|
||||
}
|
||||
ec2Tags := ConvertToEC2Tags(s.Tags)
|
||||
|
||||
_, err = ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||
Tags: ec2Tags,
|
||||
Resources: []*string{instance.InstanceId},
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error tagging source instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
ui.Message(
|
||||
fmt.Sprintf("Failed to tag a Name on the builder instance: %s", err))
|
||||
}
|
||||
|
||||
if s.Debug {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/private/waiter"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common/uuid"
|
||||
@@ -27,17 +26,6 @@ func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if len(s.SecurityGroupIds) > 0 {
|
||||
_, err := ec2conn.DescribeSecurityGroups(
|
||||
&ec2.DescribeSecurityGroupsInput{
|
||||
GroupIds: aws.StringSlice(s.SecurityGroupIds),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Couldn't find specified security group: %s", err)
|
||||
log.Printf("[DEBUG] %s", err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
log.Printf("Using specified security groups: %v", s.SecurityGroupIds)
|
||||
state.Put("securityGroupIds", s.SecurityGroupIds)
|
||||
return multistep.ActionContinue
|
||||
@@ -99,21 +87,6 @@ func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Waiting for temporary security group: %s", s.createdGroupId)
|
||||
err = waitUntilSecurityGroupExists(ec2conn,
|
||||
&ec2.DescribeSecurityGroupsInput{
|
||||
GroupIds: []*string{aws.String(s.createdGroupId)},
|
||||
},
|
||||
)
|
||||
if err == nil {
|
||||
log.Printf("[DEBUG] Found security group %s", s.createdGroupId)
|
||||
} else {
|
||||
err := fmt.Errorf("Timed out waiting for security group %s: %s", s.createdGroupId, err)
|
||||
log.Printf("[DEBUG] %s", err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set some state data for use in future steps
|
||||
state.Put("securityGroupIds", []string{s.createdGroupId})
|
||||
|
||||
@@ -146,38 +119,3 @@ func (s *StepSecurityGroup) Cleanup(state multistep.StateBag) {
|
||||
"Error cleaning up security group. Please delete the group manually: %s", s.createdGroupId))
|
||||
}
|
||||
}
|
||||
|
||||
func waitUntilSecurityGroupExists(c *ec2.EC2, input *ec2.DescribeSecurityGroupsInput) error {
|
||||
waiterCfg := waiter.Config{
|
||||
Operation: "DescribeSecurityGroups",
|
||||
Delay: 15,
|
||||
MaxAttempts: 40,
|
||||
Acceptors: []waiter.WaitAcceptor{
|
||||
{
|
||||
State: "success",
|
||||
Matcher: "path",
|
||||
Argument: "length(SecurityGroups[]) > `0`",
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
State: "retry",
|
||||
Matcher: "error",
|
||||
Argument: "",
|
||||
Expected: "InvalidGroup.NotFound",
|
||||
},
|
||||
{
|
||||
State: "retry",
|
||||
Matcher: "error",
|
||||
Argument: "",
|
||||
Expected: "InvalidSecurityGroupID.NotFound",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := waiter.Waiter{
|
||||
Client: c,
|
||||
Input: input,
|
||||
Config: waiterCfg,
|
||||
}
|
||||
return w.Wait()
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
@@ -19,58 +16,14 @@ import (
|
||||
type StepSourceAMIInfo struct {
|
||||
SourceAmi string
|
||||
EnhancedNetworking bool
|
||||
AmiFilters AmiFilterOptions
|
||||
}
|
||||
|
||||
// Build a slice of AMI filter options from the filters provided.
|
||||
func buildAmiFilters(input map[*string]*string) []*ec2.Filter {
|
||||
var filters []*ec2.Filter
|
||||
for k, v := range input {
|
||||
filters = append(filters, &ec2.Filter{
|
||||
Name: k,
|
||||
Values: []*string{v},
|
||||
})
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
type imageSort []*ec2.Image
|
||||
|
||||
func (a imageSort) Len() int { return len(a) }
|
||||
func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a imageSort) Less(i, j int) bool {
|
||||
itime, _ := time.Parse(time.RFC3339, *a[i].CreationDate)
|
||||
jtime, _ := time.Parse(time.RFC3339, *a[j].CreationDate)
|
||||
return itime.Unix() < jtime.Unix()
|
||||
}
|
||||
|
||||
// Returns the most recent AMI out of a slice of images.
|
||||
func mostRecentAmi(images []*ec2.Image) *ec2.Image {
|
||||
sortedImages := images
|
||||
sort.Sort(imageSort(sortedImages))
|
||||
return sortedImages[len(sortedImages)-1]
|
||||
}
|
||||
|
||||
func (s *StepSourceAMIInfo) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
params := &ec2.DescribeImagesInput{}
|
||||
|
||||
if s.SourceAmi != "" {
|
||||
params.ImageIds = []*string{&s.SourceAmi}
|
||||
}
|
||||
|
||||
// We have filters to apply
|
||||
if len(s.AmiFilters.Filters) > 0 {
|
||||
params.Filters = buildAmiFilters(s.AmiFilters.Filters)
|
||||
}
|
||||
if len(s.AmiFilters.Owners) > 0 {
|
||||
params.Owners = s.AmiFilters.Owners
|
||||
}
|
||||
|
||||
log.Printf("Using AMI Filters %v", params)
|
||||
imageResp, err := ec2conn.DescribeImages(params)
|
||||
ui.Say("Inspecting the source AMI...")
|
||||
imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{&s.SourceAmi}})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -79,27 +32,13 @@ func (s *StepSourceAMIInfo) Run(state multistep.StateBag) multistep.StepAction {
|
||||
}
|
||||
|
||||
if len(imageResp.Images) == 0 {
|
||||
err := fmt.Errorf("No AMI was found matching filters: %v", params)
|
||||
err := fmt.Errorf("Source AMI '%s' was not found!", s.SourceAmi)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(imageResp.Images) > 1 && s.AmiFilters.MostRecent == false {
|
||||
err := fmt.Errorf("Your query returned more than one result. Please try a more specific search, or set most_recent to true.")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var image *ec2.Image
|
||||
if s.AmiFilters.MostRecent {
|
||||
image = mostRecentAmi(imageResp.Images)
|
||||
} else {
|
||||
image = imageResp.Images[0]
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Found Image ID: %s", *image.ImageId))
|
||||
image := imageResp.Images[0]
|
||||
|
||||
// Enhanced Networking (SriovNetSupport) can only be enabled on HVM AMIs.
|
||||
// See http://goo.gl/icuXh5
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepStopEBSBackedInstance struct {
|
||||
SpotPrice string
|
||||
DisableStopInstance bool
|
||||
}
|
||||
|
||||
func (s *StepStopEBSBackedInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Skip when it is a spot instance
|
||||
if s.SpotPrice != "" && s.SpotPrice != "0" {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if !s.DisableStopInstance {
|
||||
// Stop the instance so we can create an AMI from it
|
||||
ui.Say("Stopping the source instance...")
|
||||
_, err = ec2conn.StopInstances(&ec2.StopInstancesInput{
|
||||
InstanceIds: []*string{instance.InstanceId},
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error stopping instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
} else {
|
||||
ui.Say("Automatic instance stop disabled. Please stop instance manually.")
|
||||
}
|
||||
|
||||
// Wait for the instance to actual stop
|
||||
ui.Say("Waiting for the instance to stop...")
|
||||
stateChange := StateChangeConf{
|
||||
Pending: []string{"running", "stopping"},
|
||||
Target: "stopped",
|
||||
Refresh: InstanceStateRefreshFunc(ec2conn, *instance.InstanceId),
|
||||
StepState: state,
|
||||
}
|
||||
_, err = WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for instance to stop: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepStopEBSBackedInstance) Cleanup(multistep.StateBag) {
|
||||
// No cleanup...
|
||||
}
|
||||
@@ -70,10 +70,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := session.NewSession(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
session := session.New(config)
|
||||
ec2conn := ec2.New(session)
|
||||
|
||||
// If the subnet is specified but not the AZ, try to determine the AZ automatically
|
||||
@@ -103,11 +100,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
&awscommon.StepSourceAMIInfo{
|
||||
SourceAmi: b.config.SourceAmi,
|
||||
EnhancedNetworking: b.config.AMIEnhancedNetworking,
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
},
|
||||
&awscommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
SSHAgentAuth: b.config.Comm.SSHAgentAuth,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
KeyPairName: b.config.SSHKeyPairName,
|
||||
TemporaryKeyPairName: b.config.TemporaryKeyPairName,
|
||||
@@ -137,7 +132,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
Tags: b.config.RunTags,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
},
|
||||
&stepTagEBSVolumes{
|
||||
VolumeRunTags: b.config.VolumeRunTags,
|
||||
@@ -153,46 +147,43 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
ec2conn,
|
||||
b.config.SSHPrivateIp),
|
||||
SSHConfig: awscommon.SSHConfig(
|
||||
b.config.RunConfig.Comm.SSHAgentAuth,
|
||||
b.config.RunConfig.Comm.SSHUsername,
|
||||
b.config.RunConfig.Comm.SSHPassword),
|
||||
b.config.RunConfig.Comm.SSHUsername),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&awscommon.StepStopEBSBackedInstance{
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
DisableStopInstance: b.config.DisableStopInstance,
|
||||
},
|
||||
&awscommon.StepModifyEBSBackedInstance{
|
||||
EnableEnhancedNetworking: b.config.AMIEnhancedNetworking,
|
||||
},
|
||||
&stepStopInstance{SpotPrice: b.config.SpotPrice},
|
||||
// TODO(mitchellh): verify works with spots
|
||||
&stepModifyInstance{},
|
||||
&awscommon.StepDeregisterAMI{
|
||||
ForceDeregister: b.config.AMIForceDeregister,
|
||||
ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot,
|
||||
AMIName: b.config.AMIName,
|
||||
ForceDeregister: b.config.AMIForceDeregister,
|
||||
AMIName: b.config.AMIName,
|
||||
},
|
||||
&stepCreateAMI{},
|
||||
&stepCreateEncryptedAMICopy{},
|
||||
&awscommon.StepAMIRegionCopy{
|
||||
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,
|
||||
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,
|
||||
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
|
||||
|
||||
@@ -46,60 +46,6 @@ func TestBuilderAcc_forceDeregister(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) {
|
||||
amiName := "packer-test-dereg"
|
||||
|
||||
// Build the same AMI name twice, with force_delete_snapshot on the second run
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: buildForceDeleteSnapshotConfig("false", amiName),
|
||||
SkipArtifactTeardown: true,
|
||||
})
|
||||
|
||||
// Get image data by AMI name
|
||||
ec2conn, _ := testEC2Conn()
|
||||
imageResp, _ := ec2conn.DescribeImages(
|
||||
&ec2.DescribeImagesInput{Filters: []*ec2.Filter{
|
||||
{
|
||||
Name: aws.String("name"),
|
||||
Values: []*string{aws.String(amiName)},
|
||||
},
|
||||
}},
|
||||
)
|
||||
image := imageResp.Images[0]
|
||||
|
||||
// Get snapshot ids for image
|
||||
snapshotIds := []*string{}
|
||||
for _, device := range image.BlockDeviceMappings {
|
||||
if device.Ebs != nil && device.Ebs.SnapshotId != nil {
|
||||
snapshotIds = append(snapshotIds, device.Ebs.SnapshotId)
|
||||
}
|
||||
}
|
||||
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: buildForceDeleteSnapshotConfig("true", amiName),
|
||||
Check: checkSnapshotsDeleted(snapshotIds),
|
||||
})
|
||||
}
|
||||
|
||||
func checkSnapshotsDeleted(snapshotIds []*string) builderT.TestCheckFunc {
|
||||
return func(artifacts []packer.Artifact) error {
|
||||
// Verify the snapshots are gone
|
||||
ec2conn, _ := testEC2Conn()
|
||||
snapshotResp, _ := ec2conn.DescribeSnapshots(
|
||||
&ec2.DescribeSnapshotsInput{SnapshotIds: snapshotIds},
|
||||
)
|
||||
|
||||
if len(snapshotResp.Snapshots) > 0 {
|
||||
return fmt.Errorf("Snapshots weren't successfully deleted by `force_delete_snapshot`")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderAcc_amiSharing(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
@@ -109,15 +55,6 @@ func TestBuilderAcc_amiSharing(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_encryptedBoot(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccEncrypted,
|
||||
Check: checkBootEncrypted(),
|
||||
})
|
||||
}
|
||||
|
||||
func checkAMISharing(count int, uid, group string) builderT.TestCheckFunc {
|
||||
return func(artifacts []packer.Artifact) error {
|
||||
if len(artifacts) > 1 {
|
||||
@@ -192,7 +129,7 @@ func checkRegionCopy(regions []string) builderT.TestCheckFunc {
|
||||
for _, r := range regions {
|
||||
regionSet[r] = struct{}{}
|
||||
}
|
||||
for r := range artifact.Amis {
|
||||
for r, _ := range artifact.Amis {
|
||||
if _, ok := regionSet[r]; !ok {
|
||||
return fmt.Errorf("unknown region: %s", r)
|
||||
}
|
||||
@@ -207,42 +144,6 @@ func checkRegionCopy(regions []string) builderT.TestCheckFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func checkBootEncrypted() builderT.TestCheckFunc {
|
||||
return func(artifacts []packer.Artifact) error {
|
||||
|
||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
||||
artifactRaw := artifacts[0]
|
||||
artifact, ok := artifactRaw.(*common.Artifact)
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
||||
}
|
||||
|
||||
// describe the image, get block devices with a snapshot
|
||||
ec2conn, _ := testEC2Conn()
|
||||
imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{aws.String(artifact.Amis["us-east-1"])},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error retrieving Image Attributes for AMI (%s) in AMI Encrypted Boot Test: %s", artifact, err)
|
||||
}
|
||||
|
||||
image := imageResp.Images[0] // Only requested a single AMI ID
|
||||
|
||||
rootDeviceName := image.RootDeviceName
|
||||
|
||||
for _, bd := range image.BlockDeviceMappings {
|
||||
if *bd.DeviceName == *rootDeviceName {
|
||||
if *bd.Ebs.Encrypted != true {
|
||||
return fmt.Errorf("volume not encrypted: %s", *bd.Ebs.SnapshotId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
if v := os.Getenv("AWS_ACCESS_KEY_ID"); v == "" {
|
||||
t.Fatal("AWS_ACCESS_KEY_ID must be set for acceptance tests")
|
||||
@@ -260,10 +161,7 @@ func testEC2Conn() (*ec2.EC2, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := session.NewSession(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
session := session.New(config)
|
||||
return ec2.New(session), nil
|
||||
}
|
||||
|
||||
@@ -308,21 +206,6 @@ const testBuilderAccForceDeregister = `
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccForceDeleteSnapshot = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
"region": "us-east-1",
|
||||
"instance_type": "m3.medium",
|
||||
"source_ami": "ami-76b2a71e",
|
||||
"ssh_username": "ubuntu",
|
||||
"force_deregister": "%s",
|
||||
"force_delete_snapshot": "%s",
|
||||
"ami_name": "packer-test-%s"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
// share with catsby
|
||||
const testBuilderAccSharing = `
|
||||
{
|
||||
@@ -339,24 +222,6 @@ const testBuilderAccSharing = `
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccEncrypted = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
"region": "us-east-1",
|
||||
"instance_type": "m3.medium",
|
||||
"source_ami":"ami-c15bebaa",
|
||||
"ssh_username": "ubuntu",
|
||||
"ami_name": "packer-enc-test {{timestamp}}",
|
||||
"encrypt_boot": true
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
func buildForceDeregisterConfig(val, name string) string {
|
||||
return fmt.Sprintf(testBuilderAccForceDeregister, val, name)
|
||||
}
|
||||
|
||||
func buildForceDeleteSnapshotConfig(val, name string) string {
|
||||
return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name)
|
||||
func buildForceDeregisterConfig(name, flag string) string {
|
||||
return fmt.Sprintf(testBuilderAccForceDeregister, name, flag)
|
||||
}
|
||||
|
||||
@@ -92,38 +92,3 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["shutdown_behavior"] = "terminate"
|
||||
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 good
|
||||
config["shutdown_behavior"] = "stop"
|
||||
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["shutdown_behavior"] = "foobar"
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,12 @@ func (s *stepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
||||
instance = instanceRaw.(*ec2.Instance)
|
||||
}
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
amisRaw := state.Get("amis")
|
||||
if amisRaw == nil {
|
||||
ui.Say("No AMIs to cleanup")
|
||||
return
|
||||
}
|
||||
|
||||
if instance == nil {
|
||||
ui.Say("No volumes to clean up, skipping")
|
||||
return
|
||||
@@ -66,7 +72,7 @@ func (s *stepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
||||
// date information on them
|
||||
resp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesInput{
|
||||
Filters: []*ec2.Filter{
|
||||
{
|
||||
&ec2.Filter{
|
||||
Name: aws.String("volume-id"),
|
||||
Values: vl,
|
||||
},
|
||||
@@ -92,7 +98,7 @@ func (s *stepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
||||
}
|
||||
|
||||
// Filter out any devices marked for saving
|
||||
for saveName := range save {
|
||||
for saveName, _ := range save {
|
||||
for volKey, volName := range volList {
|
||||
if volName == saveName {
|
||||
delete(volList, volKey)
|
||||
@@ -101,11 +107,11 @@ func (s *stepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
||||
}
|
||||
|
||||
// Destroy remaining volumes
|
||||
for k := range volList {
|
||||
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))
|
||||
ui.Say(fmt.Sprintf("Error deleting volume: %s", k))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -66,15 +66,6 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction {
|
||||
}
|
||||
s.image = imagesResp.Images[0]
|
||||
|
||||
snapshots := make(map[string][]string)
|
||||
for _, blockDeviceMapping := range imagesResp.Images[0].BlockDeviceMappings {
|
||||
if blockDeviceMapping.Ebs != nil && blockDeviceMapping.Ebs.SnapshotId != nil {
|
||||
|
||||
snapshots[*ec2conn.Config.Region] = append(snapshots[*ec2conn.Config.Region], *blockDeviceMapping.Ebs.SnapshotId)
|
||||
}
|
||||
}
|
||||
state.Put("snapshots", snapshots)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
package ebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type stepCreateEncryptedAMICopy struct {
|
||||
image *ec2.Image
|
||||
}
|
||||
|
||||
func (s *stepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(Config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
kmsKeyId := config.AMIConfig.AMIKmsKeyId
|
||||
|
||||
// Encrypt boot not set, so skip step
|
||||
if !config.AMIConfig.AMIEncryptBootVolume {
|
||||
if kmsKeyId != "" {
|
||||
log.Printf(fmt.Sprintf("Ignoring KMS Key ID: %s, encrypted=false", kmsKeyId))
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say("Creating Encrypted AMI Copy")
|
||||
|
||||
amis := state.Get("amis").(map[string]string)
|
||||
var region, id string
|
||||
if amis != nil {
|
||||
for region, id = range amis {
|
||||
break // Only get the first
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Copying AMI: %s(%s)", region, id))
|
||||
|
||||
if kmsKeyId != "" {
|
||||
ui.Say(fmt.Sprintf("Encypting with KMS Key ID: %s", kmsKeyId))
|
||||
}
|
||||
|
||||
copyOpts := &ec2.CopyImageInput{
|
||||
Name: &config.AMIName, // Try to overwrite existing AMI
|
||||
SourceImageId: aws.String(id),
|
||||
SourceRegion: aws.String(region),
|
||||
Encrypted: aws.Bool(true),
|
||||
KmsKeyId: aws.String(kmsKeyId),
|
||||
}
|
||||
|
||||
copyResp, err := ec2conn.CopyImage(copyOpts)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error copying AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Wait for the copy to become ready
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
Target: "available",
|
||||
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *copyResp.ImageId),
|
||||
StepState: state,
|
||||
}
|
||||
|
||||
ui.Say("Waiting for AMI copy to become ready...")
|
||||
if _, err := awscommon.WaitForState(&stateChange); err != nil {
|
||||
err := fmt.Errorf("Error waiting for AMI Copy: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Get the encrypted AMI image, we need the new snapshot id's
|
||||
encImagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{aws.String(*copyResp.ImageId)}})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error searching for AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
encImage := encImagesResp.Images[0]
|
||||
var encSnapshots []string
|
||||
for _, blockDevice := range encImage.BlockDeviceMappings {
|
||||
if blockDevice.Ebs != nil && blockDevice.Ebs.SnapshotId != nil {
|
||||
encSnapshots = append(encSnapshots, *blockDevice.Ebs.SnapshotId)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the unencrypted AMI image
|
||||
unencImagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{aws.String(id)}})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error searching for AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
unencImage := unencImagesResp.Images[0]
|
||||
|
||||
// Remove unencrypted AMI
|
||||
ui.Say("Deregistering unencrypted AMI")
|
||||
deregisterOpts := &ec2.DeregisterImageInput{ImageId: aws.String(id)}
|
||||
if _, err := ec2conn.DeregisterImage(deregisterOpts); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deregistering AMI, may still be around: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Remove associated unencrypted snapshot(s)
|
||||
ui.Say("Deleting unencrypted snapshots")
|
||||
snapshots := state.Get("snapshots").(map[string][]string)
|
||||
|
||||
for _, blockDevice := range unencImage.BlockDeviceMappings {
|
||||
if blockDevice.Ebs != nil && blockDevice.Ebs.SnapshotId != nil {
|
||||
ui.Message(fmt.Sprintf("Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
|
||||
deleteSnapOpts := &ec2.DeleteSnapshotInput{
|
||||
SnapshotId: aws.String(*blockDevice.Ebs.SnapshotId),
|
||||
}
|
||||
if _, err := ec2conn.DeleteSnapshot(deleteSnapOpts); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting snapshot, may still be around: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace original AMI ID with Encrypted ID in state
|
||||
amis[region] = *copyResp.ImageId
|
||||
snapshots[region] = encSnapshots
|
||||
state.Put("amis", amis)
|
||||
state.Put("snapshots", snapshots)
|
||||
|
||||
imagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{copyResp.ImageId}})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error searching for AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.image = imagesResp.Images[0]
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateEncryptedAMICopy) Cleanup(state multistep.StateBag) {
|
||||
if s.image == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if !cancelled && !halted {
|
||||
return
|
||||
}
|
||||
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Deregistering the AMI because cancelation or error...")
|
||||
deregisterOpts := &ec2.DeregisterImageInput{ImageId: s.image.ImageId}
|
||||
if _, err := ec2conn.DeregisterImage(deregisterOpts); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deregistering AMI, may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
+6
-7
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package ebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -8,17 +8,16 @@ import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepModifyEBSBackedInstance struct {
|
||||
EnableEnhancedNetworking bool
|
||||
}
|
||||
type stepModifyInstance struct{}
|
||||
|
||||
func (s *StepModifyEBSBackedInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
func (s *stepModifyInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(Config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
|
||||
if s.EnableEnhancedNetworking {
|
||||
if config.AMIEnhancedNetworking {
|
||||
ui.Say("Enabling Enhanced Networking...")
|
||||
simple := "simple"
|
||||
_, err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
|
||||
@@ -36,6 +35,6 @@ func (s *StepModifyEBSBackedInstance) Run(state multistep.StateBag) multistep.St
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepModifyEBSBackedInstance) Cleanup(state multistep.StateBag) {
|
||||
func (s *stepModifyInstance) Cleanup(state multistep.StateBag) {
|
||||
// No cleanup...
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package ebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type stepStopInstance struct {
|
||||
SpotPrice string
|
||||
}
|
||||
|
||||
func (s *stepStopInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Skip when it is a spot instance
|
||||
if s.SpotPrice != "" && s.SpotPrice != "0" {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Stop the instance so we can create an AMI from it
|
||||
ui.Say("Stopping the source instance...")
|
||||
_, err := ec2conn.StopInstances(&ec2.StopInstancesInput{
|
||||
InstanceIds: []*string{instance.InstanceId},
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error stopping instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Wait for the instance to actual stop
|
||||
ui.Say("Waiting for the instance to stop...")
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"running", "stopping"},
|
||||
Target: "stopped",
|
||||
Refresh: awscommon.InstanceStateRefreshFunc(ec2conn, *instance.InstanceId),
|
||||
StepState: state,
|
||||
}
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for instance to stop: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepStopInstance) Cleanup(multistep.StateBag) {
|
||||
// No cleanup...
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
@@ -18,33 +17,37 @@ func (s *stepTagEBSVolumes) Run(state multistep.StateBag) multistep.StepAction {
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if len(s.VolumeRunTags) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
if len(s.VolumeRunTags) > 0 {
|
||||
ui.Say("Tagging source EBS volumes...")
|
||||
|
||||
volumeIds := make([]*string, 0)
|
||||
for _, v := range instance.BlockDeviceMappings {
|
||||
if ebs := v.Ebs; ebs != nil {
|
||||
volumeIds = append(volumeIds, ebs.VolumeId)
|
||||
volumeIds := make([]*string, 0)
|
||||
for _, v := range instance.BlockDeviceMappings {
|
||||
if ebs := v.Ebs; ebs != nil {
|
||||
volumeIds = append(volumeIds, ebs.VolumeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(volumeIds) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
if len(volumeIds) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say("Adding tags to source EBS Volumes")
|
||||
tags := common.ConvertToEC2Tags(s.VolumeRunTags)
|
||||
tags := make([]*ec2.Tag, len(s.VolumeRunTags))
|
||||
for key, value := range s.VolumeRunTags {
|
||||
tags = append(tags, &ec2.Tag{Key: &key, Value: &value})
|
||||
}
|
||||
|
||||
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||
Resources: volumeIds,
|
||||
Tags: tags,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||
Resources: []*string{
|
||||
instance.BlockDeviceMappings[0].Ebs.VolumeId,
|
||||
},
|
||||
Tags: tags,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package ebs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@@ -12,21 +11,6 @@ import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type TFBuilder struct {
|
||||
Type string `json:"type"`
|
||||
Region string `json:"region"`
|
||||
SourceAmi string `json:"source_ami"`
|
||||
InstanceType string `json:"instance_type"`
|
||||
SshUsername string `json:"ssh_username"`
|
||||
AmiName string `json:"ami_name"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
SnapshotTags map[string]string `json:"snapshot_tags"`
|
||||
}
|
||||
|
||||
type TFConfig struct {
|
||||
Builders []TFBuilder `json:"builders"`
|
||||
}
|
||||
|
||||
func TestBuilderTagsAcc_basic(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
@@ -42,10 +26,9 @@ func checkTags() builderT.TestCheckFunc {
|
||||
return fmt.Errorf("more than 1 artifact")
|
||||
}
|
||||
|
||||
config := TFConfig{}
|
||||
json.Unmarshal([]byte(testBuilderTagsAccBasic), &config)
|
||||
tags := config.Builders[0].Tags
|
||||
snapshotTags := config.Builders[0].SnapshotTags
|
||||
tags := make(map[string]string)
|
||||
tags["OS_Version"] = "Ubuntu"
|
||||
tags["Release"] = "Latest"
|
||||
|
||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
||||
artifactRaw := artifacts[0]
|
||||
@@ -54,18 +37,18 @@ func checkTags() builderT.TestCheckFunc {
|
||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
||||
}
|
||||
|
||||
// Describe the image, get block devices with a snapshot
|
||||
// describe the image, get block devices with a snapshot
|
||||
ec2conn, _ := testEC2Conn()
|
||||
imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{aws.String(artifact.Amis["us-east-1"])},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error retrieving details for AMI Artifact (%#v) in Tags Test: %s", artifact, err)
|
||||
return fmt.Errorf("Error retrieving details for AMI Artifcat (%#v) in Tags Test: %s", artifact, err)
|
||||
}
|
||||
|
||||
if len(imageResp.Images) == 0 {
|
||||
return fmt.Errorf("No images found for AMI Artifact (%#v) in Tags Test: %s", artifact, err)
|
||||
return fmt.Errorf("No images found for AMI Artifcat (%#v) in Tags Test: %s", artifact, err)
|
||||
}
|
||||
|
||||
image := imageResp.Images[0]
|
||||
@@ -78,7 +61,7 @@ func checkTags() builderT.TestCheckFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// Grab matching snapshot info
|
||||
// grab matching snapshot info
|
||||
resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{
|
||||
SnapshotIds: snapshots,
|
||||
})
|
||||
@@ -91,14 +74,12 @@ func checkTags() builderT.TestCheckFunc {
|
||||
return fmt.Errorf("No Snapshots found for AMI Artifcat (%#v) in Tags Test", artifact)
|
||||
}
|
||||
|
||||
// Grab the snapshots, check the tags
|
||||
// grab the snapshots, check the tags
|
||||
for _, s := range resp.Snapshots {
|
||||
expected := len(tags)
|
||||
for _, t := range s.Tags {
|
||||
for key, value := range tags {
|
||||
if val, ok := snapshotTags[key]; ok && val == *t.Value {
|
||||
expected--
|
||||
} else if key == *t.Key && value == *t.Value {
|
||||
if key == *t.Key && value == *t.Value {
|
||||
expected--
|
||||
}
|
||||
}
|
||||
@@ -125,11 +106,7 @@ const testBuilderTagsAccBasic = `
|
||||
"ami_name": "packer-tags-testing-{{timestamp}}",
|
||||
"tags": {
|
||||
"OS_Version": "Ubuntu",
|
||||
"Release": "Latest",
|
||||
"Name": "Bleep"
|
||||
},
|
||||
"snapshot_tags": {
|
||||
"Name": "Foobar"
|
||||
"Release": "Latest"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package ebsvolume
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// map of region to list of volume IDs
|
||||
type EbsVolumes map[string][]string
|
||||
|
||||
// Artifact is an artifact implementation that contains built AMIs.
|
||||
type Artifact struct {
|
||||
// A map of regions to EBS Volume IDs.
|
||||
Volumes EbsVolumes
|
||||
|
||||
// BuilderId is the unique ID for the builder that created this AMI
|
||||
BuilderIdValue string
|
||||
|
||||
// EC2 connection for performing API stuff.
|
||||
Conn *ec2.EC2
|
||||
}
|
||||
|
||||
func (a *Artifact) BuilderId() string {
|
||||
return a.BuilderIdValue
|
||||
}
|
||||
|
||||
func (*Artifact) Files() []string {
|
||||
// We have no files
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns a sorted list of region:ID pairs
|
||||
func (a *Artifact) idList() []string {
|
||||
parts := make([]string, 0, len(a.Volumes))
|
||||
for region, volumeIDs := range a.Volumes {
|
||||
for _, volumeID := range volumeIDs {
|
||||
parts = append(parts, fmt.Sprintf("%s:%s", region, volumeID))
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(parts)
|
||||
return parts
|
||||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
return strings.Join(a.idList(), ",")
|
||||
}
|
||||
|
||||
func (a *Artifact) String() string {
|
||||
return fmt.Sprintf("EBS Volumes were created:\n\n%s", strings.Join(a.idList(), "\n"))
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Destroy() error {
|
||||
errors := make([]error, 0)
|
||||
|
||||
for region, volumeIDs := range a.Volumes {
|
||||
for _, volumeID := range volumeIDs {
|
||||
log.Printf("Deregistering Volume ID (%s) from region (%s)", volumeID, region)
|
||||
|
||||
input := &ec2.DeleteVolumeInput{
|
||||
VolumeId: &volumeID,
|
||||
}
|
||||
if _, err := a.Conn.DeleteVolume(input); 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
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package ebsvolume
|
||||
|
||||
import (
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
)
|
||||
|
||||
type BlockDevice struct {
|
||||
awscommon.BlockDevice `mapstructure:"-,squash"`
|
||||
Tags map[string]string `mapstructure:"tags"`
|
||||
}
|
||||
|
||||
func commonBlockDevices(mappings []BlockDevice) awscommon.BlockDevices {
|
||||
result := make([]awscommon.BlockDevice, len(mappings))
|
||||
for i, mapping := range mappings {
|
||||
result[i] = mapping.BlockDevice
|
||||
}
|
||||
|
||||
return awscommon.BlockDevices{
|
||||
LaunchBlockDevices: awscommon.LaunchBlockDevices{
|
||||
LaunchMappings: result,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
// The ebsvolume package contains a packer.Builder implementation that
|
||||
// builds EBS volumes for Amazon EC2 using an ephemeral instance,
|
||||
package ebsvolume
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
const BuilderId = "mitchellh.amazon.ebsvolume"
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
|
||||
VolumeMappings []BlockDevice `mapstructure:"ebs_volumes"`
|
||||
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
config Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
var errs *packer.MultiError
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.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
|
||||
}
|
||||
|
||||
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) {
|
||||
config, err := b.config.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := session.NewSession(config)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error creating AWS Session: {{err}}", err)
|
||||
}
|
||||
|
||||
ec2conn := ec2.New(session)
|
||||
|
||||
// If the subnet is specified but not the AZ, try to determine the AZ automatically
|
||||
if b.config.SubnetId != "" && b.config.AvailabilityZone == "" {
|
||||
log.Printf("[INFO] Finding AZ for the given subnet '%s'", b.config.SubnetId)
|
||||
resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone
|
||||
log.Printf("[INFO] AZ found: '%s'", b.config.AvailabilityZone)
|
||||
}
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", b.config)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
launchBlockDevices := commonBlockDevices(b.config.VolumeMappings)
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
&awscommon.StepSourceAMIInfo{
|
||||
SourceAmi: b.config.SourceAmi,
|
||||
EnhancedNetworking: b.config.AMIEnhancedNetworking,
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
},
|
||||
&awscommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
SSHAgentAuth: b.config.Comm.SSHAgentAuth,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
KeyPairName: b.config.SSHKeyPairName,
|
||||
TemporaryKeyPairName: b.config.TemporaryKeyPairName,
|
||||
PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey,
|
||||
},
|
||||
&awscommon.StepSecurityGroup{
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
VpcId: b.config.VpcId,
|
||||
},
|
||||
&awscommon.StepRunSourceInstance{
|
||||
Debug: b.config.PackerDebug,
|
||||
ExpectedRootDevice: "ebs",
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||
InstanceType: b.config.InstanceType,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
SubnetId: b.config.SubnetId,
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: launchBlockDevices,
|
||||
Tags: b.config.RunTags,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
},
|
||||
&stepTagEBSVolumes{
|
||||
VolumeMapping: b.config.VolumeMappings,
|
||||
},
|
||||
&awscommon.StepGetPassword{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Timeout: b.config.WindowsPasswordTimeout,
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: awscommon.SSHHost(
|
||||
ec2conn,
|
||||
b.config.SSHPrivateIp),
|
||||
SSHConfig: awscommon.SSHConfig(
|
||||
b.config.RunConfig.Comm.SSHAgentAuth,
|
||||
b.config.RunConfig.Comm.SSHUsername,
|
||||
b.config.RunConfig.Comm.SSHPassword),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&awscommon.StepStopEBSBackedInstance{
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
DisableStopInstance: b.config.DisableStopInstance,
|
||||
},
|
||||
&awscommon.StepModifyEBSBackedInstance{
|
||||
EnableEnhancedNetworking: b.config.AMIEnhancedNetworking,
|
||||
},
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Build the artifact and return it
|
||||
artifact := &Artifact{
|
||||
Volumes: state.Get("ebsvolumes").(EbsVolumes),
|
||||
BuilderIdValue: BuilderId,
|
||||
Conn: ec2conn,
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Created Volumes: %s", artifact))
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package ebsvolume
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"access_key": "foo",
|
||||
"secret_key": "bar",
|
||||
"source_ami": "foo",
|
||||
"instance_type": "foo",
|
||||
"region": "us-east-1",
|
||||
"ssh_username": "root",
|
||||
}
|
||||
}
|
||||
|
||||
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_InvalidKey(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// 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_InvalidShutdownBehavior(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["shutdown_behavior"] = "terminate"
|
||||
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 good
|
||||
config["shutdown_behavior"] = "stop"
|
||||
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["shutdown_behavior"] = "foobar"
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package ebsvolume
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type stepTagEBSVolumes struct {
|
||||
VolumeMapping []BlockDevice
|
||||
}
|
||||
|
||||
func (s *stepTagEBSVolumes) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
volumes := make(EbsVolumes)
|
||||
for _, instanceBlockDevices := range instance.BlockDeviceMappings {
|
||||
for _, configVolumeMapping := range s.VolumeMapping {
|
||||
if configVolumeMapping.DeviceName == *instanceBlockDevices.DeviceName {
|
||||
volumes[*ec2conn.Config.Region] = append(
|
||||
volumes[*ec2conn.Config.Region],
|
||||
*instanceBlockDevices.Ebs.VolumeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
state.Put("ebsvolumes", volumes)
|
||||
|
||||
if len(s.VolumeMapping) > 0 {
|
||||
ui.Say("Tagging EBS volumes...")
|
||||
|
||||
toTag := map[string][]*ec2.Tag{}
|
||||
for _, mapping := range s.VolumeMapping {
|
||||
if len(mapping.Tags) == 0 {
|
||||
ui.Say(fmt.Sprintf("No tags specified for volume on %s...", mapping.DeviceName))
|
||||
continue
|
||||
}
|
||||
|
||||
tags := make([]*ec2.Tag, 0, len(mapping.Tags))
|
||||
for key, value := range mapping.Tags {
|
||||
tags = append(tags, &ec2.Tag{
|
||||
Key: aws.String(fmt.Sprintf("%s", key)),
|
||||
Value: aws.String(fmt.Sprintf("%s", value)),
|
||||
})
|
||||
}
|
||||
|
||||
for _, v := range instance.BlockDeviceMappings {
|
||||
if *v.DeviceName == mapping.DeviceName {
|
||||
toTag[*v.Ebs.VolumeId] = tags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for volumeId, tags := range toTag {
|
||||
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||
Resources: []*string{&volumeId},
|
||||
Tags: tags,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error tagging EBS Volume %s on %s: %s", volumeId, *instance.InstanceId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepTagEBSVolumes) Cleanup(state multistep.StateBag) {
|
||||
// No cleanup...
|
||||
}
|
||||
@@ -160,10 +160,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := session.NewSession(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
session := session.New(config)
|
||||
ec2conn := ec2.New(session)
|
||||
|
||||
// If the subnet is specified but not the AZ, try to determine the AZ automatically
|
||||
@@ -193,11 +190,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
&awscommon.StepSourceAMIInfo{
|
||||
SourceAmi: b.config.SourceAmi,
|
||||
EnhancedNetworking: b.config.AMIEnhancedNetworking,
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
},
|
||||
&awscommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
SSHAgentAuth: b.config.Comm.SSHAgentAuth,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
KeyPairName: b.config.SSHKeyPairName,
|
||||
PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey,
|
||||
@@ -235,9 +230,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
ec2conn,
|
||||
b.config.SSHPrivateIp),
|
||||
SSHConfig: awscommon.SSHConfig(
|
||||
b.config.RunConfig.Comm.SSHAgentAuth,
|
||||
b.config.RunConfig.Comm.SSHUsername,
|
||||
b.config.RunConfig.Comm.SSHPassword),
|
||||
b.config.RunConfig.Comm.SSHUsername),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&StepUploadX509Cert{},
|
||||
@@ -248,9 +241,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
Debug: b.config.PackerDebug,
|
||||
},
|
||||
&awscommon.StepDeregisterAMI{
|
||||
ForceDeregister: b.config.AMIForceDeregister,
|
||||
ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot,
|
||||
AMIName: b.config.AMIName,
|
||||
ForceDeregister: b.config.AMIForceDeregister,
|
||||
AMIName: b.config.AMIName,
|
||||
},
|
||||
&StepRegisterAMI{},
|
||||
&awscommon.StepAMIRegionCopy{
|
||||
@@ -259,21 +251,26 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
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,
|
||||
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,
|
||||
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
|
||||
|
||||
@@ -63,8 +63,6 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("snapshots", map[string][]string{})
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
Here's a list of things we like to get done in no particular order:
|
||||
|
||||
- [ ] Blob/image copy post-processor
|
||||
- [ ] Blob/image rename post-processor
|
||||
- [ ] SSH to private ip through subnet
|
||||
- [ ] chroot builder
|
||||
- [ ] support cross-storage account image source (ie. pre-build blob copy)
|
||||
- [ ] look up object id when using device code (graph api /me ?)
|
||||
- [ ] device flow support for Windows
|
||||
- [x] look up tenant id in all cases (see device flow code)
|
||||
- [ ] look up resource group of storage account
|
||||
- [ ] include all _data_ disks in artifact too
|
||||
- [ ] windows sysprep provisioner (since it seems to generate a certain issue volume)
|
||||
- [ ] allow arbitrary json patching for deployment document
|
||||
- [ ] tag all resources with user-supplied tag
|
||||
@@ -0,0 +1,191 @@
|
||||
# packer-azure-arm
|
||||
|
||||
The ARM flavor of packer-azure utilizes the
|
||||
[Azure Resource Manager APIs](https://msdn.microsoft.com/en-us/library/azure/dn790568.aspx).
|
||||
Please see the
|
||||
[overview](https://azure.microsoft.com/en-us/documentation/articles/resource-group-overview/)
|
||||
for more information about ARM as well as the benefit of ARM.
|
||||
|
||||
## Device Login vs. Service Principal Name (SPN)
|
||||
|
||||
There are two ways to get started with packer-azure. The simplest is device login, and only requires a Subscription ID.
|
||||
Device login is only supported for Linux based VMs. The second is the use of an SPN. We recommend the device login
|
||||
approach for those who are first time users, and just want to ''kick the tires.'' We recommend the SPN approach if you
|
||||
intend to automate Packer, or you are deploying Windows VMs.
|
||||
|
||||
## Device Login
|
||||
|
||||
A sample template for device login is show below. There are three pieces of information
|
||||
you must provide to enable device login mode.
|
||||
|
||||
1. SubscriptionID
|
||||
1. Resource Group - parent resource group that Packer uses to build an image.
|
||||
1. Storage Account - storage account where the image will be placed.
|
||||
|
||||
> Device login mode is enabled by not setting client_id, client_secret, and tenant_id.
|
||||
|
||||
The device login flow asks that you open a web browser, navigate to http://aka.ms/devicelogin, and input the supplied
|
||||
code. This authorizes the Packer for Azure application to act on your behalf. An OAuth token will be created, and
|
||||
stored in the user's home directory (~/.azure/packer/oauth-TenantID.json, and TenantID will be replaced with the actual
|
||||
Tenant ID). This token is used if it exists, and refreshed as necessary.
|
||||
|
||||
```json
|
||||
{
|
||||
"variables": {
|
||||
"sid": "your_subscription_id",
|
||||
"rgn": "your_resource_group",
|
||||
"sa": "your_storage_account"
|
||||
},
|
||||
"builders": [
|
||||
{
|
||||
"type": "azure-arm",
|
||||
|
||||
"subscription_id": "{{user `sid`}}",
|
||||
|
||||
"resource_group_name": "{{user `rgn`}}",
|
||||
"storage_account": "{{user `sa`}}",
|
||||
|
||||
"capture_container_name": "images",
|
||||
"capture_name_prefix": "packer",
|
||||
|
||||
"os_type": "Linux",
|
||||
"image_publisher": "Canonical",
|
||||
"image_offer": "UbuntuServer",
|
||||
"image_sku": "14.04.3-LTS",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_A2"
|
||||
}
|
||||
],
|
||||
"provisioners": [
|
||||
{
|
||||
"execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'",
|
||||
"inline": [
|
||||
"apt-get update",
|
||||
"apt-get upgrade -y",
|
||||
|
||||
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
|
||||
],
|
||||
"inline_shebang": "/bin/sh -x",
|
||||
"type": "shell"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Service Principal Name
|
||||
|
||||
The ARM APIs use OAUTH to authenticate, and requires an SPN. The following articles
|
||||
are a good starting points for creating a new SPN.
|
||||
|
||||
* [Automating Azure on your CI server using a Service Principal](http://blog.davidebbo.com/2014/12/azure-service-principal.html)
|
||||
* [Authenticating a service principal with Azure Resource Manager](https://azure.microsoft.com/en-us/documentation/articles/resource-group-authenticate-service-principal/)
|
||||
|
||||
There are three (four in the case of Windows) pieces of configuration you need to note
|
||||
after creating an SPN.
|
||||
|
||||
1. Client ID (aka Service Principal ID)
|
||||
1. Client Secret (aka Service Principal generated key)
|
||||
1. Client Tenant (aka Azure Active Directory tenant that owns the
|
||||
Service Principal)
|
||||
1. Object ID (Windows only) - a certificate is used to authenticate WinRM access, and the certificate is injected into
|
||||
the VM using Azure Key Vault. Access to the key vault is protected by an ACL associated with the SPN's ObjectID.
|
||||
Linux does not need nor use a key vault, so there's no need to know the ObjectID.
|
||||
|
||||
You will also need the following.
|
||||
|
||||
1. Subscription ID
|
||||
1. Resource Group
|
||||
1. Storage Account
|
||||
|
||||
Resource Group is where your storage account is located, and Storage
|
||||
Account is where the created packer image will be stored.
|
||||
|
||||
The Service Principal has been tested with the following [permissions](https://azure.microsoft.com/en-us/documentation/articles/role-based-access-control-configure/).
|
||||
Please review the document for the [built in roles](https://azure.microsoft.com/en-gb/documentation/articles/role-based-access-built-in-roles/)
|
||||
for more details.
|
||||
|
||||
* Owner
|
||||
|
||||
> NOTE: the Owner role is too powerful, and more explicit set of roles
|
||||
> is TBD. Issue #183 is tracking this work. Permissions can be scoped to
|
||||
> a specific resource group to further limit access.
|
||||
|
||||
### Sample Ubuntu
|
||||
|
||||
The following is a sample Packer template for use with the Packer
|
||||
Azure for ARM builder.
|
||||
|
||||
```json
|
||||
{
|
||||
"variables": {
|
||||
"cid": "your_client_id",
|
||||
"cst": "your_client_secret",
|
||||
"tid": "your_client_tenant",
|
||||
"sid": "your_subscription_id",
|
||||
|
||||
"rgn": "your_resource_group",
|
||||
"sa": "your_storage_account"
|
||||
},
|
||||
"builders": [
|
||||
{
|
||||
"type": "azure-arm",
|
||||
|
||||
"client_id": "{{user `cid`}}",
|
||||
"client_secret": "{{user `cst`}}",
|
||||
"subscription_id": "{{user `sid`}}",
|
||||
"tenant_id": "{{user `tid`}}",
|
||||
|
||||
"resource_group_name": "{{user `rgn`}}",
|
||||
"storage_account": "{{user `sa`}}",
|
||||
|
||||
"capture_container_name": "images",
|
||||
"capture_name_prefix": "packer",
|
||||
|
||||
"os_type": "Linux",
|
||||
"image_publisher": "Canonical",
|
||||
"image_offer": "UbuntuServer",
|
||||
"image_sku": "14.04.3-LTS",
|
||||
|
||||
"location": "South Central US",
|
||||
|
||||
"vm_size": "Standard_A2"
|
||||
}
|
||||
],
|
||||
"provisioners": [
|
||||
{
|
||||
"execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'",
|
||||
"inline": [
|
||||
"apt-get update",
|
||||
"apt-get upgrade -y",
|
||||
|
||||
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
|
||||
],
|
||||
"inline_shebang": "/bin/sh -x",
|
||||
"type": "shell"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Using the above template, Packer would be invoked as follows.
|
||||
|
||||
> NOTE: the following variables must be **changed** based on your
|
||||
> subscription. These values are just dummy values, but they match
|
||||
> format of expected, e.g. if the value is a GUID the sample is a
|
||||
> GUID.
|
||||
|
||||
```bat
|
||||
packer build^
|
||||
-var cid="593c4dc4-9cd7-49af-9fe0-1ea5055ac1e4"^
|
||||
-var cst="GbzJfsfrVkqL/TLfZY8TXA=="^
|
||||
-var sid="ce323e74-56fc-4bd6-aa18-83b6dc262748"^
|
||||
-var tid="da3847b4-8e69-40bd-a2c2-41da6982c5e2"^
|
||||
-var rgn="My Resource Group"^
|
||||
-var sa="mystorageaccount"^
|
||||
c:\packer\ubuntu_14_LTS.json
|
||||
```
|
||||
|
||||
Please see the
|
||||
[config_sameples/arm](https://github.com/Azure/packer-azure/tree/master/config_examples)
|
||||
directory for more examples of usage.
|
||||
@@ -1,120 +0,0 @@
|
||||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminPassword": "[parameters('adminPassword')]",
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"image": {
|
||||
"uri": "https://localhost/custom.vhd"
|
||||
},
|
||||
"name": "osdisk",
|
||||
"osType": "Linux",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2015-06-15",
|
||||
"location": "[resourceGroup().location]",
|
||||
"nicName": "packerNic",
|
||||
"publicIPAddressName": "packerPublicIP",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "ignore",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "ignore",
|
||||
"virtualNetworkResourceGroup": "ignore",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ func getFakeSasUrl(name string) string {
|
||||
func TestArtifactString(t *testing.T) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
{
|
||||
CaptureResources{
|
||||
Properties: CaptureProperties{
|
||||
StorageProfile: CaptureStorageProfile{
|
||||
OSDisk: CaptureDisk{
|
||||
@@ -57,7 +57,7 @@ func TestArtifactString(t *testing.T) {
|
||||
func TestArtifactProperties(t *testing.T) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
{
|
||||
CaptureResources{
|
||||
Properties: CaptureProperties{
|
||||
StorageProfile: CaptureStorageProfile{
|
||||
OSDisk: CaptureDisk{
|
||||
@@ -97,7 +97,7 @@ func TestArtifactProperties(t *testing.T) {
|
||||
func TestArtifactOverHypenatedCaptureUri(t *testing.T) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
{
|
||||
CaptureResources{
|
||||
Properties: CaptureProperties{
|
||||
StorageProfile: CaptureStorageProfile{
|
||||
OSDisk: CaptureDisk{
|
||||
@@ -134,7 +134,7 @@ func TestArtifactRejectMalformedTemplates(t *testing.T) {
|
||||
func TestArtifactRejectMalformedStorageUri(t *testing.T) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
{
|
||||
CaptureResources{
|
||||
Properties: CaptureProperties{
|
||||
StorageProfile: CaptureStorageProfile{
|
||||
OSDisk: CaptureDisk{
|
||||
|
||||
@@ -26,7 +26,7 @@ func (a *Authenticate) getServicePrincipalToken() (*azure.ServicePrincipalToken,
|
||||
}
|
||||
|
||||
func (a *Authenticate) getServicePrincipalTokenWithResource(resource string) (*azure.ServicePrincipalToken, error) {
|
||||
oauthConfig, err := a.env.OAuthConfigForTenant(a.tenantID)
|
||||
oauthConfig, err := newOAuthConfigWithTenant(a.tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -39,3 +39,7 @@ func (a *Authenticate) getServicePrincipalTokenWithResource(resource string) (*a
|
||||
|
||||
return spt, err
|
||||
}
|
||||
|
||||
func newOAuthConfigWithTenant(tenantID string) (*azure.OAuthConfig, error) {
|
||||
return azure.PublicCloud.OAuthConfigForTenant(tenantID)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
@@ -36,9 +35,6 @@ type AzureClient struct {
|
||||
resources.DeploymentsClient
|
||||
resources.GroupsClient
|
||||
network.PublicIPAddressesClient
|
||||
network.InterfacesClient
|
||||
network.SubnetsClient
|
||||
network.VirtualNetworksClient
|
||||
compute.VirtualMachinesClient
|
||||
common.VaultClient
|
||||
armStorage.AccountsClient
|
||||
@@ -106,67 +102,43 @@ func byConcatDecorators(decorators ...autorest.RespondDecorator) autorest.Respon
|
||||
}
|
||||
|
||||
func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string,
|
||||
cloud *azure.Environment,
|
||||
servicePrincipalToken, servicePrincipalTokenVault *azure.ServicePrincipalToken) (*AzureClient, error) {
|
||||
|
||||
var azureClient = &AzureClient{}
|
||||
|
||||
maxlen := getInspectorMaxLength()
|
||||
|
||||
azureClient.DeploymentsClient = resources.NewDeploymentsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.DeploymentsClient = resources.NewDeploymentsClient(subscriptionID)
|
||||
azureClient.DeploymentsClient.Authorizer = servicePrincipalToken
|
||||
azureClient.DeploymentsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.DeploymentsClient.ResponseInspector = byInspecting(maxlen)
|
||||
azureClient.DeploymentsClient.UserAgent += packerUserAgent
|
||||
|
||||
azureClient.GroupsClient = resources.NewGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.GroupsClient = resources.NewGroupsClient(subscriptionID)
|
||||
azureClient.GroupsClient.Authorizer = servicePrincipalToken
|
||||
azureClient.GroupsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.GroupsClient.ResponseInspector = byInspecting(maxlen)
|
||||
azureClient.GroupsClient.UserAgent += packerUserAgent
|
||||
|
||||
azureClient.InterfacesClient = network.NewInterfacesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.InterfacesClient.Authorizer = servicePrincipalToken
|
||||
azureClient.InterfacesClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.InterfacesClient.ResponseInspector = byInspecting(maxlen)
|
||||
azureClient.InterfacesClient.UserAgent += packerUserAgent
|
||||
|
||||
azureClient.SubnetsClient = network.NewSubnetsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.SubnetsClient.Authorizer = servicePrincipalToken
|
||||
azureClient.SubnetsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.SubnetsClient.ResponseInspector = byInspecting(maxlen)
|
||||
azureClient.SubnetsClient.UserAgent += packerUserAgent
|
||||
|
||||
azureClient.VirtualNetworksClient = network.NewVirtualNetworksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.VirtualNetworksClient.Authorizer = servicePrincipalToken
|
||||
azureClient.VirtualNetworksClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.VirtualNetworksClient.ResponseInspector = byInspecting(maxlen)
|
||||
azureClient.VirtualNetworksClient.UserAgent += packerUserAgent
|
||||
|
||||
azureClient.PublicIPAddressesClient = network.NewPublicIPAddressesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.PublicIPAddressesClient = network.NewPublicIPAddressesClient(subscriptionID)
|
||||
azureClient.PublicIPAddressesClient.Authorizer = servicePrincipalToken
|
||||
azureClient.PublicIPAddressesClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.PublicIPAddressesClient.ResponseInspector = byInspecting(maxlen)
|
||||
azureClient.PublicIPAddressesClient.UserAgent += packerUserAgent
|
||||
|
||||
azureClient.VirtualMachinesClient = compute.NewVirtualMachinesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.VirtualMachinesClient = compute.NewVirtualMachinesClient(subscriptionID)
|
||||
azureClient.VirtualMachinesClient.Authorizer = servicePrincipalToken
|
||||
azureClient.VirtualMachinesClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.VirtualMachinesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), templateCapture(azureClient))
|
||||
azureClient.VirtualMachinesClient.UserAgent += packerUserAgent
|
||||
|
||||
azureClient.AccountsClient = armStorage.NewAccountsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.AccountsClient = armStorage.NewAccountsClient(subscriptionID)
|
||||
azureClient.AccountsClient.Authorizer = servicePrincipalToken
|
||||
azureClient.AccountsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.AccountsClient.ResponseInspector = byInspecting(maxlen)
|
||||
azureClient.AccountsClient.UserAgent += packerUserAgent
|
||||
|
||||
keyVaultURL, err := url.Parse(cloud.KeyVaultEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
azureClient.VaultClient = common.NewVaultClient(*keyVaultURL)
|
||||
azureClient.VaultClient = common.VaultClient{}
|
||||
azureClient.VaultClient.Authorizer = servicePrincipalTokenVault
|
||||
azureClient.VaultClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.VaultClient.ResponseInspector = byInspecting(maxlen)
|
||||
@@ -177,13 +149,7 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storageClient, err := storage.NewClient(
|
||||
storageAccountName,
|
||||
*(*accountKeys.Keys)[0].Value,
|
||||
cloud.StorageEndpointSuffix,
|
||||
storage.DefaultAPIVersion,
|
||||
true /*useHttps*/)
|
||||
|
||||
storageClient, err := storage.NewBasicClient(storageAccountName, *accountKeys.Key1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
packerAzureCommon "github.com/mitchellh/packer/builder/azure/common"
|
||||
@@ -30,7 +29,6 @@ type Builder struct {
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultNicName = "packerNic"
|
||||
DefaultPublicIPAddressName = "packerPublicIP"
|
||||
DefaultSasBlobContainer = "system/Microsoft.Compute"
|
||||
DefaultSasBlobPermission = "r"
|
||||
@@ -53,11 +51,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
ui.Say("Running builder ...")
|
||||
|
||||
if err := newConfigRetriever().FillParameters(b.config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ui.Say("Preparing builder ...")
|
||||
|
||||
log.Print(":: Configuration")
|
||||
packerAzureCommon.DumpConfig(b.config, func(s string) { log.Print(s) })
|
||||
@@ -75,7 +69,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
b.config.SubscriptionID,
|
||||
b.config.ResourceGroupName,
|
||||
b.config.StorageAccount,
|
||||
b.config.cloudEnvironment,
|
||||
spnCloud,
|
||||
spnKeyVault)
|
||||
|
||||
@@ -83,30 +76,21 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolver := newResourceResolver(azureClient)
|
||||
if err := resolver.Resolve(b.config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.config.storageAccountBlobEndpoint, err = b.getBlobEndpoint(azureClient, b.config.ResourceGroupName, b.config.StorageAccount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpointConnectType := PublicEndpoint
|
||||
if b.isPrivateNetworkCommunication() {
|
||||
endpointConnectType = PrivateEndpoint
|
||||
}
|
||||
|
||||
b.setTemplateParameters(b.stateBag)
|
||||
|
||||
var steps []multistep.Step
|
||||
|
||||
if b.config.OSType == constants.Target_Linux {
|
||||
steps = []multistep.Step{
|
||||
NewStepCreateResourceGroup(azureClient, ui),
|
||||
NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
|
||||
NewStepDeployTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
|
||||
NewStepGetIPAddress(azureClient, ui, endpointConnectType),
|
||||
NewStepValidateTemplate(azureClient, ui, Linux),
|
||||
NewStepDeployTemplate(azureClient, ui, Linux),
|
||||
NewStepGetIPAddress(azureClient, ui),
|
||||
&communicator.StepConnectSSH{
|
||||
Config: &b.config.Comm,
|
||||
Host: lin.SSHHost,
|
||||
@@ -122,13 +106,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
} else if b.config.OSType == constants.Target_Windows {
|
||||
steps = []multistep.Step{
|
||||
NewStepCreateResourceGroup(azureClient, ui),
|
||||
NewStepValidateTemplate(azureClient, ui, b.config, GetKeyVaultDeployment),
|
||||
NewStepDeployTemplate(azureClient, ui, b.config, GetKeyVaultDeployment),
|
||||
NewStepValidateTemplate(azureClient, ui, KeyVault),
|
||||
NewStepDeployTemplate(azureClient, ui, KeyVault),
|
||||
NewStepGetCertificate(azureClient, ui),
|
||||
NewStepSetCertificate(b.config, ui),
|
||||
NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
|
||||
NewStepDeployTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
|
||||
NewStepGetIPAddress(azureClient, ui, endpointConnectType),
|
||||
NewStepValidateTemplate(azureClient, ui, Windows),
|
||||
NewStepDeployTemplate(azureClient, ui, Windows),
|
||||
NewStepGetIPAddress(azureClient, ui),
|
||||
&communicator.StepConnectWinRM{
|
||||
Config: &b.config.Comm,
|
||||
Host: func(stateBag multistep.StateBag) (string, error) {
|
||||
@@ -157,7 +141,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
ui.Message(fmt.Sprintf("temp admin password: '%s'", b.config.Password))
|
||||
}
|
||||
|
||||
b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner = b.createRunner(&steps, ui)
|
||||
b.runner.Run(b.stateBag)
|
||||
|
||||
// Report any errors.
|
||||
@@ -187,10 +171,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
return &Artifact{}, nil
|
||||
}
|
||||
|
||||
func (b *Builder) isPrivateNetworkCommunication() bool {
|
||||
return b.config.VirtualNetworkName != ""
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
@@ -198,6 +178,19 @@ func (b *Builder) Cancel() {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) createRunner(steps *[]multistep.Step, ui packer.Ui) multistep.Runner {
|
||||
if b.config.PackerDebug {
|
||||
return &multistep.DebugRunner{
|
||||
Steps: *steps,
|
||||
PauseFn: packerCommon.MultistepDebugFn(ui),
|
||||
}
|
||||
}
|
||||
|
||||
return &multistep.BasicRunner{
|
||||
Steps: *steps,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) getBlobEndpoint(client *AzureClient, resourceGroupName string, storageAccountName string) (string, error) {
|
||||
account, err := client.AccountsClient.GetProperties(resourceGroupName, storageAccountName)
|
||||
if err != nil {
|
||||
@@ -211,18 +204,17 @@ func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
|
||||
stateBag.Put(constants.AuthorizedKey, b.config.sshAuthorizedKey)
|
||||
stateBag.Put(constants.PrivateKey, b.config.sshPrivateKey)
|
||||
|
||||
stateBag.Put(constants.ArmTags, &b.config.AzureTags)
|
||||
stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
|
||||
stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
|
||||
stateBag.Put(constants.ArmKeyVaultName, b.config.tmpKeyVaultName)
|
||||
stateBag.Put(constants.ArmLocation, b.config.Location)
|
||||
stateBag.Put(constants.ArmNicName, DefaultNicName)
|
||||
stateBag.Put(constants.ArmPublicIPAddressName, DefaultPublicIPAddressName)
|
||||
stateBag.Put(constants.ArmResourceGroupName, b.config.tmpResourceGroupName)
|
||||
stateBag.Put(constants.ArmStorageAccountName, b.config.StorageAccount)
|
||||
}
|
||||
|
||||
func (b *Builder) setTemplateParameters(stateBag multistep.StateBag) {
|
||||
stateBag.Put(constants.ArmTemplateParameters, b.config.toTemplateParameters())
|
||||
stateBag.Put(constants.ArmVirtualMachineCaptureParameters, b.config.toVirtualMachineCaptureParameters())
|
||||
}
|
||||
|
||||
@@ -233,7 +225,7 @@ func (b *Builder) getServicePrincipalTokens(say func(string)) (*azure.ServicePri
|
||||
var err error
|
||||
|
||||
if b.config.useDeviceLogin {
|
||||
servicePrincipalToken, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.TenantID, say)
|
||||
servicePrincipalToken, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.SubscriptionID, say)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -245,9 +237,7 @@ func (b *Builder) getServicePrincipalTokens(say func(string)) (*azure.ServicePri
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
servicePrincipalTokenVault, err = auth.getServicePrincipalTokenWithResource(
|
||||
strings.TrimRight(b.config.cloudEnvironment.KeyVaultEndpoint, "/"))
|
||||
|
||||
servicePrincipalTokenVault, err = auth.getServicePrincipalTokenWithResource(packerAzureCommon.AzureVaultScope)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
|
||||
@@ -20,13 +19,12 @@ func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
|
||||
constants.AuthorizedKey,
|
||||
constants.PrivateKey,
|
||||
|
||||
constants.ArmTags,
|
||||
constants.ArmComputeName,
|
||||
constants.ArmDeploymentName,
|
||||
constants.ArmLocation,
|
||||
constants.ArmNicName,
|
||||
constants.ArmResourceGroupName,
|
||||
constants.ArmStorageAccountName,
|
||||
constants.ArmTemplateParameters,
|
||||
constants.ArmVirtualMachineCaptureParameters,
|
||||
constants.ArmPublicIPAddressName,
|
||||
}
|
||||
|
||||
+78
-155
@@ -13,24 +13,24 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"regexp"
|
||||
"strings"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/masterzen/winrm"
|
||||
|
||||
"github.com/Azure/go-ntlmssp"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/packer/builder/azure/pkcs12"
|
||||
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -40,11 +40,6 @@ const (
|
||||
DefaultVMSize = "Standard_A1"
|
||||
)
|
||||
|
||||
var (
|
||||
reCaptureContainerName = regexp.MustCompile("^[a-z0-9][a-z0-9\\-]{2,62}$")
|
||||
reCaptureNamePrefix = regexp.MustCompile("^[A-Za-z0-9][A-Za-z0-9_\\-\\.]{0,23}$")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
@@ -64,26 +59,18 @@ type Config struct {
|
||||
ImageOffer string `mapstructure:"image_offer"`
|
||||
ImageSku string `mapstructure:"image_sku"`
|
||||
ImageVersion string `mapstructure:"image_version"`
|
||||
ImageUrl string `mapstructure:"image_url"`
|
||||
Location string `mapstructure:"location"`
|
||||
VMSize string `mapstructure:"vm_size"`
|
||||
|
||||
// Deployment
|
||||
AzureTags map[string]*string `mapstructure:"azure_tags"`
|
||||
ResourceGroupName string `mapstructure:"resource_group_name"`
|
||||
StorageAccount string `mapstructure:"storage_account"`
|
||||
storageAccountBlobEndpoint string
|
||||
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
|
||||
cloudEnvironment *azure.Environment
|
||||
VirtualNetworkName string `mapstructure:"virtual_network_name"`
|
||||
VirtualNetworkSubnetName string `mapstructure:"virtual_network_subnet_name"`
|
||||
VirtualNetworkResourceGroupName string `mapstructure:"virtual_network_resource_group_name"`
|
||||
CustomDataFile string `mapstructure:"custom_data_file"`
|
||||
customData string
|
||||
ResourceGroupName string `mapstructure:"resource_group_name"`
|
||||
StorageAccount string `mapstructure:"storage_account"`
|
||||
storageAccountBlobEndpoint string
|
||||
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
|
||||
cloudEnvironment *azure.Environment
|
||||
|
||||
// OS
|
||||
OSType string `mapstructure:"os_type"`
|
||||
OSDiskSizeGB int32 `mapstructure:"os_disk_size_gb"`
|
||||
OSType string `mapstructure:"os_type"`
|
||||
|
||||
// Runtime Values
|
||||
UserName string
|
||||
@@ -116,6 +103,38 @@ type keyVaultCertificate struct {
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
// If we ever feel the need to support more templates consider moving this
|
||||
// method to its own factory class.
|
||||
func (c *Config) toTemplateParameters() *TemplateParameters {
|
||||
templateParameters := &TemplateParameters{
|
||||
AdminUsername: &TemplateParameter{c.UserName},
|
||||
AdminPassword: &TemplateParameter{c.Password},
|
||||
DnsNameForPublicIP: &TemplateParameter{c.tmpComputeName},
|
||||
ImageOffer: &TemplateParameter{c.ImageOffer},
|
||||
ImagePublisher: &TemplateParameter{c.ImagePublisher},
|
||||
ImageSku: &TemplateParameter{c.ImageSku},
|
||||
ImageVersion: &TemplateParameter{c.ImageVersion},
|
||||
OSDiskName: &TemplateParameter{c.tmpOSDiskName},
|
||||
StorageAccountBlobEndpoint: &TemplateParameter{c.storageAccountBlobEndpoint},
|
||||
VMSize: &TemplateParameter{c.VMSize},
|
||||
VMName: &TemplateParameter{c.tmpComputeName},
|
||||
}
|
||||
|
||||
switch c.OSType {
|
||||
case constants.Target_Linux:
|
||||
templateParameters.SshAuthorizedKey = &TemplateParameter{c.sshAuthorizedKey}
|
||||
case constants.Target_Windows:
|
||||
templateParameters.TenantId = &TemplateParameter{c.TenantID}
|
||||
templateParameters.ObjectId = &TemplateParameter{c.ObjectID}
|
||||
|
||||
templateParameters.KeyVaultName = &TemplateParameter{c.tmpKeyVaultName}
|
||||
templateParameters.KeyVaultSecretValue = &TemplateParameter{c.winrmCertificate}
|
||||
templateParameters.WinRMCertificateUrl = &TemplateParameter{c.tmpWinRMCertificateUrl}
|
||||
}
|
||||
|
||||
return templateParameters
|
||||
}
|
||||
|
||||
func (c *Config) toVirtualMachineCaptureParameters() *compute.VirtualMachineCaptureParameters {
|
||||
return &compute.VirtualMachineCaptureParameters{
|
||||
DestinationContainerName: &c.CaptureContainerName,
|
||||
@@ -127,17 +146,17 @@ func (c *Config) toVirtualMachineCaptureParameters() *compute.VirtualMachineCapt
|
||||
func (c *Config) createCertificate() (string, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to Generate Private Key: %s", err)
|
||||
err := fmt.Errorf("Failed to Generate Private Key: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
host := fmt.Sprintf("%s.cloudapp.net", c.tmpComputeName)
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(24 * time.Hour)
|
||||
notAfter := notBefore.Add(365 * 24 * time.Hour)
|
||||
|
||||
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to Generate Serial Number: %v", err)
|
||||
err := fmt.Errorf("Failed to Generate Serial Number: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -204,33 +223,20 @@ func newConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = setCustomData(&c)
|
||||
err = setSshValues(&c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// NOTE: if the user did not specify a communicator, then default to both
|
||||
// SSH and WinRM. This is for backwards compatibility because the code did
|
||||
// not specifically force the user to set a communicator.
|
||||
if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "ssh") {
|
||||
err = setSshValues(&c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "winrm") {
|
||||
err = setWinRMCertificate(&c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = setWinRMCertificate(&c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(c.ctx)...)
|
||||
|
||||
assertRequiredParametersSet(&c, errs)
|
||||
assertTagProperties(&c, errs)
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, nil, errs
|
||||
}
|
||||
@@ -270,13 +276,14 @@ func setSshValues(c *Config) error {
|
||||
c.sshPrivateKey = sshKeyPair.PrivateKey()
|
||||
}
|
||||
|
||||
c.Comm.WinRMTransportDecorator = func(t *http.Transport) http.RoundTripper {
|
||||
return &ntlmssp.Negotiator{RoundTripper: t}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setWinRMCertificate(c *Config) error {
|
||||
c.Comm.WinRMTransportDecorator =
|
||||
func() winrm.Transporter { return &winrm.ClientNTLM{} }
|
||||
|
||||
cert, err := c.createCertificate()
|
||||
c.winrmCertificate = cert
|
||||
|
||||
@@ -310,50 +317,18 @@ func setUserNamePassword(c *Config) {
|
||||
}
|
||||
|
||||
func setCloudEnvironment(c *Config) error {
|
||||
lookup := map[string]string{
|
||||
"CHINA": "AzureChinaCloud",
|
||||
"CHINACLOUD": "AzureChinaCloud",
|
||||
"AZURECHINACLOUD": "AzureChinaCloud",
|
||||
|
||||
"GERMAN": "AzureGermanCloud",
|
||||
"GERMANCLOUD": "AzureGermanCloud",
|
||||
"AZUREGERMANCLOUD": "AzureGermanCloud",
|
||||
|
||||
"GERMANY": "AzureGermanCloud",
|
||||
"GERMANYCLOUD": "AzureGermanCloud",
|
||||
"AZUREGERMANYCLOUD": "AzureGermanCloud",
|
||||
|
||||
"PUBLIC": "AzurePublicCloud",
|
||||
"PUBLICCLOUD": "AzurePublicCloud",
|
||||
"AZUREPUBLICCLOUD": "AzurePublicCloud",
|
||||
|
||||
"USGOVERNMENT": "AzureUSGovernmentCloud",
|
||||
"USGOVERNMENTCLOUD": "AzureUSGovernmentCloud",
|
||||
"AZUREUSGOVERNMENTCLOUD": "AzureUSGovernmentCloud",
|
||||
}
|
||||
|
||||
name := strings.ToUpper(c.CloudEnvironmentName)
|
||||
envName, ok := lookup[name]
|
||||
if !ok {
|
||||
switch name {
|
||||
case "CHINA", "CHINACLOUD", "AZURECHINACLOUD":
|
||||
c.cloudEnvironment = &azure.ChinaCloud
|
||||
case "PUBLIC", "PUBLICCLOUD", "AZUREPUBLICCLOUD":
|
||||
c.cloudEnvironment = &azure.PublicCloud
|
||||
case "USGOVERNMENT", "USGOVERNMENTCLOUD", "AZUREUSGOVERNMENTCLOUD":
|
||||
c.cloudEnvironment = &azure.USGovernmentCloud
|
||||
default:
|
||||
return fmt.Errorf("There is no cloud envionment matching the name '%s'!", c.CloudEnvironmentName)
|
||||
}
|
||||
|
||||
env, err := azure.EnvironmentFromName(envName)
|
||||
c.cloudEnvironment = &env
|
||||
return err
|
||||
}
|
||||
|
||||
func setCustomData(c *Config) error {
|
||||
if c.CustomDataFile == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(c.CustomDataFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.customData = base64.StdEncoding.EncodeToString(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -362,7 +337,7 @@ func provideDefaultValues(c *Config) {
|
||||
c.VMSize = DefaultVMSize
|
||||
}
|
||||
|
||||
if c.ImageUrl == "" && c.ImageVersion == "" {
|
||||
if c.ImageVersion == "" {
|
||||
c.ImageVersion = DefaultImageVersion
|
||||
}
|
||||
|
||||
@@ -371,21 +346,6 @@ func provideDefaultValues(c *Config) {
|
||||
}
|
||||
}
|
||||
|
||||
func assertTagProperties(c *Config, errs *packer.MultiError) {
|
||||
if len(c.AzureTags) > 15 {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("a max of 15 tags are supported, but %d were provided", len(c.AzureTags)))
|
||||
}
|
||||
|
||||
for k, v := range c.AzureTags {
|
||||
if len(k) > 512 {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 512 character limit", k, len(k)))
|
||||
}
|
||||
if len(*v) > 256 {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", v, len(*v)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
|
||||
/////////////////////////////////////////////
|
||||
// Authentication via OAUTH
|
||||
@@ -420,6 +380,10 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_secret must be specified"))
|
||||
}
|
||||
|
||||
if c.TenantID == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A tenant_id must be specified"))
|
||||
}
|
||||
|
||||
if c.SubscriptionID == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A subscription_id must be specified"))
|
||||
}
|
||||
@@ -428,51 +392,25 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
|
||||
/////////////////////////////////////////////
|
||||
// Capture
|
||||
if c.CaptureContainerName == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must be specified"))
|
||||
}
|
||||
|
||||
if !reCaptureContainerName.MatchString(c.CaptureContainerName) {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must satisfy the regular expression %q.", reCaptureContainerName.String()))
|
||||
}
|
||||
|
||||
if strings.HasSuffix(c.CaptureContainerName, "-") {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not end with a hyphen, e.g. '-'."))
|
||||
}
|
||||
|
||||
if strings.Contains(c.CaptureContainerName, "--") {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not contain consecutive hyphens, e.g. '--'."))
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An capture_container_name must be specified"))
|
||||
}
|
||||
|
||||
if c.CaptureNamePrefix == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must be specified"))
|
||||
}
|
||||
|
||||
if !reCaptureNamePrefix.MatchString(c.CaptureNamePrefix) {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must satisfy the regular expression %q.", reCaptureNamePrefix.String()))
|
||||
}
|
||||
|
||||
if strings.HasSuffix(c.CaptureNamePrefix, "-") || strings.HasSuffix(c.CaptureNamePrefix, ".") {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must not end with a hyphen or period."))
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An capture_name_prefix must be specified"))
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// Compute
|
||||
if c.ImageUrl == "" {
|
||||
if c.ImagePublisher == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_publisher must be specified"))
|
||||
}
|
||||
if c.ImagePublisher == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A image_publisher must be specified"))
|
||||
}
|
||||
|
||||
if c.ImageOffer == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_offer must be specified"))
|
||||
}
|
||||
if c.ImageOffer == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A image_offer must be specified"))
|
||||
}
|
||||
|
||||
if c.ImageSku == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_sku must be specified"))
|
||||
}
|
||||
} else {
|
||||
if c.ImagePublisher != "" || c.ImageOffer != "" || c.ImageSku != "" || c.ImageVersion != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_url must not be specified if image_publisher, image_offer, image_sku, or image_version is specified"))
|
||||
}
|
||||
if c.ImageSku == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A image_sku must be specified"))
|
||||
}
|
||||
|
||||
if c.Location == "" {
|
||||
@@ -484,25 +422,10 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
|
||||
if c.StorageAccount == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A storage_account must be specified"))
|
||||
}
|
||||
if c.ResourceGroupName == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A resource_group_name must be specified"))
|
||||
}
|
||||
if c.VirtualNetworkName == "" && c.VirtualNetworkResourceGroupName != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_resource_group_name is specified, so must virtual_network_name"))
|
||||
}
|
||||
if c.VirtualNetworkName == "" && c.VirtualNetworkSubnetName != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_subnet_name is specified, so must virtual_network_name"))
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// OS
|
||||
if strings.EqualFold(c.OSType, constants.Target_Linux) {
|
||||
c.OSType = constants.Target_Linux
|
||||
} else if strings.EqualFold(c.OSType, constants.Target_Windows) {
|
||||
c.OSType = constants.Target_Windows
|
||||
} else if c.OSType == "" {
|
||||
if c.OSType != constants.Target_Linux && c.OSType != constants.Target_Windows {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An os_type must be specified"))
|
||||
} else {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The os_type %q is invalid", c.OSType))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package arm
|
||||
|
||||
// Method to resolve information about the user so that a client can be
|
||||
// constructed to communicated with Azure.
|
||||
//
|
||||
// The following data are resolved.
|
||||
//
|
||||
// 1. TenantID
|
||||
|
||||
import (
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/mitchellh/packer/builder/azure/common"
|
||||
)
|
||||
|
||||
type configRetriever struct {
|
||||
// test seams
|
||||
findTenantID func(azure.Environment, string) (string, error)
|
||||
}
|
||||
|
||||
func newConfigRetriever() configRetriever {
|
||||
return configRetriever{
|
||||
common.FindTenantID,
|
||||
}
|
||||
}
|
||||
|
||||
func (cr configRetriever) FillParameters(c *Config) error {
|
||||
if c.TenantID == "" {
|
||||
tenantID, err := cr.findTenantID(*c.cloudEnvironment, c.SubscriptionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.TenantID = tenantID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
)
|
||||
|
||||
func TestConfigRetrieverFillsTenantIDWhenEmpty(t *testing.T) {
|
||||
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
if expected := ""; c.TenantID != expected {
|
||||
t.Errorf("Expected TenantID to be %q but got %q", expected, c.TenantID)
|
||||
}
|
||||
|
||||
sut := newTestConfigRetriever()
|
||||
retrievedTid := "my-tenant-id"
|
||||
sut.findTenantID = func(azure.Environment, string) (string, error) { return retrievedTid, nil }
|
||||
if err := sut.FillParameters(c); err != nil {
|
||||
t.Errorf("Unexpected error when calling sut.FillParameters: %v", err)
|
||||
}
|
||||
|
||||
if expected := retrievedTid; c.TenantID != expected {
|
||||
t.Errorf("Expected TenantID to be %q but got %q", expected, c.TenantID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigRetrieverLeavesTenantIDWhenNotEmpty(t *testing.T) {
|
||||
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
userSpecifiedTid := "not-empty"
|
||||
c.TenantID = userSpecifiedTid
|
||||
|
||||
sut := newTestConfigRetriever()
|
||||
sut.findTenantID = nil // assert that this not even called
|
||||
if err := sut.FillParameters(c); err != nil {
|
||||
t.Errorf("Unexpected error when calling sut.FillParameters: %v", err)
|
||||
}
|
||||
|
||||
if expected := userSpecifiedTid; c.TenantID != expected {
|
||||
t.Errorf("Expected TenantID to be %q but got %q", expected, c.TenantID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigRetrieverReturnsErrorWhenTenantIDEmptyAndRetrievalFails(t *testing.T) {
|
||||
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
if expected := ""; c.TenantID != expected {
|
||||
t.Errorf("Expected TenantID to be %q but got %q", expected, c.TenantID)
|
||||
}
|
||||
|
||||
sut := newTestConfigRetriever()
|
||||
errorString := "sorry, I failed"
|
||||
sut.findTenantID = func(azure.Environment, string) (string, error) { return "", errors.New(errorString) }
|
||||
if err := sut.FillParameters(c); err != nil && err.Error() != errorString {
|
||||
t.Errorf("Unexpected error when calling sut.FillParameters: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func newTestConfigRetriever() configRetriever {
|
||||
return configRetriever{
|
||||
findTenantID: func(azure.Environment, string) (string, error) { return "findTenantID is mocked", nil },
|
||||
}
|
||||
}
|
||||
+114
-479
@@ -4,6 +4,7 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -25,24 +26,24 @@ var requiredConfigValues = []string{
|
||||
"location",
|
||||
"os_type",
|
||||
"storage_account",
|
||||
"resource_group_name",
|
||||
"subscription_id",
|
||||
"tenant_id",
|
||||
}
|
||||
|
||||
func TestConfigShouldProvideReasonableDefaultValues(t *testing.T) {
|
||||
c, _, err := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
|
||||
if err != nil {
|
||||
t.Error("Expected configuration creation to succeed, but it failed!\n")
|
||||
t.Errorf("Expected configuration creation to succeed, but it failed!\n")
|
||||
t.Fatalf(" errors: %s\n", err)
|
||||
}
|
||||
|
||||
if c.UserName == "" {
|
||||
t.Error("Expected 'UserName' to be populated, but it was empty!")
|
||||
t.Errorf("Expected 'UserName' to be populated, but it was empty!")
|
||||
}
|
||||
|
||||
if c.VMSize == "" {
|
||||
t.Error("Expected 'VMSize' to be populated, but it was empty!")
|
||||
t.Errorf("Expected 'VMSize' to be populated, but it was empty!")
|
||||
}
|
||||
|
||||
if c.ObjectID != "" {
|
||||
@@ -55,7 +56,6 @@ func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) {
|
||||
builderValues["ssh_password"] = "override_password"
|
||||
builderValues["ssh_username"] = "override_username"
|
||||
builderValues["vm_size"] = "override_vm_size"
|
||||
builderValues["communicator"] = "ssh"
|
||||
|
||||
c, _, err := newConfig(builderValues, getPackerConfiguration())
|
||||
|
||||
@@ -100,162 +100,6 @@ func TestConfigShouldDefaultImageVersionToLatest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldNotDefaultImageVersionIfCustomImage(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"location": "ignore",
|
||||
"image_url": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
}
|
||||
|
||||
c, _, _ := newConfig(config, getPackerConfiguration())
|
||||
if c.ImageVersion != "" {
|
||||
t.Errorf("Expected 'ImageVersion' to empty, but got '%s'.", c.ImageVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldNormalizeOSTypeCase(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"location": "ignore",
|
||||
"image_url": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
}
|
||||
|
||||
os_types := map[string][]string{
|
||||
constants.Target_Linux: {"linux", "LiNuX"},
|
||||
constants.Target_Windows: {"windows", "WiNdOWs"},
|
||||
}
|
||||
|
||||
for k, v := range os_types {
|
||||
for _, os_type := range v {
|
||||
config["os_type"] = os_type
|
||||
c, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Fatalf("Expected config to accept the value %q, but it did not", os_type)
|
||||
}
|
||||
|
||||
if c.OSType != k {
|
||||
t.Fatalf("Expected config to normalize the value %q to %q, but it did not", os_type, constants.Target_Linux)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bad_os_types := []string{"", "does-not-exist"}
|
||||
for _, os_type := range bad_os_types {
|
||||
config["os_type"] = os_type
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Fatalf("Expected config to not accept the value %q, but it did", os_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectCustomImageAndMarketPlace(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"location": "ignore",
|
||||
"image_url": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
}
|
||||
packerConfiguration := getPackerConfiguration()
|
||||
marketPlace := []string{"image_publisher", "image_offer", "image_sku"}
|
||||
|
||||
for _, x := range marketPlace {
|
||||
config[x] = "ignore"
|
||||
_, _, err := newConfig(config, packerConfiguration)
|
||||
if err == nil {
|
||||
t.Errorf("Expected Config to reject image_url and %s, but it did not", x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigVirtualNetworkNameIsOptional(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"location": "ignore",
|
||||
"image_url": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
"virtual_network_name": "MyVirtualNetwork",
|
||||
}
|
||||
|
||||
c, _, _ := newConfig(config, getPackerConfiguration())
|
||||
if c.VirtualNetworkName != "MyVirtualNetwork" {
|
||||
t.Errorf("Expected Config to set virtual_network_name to MyVirtualNetwork, but got %q", c.VirtualNetworkName)
|
||||
}
|
||||
if c.VirtualNetworkResourceGroupName != "" {
|
||||
t.Errorf("Expected Config to leave virtual_network_resource_group_name to '', but got %q", c.VirtualNetworkResourceGroupName)
|
||||
}
|
||||
if c.VirtualNetworkSubnetName != "" {
|
||||
t.Errorf("Expected Config to leave virtual_network_subnet_name to '', but got %q", c.VirtualNetworkSubnetName)
|
||||
}
|
||||
}
|
||||
|
||||
// The user can pass the value virtual_network_resource_group_name to avoid the lookup of
|
||||
// a virtual network's resource group, or to help with disambiguation. The value should
|
||||
// only be set if virtual_network_name was set.
|
||||
func TestConfigVirtualNetworkResourceGroupNameMustBeSetWithVirtualNetworkName(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"location": "ignore",
|
||||
"image_url": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
"virtual_network_resource_group_name": "MyVirtualNetworkRG",
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Error("Expected Config to reject virtual_network_resource_group_name, if virtual_network_name is not set.")
|
||||
}
|
||||
}
|
||||
|
||||
// The user can pass the value virtual_network_subnet_name to avoid the lookup of
|
||||
// a virtual network subnet's name, or to help with disambiguation. The value should
|
||||
// only be set if virtual_network_name was set.
|
||||
func TestConfigVirtualNetworkSubnetNameMustBeSetWithVirtualNetworkName(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"location": "ignore",
|
||||
"image_url": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
"virtual_network_subnet_name": "MyVirtualNetworkRG",
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Error("Expected Config to reject virtual_network_subnet_name, if virtual_network_name is not set.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldDefaultToPublicCloud(t *testing.T) {
|
||||
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
|
||||
@@ -277,10 +121,8 @@ func TestConfigInstantiatesCorrectAzureEnvironment(t *testing.T) {
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
}
|
||||
|
||||
// user input is fun :)
|
||||
@@ -308,10 +150,7 @@ func TestConfigInstantiatesCorrectAzureEnvironment(t *testing.T) {
|
||||
|
||||
for _, x := range table {
|
||||
config["cloud_environment_name"] = x.name
|
||||
c, _, err := newConfig(config, packerConfiguration)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, _, _ := newConfig(config, packerConfiguration)
|
||||
|
||||
if c.cloudEnvironment == nil || c.cloudEnvironment.Name != x.environmentName {
|
||||
t.Errorf("Expected 'cloudEnvironment' to be set to '%s', but got '%s'.", x.environmentName, c.cloudEnvironment)
|
||||
@@ -325,7 +164,7 @@ func TestUserShouldProvideRequiredValues(t *testing.T) {
|
||||
// Ensure we can successfully create a config.
|
||||
_, _, err := newConfig(builderValues, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Error("Expected configuration creation to succeed, but it failed!\n")
|
||||
t.Errorf("Expected configuration creation to succeed, but it failed!\n")
|
||||
t.Fatalf(" -> %+v\n", builderValues)
|
||||
}
|
||||
|
||||
@@ -336,7 +175,7 @@ func TestUserShouldProvideRequiredValues(t *testing.T) {
|
||||
|
||||
_, _, err := newConfig(builderValues, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Error("Expected configuration creation to fail, but it succeeded!\n")
|
||||
t.Errorf("Expected configuration creation to fail, but it succeeded!\n")
|
||||
t.Fatalf(" -> %+v\n", builderValues)
|
||||
}
|
||||
|
||||
@@ -368,6 +207,91 @@ func TestSystemShouldDefineRuntimeValues(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldTransformToTemplateParameters(t *testing.T) {
|
||||
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
templateParameters := c.toTemplateParameters()
|
||||
|
||||
if templateParameters.AdminUsername.Value != c.UserName {
|
||||
t.Errorf("Expected AdminUsername to be equal to config's AdminUsername, but they were '%s' and '%s' respectively.", templateParameters.AdminUsername.Value, c.UserName)
|
||||
}
|
||||
|
||||
if templateParameters.DnsNameForPublicIP.Value != c.tmpComputeName {
|
||||
t.Errorf("Expected DnsNameForPublicIP to be equal to config's DnsNameForPublicIP, but they were '%s' and '%s' respectively.", templateParameters.DnsNameForPublicIP.Value, c.tmpComputeName)
|
||||
}
|
||||
|
||||
if templateParameters.ImageOffer.Value != c.ImageOffer {
|
||||
t.Errorf("Expected ImageOffer to be equal to config's ImageOffer, but they were '%s' and '%s' respectively.", templateParameters.ImageOffer.Value, c.ImageOffer)
|
||||
}
|
||||
|
||||
if templateParameters.ImagePublisher.Value != c.ImagePublisher {
|
||||
t.Errorf("Expected ImagePublisher to be equal to config's ImagePublisher, but they were '%s' and '%s' respectively.", templateParameters.ImagePublisher.Value, c.ImagePublisher)
|
||||
}
|
||||
|
||||
if templateParameters.ImageSku.Value != c.ImageSku {
|
||||
t.Errorf("Expected ImageSku to be equal to config's ImageSku, but they were '%s' and '%s' respectively.", templateParameters.ImageSku.Value, c.ImageSku)
|
||||
}
|
||||
|
||||
if templateParameters.OSDiskName.Value != c.tmpOSDiskName {
|
||||
t.Errorf("Expected OSDiskName to be equal to config's OSDiskName, but they were '%s' and '%s' respectively.", templateParameters.OSDiskName.Value, c.tmpOSDiskName)
|
||||
}
|
||||
|
||||
if templateParameters.StorageAccountBlobEndpoint.Value != c.storageAccountBlobEndpoint {
|
||||
t.Errorf("Expected StorageAccountBlobEndpoint to be equal to config's storageAccountBlobEndpoint, but they were '%s' and '%s' respectively.", templateParameters.StorageAccountBlobEndpoint.Value, c.storageAccountBlobEndpoint)
|
||||
}
|
||||
|
||||
if templateParameters.VMName.Value != c.tmpComputeName {
|
||||
t.Errorf("Expected VMName to be equal to config's VMName, but they were '%s' and '%s' respectively.", templateParameters.VMName.Value, c.tmpComputeName)
|
||||
}
|
||||
|
||||
if templateParameters.VMSize.Value != c.VMSize {
|
||||
t.Errorf("Expected VMSize to be equal to config's VMSize, but they were '%s' and '%s' respectively.", templateParameters.VMSize.Value, c.VMSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldTransformToTemplateParametersLinux(t *testing.T) {
|
||||
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
c.OSType = constants.Target_Linux
|
||||
templateParameters := c.toTemplateParameters()
|
||||
|
||||
if templateParameters.KeyVaultSecretValue != nil {
|
||||
t.Errorf("Expected KeyVaultSecretValue to be empty for an os_type == '%s', but it was not.", c.OSType)
|
||||
}
|
||||
|
||||
if templateParameters.ObjectId != nil {
|
||||
t.Errorf("Expected ObjectId to be empty for an os_type == '%s', but it was not.", c.OSType)
|
||||
}
|
||||
|
||||
if templateParameters.TenantId != nil {
|
||||
t.Errorf("Expected TenantId to be empty for an os_type == '%s', but it was not.", c.OSType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldTransformToTemplateParametersWindows(t *testing.T) {
|
||||
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
c.OSType = constants.Target_Windows
|
||||
templateParameters := c.toTemplateParameters()
|
||||
|
||||
if templateParameters.SshAuthorizedKey != nil {
|
||||
t.Errorf("Expected SshAuthorizedKey to be empty for an os_type == '%s', but it was not.", c.OSType)
|
||||
}
|
||||
|
||||
if templateParameters.KeyVaultName == nil {
|
||||
t.Errorf("Expected KeyVaultName to not be empty for an os_type == '%s', but it was not.", c.OSType)
|
||||
}
|
||||
|
||||
if templateParameters.KeyVaultSecretValue == nil {
|
||||
t.Errorf("Expected KeyVaultSecretValue to not be empty for an os_type == '%s', but it was not.", c.OSType)
|
||||
}
|
||||
|
||||
if templateParameters.ObjectId == nil {
|
||||
t.Errorf("Expected ObjectId to not be empty for an os_type == '%s', but it was not.", c.OSType)
|
||||
}
|
||||
|
||||
if templateParameters.TenantId == nil {
|
||||
t.Errorf("Expected TenantId to not be empty for an os_type == '%s', but it was not.", c.OSType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldTransformToVirtualMachineCaptureParameters(t *testing.T) {
|
||||
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
parameters := c.toVirtualMachineCaptureParameters()
|
||||
@@ -404,22 +328,6 @@ func TestConfigShouldSupportPackersConfigElements(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWinRMConfigShouldSetRoundTripDecorator(t *testing.T) {
|
||||
config := getArmBuilderConfiguration()
|
||||
config["communicator"] = "winrm"
|
||||
config["winrm_username"] = "username"
|
||||
config["winrm_password"] = "password"
|
||||
|
||||
c, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.Comm.WinRMTransportDecorator == nil {
|
||||
t.Error("Expected WinRMTransportDecorator to be set, but it was nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserDeviceLoginIsEnabledForLinux(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"capture_name_prefix": "ignore",
|
||||
@@ -429,10 +337,8 @@ func TestUserDeviceLoginIsEnabledForLinux(t *testing.T) {
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
@@ -450,326 +356,55 @@ func TestUseDeviceLoginIsDisabledForWindows(t *testing.T) {
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Windows,
|
||||
"communicator": "none",
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Fatal("Expected test to fail, but it succeeded")
|
||||
t.Fatalf("Expected test to fail, but it succeeded")
|
||||
}
|
||||
|
||||
multiError, _ := err.(*packer.MultiError)
|
||||
if len(multiError.Errors) != 2 {
|
||||
t.Errorf("Expected to find 2 errors, but found %d errors", len(multiError.Errors))
|
||||
if len(multiError.Errors) != 3 {
|
||||
t.Errorf("Expected to find 3 errors, but found %d errors", len(multiError.Errors))
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "client_id must be specified") {
|
||||
t.Error("Expected to find error for 'client_id must be specified")
|
||||
t.Errorf("Expected to find error for 'client_id must be specified")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "client_secret must be specified") {
|
||||
t.Error("Expected to find error for 'client_secret must be specified")
|
||||
t.Errorf("Expected to find error for 'client_secret must be specified")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectMalformedCaptureNamePrefix(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"capture_container_name": "ignore",
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
}
|
||||
|
||||
wellFormedCaptureNamePrefix := []string{
|
||||
"packer",
|
||||
"AbcdefghijklmnopqrstuvwX",
|
||||
"hypen-hypen",
|
||||
"0leading-number",
|
||||
"v1.core.local",
|
||||
}
|
||||
|
||||
for _, x := range wellFormedCaptureNamePrefix {
|
||||
config["capture_name_prefix"] = x
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected test to pass, but it failed with the well-formed capture_name_prefix set to %q.", x)
|
||||
}
|
||||
}
|
||||
|
||||
malformedCaptureNamePrefix := []string{
|
||||
"-leading-hypen",
|
||||
"trailing-hypen-",
|
||||
"trailing-period.",
|
||||
"_leading-underscore",
|
||||
"punc-!@#$%^&*()_+-=-punc",
|
||||
"There-are-too-many-characters-in-the-name-and-the-limit-is-twenty-four",
|
||||
}
|
||||
|
||||
for _, x := range malformedCaptureNamePrefix {
|
||||
config["capture_name_prefix"] = x
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected test to fail, but it succeeded with the malformed capture_name_prefix set to %q.", x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectMalformedCaptureContainerName(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"capture_name_prefix": "ignore",
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
}
|
||||
|
||||
wellFormedCaptureContainerName := []string{
|
||||
"0leading",
|
||||
"aleading",
|
||||
"hype-hypen",
|
||||
"abcdefghijklmnopqrstuvwxyz0123456789-abcdefghijklmnopqrstuvwxyz", // 63 characters
|
||||
}
|
||||
|
||||
for _, x := range wellFormedCaptureContainerName {
|
||||
config["capture_container_name"] = x
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected test to pass, but it failed with the well-formed capture_container_name set to %q.", x)
|
||||
}
|
||||
}
|
||||
|
||||
malformedCaptureContainerName := []string{
|
||||
"No-Capitals",
|
||||
"double--hypens",
|
||||
"-leading-hypen",
|
||||
"trailing-hypen-",
|
||||
"punc-!@#$%^&*()_+-=-punc",
|
||||
"there-are-over-63-characters-in-this-string-and-that-is-a-bad-container-name",
|
||||
}
|
||||
|
||||
for _, x := range malformedCaptureContainerName {
|
||||
config["capture_container_name"] = x
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected test to fail, but it succeeded with the malformed capture_container_name set to %q.", x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldAcceptTags(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
"azure_tags": map[string]string{
|
||||
"tag01": "value01",
|
||||
"tag02": "value02",
|
||||
},
|
||||
}
|
||||
|
||||
c, _, err := newConfig(config, getPackerConfiguration())
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(c.AzureTags) != 2 {
|
||||
t.Fatalf("expected to find 2 tags, but got %d", len(c.AzureTags))
|
||||
}
|
||||
|
||||
if _, ok := c.AzureTags["tag01"]; !ok {
|
||||
t.Error("expected to find key=\"tag01\", but did not")
|
||||
}
|
||||
if _, ok := c.AzureTags["tag02"]; !ok {
|
||||
t.Error("expected to find key=\"tag02\", but did not")
|
||||
}
|
||||
|
||||
value := c.AzureTags["tag01"]
|
||||
if *value != "value01" {
|
||||
t.Errorf("expected AzureTags[\"tag01\"] to have value \"value01\", but got %q", value)
|
||||
}
|
||||
|
||||
value = c.AzureTags["tag02"]
|
||||
if *value != "value02" {
|
||||
t.Errorf("expected AzureTags[\"tag02\"] to have value \"value02\", but got %q", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectTagsInExcessOf15AcceptTags(t *testing.T) {
|
||||
tooManyTags := map[string]string{}
|
||||
for i := 0; i < 16; i++ {
|
||||
tooManyTags[fmt.Sprintf("tag%.2d", i)] = "ignored"
|
||||
}
|
||||
|
||||
config := map[string]interface{}{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
"azure_tags": tooManyTags,
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected config to reject based on an excessive amount of tags (> 15)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectExcessiveTagNameLength(t *testing.T) {
|
||||
nameTooLong := make([]byte, 513)
|
||||
for i := range nameTooLong {
|
||||
nameTooLong[i] = 'a'
|
||||
}
|
||||
|
||||
tags := map[string]string{}
|
||||
tags[string(nameTooLong)] = "ignored"
|
||||
|
||||
config := map[string]interface{}{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
"azure_tags": tags,
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Fatal("expected config to reject tag name based on length (> 512)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectExcessiveTagValueLength(t *testing.T) {
|
||||
valueTooLong := make([]byte, 257)
|
||||
for i := range valueTooLong {
|
||||
valueTooLong[i] = 'a'
|
||||
}
|
||||
|
||||
tags := map[string]string{}
|
||||
tags["tag01"] = string(valueTooLong)
|
||||
|
||||
config := map[string]interface{}{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
"azure_tags": tags,
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Fatal("expected config to reject tag value based on length (> 256)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectMissingCustomDataFile(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"resource_group_name": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
// Does not matter for this test case, just pick one.
|
||||
"os_type": constants.Target_Linux,
|
||||
"custom_data_file": "/this/file/does/not/exist",
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Fatal("expected config to reject missing custom data file")
|
||||
if !strings.Contains(err.Error(), "tenant_id must be specified") {
|
||||
t.Errorf("Expected to find error for 'tenant_id must be specified")
|
||||
}
|
||||
}
|
||||
|
||||
func getArmBuilderConfiguration() map[string]string {
|
||||
m := make(map[string]string)
|
||||
for _, v := range requiredConfigValues {
|
||||
m[v] = "ignored00"
|
||||
m[v] = fmt.Sprintf("%s00", v)
|
||||
}
|
||||
|
||||
m["communicator"] = "none"
|
||||
m["os_type"] = constants.Target_Linux
|
||||
return m
|
||||
}
|
||||
|
||||
func getArmBuilderConfigurationWithWindows() map[string]string {
|
||||
m := make(map[string]string)
|
||||
for _, v := range requiredConfigValues {
|
||||
m[v] = "ignored00"
|
||||
}
|
||||
|
||||
m["object_id"] = "ignored00"
|
||||
m["tenant_id"] = "ignored00"
|
||||
m["winrm_username"] = "ignored00"
|
||||
m["communicator"] = "winrm"
|
||||
m["os_type"] = constants.Target_Windows
|
||||
return m
|
||||
}
|
||||
|
||||
func getPackerConfiguration() interface{} {
|
||||
config := map[string]interface{}{
|
||||
"packer_build_name": "azure-arm-vm",
|
||||
"packer_builder_type": "azure-arm-vm",
|
||||
"packer_debug": "false",
|
||||
"packer_force": "false",
|
||||
"packer_template_path": "/home/jenkins/azure-arm-vm/template.json",
|
||||
}
|
||||
var doc = `{
|
||||
"packer_user_variables": {
|
||||
"sa": "my_storage_account"
|
||||
},
|
||||
"packer_build_name": "azure-arm-vm",
|
||||
"packer_builder_type": "azure-arm-vm",
|
||||
"packer_debug": "false",
|
||||
"packer_force": "false",
|
||||
"packer_template_path": "/home/jenkins/azure-arm-vm/template.json"
|
||||
}`
|
||||
|
||||
var config interface{}
|
||||
json.Unmarshal([]byte(doc), &config)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
|
||||
|
||||
package arm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
|
||||
)
|
||||
|
||||
type DeploymentFactory struct {
|
||||
template string
|
||||
}
|
||||
|
||||
func newDeploymentFactory(template string) DeploymentFactory {
|
||||
return DeploymentFactory{
|
||||
template: template,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *DeploymentFactory) create(templateParameters TemplateParameters) (*resources.Deployment, error) {
|
||||
template, err := f.getTemplate(templateParameters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parameters, err := f.getTemplateParameters(templateParameters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resources.Deployment{
|
||||
Properties: &resources.DeploymentProperties{
|
||||
Mode: resources.Incremental,
|
||||
Template: template,
|
||||
Parameters: parameters,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *DeploymentFactory) getTemplate(templateParameters TemplateParameters) (*map[string]interface{}, error) {
|
||||
var t map[string]interface{}
|
||||
err := json.Unmarshal([]byte(f.template), &t)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func (f *DeploymentFactory) getTemplateParameters(templateParameters TemplateParameters) (*map[string]interface{}, error) {
|
||||
b, err := json.Marshal(templateParameters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var t map[string]interface{}
|
||||
err = json.Unmarshal(b, &t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &t, nil
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
|
||||
|
||||
package arm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeploymentFactoryShouldBeIncremental(t *testing.T) {
|
||||
var testSubject = newDeploymentFactory(Linux)
|
||||
|
||||
deployment, err := testSubject.create(getTemplateParameters())
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s\n", err)
|
||||
}
|
||||
|
||||
if deployment.Properties.Mode != "Incremental" {
|
||||
t.Fatalf("Expected the mode to be 'Incremental', but got '%s'.", deployment.Properties.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
// Either {Template,Parameter} are set or {Template,Parameter}Link values are
|
||||
// set, but never both.
|
||||
func TestDeploymentFactoryShouldNotSetLinks(t *testing.T) {
|
||||
testSubject := newDeploymentFactory(Linux)
|
||||
|
||||
deployment, err := testSubject.create(getTemplateParameters())
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s\n", err)
|
||||
}
|
||||
|
||||
if deployment.Properties.ParametersLink != nil {
|
||||
t.Fatalf("Expected the ParametersLink to be nil!")
|
||||
}
|
||||
|
||||
if deployment.Properties.TemplateLink != nil {
|
||||
t.Fatalf("Expected the TemplateLink to be nil!")
|
||||
}
|
||||
|
||||
if deployment.Properties.Parameters == nil {
|
||||
t.Fatalf("Expected the Parameters to not be nil!")
|
||||
}
|
||||
|
||||
if deployment.Properties.Template == nil {
|
||||
t.Fatalf("Expected the Template to not be nil!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFactoryShouldCreateDeploymentInstance(t *testing.T) {
|
||||
testSubject := newDeploymentFactory(Linux)
|
||||
|
||||
deployment, err := testSubject.create(getTemplateParameters())
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s\n", err)
|
||||
}
|
||||
|
||||
// spot check well known values to ensure correct serialization.
|
||||
|
||||
parametersMap := *deployment.Properties.Parameters
|
||||
if _, ok := parametersMap["adminUsername"]; ok == false {
|
||||
t.Fatalf("Expected the parameter value 'adminUsername' to be set, but it was not")
|
||||
}
|
||||
|
||||
templateMap := *deployment.Properties.Template
|
||||
if _, ok := templateMap["contentVersion"]; ok == false {
|
||||
t.Fatalf("Expected the parameter value 'contentVersion' to be set, but it was not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMalformedTemplatesShouldReturnError(t *testing.T) {
|
||||
testSubject := newDeploymentFactory("")
|
||||
|
||||
_, err := testSubject.create(getTemplateParameters())
|
||||
if err == nil {
|
||||
t.Fatalf("Expected an error, but did not receive one!\n")
|
||||
}
|
||||
}
|
||||
|
||||
func getTemplateParameters() TemplateParameters {
|
||||
templateParameters := TemplateParameters{
|
||||
AdminUsername: &TemplateParameter{"adminusername00"},
|
||||
DnsNameForPublicIP: &TemplateParameter{"dnsnameforpublicip00"},
|
||||
OSDiskName: &TemplateParameter{"osdiskname00"},
|
||||
SshAuthorizedKey: &TemplateParameter{"sshkeydata00"},
|
||||
StorageAccountBlobEndpoint: &TemplateParameter{"storageaccountblobendpoint00"},
|
||||
VMName: &TemplateParameter{"vmname00"},
|
||||
VMSize: &TemplateParameter{"vmsize00"},
|
||||
}
|
||||
|
||||
return templateParameters
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
|
||||
|
||||
package arm
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
DeployCanceled = "Canceled"
|
||||
DeployFailed = "Failed"
|
||||
DeployDeleted = "Deleted"
|
||||
DeploySucceeded = "Succeeded"
|
||||
)
|
||||
|
||||
type DeploymentPoller struct {
|
||||
getProvisioningState func() (string, error)
|
||||
pause func()
|
||||
}
|
||||
|
||||
func NewDeploymentPoller(getProvisioningState func() (string, error)) *DeploymentPoller {
|
||||
pollDuration := time.Second * 15
|
||||
|
||||
return &DeploymentPoller{
|
||||
getProvisioningState: getProvisioningState,
|
||||
pause: func() { time.Sleep(pollDuration) },
|
||||
}
|
||||
}
|
||||
|
||||
func (t *DeploymentPoller) PollAsNeeded() (string, error) {
|
||||
for {
|
||||
res, err := t.getProvisioningState()
|
||||
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
switch res {
|
||||
case DeployCanceled, DeployDeleted, DeployFailed, DeploySucceeded:
|
||||
return res, nil
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
t.pause()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
|
||||
|
||||
package arm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCanceledShouldImmediatelyStopPolling(t *testing.T) {
|
||||
var testSubject = NewDeploymentPoller(func() (string, error) { return "Canceled", nil })
|
||||
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
|
||||
|
||||
res, err := testSubject.PollAsNeeded()
|
||||
if err != nil {
|
||||
t.Errorf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
|
||||
}
|
||||
|
||||
if res != "Canceled" {
|
||||
t.Fatalf("Expected PollAsNeeded to return a result of 'Canceled', but got '%s' instead.", res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailedShouldImmediatelyStopPolling(t *testing.T) {
|
||||
var testSubject = NewDeploymentPoller(func() (string, error) { return "Failed", nil })
|
||||
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
|
||||
|
||||
res, err := testSubject.PollAsNeeded()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
|
||||
}
|
||||
|
||||
if res != "Failed" {
|
||||
t.Fatalf("Expected PollAsNeeded to return a result of 'Failed', but got '%s' instead.", res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeletedShouldImmediatelyStopPolling(t *testing.T) {
|
||||
var testSubject = NewDeploymentPoller(func() (string, error) { return "Deleted", nil })
|
||||
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
|
||||
|
||||
res, err := testSubject.PollAsNeeded()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
|
||||
}
|
||||
|
||||
if res != "Deleted" {
|
||||
t.Fatalf("Expected PollAsNeeded to return a result of 'Deleted', but got '%s' instead.", res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSucceededShouldImmediatelyStopPolling(t *testing.T) {
|
||||
var testSubject = NewDeploymentPoller(func() (string, error) { return "Succeeded", nil })
|
||||
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
|
||||
|
||||
res, err := testSubject.PollAsNeeded()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
|
||||
}
|
||||
|
||||
if res != "Succeeded" {
|
||||
t.Fatalf("Expected PollAsNeeded to return a result of 'Succeeded', but got '%s' instead.", res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollerShouldPollOnNonStoppingStatus(t *testing.T) {
|
||||
count := 0
|
||||
|
||||
var testSubject = NewDeploymentPoller(func() (string, error) { return "Succeeded", nil })
|
||||
testSubject.pause = func() { count += 1 }
|
||||
testSubject.getProvisioningState = func() (string, error) {
|
||||
count += 1
|
||||
switch count {
|
||||
case 0, 1:
|
||||
return "Working", nil
|
||||
default:
|
||||
return "Succeeded", nil
|
||||
}
|
||||
}
|
||||
|
||||
res, err := testSubject.PollAsNeeded()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
|
||||
}
|
||||
|
||||
if res != "Succeeded" {
|
||||
t.Fatalf("Expected PollAsNeeded to return a result of 'Succeeded', but got '%s' instead.", res)
|
||||
}
|
||||
|
||||
if count != 3 {
|
||||
t.Fatal("Expected DeploymentPoller to poll until 'Succeeded', but it did not.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollerShouldReturnErrorImmediately(t *testing.T) {
|
||||
var testSubject = NewDeploymentPoller(func() (string, error) { return "bad-bad-bad", fmt.Errorf("BOOM") })
|
||||
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
|
||||
|
||||
res, err := testSubject.PollAsNeeded()
|
||||
if err == nil {
|
||||
t.Fatal("Expected PollAsNeeded to return an error, but it did not.")
|
||||
}
|
||||
|
||||
if res != "bad-bad-bad" {
|
||||
t.Fatalf("Expected PollAsNeeded to return a result of 'bad-bad-bad', but got '%s' instead.", res)
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package arm
|
||||
|
||||
// Code to resolve resources that are required by the API. These resources
|
||||
// can most likely be resolved without asking the user, thereby reducing the
|
||||
// amount of configuration they need to provide.
|
||||
//
|
||||
// Resource resolver differs from config retriever because resource resolver
|
||||
// requires a client to communicate with the Azure API. A config retriever is
|
||||
// used to determine values without use of a client.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type resourceResolver struct {
|
||||
client *AzureClient
|
||||
findVirtualNetworkResourceGroup func(*AzureClient, string) (string, error)
|
||||
findVirtualNetworkSubnet func(*AzureClient, string, string) (string, error)
|
||||
}
|
||||
|
||||
func newResourceResolver(client *AzureClient) *resourceResolver {
|
||||
return &resourceResolver{
|
||||
client: client,
|
||||
findVirtualNetworkResourceGroup: findVirtualNetworkResourceGroup,
|
||||
findVirtualNetworkSubnet: findVirtualNetworkSubnet,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *resourceResolver) Resolve(c *Config) error {
|
||||
if s.shouldResolveResourceGroup(c) {
|
||||
resourceGroupName, err := s.findVirtualNetworkResourceGroup(s.client, c.VirtualNetworkName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subnetName, err := s.findVirtualNetworkSubnet(s.client, resourceGroupName, c.VirtualNetworkName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.VirtualNetworkResourceGroupName = resourceGroupName
|
||||
c.VirtualNetworkSubnetName = subnetName
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *resourceResolver) shouldResolveResourceGroup(c *Config) bool {
|
||||
return c.VirtualNetworkName != "" && c.VirtualNetworkResourceGroupName == ""
|
||||
}
|
||||
|
||||
func getResourceGroupNameFromId(id string) string {
|
||||
// "/subscriptions/3f499422-dd76-4114-8859-86d526c9deb6/resourceGroups/packer-Resource-Group-yylnwsl30j/providers/...
|
||||
xs := strings.Split(id, "/")
|
||||
return xs[4]
|
||||
}
|
||||
|
||||
func findVirtualNetworkResourceGroup(client *AzureClient, name string) (string, error) {
|
||||
virtualNetworks, err := client.VirtualNetworksClient.ListAll()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resourceGroupNames := make([]string, 0)
|
||||
|
||||
for _, virtualNetwork := range *virtualNetworks.Value {
|
||||
if strings.EqualFold(name, *virtualNetwork.Name) {
|
||||
rgn := getResourceGroupNameFromId(*virtualNetwork.ID)
|
||||
resourceGroupNames = append(resourceGroupNames, rgn)
|
||||
}
|
||||
}
|
||||
|
||||
if len(resourceGroupNames) == 0 {
|
||||
return "", fmt.Errorf("Cannot find a resource group with a virtual network called %q", name)
|
||||
}
|
||||
|
||||
if len(resourceGroupNames) > 1 {
|
||||
return "", fmt.Errorf("Found multiple resource groups with a virtual network called %q, please use virtual_network_subnet_name and virtual_network_resource_group_name to disambiguate", name)
|
||||
}
|
||||
|
||||
return resourceGroupNames[0], nil
|
||||
}
|
||||
|
||||
func findVirtualNetworkSubnet(client *AzureClient, resourceGroupName string, name string) (string, error) {
|
||||
subnets, err := client.SubnetsClient.List(resourceGroupName, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(*subnets.Value) == 0 {
|
||||
return "", fmt.Errorf("Cannot find a subnet in the resource group %q associated with the virtual network called %q", resourceGroupName, name)
|
||||
}
|
||||
|
||||
if len(*subnets.Value) > 1 {
|
||||
return "", fmt.Errorf("Found multiple subnets in the resource group %q associated with the virtual network called %q, please use virtual_network_subnet_name and virtual_network_resource_group_name to disambiguate", resourceGroupName, name)
|
||||
}
|
||||
|
||||
subnet := (*subnets.Value)[0]
|
||||
return *subnet.Name, nil
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResourceResolverIgnoresEmptyVirtualNetworkName(t *testing.T) {
|
||||
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
if c.VirtualNetworkName != "" {
|
||||
t.Fatalf("Expected VirtualNetworkName to be empty by default")
|
||||
}
|
||||
|
||||
sut := newTestResourceResolver()
|
||||
sut.findVirtualNetworkResourceGroup = nil // assert that this is not even called
|
||||
sut.Resolve(c)
|
||||
|
||||
if c.VirtualNetworkName != "" {
|
||||
t.Fatalf("Expected VirtualNetworkName to be empty")
|
||||
}
|
||||
if c.VirtualNetworkResourceGroupName != "" {
|
||||
t.Fatalf("Expected VirtualNetworkResourceGroupName to be empty")
|
||||
}
|
||||
}
|
||||
|
||||
// If the user fully specified the virtual network name and resource group then
|
||||
// there is no need to do a lookup.
|
||||
func TestResourceResolverIgnoresSetVirtualNetwork(t *testing.T) {
|
||||
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
c.VirtualNetworkName = "--virtual-network-name--"
|
||||
c.VirtualNetworkResourceGroupName = "--virtual-network-resource-group-name--"
|
||||
c.VirtualNetworkSubnetName = "--virtual-network-subnet-name--"
|
||||
|
||||
sut := newTestResourceResolver()
|
||||
sut.findVirtualNetworkResourceGroup = nil // assert that this is not even called
|
||||
sut.findVirtualNetworkSubnet = nil // assert that this is not even called
|
||||
sut.Resolve(c)
|
||||
|
||||
if c.VirtualNetworkName != "--virtual-network-name--" {
|
||||
t.Fatalf("Expected VirtualNetworkName to be --virtual-network-name--")
|
||||
}
|
||||
if c.VirtualNetworkResourceGroupName != "--virtual-network-resource-group-name--" {
|
||||
t.Fatalf("Expected VirtualNetworkResourceGroupName to be --virtual-network-resource-group-name--")
|
||||
}
|
||||
if c.VirtualNetworkSubnetName != "--virtual-network-subnet-name--" {
|
||||
t.Fatalf("Expected VirtualNetworkSubnetName to be --virtual-network-subnet-name--")
|
||||
}
|
||||
}
|
||||
|
||||
// If the user set virtual network name then the code should resolve virtual network
|
||||
// resource group name.
|
||||
func TestResourceResolverSetVirtualNetworkResourceGroupName(t *testing.T) {
|
||||
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
c.VirtualNetworkName = "--virtual-network-name--"
|
||||
|
||||
sut := newTestResourceResolver()
|
||||
sut.Resolve(c)
|
||||
|
||||
if c.VirtualNetworkResourceGroupName != "findVirtualNetworkResourceGroup is mocked" {
|
||||
t.Fatalf("Expected VirtualNetworkResourceGroupName to be 'findVirtualNetworkResourceGroup is mocked'")
|
||||
}
|
||||
if c.VirtualNetworkSubnetName != "findVirtualNetworkSubnet is mocked" {
|
||||
t.Fatalf("Expected findVirtualNetworkSubnet to be 'findVirtualNetworkSubnet is mocked'")
|
||||
}
|
||||
}
|
||||
|
||||
func newTestResourceResolver() resourceResolver {
|
||||
return resourceResolver{
|
||||
client: nil,
|
||||
findVirtualNetworkResourceGroup: func(*AzureClient, string) (string, error) {
|
||||
return "findVirtualNetworkResourceGroup is mocked", nil
|
||||
},
|
||||
findVirtualNetworkSubnet: func(*AzureClient, string, string) (string, error) {
|
||||
return "findVirtualNetworkSubnet is mocked", nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func processInterruptibleResult(
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func TestStepCaptureImageShouldFailIfCaptureFails(t *testing.T) {
|
||||
@@ -97,19 +97,19 @@ func TestStepCaptureImageShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var expectedCaptureTemplate = stateBag.Get(constants.ArmCaptureTemplate).(*CaptureTemplate)
|
||||
|
||||
if actualComputeName != expectedComputeName {
|
||||
t.Fatal("Expected StepCaptureImage to source 'constants.ArmComputeName' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected StepCaptureImage to source 'constants.ArmComputeName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected StepCaptureImage to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected StepCaptureImage to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualVirtualMachineCaptureParameters != expectedVirtualMachineCaptureParameters {
|
||||
t.Fatal("Expected StepCaptureImage to source 'constants.ArmVirtualMachineCaptureParameters' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected StepCaptureImage to source 'constants.ArmVirtualMachineCaptureParameters' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualCaptureTemplate != expectedCaptureTemplate {
|
||||
t.Fatal("Expected StepCaptureImage to source 'constants.ArmCaptureTemplate' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected StepCaptureImage to source 'constants.ArmCaptureTemplate' from the state bag, but it did not.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,14 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepCreateResourceGroup struct {
|
||||
client *AzureClient
|
||||
create func(resourceGroupName string, location string, tags *map[string]*string) error
|
||||
create func(resourceGroupName string, location string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
@@ -30,10 +30,9 @@ func NewStepCreateResourceGroup(client *AzureClient, ui packer.Ui) *StepCreateRe
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepCreateResourceGroup) createResourceGroup(resourceGroupName string, location string, tags *map[string]*string) error {
|
||||
func (s *StepCreateResourceGroup) createResourceGroup(resourceGroupName string, location string) error {
|
||||
_, err := s.client.GroupsClient.CreateOrUpdate(resourceGroupName, resources.ResourceGroup{
|
||||
Location: &location,
|
||||
Tags: tags,
|
||||
})
|
||||
|
||||
return err
|
||||
@@ -44,16 +43,11 @@ func (s *StepCreateResourceGroup) Run(state multistep.StateBag) multistep.StepAc
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var location = state.Get(constants.ArmLocation).(string)
|
||||
var tags = state.Get(constants.ArmTags).(*map[string]*string)
|
||||
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> Location : '%s'", location))
|
||||
s.say(fmt.Sprintf(" -> Tags :"))
|
||||
for k, v := range *tags {
|
||||
s.say(fmt.Sprintf(" ->> %s : %s", k, *v))
|
||||
}
|
||||
|
||||
err := s.create(resourceGroupName, location, tags)
|
||||
err := s.create(resourceGroupName, location)
|
||||
if err == nil {
|
||||
state.Put(constants.ArmIsResourceGroupCreated, true)
|
||||
}
|
||||
@@ -62,8 +56,8 @@ func (s *StepCreateResourceGroup) Run(state multistep.StateBag) multistep.StepAc
|
||||
}
|
||||
|
||||
func (s *StepCreateResourceGroup) Cleanup(state multistep.StateBag) {
|
||||
isCreated, ok := state.GetOk(constants.ArmIsResourceGroupCreated)
|
||||
if !ok || !isCreated.(bool) {
|
||||
_, ok := state.GetOk(constants.ArmIsResourceGroupCreated)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func TestStepCreateResourceGroupShouldFailIfCreateFails(t *testing.T) {
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(string, string, *map[string]*string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
create: func(string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func TestStepCreateResourceGroupShouldFailIfCreateFails(t *testing.T) {
|
||||
|
||||
func TestStepCreateResourceGroupShouldPassIfCreatePasses(t *testing.T) {
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(string, string, *map[string]*string) error { return nil },
|
||||
create: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
@@ -52,13 +52,11 @@ func TestStepCreateResourceGroupShouldPassIfCreatePasses(t *testing.T) {
|
||||
func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualResourceGroupName string
|
||||
var actualLocation string
|
||||
var actualTags *map[string]*string
|
||||
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(resourceGroupName string, location string, tags *map[string]*string) error {
|
||||
create: func(resourceGroupName string, location string) error {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualLocation = location
|
||||
actualTags = tags
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
@@ -72,25 +70,20 @@ func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
var expectedLocation = stateBag.Get(constants.ArmLocation).(string)
|
||||
var expectedTags = stateBag.Get(constants.ArmTags).(*map[string]*string)
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualLocation != expectedLocation {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if len(*expectedTags) != len(*actualTags) && *(*expectedTags)["tag01"] != *(*actualTags)["tag01"] {
|
||||
t.Fatal("Expected the step to source 'constants.ArmTags' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
_, ok := stateBag.GetOk(constants.ArmIsResourceGroupCreated)
|
||||
if !ok {
|
||||
t.Fatal("Expected the step to add item to stateBag['constants.ArmIsResourceGroupCreated'], but it did not.")
|
||||
t.Fatalf("Expected the step to add item to stateBag['constants.ArmIsResourceGroupCreated'], but it did not.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,12 +93,5 @@ func createTestStateBagStepCreateResourceGroup() multistep.StateBag {
|
||||
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
|
||||
value := "Unit Test: Tags"
|
||||
tags := map[string]*string{
|
||||
"tag01": &value,
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmTags, &tags)
|
||||
|
||||
return stateBag
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func (s *StepDeleteOSDisk) Run(state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Deleting the temporary OS disk ...")
|
||||
|
||||
var osDisk = state.Get(constants.ArmOSDiskVhd).(string)
|
||||
s.say(fmt.Sprintf(" -> OS Disk : '%s'", osDisk))
|
||||
s.say(fmt.Sprintf(" -> OS Disk : '%s'", osDisk))
|
||||
|
||||
u, err := url.Parse(osDisk)
|
||||
if err != nil {
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func TestStepDeleteOSDiskShouldFailIfGetFails(t *testing.T) {
|
||||
|
||||
@@ -6,9 +6,9 @@ package arm
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
@@ -46,10 +46,7 @@ func (s *StepDeleteResourceGroup) Run(state multistep.StateBag) multistep.StepAc
|
||||
func() bool { return common.IsStateCancelled(state) },
|
||||
func(cancelCh <-chan struct{}) error { return s.delete(resourceGroupName, cancelCh) })
|
||||
|
||||
stepAction := processInterruptibleResult(result, s.error, state)
|
||||
state.Put(constants.ArmIsResourceGroupCreated, false)
|
||||
|
||||
return stepAction
|
||||
return processInterruptibleResult(result, s.error, state)
|
||||
}
|
||||
|
||||
func (*StepDeleteResourceGroup) Cleanup(multistep.StateBag) {
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func TestStepDeleteResourceGroupShouldFailIfDeleteFails(t *testing.T) {
|
||||
@@ -49,9 +49,12 @@ func TestStepDeleteResourceGroupShouldPassIfDeletePasses(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeleteResourceGroupShouldDeleteStateBagArmResourceGroupCreated(t *testing.T) {
|
||||
func TestStepDeleteResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualResourceGroupName string
|
||||
|
||||
var testSubject = &StepDeleteResourceGroup{
|
||||
delete: func(resourceGroupName string, cancelCh <-chan struct{}) error {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
@@ -59,22 +62,22 @@ func TestStepDeleteResourceGroupShouldDeleteStateBagArmResourceGroupCreated(t *t
|
||||
}
|
||||
|
||||
stateBag := DeleteTestStateBagStepDeleteResourceGroup()
|
||||
testSubject.Run(stateBag)
|
||||
var result = testSubject.Run(stateBag)
|
||||
|
||||
value, ok := stateBag.GetOk(constants.ArmIsResourceGroupCreated)
|
||||
if !ok {
|
||||
t.Fatal("Expected the resource bag value arm.IsResourceGroupCreated to exist")
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if value.(bool) {
|
||||
t.Fatalf("Expected arm.IsResourceGroupCreated to be false, but got %q", value)
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteTestStateBagStepDeleteResourceGroup() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
stateBag.Put(constants.ArmIsResourceGroupCreated, "Unit Test: IsResourceGroupCreated")
|
||||
|
||||
return stateBag
|
||||
}
|
||||
|
||||
@@ -6,42 +6,63 @@ package arm
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepDeployTemplate struct {
|
||||
client *AzureClient
|
||||
deploy func(resourceGroupName string, deploymentName string, cancelCh <-chan struct{}) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
config *Config
|
||||
factory templateFactoryFunc
|
||||
client *AzureClient
|
||||
template string
|
||||
deploy func(resourceGroupName string, deploymentName string, templateParameters *TemplateParameters, cancelCh <-chan struct{}) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
|
||||
func NewStepDeployTemplate(client *AzureClient, ui packer.Ui, config *Config, factory templateFactoryFunc) *StepDeployTemplate {
|
||||
func NewStepDeployTemplate(client *AzureClient, ui packer.Ui, template string) *StepDeployTemplate {
|
||||
var step = &StepDeployTemplate{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
config: config,
|
||||
factory: factory,
|
||||
client: client,
|
||||
template: template,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
step.deploy = step.deployTemplate
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) deployTemplate(resourceGroupName string, deploymentName string, cancelCh <-chan struct{}) error {
|
||||
deployment, err := s.factory(s.config)
|
||||
func (s *StepDeployTemplate) deployTemplate(resourceGroupName string, deploymentName string, templateParameters *TemplateParameters, cancelCh <-chan struct{}) error {
|
||||
factory := newDeploymentFactory(s.template)
|
||||
deployment, err := factory.create(*templateParameters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.client.DeploymentsClient.CreateOrUpdate(resourceGroupName, deploymentName, *deployment, cancelCh)
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
poller := NewDeploymentPoller(func() (string, error) {
|
||||
r, e := s.client.DeploymentsClient.Get(resourceGroupName, deploymentName)
|
||||
if r.Properties != nil && r.Properties.ProvisioningState != nil {
|
||||
return *r.Properties.ProvisioningState, e
|
||||
}
|
||||
|
||||
return "UNKNOWN", e
|
||||
})
|
||||
|
||||
pollStatus, err := poller.PollAsNeeded()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pollStatus != DeploySucceeded {
|
||||
return fmt.Errorf("Deployment failed with a status of '%s'.", pollStatus)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) Run(state multistep.StateBag) multistep.StepAction {
|
||||
@@ -49,6 +70,7 @@ func (s *StepDeployTemplate) Run(state multistep.StateBag) multistep.StepAction
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var deploymentName = state.Get(constants.ArmDeploymentName).(string)
|
||||
var templateParameters = state.Get(constants.ArmTemplateParameters).(*TemplateParameters)
|
||||
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", deploymentName))
|
||||
@@ -56,7 +78,7 @@ func (s *StepDeployTemplate) Run(state multistep.StateBag) multistep.StepAction
|
||||
result := common.StartInterruptibleTask(
|
||||
func() bool { return common.IsStateCancelled(state) },
|
||||
func(cancelCh <-chan struct{}) error {
|
||||
return s.deploy(resourceGroupName, deploymentName, cancelCh)
|
||||
return s.deploy(resourceGroupName, deploymentName, templateParameters, cancelCh)
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -7,13 +7,14 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func TestStepDeployTemplateShouldFailIfDeployFails(t *testing.T) {
|
||||
var testSubject = &StepDeployTemplate{
|
||||
deploy: func(string, string, <-chan struct{}) error {
|
||||
template: Linux,
|
||||
deploy: func(string, string, *TemplateParameters, <-chan struct{}) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
@@ -34,9 +35,10 @@ func TestStepDeployTemplateShouldFailIfDeployFails(t *testing.T) {
|
||||
|
||||
func TestStepDeployTemplateShouldPassIfDeployPasses(t *testing.T) {
|
||||
var testSubject = &StepDeployTemplate{
|
||||
deploy: func(string, string, <-chan struct{}) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
template: Linux,
|
||||
deploy: func(string, string, *TemplateParameters, <-chan struct{}) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepDeployTemplate()
|
||||
@@ -54,11 +56,14 @@ func TestStepDeployTemplateShouldPassIfDeployPasses(t *testing.T) {
|
||||
func TestStepDeployTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualResourceGroupName string
|
||||
var actualDeploymentName string
|
||||
var actualTemplateParameters *TemplateParameters
|
||||
|
||||
var testSubject = &StepDeployTemplate{
|
||||
deploy: func(resourceGroupName string, deploymentName string, cancelCh <-chan struct{}) error {
|
||||
template: Linux,
|
||||
deploy: func(resourceGroupName string, deploymentName string, templateParameter *TemplateParameters, cancelCh <-chan struct{}) error {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualDeploymentName = deploymentName
|
||||
actualTemplateParameters = templateParameter
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -75,13 +80,18 @@ func TestStepDeployTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
|
||||
var expectedDeploymentName = stateBag.Get(constants.ArmDeploymentName).(string)
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
var expectedTemplateParameters = stateBag.Get(constants.ArmTemplateParameters).(*TemplateParameters)
|
||||
|
||||
if actualDeploymentName != expectedDeploymentName {
|
||||
t.Fatal("Expected StepValidateTemplate to source 'constants.ArmDeploymentName' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected StepValidateTemplate to source 'constants.ArmDeploymentName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualTemplateParameters != expectedTemplateParameters {
|
||||
t.Fatalf("Expected the step to source 'constants.ArmTemplateParameters' from the state bag, but it did not.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +100,7 @@ func createTestStateBagStepDeployTemplate() multistep.StateBag {
|
||||
|
||||
stateBag.Put(constants.ArmDeploymentName, "Unit Test: DeploymentName")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
stateBag.Put(constants.ArmTemplateParameters, &TemplateParameters{})
|
||||
|
||||
return stateBag
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func TestStepGetCertificateShouldFailIfGetFails(t *testing.T) {
|
||||
@@ -77,10 +77,10 @@ func TestStepGetCertificateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var expectedKeyVaultName = stateBag.Get(constants.ArmKeyVaultName).(string)
|
||||
|
||||
if actualKeyVaultName != expectedKeyVaultName {
|
||||
t.Fatal("Expected StepGetCertificate to source 'constants.ArmKeyVaultName' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected StepGetCertificate to source 'constants.ArmKeyVaultName' from the state bag, but it did not.")
|
||||
}
|
||||
if actualSecretName != DefaultSecretName {
|
||||
t.Fatal("Expected StepGetCertificate to use default value for secret, but it did not.")
|
||||
t.Fatalf("Expected StepGetCertificate to use default value for secret, but it did not.")
|
||||
}
|
||||
|
||||
expectedCertificateUrl, ok := stateBag.GetOk(constants.ArmCertificateUrl)
|
||||
|
||||
@@ -6,82 +6,48 @@ package arm
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type EndpointType int
|
||||
|
||||
const (
|
||||
PublicEndpoint EndpointType = iota
|
||||
PrivateEndpoint
|
||||
)
|
||||
|
||||
var (
|
||||
EndpointCommunicationText = map[EndpointType]string{
|
||||
PublicEndpoint: "PublicEndpoint",
|
||||
PrivateEndpoint: "PrivateEndpoint",
|
||||
}
|
||||
)
|
||||
|
||||
type StepGetIPAddress struct {
|
||||
client *AzureClient
|
||||
endpoint EndpointType
|
||||
get func(resourceGroupName string, ipAddressName string, interfaceName string) (string, error)
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
client *AzureClient
|
||||
get func(resourceGroupName string, ipAddressName string) (string, error)
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
|
||||
func NewStepGetIPAddress(client *AzureClient, ui packer.Ui, endpoint EndpointType) *StepGetIPAddress {
|
||||
func NewStepGetIPAddress(client *AzureClient, ui packer.Ui) *StepGetIPAddress {
|
||||
var step = &StepGetIPAddress{
|
||||
client: client,
|
||||
endpoint: endpoint,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
switch endpoint {
|
||||
case PrivateEndpoint:
|
||||
step.get = step.getPrivateIP
|
||||
case PublicEndpoint:
|
||||
step.get = step.getPublicIP
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
step.get = step.getIPAddress
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepGetIPAddress) getPrivateIP(resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
|
||||
resp, err := s.client.InterfacesClient.Get(resourceGroupName, interfaceName, "")
|
||||
func (s *StepGetIPAddress) getIPAddress(resourceGroupName string, ipAddressName string) (string, error) {
|
||||
res, err := s.client.PublicIPAddressesClient.Get(resourceGroupName, ipAddressName, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return *(*resp.Properties.IPConfigurations)[0].Properties.PrivateIPAddress, nil
|
||||
}
|
||||
|
||||
func (s *StepGetIPAddress) getPublicIP(resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
|
||||
resp, err := s.client.PublicIPAddressesClient.Get(resourceGroupName, ipAddressName, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return *resp.Properties.IPAddress, nil
|
||||
return *res.Properties.IPAddress, nil
|
||||
}
|
||||
|
||||
func (s *StepGetIPAddress) Run(state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Getting the VM's IP address ...")
|
||||
s.say("Getting the public IP address ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var ipAddressName = state.Get(constants.ArmPublicIPAddressName).(string)
|
||||
var nicName = state.Get(constants.ArmNicName).(string)
|
||||
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> PublicIPAddressName : '%s'", ipAddressName))
|
||||
s.say(fmt.Sprintf(" -> NicName : '%s'", nicName))
|
||||
s.say(fmt.Sprintf(" -> Network Connection : '%s'", EndpointCommunicationText[s.endpoint]))
|
||||
|
||||
address, err := s.get(resourceGroupName, ipAddressName, nicName)
|
||||
address, err := s.get(resourceGroupName, ipAddressName)
|
||||
if err != nil {
|
||||
state.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
@@ -89,8 +55,8 @@ func (s *StepGetIPAddress) Run(state multistep.StateBag) multistep.StepAction {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.say(fmt.Sprintf(" -> Public IP : '%s'", address))
|
||||
state.Put(constants.SSHHost, address)
|
||||
s.say(fmt.Sprintf(" -> IP Address : '%s'", address))
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
@@ -7,16 +7,15 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func TestStepGetIPAddressShouldFailIfGetFails(t *testing.T) {
|
||||
var testSubject = &StepGetIPAddress{
|
||||
get: func(string, string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
endpoint: PublicEndpoint,
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
get: func(string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetIPAddress()
|
||||
@@ -33,10 +32,9 @@ func TestStepGetIPAddressShouldFailIfGetFails(t *testing.T) {
|
||||
|
||||
func TestStepGetIPAddressShouldPassIfGetPasses(t *testing.T) {
|
||||
var testSubject = &StepGetIPAddress{
|
||||
get: func(string, string, string) (string, error) { return "", nil },
|
||||
endpoint: PublicEndpoint,
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
get: func(string, string) (string, error) { return "", nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetIPAddress()
|
||||
@@ -54,19 +52,16 @@ func TestStepGetIPAddressShouldPassIfGetPasses(t *testing.T) {
|
||||
func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualResourceGroupName string
|
||||
var actualIPAddressName string
|
||||
var actualNicName string
|
||||
|
||||
var testSubject = &StepGetIPAddress{
|
||||
get: func(resourceGroupName string, ipAddressName string, nicName string) (string, error) {
|
||||
get: func(resourceGroupName string, ipAddressName string) (string, error) {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualIPAddressName = ipAddressName
|
||||
actualNicName = nicName
|
||||
|
||||
return "127.0.0.1", nil
|
||||
},
|
||||
endpoint: PublicEndpoint,
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetIPAddress()
|
||||
@@ -78,18 +73,13 @@ func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
var expectedIPAddressName = stateBag.Get(constants.ArmPublicIPAddressName).(string)
|
||||
var expectedNicName = stateBag.Get(constants.ArmNicName).(string)
|
||||
|
||||
if actualIPAddressName != expectedIPAddressName {
|
||||
t.Fatal("Expected StepGetIPAddress to source 'constants.ArmIPAddressName' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected StepGetIPAddress to source 'constants.ArmIPAddressName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected StepGetIPAddress to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualNicName != expectedNicName {
|
||||
t.Fatalf("Expected StepGetIPAddress to source 'constants.ArmNetworkInterfaceName' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected StepGetIPAddress to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
expectedIPAddress, ok := stateBag.GetOk(constants.SSHHost)
|
||||
@@ -106,7 +96,6 @@ func createTestStateBagStepGetIPAddress() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmPublicIPAddressName, "Unit Test: PublicIPAddressName")
|
||||
stateBag.Put(constants.ArmNicName, "Unit Test: NicName")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
|
||||
return stateBag
|
||||
|
||||
@@ -82,11 +82,11 @@ func TestStepGetOSDiskShouldTakeValidateArgumentsFromStateBag(t *testing.T) {
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
|
||||
if actualComputeName != expectedComputeName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
expectedOSDiskVhd, ok := stateBag.GetOk(constants.ArmOSDiskVhd)
|
||||
|
||||
@@ -6,9 +6,9 @@ package arm
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func TestStepPowerOffComputeShouldFailIfPowerOffFails(t *testing.T) {
|
||||
@@ -75,11 +75,11 @@ func TestStepPowerOffComputeShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
|
||||
if actualComputeName != expectedComputeName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
@@ -31,6 +31,7 @@ func (s *StepSetCertificate) Run(state multistep.StateBag) multistep.StepAction
|
||||
var winRMCertificateUrl = state.Get(constants.ArmCertificateUrl).(string)
|
||||
s.config.tmpWinRMCertificateUrl = winRMCertificateUrl
|
||||
|
||||
state.Put(constants.ArmTemplateParameters, s.config.toTemplateParameters())
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ package arm
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func TestStepSetCertificateShouldPassIfGetPasses(t *testing.T) {
|
||||
@@ -30,9 +30,8 @@ func TestStepSetCertificateShouldPassIfGetPasses(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStepSetCertificateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
config := new(Config)
|
||||
var testSubject = &StepSetCertificate{
|
||||
config: config,
|
||||
config: new(Config),
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
@@ -44,8 +43,9 @@ func TestStepSetCertificateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if config.tmpWinRMCertificateUrl != stateBag.Get(constants.ArmCertificateUrl) {
|
||||
t.Fatalf("Expected config.tmpWinRMCertificateUrl to be %s, but got %s'", stateBag.Get(constants.ArmCertificateUrl), config.tmpWinRMCertificateUrl)
|
||||
_, ok := stateBag.GetOk(constants.ArmTemplateParameters)
|
||||
if !ok {
|
||||
t.Fatalf("Expected the state bag to have a value for '%s', but it did not.", constants.ArmTemplateParameters)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,16 +5,16 @@ package arm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProcessStepResultShouldContinueForNonErrors(t *testing.T) {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
code := processStepResult(nil, func(error) { t.Fatal("Should not be called!") }, stateBag)
|
||||
code := processStepResult(nil, func(error) { t.Fatalf("Should not be called!") }, stateBag)
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok {
|
||||
t.Errorf("Error was nil, but was still in the state bag.")
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func TestProcessStepResultShouldContinueOnSuccessfulTask(t *testing.T) {
|
||||
Err: nil,
|
||||
}
|
||||
|
||||
code := processInterruptibleResult(result, func(error) { t.Fatal("Should not be called!") }, stateBag)
|
||||
code := processInterruptibleResult(result, func(error) { t.Fatalf("Should not be called!") }, stateBag)
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok {
|
||||
t.Errorf("Error was nil, but was still in the state bag.")
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func TestProcessStepResultShouldHaltWhenTaskIsCancelled(t *testing.T) {
|
||||
Err: nil,
|
||||
}
|
||||
|
||||
code := processInterruptibleResult(result, func(error) { t.Fatal("Should not be called!") }, stateBag)
|
||||
code := processInterruptibleResult(result, func(error) { t.Fatalf("Should not be called!") }, stateBag)
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok {
|
||||
t.Errorf("Error was nil, but was still in the state bag.")
|
||||
}
|
||||
|
||||
@@ -6,35 +6,35 @@ package arm
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepValidateTemplate struct {
|
||||
client *AzureClient
|
||||
validate func(resourceGroupName string, deploymentName string) error
|
||||
template string
|
||||
validate func(resourceGroupName string, deploymentName string, templateParameters *TemplateParameters) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
config *Config
|
||||
factory templateFactoryFunc
|
||||
}
|
||||
|
||||
func NewStepValidateTemplate(client *AzureClient, ui packer.Ui, config *Config, factory templateFactoryFunc) *StepValidateTemplate {
|
||||
func NewStepValidateTemplate(client *AzureClient, ui packer.Ui, template string) *StepValidateTemplate {
|
||||
var step = &StepValidateTemplate{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
config: config,
|
||||
factory: factory,
|
||||
client: client,
|
||||
template: template,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
step.validate = step.validateTemplate
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepValidateTemplate) validateTemplate(resourceGroupName string, deploymentName string) error {
|
||||
deployment, err := s.factory(s.config)
|
||||
func (s *StepValidateTemplate) validateTemplate(resourceGroupName string, deploymentName string, templateParameters *TemplateParameters) error {
|
||||
factory := newDeploymentFactory(s.template)
|
||||
deployment, err := factory.create(*templateParameters)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -48,11 +48,12 @@ func (s *StepValidateTemplate) Run(state multistep.StateBag) multistep.StepActio
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var deploymentName = state.Get(constants.ArmDeploymentName).(string)
|
||||
var templateParameters = state.Get(constants.ArmTemplateParameters).(*TemplateParameters)
|
||||
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", deploymentName))
|
||||
|
||||
err := s.validate(resourceGroupName, deploymentName)
|
||||
err := s.validate(resourceGroupName, deploymentName, templateParameters)
|
||||
return processStepResult(err, s.error, state)
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user