Merge remote-tracking branch 'upstream/master' into packer-builder-profitbricks
This commit is contained in:
commit
dca286bf38
69
CHANGELOG.md
69
CHANGELOG.md
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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.
|
17
Makefile
17
Makefile
|
@ -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}'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, ""},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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": ""
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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())
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package googlecompute
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -1,4 +1,4 @@
|
|||
package googlecompute
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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, ¶ms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}]
|
||||
}
|
|
@ -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:\\"
|
||||
]
|
||||
}]
|
||||
}
|
|
@ -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
2
log.go
|
@ -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 != "" {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue