Merge remote-tracking branch 'upstream/master' into packer-builder-profitbricks

This commit is contained in:
jasminSPC 2016-10-10 23:53:52 +02:00
commit dca286bf38
713 changed files with 10072 additions and 210118 deletions

View File

@ -9,7 +9,10 @@ BACKWARDS INCOMPATIBILITIES:
FEATURES:
* **New Checksum post-processor**: Create a checksum file from your build artifacts as part of your build. [GH-3492]
* **New Checksum post-processor**: Create a checksum file from your build artifacts as part of your build. [GH-3492]
[GH-3790]
* **New build flag** `-on-error` to allow inspection and keeping artifacts on builder errors. [GH-3885]
IMPROVEMENTS:
@ -21,7 +24,8 @@ IMPROVEMENTS:
* builder/amazon: Added `shutdown_behavior` option to support `stop` or
`terminate` at the end of the build [GH-3556]
* builder/amazon: Support building from scratch with amazon-chroot builder.
[GH-3855]
[GH-3855] [GH-3895]
* builder/amazon: Support create an AMI with an `encrypted_boot` volume. [GH-3382]
* builder/azure: Now pre-validates `capture_container_name` and
`capture_name_prefix` [GH-3537]
* builder/azure: Support for custom images [GH-3575]
@ -30,28 +34,44 @@ IMPROVEMENTS:
* builder/digitalocean: Use `state_timeout` for unlock and off transitions.
[GH-3444]
* builder/digitalocean: Fixes timeout waiting for snapshot [GH-3868]
* builder/digitalocean: Added `user_data_file` support. [GH-3933]
* builder/docker: Improved support for Docker pull from Amazon ECR. [GH-3856]
* builder/google: Added support for `image_family` [GH-3503]
* builder/google: Use gcloud application default credentials. [GH-3655]
* builder/google: Signal that startup script fished via metadata. [GH-3873]
* builder/google: Add image license metadata. [GH-3873]
* builder/google: Enable to select NVMe images. [GH-3338]
* builder/google: Create passwords for Windows instances. [GH-3932]
* builder/null: Can now be used with WinRM [GH-2525]
* builder/parallels: Now pauses between `boot_command` entries when running
with `-debug` [GH-3547]
* builder/parallels: Support future versions of Parallels by using the latest
driver [GH-3673]
* builder/parallels: Add support for ctrl, shift and alt keys in `boot_command`.
[GH-3767]
* builder/parallels: Copy directories recursively with `floppy_dirs`. [GH-2919]
* builder/qemu: Added `vnc_bind_address` option [GH-3574]
* builder/qemu: Specify disk format when starting qemu [GH-3888]
* builder/qemu: Now pauses between `boot_command` entries when running with
`-debug` [GH-3547]
* builder/qemu: Add support for ctrl, shift and alt keys in `boot_command`.
[GH-3767]
* builder/qemu: Copy directories recursively with `floppy_dirs`. [GH-2919]
* builder/virtualbox: Now pauses between `boot_command` entries when running
with `-debug` [GH-3542]
* builder/virtualbox: Added `vrdp_bind_address` option [GH-3566]
* builder/virtualbox: Add support for ctrl, shift and alt keys in `boot_command`.
[GH-3767]
* builder/virtualbox: Copy directories recursively with `floppy_dirs`. [GH-2919]
* builder/vmware: Now paused between `boot_command` entries when running with
`-debug` [GH-3542]
* builder/vmware: Added `vnc_bind_address` option [GH-3565]
* builder/vmware: Adds passwords for VNC [GH-2325]
* builder/vmware: Handle connection to VM with more than one NIC on ESXi
[GH-3347]
* builder/qemu: Now pauses between `boot_command` entries when running with
`-debug` [GH-3547]
* builder/vmware: Add support for ctrl, shift and alt keys in `boot_command`.
[GH-3767]
* builder/vmware: Copy directories recursively with `floppy_dirs`. [GH-2919]
* provisioner/ansible: Improved logging and error handling [GH-3477]
* provisioner/ansible: Support scp [GH-3861]
* provisioner/ansible-local: Support for ansible-galaxy [GH-3350] [GH-3836]
@ -59,32 +79,65 @@ IMPROVEMENTS:
value for Windows [GH-3622]
* provisioner/chef: Installs 64bit chef on Windows if available [GH-3848]
* provisioner/puppet: Added `execute_command` option [GH-3614]
* post-processor/amazon-import: Support `ami_name` for naming imported AMI.
[GH-3941]
* post-processor/compress: Added support for bgzf compression [GH-3501]
* post-processor/docker: Preserve tags when running docker push [GH-3631]
* post-processor/docker: Improved support for Docker push to Amazon ECR [GH-3856]
* scripts: Added `help` target to Makefile [GH-3290]
* builder/googlecompute: Add `-force` option to delete old image before
creating new one. [GH-3918]
* builder/virtualbox-iso: Added `keep_registed` option to skip cleaning up
the image. [GH-3954]
* builder/virtualbox: Added `post_shutdown_delay` option to wait after
shutting down to prevent issues removing floppy drive. [GH-3952]
* core: setting `PACKER_LOG=0` now disables logging. [GH-3964]
* vendor: Moving from Godep to govendor. See `CONTRIBUTING.md` for details.
[GH-3956]
* post-processor/vagrant: Added vsphere-esx hosts to supported machine types.
[GH-3967]
* provisioner/salt: Added `custom_state` to specify state to run instead of
`highstate`. [GH-3776]
* builder/openstack: Added support for `ssh_password` instead of generating
ssh keys. [GH-3976]
BUG FIXES:
* post-processor/shell-local: Do not set execute bit on artifact file [GH-3505]
* post-processor/vsphere: Fix upload failures with vsphere [GH-3321]
* provisioner/ansible: Properly set host key checking even when a custom ENV
is specified [GH-3568]
* builder/amazon: Use `temporary_key_pair_name` when specified. [GH-3739]
* builder/amazon: Add 0.5 cents to discovered spot price. [GH-3662]
* builder/amazon: Fix packer crash when waiting for SSH. [GH-3865]
* builder/amazon: Honor ssh_private_ip flag in EC2-Classic. [GH-3752]
* builder/amazon: Allow using `ssh_private_key_file` and `ssh_password`.
[GH-3953]
* builder/azure: check for empty resource group [GH-3606]
* builder/azure: fix token validity test [GH-3609]
* builder/docker: fix docker builder with ansible provisioner. [GH-3476]
* builder/docker: Fix file provisioner dotfile matching. [GH-3800]
* builder/virtualbox: Respect `ssh_host` [GH-3617]
* builder/virtualbox: Make `ssh_host_port_max` an inclusive bound. [GH-2784]
* builder/vmware: Re-introduce case sensitive VMX keys [GH-2707]
* builder/vmware: Don't check for poweron errors on ESXi [GH-3195]
* builder/vmware: Respect `ssh_host`/`winrm_host` on ESXi [GH-3738]
* builder/vmware: Do not add remotedisplay.vnc.ip to VMX data on ESXi
[GH-3740]
* builder/qemu: Don't fail on communicator set to `none`. [GH-3681]
* builder/qemu: Make `ssh_host_port_max` an inclusive bound. [GH-2784]
* post-processor/shell-local: Do not set execute bit on artifact file [GH-3505]
* post-processor/vsphere: Fix upload failures with vsphere [GH-3321]
* provisioner/ansible: Properly set host key checking even when a custom ENV
is specified [GH-3568]
* website: improved rendering on iPad [GH-3780]
* provisioner/file: Fix directory download. [GH-3899]
* command/push: Allows dot (`.`) in image names. [GH-3937]
* builder/amazon: add retry logic when creating tags.
* communicator/ssh: handle error case where server closes the connection but
doesn't give us an error code. [GH-3966]
## 0.10.2 (September 20, 2016)
BUG FIXES:
* Rebuilding with OS X Sierra and go 1.7.1 to fix bug in Sierra
## 0.10.1 (May 7, 2016)

View File

@ -100,11 +100,16 @@ From there, open your fork in your browser to open a new pull-request.
### Tips for Working on Packer
#### Godeps
#### Govendor
If you are submitting a change that requires new or updated dependencies, please include them in `Godeps/Godeps.json` and in the `vendor/` folder. This helps everything get tested properly in CI.
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 [Godep](https://github.com/tools/godep) to do this. This step is recommended but not required; if you don't use Godep please indicate in your PR which dependencies have changed and to what versions.
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.

722
Godeps/Godeps.json generated
View File

@ -1,722 +0,0 @@
{
"ImportPath": "github.com/mitchellh/packer",
"GoVersion": "go1.6",
"GodepVersion": "v74",
"Deps": [
{
"ImportPath": "github.com/ActiveState/tail",
"Comment": "v0-41-g1a0242e",
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/compute",
"Comment": "v3.1.0-beta",
"Rev": "902d95d9f311ae585ee98cfd18f418b467d60d5a"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/network",
"Comment": "v3.1.0-beta",
"Rev": "902d95d9f311ae585ee98cfd18f418b467d60d5a"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/resources/resources",
"Comment": "v3.1.0-beta",
"Rev": "902d95d9f311ae585ee98cfd18f418b467d60d5a"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/resources/subscriptions",
"Comment": "v3.1.0-beta",
"Rev": "902d95d9f311ae585ee98cfd18f418b467d60d5a"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/storage",
"Comment": "v3.1.0-beta",
"Rev": "902d95d9f311ae585ee98cfd18f418b467d60d5a"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/storage",
"Comment": "v3.1.0-beta",
"Rev": "902d95d9f311ae585ee98cfd18f418b467d60d5a"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest",
"Comment": "v7.0.7",
"Rev": "6f40a8acfe03270d792cb8155e2942c09d7cff95"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/azure",
"Comment": "v7.0.7",
"Rev": "6f40a8acfe03270d792cb8155e2942c09d7cff95"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/date",
"Comment": "v7.0.7",
"Rev": "6f40a8acfe03270d792cb8155e2942c09d7cff95"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/to",
"Comment": "v7.0.7",
"Rev": "6f40a8acfe03270d792cb8155e2942c09d7cff95"
},
{
"ImportPath": "github.com/Azure/go-ntlmssp",
"Rev": "e0b63eb299a769ea4b04dadfe530f6074b277afb"
},
{
"ImportPath": "github.com/armon/go-radix",
"Rev": "4239b77079c7b5d1243b7b4736304ce8ddb6f0f2"
},
{
"ImportPath": "github.com/approvals/go-approval-tests",
"Rev": "ad96e53bea43a905c17beeb983a0f9ce087dc48d"
},
{
"ImportPath": "github.com/approvals/go-approval-tests/reporters",
"Rev": "ad96e53bea43a905c17beeb983a0f9ce087dc48d"
},
{
"ImportPath": "github.com/approvals/go-approval-tests/utils",
"Rev": "ad96e53bea43a905c17beeb983a0f9ce087dc48d"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/awserr",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/awsutil",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/client",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/client/metadata",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/corehandlers",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/endpointcreds",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/stscreds",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/defaults",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/ec2metadata",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/request",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/session",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/signer/v4",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/endpoints",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/json/jsonutil",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/jsonrpc",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/restxml",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/waiter",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/ec2",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/ecr",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/s3",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3iface",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3manager",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/sts",
"Comment": "v1.4.6",
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
},
{
"ImportPath": "github.com/bgentry/speakeasy",
"Rev": "36e9cfdd690967f4f690c6edcc9ffacd006014a0"
},
{
"ImportPath": "github.com/biogo/hts/bgzf",
"Rev": "50da7d4131a3b5c9d063932461cab4d1fafb20b0"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d"
},
{
"ImportPath": "github.com/dgrijalva/jwt-go",
"Comment": "v3.0.0",
"Rev": "d2709f9f1f31ebcda9651b03077758c1f3a0018c"
},
{
"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-uuid",
"Rev": "73d19cdc2bf00788cc25f7d5fd74347d48ada9ac"
},
{
"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/pmezard/go-difflib/difflib",
"Comment": "v1.0.0",
"Rev": "792786c7400a136282c1664665ae0a8db921c6c2"
},
{
"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/stretchr/testify/assert",
"Comment": "v1.1.3-19-gd77da35",
"Rev": "d77da356e56a7428ad25149ca77381849a6a5232"
},
{
"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": "ff0a1ff302946b997eb1832381419d1f95143483"
},
{
"ImportPath": "google.golang.org/api/gensupport",
"Rev": "ff0a1ff302946b997eb1832381419d1f95143483"
},
{
"ImportPath": "google.golang.org/api/googleapi",
"Rev": "ff0a1ff302946b997eb1832381419d1f95143483"
},
{
"ImportPath": "google.golang.org/api/googleapi/internal/uritemplates",
"Rev": "ff0a1ff302946b997eb1832381419d1f95143483"
},
{
"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"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/vendor/github.com/Azure/go-autorest/autorest",
"Comment": "v7.0.5-4-g0a0ee7d",
"Rev": "0a0ee7d5b9b1b3d980434cbb0afff33e9ca9e907"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/vendor/github.com/Azure/go-autorest/autorest/azure",
"Comment": "v7.0.5-4-g0a0ee7d",
"Rev": "0a0ee7d5b9b1b3d980434cbb0afff33e9ca9e907"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/vendor/github.com/Azure/go-autorest/autorest/date",
"Comment": "v7.0.5-4-g0a0ee7d",
"Rev": "0a0ee7d5b9b1b3d980434cbb0afff33e9ca9e907"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/vendor/github.com/Azure/go-autorest/autorest/to",
"Comment": "v7.0.5-4-g0a0ee7d",
"Rev": "0a0ee7d5b9b1b3d980434cbb0afff33e9ca9e907"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/vendor/github.com/dgrijalva/jwt-go",
"Comment": "v3.0.0-2-gf077707",
"Rev": "f0777076321ab64f6efc15a82d9d23b98539b943"
},
{
"ImportPath": "github.com/profitbricks/profitbricks-sdk-go",
"Comment": "v2.1.0",
"Rev": "565df72e7b92b63c68c17b2c305256ba09a251f4"
}
]
}

5
Godeps/Readme generated
View File

@ -1,5 +0,0 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

View File

@ -29,11 +29,8 @@ package:
deps:
go get github.com/mitchellh/gox
go get golang.org/x/tools/cmd/stringer
@go version | grep 1.4 ; if [ $$? -eq 0 ]; then \
echo "Installing godep and restoring dependencies"; \
go get github.com/tools/godep; \
godep restore; \
fi
go get github.com/kardianos/govendor
govendor sync
dev: deps ## Build and install a development build
@grep 'const VersionPrerelease = ""' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \
@ -53,7 +50,7 @@ fmt-examples:
# source files.
generate: deps ## Generate dynamically generated code
go generate .
go fmt command/plugin.go
gofmt -w command/plugin.go
test: deps ## Run unit tests
@go test $(TEST) $(TESTARGS) -timeout=2m
@ -73,13 +70,7 @@ testrace: deps ## Test for race conditions
updatedeps:
go get -u github.com/mitchellh/gox
go get -u golang.org/x/tools/cmd/stringer
@echo "INFO: Packer deps are managed by godep. See CONTRIBUTING.md"
# This is used to add new dependencies to packer. If you are submitting a PR
# that includes new dependencies you will need to run this.
vendor: ## Add new dependencies.
godep restore
godep save
@echo "INFO: Packer deps are managed by govendor. See CONTRIBUTING.md"
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

View File

@ -262,15 +262,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
)
// Run!
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// If there was an error, return that

View File

@ -25,20 +25,22 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
var (
registerOpts *ec2.RegisterImageInput
blockDevices []*ec2.BlockDeviceMapping
mappings []*ec2.BlockDeviceMapping
image *ec2.Image
rootDeviceName string
)
if config.FromScratch {
blockDevices = config.AMIBlockDevices.BuildAMIDevices()
mappings = config.AMIBlockDevices.BuildAMIDevices()
rootDeviceName = config.RootDeviceName
} else {
image = state.Get("source_image").(*ec2.Image)
blockDevices = make([]*ec2.BlockDeviceMapping, len(image.BlockDeviceMappings))
mappings = image.BlockDeviceMappings
rootDeviceName = *image.RootDeviceName
}
for i, device := range blockDevices {
newMappings := make([]*ec2.BlockDeviceMapping, len(mappings))
for i, device := range mappings {
newDevice := device
if *newDevice.DeviceName == rootDeviceName {
if newDevice.Ebs != nil {
@ -58,7 +60,7 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
newDevice.Ebs.Encrypted = nil
}
blockDevices[i] = newDevice
newMappings[i] = newDevice
}
if config.FromScratch {
@ -67,10 +69,10 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
Architecture: aws.String(ec2.ArchitectureValuesX8664),
RootDeviceName: aws.String(rootDeviceName),
VirtualizationType: aws.String(config.AMIVirtType),
BlockDeviceMappings: blockDevices,
BlockDeviceMappings: newMappings,
}
} else {
registerOpts = buildRegisterOpts(config, image, blockDevices)
registerOpts = buildRegisterOpts(config, image, newMappings)
}
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
@ -112,12 +114,12 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {}
func buildRegisterOpts(config *Config, image *ec2.Image, blockDevices []*ec2.BlockDeviceMapping) *ec2.RegisterImageInput {
func buildRegisterOpts(config *Config, image *ec2.Image, mappings []*ec2.BlockDeviceMapping) *ec2.RegisterImageInput {
registerOpts := &ec2.RegisterImageInput{
Name: &config.AMIName,
Architecture: image.Architecture,
RootDeviceName: image.RootDeviceName,
BlockDeviceMappings: blockDevices,
BlockDeviceMappings: mappings,
VirtualizationType: image.VirtualizationType,
}

View File

@ -19,6 +19,7 @@ type AMIConfig struct {
AMITags map[string]string `mapstructure:"tags"`
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
AMIForceDeregister bool `mapstructure:"force_deregister"`
AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"`
}
func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
@ -54,6 +55,10 @@ 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(errs) > 0 {
return errs
}

View File

@ -58,3 +58,12 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
c.AMISkipRegionValidation = false
}
func TestAMIConfigPrepare_EncryptBoot(t *testing.T) {
c := testAMIConfig()
c.AMIUsers = []string{"testAccountID"}
c.AMIEncryptBootVolume = true
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
}

View File

@ -44,14 +44,14 @@ type RunConfig struct {
}
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
// If we are not given an explicit ssh_keypair_name,
// then create a temporary one, but only if the
// temporary_key_pair_name has not been provided.
if c.SSHKeyPairName == "" {
if c.TemporaryKeyPairName == "" {
c.TemporaryKeyPairName = fmt.Sprintf(
"packer_%s", uuid.TimeOrderedUUID())
}
// 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 c.WindowsPasswordTimeout == 0 {

View File

@ -7,6 +7,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mitchellh/multistep"
packerssh "github.com/mitchellh/packer/communicator/ssh"
"golang.org/x/crypto/ssh"
)
@ -34,7 +35,7 @@ func SSHHost(e ec2Describer, private bool) func(multistep.StateBag) (string, err
} else if i.PrivateIpAddress != nil && *i.PrivateIpAddress != "" {
host = *i.PrivateIpAddress
}
} else if private {
} else if private && i.PrivateIpAddress != nil && *i.PrivateIpAddress != "" {
host = *i.PrivateIpAddress
} else if i.PublicDnsName != nil && *i.PublicDnsName != "" {
host = *i.PublicDnsName
@ -64,22 +65,33 @@ 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 generated
// private key.
func SSHConfig(username string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
// config for connecting to the instance created over SSH using the private key
// or password.
func SSHConfig(username, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
privateKey := state.Get("privateKey").(string)
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
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
}
}

View File

@ -28,11 +28,11 @@ func TestSSHHost(t *testing.T) {
wantHost string
}{
{1, "", false, true, publicDNS},
{1, "", true, 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, publicDNS},
{2, "", true, true, privateIP},
{2, "vpc-id", false, true, publicIP},
{2, "vpc-id", true, true, privateIP},
{3, "", false, false, ""},

View File

@ -4,9 +4,11 @@ import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mitchellh/multistep"
retry "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
)
@ -71,9 +73,22 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
}
}
_, err = regionconn.CreateTags(&ec2.CreateTagsInput{
Resources: resourceIds,
Tags: ec2Tags,
// Retry creating tags for about 2.5 minutes
err = retry.Retry(0.2, 30, 11, func() (bool, error) {
_, err := regionconn.CreateTags(&ec2.CreateTagsInput{
Resources: resourceIds,
Tags: ec2Tags,
})
if err == nil {
return true, nil
}
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidAMIID.NotFound" ||
awsErr.Code() == "InvalidSnapshot.NotFound" {
return false, nil
}
}
return true, err
})
if err != nil {

View File

@ -22,7 +22,10 @@ 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(
@ -36,8 +39,13 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
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{
@ -87,7 +95,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 != "" {
if s.PrivateKeyFile != "" || s.KeyPairName == "" {
return
}

View File

@ -160,7 +160,6 @@ 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,
@ -172,6 +171,10 @@ 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{
@ -203,29 +206,35 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
ui.Message(fmt.Sprintf(
"Requesting spot instance '%s' for: %s",
s.InstanceType, spotPrice))
runSpotResp, err := ec2conn.RequestSpotInstances(&ec2.RequestSpotInstancesInput{
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),
},
runOpts := &ec2.RequestSpotLaunchSpecification{
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,
},
Placement: &ec2.SpotPlacement{
AvailabilityZone: &availabilityZone,
},
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
EbsOptimized: &s.EbsOptimized,
}
if keyName != "" {
runOpts.KeyName = &keyName
}
runSpotResp, err := ec2conn.RequestSpotInstances(&ec2.RequestSpotInstancesInput{
SpotPrice: &spotPrice,
LaunchSpecification: runOpts,
})
if err != nil {
err := fmt.Errorf("Error launching source spot instance: %s", err)

View File

@ -148,7 +148,8 @@ 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.SSHUsername),
b.config.RunConfig.Comm.SSHUsername,
b.config.RunConfig.Comm.SSHPassword),
},
&common.StepProvision{},
&stepStopInstance{
@ -162,6 +163,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
AMIName: b.config.AMIName,
},
&stepCreateAMI{},
&stepCreateEncryptedAMICopy{},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,
@ -179,15 +181,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Run!
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// If there was an error, return that

View File

@ -55,6 +55,15 @@ 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 {
@ -144,6 +153,42 @@ 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")
@ -222,6 +267,20 @@ 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(name, flag string) string {
return fmt.Sprintf(testBuilderAccForceDeregister, name, flag)
}

View File

@ -0,0 +1,142 @@
package ebs
import (
"fmt"
"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)
// Encrypt boot not set, so skip step
if !config.AMIConfig.AMIEncryptBootVolume {
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))
copyOpts := &ec2.CopyImageInput{
Name: &config.AMIName, // Try to overwrite existing AMI
SourceImageId: aws.String(id),
SourceRegion: aws.String(region),
Encrypted: aws.Bool(true),
}
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 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 unecrypted 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")
for _, blockDevice := range unencImage.BlockDeviceMappings {
if blockDevice.Ebs != nil {
if 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
state.Put("amis", amis)
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
}
}

View File

@ -230,7 +230,8 @@ 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.SSHUsername),
b.config.RunConfig.Comm.SSHUsername,
b.config.RunConfig.Comm.SSHPassword),
},
&common.StepProvision{},
&StepUploadX509Cert{},
@ -262,15 +263,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Run!
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// If there was an error, return that

View File

@ -157,7 +157,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 = b.createRunner(&steps, ui)
b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(b.stateBag)
// Report any errors.
@ -198,19 +198,6 @@ 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 {

View File

@ -76,7 +76,7 @@ func findVirtualNetworkResourceGroup(client *AzureClient, name string) (string,
}
if len(resourceGroupNames) > 1 {
return "", fmt.Errorf("Found multiple resource groups with a virtual network called %q, please use virtual_network_resource_group_name to disambiguate", name)
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
@ -93,7 +93,7 @@ func findVirtualNetworkSubnet(client *AzureClient, resourceGroupName string, nam
}
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 to disambiguate", resourceGroupName, name)
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]

View File

@ -73,15 +73,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Run the steps
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// If there was an error, return that

View File

@ -27,7 +27,9 @@ const testBuilderAccBasic = `
"type": "test",
"region": "nyc2",
"size": "512mb",
"image": "ubuntu-12-04-x64"
"image": "ubuntu-12-04-x64",
"user_date": "",
"user_date_file": ""
}]
}
`

View File

@ -31,6 +31,7 @@ type Config struct {
StateTimeout time.Duration `mapstructure:"state_timeout"`
DropletName string `mapstructure:"droplet_name"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
ctx interpolate.Context
}
@ -113,6 +114,16 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
errs, errors.New("image is required"))
}
if c.UserData != "" && c.UserDataFile != "" {
errs = packer.MultiErrorAppend(
errs, errors.New("only one of user_data or user_data_file can be specified"))
} else if c.UserDataFile != "" {
if _, err := os.Stat(c.UserDataFile); err != nil {
errs = packer.MultiErrorAppend(
errs, errors.New(fmt.Sprintf("user_data_file not found: %s", c.UserDataFile)))
}
}
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}

View File

@ -6,6 +6,7 @@ import (
"github.com/digitalocean/godo"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"io/ioutil"
)
type stepCreateDroplet struct {
@ -20,6 +21,18 @@ func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction {
// Create the droplet based on configuration
ui.Say("Creating droplet...")
userData := c.UserData
if c.UserDataFile != "" {
contents, err := ioutil.ReadFile(c.UserDataFile)
if err != nil {
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
return multistep.ActionHalt
}
userData = string(contents)
}
droplet, _, err := client.Droplets.Create(&godo.DropletCreateRequest{
Name: c.DropletName,
Region: c.Region,
@ -31,7 +44,7 @@ func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction {
godo.DropletCreateSSHKey{ID: int(sshKeyId)},
},
PrivateNetworking: c.PrivateNetworking,
UserData: c.UserData,
UserData: userData,
})
if err != nil {
err := fmt.Errorf("Error creating droplet: %s", err)

View File

@ -78,15 +78,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("driver", driver)
// Run!
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// If there was an error, return that

View File

@ -169,7 +169,7 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error
// Make the directory, then copy into it
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("set -e; mkdir -p %s; command cp -R %s/* %s",
Command: fmt.Sprintf("set -e; mkdir -p %s; cd %s; command cp -R `ls -A .` %s",
containerDst, containerSrc, containerDst),
}
if err := c.Start(cmd); err != nil {

View File

@ -58,13 +58,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepCreateInstance{
Debug: b.config.PackerDebug,
},
&StepCreateWindowsPassword{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("gce_windows_%s.pem", b.config.PackerBuildName),
},
&StepInstanceInfo{
Debug: b.config.PackerDebug,
},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: commHost,
SSHConfig: sshConfig,
Config: &b.config.Comm,
Host: commHost,
SSHConfig: sshConfig,
WinRMConfig: winrmConfig,
},
new(common.StepProvision),
new(StepWaitInstanceStartup),
@ -73,14 +78,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Run the steps.
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// Report any errors.

View File

@ -49,10 +49,11 @@ type Config struct {
UseInternalIP bool `mapstructure:"use_internal_ip"`
Zone string `mapstructure:"zone"`
Account AccountFile
privateKeyBytes []byte
stateTimeout time.Duration
ctx interpolate.Context
Account AccountFile
privateKeyBytes []byte
stateTimeout time.Duration
imageAlreadyExists bool
ctx interpolate.Context
}
func NewConfig(raws ...interface{}) (*Config, []string, error) {

View File

@ -1,5 +1,10 @@
package googlecompute
import (
"crypto/rsa"
"time"
)
// Driver is the interface that has to be implemented to communicate
// with GCE. The Driver interface exists mostly to allow a mock implementation
// to be used to test the steps.
@ -44,6 +49,9 @@ type Driver interface {
// WaitForInstance waits for an instance to reach the given state.
WaitForInstance(state, zone, name string) <-chan error
// CreateOrResetWindowsPassword creates or resets the password for a user on an Windows instance.
CreateOrResetWindowsPassword(zone, name string, config *WindowsPasswordConfig) (<-chan error, error)
}
type InstanceConfig struct {
@ -64,3 +72,24 @@ type InstanceConfig struct {
Tags []string
Zone string
}
// WindowsPasswordConfig is the data structue that GCE needs to encrypt the created
// windows password.
type WindowsPasswordConfig struct {
key *rsa.PrivateKey
password string
UserName string `json:"userName"`
Modulus string `json:"modulus"`
Exponent string `json:"exponent"`
Email string `json:"email"`
ExpireOn time.Time `json:"expireOn"`
}
type windowsPasswordResponse struct {
UserName string `json:"userName"`
PasswordFound bool `json:"passwordFound"`
EncryptedPassword string `json:"encryptedPassword"`
Modulus string `json:"modulus"`
Exponent string `json:"exponent"`
ErrorMessage string `json:"errorMessage"`
}

View File

@ -1,12 +1,20 @@
package googlecompute
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"runtime"
"strings"
"time"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/version"
@ -160,7 +168,7 @@ func (d *driverGCE) DeleteDisk(zone, name string) (<-chan error, error) {
}
func (d *driverGCE) GetImage(name string) (*Image, error) {
projects := []string{d.projectId, "centos-cloud", "coreos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "ubuntu-os-cloud", "windows-cloud"}
projects := []string{d.projectId, "centos-cloud", "coreos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "ubuntu-os-cloud", "windows-cloud", "gce-nvme"}
var errs error
for _, project := range projects {
image, err := d.GetImageFromProject(project, name)
@ -393,6 +401,112 @@ func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
return errCh, nil
}
func (d *driverGCE) CreateOrResetWindowsPassword(instance, zone string, c *WindowsPasswordConfig) (<-chan error, error) {
errCh := make(chan error, 1)
go d.createWindowsPassword(errCh, instance, zone, c)
return errCh, nil
}
func (d *driverGCE) createWindowsPassword(errCh chan<- error, name, zone string, c *WindowsPasswordConfig) {
data, err := json.Marshal(c)
if err != nil {
errCh <- err
return
}
dCopy := string(data)
instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{Key: "windows-keys", Value: &dCopy})
op, err := d.service.Instances.SetMetadata(d.projectId, zone, name, &compute.Metadata{
Fingerprint: instance.Metadata.Fingerprint,
Items: instance.Metadata.Items,
}).Do()
if err != nil {
errCh <- err
return
}
newErrCh := make(chan error, 1)
go waitForState(newErrCh, "DONE", d.refreshZoneOp(zone, op))
select {
case err = <-newErrCh:
case <-time.After(time.Second * 30):
err = errors.New("time out while waiting for instance to create")
}
if err != nil {
errCh <- err
return
}
timeout := time.Now().Add(time.Minute * 3)
hash := sha1.New()
random := rand.Reader
for time.Now().Before(timeout) {
if passwordResponses, err := d.getPasswordResponses(zone, name); err == nil {
for _, response := range passwordResponses {
if response.Modulus == c.Modulus {
decodedPassword, err := base64.StdEncoding.DecodeString(response.EncryptedPassword)
if err != nil {
errCh <- err
return
}
password, err := rsa.DecryptOAEP(hash, random, c.key, decodedPassword, nil)
if err != nil {
errCh <- err
return
}
c.password = string(password)
errCh <- nil
return
}
}
}
time.Sleep(2 * time.Second)
}
err = errors.New("Could not retrieve password. Timed out.")
errCh <- err
return
}
func (d *driverGCE) getPasswordResponses(zone, instance string) ([]windowsPasswordResponse, error) {
output, err := d.service.Instances.GetSerialPortOutput(d.projectId, zone, instance).Port(4).Do()
if err != nil {
return nil, err
}
responses := strings.Split(output.Contents, "\n")
passwordResponses := make([]windowsPasswordResponse, 0, len(responses))
for _, response := range responses {
var passwordResponse windowsPasswordResponse
if err := json.Unmarshal([]byte(response), &passwordResponse); err != nil {
continue
}
passwordResponses = append(passwordResponses, passwordResponse)
}
return passwordResponses, nil
}
func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error {
errCh := make(chan error, 1)
go waitForState(errCh, state, d.refreshInstanceState(zone, name))
@ -458,7 +572,7 @@ type stateRefreshFunc func() (string, error)
// waitForState will spin in a loop forever waiting for state to
// reach a certain target.
func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) error {
err := Retry(2, 2, 0, func() (bool, error) {
err := common.Retry(2, 2, 0, func() (bool, error) {
state, err := refresh()
if err != nil {
return false, err

View File

@ -67,6 +67,12 @@ type DriverMock struct {
RunInstanceErrCh <-chan error
RunInstanceErr error
CreateOrResetWindowsPasswordZone string
CreateOrResetWindowsPasswordInstance string
CreateOrResetWindowsPasswordConfig *WindowsPasswordConfig
CreateOrResetWindowsPasswordErr error
CreateOrResetWindowsPasswordErrCh <-chan error
WaitForInstanceState string
WaitForInstanceZone string
WaitForInstanceName string
@ -224,3 +230,25 @@ func (d *DriverMock) WaitForInstance(state, zone, name string) <-chan error {
return resultCh
}
func (d *DriverMock) GetWindowsPassword() (string, error) {
return "", nil
}
func (d *DriverMock) CreateOrResetWindowsPassword(instance, zone string, c *WindowsPasswordConfig) (<-chan error, error) {
d.CreateOrResetWindowsPasswordInstance = instance
d.CreateOrResetWindowsPasswordZone = zone
d.CreateOrResetWindowsPasswordConfig = c
c.password = "MOCK_PASSWORD"
resultCh := d.CreateOrResetWindowsPasswordErrCh
if resultCh == nil {
ch := make(chan error)
close(ch)
resultCh = ch
}
return resultCh, d.CreateOrResetWindowsPasswordErr
}

View File

@ -18,14 +18,14 @@ func (s *StepCheckExistingImage) Run(state multistep.StateBag) multistep.StepAct
ui := state.Get("ui").(packer.Ui)
ui.Say("Checking image does not exist...")
exists := d.ImageExists(c.ImageName)
if exists {
err := fmt.Errorf("Image %s already exists", c.ImageName)
c.imageAlreadyExists = d.ImageExists(c.ImageName)
if !c.PackerForce && c.imageAlreadyExists {
err := fmt.Errorf("Image %s already exists.\n"+
"Use the force flag to delete it prior to building.", c.ImageName)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}

View File

@ -22,6 +22,19 @@ func (s *StepCreateImage) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if config.PackerForce && config.imageAlreadyExists {
ui.Say("Deleting previous image...")
errCh := driver.DeleteImage(config.ImageName)
err := <-errCh
if err != nil {
err := fmt.Errorf("Error deleting image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
ui.Say("Creating image...")
imageCh, errCh := driver.CreateImage(

View File

@ -76,6 +76,10 @@ func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction
return multistep.ActionHalt
}
if sourceImage.IsWindows() && c.Comm.Type == "winrm" && c.Comm.WinRMPassword == "" {
state.Put("create_windows_password", true)
}
ui.Say("Creating instance...")
name := c.InstanceName

View File

@ -41,6 +41,101 @@ func TestStepCreateInstance(t *testing.T) {
assert.Equal(t, d.DeleteDiskZone, c.Zone, "Incorrect disk zone passed to driver.")
}
func TestStepCreateInstance_windowsNeedsPassword(t *testing.T) {
state := testState(t)
step := new(StepCreateInstance)
defer step.Cleanup(state)
state.Put("ssh_public_key", "key")
c := state.Get("config").(*Config)
d := state.Get("driver").(*DriverMock)
d.GetImageResult = StubImage("test-image", "test-project", []string{"windows"}, 100)
c.Comm.Type = "winrm"
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// Verify state
nameRaw, ok := state.GetOk("instance_name")
if !ok {
t.Fatal("should have instance name")
}
createPassword, ok := state.GetOk("create_windows_password")
if !ok || !createPassword.(bool) {
t.Fatal("should need to create a windows password")
}
// cleanup
step.Cleanup(state)
if d.DeleteInstanceName != nameRaw.(string) {
t.Fatal("should've deleted instance")
}
if d.DeleteInstanceZone != c.Zone {
t.Fatalf("bad instance zone: %#v", d.DeleteInstanceZone)
}
if d.DeleteDiskName != c.InstanceName {
t.Fatal("should've deleted disk")
}
if d.DeleteDiskZone != c.Zone {
t.Fatalf("bad disk zone: %#v", d.DeleteDiskZone)
}
}
func TestStepCreateInstance_windowsPasswordSet(t *testing.T) {
state := testState(t)
step := new(StepCreateInstance)
defer step.Cleanup(state)
state.Put("ssh_public_key", "key")
config := state.Get("config").(*Config)
driver := state.Get("driver").(*DriverMock)
driver.GetImageResult = StubImage("test-image", "test-project", []string{"windows"}, 100)
config.Comm.Type = "winrm"
config.Comm.WinRMPassword = "password"
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// Verify state
nameRaw, ok := state.GetOk("instance_name")
if !ok {
t.Fatal("should have instance name")
}
_, ok = state.GetOk("create_windows_password")
if ok {
t.Fatal("should not need to create windows password")
}
// cleanup
step.Cleanup(state)
if driver.DeleteInstanceName != nameRaw.(string) {
t.Fatal("should've deleted instance")
}
if driver.DeleteInstanceZone != config.Zone {
t.Fatalf("bad instance zone: %#v", driver.DeleteInstanceZone)
}
if driver.DeleteDiskName != config.InstanceName {
t.Fatal("should've deleted disk")
}
if driver.DeleteDiskZone != config.Zone {
t.Fatalf("bad disk zone: %#v", driver.DeleteDiskZone)
}
}
func TestStepCreateInstance_error(t *testing.T) {
state := testState(t)
step := new(StepCreateInstance)

View File

@ -0,0 +1,119 @@
package googlecompute
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"encoding/pem"
"errors"
"fmt"
"os"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
// StepCreateWindowsPassword represents a Packer build step that sets the windows password on a Windows GCE instance.
type StepCreateWindowsPassword struct {
Debug bool
DebugKeyPath string
}
// Run executes the Packer build step that sets the windows password on a Windows GCE instance.
func (s *StepCreateWindowsPassword) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
d := state.Get("driver").(Driver)
c := state.Get("config").(*Config)
name := state.Get("instance_name").(string)
if c.Comm.WinRMPassword != "" {
state.Put("winrm_password", c.Comm.WinRMPassword)
return multistep.ActionContinue
}
create, ok := state.GetOk("create_windows_password")
if !ok || !create.(bool) {
return multistep.ActionContinue
}
ui.Say("Creating windows user for instance...")
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
err := fmt.Errorf("Error creating temporary key: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, uint32(priv.E))
data := WindowsPasswordConfig{
key: priv,
UserName: c.Comm.WinRMUser,
Modulus: base64.StdEncoding.EncodeToString(priv.N.Bytes()),
Exponent: base64.StdEncoding.EncodeToString(buf[1:]),
Email: c.Account.ClientEmail,
ExpireOn: time.Now().Add(time.Minute * 5),
}
if s.Debug {
priv_blk := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: x509.MarshalPKCS1PrivateKey(priv),
}
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
f, err := os.Create(s.DebugKeyPath)
if err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
// Write out the key
err = pem.Encode(f, &priv_blk)
f.Close()
if err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
}
errCh, err := d.CreateOrResetWindowsPassword(name, c.Zone, &data)
if err == nil {
ui.Message("Waiting for windows password to complete...")
select {
case err = <-errCh:
case <-time.After(c.stateTimeout):
err = errors.New("time out while waiting for the password to be created")
}
}
if err != nil {
err := fmt.Errorf("Error creating windows password: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Message("Created password.")
if s.Debug {
ui.Message(fmt.Sprintf(
"Password (since debug is enabled): %s", data.password))
}
state.Put("winrm_password", data.password)
return multistep.ActionContinue
}
// Nothing to clean up. The windows password is only created on the single instance.
func (s *StepCreateWindowsPassword) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,162 @@
package googlecompute
import (
"errors"
"io/ioutil"
"os"
"github.com/mitchellh/multistep"
"testing"
)
func TestStepCreateOrResetWindowsPassword(t *testing.T) {
state := testState(t)
// Step is run after the instance is created so we will have an instance name set
state.Put("instance_name", "mock_instance")
state.Put("create_windows_password", true)
step := new(StepCreateWindowsPassword)
defer step.Cleanup(state)
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if password, ok := state.GetOk("winrm_password"); !ok || password.(string) != "MOCK_PASSWORD" {
t.Fatal("should have a password", password, ok)
}
}
func TestStepCreateOrResetWindowsPassword_passwordSet(t *testing.T) {
state := testState(t)
// Step is run after the instance is created so we will have an instance name set
state.Put("instance_name", "mock_instance")
c := state.Get("config").(*Config)
c.Comm.WinRMPassword = "password"
step := new(StepCreateWindowsPassword)
defer step.Cleanup(state)
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if password, ok := state.GetOk("winrm_password"); !ok || password.(string) != "password" {
t.Fatal("should have used existing password", password, ok)
}
}
func TestStepCreateOrResetWindowsPassword_dontNeedPassword(t *testing.T) {
state := testState(t)
// Step is run after the instance is created so we will have an instance name set
state.Put("instance_name", "mock_instance")
step := new(StepCreateWindowsPassword)
defer step.Cleanup(state)
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
}
func TestStepCreateOrResetWindowsPassword_debug(t *testing.T) {
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
state := testState(t)
// Step is run after the instance is created so we will have an instance name set
state.Put("instance_name", "mock_instance")
state.Put("create_windows_password", true)
step := new(StepCreateWindowsPassword)
step.Debug = true
step.DebugKeyPath = tf.Name()
defer step.Cleanup(state)
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if password, ok := state.GetOk("winrm_password"); !ok || password.(string) != "MOCK_PASSWORD" {
t.Fatal("should have a password", password, ok)
}
if _, err := os.Stat(tf.Name()); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestStepCreateOrResetWindowsPassword_error(t *testing.T) {
state := testState(t)
// Step is run after the instance is created so we will have an instance name set
state.Put("instance_name", "mock_instance")
state.Put("create_windows_password", true)
step := new(StepCreateWindowsPassword)
defer step.Cleanup(state)
driver := state.Get("driver").(*DriverMock)
driver.CreateOrResetWindowsPasswordErr = errors.New("error")
// run the step
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// Verify state
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
if _, ok := state.GetOk("winrm_password"); ok {
t.Fatal("should NOT have instance name")
}
}
func TestStepCreateOrResetWindowsPassword_errorOnChannel(t *testing.T) {
state := testState(t)
// Step is run after the instance is created so we will have an instance name set
state.Put("instance_name", "mock_instance")
state.Put("create_windows_password", true)
step := new(StepCreateWindowsPassword)
defer step.Cleanup(state)
driver := state.Get("driver").(*DriverMock)
errCh := make(chan error, 1)
errCh <- errors.New("error")
driver.CreateOrResetWindowsPasswordErrCh = errCh
// run the step
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// Verify state
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
if _, ok := state.GetOk("winrm_password"); ok {
t.Fatal("should NOT have instance name")
}
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
)
@ -21,7 +22,7 @@ func (s *StepWaitInstanceStartup) Run(state multistep.StateBag) multistep.StepAc
ui.Say("Waiting for any running startup script to finish...")
// Keep checking the serial port output to see if the startup script is done.
err := Retry(10, 60, 0, func() (bool, error) {
err := common.Retry(10, 60, 0, func() (bool, error) {
status, err := driver.GetInstanceMetadata(config.Zone,
instanceName, StartupScriptStatusKey)

View File

@ -0,0 +1,17 @@
package googlecompute
import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/helper/communicator"
)
// winrmConfig returns the WinRM configuration.
func winrmConfig(state multistep.StateBag) (*communicator.WinRMConfig, error) {
config := state.Get("config").(*Config)
password := state.Get("winrm_password").(string)
return &communicator.WinRMConfig{
Username: config.Comm.WinRMUser,
Password: password,
}, nil
}

View File

@ -46,15 +46,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("ui", ui)
// Run!
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// If there was an error, return that

View File

@ -108,7 +108,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
computeClient,
b.config.SSHInterface,
b.config.SSHIPVersion),
SSHConfig: SSHConfig(b.config.RunConfig.Comm.SSHUsername),
SSHConfig: SSHConfig(b.config.RunConfig.Comm.SSHUsername,
b.config.RunConfig.Comm.SSHPassword),
},
&common.StepProvision{},
&StepStopServer{},
@ -116,15 +117,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Run!
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// If there was an error, return that

View File

@ -7,6 +7,7 @@ import (
"time"
"github.com/mitchellh/multistep"
packerssh "github.com/mitchellh/packer/communicator/ssh"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
@ -60,23 +61,37 @@ func CommHost(
}
// SSHConfig returns a function that can be used for the SSH communicator
// config for connecting to the instance created over SSH using the generated
// private key.
func SSHConfig(username string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
// config for connecting to the instance created over SSH using a private key
// or a password.
func SSHConfig(username, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
privateKey := state.Get("privateKey").(string)
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
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
}
}

View File

@ -42,6 +42,11 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(Config)
ui := state.Get("ui").(packer.Ui)
if config.Comm.Type == "ssh" && config.Comm.SSHPassword != "" {
ui.Say("Not creating temporary keypair when using password.")
return multistep.ActionContinue
}
// We need the v2 compute client
computeClient, err := config.computeV2Client()
if err != nil {

View File

@ -27,7 +27,6 @@ type StepRunSourceServer struct {
func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(Config)
flavor := state.Get("flavor_id").(string)
keyName := state.Get("keyPair").(string)
ui := state.Get("ui").(packer.Ui)
// We need the v2 compute client
@ -54,21 +53,31 @@ func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction
}
ui.Say("Launching server...")
s.server, err = servers.Create(computeClient, keypairs.CreateOptsExt{
CreateOptsBuilder: servers.CreateOpts{
Name: s.Name,
ImageRef: s.SourceImage,
ImageName: s.SourceImageName,
FlavorRef: flavor,
SecurityGroups: s.SecurityGroups,
Networks: networks,
AvailabilityZone: s.AvailabilityZone,
UserData: userData,
ConfigDrive: s.ConfigDrive,
},
KeyName: keyName,
}).Extract()
serverOpts := servers.CreateOpts{
Name: s.Name,
ImageRef: s.SourceImage,
ImageName: s.SourceImageName,
FlavorRef: flavor,
SecurityGroups: s.SecurityGroups,
Networks: networks,
AvailabilityZone: s.AvailabilityZone,
UserData: userData,
ConfigDrive: s.ConfigDrive,
}
var serverOptsExt servers.CreateOptsBuilder
keyName, hasKey := state.GetOk("keyPair")
if hasKey {
serverOptsExt = keypairs.CreateOptsExt{
CreateOptsBuilder: serverOpts,
KeyName: keyName.(string),
}
} else {
serverOptsExt = serverOpts
}
s.server, err = servers.Create(computeClient, serverOptsExt).Extract()
if err != nil {
err := fmt.Errorf("Error launching source server: %s", err)
state.Put("error", err)

View File

@ -186,6 +186,13 @@ func scancodes(message string) []string {
special["<pageUp>"] = []string{"49", "c9"}
special["<pageDown>"] = []string{"51", "d1"}
special["<leftAlt>"] = []string{"38", "b8"}
special["<leftCtrl>"] = []string{"1d", "9d"}
special["<leftShift>"] = []string{"2a", "aa"}
special["<rightAlt>"] = []string{"e038", "e0b8"}
special["<rightCtrl>"] = []string{"e01d", "e09d"}
special["<rightShift>"] = []string{"36", "b6"}
shiftedChars := "!@#$%^&*()_+{}:\"~|<>?"
scancodeIndex := make(map[string]uint)
@ -214,6 +221,78 @@ func scancodes(message string) []string {
for len(message) > 0 {
var scancode []string
if strings.HasPrefix(message, "<leftAltOn>") {
scancode = []string{"38"}
message = message[len("<leftAltOn>"):]
log.Printf("Special code '<leftAltOn>' found, replacing with: 38")
}
if strings.HasPrefix(message, "<leftCtrlOn>") {
scancode = []string{"1d"}
message = message[len("<leftCtrlOn>"):]
log.Printf("Special code '<leftCtrlOn>' found, replacing with: 1d")
}
if strings.HasPrefix(message, "<leftShiftOn>") {
scancode = []string{"2a"}
message = message[len("<leftShiftOn>"):]
log.Printf("Special code '<leftShiftOn>' found, replacing with: 2a")
}
if strings.HasPrefix(message, "<leftAltOff>") {
scancode = []string{"b8"}
message = message[len("<leftAltOff>"):]
log.Printf("Special code '<leftAltOff>' found, replacing with: b8")
}
if strings.HasPrefix(message, "<leftCtrlOff>") {
scancode = []string{"9d"}
message = message[len("<leftCtrlOff>"):]
log.Printf("Special code '<leftCtrlOff>' found, replacing with: 9d")
}
if strings.HasPrefix(message, "<leftShiftOff>") {
scancode = []string{"aa"}
message = message[len("<leftShiftOff>"):]
log.Printf("Special code '<leftShiftOff>' found, replacing with: aa")
}
if strings.HasPrefix(message, "<rightAltOn>") {
scancode = []string{"e038"}
message = message[len("<rightAltOn>"):]
log.Printf("Special code '<rightAltOn>' found, replacing with: e038")
}
if strings.HasPrefix(message, "<rightCtrlOn>") {
scancode = []string{"e01d"}
message = message[len("<rightCtrlOn>"):]
log.Printf("Special code '<rightCtrlOn>' found, replacing with: e01d")
}
if strings.HasPrefix(message, "<rightShiftOn>") {
scancode = []string{"36"}
message = message[len("<rightShiftOn>"):]
log.Printf("Special code '<rightShiftOn>' found, replacing with: 36")
}
if strings.HasPrefix(message, "<rightAltOff>") {
scancode = []string{"e0b8"}
message = message[len("<rightAltOff>"):]
log.Printf("Special code '<rightAltOff>' found, replacing with: e0b8")
}
if strings.HasPrefix(message, "<rightCtrlOff>") {
scancode = []string{"e09d"}
message = message[len("<rightCtrlOff>"):]
log.Printf("Special code '<rightCtrlOff>' found, replacing with: e09d")
}
if strings.HasPrefix(message, "<rightShiftOff>") {
scancode = []string{"b6"}
message = message[len("<rightShiftOff>"):]
log.Printf("Special code '<rightShiftOff>' found, replacing with: b6")
}
if strings.HasPrefix(message, "<wait>") {
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
scancode = []string{"wait"}

View File

@ -149,7 +149,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Path: b.config.OutputDir,
},
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
Files: b.config.FloppyConfig.FloppyFiles,
Directories: b.config.FloppyConfig.FloppyDirectories,
},
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
@ -215,17 +216,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("ui", ui)
// Run
if b.config.PackerDebug {
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(state)
// If there was an error, return that

View File

@ -59,7 +59,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Path: b.config.OutputDir,
},
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
Files: b.config.FloppyConfig.FloppyFiles,
Directories: b.config.FloppyConfig.FloppyDirectories,
},
&StepImport{
Name: b.config.VMName,
@ -108,16 +109,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Run the steps.
if b.config.PackerDebug {
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(state)
// Report any errors.

View File

@ -93,7 +93,6 @@ type Config struct {
DiskDiscard string `mapstructure:"disk_discard"`
SkipCompaction bool `mapstructure:"skip_compaction"`
DiskCompression bool `mapstructure:"disk_compression"`
FloppyFiles []string `mapstructure:"floppy_files"`
Format string `mapstructure:"format"`
Headless bool `mapstructure:"headless"`
DiskImage bool `mapstructure:"disk_image"`
@ -365,7 +364,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
steps = append(steps, new(stepPrepareOutputDir),
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
Files: b.config.FloppyConfig.FloppyFiles,
Directories: b.config.FloppyConfig.FloppyDirectories,
},
new(stepCreateDisk),
new(stepCopyDisk),
@ -422,17 +422,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("ui", ui)
// Run
if b.config.PackerDebug {
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(state)
// If there was an error, return that

View File

@ -24,20 +24,12 @@ func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
log.Printf("Looking for available communicator (SSH, WinRM, etc) port between %d and %d", config.SSHHostPortMin, config.SSHHostPortMax)
var sshHostPort uint
var offset uint = 0
portRange := int(config.SSHHostPortMax - config.SSHHostPortMin)
if portRange > 0 {
// Have to check if > 0 to avoid a panic
offset = uint(rand.Intn(portRange))
}
portRange := config.SSHHostPortMax - config.SSHHostPortMin + 1
offset := uint(rand.Intn(int(portRange)))
for {
sshHostPort = offset + config.SSHHostPortMin
if sshHostPort >= config.SSHHostPortMax {
offset = 0
sshHostPort = config.SSHHostPortMin
}
log.Printf("Trying port: %d", sshHostPort)
l, err := net.Listen("tcp", fmt.Sprintf(":%d", sshHostPort))
if err == nil {
@ -45,6 +37,9 @@ func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
break
}
offset++
if offset == portRange {
offset = 0
}
}
ui.Say(fmt.Sprintf("Found port for communicator (SSH, WinRM, etc): %d.", sshHostPort))

View File

@ -35,7 +35,7 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
command, err := getCommandArgs(s.BootDrive, state)
if err != nil {
err := fmt.Errorf("Error processing QemuArggs: %s", err)
err := fmt.Errorf("Error processing QemuArgs: %s", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@ -96,12 +96,12 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
if qemuMajor >= 2 {
if config.DiskInterface == "virtio-scsi" {
deviceArgs = append(deviceArgs, "virtio-scsi-pci,id=scsi0", "scsi-hd,bus=scsi0.0,drive=drive0")
driveArgs = append(driveArgs, fmt.Sprintf("if=none,file=%s,id=drive0,cache=%s,discard=%s", imgPath, config.DiskCache, config.DiskDiscard))
driveArgs = append(driveArgs, fmt.Sprintf("if=none,file=%s,id=drive0,cache=%s,discard=%s,format=%s", imgPath, config.DiskCache, config.DiskDiscard, config.Format))
} else {
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,cache=%s,discard=%s", imgPath, config.DiskInterface, config.DiskCache, config.DiskDiscard))
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,cache=%s,discard=%s,format=%s", imgPath, config.DiskInterface, config.DiskCache, config.DiskDiscard, config.Format))
}
} else {
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,cache=%s", imgPath, config.DiskInterface, config.DiskCache))
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,cache=%s,format=%s", imgPath, config.DiskInterface, config.DiskCache, config.Format))
}
deviceArgs = append(deviceArgs, fmt.Sprintf("%s,netdev=user.0", config.NetDevice))

View File

@ -136,6 +136,12 @@ func vncSendString(c *vnc.ClientConn, original string) {
special["<end>"] = 0xFF57
special["<pageUp>"] = 0xFF55
special["<pageDown>"] = 0xFF56
special["<leftAlt>"] = 0xFFE9
special["<leftCtrl>"] = 0xFFE3
special["<leftShift>"] = 0xFFE1
special["<rightAlt>"] = 0xFFEA
special["<rightCtrl>"] = 0xFFE4
special["<rightShift>"] = 0xFFE2
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
@ -144,6 +150,174 @@ func vncSendString(c *vnc.ClientConn, original string) {
var keyCode uint32
keyShift := false
if strings.HasPrefix(original, "<leftAltOn>") {
keyCode = special["<leftAlt>"]
original = original[len("<leftAltOn>"):]
log.Printf("Special code '<leftAltOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(time.Second / 10)
// qemu is picky, so no matter what, wait a small period
time.Sleep(100 * time.Millisecond)
continue
}
if strings.HasPrefix(original, "<leftCtrlOn>") {
keyCode = special["<leftCtrlOn>"]
original = original[len("<leftCtrlOn>"):]
log.Printf("Special code '<leftCtrlOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(time.Second / 10)
// qemu is picky, so no matter what, wait a small period
time.Sleep(100 * time.Millisecond)
continue
}
if strings.HasPrefix(original, "<leftShiftOn>") {
keyCode = special["<leftShiftOn>"]
original = original[len("<leftShiftOn>"):]
log.Printf("Special code '<leftShiftOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(time.Second / 10)
// qemu is picky, so no matter what, wait a small period
time.Sleep(100 * time.Millisecond)
continue
}
if strings.HasPrefix(original, "<leftAltOff>") {
keyCode = special["<leftAltOff>"]
original = original[len("<leftAltOff>"):]
log.Printf("Special code '<leftAltOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(time.Second / 10)
// qemu is picky, so no matter what, wait a small period
time.Sleep(100 * time.Millisecond)
continue
}
if strings.HasPrefix(original, "<leftCtrlOff>") {
keyCode = special["<leftCtrlOff>"]
original = original[len("<leftCtrlOff>"):]
log.Printf("Special code '<leftCtrlOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(time.Second / 10)
// qemu is picky, so no matter what, wait a small period
time.Sleep(100 * time.Millisecond)
continue
}
if strings.HasPrefix(original, "<leftShiftOff>") {
keyCode = special["<leftShiftOff>"]
original = original[len("<leftShiftOff>"):]
log.Printf("Special code '<leftShiftOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(time.Second / 10)
// qemu is picky, so no matter what, wait a small period
time.Sleep(100 * time.Millisecond)
continue
}
if strings.HasPrefix(original, "<rightAltOn>") {
keyCode = special["<rightAltOn>"]
original = original[len("<rightAltOn>"):]
log.Printf("Special code '<rightAltOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(time.Second / 10)
// qemu is picky, so no matter what, wait a small period
time.Sleep(100 * time.Millisecond)
continue
}
if strings.HasPrefix(original, "<rightCtrlOn>") {
keyCode = special["<rightCtrlOn>"]
original = original[len("<rightCtrlOn>"):]
log.Printf("Special code '<rightCtrlOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(time.Second / 10)
// qemu is picky, so no matter what, wait a small period
time.Sleep(100 * time.Millisecond)
continue
}
if strings.HasPrefix(original, "<rightShiftOn>") {
keyCode = special["<rightShiftOn>"]
original = original[len("<rightShiftOn>"):]
log.Printf("Special code '<rightShiftOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(time.Second / 10)
// qemu is picky, so no matter what, wait a small period
time.Sleep(100 * time.Millisecond)
continue
}
if strings.HasPrefix(original, "<rightAltOff>") {
keyCode = special["<rightAltOff>"]
original = original[len("<rightAltOff>"):]
log.Printf("Special code '<rightAltOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(time.Second / 10)
// qemu is picky, so no matter what, wait a small period
time.Sleep(100 * time.Millisecond)
continue
}
if strings.HasPrefix(original, "<rightCtrlOff>") {
keyCode = special["<rightCtrlOff>"]
original = original[len("<rightCtrlOff>"):]
log.Printf("Special code '<rightCtrlOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(time.Second / 10)
// qemu is picky, so no matter what, wait a small period
time.Sleep(100 * time.Millisecond)
continue
}
if strings.HasPrefix(original, "<rightShiftOff>") {
keyCode = special["<rightShiftOff>"]
original = original[len("<rightShiftOff>"):]
log.Printf("Special code '<rightShiftOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(time.Second / 10)
// qemu is picky, so no matter what, wait a small period
time.Sleep(100 * time.Millisecond)
continue
}
if strings.HasPrefix(original, "<wait>") {
log.Printf("Special code '<wait>' found, sleeping one second")
time.Sleep(1 * time.Second)

View File

@ -10,8 +10,10 @@ import (
type ShutdownConfig struct {
ShutdownCommand string `mapstructure:"shutdown_command"`
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
RawPostShutdownDelay string `mapstructure:"post_shutdown_delay"`
ShutdownTimeout time.Duration ``
PostShutdownDelay time.Duration ``
}
func (c *ShutdownConfig) Prepare(ctx *interpolate.Context) []error {
@ -19,6 +21,10 @@ func (c *ShutdownConfig) Prepare(ctx *interpolate.Context) []error {
c.RawShutdownTimeout = "5m"
}
if c.RawPostShutdownDelay == "" {
c.RawPostShutdownDelay = "0s"
}
var errs []error
var err error
c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout)
@ -26,5 +32,10 @@ func (c *ShutdownConfig) Prepare(ctx *interpolate.Context) []error {
errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
}
c.PostShutdownDelay, err = time.ParseDuration(c.RawPostShutdownDelay)
if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing post_shutdown_delay: %s", err))
}
return errs
}

View File

@ -43,3 +43,38 @@ func TestShutdownConfigPrepare_ShutdownTimeout(t *testing.T) {
t.Fatalf("bad: %s", c.ShutdownTimeout)
}
}
func TestShutdownConfigPrepare_PostShutdownDelay(t *testing.T) {
var c *ShutdownConfig
var errs []error
// Test with a bad value
c = testShutdownConfig()
c.RawPostShutdownDelay = "this is not good"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) == 0 {
t.Fatalf("should have error")
}
// Test with default value
c = testShutdownConfig()
c.RawPostShutdownDelay = ""
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("err: %#v", errs)
}
if c.PostShutdownDelay.Nanoseconds() != 0 {
t.Fatalf("bad: %s", c.PostShutdownDelay)
}
// Test with a good one
c = testShutdownConfig()
c.RawPostShutdownDelay = "5s"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("err: %#v", errs)
}
if c.PostShutdownDelay != 5*time.Second {
t.Fatalf("bad: %s", c.PostShutdownDelay)
}
}

View File

@ -37,20 +37,12 @@ func (s *StepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
if !s.SkipNatMapping {
log.Printf("Looking for available communicator (SSH, WinRM, etc) port between %d and %d",
s.HostPortMin, s.HostPortMax)
offset := 0
portRange := int(s.HostPortMax - s.HostPortMin)
if portRange > 0 {
// Have to check if > 0 to avoid a panic
offset = rand.Intn(portRange)
}
portRange := int(s.HostPortMax - s.HostPortMin + 1)
offset := rand.Intn(portRange)
for {
sshHostPort = offset + int(s.HostPortMin)
if sshHostPort >= int(s.HostPortMax) {
offset = 0
sshHostPort = int(s.HostPortMin)
}
log.Printf("Trying port: %d", sshHostPort)
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", sshHostPort))
if err == nil {
@ -58,6 +50,9 @@ func (s *StepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
break
}
offset++
if offset == portRange {
offset = 0
}
}
// Create a forwarded port mapping to the VM

View File

@ -23,6 +23,7 @@ import (
type StepShutdown struct {
Command string
Timeout time.Duration
Delay time.Duration
}
func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction {
@ -48,6 +49,12 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction {
for {
running, _ := driver.IsRunning(vmName)
if !running {
if s.Delay.Nanoseconds() > 0 {
log.Printf("Delay for %s after shutdown to allow locks to clear...", s.Delay)
time.Sleep(s.Delay)
}
break
}

View File

@ -103,3 +103,66 @@ func TestStepShutdown_shutdownTimeout(t *testing.T) {
t.Fatal("should have error")
}
}
func TestStepShutdown_shutdownDelay(t *testing.T) {
state := testState(t)
step := new(StepShutdown)
step.Command = "poweroff"
step.Timeout = 5 * time.Second
step.Delay = 2 * time.Second
comm := new(packer.MockCommunicator)
state.Put("communicator", comm)
state.Put("vmName", "foo")
driver := state.Get("driver").(*DriverMock)
driver.IsRunningReturn = true
start := time.Now()
go func() {
time.Sleep(10 * time.Millisecond)
driver.Lock()
defer driver.Unlock()
driver.IsRunningReturn = false
}()
// Test the run
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
testDuration := time.Since(start).Seconds()
if testDuration < 2.5 || testDuration > 2.6 {
t.Fatal("incorrect duration")
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
step.Delay = 0
driver.IsRunningReturn = true
start = time.Now()
go func() {
time.Sleep(10 * time.Millisecond)
driver.Lock()
defer driver.Unlock()
driver.IsRunningReturn = false
}()
// Test the run
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
testDuration = time.Since(start).Seconds()
if testDuration > 0.6 {
t.Fatal("incorrect duration")
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
}

View File

@ -141,6 +141,12 @@ func scancodes(message string) []string {
special["<end>"] = []string{"4f", "cf"}
special["<pageUp>"] = []string{"49", "c9"}
special["<pageDown>"] = []string{"51", "d1"}
special["<leftAlt>"] = []string{"38", "b8"}
special["<leftCtrl>"] = []string{"1d", "9d"}
special["<leftShift>"] = []string{"2a", "aa"}
special["<rightAlt>"] = []string{"e038", "e0b8"}
special["<rightCtrl>"] = []string{"e01d", "e09d"}
special["<rightShift>"] = []string{"36", "b6"}
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
@ -170,6 +176,78 @@ func scancodes(message string) []string {
for len(message) > 0 {
var scancode []string
if strings.HasPrefix(message, "<leftAltOn>") {
scancode = []string{"38"}
message = message[len("<leftAltOn>"):]
log.Printf("Special code '<leftAltOn>' found, replacing with: 38")
}
if strings.HasPrefix(message, "<leftCtrlOn>") {
scancode = []string{"1d"}
message = message[len("<leftCtrlOn>"):]
log.Printf("Special code '<leftCtrlOn>' found, replacing with: 1d")
}
if strings.HasPrefix(message, "<leftShiftOn>") {
scancode = []string{"2a"}
message = message[len("<leftShiftOn>"):]
log.Printf("Special code '<leftShiftOn>' found, replacing with: 2a")
}
if strings.HasPrefix(message, "<leftAltOff>") {
scancode = []string{"b8"}
message = message[len("<leftAltOff>"):]
log.Printf("Special code '<leftAltOff>' found, replacing with: b8")
}
if strings.HasPrefix(message, "<leftCtrlOff>") {
scancode = []string{"9d"}
message = message[len("<leftCtrlOff>"):]
log.Printf("Special code '<leftCtrlOff>' found, replacing with: 9d")
}
if strings.HasPrefix(message, "<leftShiftOff>") {
scancode = []string{"aa"}
message = message[len("<leftShiftOff>"):]
log.Printf("Special code '<leftShiftOff>' found, replacing with: aa")
}
if strings.HasPrefix(message, "<rightAltOn>") {
scancode = []string{"e038"}
message = message[len("<rightAltOn>"):]
log.Printf("Special code '<rightAltOn>' found, replacing with: e038")
}
if strings.HasPrefix(message, "<rightCtrlOn>") {
scancode = []string{"e01d"}
message = message[len("<rightCtrlOn>"):]
log.Printf("Special code '<rightCtrlOn>' found, replacing with: e01d")
}
if strings.HasPrefix(message, "<rightShiftOn>") {
scancode = []string{"36"}
message = message[len("<rightShiftOn>"):]
log.Printf("Special code '<rightShiftOn>' found, replacing with: 36")
}
if strings.HasPrefix(message, "<rightAltOff>") {
scancode = []string{"e0b8"}
message = message[len("<rightAltOff>"):]
log.Printf("Special code '<rightAltOff>' found, replacing with: e0b8")
}
if strings.HasPrefix(message, "<rightCtrlOff>") {
scancode = []string{"e09d"}
message = message[len("<rightCtrlOff>"):]
log.Printf("Special code '<rightCtrlOff>' found, replacing with: e09d")
}
if strings.HasPrefix(message, "<rightShiftOff>") {
scancode = []string{"b6"}
message = message[len("<rightShiftOff>"):]
log.Printf("Special code '<rightShiftOff>' found, replacing with: b6")
}
if strings.HasPrefix(message, "<wait>") {
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
scancode = []string{"wait"}

View File

@ -39,6 +39,7 @@ type Config struct {
BootCommand []string `mapstructure:"boot_command"`
DiskSize uint `mapstructure:"disk_size"`
KeepRegistered bool `mapstructure:"keep_registered"`
GuestAdditionsMode string `mapstructure:"guest_additions_mode"`
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
@ -194,7 +195,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Path: b.config.OutputDir,
},
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
Files: b.config.FloppyConfig.FloppyFiles,
Directories: b.config.FloppyConfig.FloppyDirectories,
},
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
@ -251,6 +253,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&vboxcommon.StepShutdown{
Command: b.config.ShutdownCommand,
Timeout: b.config.ShutdownTimeout,
Delay: b.config.PostShutdownDelay,
},
new(vboxcommon.StepRemoveDevices),
&vboxcommon.StepVBoxManage{
@ -275,17 +278,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("ui", ui)
// Run
if b.config.PackerDebug {
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(state)
// If there was an error, return that

View File

@ -64,6 +64,14 @@ func (s *stepCreateVM) Cleanup(state multistep.StateBag) {
driver := state.Get("driver").(vboxcommon.Driver)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if (config.KeepRegistered) && (!cancelled && !halted) {
ui.Say("Keeping virtual machine registered with VirtualBox host (keep_registered = true)")
return
}
ui.Say("Unregistering and deleting virtual machine...")
var err error = nil

View File

@ -56,7 +56,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
new(vboxcommon.StepSuppressMessages),
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
Files: b.config.FloppyConfig.FloppyFiles,
Directories: b.config.FloppyConfig.FloppyDirectories,
},
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
@ -120,6 +121,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&vboxcommon.StepShutdown{
Command: b.config.ShutdownCommand,
Timeout: b.config.ShutdownTimeout,
Delay: b.config.PostShutdownDelay,
},
new(vboxcommon.StepRemoveDevices),
&vboxcommon.StepVBoxManage{
@ -135,16 +137,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Run the steps.
if b.config.PackerDebug {
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(state)
// Report any errors.

View File

@ -15,7 +15,7 @@ import (
)
func workstationCheckLicense() error {
matches, err := filepath.Glob("/etc/vmware/license-*")
matches, err := filepath.Glob("/etc/vmware/license-ws-*")
if err != nil {
return fmt.Errorf("Error looking for VMware license: %s", err)
}

View File

@ -168,6 +168,12 @@ func vncSendString(c *vnc.ClientConn, original string) {
special["<end>"] = 0xFF57
special["<pageUp>"] = 0xFF55
special["<pageDown>"] = 0xFF56
special["<leftAlt>"] = 0xFFE9
special["<leftCtrl>"] = 0xFFE3
special["<leftShift>"] = 0xFFE1
special["<rightAlt>"] = 0xFFEA
special["<rightCtrl>"] = 0xFFE4
special["<rightShift>"] = 0xFFE2
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
@ -176,6 +182,138 @@ func vncSendString(c *vnc.ClientConn, original string) {
var keyCode uint32
keyShift := false
if strings.HasPrefix(original, "<leftAltOn>") {
keyCode = special["<leftAlt>"]
original = original[len("<leftAltOn>"):]
log.Printf("Special code '<leftAltOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(time.Second / 10)
continue
}
if strings.HasPrefix(original, "<leftCtrlOn>") {
keyCode = special["<leftCtrlOn>"]
original = original[len("<leftCtrlOn>"):]
log.Printf("Special code '<leftCtrlOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(time.Second / 10)
continue
}
if strings.HasPrefix(original, "<leftShiftOn>") {
keyCode = special["<leftShiftOn>"]
original = original[len("<leftShiftOn>"):]
log.Printf("Special code '<leftShiftOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(time.Second / 10)
continue
}
if strings.HasPrefix(original, "<leftAltOff>") {
keyCode = special["<leftAltOff>"]
original = original[len("<leftAltOff>"):]
log.Printf("Special code '<leftAltOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(time.Second / 10)
continue
}
if strings.HasPrefix(original, "<leftCtrlOff>") {
keyCode = special["<leftCtrlOff>"]
original = original[len("<leftCtrlOff>"):]
log.Printf("Special code '<leftCtrlOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(time.Second / 10)
continue
}
if strings.HasPrefix(original, "<leftShiftOff>") {
keyCode = special["<leftShiftOff>"]
original = original[len("<leftShiftOff>"):]
log.Printf("Special code '<leftShiftOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(time.Second / 10)
continue
}
if strings.HasPrefix(original, "<rightAltOn>") {
keyCode = special["<rightAltOn>"]
original = original[len("<rightAltOn>"):]
log.Printf("Special code '<rightAltOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(time.Second / 10)
continue
}
if strings.HasPrefix(original, "<rightCtrlOn>") {
keyCode = special["<rightCtrlOn>"]
original = original[len("<rightCtrlOn>"):]
log.Printf("Special code '<rightCtrlOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(time.Second / 10)
continue
}
if strings.HasPrefix(original, "<rightShiftOn>") {
keyCode = special["<rightShiftOn>"]
original = original[len("<rightShiftOn>"):]
log.Printf("Special code '<rightShiftOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(time.Second / 10)
continue
}
if strings.HasPrefix(original, "<rightAltOff>") {
keyCode = special["<rightAltOff>"]
original = original[len("<rightAltOff>"):]
log.Printf("Special code '<rightAltOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(time.Second / 10)
continue
}
if strings.HasPrefix(original, "<rightCtrlOff>") {
keyCode = special["<rightCtrlOff>"]
original = original[len("<rightCtrlOff>"):]
log.Printf("Special code '<rightCtrlOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(time.Second / 10)
continue
}
if strings.HasPrefix(original, "<rightShiftOff>") {
keyCode = special["<rightShiftOff>"]
original = original[len("<rightShiftOff>"):]
log.Printf("Special code '<rightShiftOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(time.Second / 10)
continue
}
if strings.HasPrefix(original, "<wait>") {
log.Printf("Special code '<wait>' found, sleeping one second")
time.Sleep(1 * time.Second)

View File

@ -231,7 +231,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Force: b.config.PackerForce,
},
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
Files: b.config.FloppyConfig.FloppyFiles,
Directories: b.config.FloppyConfig.FloppyDirectories,
},
&stepRemoteUpload{
Key: "floppy_path",
@ -305,17 +306,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Run!
if b.config.PackerDebug {
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(state)
// If there was an error, return that

View File

@ -62,7 +62,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Force: b.config.PackerForce,
},
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
Files: b.config.FloppyConfig.FloppyFiles,
Directories: b.config.FloppyConfig.FloppyDirectories,
},
&StepCloneVMX{
OutputDir: b.config.OutputDir,
@ -122,16 +123,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Run the steps.
if b.config.PackerDebug {
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(state)
// Report any errors.

View File

@ -10,6 +10,7 @@ import (
"strings"
"sync"
"github.com/mitchellh/packer/helper/enumflag"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template"
)
@ -20,11 +21,14 @@ type BuildCommand struct {
func (c BuildCommand) Run(args []string) int {
var cfgColor, cfgDebug, cfgForce, cfgParallel bool
var cfgOnError string
flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars)
flags.Usage = func() { c.Ui.Say(c.Help()) }
flags.BoolVar(&cfgColor, "color", true, "")
flags.BoolVar(&cfgDebug, "debug", false, "")
flags.BoolVar(&cfgForce, "force", false, "")
flagOnError := enumflag.New(&cfgOnError, "cleanup", "abort", "ask")
flags.Var(flagOnError, "on-error", "")
flags.BoolVar(&cfgParallel, "parallel", true, "")
if err := flags.Parse(args); err != nil {
return 1
@ -99,12 +103,14 @@ func (c BuildCommand) Run(args []string) int {
log.Printf("Build debug mode: %v", cfgDebug)
log.Printf("Force build: %v", cfgForce)
log.Printf("On error: %v", cfgOnError)
// Set the debug and force mode and prepare all the builds
for _, b := range builds {
log.Printf("Preparing build: %s", b.Name())
b.SetDebug(cfgDebug)
b.SetForce(cfgForce)
b.SetOnError(cfgOnError)
warnings, err := b.Prepare()
if err != nil {
@ -284,7 +290,7 @@ Options:
-except=foo,bar,baz Build all builds other than these
-force Force a build to continue if artifacts exist, deletes existing artifacts
-machine-readable Machine-readable output
-only=foo,bar,baz Only build the given builds by name
-on-error=[cleanup|abort|ask] If the build fails do: clean up (default), abort, or ask
-parallel=false Disable parallelization (on by default)
-var 'key=value' Variable for templates, can be used multiple times.
-var-file=path JSON file containing user variables.

View File

@ -18,7 +18,7 @@ import (
const archiveTemplateEntry = ".packer-template"
var (
reName = regexp.MustCompile("^[a-zA-Z0-9-_/]+$")
reName = regexp.MustCompile("^[a-zA-Z0-9-_./]+$")
errInvalidName = fmt.Errorf("Your build name can only contain these characters: %s", reName.String())
)

View File

@ -8,7 +8,8 @@ import (
)
type FloppyConfig struct {
FloppyFiles []string `mapstructure:"floppy_files"`
FloppyFiles []string `mapstructure:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs"`
}
func (c *FloppyConfig) Prepare(ctx *interpolate.Context) []error {
@ -24,5 +25,15 @@ func (c *FloppyConfig) Prepare(ctx *interpolate.Context) []error {
}
}
if c.FloppyDirectories == nil {
c.FloppyDirectories = make([]string, 0)
}
for _, path := range c.FloppyDirectories {
if _, err := os.Stat(path); err != nil {
errs = append(errs, fmt.Errorf("Bad Floppy disk directory '%s': %s", path, err))
}
}
return errs
}

154
common/multistep_runner.go Normal file
View File

@ -0,0 +1,154 @@
package common
import (
"fmt"
"log"
"os"
"reflect"
"strings"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
func newRunner(steps []multistep.Step, config PackerConfig, ui packer.Ui) (multistep.Runner, multistep.DebugPauseFn) {
switch config.PackerOnError {
case "", "cleanup":
case "abort":
for i, step := range steps {
steps[i] = abortStep{step, ui}
}
case "ask":
for i, step := range steps {
steps[i] = askStep{step, ui}
}
}
if config.PackerDebug {
pauseFn := MultistepDebugFn(ui)
return &multistep.DebugRunner{Steps: steps, PauseFn: pauseFn}, pauseFn
} else {
return &multistep.BasicRunner{Steps: steps}, nil
}
}
// NewRunner returns a multistep.Runner that runs steps augmented with support
// for -debug and -on-error command line arguments.
func NewRunner(steps []multistep.Step, config PackerConfig, ui packer.Ui) multistep.Runner {
runner, _ := newRunner(steps, config, ui)
return runner
}
// NewRunnerWithPauseFn returns a multistep.Runner that runs steps augmented
// with support for -debug and -on-error command line arguments. With -debug it
// puts the multistep.DebugPauseFn that will pause execution between steps into
// the state under the key "pauseFn".
func NewRunnerWithPauseFn(steps []multistep.Step, config PackerConfig, ui packer.Ui, state multistep.StateBag) multistep.Runner {
runner, pauseFn := newRunner(steps, config, ui)
if pauseFn != nil {
state.Put("pauseFn", pauseFn)
}
return runner
}
func typeName(i interface{}) string {
return reflect.Indirect(reflect.ValueOf(i)).Type().Name()
}
type abortStep struct {
step multistep.Step
ui packer.Ui
}
func (s abortStep) Run(state multistep.StateBag) multistep.StepAction {
return s.step.Run(state)
}
func (s abortStep) Cleanup(state multistep.StateBag) {
if _, ok := state.GetOk(multistep.StateCancelled); ok {
s.ui.Error("Interrupted, aborting...")
os.Exit(1)
}
if _, ok := state.GetOk(multistep.StateHalted); ok {
s.ui.Error(fmt.Sprintf("Step %q failed, aborting...", typeName(s.step)))
os.Exit(1)
}
s.step.Cleanup(state)
}
type askStep struct {
step multistep.Step
ui packer.Ui
}
func (s askStep) Run(state multistep.StateBag) (action multistep.StepAction) {
for {
action = s.step.Run(state)
if action != multistep.ActionHalt {
return
}
switch ask(s.ui, typeName(s.step), state) {
case askCleanup:
return
case askAbort:
os.Exit(1)
case askRetry:
continue
}
}
}
func (s askStep) Cleanup(state multistep.StateBag) {
s.step.Cleanup(state)
}
type askResponse int
const (
askCleanup askResponse = iota
askAbort
askRetry
)
func ask(ui packer.Ui, name string, state multistep.StateBag) askResponse {
ui.Say(fmt.Sprintf("Step %q failed", name))
result := make(chan askResponse)
go func() {
result <- askPrompt(ui)
}()
for {
select {
case response := <-result:
return response
case <-time.After(100 * time.Millisecond):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return askCleanup
}
}
}
}
func askPrompt(ui packer.Ui) askResponse {
for {
line, err := ui.Ask("[c] Clean up and exit, [a] abort without cleanup, or [r] retry step (build may fail even if retry succeeds)?")
if err != nil {
log.Printf("Error asking for input: %s", err)
}
input := strings.ToLower(line) + "c"
switch input[0] {
case 'c':
return askCleanup
case 'a':
return askAbort
case 'r':
return askRetry
}
ui.Say(fmt.Sprintf("Incorrect input: %#v", line))
}
}

View File

@ -8,5 +8,6 @@ type PackerConfig struct {
PackerBuilderType string `mapstructure:"packer_builder_type"`
PackerDebug bool `mapstructure:"packer_debug"`
PackerForce bool `mapstructure:"packer_force"`
PackerOnError string `mapstructure:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables"`
}

View File

@ -1,4 +1,4 @@
package googlecompute
package common
import (
"fmt"

View File

@ -1,4 +1,4 @@
package googlecompute
package common
import (
"fmt"

View File

@ -10,15 +10,15 @@ import (
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"strings"
)
// StepCreateFloppy will create a floppy disk with the given files.
// The floppy disk doesn't support sub-directories. Only files at the
// root level are supported.
type StepCreateFloppy struct {
Files []string
Files []string
Directories []string
floppyPath string
@ -26,7 +26,7 @@ type StepCreateFloppy struct {
}
func (s *StepCreateFloppy) Run(state multistep.StateBag) multistep.StepAction {
if len(s.Files) == 0 {
if len(s.Files) == 0 && len(s.Directories) == 0 {
log.Println("No floppy files specified. Floppy disk will not be made.")
return multistep.ActionContinue
}
@ -84,22 +84,114 @@ func (s *StepCreateFloppy) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt
}
// Get the root directory to the filesystem
// Get the root directory to the filesystem and create a cache for any directories within
log.Println("Reading the root directory from the filesystem")
rootDir, err := fatFs.RootDir()
if err != nil {
state.Put("error", fmt.Errorf("Error creating floppy: %s", err))
return multistep.ActionHalt
}
cache := fsDirectoryCache(rootDir)
// Go over each file and copy it.
for _, filename := range s.Files {
ui.Message(fmt.Sprintf("Copying: %s", filename))
if err := s.addFilespec(rootDir, filename); err != nil {
state.Put("error", fmt.Errorf("Error adding file to floppy: %s", err))
// Utility functions for walking through a directory grabbing all files flatly
globFiles := func(files []string, list chan string) {
for _,filename := range files {
if strings.IndexAny(filename, "*?[") >= 0 {
matches,_ := filepath.Glob(filename)
if err != nil { continue }
for _,match := range matches {
list <- match
}
continue
}
list <- filename
}
close(list)
}
var crawlDirectoryFiles []string
crawlDirectory := func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
crawlDirectoryFiles = append(crawlDirectoryFiles, path)
ui.Message(fmt.Sprintf("Adding file: %s", path))
}
return nil
}
crawlDirectoryFiles = []string{}
// Collect files and copy them flatly...because floppy_files is broken on purpose.
var filelist chan string
filelist = make(chan string)
go globFiles(s.Files, filelist)
ui.Message("Copying files flatly from floppy_files")
for {
filename, ok := <-filelist
if !ok { break }
finfo,err := os.Stat(filename)
if err != nil {
state.Put("error", fmt.Errorf("Error trying to stat : %s : %s", filename, err))
return multistep.ActionHalt
}
// walk through directory adding files to the root of the fs
if finfo.IsDir() {
ui.Message(fmt.Sprintf("Copying directory: %s", filename))
err := filepath.Walk(filename, crawlDirectory)
if err != nil {
state.Put("error", fmt.Errorf("Error adding file from floppy_files : %s : %s", filename, err))
return multistep.ActionHalt
}
for _,crawlfilename := range crawlDirectoryFiles {
s.Add(cache, crawlfilename)
s.FilesAdded[crawlfilename] = true
}
crawlDirectoryFiles = []string{}
continue
}
// add just a single file
ui.Message(fmt.Sprintf("Copying file: %s", filename))
s.Add(cache, filename)
s.FilesAdded[filename] = true
}
ui.Message("Done copying files from floppy_files")
// Collect all paths (expanding wildcards) into pathqueue
ui.Message("Collecting paths from floppy_dirs")
var pathqueue []string
for _,filename := range s.Directories {
if strings.IndexAny(filename, "*?[") >= 0 {
matches,err := filepath.Glob(filename)
if err != nil {
state.Put("error", fmt.Errorf("Error adding path %s to floppy: %s", filename, err))
return multistep.ActionHalt
}
for _,filename := range matches {
pathqueue = append(pathqueue, filename)
}
continue
}
pathqueue = append(pathqueue, filename)
}
ui.Message(fmt.Sprintf("Resulting paths from floppy_dirs : %v", pathqueue))
// Go over each path in pathqueue and copy it.
for _,src := range pathqueue {
ui.Message(fmt.Sprintf("Recursively copying : %s", src))
err = s.Add(cache, src)
if err != nil {
state.Put("error", fmt.Errorf("Error adding path %s to floppy: %s", src, err))
return multistep.ActionHalt
}
}
ui.Message("Done copying paths from floppy_dirs")
// Set the path to the floppy so it can be used later
state.Put("floppy_path", s.floppyPath)
@ -107,6 +199,68 @@ func (s *StepCreateFloppy) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *StepCreateFloppy) Add(dircache directoryCache, src string) error {
finfo,err := os.Stat(src)
if err != nil {
return fmt.Errorf("Error adding path to floppy: %s", err)
}
// add a file
if !finfo.IsDir() {
inputF, err := os.Open(src)
if err != nil { return err }
defer inputF.Close()
d,err := dircache("")
if err != nil { return err }
entry,err := d.AddFile(path.Base(src))
if err != nil { return err }
fatFile,err := entry.File()
if err != nil { return err }
_,err = io.Copy(fatFile,inputF)
s.FilesAdded[src] = true
return err
}
// add a directory and it's subdirectories
basedirectory := filepath.Join(src, "..")
visit := func(pathname string, fi os.FileInfo, err error) error {
if err != nil { return err }
if fi.Mode().IsDir() {
base,err := removeBase(basedirectory, pathname)
if err != nil { return err }
_,err = dircache(filepath.ToSlash(base))
return err
}
directory,filename := filepath.Split(pathname)
base,err := removeBase(basedirectory, directory)
if err != nil { return err }
inputF, err := os.Open(pathname)
if err != nil { return err }
defer inputF.Close()
wd,err := dircache(filepath.ToSlash(base))
if err != nil { return err }
entry,err := wd.AddFile(filename)
if err != nil { return err }
fatFile,err := entry.File()
if err != nil { return err }
_,err = io.Copy(fatFile,inputF)
s.FilesAdded[pathname] = true
return err
}
return filepath.Walk(src, visit)
}
func (s *StepCreateFloppy) Cleanup(multistep.StateBag) {
if s.floppyPath != "" {
log.Printf("Deleting floppy disk: %s", s.floppyPath)
@ -114,85 +268,98 @@ func (s *StepCreateFloppy) Cleanup(multistep.StateBag) {
}
}
func (s *StepCreateFloppy) addFilespec(dir fs.Directory, src string) error {
// same as http://golang.org/src/pkg/path/filepath/match.go#L308
if strings.IndexAny(src, "*?[") >= 0 {
matches, err := filepath.Glob(src)
if err != nil {
return err
// removeBase will take a regular os.PathSeparator-separated path and remove the
// prefix directory base from it. Both paths are converted to their absolute
// formats before the stripping takes place.
func removeBase(base string, path string) (string,error) {
var idx int
var err error
if res,err := filepath.Abs(path); err == nil {
path = res
}
path = filepath.Clean(path)
if base,err = filepath.Abs(base); err != nil {
return path,err
}
c1,c2 := strings.Split(base, string(os.PathSeparator)), strings.Split(path, string(os.PathSeparator))
for idx = 0; idx < len(c1); idx++ {
if len(c1[idx]) == 0 && len(c2[idx]) != 0 { break }
if c1[idx] != c2[idx] {
return "", fmt.Errorf("Path %s is not prefixed by Base %s", path, base)
}
return s.addFiles(dir, matches)
}
finfo, err := os.Stat(src)
if err != nil {
return err
}
if finfo.IsDir() {
return s.addDirectory(dir, src)
}
return s.addSingleFile(dir, src)
return strings.Join(c2[idx:], string(os.PathSeparator)),nil
}
func (s *StepCreateFloppy) addFiles(dir fs.Directory, files []string) error {
for _, file := range files {
err := s.addFilespec(dir, file)
if err != nil {
return err
// fsDirectoryCache returns a function that can be used to grab the fs.Directory
// entry associated with a given path. If an fs.Directory entry is not found
// then it will be created relative to the rootDirectory argument that is
// passed.
type directoryCache func(string) (fs.Directory,error)
func fsDirectoryCache(rootDirectory fs.Directory) directoryCache {
var cache map[string]fs.Directory
cache = make(map[string]fs.Directory)
cache[""] = rootDirectory
Input,Output,Error := make(chan string),make(chan fs.Directory),make(chan error)
go func(Error chan error) {
for {
input := path.Clean(<-Input)
// found a directory, so yield it
res,ok := cache[input]
if ok {
Output <- res
continue
}
component := strings.Split(input, "/")
// directory not cached, so start at the root and walk each component
// creating them if they're not in cache
var entry fs.Directory
for i,_ := range component {
// join all of our components into a key
path := strings.Join(component[:i], "/")
// check if parent directory is cached
res,ok = cache[path]
if !ok {
// add directory into cache
directory,err := entry.AddDirectory(component[i-1])
if err != nil { Error <- err; continue }
res,err = directory.Dir()
if err != nil { Error <- err; continue }
cache[path] = res
}
// cool, found a directory
entry = res
}
// finally create our directory
directory,err := entry.AddDirectory(component[len(component)-1])
if err != nil { Error <- err; continue }
res,err = directory.Dir()
if err != nil { Error <- err; continue }
cache[input] = res
// ..and yield it
Output <- entry
}
}(Error)
getFilesystemDirectory := func(input string) (fs.Directory,error) {
Input <- input
select {
case res := <-Output:
return res,nil
case err := <-Error:
return *new(fs.Directory),err
}
}
return nil
}
func (s *StepCreateFloppy) addDirectory(dir fs.Directory, src string) error {
log.Printf("Adding directory to floppy: %s", src)
walkFn := func(path string, finfo os.FileInfo, err error) error {
if err != nil {
return err
}
if path == src {
return nil
}
if finfo.IsDir() {
return s.addDirectory(dir, path)
}
return s.addSingleFile(dir, path)
}
return filepath.Walk(src, walkFn)
}
func (s *StepCreateFloppy) addSingleFile(dir fs.Directory, src string) error {
log.Printf("Adding file to floppy: %s", src)
inputF, err := os.Open(src)
if err != nil {
return err
}
defer inputF.Close()
entry, err := dir.AddFile(filepath.Base(src))
if err != nil {
return err
}
fatFile, err := entry.File()
if err != nil {
return err
}
if _, err := io.Copy(fatFile, inputF); err != nil {
return err
}
s.FilesAdded[src] = true
return nil
return getFilesystemDirectory
}

View File

@ -7,10 +7,30 @@ import (
"io/ioutil"
"os"
"path"
"path/filepath"
"strconv"
"testing"
"log"
"strings"
"fmt"
)
const TestFixtures = "test-fixtures"
// utility function for returning a directory structure as a list of strings
func getDirectory(path string) []string {
var result []string
walk := func(path string, info os.FileInfo, err error) error {
if info.IsDir() && !strings.HasSuffix(path, "/") {
path = path + "/"
}
result = append(result, filepath.ToSlash(path))
return nil
}
filepath.Walk(path, walk)
return result
}
func TestStepCreateFloppy_Impl(t *testing.T) {
var raw interface{}
raw = new(StepCreateFloppy)
@ -191,3 +211,83 @@ func xxxTestStepCreateFloppy_notfound(t *testing.T) {
}
}
}
func TestStepCreateFloppyDirectories(t *testing.T) {
const TestName = "floppy-hier"
// file-system hierarchies
var basePath = filepath.Join(".", TestFixtures, TestName)
type contentsTest struct {
dirs []string
result []string
}
// keep in mind that .FilesAdded doesn't keep track of the target filename or directories, but rather the source filename.
directories := [][]contentsTest{
[]contentsTest{
contentsTest{dirs:[]string{"file1","file2","file3"},result:[]string{"file1","file2","file3"}},
contentsTest{dirs:[]string{"file?"},result:[]string{"file1","file2","file3"}},
contentsTest{dirs:[]string{"*"},result:[]string{"file1","file2","file3"}},
},
[]contentsTest{
contentsTest{dirs:[]string{"dir1"},result:[]string{"dir1/file1","dir1/file2","dir1/file3"}},
contentsTest{dirs:[]string{"dir1/file1","dir1/file2","dir1/file3"},result:[]string{"dir1/file1","dir1/file2","dir1/file3"}},
contentsTest{dirs:[]string{"*"},result:[]string{"dir1/file1","dir1/file2","dir1/file3"}},
contentsTest{dirs:[]string{"*/*"},result:[]string{"dir1/file1","dir1/file2","dir1/file3"}},
},
[]contentsTest{
contentsTest{dirs:[]string{"dir1"},result:[]string{"dir1/file1","dir1/subdir1/file1","dir1/subdir1/file2"}},
contentsTest{dirs:[]string{"dir2/*"},result:[]string{"dir2/subdir1/file1","dir2/subdir1/file2"}},
contentsTest{dirs:[]string{"dir2/subdir1"},result:[]string{"dir2/subdir1/file1","dir2/subdir1/file2"}},
contentsTest{dirs:[]string{"dir?"},result:[]string{"dir1/file1","dir1/subdir1/file1","dir1/subdir1/file2","dir2/subdir1/file1","dir2/subdir1/file2"}},
},
}
// create the hierarchy for each file
for i := 0; i < 2; i++ {
dir := filepath.Join(basePath, fmt.Sprintf("test-%d", i))
for _,test := range directories[i] {
// create a new state and step
state := testStepCreateFloppyState(t)
step := new(StepCreateFloppy)
// modify step.Directories with ones from testcase
step.Directories = []string{}
for _,c := range test.dirs {
step.Directories = append(step.Directories, filepath.Join(dir,filepath.FromSlash(c)))
}
log.Println(fmt.Sprintf("Trying against floppy_dirs : %v",step.Directories))
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v for %v : %v", action, step.Directories, state.Get("error"))
}
if _, ok := state.GetOk("error"); ok {
t.Fatalf("state should be ok for %v : %v", step.Directories, state.Get("error"))
}
floppy_path := state.Get("floppy_path").(string)
if _, err := os.Stat(floppy_path); err != nil {
t.Fatalf("file not found: %s for %v : %v", floppy_path, step.Directories, err)
}
// check the FilesAdded array to see if it matches
for _,rpath := range test.result {
fpath := filepath.Join(dir, filepath.FromSlash(rpath))
if !step.FilesAdded[fpath] {
t.Fatalf("unable to find file: %s for %v", fpath, step.Directories)
}
}
// cleanup the step
step.Cleanup(state)
if _, err := os.Stat(floppy_path); err == nil {
t.Fatalf("file found: %s for %v", floppy_path, step.Directories)
}
}
}
}

View File

@ -13,7 +13,6 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/mitchellh/packer/packer"
@ -105,11 +104,6 @@ func (c *comm) Start(cmd *packer.RemoteCmd) (err error) {
return
}
// A channel to keep track of our done state
doneCh := make(chan struct{})
sessionLock := new(sync.Mutex)
timedOut := false
// Start a goroutine to wait for the session to end and set the
// exit boolean and status.
go func() {
@ -118,23 +112,19 @@ func (c *comm) Start(cmd *packer.RemoteCmd) (err error) {
err := session.Wait()
exitStatus := 0
if err != nil {
exitErr, ok := err.(*ssh.ExitError)
if ok {
exitStatus = exitErr.ExitStatus()
switch err.(type) {
case *ssh.ExitError:
exitStatus = err.(*ssh.ExitError).ExitStatus()
log.Printf("Remote command exited with '%d': %s", exitStatus, cmd.Command)
case *ssh.ExitMissingError:
log.Printf("Remote command exited without exit status or exit signal.")
exitStatus = -1
default:
log.Printf("Error occurred waiting for ssh session: %s", err.Error())
exitStatus = -1
}
}
sessionLock.Lock()
defer sessionLock.Unlock()
if timedOut {
// We timed out, so set the exit status to -1
exitStatus = -1
}
log.Printf("remote command exited with '%d': %s", exitStatus, cmd.Command)
cmd.SetExited(exitStatus)
close(doneCh)
}()
return
@ -160,6 +150,7 @@ func (c *comm) UploadDir(dst string, src string, excl []string) error {
func (c *comm) DownloadDir(src string, dst string, excl []string) error {
log.Printf("Download dir '%s' to '%s'", src, dst)
scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error {
dirStack := []string{dst}
for {
fmt.Fprint(w, "\x00")
@ -178,6 +169,13 @@ func (c *comm) DownloadDir(src string, dst string, excl []string) error {
return fmt.Errorf("%s", fi[1:len(fi)])
case 'C', 'D':
break
case 'E':
dirStack = dirStack[:len(dirStack)-1]
if len(dirStack) == 1 {
fmt.Fprint(w, "\x00")
return nil
}
continue
default:
return fmt.Errorf("unexpected server response (%x)", fi[0])
}
@ -195,14 +193,16 @@ func (c *comm) DownloadDir(src string, dst string, excl []string) error {
}
log.Printf("Download dir mode:%s size:%d name:%s", mode, size, name)
dst = filepath.Join(dirStack...)
switch fi[0] {
case 'D':
err = os.MkdirAll(filepath.Join(dst, name), os.FileMode(0755))
if err != nil {
return err
}
fmt.Fprint(w, "\x00")
return nil
dirStack = append(dirStack, name)
continue
case 'C':
fmt.Fprint(w, "\x00")
err = scpDownloadFile(filepath.Join(dst, name), stdoutR, size, os.FileMode(0644))

View File

@ -7,13 +7,9 @@ import (
"os"
"sync"
"github.com/masterzen/winrm/winrm"
"github.com/masterzen/winrm"
"github.com/mitchellh/packer/packer"
"github.com/packer-community/winrmcp/winrmcp"
// This import is a bit strange, but it's needed so `make updatedeps`
// can see and download it
_ "github.com/dylanmei/winrmtest"
)
// Communicator represents the WinRM communicator
@ -40,7 +36,7 @@ func New(config *Config) (*Communicator, error) {
}
// Create the client
params := winrm.DefaultParameters()
params := *winrm.DefaultParameters
if config.TransportDecorator != nil {
params.TransportDecorator = config.TransportDecorator
@ -48,7 +44,7 @@ func New(config *Config) (*Communicator, error) {
params.Timeout = formatDuration(config.Timeout)
client, err := winrm.NewClientWithParameters(
endpoint, config.Username, config.Password, params)
endpoint, config.Username, config.Password, &params)
if err != nil {
return nil, err
}

View File

@ -0,0 +1,44 @@
{
"variables": {
"client_id": "{{env `ARM_CLIENT_ID`}}",
"client_secret": "{{env `ARM_CLIENT_SECRET`}}",
"resource_group": "{{env `ARM_RESOURCE_GROUP`}}",
"storage_account": "{{env `ARM_STORAGE_ACCOUNT`}}",
"subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}"
},
"builders": [
{
"type": "azure-arm",
"client_id": "{{user `client_id`}}",
"client_secret": "{{user `client_secret`}}",
"resource_group_name": "{{user `resource_group`}}",
"storage_account": "{{user `storage_account`}}",
"subscription_id": "{{user `subscription_id`}}",
"capture_container_name": "images",
"capture_name_prefix": "packer",
"os_type": "Linux",
"image_url": "https://my-storage-account.blob.core.windows.net/path/to/your/custom/image.vhd",
"azure_tags": {
"dept": "engineering",
"task": "image deployment"
},
"location": "West 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"
}]
}

View File

@ -0,0 +1,40 @@
{
"variables": {
"client_id": "{{env `ARM_CLIENT_ID`}}",
"client_secret": "{{env `ARM_CLIENT_SECRET`}}",
"resource_group": "{{env `ARM_RESOURCE_GROUP`}}",
"storage_account": "{{env `ARM_STORAGE_ACCOUNT`}}",
"subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}"
},
"builders": [
{
"type": "azure-arm",
"client_id": "{{user `client_id`}}",
"client_secret": "{{user `client_secret`}}",
"resource_group_name": "{{user `resource_group`}}",
"storage_account": "{{user `storage_account`}}",
"subscription_id": "{{user `subscription_id`}}",
"capture_container_name": "images",
"capture_name_prefix": "packer",
"os_type": "Windows",
"image_url": "https://my-storage-account.blob.core.windows.net/path/to/your/custom/image.vhd",
"azure_tags": {
"dept": "engineering",
"task": "image deployment"
},
"location": "West US",
"vm_size": "Standard_A2"
}
],
"provisioners": [{
"type": "powershell",
"inline": [
"dir c:\\"
]
}]
}

28
helper/enumflag/flag.go Normal file
View File

@ -0,0 +1,28 @@
package enumflag
import "fmt"
type enumFlag struct {
target *string
options []string
}
// New returns a flag.Value implementation for parsing flags with a one-of-a-set value
func New(target *string, options ...string) *enumFlag {
return &enumFlag{target: target, options: options}
}
func (f *enumFlag) String() string {
return *f.target
}
func (f *enumFlag) Set(value string) error {
for _, v := range f.options {
if v == value {
*f.target = value
return nil
}
}
return fmt.Errorf("expected one of %q", f.options)
}

2
log.go
View File

@ -13,7 +13,7 @@ const EnvLogFile = "PACKER_LOG_PATH" //Set to a file
// logOutput determines where we should send logs (if anywhere).
func logOutput() (logOutput io.Writer, err error) {
logOutput = nil
if os.Getenv(EnvLog) != "" {
if os.Getenv(EnvLog) != "" && os.Getenv(EnvLog) != "0" {
logOutput = os.Stderr
if logPath := os.Getenv(EnvLogFile); logPath != "" {

View File

@ -24,6 +24,12 @@ const (
// force build is enabled.
ForceConfigKey = "packer_force"
// This key determines what to do when a normal multistep step fails
// - "cleanup" - run cleanup steps
// - "abort" - exit without cleanup
// - "ask" - ask the user
OnErrorConfigKey = "packer_on_error"
// TemplatePathKey is the path to the template that configured this build
TemplatePathKey = "packer_template_path"
@ -67,6 +73,12 @@ type Build interface {
// When SetForce is set to true, existing artifacts from the build are
// deleted prior to the build.
SetForce(bool)
// SetOnError will determine what to do when a normal multistep step fails
// - "cleanup" - run cleanup steps
// - "abort" - exit without cleanup
// - "ask" - ask the user
SetOnError(string)
}
// A build struct represents a single build job, the result of which should
@ -86,6 +98,7 @@ type coreBuild struct {
debug bool
force bool
onError string
l sync.Mutex
prepareCalled bool
}
@ -129,6 +142,7 @@ func (b *coreBuild) Prepare() (warn []string, err error) {
BuilderTypeConfigKey: b.builderType,
DebugConfigKey: b.debug,
ForceConfigKey: b.force,
OnErrorConfigKey: b.onError,
TemplatePathKey: b.templatePath,
UserVariablesConfigKey: b.variables,
}
@ -306,6 +320,14 @@ func (b *coreBuild) SetForce(val bool) {
b.force = val
}
func (b *coreBuild) SetOnError(val string) {
if b.prepareCalled {
panic("prepare has already been called")
}
b.onError = val
}
// Cancels the build if it is running.
func (b *coreBuild) Cancel() {
b.builder.Cancel()

View File

@ -23,6 +23,7 @@ func testBuild() *coreBuild {
},
},
variables: make(map[string]string),
onError: "cleanup",
}
}
@ -32,6 +33,7 @@ func testDefaultPackerConfig() map[string]interface{} {
BuilderTypeConfigKey: "foo",
DebugConfigKey: false,
ForceConfigKey: false,
OnErrorConfigKey: "cleanup",
TemplatePathKey: "",
UserVariablesConfigKey: make(map[string]string),
}

View File

@ -1,8 +1,9 @@
package rpc
import (
"github.com/mitchellh/packer/packer"
"net/rpc"
"github.com/mitchellh/packer/packer"
)
// An implementation of packer.Build where the build is actually executed
@ -79,6 +80,12 @@ func (b *build) SetForce(val bool) {
}
}
func (b *build) SetOnError(val string) {
if err := b.client.Call("Build.SetOnError", val, new(interface{})); err != nil {
panic(err)
}
}
func (b *build) Cancel() {
if err := b.client.Call("Build.Cancel", new(interface{}), new(interface{})); err != nil {
panic(err)
@ -134,6 +141,11 @@ func (b *BuildServer) SetForce(val *bool, reply *interface{}) error {
return nil
}
func (b *BuildServer) SetOnError(val *string, reply *interface{}) error {
b.build.SetOnError(*val)
return nil
}
func (b *BuildServer) Cancel(args *interface{}, reply *interface{}) error {
b.build.Cancel()
return nil

View File

@ -2,23 +2,25 @@ package rpc
import (
"errors"
"github.com/mitchellh/packer/packer"
"reflect"
"testing"
"github.com/mitchellh/packer/packer"
)
var testBuildArtifact = &packer.MockArtifact{}
type testBuild struct {
nameCalled bool
prepareCalled bool
prepareWarnings []string
runCalled bool
runCache packer.Cache
runUi packer.Ui
setDebugCalled bool
setForceCalled bool
cancelCalled bool
nameCalled bool
prepareCalled bool
prepareWarnings []string
runCalled bool
runCache packer.Cache
runUi packer.Ui
setDebugCalled bool
setForceCalled bool
setOnErrorCalled bool
cancelCalled bool
errRunResult bool
}
@ -53,6 +55,10 @@ func (b *testBuild) SetForce(bool) {
b.setForceCalled = true
}
func (b *testBuild) SetOnError(string) {
b.setOnErrorCalled = true
}
func (b *testBuild) Cancel() {
b.cancelCalled = true
}
@ -116,6 +122,12 @@ func TestBuild(t *testing.T) {
t.Fatal("should be called")
}
// Test SetOnError
bClient.SetOnError("ask")
if !b.setOnErrorCalled {
t.Fatal("should be called")
}
// Test Cancel
bClient.Cancel()
if !b.cancelCalled {

View File

@ -30,8 +30,9 @@ type Config struct {
S3Key string `mapstructure:"s3_key_name"`
SkipClean bool `mapstructure:"skip_clean"`
Tags map[string]string `mapstructure:"tags"`
Name string `mapstructure:"ami_name"`
ctx interpolate.Context
ctx interpolate.Context
}
type PostProcessor struct {
@ -206,6 +207,45 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
// Pull AMI ID out of the completed job
createdami := *import_result.ImportImageTasks[0].ImageId
if p.config.Name != "" {
ui.Message(fmt.Sprintf("Starting rename of AMI (%s)", createdami))
resp, err := ec2conn.CopyImage(&ec2.CopyImageInput{
Name: &p.config.Name,
SourceImageId: &createdami,
SourceRegion: config.Region,
})
if err != nil {
return nil, false, fmt.Errorf("Error Copying AMI (%s): %s", createdami, err)
}
ui.Message(fmt.Sprintf("Waiting for AMI rename to complete (may take a while)"))
stateChange := awscommon.StateChangeConf{
Pending: []string{"pending"},
Target: "available",
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *resp.ImageId),
}
if _, err := awscommon.WaitForState(&stateChange); err != nil {
return nil, false, fmt.Errorf("Error waiting for AMI (%s): %s", *resp.ImageId, err)
}
ec2conn.DeregisterImage(&ec2.DeregisterImageInput{
ImageId: &createdami,
})
if err != nil {
return nil, false, fmt.Errorf("Error deregistering existing AMI: %s", err)
}
ui.Message(fmt.Sprintf("AMI rename completed"))
createdami = *resp.ImageId
}
// If we have tags, then apply them now to both the AMI and snaps
// created by the import
if len(p.config.Tags) > 0 {

View File

@ -4,13 +4,18 @@ import "fmt"
const BuilderId = "packer.post-processor.manifest"
type ArtifactFile struct {
Name string `json:"name"`
Size int64 `json:"size"`
}
type Artifact struct {
BuildName string `json:"name"`
BuilderType string `json:"builder_type"`
BuildTime int64 `json:"build_time"`
ArtifactFiles []string `json:"files"`
ArtifactId string `json:"artifact_id"`
PackerRunUUID string `json:"packer_run_uuid"`
BuildName string `json:"name"`
BuilderType string `json:"builder_type"`
BuildTime int64 `json:"build_time"`
ArtifactFiles []ArtifactFile `json:"files"`
ArtifactId string `json:"artifact_id"`
PackerRunUUID string `json:"packer_run_uuid"`
}
func (a *Artifact) BuilderId() string {
@ -18,7 +23,11 @@ func (a *Artifact) BuilderId() string {
}
func (a *Artifact) Files() []string {
return a.ArtifactFiles
var files []string
for _, af := range a.ArtifactFiles {
files = append(files, af.Name)
}
return files
}
func (a *Artifact) Id() string {

View File

@ -6,6 +6,7 @@ import (
"io/ioutil"
"log"
"os"
"path/filepath"
"time"
"github.com/mitchellh/packer/common"
@ -17,9 +18,9 @@ import (
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Filename string `mapstructure:"filename"`
ctx interpolate.Context
Filename string `mapstructure:"filename"`
StripPath bool `mapstructure:"strip_path"`
ctx interpolate.Context
}
type PostProcessor struct {
@ -54,9 +55,21 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, source packer.Artifact) (packe
artifact := &Artifact{}
var err error
var fi os.FileInfo
// Create the current artifact.
artifact.ArtifactFiles = source.Files()
for _, name := range source.Files() {
af := ArtifactFile{}
if fi, err = os.Stat(name); err == nil {
af.Size = fi.Size()
}
if p.config.StripPath {
af.Name = filepath.Base(name)
} else {
af.Name = name
}
artifact.ArtifactFiles = append(artifact.ArtifactFiles, af)
}
artifact.ArtifactId = source.Id()
artifact.BuilderType = p.config.PackerBuilderType
artifact.BuildName = p.config.PackerBuildName

View File

@ -23,6 +23,7 @@ var builtins = map[string]string{
"mitchellh.amazon.instance": "aws",
"mitchellh.virtualbox": "virtualbox",
"mitchellh.vmware": "vmware",
"mitchellh.vmware-esx": "vmware",
"pearkes.digitalocean": "digitalocean",
"packer.parallels": "parallels",
"MSOpenTech.hyperv": "hyperv",

View File

@ -109,7 +109,7 @@ func (p *Provisioner) ProvisionDownload(ui packer.Ui, comm packer.Communicator)
}
}
// if the config.Destination was a dir, download the dir
if !strings.HasSuffix(p.config.Destination, "/") {
if strings.HasSuffix(p.config.Destination, "/") {
return comm.DownloadDir(src, p.config.Destination, nil)
}

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