Merge remote-tracking branch 'origin/master' into dynamic-source-ami
This commit is contained in:
commit
1b4895c684
58
CHANGELOG.md
58
CHANGELOG.md
|
@ -5,19 +5,26 @@ BACKWARDS INCOMPATIBILITIES:
|
||||||
* VNC and VRDP-like features in VirtualBox, VMware, and QEMU now configurable
|
* VNC and VRDP-like features in VirtualBox, VMware, and QEMU now configurable
|
||||||
but bind to 127.0.0.1 by default to improve security. See the relevant
|
but bind to 127.0.0.1 by default to improve security. See the relevant
|
||||||
builder docs for more info.
|
builder docs for more info.
|
||||||
|
* Docker builder requires Docker > 1.3
|
||||||
|
|
||||||
FEATURES:
|
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:
|
IMPROVEMENTS:
|
||||||
|
|
||||||
|
* core: Test floppy disk files actually exist [GH-3756]
|
||||||
* builder/amazon: Added `disable_stop_instance` option to prevent automatic
|
* builder/amazon: Added `disable_stop_instance` option to prevent automatic
|
||||||
shutdown when the build is complete [GH-3352]
|
shutdown when the build is complete [GH-3352]
|
||||||
* builder/amazon: Added `skip_region_validation` option to allow newer or
|
* builder/amazon: Added `skip_region_validation` option to allow newer or
|
||||||
custom AWS regions [GH-3598]
|
custom AWS regions [GH-3598]
|
||||||
* builder/amazon: Added `shutdown_behavior` option to support `stop` or
|
* builder/amazon: Added `shutdown_behavior` option to support `stop` or
|
||||||
`terminate` at the end of the build [GH-3556]
|
`terminate` at the end of the build [GH-3556]
|
||||||
|
* builder/amazon: Support building from scratch with amazon-chroot builder.
|
||||||
|
[GH-3855] [GH-3895]
|
||||||
* builder/azure: Now pre-validates `capture_container_name` and
|
* builder/azure: Now pre-validates `capture_container_name` and
|
||||||
`capture_name_prefix` [GH-3537]
|
`capture_name_prefix` [GH-3537]
|
||||||
* builder/azure: Support for custom images [GH-3575]
|
* builder/azure: Support for custom images [GH-3575]
|
||||||
|
@ -25,51 +32,90 @@ IMPROVEMENTS:
|
||||||
* builder/azure: Made `tenant_id` optional [GH-3643]
|
* builder/azure: Made `tenant_id` optional [GH-3643]
|
||||||
* builder/digitalocean: Use `state_timeout` for unlock and off transitions.
|
* builder/digitalocean: Use `state_timeout` for unlock and off transitions.
|
||||||
[GH-3444]
|
[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: Added support for `image_family` [GH-3503]
|
||||||
* builder/google: Use gcloud application default credentials. [GH-3655]
|
* 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/null: Can now be used with WinRM [GH-2525]
|
||||||
* builder/parallels: Now pauses between `boot_command` entries when running
|
* builder/parallels: Now pauses between `boot_command` entries when running
|
||||||
with `-debug` [GH-3547]
|
with `-debug` [GH-3547]
|
||||||
* builder/parallels: Support future versions of Parallels by using the latest
|
* builder/parallels: Support future versions of Parallels by using the latest
|
||||||
driver [GH-3673]
|
driver [GH-3673]
|
||||||
|
* builder/parallels: Add support for ctrl, shift and alt keys in `boot_command`.
|
||||||
|
[GH-3767]
|
||||||
* builder/qemu: Added `vnc_bind_address` option [GH-3574]
|
* 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/virtualbox: Now pauses between `boot_command` entries when running
|
* builder/virtualbox: Now pauses between `boot_command` entries when running
|
||||||
with `-debug` [GH-3542]
|
with `-debug` [GH-3542]
|
||||||
* builder/virtualbox: Added `vrdp_bind_address` option [GH-3566]
|
* 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/vmware: Now paused between `boot_command` entries when running with
|
* builder/vmware: Now paused between `boot_command` entries when running with
|
||||||
`-debug` [GH-3542]
|
`-debug` [GH-3542]
|
||||||
* builder/vmware: Added `vnc_bind_address` option [GH-3565]
|
* builder/vmware: Added `vnc_bind_address` option [GH-3565]
|
||||||
* builder/vmware: Adds passwords for VNC [GH-2325]
|
* builder/vmware: Adds passwords for VNC [GH-2325]
|
||||||
* builder/vmware: Handle connection to VM with more than one NIC on ESXi
|
* builder/vmware: Handle connection to VM with more than one NIC on ESXi
|
||||||
[GH-3347]
|
[GH-3347]
|
||||||
* builder/qemu: Now pauses between `boot_command` entries when running with
|
* builder/vmware: Add support for ctrl, shift and alt keys in `boot_command`.
|
||||||
`-debug` [GH-3547]
|
[GH-3767]
|
||||||
* provisioner/ansible: Improved logging and error handling [GH-3477]
|
* 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]
|
* provisioner/ansible-local: Support for ansible-galaxy [GH-3350] [GH-3836]
|
||||||
* provisioner/chef: Added `knife_command` option and added a correct default
|
* provisioner/chef: Added `knife_command` option and added a correct default
|
||||||
value for Windows [GH-3622]
|
value for Windows [GH-3622]
|
||||||
|
* provisioner/chef: Installs 64bit chef on Windows if available [GH-3848]
|
||||||
* provisioner/puppet: Added `execute_command` option [GH-3614]
|
* 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/compress: Added support for bgzf compression [GH-3501]
|
||||||
* post-processor/docker: Preserve tags when running docker push [GH-3631]
|
* 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]
|
* scripts: Added `help` target to Makefile [GH-3290]
|
||||||
|
* builder/googlecompute: Add `-force` option to delete old image before
|
||||||
|
creating new one. [GH-3918]
|
||||||
|
|
||||||
BUG FIXES:
|
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: Use `temporary_key_pair_name` when specified. [GH-3739]
|
||||||
* builder/amazon: Add 0.5 cents to discovered spot price. [GH-3662]
|
* 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/azure: check for empty resource group [GH-3606]
|
* builder/azure: check for empty resource group [GH-3606]
|
||||||
* builder/azure: fix token validity test [GH-3609]
|
* 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: 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: Re-introduce case sensitive VMX keys [GH-2707]
|
||||||
* builder/vmware: Don't check for poweron errors on ESXi [GH-3195]
|
* 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: Respect `ssh_host`/`winrm_host` on ESXi [GH-3738]
|
||||||
* builder/vmware: Do not add remotedisplay.vnc.ip to VMX data on ESXi
|
* builder/vmware: Do not add remotedisplay.vnc.ip to VMX data on ESXi
|
||||||
[GH-3740]
|
[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]
|
* 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.
|
||||||
|
|
||||||
|
## 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)
|
## 0.10.1 (May 7, 2016)
|
||||||
|
|
||||||
|
|
|
@ -80,138 +80,163 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/aws",
|
"ImportPath": "github.com/aws/aws-sdk-go/aws",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/awserr",
|
"ImportPath": "github.com/aws/aws-sdk-go/aws/awserr",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/awsutil",
|
"ImportPath": "github.com/aws/aws-sdk-go/aws/awsutil",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/client",
|
"ImportPath": "github.com/aws/aws-sdk-go/aws/client",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/client/metadata",
|
"ImportPath": "github.com/aws/aws-sdk-go/aws/client/metadata",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/corehandlers",
|
"ImportPath": "github.com/aws/aws-sdk-go/aws/corehandlers",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials",
|
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds",
|
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"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",
|
"ImportPath": "github.com/aws/aws-sdk-go/aws/defaults",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/ec2metadata",
|
"ImportPath": "github.com/aws/aws-sdk-go/aws/ec2metadata",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/request",
|
"ImportPath": "github.com/aws/aws-sdk-go/aws/request",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/aws/session",
|
"ImportPath": "github.com/aws/aws-sdk-go/aws/session",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"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",
|
"ImportPath": "github.com/aws/aws-sdk-go/private/endpoints",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol",
|
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query",
|
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"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",
|
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil",
|
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest",
|
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/restxml",
|
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/restxml",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
|
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/private/signer/v4",
|
|
||||||
"Comment": "v1.1.2",
|
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/private/waiter",
|
"ImportPath": "github.com/aws/aws-sdk-go/private/waiter",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/service/ec2",
|
"ImportPath": "github.com/aws/aws-sdk-go/service/ec2",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"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",
|
"ImportPath": "github.com/aws/aws-sdk-go/service/s3",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3iface",
|
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3iface",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3manager",
|
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3manager",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/aws/aws-sdk-go/service/sts",
|
"ImportPath": "github.com/aws/aws-sdk-go/service/sts",
|
||||||
"Comment": "v1.1.2",
|
"Comment": "v1.4.6",
|
||||||
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
|
"Rev": "6ac30507cca29249f4d49af45a8efc98b84088ee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/bgentry/speakeasy",
|
"ImportPath": "github.com/bgentry/speakeasy",
|
||||||
|
@ -221,6 +246,10 @@
|
||||||
"ImportPath": "github.com/biogo/hts/bgzf",
|
"ImportPath": "github.com/biogo/hts/bgzf",
|
||||||
"Rev": "50da7d4131a3b5c9d063932461cab4d1fafb20b0"
|
"Rev": "50da7d4131a3b5c9d063932461cab4d1fafb20b0"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||||
|
"Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/dgrijalva/jwt-go",
|
"ImportPath": "github.com/dgrijalva/jwt-go",
|
||||||
"Comment": "v3.0.0",
|
"Comment": "v3.0.0",
|
||||||
|
@ -427,6 +456,11 @@
|
||||||
"ImportPath": "github.com/pkg/sftp",
|
"ImportPath": "github.com/pkg/sftp",
|
||||||
"Rev": "e84cc8c755ca39b7b64f510fe1fffc1b51f210a5"
|
"Rev": "e84cc8c755ca39b7b64f510fe1fffc1b51f210a5"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
||||||
|
"Comment": "v1.0.0",
|
||||||
|
"Rev": "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/rackspace/gophercloud",
|
"ImportPath": "github.com/rackspace/gophercloud",
|
||||||
"Comment": "v1.0.0-810-g53d1dc4",
|
"Comment": "v1.0.0-810-g53d1dc4",
|
||||||
|
@ -516,6 +550,11 @@
|
||||||
"ImportPath": "github.com/satori/go.uuid",
|
"ImportPath": "github.com/satori/go.uuid",
|
||||||
"Rev": "d41af8bb6a7704f00bc3b7cba9355ae6a5a80048"
|
"Rev": "d41af8bb6a7704f00bc3b7cba9355ae6a5a80048"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/stretchr/testify/assert",
|
||||||
|
"Comment": "v1.1.3-19-gd77da35",
|
||||||
|
"Rev": "d77da356e56a7428ad25149ca77381849a6a5232"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/tent/http-link-go",
|
"ImportPath": "github.com/tent/http-link-go",
|
||||||
"Rev": "ac974c61c2f990f4115b119354b5e0b47550e888"
|
"Rev": "ac974c61c2f990f4115b119354b5e0b47550e888"
|
||||||
|
@ -648,31 +687,6 @@
|
||||||
{
|
{
|
||||||
"ImportPath": "gopkg.in/xmlpath.v2",
|
"ImportPath": "gopkg.in/xmlpath.v2",
|
||||||
"Rev": "860cbeca3ebcc600db0b213c0e83ad6ce91f5739"
|
"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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -53,7 +53,7 @@ fmt-examples:
|
||||||
# source files.
|
# source files.
|
||||||
generate: deps ## Generate dynamically generated code
|
generate: deps ## Generate dynamically generated code
|
||||||
go generate .
|
go generate .
|
||||||
go fmt command/plugin.go
|
gofmt -w command/plugin.go
|
||||||
|
|
||||||
test: deps ## Run unit tests
|
test: deps ## Run unit tests
|
||||||
@go test $(TEST) $(TESTARGS) -timeout=2m
|
@go test $(TEST) $(TESTARGS) -timeout=2m
|
||||||
|
|
|
@ -25,19 +25,24 @@ const BuilderId = "mitchellh.amazon.chroot"
|
||||||
// Config is the configuration that is chained through the steps and
|
// Config is the configuration that is chained through the steps and
|
||||||
// settable from the template.
|
// settable from the template.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
awscommon.AccessConfig `mapstructure:",squash"`
|
awscommon.AMIBlockDevices `mapstructure:",squash"`
|
||||||
awscommon.AMIConfig `mapstructure:",squash"`
|
awscommon.AMIConfig `mapstructure:",squash"`
|
||||||
|
awscommon.AccessConfig `mapstructure:",squash"`
|
||||||
|
|
||||||
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
|
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
|
||||||
CommandWrapper string `mapstructure:"command_wrapper"`
|
CommandWrapper string `mapstructure:"command_wrapper"`
|
||||||
CopyFiles []string `mapstructure:"copy_files"`
|
CopyFiles []string `mapstructure:"copy_files"`
|
||||||
DevicePath string `mapstructure:"device_path"`
|
DevicePath string `mapstructure:"device_path"`
|
||||||
MountPath string `mapstructure:"mount_path"`
|
FromScratch bool `mapstructure:"from_scratch"`
|
||||||
SourceAmi string `mapstructure:"source_ami"`
|
MountOptions []string `mapstructure:"mount_options"`
|
||||||
RootVolumeSize int64 `mapstructure:"root_volume_size"`
|
MountPartition int `mapstructure:"mount_partition"`
|
||||||
MountOptions []string `mapstructure:"mount_options"`
|
MountPath string `mapstructure:"mount_path"`
|
||||||
MountPartition int `mapstructure:"mount_partition"`
|
PostMountCommands []string `mapstructure:"post_mount_commands"`
|
||||||
|
PreMountCommands []string `mapstructure:"pre_mount_commands"`
|
||||||
|
RootDeviceName string `mapstructure:"root_device_name"`
|
||||||
|
RootVolumeSize int64 `mapstructure:"root_volume_size"`
|
||||||
|
SourceAmi string `mapstructure:"source_ami"`
|
||||||
|
|
||||||
ctx interpolate.Context
|
ctx interpolate.Context
|
||||||
}
|
}
|
||||||
|
@ -59,6 +64,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
Exclude: []string{
|
Exclude: []string{
|
||||||
"command_wrapper",
|
"command_wrapper",
|
||||||
|
"post_mount_commands",
|
||||||
|
"pre_mount_commands",
|
||||||
"mount_path",
|
"mount_path",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -86,7 +93,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(b.config.CopyFiles) == 0 {
|
if len(b.config.CopyFiles) == 0 && !b.config.FromScratch {
|
||||||
b.config.CopyFiles = []string{"/etc/resolv.conf"}
|
b.config.CopyFiles = []string{"/etc/resolv.conf"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,8 +109,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
b.config.MountPartition = 1
|
b.config.MountPartition = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accumulate any errors
|
// Accumulate any errors or warnings
|
||||||
var errs *packer.MultiError
|
var errs *packer.MultiError
|
||||||
|
var warns []string
|
||||||
|
|
||||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
|
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...)
|
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...)
|
||||||
|
|
||||||
|
@ -115,16 +124,49 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.config.SourceAmi == "" {
|
if b.config.FromScratch {
|
||||||
errs = packer.MultiErrorAppend(errs, errors.New("source_ami is required."))
|
if b.config.SourceAmi != "" {
|
||||||
|
warns = append(warns, "source_ami is unused when from_scratch is true")
|
||||||
|
}
|
||||||
|
if b.config.RootVolumeSize == 0 {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("root_volume_size is required with from_scratch."))
|
||||||
|
}
|
||||||
|
if len(b.config.PreMountCommands) == 0 {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("pre_mount_commands is required with from_scratch."))
|
||||||
|
}
|
||||||
|
if b.config.AMIVirtType == "" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("ami_virtualization_type is required with from_scratch."))
|
||||||
|
}
|
||||||
|
if b.config.RootDeviceName == "" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("root_device_name is required with from_scratch."))
|
||||||
|
}
|
||||||
|
if len(b.config.AMIMappings) == 0 {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("ami_block_device_mappings is required with from_scratch."))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if b.config.SourceAmi == "" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("source_ami is required."))
|
||||||
|
}
|
||||||
|
if len(b.config.AMIMappings) != 0 {
|
||||||
|
warns = append(warns, "ami_block_device_mappings are unused when from_scratch is false")
|
||||||
|
}
|
||||||
|
if b.config.RootDeviceName != "" {
|
||||||
|
warns = append(warns, "root_device_name is unused when from_scratch is false")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
return nil, errs
|
return warns, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
|
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
|
||||||
return nil, nil
|
return warns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||||
|
@ -161,11 +203,19 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
ForceDeregister: b.config.AMIForceDeregister,
|
ForceDeregister: b.config.AMIForceDeregister,
|
||||||
},
|
},
|
||||||
&StepInstanceInfo{},
|
&StepInstanceInfo{},
|
||||||
&awscommon.StepSourceAMIInfo{
|
}
|
||||||
SourceAmi: b.config.SourceAmi,
|
|
||||||
EnhancedNetworking: b.config.AMIEnhancedNetworking,
|
if !b.config.FromScratch {
|
||||||
},
|
steps = append(steps,
|
||||||
&StepCheckRootDevice{},
|
&awscommon.StepSourceAMIInfo{
|
||||||
|
SourceAmi: b.config.SourceAmi,
|
||||||
|
EnhancedNetworking: b.config.AMIEnhancedNetworking,
|
||||||
|
},
|
||||||
|
&StepCheckRootDevice{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
steps = append(steps,
|
||||||
&StepFlock{},
|
&StepFlock{},
|
||||||
&StepPrepareDevice{},
|
&StepPrepareDevice{},
|
||||||
&StepCreateVolume{
|
&StepCreateVolume{
|
||||||
|
@ -173,10 +223,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
},
|
},
|
||||||
&StepAttachVolume{},
|
&StepAttachVolume{},
|
||||||
&StepEarlyUnflock{},
|
&StepEarlyUnflock{},
|
||||||
|
&StepPreMountCommands{
|
||||||
|
Commands: b.config.PreMountCommands,
|
||||||
|
},
|
||||||
&StepMountDevice{
|
&StepMountDevice{
|
||||||
MountOptions: b.config.MountOptions,
|
MountOptions: b.config.MountOptions,
|
||||||
MountPartition: b.config.MountPartition,
|
MountPartition: b.config.MountPartition,
|
||||||
},
|
},
|
||||||
|
&StepPostMountCommands{
|
||||||
|
Commands: b.config.PostMountCommands,
|
||||||
|
},
|
||||||
&StepMountExtra{},
|
&StepMountExtra{},
|
||||||
&StepCopyFiles{},
|
&StepCopyFiles{},
|
||||||
&StepChrootProvision{},
|
&StepChrootProvision{},
|
||||||
|
@ -203,18 +259,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
&awscommon.StepCreateTags{
|
&awscommon.StepCreateTags{
|
||||||
Tags: b.config.AMITags,
|
Tags: b.config.AMITags,
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
|
|
||||||
// Run!
|
// Run!
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
b.runner = &multistep.DebugRunner{
|
|
||||||
Steps: steps,
|
|
||||||
PauseFn: common.MultistepDebugFn(ui),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.runner.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// If there was an error, return that
|
// If there was an error, return that
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package chroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"github.com/mitchellh/packer/post-processor/shell-local"
|
||||||
|
"github.com/mitchellh/packer/template/interpolate"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RunLocalCommands(commands []string, wrappedCommand CommandWrapper, ctx interpolate.Context, ui packer.Ui) error {
|
||||||
|
for _, rawCmd := range commands {
|
||||||
|
intCmd, err := interpolate.Render(rawCmd, &ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error interpolating: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
command, err := wrappedCommand(intCmd)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error wrapping command: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf("Executing command: %s", command))
|
||||||
|
comm := &shell_local.Communicator{}
|
||||||
|
cmd := &packer.RemoteCmd{Command: command}
|
||||||
|
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||||
|
return fmt.Errorf("Error executing command: %s", err)
|
||||||
|
}
|
||||||
|
if cmd.ExitStatus != 0 {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Received non-zero exit code %d from command: %s",
|
||||||
|
cmd.ExitStatus,
|
||||||
|
command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -22,40 +22,52 @@ type StepCreateVolume struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||||
image := state.Get("source_image").(*ec2.Image)
|
|
||||||
instance := state.Get("instance").(*ec2.Instance)
|
instance := state.Get("instance").(*ec2.Instance)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
// Determine the root device snapshot
|
var createVolume *ec2.CreateVolumeInput
|
||||||
log.Printf("Searching for root device of the image (%s)", *image.RootDeviceName)
|
if config.FromScratch {
|
||||||
var rootDevice *ec2.BlockDeviceMapping
|
createVolume = &ec2.CreateVolumeInput{
|
||||||
for _, device := range image.BlockDeviceMappings {
|
AvailabilityZone: instance.Placement.AvailabilityZone,
|
||||||
if *device.DeviceName == *image.RootDeviceName {
|
Size: aws.Int64(s.RootVolumeSize),
|
||||||
rootDevice = device
|
VolumeType: aws.String(ec2.VolumeTypeGp2),
|
||||||
break
|
}
|
||||||
|
} else {
|
||||||
|
// Determine the root device snapshot
|
||||||
|
image := state.Get("source_image").(*ec2.Image)
|
||||||
|
log.Printf("Searching for root device of the image (%s)", *image.RootDeviceName)
|
||||||
|
var rootDevice *ec2.BlockDeviceMapping
|
||||||
|
for _, device := range image.BlockDeviceMappings {
|
||||||
|
if *device.DeviceName == *image.RootDeviceName {
|
||||||
|
rootDevice = device
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rootDevice == nil {
|
||||||
|
err := fmt.Errorf("Couldn't find root device!")
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Creating the root volume...")
|
||||||
|
vs := *rootDevice.Ebs.VolumeSize
|
||||||
|
if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize {
|
||||||
|
vs = s.RootVolumeSize
|
||||||
|
}
|
||||||
|
|
||||||
|
createVolume = &ec2.CreateVolumeInput{
|
||||||
|
AvailabilityZone: instance.Placement.AvailabilityZone,
|
||||||
|
Size: aws.Int64(vs),
|
||||||
|
SnapshotId: rootDevice.Ebs.SnapshotId,
|
||||||
|
VolumeType: rootDevice.Ebs.VolumeType,
|
||||||
|
Iops: rootDevice.Ebs.Iops,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rootDevice == nil {
|
|
||||||
err := fmt.Errorf("Couldn't find root device!")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Creating the root volume...")
|
|
||||||
vs := *rootDevice.Ebs.VolumeSize
|
|
||||||
if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize {
|
|
||||||
vs = s.RootVolumeSize
|
|
||||||
}
|
|
||||||
createVolume := &ec2.CreateVolumeInput{
|
|
||||||
AvailabilityZone: instance.Placement.AvailabilityZone,
|
|
||||||
Size: aws.Int64(vs),
|
|
||||||
SnapshotId: rootDevice.Ebs.SnapshotId,
|
|
||||||
VolumeType: rootDevice.Ebs.VolumeType,
|
|
||||||
Iops: rootDevice.Ebs.Iops,
|
|
||||||
}
|
|
||||||
log.Printf("Create args: %+v", createVolume)
|
log.Printf("Create args: %+v", createVolume)
|
||||||
|
|
||||||
createVolumeResp, err := ec2conn.CreateVolume(createVolume)
|
createVolumeResp, err := ec2conn.CreateVolume(createVolume)
|
||||||
|
|
|
@ -33,10 +33,18 @@ type StepMountDevice struct {
|
||||||
func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
image := state.Get("source_image").(*ec2.Image)
|
|
||||||
device := state.Get("device").(string)
|
device := state.Get("device").(string)
|
||||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||||
|
|
||||||
|
var virtualizationType string
|
||||||
|
if config.FromScratch {
|
||||||
|
virtualizationType = config.AMIVirtType
|
||||||
|
} else {
|
||||||
|
image := state.Get("source_image").(*ec2.Image)
|
||||||
|
virtualizationType = *image.VirtualizationType
|
||||||
|
log.Printf("Source image virtualization type is: %s", virtualizationType)
|
||||||
|
}
|
||||||
|
|
||||||
ctx := config.ctx
|
ctx := config.ctx
|
||||||
ctx.Data = &mountPathData{Device: filepath.Base(device)}
|
ctx.Data = &mountPathData{Device: filepath.Base(device)}
|
||||||
mountPath, err := interpolate.Render(config.MountPath, &ctx)
|
mountPath, err := interpolate.Render(config.MountPath, &ctx)
|
||||||
|
@ -65,9 +73,8 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Source image virtualization type is: %s", *image.VirtualizationType)
|
|
||||||
deviceMount := device
|
deviceMount := device
|
||||||
if *image.VirtualizationType == "hvm" {
|
if virtualizationType == "hvm" {
|
||||||
deviceMount = fmt.Sprintf("%s%d", device, s.MountPartition)
|
deviceMount = fmt.Sprintf("%s%d", device, s.MountPartition)
|
||||||
}
|
}
|
||||||
state.Put("deviceMount", deviceMount)
|
state.Put("deviceMount", deviceMount)
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package chroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type postMountCommandsData struct {
|
||||||
|
Device string
|
||||||
|
MountPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StepPostMountCommands allows running arbitrary commands after mounting the
|
||||||
|
// device, but prior to the bind mount and copy steps.
|
||||||
|
type StepPostMountCommands struct {
|
||||||
|
Commands []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepPostMountCommands) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
device := state.Get("device").(string)
|
||||||
|
mountPath := state.Get("mount_path").(string)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||||
|
|
||||||
|
if len(s.Commands) == 0 {
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := config.ctx
|
||||||
|
ctx.Data = &postMountCommandsData{
|
||||||
|
Device: device,
|
||||||
|
MountPath: mountPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Running post-mount commands...")
|
||||||
|
if err := RunLocalCommands(s.Commands, wrappedCommand, ctx, ui); err != nil {
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepPostMountCommands) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package chroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type preMountCommandsData struct {
|
||||||
|
Device string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StepPreMountCommands sets up the a new block device when building from scratch
|
||||||
|
type StepPreMountCommands struct {
|
||||||
|
Commands []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepPreMountCommands) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
device := state.Get("device").(string)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||||
|
|
||||||
|
if len(s.Commands) == 0 {
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := config.ctx
|
||||||
|
ctx.Data = &preMountCommandsData{Device: device}
|
||||||
|
|
||||||
|
ui.Say("Running device setup commands...")
|
||||||
|
if err := RunLocalCommands(s.Commands, wrappedCommand, ctx, ui); err != nil {
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepPreMountCommands) Cleanup(state multistep.StateBag) {}
|
|
@ -18,22 +18,38 @@ type StepRegisterAMI struct {
|
||||||
func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||||
image := state.Get("source_image").(*ec2.Image)
|
|
||||||
snapshotId := state.Get("snapshot_id").(string)
|
snapshotId := state.Get("snapshot_id").(string)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
ui.Say("Registering the AMI...")
|
ui.Say("Registering the AMI...")
|
||||||
blockDevices := make([]*ec2.BlockDeviceMapping, len(image.BlockDeviceMappings))
|
|
||||||
for i, device := range image.BlockDeviceMappings {
|
var (
|
||||||
|
registerOpts *ec2.RegisterImageInput
|
||||||
|
mappings []*ec2.BlockDeviceMapping
|
||||||
|
image *ec2.Image
|
||||||
|
rootDeviceName string
|
||||||
|
)
|
||||||
|
|
||||||
|
if config.FromScratch {
|
||||||
|
mappings = config.AMIBlockDevices.BuildAMIDevices()
|
||||||
|
rootDeviceName = config.RootDeviceName
|
||||||
|
} else {
|
||||||
|
image = state.Get("source_image").(*ec2.Image)
|
||||||
|
mappings = image.BlockDeviceMappings
|
||||||
|
rootDeviceName = *image.RootDeviceName
|
||||||
|
}
|
||||||
|
|
||||||
|
newMappings := make([]*ec2.BlockDeviceMapping, len(mappings))
|
||||||
|
for i, device := range mappings {
|
||||||
newDevice := device
|
newDevice := device
|
||||||
if *newDevice.DeviceName == *image.RootDeviceName {
|
if *newDevice.DeviceName == rootDeviceName {
|
||||||
if newDevice.Ebs != nil {
|
if newDevice.Ebs != nil {
|
||||||
newDevice.Ebs.SnapshotId = aws.String(snapshotId)
|
newDevice.Ebs.SnapshotId = aws.String(snapshotId)
|
||||||
} else {
|
} else {
|
||||||
newDevice.Ebs = &ec2.EbsBlockDevice{SnapshotId: aws.String(snapshotId)}
|
newDevice.Ebs = &ec2.EbsBlockDevice{SnapshotId: aws.String(snapshotId)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.RootVolumeSize > *newDevice.Ebs.VolumeSize {
|
if config.FromScratch || s.RootVolumeSize > *newDevice.Ebs.VolumeSize {
|
||||||
newDevice.Ebs.VolumeSize = aws.Int64(s.RootVolumeSize)
|
newDevice.Ebs.VolumeSize = aws.Int64(s.RootVolumeSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,10 +60,20 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
newDevice.Ebs.Encrypted = nil
|
newDevice.Ebs.Encrypted = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
blockDevices[i] = newDevice
|
newMappings[i] = newDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
registerOpts := buildRegisterOpts(config, image, blockDevices)
|
if config.FromScratch {
|
||||||
|
registerOpts = &ec2.RegisterImageInput{
|
||||||
|
Name: &config.AMIName,
|
||||||
|
Architecture: aws.String(ec2.ArchitectureValuesX8664),
|
||||||
|
RootDeviceName: aws.String(rootDeviceName),
|
||||||
|
VirtualizationType: aws.String(config.AMIVirtType),
|
||||||
|
BlockDeviceMappings: newMappings,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
registerOpts = buildRegisterOpts(config, image, newMappings)
|
||||||
|
}
|
||||||
|
|
||||||
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
|
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
|
||||||
if config.AMIEnhancedNetworking {
|
if config.AMIEnhancedNetworking {
|
||||||
|
@ -88,12 +114,12 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
|
||||||
func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {}
|
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{
|
registerOpts := &ec2.RegisterImageInput{
|
||||||
Name: &config.AMIName,
|
Name: &config.AMIName,
|
||||||
Architecture: image.Architecture,
|
Architecture: image.Architecture,
|
||||||
RootDeviceName: image.RootDeviceName,
|
RootDeviceName: image.RootDeviceName,
|
||||||
BlockDeviceMappings: blockDevices,
|
BlockDeviceMappings: mappings,
|
||||||
VirtualizationType: image.VirtualizationType,
|
VirtualizationType: image.VirtualizationType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +131,5 @@ func buildRegisterOpts(config *Config, image *ec2.Image, blockDevices []*ec2.Blo
|
||||||
registerOpts.KernelId = image.KernelId
|
registerOpts.KernelId = image.KernelId
|
||||||
registerOpts.RamdiskId = image.RamdiskId
|
registerOpts.RamdiskId = image.RamdiskId
|
||||||
}
|
}
|
||||||
|
|
||||||
return registerOpts
|
return registerOpts
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,15 @@ type BlockDevice struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type BlockDevices struct {
|
type BlockDevices struct {
|
||||||
AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings"`
|
AMIBlockDevices `mapstructure:",squash"`
|
||||||
|
LaunchBlockDevices `mapstructure:",squash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AMIBlockDevices struct {
|
||||||
|
AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LaunchBlockDevices struct {
|
||||||
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings"`
|
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,10 +85,10 @@ func (b *BlockDevices) Prepare(ctx *interpolate.Context) []error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BlockDevices) BuildAMIDevices() []*ec2.BlockDeviceMapping {
|
func (b *AMIBlockDevices) BuildAMIDevices() []*ec2.BlockDeviceMapping {
|
||||||
return buildBlockDevices(b.AMIMappings)
|
return buildBlockDevices(b.AMIMappings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BlockDevices) BuildLaunchDevices() []*ec2.BlockDeviceMapping {
|
func (b *LaunchBlockDevices) BuildLaunchDevices() []*ec2.BlockDeviceMapping {
|
||||||
return buildBlockDevices(b.LaunchMappings)
|
return buildBlockDevices(b.LaunchMappings)
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,22 +124,26 @@ func TestBlockDevice(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
blockDevices := BlockDevices{
|
amiBlockDevices := AMIBlockDevices{
|
||||||
AMIMappings: []BlockDevice{*tc.Config},
|
AMIMappings: []BlockDevice{*tc.Config},
|
||||||
|
}
|
||||||
|
|
||||||
|
launchBlockDevices := LaunchBlockDevices{
|
||||||
LaunchMappings: []BlockDevice{*tc.Config},
|
LaunchMappings: []BlockDevice{*tc.Config},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := []*ec2.BlockDeviceMapping{tc.Result}
|
expected := []*ec2.BlockDeviceMapping{tc.Result}
|
||||||
got := blockDevices.BuildAMIDevices()
|
|
||||||
if !reflect.DeepEqual(expected, got) {
|
amiResults := amiBlockDevices.BuildAMIDevices()
|
||||||
|
if !reflect.DeepEqual(expected, amiResults) {
|
||||||
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
|
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
|
||||||
expected, got)
|
expected, amiResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, blockDevices.BuildLaunchDevices()) {
|
launchResults := launchBlockDevices.BuildLaunchDevices()
|
||||||
|
if !reflect.DeepEqual(expected, launchResults) {
|
||||||
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
|
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
|
||||||
expected,
|
expected, launchResults)
|
||||||
blockDevices.BuildLaunchDevices())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,19 +10,32 @@ import (
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ec2Describer interface {
|
||||||
|
DescribeInstances(*ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// modified in tests
|
||||||
|
sshHostSleepDuration = time.Second
|
||||||
|
)
|
||||||
|
|
||||||
// SSHHost returns a function that can be given to the SSH communicator
|
// SSHHost returns a function that can be given to the SSH communicator
|
||||||
// for determining the SSH address based on the instance DNS name.
|
// for determining the SSH address based on the instance DNS name.
|
||||||
func SSHHost(e *ec2.EC2, private bool) func(multistep.StateBag) (string, error) {
|
func SSHHost(e ec2Describer, private bool) func(multistep.StateBag) (string, error) {
|
||||||
return func(state multistep.StateBag) (string, error) {
|
return func(state multistep.StateBag) (string, error) {
|
||||||
for j := 0; j < 2; j++ {
|
const tries = 2
|
||||||
|
// <= with current structure to check result of describing `tries` times
|
||||||
|
for j := 0; j <= tries; j++ {
|
||||||
var host string
|
var host string
|
||||||
i := state.Get("instance").(*ec2.Instance)
|
i := state.Get("instance").(*ec2.Instance)
|
||||||
if i.VpcId != nil && *i.VpcId != "" {
|
if i.VpcId != nil && *i.VpcId != "" {
|
||||||
if i.PublicIpAddress != nil && *i.PublicIpAddress != "" && !private {
|
if i.PublicIpAddress != nil && *i.PublicIpAddress != "" && !private {
|
||||||
host = *i.PublicIpAddress
|
host = *i.PublicIpAddress
|
||||||
} else {
|
} else if i.PrivateIpAddress != nil && *i.PrivateIpAddress != "" {
|
||||||
host = *i.PrivateIpAddress
|
host = *i.PrivateIpAddress
|
||||||
}
|
}
|
||||||
|
} else if private && i.PrivateIpAddress != nil && *i.PrivateIpAddress != "" {
|
||||||
|
host = *i.PrivateIpAddress
|
||||||
} else if i.PublicDnsName != nil && *i.PublicDnsName != "" {
|
} else if i.PublicDnsName != nil && *i.PublicDnsName != "" {
|
||||||
host = *i.PublicDnsName
|
host = *i.PublicDnsName
|
||||||
}
|
}
|
||||||
|
@ -42,8 +55,8 @@ func SSHHost(e *ec2.EC2, private bool) func(multistep.StateBag) (string, error)
|
||||||
return "", fmt.Errorf("instance not found: %s", *i.InstanceId)
|
return "", fmt.Errorf("instance not found: %s", *i.InstanceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Put("instance", &r.Reservations[0].Instances[0])
|
state.Put("instance", r.Reservations[0].Instances[0])
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(sshHostSleepDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", errors.New("couldn't determine IP address for instance")
|
return "", errors.New("couldn't determine IP address for instance")
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
privateIP = "10.0.0.1"
|
||||||
|
publicIP = "192.168.1.1"
|
||||||
|
publicDNS = "public.dns.test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSSHHost(t *testing.T) {
|
||||||
|
origSshHostSleepDuration := sshHostSleepDuration
|
||||||
|
defer func() { sshHostSleepDuration = origSshHostSleepDuration }()
|
||||||
|
sshHostSleepDuration = 0
|
||||||
|
|
||||||
|
var cases = []struct {
|
||||||
|
allowTries int
|
||||||
|
vpcId string
|
||||||
|
private bool
|
||||||
|
|
||||||
|
ok bool
|
||||||
|
wantHost string
|
||||||
|
}{
|
||||||
|
{1, "", false, true, publicDNS},
|
||||||
|
{1, "", true, true, privateIP},
|
||||||
|
{1, "vpc-id", false, true, publicIP},
|
||||||
|
{1, "vpc-id", true, true, privateIP},
|
||||||
|
{2, "", false, true, publicDNS},
|
||||||
|
{2, "", true, true, privateIP},
|
||||||
|
{2, "vpc-id", false, true, publicIP},
|
||||||
|
{2, "vpc-id", true, true, privateIP},
|
||||||
|
{3, "", false, false, ""},
|
||||||
|
{3, "", true, false, ""},
|
||||||
|
{3, "vpc-id", false, false, ""},
|
||||||
|
{3, "vpc-id", true, false, ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
testSSHHost(t, c.allowTries, c.vpcId, c.private, c.ok, c.wantHost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSSHHost(t *testing.T, allowTries int, vpcId string, private, ok bool, wantHost string) {
|
||||||
|
t.Logf("allowTries=%d vpcId=%s private=%t ok=%t wantHost=%q", allowTries, vpcId, private, ok, wantHost)
|
||||||
|
|
||||||
|
e := &fakeEC2Describer{
|
||||||
|
allowTries: allowTries,
|
||||||
|
vpcId: vpcId,
|
||||||
|
privateIP: privateIP,
|
||||||
|
publicIP: publicIP,
|
||||||
|
publicDNS: publicDNS,
|
||||||
|
}
|
||||||
|
|
||||||
|
f := SSHHost(e, private)
|
||||||
|
st := &multistep.BasicStateBag{}
|
||||||
|
st.Put("instance", &ec2.Instance{
|
||||||
|
InstanceId: aws.String("instance-id"),
|
||||||
|
})
|
||||||
|
|
||||||
|
host, err := f(st)
|
||||||
|
|
||||||
|
if e.tries > allowTries {
|
||||||
|
t.Fatalf("got %d ec2 DescribeInstances tries, want %d", e.tries, allowTries)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case ok && err != nil:
|
||||||
|
t.Fatalf("expected no error, got %+v", err)
|
||||||
|
case !ok && err == nil:
|
||||||
|
t.Fatalf("expected error, got none and host %s", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
if host != wantHost {
|
||||||
|
t.Fatalf("got host %s, want %s", host, wantHost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeEC2Describer struct {
|
||||||
|
allowTries int
|
||||||
|
tries int
|
||||||
|
|
||||||
|
vpcId string
|
||||||
|
privateIP, publicIP, publicDNS string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *fakeEC2Describer) DescribeInstances(in *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
|
||||||
|
d.tries++
|
||||||
|
|
||||||
|
instance := &ec2.Instance{
|
||||||
|
InstanceId: aws.String("instance-id"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.vpcId != "" {
|
||||||
|
instance.VpcId = aws.String(d.vpcId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.tries >= d.allowTries {
|
||||||
|
instance.PublicIpAddress = aws.String(d.publicIP)
|
||||||
|
instance.PrivateIpAddress = aws.String(d.privateIP)
|
||||||
|
instance.PublicDnsName = aws.String(d.publicDNS)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := &ec2.DescribeInstancesOutput{
|
||||||
|
Reservations: []*ec2.Reservation{
|
||||||
|
{
|
||||||
|
Instances: []*ec2.Instance{instance},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
|
@ -4,9 +4,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"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/aws/session"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
retry "github.com/mitchellh/packer/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,9 +73,22 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = regionconn.CreateTags(&ec2.CreateTagsInput{
|
// Retry creating tags for about 2.5 minutes
|
||||||
Resources: resourceIds,
|
err = retry.Retry(0.2, 30, 11, func() (bool, error) {
|
||||||
Tags: ec2Tags,
|
_, 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 {
|
if err != nil {
|
||||||
|
|
|
@ -180,15 +180,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run!
|
// Run!
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
b.runner = &multistep.DebugRunner{
|
|
||||||
Steps: steps,
|
|
||||||
PauseFn: common.MultistepDebugFn(ui),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.runner.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// If there was an error, return that
|
// If there was an error, return that
|
||||||
|
|
|
@ -262,15 +262,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run!
|
// Run!
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
b.runner = &multistep.DebugRunner{
|
|
||||||
Steps: steps,
|
|
||||||
PauseFn: common.MultistepDebugFn(ui),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.runner.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// If there was an error, return that
|
// 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))
|
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)
|
b.runner.Run(b.stateBag)
|
||||||
|
|
||||||
// Report any errors.
|
// 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) {
|
func (b *Builder) getBlobEndpoint(client *AzureClient, resourceGroupName string, storageAccountName string) (string, error) {
|
||||||
account, err := client.AccountsClient.GetProperties(resourceGroupName, storageAccountName)
|
account, err := client.AccountsClient.GetProperties(resourceGroupName, storageAccountName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -76,7 +76,7 @@ func findVirtualNetworkResourceGroup(client *AzureClient, name string) (string,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resourceGroupNames) > 1 {
|
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
|
return resourceGroupNames[0], nil
|
||||||
|
@ -93,7 +93,7 @@ func findVirtualNetworkSubnet(client *AzureClient, resourceGroupName string, nam
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(*subnets.Value) > 1 {
|
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]
|
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
|
// Run the steps
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
b.runner = &multistep.DebugRunner{
|
|
||||||
Steps: steps,
|
|
||||||
PauseFn: common.MultistepDebugFn(ui),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.runner.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// If there was an error, return that
|
// If there was an error, return that
|
||||||
|
|
|
@ -27,7 +27,9 @@ const testBuilderAccBasic = `
|
||||||
"type": "test",
|
"type": "test",
|
||||||
"region": "nyc2",
|
"region": "nyc2",
|
||||||
"size": "512mb",
|
"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"`
|
StateTimeout time.Duration `mapstructure:"state_timeout"`
|
||||||
DropletName string `mapstructure:"droplet_name"`
|
DropletName string `mapstructure:"droplet_name"`
|
||||||
UserData string `mapstructure:"user_data"`
|
UserData string `mapstructure:"user_data"`
|
||||||
|
UserDataFile string `mapstructure:"user_data_file"`
|
||||||
|
|
||||||
ctx interpolate.Context
|
ctx interpolate.Context
|
||||||
}
|
}
|
||||||
|
@ -113,6 +114,16 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
errs, errors.New("image is required"))
|
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 {
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
return nil, nil, errs
|
return nil, nil, errs
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/digitalocean/godo"
|
"github.com/digitalocean/godo"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"io/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stepCreateDroplet struct {
|
type stepCreateDroplet struct {
|
||||||
|
@ -20,6 +21,18 @@ func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
|
||||||
// Create the droplet based on configuration
|
// Create the droplet based on configuration
|
||||||
ui.Say("Creating droplet...")
|
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{
|
droplet, _, err := client.Droplets.Create(&godo.DropletCreateRequest{
|
||||||
Name: c.DropletName,
|
Name: c.DropletName,
|
||||||
Region: c.Region,
|
Region: c.Region,
|
||||||
|
@ -31,7 +44,7 @@ func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
godo.DropletCreateSSHKey{ID: int(sshKeyId)},
|
godo.DropletCreateSSHKey{ID: int(sshKeyId)},
|
||||||
},
|
},
|
||||||
PrivateNetworking: c.PrivateNetworking,
|
PrivateNetworking: c.PrivateNetworking,
|
||||||
UserData: c.UserData,
|
UserData: userData,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error creating droplet: %s", err)
|
err := fmt.Errorf("Error creating droplet: %s", err)
|
||||||
|
|
|
@ -20,7 +20,7 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
dropletId := state.Get("droplet_id").(int)
|
dropletId := state.Get("droplet_id").(int)
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Creating snapshot: %v", c.SnapshotName))
|
ui.Say(fmt.Sprintf("Creating snapshot: %v", c.SnapshotName))
|
||||||
_, _, err := client.DropletActions.Snapshot(dropletId, c.SnapshotName)
|
action, _, err := client.DropletActions.Snapshot(dropletId, c.SnapshotName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error creating snapshot: %s", err)
|
err := fmt.Errorf("Error creating snapshot: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
@ -28,6 +28,17 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// With the pending state over, verify that we're in the active state
|
||||||
|
ui.Say("Waiting for snapshot to complete...")
|
||||||
|
if err := waitForActionState(godo.ActionCompleted, dropletId, action.ID,
|
||||||
|
client, 20*time.Minute); err != nil {
|
||||||
|
// If we get an error the first time, actually report it
|
||||||
|
err := fmt.Errorf("Error waiting for snapshot: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for the droplet to become unlocked first. For snapshots
|
// Wait for the droplet to become unlocked first. For snapshots
|
||||||
// this can end up taking quite a long time, so we hardcode this to
|
// this can end up taking quite a long time, so we hardcode this to
|
||||||
// 20 minutes.
|
// 20 minutes.
|
||||||
|
@ -39,16 +50,6 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the pending state over, verify that we're in the active state
|
|
||||||
ui.Say("Waiting for snapshot to complete...")
|
|
||||||
err = waitForDropletState("active", dropletId, client, c.StateTimeout)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error waiting for snapshot to complete: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Looking up snapshot ID for snapshot: %s", c.SnapshotName)
|
log.Printf("Looking up snapshot ID for snapshot: %s", c.SnapshotName)
|
||||||
images, _, err := client.Droplets.Snapshots(dropletId, nil)
|
images, _, err := client.Droplets.Snapshots(dropletId, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -57,7 +57,7 @@ func waitForDropletUnlocked(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitForState simply blocks until the droplet is in
|
// waitForDropletState simply blocks until the droplet is in
|
||||||
// a state we expect, while eventually timing out.
|
// a state we expect, while eventually timing out.
|
||||||
func waitForDropletState(
|
func waitForDropletState(
|
||||||
desiredState string, dropletId int,
|
desiredState string, dropletId int,
|
||||||
|
@ -106,3 +106,53 @@ func waitForDropletState(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// waitForActionState simply blocks until the droplet action is in
|
||||||
|
// a state we expect, while eventually timing out.
|
||||||
|
func waitForActionState(
|
||||||
|
desiredState string, dropletId, actionId int,
|
||||||
|
client *godo.Client, timeout time.Duration) error {
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
result := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
attempts := 0
|
||||||
|
for {
|
||||||
|
attempts += 1
|
||||||
|
|
||||||
|
log.Printf("Checking action status... (attempt: %d)", attempts)
|
||||||
|
action, _, err := client.DropletActions.Get(dropletId, actionId)
|
||||||
|
if err != nil {
|
||||||
|
result <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if action.Status == desiredState {
|
||||||
|
result <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait 3 seconds in between
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
// Verify we shouldn't exit
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
// We finished, so just exit the goroutine
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// Keep going
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Printf("Waiting for up to %d seconds for action to become %s", timeout/time.Second, desiredState)
|
||||||
|
select {
|
||||||
|
case err := <-result:
|
||||||
|
return err
|
||||||
|
case <-time.After(timeout):
|
||||||
|
err := fmt.Errorf("Timeout while waiting to for action to become '%s'", desiredState)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -78,15 +78,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
state.Put("driver", driver)
|
state.Put("driver", driver)
|
||||||
|
|
||||||
// Run!
|
// Run!
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
b.runner = &multistep.DebugRunner{
|
|
||||||
Steps: steps,
|
|
||||||
PauseFn: common.MultistepDebugFn(ui),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.runner.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// If there was an error, return that
|
// If there was an error, return that
|
||||||
|
|
|
@ -2,7 +2,6 @@ package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -10,12 +9,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ActiveState/tail"
|
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
@ -30,42 +27,35 @@ type Communicator struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Communicator) Start(remote *packer.RemoteCmd) error {
|
func (c *Communicator) Start(remote *packer.RemoteCmd) error {
|
||||||
// Create a temporary file to store the output. Because of a bug in
|
var cmd *exec.Cmd
|
||||||
// Docker, sometimes all the output doesn't properly show up. This
|
if c.Config.Pty {
|
||||||
// file will capture ALL of the output, and we'll read that.
|
cmd = exec.Command("docker", "exec", "-i", "-t", c.ContainerId, "/bin/sh", "-c", fmt.Sprintf("(%s)", remote.Command))
|
||||||
//
|
} else {
|
||||||
// https://github.com/dotcloud/docker/issues/2625
|
cmd = exec.Command("docker", "exec", "-i", c.ContainerId, "/bin/sh", "-c", fmt.Sprintf("(%s)", remote.Command))
|
||||||
outputFile, err := ioutil.TempFile(c.HostDir, "cmd")
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
stdin_w io.WriteCloser
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
stdin_w, err = cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
outputFile.Close()
|
|
||||||
|
|
||||||
// This file will store the exit code of the command once it is complete.
|
stderr_r, err := cmd.StderrPipe()
|
||||||
exitCodePath := outputFile.Name() + "-exit"
|
if err != nil {
|
||||||
|
return err
|
||||||
var cmd *exec.Cmd
|
|
||||||
if c.canExec() {
|
|
||||||
if c.Config.Pty {
|
|
||||||
cmd = exec.Command("docker", "exec", "-i", "-t", c.ContainerId, "/bin/sh")
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command("docker", "exec", "-i", c.ContainerId, "/bin/sh")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command("docker", "attach", c.ContainerId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stdin_w, err := cmd.StdinPipe()
|
stdout_r, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We have to do some cleanup since run was never called
|
|
||||||
os.Remove(outputFile.Name())
|
|
||||||
os.Remove(exitCodePath)
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the actual command in a goroutine so that Start doesn't block
|
// Run the actual command in a goroutine so that Start doesn't block
|
||||||
go c.run(cmd, remote, stdin_w, outputFile, exitCodePath)
|
go c.run(cmd, remote, stdin_w, stdout_r, stderr_r)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -179,7 +169,7 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error
|
||||||
|
|
||||||
// Make the directory, then copy into it
|
// Make the directory, then copy into it
|
||||||
cmd := &packer.RemoteCmd{
|
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),
|
containerDst, containerSrc, containerDst),
|
||||||
}
|
}
|
||||||
if err := c.Start(cmd); err != nil {
|
if err := c.Start(cmd); err != nil {
|
||||||
|
@ -237,105 +227,51 @@ func (c *Communicator) DownloadDir(src string, dst string, exclude []string) err
|
||||||
return fmt.Errorf("DownloadDir is not implemented for docker")
|
return fmt.Errorf("DownloadDir is not implemented for docker")
|
||||||
}
|
}
|
||||||
|
|
||||||
// canExec tells us whether `docker exec` is supported
|
|
||||||
func (c *Communicator) canExec() bool {
|
|
||||||
execConstraint, err := version.NewConstraint(">= 1.4.0")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return execConstraint.Check(c.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runs the given command and blocks until completion
|
// Runs the given command and blocks until completion
|
||||||
func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin_w io.WriteCloser, outputFile *os.File, exitCodePath string) {
|
func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin io.WriteCloser, stdout, stderr io.ReadCloser) {
|
||||||
// For Docker, remote communication must be serialized since it
|
// For Docker, remote communication must be serialized since it
|
||||||
// only supports single execution.
|
// only supports single execution.
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
// Clean up after ourselves by removing our temporary files
|
wg := sync.WaitGroup{}
|
||||||
defer os.Remove(outputFile.Name())
|
repeat := func(w io.Writer, r io.ReadCloser) {
|
||||||
defer os.Remove(exitCodePath)
|
io.Copy(w, r)
|
||||||
|
r.Close()
|
||||||
// Tail the output file and send the data to the stdout listener
|
wg.Done()
|
||||||
tail, err := tail.TailFile(outputFile.Name(), tail.Config{
|
|
||||||
Poll: true,
|
|
||||||
ReOpen: true,
|
|
||||||
Follow: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error tailing output file: %s", err)
|
|
||||||
remote.SetExited(254)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer tail.Stop()
|
|
||||||
|
|
||||||
// Modify the remote command so that all the output of the commands
|
if remote.Stdout != nil {
|
||||||
// go to a single file and so that the exit code is redirected to
|
wg.Add(1)
|
||||||
// a single file. This lets us determine both when the command
|
go repeat(remote.Stdout, stdout)
|
||||||
// is truly complete (because the file will have data), what the
|
}
|
||||||
// exit status is (because Docker loses it because of the pty, not
|
|
||||||
// Docker's fault), and get the output (Docker bug).
|
if remote.Stderr != nil {
|
||||||
remoteCmd := fmt.Sprintf("(%s) >%s 2>&1; echo $? >%s",
|
wg.Add(1)
|
||||||
remote.Command,
|
go repeat(remote.Stderr, stderr)
|
||||||
filepath.Join(c.ContainerDir, filepath.Base(outputFile.Name())),
|
}
|
||||||
filepath.Join(c.ContainerDir, filepath.Base(exitCodePath)))
|
|
||||||
|
|
||||||
// Start the command
|
// Start the command
|
||||||
log.Printf("Executing in container %s: %#v", c.ContainerId, remoteCmd)
|
log.Printf("Executing %s:", strings.Join(cmd.Args, " "))
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
log.Printf("Error executing: %s", err)
|
log.Printf("Error executing: %s", err)
|
||||||
remote.SetExited(254)
|
remote.SetExited(254)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer stdin_w.Close()
|
|
||||||
|
|
||||||
// This sleep needs to be here because of the issue linked to below.
|
|
||||||
// Basically, without it, Docker will hang on reading stdin forever,
|
|
||||||
// and won't see what we write, for some reason.
|
|
||||||
//
|
|
||||||
// https://github.com/dotcloud/docker/issues/2628
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
|
|
||||||
stdin_w.Write([]byte(remoteCmd + "\n"))
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Start a goroutine to read all the lines out of the logs. These channels
|
|
||||||
// allow us to stop the go-routine and wait for it to be stopped.
|
|
||||||
stopTailCh := make(chan struct{})
|
|
||||||
doneCh := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
defer close(doneCh)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-tail.Dead():
|
|
||||||
return
|
|
||||||
case line := <-tail.Lines:
|
|
||||||
if remote.Stdout != nil {
|
|
||||||
remote.Stdout.Write([]byte(line.Text + "\n"))
|
|
||||||
} else {
|
|
||||||
log.Printf("Command stdout: %#v", line.Text)
|
|
||||||
}
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
// If we're done, then return. Otherwise, keep grabbing
|
|
||||||
// data. This gives us a chance to flush all the lines
|
|
||||||
// out of the tailed file.
|
|
||||||
select {
|
|
||||||
case <-stopTailCh:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var exitRaw []byte
|
|
||||||
var exitStatus int
|
var exitStatus int
|
||||||
var exitStatusRaw int64
|
|
||||||
err = cmd.Wait()
|
if remote.Stdin != nil {
|
||||||
|
go func() {
|
||||||
|
io.Copy(stdin, remote.Stdin)
|
||||||
|
// close stdin to support commands that wait for stdin to be closed before exiting.
|
||||||
|
stdin.Close()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
err := cmd.Wait()
|
||||||
|
|
||||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
exitStatus = 1
|
exitStatus = 1
|
||||||
|
|
||||||
|
@ -344,45 +280,8 @@ func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin_w io.W
|
||||||
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
||||||
exitStatus = status.ExitStatus()
|
exitStatus = status.ExitStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Say that we ended, since if Docker itself failed, then
|
|
||||||
// the command must've not run, or so we assume
|
|
||||||
goto REMOTE_EXIT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the exit code to appear in our file...
|
|
||||||
log.Println("Waiting for exit code to appear for remote command...")
|
|
||||||
for {
|
|
||||||
fi, err := os.Stat(exitCodePath)
|
|
||||||
if err == nil && fi.Size() > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the exit code
|
|
||||||
exitRaw, err = ioutil.ReadFile(exitCodePath)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error executing: %s", err)
|
|
||||||
exitStatus = 254
|
|
||||||
goto REMOTE_EXIT
|
|
||||||
}
|
|
||||||
|
|
||||||
exitStatusRaw, err = strconv.ParseInt(string(bytes.TrimSpace(exitRaw)), 10, 0)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error executing: %s", err)
|
|
||||||
exitStatus = 254
|
|
||||||
goto REMOTE_EXIT
|
|
||||||
}
|
|
||||||
exitStatus = int(exitStatusRaw)
|
|
||||||
log.Printf("Executed command exit status: %d", exitStatus)
|
|
||||||
|
|
||||||
REMOTE_EXIT:
|
|
||||||
// Wait for the tail to finish
|
|
||||||
close(stopTailCh)
|
|
||||||
<-doneCh
|
|
||||||
|
|
||||||
// Set the exit status which triggers waiters
|
// Set the exit status which triggers waiters
|
||||||
remote.SetExited(exitStatus)
|
remote.SetExited(exitStatus)
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,11 +35,13 @@ type Config struct {
|
||||||
|
|
||||||
// This is used to login to dockerhub to pull a private base container. For
|
// This is used to login to dockerhub to pull a private base container. For
|
||||||
// pushing to dockerhub, see the docker post-processors
|
// pushing to dockerhub, see the docker post-processors
|
||||||
Login bool
|
Login bool
|
||||||
LoginEmail string `mapstructure:"login_email"`
|
LoginEmail string `mapstructure:"login_email"`
|
||||||
LoginPassword string `mapstructure:"login_password"`
|
LoginPassword string `mapstructure:"login_password"`
|
||||||
LoginServer string `mapstructure:"login_server"`
|
LoginServer string `mapstructure:"login_server"`
|
||||||
LoginUsername string `mapstructure:"login_username"`
|
LoginUsername string `mapstructure:"login_username"`
|
||||||
|
EcrLogin bool `mapstructure:"ecr_login"`
|
||||||
|
AwsAccessConfig `mapstructure:",squash"`
|
||||||
|
|
||||||
ctx interpolate.Context
|
ctx interpolate.Context
|
||||||
}
|
}
|
||||||
|
@ -107,6 +109,10 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.EcrLogin && c.LoginServer == "" {
|
||||||
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("ECR login requires login server to be provided."))
|
||||||
|
}
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
return nil, nil, errs
|
return nil, nil, errs
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ecr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AwsAccessConfig struct {
|
||||||
|
AccessKey string `mapstructure:"aws_access_key"`
|
||||||
|
SecretKey string `mapstructure:"aws_secret_key"`
|
||||||
|
Token string `mapstructure:"aws_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config returns a valid aws.Config object for access to AWS services, or
|
||||||
|
// an error if the authentication and region couldn't be resolved
|
||||||
|
func (c *AwsAccessConfig) config(region string) (*aws.Config, error) {
|
||||||
|
var creds *credentials.Credentials
|
||||||
|
|
||||||
|
config := aws.NewConfig().WithRegion(region).WithMaxRetries(11)
|
||||||
|
sess := session.New(config)
|
||||||
|
creds = credentials.NewChainCredentials([]credentials.Provider{
|
||||||
|
&credentials.StaticProvider{Value: credentials.Value{
|
||||||
|
AccessKeyID: c.AccessKey,
|
||||||
|
SecretAccessKey: c.SecretKey,
|
||||||
|
SessionToken: c.Token,
|
||||||
|
}},
|
||||||
|
&credentials.EnvProvider{},
|
||||||
|
&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
|
||||||
|
&ec2rolecreds.EC2RoleProvider{
|
||||||
|
Client: ec2metadata.New(sess),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return config.WithCredentials(creds), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a login token for Amazon AWS ECR. Returns username and password
|
||||||
|
// or an error.
|
||||||
|
func (c *AwsAccessConfig) EcrGetLogin(ecrUrl string) (string, string, error) {
|
||||||
|
|
||||||
|
exp := regexp.MustCompile("(?:http://|https://|)([0-9]*)\\.dkr\\.ecr\\.(.*)\\.amazonaws\\.com.*")
|
||||||
|
splitUrl := exp.FindStringSubmatch(ecrUrl)
|
||||||
|
accountId := splitUrl[1]
|
||||||
|
region := splitUrl[2]
|
||||||
|
|
||||||
|
log.Println(fmt.Sprintf("Getting ECR token for account: %s in %s..", accountId, region))
|
||||||
|
|
||||||
|
awsConfig, err := c.config(region)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := session.NewSession(awsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("failed to create session: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
service := ecr.New(session)
|
||||||
|
|
||||||
|
params := &ecr.GetAuthorizationTokenInput{
|
||||||
|
RegistryIds: []*string{
|
||||||
|
aws.String(accountId),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp, err := service.GetAuthorizationToken(params)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, err := base64.StdEncoding.DecodeString(*resp.AuthorizationData[0].AuthorizationToken)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("Error decoding ECR AuthorizationToken: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authParts := strings.SplitN(string(auth), ":", 2)
|
||||||
|
log.Printf("Successfully got login for ECR: %s", ecrUrl)
|
||||||
|
|
||||||
|
return authParts[0], authParts[1], nil
|
||||||
|
}
|
|
@ -21,7 +21,22 @@ func (s *StepPull) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Pulling Docker image: %s", config.Image))
|
ui.Say(fmt.Sprintf("Pulling Docker image: %s", config.Image))
|
||||||
|
|
||||||
if config.Login {
|
if config.EcrLogin {
|
||||||
|
ui.Message("Fetching ECR credentials...")
|
||||||
|
|
||||||
|
username, password, err := config.EcrGetLogin(config.LoginServer)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error fetching ECR credentials: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
config.LoginUsername = username
|
||||||
|
config.LoginPassword = password
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Login || config.EcrLogin {
|
||||||
ui.Message("Logging in...")
|
ui.Message("Logging in...")
|
||||||
err := driver.Login(
|
err := driver.Login(
|
||||||
config.LoginServer,
|
config.LoginServer,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
// Artifact represents a GCE image as the result of a Packer build.
|
// Artifact represents a GCE image as the result of a Packer build.
|
||||||
type Artifact struct {
|
type Artifact struct {
|
||||||
image Image
|
image *Image
|
||||||
driver Driver
|
driver Driver
|
||||||
config *Config
|
config *Config
|
||||||
}
|
}
|
||||||
|
@ -41,16 +41,16 @@ func (a *Artifact) String() string {
|
||||||
|
|
||||||
func (a *Artifact) State(name string) interface{} {
|
func (a *Artifact) State(name string) interface{} {
|
||||||
switch name {
|
switch name {
|
||||||
case "ImageName":
|
case "ImageName":
|
||||||
return a.image.Name
|
return a.image.Name
|
||||||
case "ImageSizeGb":
|
case "ImageSizeGb":
|
||||||
return a.image.SizeGb
|
return a.image.SizeGb
|
||||||
case "AccountFilePath":
|
case "AccountFilePath":
|
||||||
return a.config.AccountFile
|
return a.config.AccountFile
|
||||||
case "ProjectId":
|
case "ProjectId":
|
||||||
return a.config.ProjectId
|
return a.config.ProjectId
|
||||||
case "BuildZone":
|
case "BuildZone":
|
||||||
return a.config.Zone
|
return a.config.Zone
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,13 +58,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
&StepCreateInstance{
|
&StepCreateInstance{
|
||||||
Debug: b.config.PackerDebug,
|
Debug: b.config.PackerDebug,
|
||||||
},
|
},
|
||||||
|
&StepCreateWindowsPassword{
|
||||||
|
Debug: b.config.PackerDebug,
|
||||||
|
DebugKeyPath: fmt.Sprintf("gce_windows_%s.pem", b.config.PackerBuildName),
|
||||||
|
},
|
||||||
&StepInstanceInfo{
|
&StepInstanceInfo{
|
||||||
Debug: b.config.PackerDebug,
|
Debug: b.config.PackerDebug,
|
||||||
},
|
},
|
||||||
&communicator.StepConnect{
|
&communicator.StepConnect{
|
||||||
Config: &b.config.Comm,
|
Config: &b.config.Comm,
|
||||||
Host: commHost,
|
Host: commHost,
|
||||||
SSHConfig: sshConfig,
|
SSHConfig: sshConfig,
|
||||||
|
WinRMConfig: winrmConfig,
|
||||||
},
|
},
|
||||||
new(common.StepProvision),
|
new(common.StepProvision),
|
||||||
new(StepWaitInstanceStartup),
|
new(StepWaitInstanceStartup),
|
||||||
|
@ -73,14 +78,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the steps.
|
// Run the steps.
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
b.runner = &multistep.DebugRunner{
|
|
||||||
Steps: steps,
|
|
||||||
PauseFn: common.MultistepDebugFn(ui),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
|
||||||
}
|
|
||||||
b.runner.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// Report any errors.
|
// Report any errors.
|
||||||
|
@ -93,7 +91,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
}
|
}
|
||||||
|
|
||||||
artifact := &Artifact{
|
artifact := &Artifact{
|
||||||
image: state.Get("image").(Image),
|
image: state.Get("image").(*Image),
|
||||||
driver: driver,
|
driver: driver,
|
||||||
config: b.config,
|
config: b.config,
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,10 +49,11 @@ type Config struct {
|
||||||
UseInternalIP bool `mapstructure:"use_internal_ip"`
|
UseInternalIP bool `mapstructure:"use_internal_ip"`
|
||||||
Zone string `mapstructure:"zone"`
|
Zone string `mapstructure:"zone"`
|
||||||
|
|
||||||
Account AccountFile
|
Account AccountFile
|
||||||
privateKeyBytes []byte
|
privateKeyBytes []byte
|
||||||
stateTimeout time.Duration
|
stateTimeout time.Duration
|
||||||
ctx interpolate.Context
|
imageAlreadyExists bool
|
||||||
|
ctx interpolate.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
package googlecompute
|
package googlecompute
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// Driver is the interface that has to be implemented to communicate
|
// Driver is the interface that has to be implemented to communicate
|
||||||
// with GCE. The Driver interface exists mostly to allow a mock implementation
|
// with GCE. The Driver interface exists mostly to allow a mock implementation
|
||||||
// to be used to test the steps.
|
// to be used to test the steps.
|
||||||
type Driver interface {
|
type Driver interface {
|
||||||
// ImageExists returns true if the specified image exists. If an error
|
|
||||||
// occurs calling the API, this method returns false.
|
|
||||||
ImageExists(name string) bool
|
|
||||||
|
|
||||||
// CreateImage creates an image from the given disk in Google Compute
|
// CreateImage creates an image from the given disk in Google Compute
|
||||||
// Engine.
|
// Engine.
|
||||||
CreateImage(name, description, family, zone, disk string) (<-chan Image, <-chan error)
|
CreateImage(name, description, family, zone, disk string) (<-chan *Image, <-chan error)
|
||||||
|
|
||||||
// DeleteImage deletes the image with the given name.
|
// DeleteImage deletes the image with the given name.
|
||||||
DeleteImage(name string) <-chan error
|
DeleteImage(name string) <-chan error
|
||||||
|
@ -21,6 +22,15 @@ type Driver interface {
|
||||||
// DeleteDisk deletes the disk with the given name.
|
// DeleteDisk deletes the disk with the given name.
|
||||||
DeleteDisk(zone, name string) (<-chan error, error)
|
DeleteDisk(zone, name string) (<-chan error, error)
|
||||||
|
|
||||||
|
// GetImage gets an image; tries the default and public projects.
|
||||||
|
GetImage(name string) (*Image, error)
|
||||||
|
|
||||||
|
// GetImageFromProject gets an image from a specific project.
|
||||||
|
GetImageFromProject(project, name string) (*Image, error)
|
||||||
|
|
||||||
|
// GetInstanceMetadata gets a metadata variable for the instance, name.
|
||||||
|
GetInstanceMetadata(zone, name, key string) (string, error)
|
||||||
|
|
||||||
// GetInternalIP gets the GCE-internal IP address for the instance.
|
// GetInternalIP gets the GCE-internal IP address for the instance.
|
||||||
GetInternalIP(zone, name string) (string, error)
|
GetInternalIP(zone, name string) (string, error)
|
||||||
|
|
||||||
|
@ -30,17 +40,18 @@ type Driver interface {
|
||||||
// GetSerialPortOutput gets the Serial Port contents for the instance.
|
// GetSerialPortOutput gets the Serial Port contents for the instance.
|
||||||
GetSerialPortOutput(zone, name string) (string, error)
|
GetSerialPortOutput(zone, name string) (string, error)
|
||||||
|
|
||||||
|
// ImageExists returns true if the specified image exists. If an error
|
||||||
|
// occurs calling the API, this method returns false.
|
||||||
|
ImageExists(name string) bool
|
||||||
|
|
||||||
// RunInstance takes the given config and launches an instance.
|
// RunInstance takes the given config and launches an instance.
|
||||||
RunInstance(*InstanceConfig) (<-chan error, error)
|
RunInstance(*InstanceConfig) (<-chan error, error)
|
||||||
|
|
||||||
// WaitForInstance waits for an instance to reach the given state.
|
// WaitForInstance waits for an instance to reach the given state.
|
||||||
WaitForInstance(state, zone, name string) <-chan error
|
WaitForInstance(state, zone, name string) <-chan error
|
||||||
}
|
|
||||||
|
|
||||||
type Image struct {
|
// CreateOrResetWindowsPassword creates or resets the password for a user on an Windows instance.
|
||||||
Name string
|
CreateOrResetWindowsPassword(zone, name string, config *WindowsPasswordConfig) (<-chan error, error)
|
||||||
ProjectId string
|
|
||||||
SizeGb int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstanceConfig struct {
|
type InstanceConfig struct {
|
||||||
|
@ -48,7 +59,7 @@ type InstanceConfig struct {
|
||||||
Description string
|
Description string
|
||||||
DiskSizeGb int64
|
DiskSizeGb int64
|
||||||
DiskType string
|
DiskType string
|
||||||
Image Image
|
Image *Image
|
||||||
MachineType string
|
MachineType string
|
||||||
Metadata map[string]string
|
Metadata map[string]string
|
||||||
Name string
|
Name string
|
||||||
|
@ -61,3 +72,24 @@ type InstanceConfig struct {
|
||||||
Tags []string
|
Tags []string
|
||||||
Zone 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,11 +1,20 @@
|
||||||
package googlecompute
|
package googlecompute
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"github.com/mitchellh/packer/version"
|
"github.com/mitchellh/packer/version"
|
||||||
|
|
||||||
|
@ -13,7 +22,6 @@ import (
|
||||||
"golang.org/x/oauth2/google"
|
"golang.org/x/oauth2/google"
|
||||||
"golang.org/x/oauth2/jwt"
|
"golang.org/x/oauth2/jwt"
|
||||||
"google.golang.org/api/compute/v1"
|
"google.golang.org/api/compute/v1"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// driverGCE is a Driver implementation that actually talks to GCE.
|
// driverGCE is a Driver implementation that actually talks to GCE.
|
||||||
|
@ -88,15 +96,8 @@ func NewDriverGCE(ui packer.Ui, p string, a *AccountFile) (Driver, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *driverGCE) ImageExists(name string) bool {
|
func (d *driverGCE) CreateImage(name, description, family, zone, disk string) (<-chan *Image, <-chan error) {
|
||||||
_, err := d.service.Images.Get(d.projectId, name).Do()
|
gce_image := &compute.Image{
|
||||||
// The API may return an error for reasons other than the image not
|
|
||||||
// existing, but this heuristic is sufficient for now.
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driverGCE) CreateImage(name, description, family, zone, disk string) (<-chan Image, <-chan error) {
|
|
||||||
image := &compute.Image{
|
|
||||||
Description: description,
|
Description: description,
|
||||||
Name: name,
|
Name: name,
|
||||||
Family: family,
|
Family: family,
|
||||||
|
@ -104,9 +105,9 @@ func (d *driverGCE) CreateImage(name, description, family, zone, disk string) (<
|
||||||
SourceType: "RAW",
|
SourceType: "RAW",
|
||||||
}
|
}
|
||||||
|
|
||||||
imageCh := make(chan Image, 1)
|
imageCh := make(chan *Image, 1)
|
||||||
errCh := make(chan error, 1)
|
errCh := make(chan error, 1)
|
||||||
op, err := d.service.Images.Insert(d.projectId, image).Do()
|
op, err := d.service.Images.Insert(d.projectId, gce_image).Do()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errCh <- err
|
errCh <- err
|
||||||
} else {
|
} else {
|
||||||
|
@ -114,17 +115,17 @@ func (d *driverGCE) CreateImage(name, description, family, zone, disk string) (<
|
||||||
err = waitForState(errCh, "DONE", d.refreshGlobalOp(op))
|
err = waitForState(errCh, "DONE", d.refreshGlobalOp(op))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
close(imageCh)
|
close(imageCh)
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
}
|
}
|
||||||
image, err = d.getImage(name, d.projectId)
|
var image *Image
|
||||||
|
image, err = d.GetImageFromProject(d.projectId, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
close(imageCh)
|
close(imageCh)
|
||||||
errCh <- err
|
errCh <- err
|
||||||
|
return
|
||||||
}
|
}
|
||||||
imageCh <- Image{
|
imageCh <- image
|
||||||
Name: name,
|
|
||||||
ProjectId: d.projectId,
|
|
||||||
SizeGb: image.DiskSizeGb,
|
|
||||||
}
|
|
||||||
close(imageCh)
|
close(imageCh)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -166,6 +167,57 @@ func (d *driverGCE) DeleteDisk(zone, name string) (<-chan error, error) {
|
||||||
return errCh, nil
|
return errCh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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", "gce-nvme"}
|
||||||
|
var errs error
|
||||||
|
for _, project := range projects {
|
||||||
|
image, err := d.GetImageFromProject(project, name)
|
||||||
|
if err != nil {
|
||||||
|
errs = packer.MultiErrorAppend(errs, err)
|
||||||
|
}
|
||||||
|
if image != nil {
|
||||||
|
return image, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Could not find image, %s, in projects, %s: %s", name,
|
||||||
|
projects, errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driverGCE) GetImageFromProject(project, name string) (*Image, error) {
|
||||||
|
image, err := d.service.Images.Get(project, name).Do()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if image == nil || image.SelfLink == "" {
|
||||||
|
return nil, fmt.Errorf("Image, %s, could not be found in project: %s", name, project)
|
||||||
|
} else {
|
||||||
|
return &Image{
|
||||||
|
Licenses: image.Licenses,
|
||||||
|
Name: image.Name,
|
||||||
|
ProjectId: project,
|
||||||
|
SelfLink: image.SelfLink,
|
||||||
|
SizeGb: image.DiskSizeGb,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driverGCE) GetInstanceMetadata(zone, name, key string) (string, error) {
|
||||||
|
instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range instance.Metadata.Items {
|
||||||
|
if item.Key == key {
|
||||||
|
return *item.Value, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("Instance metadata key, %s, not found.", key)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *driverGCE) GetNatIP(zone, name string) (string, error) {
|
func (d *driverGCE) GetNatIP(zone, name string) (string, error) {
|
||||||
instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
|
instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -211,6 +263,13 @@ func (d *driverGCE) GetSerialPortOutput(zone, name string) (string, error) {
|
||||||
return output.Contents, nil
|
return output.Contents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *driverGCE) ImageExists(name string) bool {
|
||||||
|
_, err := d.GetImageFromProject(d.projectId, name)
|
||||||
|
// The API may return an error for reasons other than the image not
|
||||||
|
// existing, but this heuristic is sufficient for now.
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
|
func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
|
||||||
// Get the zone
|
// Get the zone
|
||||||
d.ui.Message(fmt.Sprintf("Loading zone: %s", c.Zone))
|
d.ui.Message(fmt.Sprintf("Loading zone: %s", c.Zone))
|
||||||
|
@ -219,13 +278,6 @@ func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the image
|
|
||||||
d.ui.Message(fmt.Sprintf("Loading image: %s in project %s", c.Image.Name, c.Image.ProjectId))
|
|
||||||
image, err := d.getImage(c.Image.Name, c.Image.ProjectId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the machine type
|
// Get the machine type
|
||||||
d.ui.Message(fmt.Sprintf("Loading machine type: %s", c.MachineType))
|
d.ui.Message(fmt.Sprintf("Loading machine type: %s", c.MachineType))
|
||||||
machineType, err := d.service.MachineTypes.Get(
|
machineType, err := d.service.MachineTypes.Get(
|
||||||
|
@ -302,7 +354,7 @@ func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
|
||||||
Boot: true,
|
Boot: true,
|
||||||
AutoDelete: false,
|
AutoDelete: false,
|
||||||
InitializeParams: &compute.AttachedDiskInitializeParams{
|
InitializeParams: &compute.AttachedDiskInitializeParams{
|
||||||
SourceImage: image.SelfLink,
|
SourceImage: c.Image.SelfLink,
|
||||||
DiskSizeGb: c.DiskSizeGb,
|
DiskSizeGb: c.DiskSizeGb,
|
||||||
DiskType: fmt.Sprintf("zones/%s/diskTypes/%s", zone.Name, c.DiskType),
|
DiskType: fmt.Sprintf("zones/%s/diskTypes/%s", zone.Name, c.DiskType),
|
||||||
},
|
},
|
||||||
|
@ -349,26 +401,118 @@ func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
|
||||||
return errCh, nil
|
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 {
|
func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error {
|
||||||
errCh := make(chan error, 1)
|
errCh := make(chan error, 1)
|
||||||
go waitForState(errCh, state, d.refreshInstanceState(zone, name))
|
go waitForState(errCh, state, d.refreshInstanceState(zone, name))
|
||||||
return errCh
|
return errCh
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *driverGCE) getImage(name, projectId string) (image *compute.Image, err error) {
|
|
||||||
projects := []string{projectId, "centos-cloud", "coreos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "ubuntu-os-cloud", "windows-cloud"}
|
|
||||||
for _, project := range projects {
|
|
||||||
image, err = d.service.Images.Get(project, name).Do()
|
|
||||||
if err == nil && image != nil && image.SelfLink != "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
image = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fmt.Errorf("Image %s could not be found in any of these projects: %s", name, projects)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driverGCE) refreshInstanceState(zone, name string) stateRefreshFunc {
|
func (d *driverGCE) refreshInstanceState(zone, name string) stateRefreshFunc {
|
||||||
return func() (string, error) {
|
return func() (string, error) {
|
||||||
instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
|
instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
|
||||||
|
@ -428,7 +572,7 @@ type stateRefreshFunc func() (string, error)
|
||||||
// waitForState will spin in a loop forever waiting for state to
|
// waitForState will spin in a loop forever waiting for state to
|
||||||
// reach a certain target.
|
// reach a certain target.
|
||||||
func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) error {
|
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()
|
state, err := refresh()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
package googlecompute
|
package googlecompute
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
// DriverMock is a Driver implementation that is a mocked out so that
|
// DriverMock is a Driver implementation that is a mocked out so that
|
||||||
// it can be used for tests.
|
// it can be used for tests.
|
||||||
type DriverMock struct {
|
type DriverMock struct {
|
||||||
ImageExistsName string
|
CreateImageName string
|
||||||
ImageExistsResult bool
|
CreateImageDesc string
|
||||||
|
CreateImageFamily string
|
||||||
CreateImageName string
|
CreateImageZone string
|
||||||
CreateImageDesc string
|
CreateImageDisk string
|
||||||
CreateImageFamily string
|
CreateImageResultLicenses []string
|
||||||
CreateImageZone string
|
CreateImageResultProjectId string
|
||||||
CreateImageDisk string
|
CreateImageResultSelfLink string
|
||||||
CreateImageProjectId string
|
CreateImageResultSizeGb int64
|
||||||
CreateImageSizeGb int64
|
CreateImageErrCh <-chan error
|
||||||
CreateImageErrCh <-chan error
|
CreateImageResultCh <-chan *Image
|
||||||
CreateImageResultCh <-chan Image
|
|
||||||
|
|
||||||
DeleteImageName string
|
DeleteImageName string
|
||||||
DeleteImageErrCh <-chan error
|
DeleteImageErrCh <-chan error
|
||||||
|
@ -29,6 +30,21 @@ type DriverMock struct {
|
||||||
DeleteDiskErrCh <-chan error
|
DeleteDiskErrCh <-chan error
|
||||||
DeleteDiskErr error
|
DeleteDiskErr error
|
||||||
|
|
||||||
|
GetImageName string
|
||||||
|
GetImageResult *Image
|
||||||
|
GetImageErr error
|
||||||
|
|
||||||
|
GetImageFromProjectProject string
|
||||||
|
GetImageFromProjectName string
|
||||||
|
GetImageFromProjectResult *Image
|
||||||
|
GetImageFromProjectErr error
|
||||||
|
|
||||||
|
GetInstanceMetadataZone string
|
||||||
|
GetInstanceMetadataName string
|
||||||
|
GetInstanceMetadataKey string
|
||||||
|
GetInstanceMetadataResult string
|
||||||
|
GetInstanceMetadataErr error
|
||||||
|
|
||||||
GetNatIPZone string
|
GetNatIPZone string
|
||||||
GetNatIPName string
|
GetNatIPName string
|
||||||
GetNatIPResult string
|
GetNatIPResult string
|
||||||
|
@ -44,41 +60,52 @@ type DriverMock struct {
|
||||||
GetSerialPortOutputResult string
|
GetSerialPortOutputResult string
|
||||||
GetSerialPortOutputErr error
|
GetSerialPortOutputErr error
|
||||||
|
|
||||||
|
ImageExistsName string
|
||||||
|
ImageExistsResult bool
|
||||||
|
|
||||||
RunInstanceConfig *InstanceConfig
|
RunInstanceConfig *InstanceConfig
|
||||||
RunInstanceErrCh <-chan error
|
RunInstanceErrCh <-chan error
|
||||||
RunInstanceErr error
|
RunInstanceErr error
|
||||||
|
|
||||||
|
CreateOrResetWindowsPasswordZone string
|
||||||
|
CreateOrResetWindowsPasswordInstance string
|
||||||
|
CreateOrResetWindowsPasswordConfig *WindowsPasswordConfig
|
||||||
|
CreateOrResetWindowsPasswordErr error
|
||||||
|
CreateOrResetWindowsPasswordErrCh <-chan error
|
||||||
|
|
||||||
WaitForInstanceState string
|
WaitForInstanceState string
|
||||||
WaitForInstanceZone string
|
WaitForInstanceZone string
|
||||||
WaitForInstanceName string
|
WaitForInstanceName string
|
||||||
WaitForInstanceErrCh <-chan error
|
WaitForInstanceErrCh <-chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DriverMock) ImageExists(name string) bool {
|
func (d *DriverMock) CreateImage(name, description, family, zone, disk string) (<-chan *Image, <-chan error) {
|
||||||
d.ImageExistsName = name
|
|
||||||
return d.ImageExistsResult
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DriverMock) CreateImage(name, description, family, zone, disk string) (<-chan Image, <-chan error) {
|
|
||||||
d.CreateImageName = name
|
d.CreateImageName = name
|
||||||
d.CreateImageDesc = description
|
d.CreateImageDesc = description
|
||||||
d.CreateImageFamily = family
|
d.CreateImageFamily = family
|
||||||
d.CreateImageZone = zone
|
d.CreateImageZone = zone
|
||||||
d.CreateImageDisk = disk
|
d.CreateImageDisk = disk
|
||||||
if d.CreateImageSizeGb == 0 {
|
if d.CreateImageResultProjectId == "" {
|
||||||
d.CreateImageSizeGb = 10
|
d.CreateImageResultProjectId = "test"
|
||||||
}
|
}
|
||||||
if d.CreateImageProjectId == "" {
|
if d.CreateImageResultSelfLink == "" {
|
||||||
d.CreateImageProjectId = "test"
|
d.CreateImageResultSelfLink = fmt.Sprintf(
|
||||||
|
"http://content.googleapis.com/compute/v1/%s/global/licenses/test",
|
||||||
|
d.CreateImageResultProjectId)
|
||||||
|
}
|
||||||
|
if d.CreateImageResultSizeGb == 0 {
|
||||||
|
d.CreateImageResultSizeGb = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
resultCh := d.CreateImageResultCh
|
resultCh := d.CreateImageResultCh
|
||||||
if resultCh == nil {
|
if resultCh == nil {
|
||||||
ch := make(chan Image, 1)
|
ch := make(chan *Image, 1)
|
||||||
ch <- Image{
|
ch <- &Image{
|
||||||
|
Licenses: d.CreateImageResultLicenses,
|
||||||
Name: name,
|
Name: name,
|
||||||
ProjectId: d.CreateImageProjectId,
|
ProjectId: d.CreateImageResultProjectId,
|
||||||
SizeGb: d.CreateImageSizeGb,
|
SelfLink: d.CreateImageResultSelfLink,
|
||||||
|
SizeGb: d.CreateImageResultSizeGb,
|
||||||
}
|
}
|
||||||
close(ch)
|
close(ch)
|
||||||
resultCh = ch
|
resultCh = ch
|
||||||
|
@ -135,6 +162,24 @@ func (d *DriverMock) DeleteDisk(zone, name string) (<-chan error, error) {
|
||||||
return resultCh, d.DeleteDiskErr
|
return resultCh, d.DeleteDiskErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) GetImage(name string) (*Image, error) {
|
||||||
|
d.GetImageName = name
|
||||||
|
return d.GetImageResult, d.GetImageErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) GetImageFromProject(project, name string) (*Image, error) {
|
||||||
|
d.GetImageFromProjectProject = project
|
||||||
|
d.GetImageFromProjectName = name
|
||||||
|
return d.GetImageFromProjectResult, d.GetImageFromProjectErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) GetInstanceMetadata(zone, name, key string) (string, error) {
|
||||||
|
d.GetInstanceMetadataZone = zone
|
||||||
|
d.GetInstanceMetadataName = name
|
||||||
|
d.GetInstanceMetadataKey = key
|
||||||
|
return d.GetInstanceMetadataResult, d.GetInstanceMetadataErr
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DriverMock) GetNatIP(zone, name string) (string, error) {
|
func (d *DriverMock) GetNatIP(zone, name string) (string, error) {
|
||||||
d.GetNatIPZone = zone
|
d.GetNatIPZone = zone
|
||||||
d.GetNatIPName = name
|
d.GetNatIPName = name
|
||||||
|
@ -153,6 +198,11 @@ func (d *DriverMock) GetSerialPortOutput(zone, name string) (string, error) {
|
||||||
return d.GetSerialPortOutputResult, d.GetSerialPortOutputErr
|
return d.GetSerialPortOutputResult, d.GetSerialPortOutputErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) ImageExists(name string) bool {
|
||||||
|
d.ImageExistsName = name
|
||||||
|
return d.ImageExistsResult
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DriverMock) RunInstance(c *InstanceConfig) (<-chan error, error) {
|
func (d *DriverMock) RunInstance(c *InstanceConfig) (<-chan error, error) {
|
||||||
d.RunInstanceConfig = c
|
d.RunInstanceConfig = c
|
||||||
|
|
||||||
|
@ -180,3 +230,25 @@ func (d *DriverMock) WaitForInstance(state, zone, name string) <-chan error {
|
||||||
|
|
||||||
return resultCh
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package googlecompute
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Image struct {
|
||||||
|
Licenses []string
|
||||||
|
Name string
|
||||||
|
ProjectId string
|
||||||
|
SelfLink string
|
||||||
|
SizeGb int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Image) IsWindows() bool {
|
||||||
|
for _, license := range i.Licenses {
|
||||||
|
if strings.Contains(license, "windows") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package googlecompute
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StubImage(name, project string, licenses []string, sizeGb int64) *Image {
|
||||||
|
return &Image{
|
||||||
|
Licenses: licenses,
|
||||||
|
Name: name,
|
||||||
|
ProjectId: project,
|
||||||
|
SelfLink: fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/images/%s", project, name),
|
||||||
|
SizeGb: sizeGb,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImage_IsWindows(t *testing.T) {
|
||||||
|
i := StubImage("foo", "foo-project", []string{"license-foo", "license-bar"}, 100)
|
||||||
|
assert.False(t, i.IsWindows())
|
||||||
|
|
||||||
|
i = StubImage("foo", "foo-project", []string{"license-foo", "windows-license"}, 100)
|
||||||
|
assert.True(t, i.IsWindows())
|
||||||
|
}
|
|
@ -1,37 +1,40 @@
|
||||||
package googlecompute
|
package googlecompute
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const StartupScriptStartLog string = "Packer startup script starting."
|
|
||||||
const StartupScriptDoneLog string = "Packer startup script done."
|
|
||||||
const StartupScriptKey string = "startup-script"
|
const StartupScriptKey string = "startup-script"
|
||||||
|
const StartupScriptStatusKey string = "startup-script-status"
|
||||||
const StartupWrappedScriptKey string = "packer-wrapped-startup-script"
|
const StartupWrappedScriptKey string = "packer-wrapped-startup-script"
|
||||||
|
|
||||||
// We have to encode StartupScriptDoneLog because we use it as a sentinel value to indicate
|
const StartupScriptStatusDone string = "done"
|
||||||
// that the user-provided startup script is done. If we pass StartupScriptDoneLog as-is, it
|
const StartupScriptStatusError string = "error"
|
||||||
// will be printed early in the instance console log (before the startup script even runs;
|
const StartupScriptStatusNotDone string = "notdone"
|
||||||
// we print out instance creation metadata which contains this wrapper script).
|
|
||||||
var StartupScriptDoneLogBase64 string = base64.StdEncoding.EncodeToString([]byte(StartupScriptDoneLog))
|
|
||||||
|
|
||||||
var StartupScript string = fmt.Sprintf(`#!/bin/bash
|
var StartupScriptLinux string = fmt.Sprintf(`#!/bin/bash
|
||||||
echo %s
|
echo "Packer startup script starting."
|
||||||
RETVAL=0
|
RETVAL=0
|
||||||
|
BASEMETADATAURL=http://metadata/computeMetadata/v1/instance/
|
||||||
|
|
||||||
GetMetadata () {
|
GetMetadata () {
|
||||||
echo "$(curl -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/attributes/$1 2> /dev/null)"
|
echo "$(curl -f -H "Metadata-Flavor: Google" ${BASEMETADATAURL}/${1} 2> /dev/null)"
|
||||||
}
|
}
|
||||||
|
|
||||||
STARTUPSCRIPT=$(GetMetadata %s)
|
ZONE=$(GetMetadata zone | grep -oP "[^/]*$")
|
||||||
|
|
||||||
|
SetMetadata () {
|
||||||
|
gcloud compute instances add-metadata ${HOSTNAME} --metadata ${1}=${2} --zone ${ZONE}
|
||||||
|
}
|
||||||
|
|
||||||
|
STARTUPSCRIPT=$(GetMetadata attributes/%s)
|
||||||
STARTUPSCRIPTPATH=/packer-wrapped-startup-script
|
STARTUPSCRIPTPATH=/packer-wrapped-startup-script
|
||||||
if [ -f "/var/log/startupscript.log" ]; then
|
if [ -f "/var/log/startupscript.log" ]; then
|
||||||
STARTUPSCRIPTLOGPATH=/var/log/startupscript.log
|
STARTUPSCRIPTLOGPATH=/var/log/startupscript.log
|
||||||
else
|
else
|
||||||
STARTUPSCRIPTLOGPATH=/var/log/daemon.log
|
STARTUPSCRIPTLOGPATH=/var/log/daemon.log
|
||||||
fi
|
fi
|
||||||
STARTUPSCRIPTLOGDEST=$(GetMetadata startup-script-log-dest)
|
STARTUPSCRIPTLOGDEST=$(GetMetadata attributes/startup-script-log-dest)
|
||||||
|
|
||||||
if [[ ! -z $STARTUPSCRIPT ]]; then
|
if [[ ! -z $STARTUPSCRIPT ]]; then
|
||||||
echo "Executing user-provided startup script..."
|
echo "Executing user-provided startup script..."
|
||||||
|
@ -48,6 +51,9 @@ if [[ ! -z $STARTUPSCRIPT ]]; then
|
||||||
rm ${STARTUPSCRIPTPATH}
|
rm ${STARTUPSCRIPTPATH}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo $(echo %s | base64 --decode)
|
echo "Packer startup script done."
|
||||||
|
SetMetadata %s %s
|
||||||
exit $RETVAL
|
exit $RETVAL
|
||||||
`, StartupScriptStartLog, StartupWrappedScriptKey, StartupScriptDoneLogBase64)
|
`, StartupWrappedScriptKey, StartupScriptStatusKey, StartupScriptStatusDone)
|
||||||
|
|
||||||
|
var StartupScriptWindows string = ""
|
||||||
|
|
|
@ -13,19 +13,19 @@ type StepCheckExistingImage int
|
||||||
|
|
||||||
// Run executes the Packer build step that checks if the image already exists.
|
// Run executes the Packer build step that checks if the image already exists.
|
||||||
func (s *StepCheckExistingImage) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *StepCheckExistingImage) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*Config)
|
c := state.Get("config").(*Config)
|
||||||
driver := state.Get("driver").(Driver)
|
d := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
ui.Say("Checking image does not exist...")
|
ui.Say("Checking image does not exist...")
|
||||||
exists := driver.ImageExists(config.ImageName)
|
c.imageAlreadyExists = d.ImageExists(c.ImageName)
|
||||||
if exists {
|
if !c.PackerForce && c.imageAlreadyExists {
|
||||||
err := fmt.Errorf("Image %s already exists", config.ImageName)
|
err := fmt.Errorf("Image %s already exists.\n"+
|
||||||
|
"Use the force flag to delete it prior to building.", c.ImageName)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,24 @@ func (s *StepCreateImage) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
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...")
|
ui.Say("Creating image...")
|
||||||
|
|
||||||
imageCh, errCh := driver.CreateImage(config.ImageName, config.ImageDescription, config.ImageFamily, config.Zone, config.DiskName)
|
imageCh, errCh := driver.CreateImage(
|
||||||
|
config.ImageName, config.ImageDescription, config.ImageFamily, config.Zone,
|
||||||
|
config.DiskName)
|
||||||
var err error
|
var err error
|
||||||
select {
|
select {
|
||||||
case err = <-errCh:
|
case err = <-errCh:
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStepCreateImage_impl(t *testing.T) {
|
func TestStepCreateImage_impl(t *testing.T) {
|
||||||
|
@ -16,52 +17,35 @@ func TestStepCreateImage(t *testing.T) {
|
||||||
step := new(StepCreateImage)
|
step := new(StepCreateImage)
|
||||||
defer step.Cleanup(state)
|
defer step.Cleanup(state)
|
||||||
|
|
||||||
config := state.Get("config").(*Config)
|
c := state.Get("config").(*Config)
|
||||||
driver := state.Get("driver").(*DriverMock)
|
d := state.Get("driver").(*DriverMock)
|
||||||
driver.CreateImageProjectId = "createimage-project"
|
|
||||||
driver.CreateImageSizeGb = 100
|
// These are the values of the image the driver will return.
|
||||||
|
d.CreateImageResultLicenses = []string{"test-license"}
|
||||||
|
d.CreateImageResultProjectId = "test-project"
|
||||||
|
d.CreateImageResultSizeGb = 100
|
||||||
|
|
||||||
// run the step
|
// run the step
|
||||||
if action := step.Run(state); action != multistep.ActionContinue {
|
action := step.Run(state)
|
||||||
t.Fatalf("bad action: %#v", action)
|
assert.Equal(t, action, multistep.ActionContinue, "Step did not pass.")
|
||||||
}
|
|
||||||
|
|
||||||
uncastImage, ok := state.GetOk("image")
|
uncastImage, ok := state.GetOk("image")
|
||||||
if !ok {
|
assert.True(t, ok, "State does not have resulting image.")
|
||||||
t.Fatal("should have image")
|
image, ok := uncastImage.(*Image)
|
||||||
}
|
assert.True(t, ok, "Image in state is not an Image.")
|
||||||
image, ok := uncastImage.(Image)
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("image is not an Image")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify created Image results.
|
// Verify created Image results.
|
||||||
if image.Name != config.ImageName {
|
assert.Equal(t, image.Licenses, d.CreateImageResultLicenses, "Created image licenses don't match the licenses returned by the driver.")
|
||||||
t.Fatalf("Created image name, %s, does not match config name, %s.", image.Name, config.ImageName)
|
assert.Equal(t, image.Name, c.ImageName, "Created image does not match config name.")
|
||||||
}
|
assert.Equal(t, image.ProjectId, d.CreateImageResultProjectId, "Created image project does not match driver project.")
|
||||||
if driver.CreateImageProjectId != image.ProjectId {
|
assert.Equal(t, image.SizeGb, d.CreateImageResultSizeGb, "Created image size does not match the size returned by the driver.")
|
||||||
t.Fatalf("Created image project ID, %s, does not match driver project ID, %s.", image.ProjectId, driver.CreateImageProjectId)
|
|
||||||
}
|
|
||||||
if driver.CreateImageSizeGb != image.SizeGb {
|
|
||||||
t.Fatalf("Created image size, %d, does not match the expected test value, %d.", image.SizeGb, driver.CreateImageSizeGb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify proper args passed to driver.CreateImage.
|
// Verify proper args passed to driver.CreateImage.
|
||||||
if driver.CreateImageName != config.ImageName {
|
assert.Equal(t, d.CreateImageName, c.ImageName, "Incorrect image name passed to driver.")
|
||||||
t.Fatalf("bad: %#v", driver.CreateImageName)
|
assert.Equal(t, d.CreateImageDesc, c.ImageDescription, "Incorrect image description passed to driver.")
|
||||||
}
|
assert.Equal(t, d.CreateImageFamily, c.ImageFamily, "Incorrect image family passed to driver.")
|
||||||
if driver.CreateImageDesc != config.ImageDescription {
|
assert.Equal(t, d.CreateImageZone, c.Zone, "Incorrect image zone passed to driver.")
|
||||||
t.Fatalf("bad: %#v", driver.CreateImageDesc)
|
assert.Equal(t, d.CreateImageDisk, c.DiskName, "Incorrect disk passed to driver.")
|
||||||
}
|
|
||||||
if driver.CreateImageFamily != config.ImageFamily {
|
|
||||||
t.Fatalf("bad: %#v", driver.CreateImageFamily)
|
|
||||||
}
|
|
||||||
if driver.CreateImageZone != config.Zone {
|
|
||||||
t.Fatalf("bad: %#v", driver.CreateImageZone)
|
|
||||||
}
|
|
||||||
if driver.CreateImageDisk != config.DiskName {
|
|
||||||
t.Fatalf("bad: %#v", driver.CreateImageDisk)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepCreateImage_errorOnChannel(t *testing.T) {
|
func TestStepCreateImage_errorOnChannel(t *testing.T) {
|
||||||
|
@ -76,14 +60,10 @@ func TestStepCreateImage_errorOnChannel(t *testing.T) {
|
||||||
driver.CreateImageErrCh = errCh
|
driver.CreateImageErrCh = errCh
|
||||||
|
|
||||||
// run the step
|
// run the step
|
||||||
if action := step.Run(state); action != multistep.ActionHalt {
|
action := step.Run(state)
|
||||||
t.Fatalf("bad action: %#v", action)
|
assert.Equal(t, action, multistep.ActionHalt, "Step should not have passed.")
|
||||||
}
|
_, ok := state.GetOk("error")
|
||||||
|
assert.True(t, ok, "State should have an error.")
|
||||||
if _, ok := state.GetOk("error"); !ok {
|
_, ok = state.GetOk("image_name")
|
||||||
t.Fatal("should have error")
|
assert.False(t, ok, "State should not have a resulting image.")
|
||||||
}
|
|
||||||
if _, ok := state.GetOk("image_name"); ok {
|
|
||||||
t.Fatal("should NOT have image")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,82 +15,101 @@ type StepCreateInstance struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) getImage() Image {
|
func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string) (map[string]string, error) {
|
||||||
project := config.ProjectId
|
|
||||||
if config.SourceImageProjectId != "" {
|
|
||||||
project = config.SourceImageProjectId
|
|
||||||
}
|
|
||||||
return Image{Name: config.SourceImage, ProjectId: project}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (config *Config) getInstanceMetadata(sshPublicKey string) (map[string]string, error) {
|
|
||||||
instanceMetadata := make(map[string]string)
|
instanceMetadata := make(map[string]string)
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Copy metadata from config.
|
// Copy metadata from config.
|
||||||
for k, v := range config.Metadata {
|
for k, v := range c.Metadata {
|
||||||
instanceMetadata[k] = v
|
instanceMetadata[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge any existing ssh keys with our public key.
|
// Merge any existing ssh keys with our public key.
|
||||||
sshMetaKey := "sshKeys"
|
sshMetaKey := "sshKeys"
|
||||||
sshKeys := fmt.Sprintf("%s:%s", config.Comm.SSHUsername, sshPublicKey)
|
sshKeys := fmt.Sprintf("%s:%s", c.Comm.SSHUsername, sshPublicKey)
|
||||||
if confSshKeys, exists := instanceMetadata[sshMetaKey]; exists {
|
if confSshKeys, exists := instanceMetadata[sshMetaKey]; exists {
|
||||||
sshKeys = fmt.Sprintf("%s\n%s", sshKeys, confSshKeys)
|
sshKeys = fmt.Sprintf("%s\n%s", sshKeys, confSshKeys)
|
||||||
}
|
}
|
||||||
instanceMetadata[sshMetaKey] = sshKeys
|
instanceMetadata[sshMetaKey] = sshKeys
|
||||||
|
|
||||||
// Wrap any startup script with our own startup script.
|
// Wrap any startup script with our own startup script.
|
||||||
if config.StartupScriptFile != "" {
|
if c.StartupScriptFile != "" {
|
||||||
var content []byte
|
var content []byte
|
||||||
content, err = ioutil.ReadFile(config.StartupScriptFile)
|
content, err = ioutil.ReadFile(c.StartupScriptFile)
|
||||||
instanceMetadata[StartupWrappedScriptKey] = string(content)
|
instanceMetadata[StartupWrappedScriptKey] = string(content)
|
||||||
} else if wrappedStartupScript, exists := instanceMetadata[StartupScriptKey]; exists {
|
} else if wrappedStartupScript, exists := instanceMetadata[StartupScriptKey]; exists {
|
||||||
instanceMetadata[StartupWrappedScriptKey] = wrappedStartupScript
|
instanceMetadata[StartupWrappedScriptKey] = wrappedStartupScript
|
||||||
}
|
}
|
||||||
instanceMetadata[StartupScriptKey] = StartupScript
|
if sourceImage.IsWindows() {
|
||||||
|
// Windows startup script support is not yet implemented.
|
||||||
|
// Mark the startup script as done.
|
||||||
|
instanceMetadata[StartupScriptKey] = StartupScriptWindows
|
||||||
|
instanceMetadata[StartupScriptStatusKey] = StartupScriptStatusDone
|
||||||
|
} else {
|
||||||
|
instanceMetadata[StartupScriptKey] = StartupScriptLinux
|
||||||
|
instanceMetadata[StartupScriptStatusKey] = StartupScriptStatusNotDone
|
||||||
|
}
|
||||||
|
|
||||||
return instanceMetadata, err
|
return instanceMetadata, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getImage(c *Config, d Driver) (*Image, error) {
|
||||||
|
if c.SourceImageProjectId == "" {
|
||||||
|
return d.GetImage(c.SourceImage)
|
||||||
|
} else {
|
||||||
|
return d.GetImageFromProject(c.SourceImageProjectId, c.SourceImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Run executes the Packer build step that creates a GCE instance.
|
// Run executes the Packer build step that creates a GCE instance.
|
||||||
func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*Config)
|
c := state.Get("config").(*Config)
|
||||||
driver := state.Get("driver").(Driver)
|
d := state.Get("driver").(Driver)
|
||||||
sshPublicKey := state.Get("ssh_public_key").(string)
|
sshPublicKey := state.Get("ssh_public_key").(string)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
sourceImage, err := getImage(c, d)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error getting source image for instance creation: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceImage.IsWindows() && c.Comm.Type == "winrm" && c.Comm.WinRMPassword == "" {
|
||||||
|
state.Put("create_windows_password", true)
|
||||||
|
}
|
||||||
|
|
||||||
ui.Say("Creating instance...")
|
ui.Say("Creating instance...")
|
||||||
name := config.InstanceName
|
name := c.InstanceName
|
||||||
|
|
||||||
var errCh <-chan error
|
var errCh <-chan error
|
||||||
var err error
|
|
||||||
var metadata map[string]string
|
var metadata map[string]string
|
||||||
metadata, err = config.getInstanceMetadata(sshPublicKey)
|
metadata, err = c.createInstanceMetadata(sourceImage, sshPublicKey)
|
||||||
errCh, err = driver.RunInstance(&InstanceConfig{
|
errCh, err = d.RunInstance(&InstanceConfig{
|
||||||
Address: config.Address,
|
Address: c.Address,
|
||||||
Description: "New instance created by Packer",
|
Description: "New instance created by Packer",
|
||||||
DiskSizeGb: config.DiskSizeGb,
|
DiskSizeGb: c.DiskSizeGb,
|
||||||
DiskType: config.DiskType,
|
DiskType: c.DiskType,
|
||||||
Image: config.getImage(),
|
Image: sourceImage,
|
||||||
MachineType: config.MachineType,
|
MachineType: c.MachineType,
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
Name: name,
|
Name: name,
|
||||||
Network: config.Network,
|
Network: c.Network,
|
||||||
OmitExternalIP: config.OmitExternalIP,
|
OmitExternalIP: c.OmitExternalIP,
|
||||||
Preemptible: config.Preemptible,
|
Preemptible: c.Preemptible,
|
||||||
Region: config.Region,
|
Region: c.Region,
|
||||||
ServiceAccountEmail: config.Account.ClientEmail,
|
ServiceAccountEmail: c.Account.ClientEmail,
|
||||||
Subnetwork: config.Subnetwork,
|
Subnetwork: c.Subnetwork,
|
||||||
Tags: config.Tags,
|
Tags: c.Tags,
|
||||||
Zone: config.Zone,
|
Zone: c.Zone,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ui.Message("Waiting for creation operation to complete...")
|
ui.Message("Waiting for creation operation to complete...")
|
||||||
select {
|
select {
|
||||||
case err = <-errCh:
|
case err = <-errCh:
|
||||||
case <-time.After(config.stateTimeout):
|
case <-time.After(c.stateTimeout):
|
||||||
err = errors.New("time out while waiting for instance to create")
|
err = errors.New("time out while waiting for instance to create")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +125,7 @@ func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction
|
||||||
|
|
||||||
if s.Debug {
|
if s.Debug {
|
||||||
if name != "" {
|
if name != "" {
|
||||||
ui.Message(fmt.Sprintf("Instance: %s started in %s", name, config.Zone))
|
ui.Message(fmt.Sprintf("Instance: %s started in %s", name, c.Zone))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,11 @@ package googlecompute
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/mitchellh/multistep"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStepCreateInstance_impl(t *testing.T) {
|
func TestStepCreateInstance_impl(t *testing.T) {
|
||||||
|
@ -18,8 +20,86 @@ func TestStepCreateInstance(t *testing.T) {
|
||||||
|
|
||||||
state.Put("ssh_public_key", "key")
|
state.Put("ssh_public_key", "key")
|
||||||
|
|
||||||
|
c := state.Get("config").(*Config)
|
||||||
|
d := state.Get("driver").(*DriverMock)
|
||||||
|
d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100)
|
||||||
|
|
||||||
|
// run the step
|
||||||
|
assert.Equal(t, step.Run(state), multistep.ActionContinue, "Step should have passed and continued.")
|
||||||
|
|
||||||
|
// Verify state
|
||||||
|
nameRaw, ok := state.GetOk("instance_name")
|
||||||
|
assert.True(t, ok, "State should have an instance name.")
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
step.Cleanup(state)
|
||||||
|
|
||||||
|
// Check args passed to the driver.
|
||||||
|
assert.Equal(t, d.DeleteInstanceName, nameRaw.(string), "Incorrect instance name passed to driver.")
|
||||||
|
assert.Equal(t, d.DeleteInstanceZone, c.Zone, "Incorrect instance zone passed to driver.")
|
||||||
|
assert.Equal(t, d.DeleteDiskName, c.InstanceName, "Incorrect disk name passed to driver.")
|
||||||
|
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)
|
config := state.Get("config").(*Config)
|
||||||
driver := state.Get("driver").(*DriverMock)
|
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
|
// run the step
|
||||||
if action := step.Run(state); action != multistep.ActionContinue {
|
if action := step.Run(state); action != multistep.ActionContinue {
|
||||||
|
@ -32,6 +112,12 @@ func TestStepCreateInstance(t *testing.T) {
|
||||||
t.Fatal("should have instance name")
|
t.Fatal("should have instance name")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, ok = state.GetOk("create_windows_password")
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
t.Fatal("should not need to create windows password")
|
||||||
|
}
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
step.Cleanup(state)
|
step.Cleanup(state)
|
||||||
|
|
||||||
|
@ -57,21 +143,18 @@ func TestStepCreateInstance_error(t *testing.T) {
|
||||||
|
|
||||||
state.Put("ssh_public_key", "key")
|
state.Put("ssh_public_key", "key")
|
||||||
|
|
||||||
driver := state.Get("driver").(*DriverMock)
|
d := state.Get("driver").(*DriverMock)
|
||||||
driver.RunInstanceErr = errors.New("error")
|
d.RunInstanceErr = errors.New("error")
|
||||||
|
d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100)
|
||||||
|
|
||||||
// run the step
|
// run the step
|
||||||
if action := step.Run(state); action != multistep.ActionHalt {
|
assert.Equal(t, step.Run(state), multistep.ActionHalt, "Step should have failed and halted.")
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify state
|
// Verify state
|
||||||
if _, ok := state.GetOk("error"); !ok {
|
_, ok := state.GetOk("error")
|
||||||
t.Fatal("should have error")
|
assert.True(t, ok, "State should have an error.")
|
||||||
}
|
_, ok = state.GetOk("instance_name")
|
||||||
if _, ok := state.GetOk("instance_name"); ok {
|
assert.False(t, ok, "State should not have an instance name.")
|
||||||
t.Fatal("should NOT have instance name")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepCreateInstance_errorOnChannel(t *testing.T) {
|
func TestStepCreateInstance_errorOnChannel(t *testing.T) {
|
||||||
|
@ -79,26 +162,23 @@ func TestStepCreateInstance_errorOnChannel(t *testing.T) {
|
||||||
step := new(StepCreateInstance)
|
step := new(StepCreateInstance)
|
||||||
defer step.Cleanup(state)
|
defer step.Cleanup(state)
|
||||||
|
|
||||||
|
state.Put("ssh_public_key", "key")
|
||||||
|
|
||||||
errCh := make(chan error, 1)
|
errCh := make(chan error, 1)
|
||||||
errCh <- errors.New("error")
|
errCh <- errors.New("error")
|
||||||
|
|
||||||
state.Put("ssh_public_key", "key")
|
d := state.Get("driver").(*DriverMock)
|
||||||
|
d.RunInstanceErrCh = errCh
|
||||||
driver := state.Get("driver").(*DriverMock)
|
d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100)
|
||||||
driver.RunInstanceErrCh = errCh
|
|
||||||
|
|
||||||
// run the step
|
// run the step
|
||||||
if action := step.Run(state); action != multistep.ActionHalt {
|
assert.Equal(t, step.Run(state), multistep.ActionHalt, "Step should have failed and halted.")
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify state
|
// Verify state
|
||||||
if _, ok := state.GetOk("error"); !ok {
|
_, ok := state.GetOk("error")
|
||||||
t.Fatal("should have error")
|
assert.True(t, ok, "State should have an error.")
|
||||||
}
|
_, ok = state.GetOk("instance_name")
|
||||||
if _, ok := state.GetOk("instance_name"); ok {
|
assert.False(t, ok, "State should not have an instance name.")
|
||||||
t.Fatal("should NOT have instance name")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepCreateInstance_errorTimeout(t *testing.T) {
|
func TestStepCreateInstance_errorTimeout(t *testing.T) {
|
||||||
|
@ -106,30 +186,27 @@ func TestStepCreateInstance_errorTimeout(t *testing.T) {
|
||||||
step := new(StepCreateInstance)
|
step := new(StepCreateInstance)
|
||||||
defer step.Cleanup(state)
|
defer step.Cleanup(state)
|
||||||
|
|
||||||
|
state.Put("ssh_public_key", "key")
|
||||||
|
|
||||||
errCh := make(chan error, 1)
|
errCh := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
<-time.After(10 * time.Millisecond)
|
<-time.After(10 * time.Millisecond)
|
||||||
errCh <- nil
|
errCh <- nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
state.Put("ssh_public_key", "key")
|
|
||||||
|
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
config.stateTimeout = 1 * time.Microsecond
|
config.stateTimeout = 1 * time.Microsecond
|
||||||
|
|
||||||
driver := state.Get("driver").(*DriverMock)
|
d := state.Get("driver").(*DriverMock)
|
||||||
driver.RunInstanceErrCh = errCh
|
d.RunInstanceErrCh = errCh
|
||||||
|
d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100)
|
||||||
|
|
||||||
// run the step
|
// run the step
|
||||||
if action := step.Run(state); action != multistep.ActionHalt {
|
assert.Equal(t, step.Run(state), multistep.ActionHalt, "Step should have failed and halted.")
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify state
|
// Verify state
|
||||||
if _, ok := state.GetOk("error"); !ok {
|
_, ok := state.GetOk("error")
|
||||||
t.Fatal("should have error")
|
assert.True(t, ok, "State should have an error.")
|
||||||
}
|
_, ok = state.GetOk("instance_name")
|
||||||
if _, ok := state.GetOk("instance_name"); ok {
|
assert.False(t, ok, "State should not have an instance name.")
|
||||||
t.Fatal("should NOT have instance name")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,8 +71,8 @@ func (s *StepTeardownInstance) Cleanup(state multistep.StateBag) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Error(fmt.Sprintf(
|
ui.Error(fmt.Sprintf(
|
||||||
"Error deleting disk. Please delete it manually.\n\n"+
|
"Error deleting disk. Please delete it manually.\n\n"+
|
||||||
"DiskName: %s\n" +
|
"DiskName: %s\n"+
|
||||||
"Zone: %s\n" +
|
"Zone: %s\n"+
|
||||||
"Error: %s", config.DiskName, config.Zone, err))
|
"Error: %s", config.DiskName, config.Zone, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,10 @@ package googlecompute
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func testState(t *testing.T) multistep.StateBag {
|
func testState(t *testing.T) multistep.StateBag {
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
package googlecompute
|
package googlecompute
|
||||||
|
|
||||||
import(
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StepWaitInstanceStartup int
|
type StepWaitInstanceStartup int
|
||||||
|
|
||||||
// Run reads the instance serial port output and looks for the log entry indicating the startup script finished.
|
// Run reads the instance metadata and looks for the log entry
|
||||||
|
// indicating the startup script finished.
|
||||||
func (s *StepWaitInstanceStartup) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *StepWaitInstanceStartup) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
|
@ -20,15 +22,21 @@ func (s *StepWaitInstanceStartup) Run(state multistep.StateBag) multistep.StepAc
|
||||||
ui.Say("Waiting for any running startup script to finish...")
|
ui.Say("Waiting for any running startup script to finish...")
|
||||||
|
|
||||||
// Keep checking the serial port output to see if the startup script is done.
|
// 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) {
|
||||||
output, err := driver.GetSerialPortOutput(config.Zone, instanceName)
|
status, err := driver.GetInstanceMetadata(config.Zone,
|
||||||
|
instanceName, StartupScriptStatusKey)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error getting serial port output: %s", err)
|
err := fmt.Errorf("Error getting startup script status: %s", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
done := strings.Contains(output, StartupScriptDoneLog)
|
if status == StartupScriptStatusError {
|
||||||
|
err = errors.New("Startup script error.")
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
done := status == StartupScriptStatusDone
|
||||||
if !done {
|
if !done {
|
||||||
ui.Say("Startup script not finished yet. Waiting...")
|
ui.Say("Startup script not finished yet. Waiting...")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,37 +2,29 @@ package googlecompute
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStepWaitInstanceStartup(t *testing.T) {
|
func TestStepWaitInstanceStartup(t *testing.T) {
|
||||||
state := testState(t)
|
state := testState(t)
|
||||||
step := new(StepWaitInstanceStartup)
|
step := new(StepWaitInstanceStartup)
|
||||||
config := state.Get("config").(*Config)
|
c := state.Get("config").(*Config)
|
||||||
driver := state.Get("driver").(*DriverMock)
|
d := state.Get("driver").(*DriverMock)
|
||||||
|
|
||||||
testZone := "test-zone"
|
testZone := "test-zone"
|
||||||
testInstanceName := "test-instance-name"
|
testInstanceName := "test-instance-name"
|
||||||
|
|
||||||
config.Zone = testZone
|
c.Zone = testZone
|
||||||
state.Put("instance_name", testInstanceName)
|
state.Put("instance_name", testInstanceName)
|
||||||
// The done log triggers step completion.
|
|
||||||
driver.GetSerialPortOutputResult = StartupScriptDoneLog
|
// This step stops when it gets Done back from the metadata.
|
||||||
|
d.GetInstanceMetadataResult = StartupScriptStatusDone
|
||||||
|
|
||||||
// Run the step.
|
// Run the step.
|
||||||
if action := step.Run(state); action != multistep.ActionContinue {
|
assert.Equal(t, step.Run(state), multistep.ActionContinue, "Step should have passed and continued.")
|
||||||
t.Fatalf("StepWaitInstanceStartup did not return a Continue action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that GetSerialPortOutput was called properly.
|
// Check that GetInstanceMetadata was called properly.
|
||||||
if driver.GetSerialPortOutputZone != testZone {
|
assert.Equal(t, d.GetInstanceMetadataZone, testZone, "Incorrect zone passed to GetInstanceMetadata.")
|
||||||
t.Fatalf(
|
assert.Equal(t, d.GetInstanceMetadataName, testInstanceName, "Incorrect instance name passed to GetInstanceMetadata.")
|
||||||
"GetSerialPortOutput wrong zone. Expected: %s, Actual: %s", driver.GetSerialPortOutputZone,
|
|
||||||
testZone)
|
|
||||||
}
|
|
||||||
if driver.GetSerialPortOutputName != testInstanceName {
|
|
||||||
t.Fatalf(
|
|
||||||
"GetSerialPortOutput wrong instance name. Expected: %s, Actual: %s", driver.GetSerialPortOutputName,
|
|
||||||
testInstanceName)
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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)
|
state.Put("ui", ui)
|
||||||
|
|
||||||
// Run!
|
// Run!
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
b.runner = &multistep.DebugRunner{
|
|
||||||
Steps: steps,
|
|
||||||
PauseFn: common.MultistepDebugFn(ui),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.runner.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// If there was an error, return that
|
// If there was an error, return that
|
||||||
|
|
|
@ -116,15 +116,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run!
|
// Run!
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
b.runner = &multistep.DebugRunner{
|
|
||||||
Steps: steps,
|
|
||||||
PauseFn: common.MultistepDebugFn(ui),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.runner.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// If there was an error, return that
|
// If there was an error, return that
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mitchellh/packer/template/interpolate"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FloppyConfig is configuration related to created floppy disks and attaching
|
|
||||||
// them to a Parallels virtual machine.
|
|
||||||
type FloppyConfig struct {
|
|
||||||
FloppyFiles []string `mapstructure:"floppy_files"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FloppyConfig) Prepare(ctx *interpolate.Context) []error {
|
|
||||||
if c.FloppyFiles == nil {
|
|
||||||
c.FloppyFiles = make([]string, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFloppyConfigPrepare(t *testing.T) {
|
|
||||||
c := new(FloppyConfig)
|
|
||||||
|
|
||||||
errs := c.Prepare(testConfigTemplate(t))
|
|
||||||
if len(errs) > 0 {
|
|
||||||
t.Fatalf("err: %#v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.FloppyFiles) > 0 {
|
|
||||||
t.Fatal("should not have floppy files")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -186,6 +186,13 @@ func scancodes(message string) []string {
|
||||||
special["<pageUp>"] = []string{"49", "c9"}
|
special["<pageUp>"] = []string{"49", "c9"}
|
||||||
special["<pageDown>"] = []string{"51", "d1"}
|
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 := "!@#$%^&*()_+{}:\"~|<>?"
|
shiftedChars := "!@#$%^&*()_+{}:\"~|<>?"
|
||||||
|
|
||||||
scancodeIndex := make(map[string]uint)
|
scancodeIndex := make(map[string]uint)
|
||||||
|
@ -214,6 +221,78 @@ func scancodes(message string) []string {
|
||||||
for len(message) > 0 {
|
for len(message) > 0 {
|
||||||
var scancode []string
|
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>") {
|
if strings.HasPrefix(message, "<wait>") {
|
||||||
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
|
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
|
||||||
scancode = []string{"wait"}
|
scancode = []string{"wait"}
|
||||||
|
|
|
@ -25,7 +25,7 @@ type Config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
common.HTTPConfig `mapstructure:",squash"`
|
common.HTTPConfig `mapstructure:",squash"`
|
||||||
common.ISOConfig `mapstructure:",squash"`
|
common.ISOConfig `mapstructure:",squash"`
|
||||||
parallelscommon.FloppyConfig `mapstructure:",squash"`
|
common.FloppyConfig `mapstructure:",squash"`
|
||||||
parallelscommon.OutputConfig `mapstructure:",squash"`
|
parallelscommon.OutputConfig `mapstructure:",squash"`
|
||||||
parallelscommon.PrlctlConfig `mapstructure:",squash"`
|
parallelscommon.PrlctlConfig `mapstructure:",squash"`
|
||||||
parallelscommon.PrlctlPostConfig `mapstructure:",squash"`
|
parallelscommon.PrlctlPostConfig `mapstructure:",squash"`
|
||||||
|
@ -215,17 +215,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
state.Put("ui", ui)
|
state.Put("ui", ui)
|
||||||
|
|
||||||
// Run
|
// Run
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
||||||
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.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// If there was an error, return that
|
// If there was an error, return that
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package iso
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mitchellh/packer/packer"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testConfig() map[string]interface{} {
|
func testConfig() map[string]interface{} {
|
||||||
|
@ -46,6 +49,55 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
delete(config, "floppy_files")
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.config.FloppyFiles) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
floppies_path := "../../../common/test-fixtures/floppies"
|
||||||
|
config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||||
|
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
|
||||||
|
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
config["floppy_files"] = []string{"nonexistant.bat", "nonexistant.ps1"}
|
||||||
|
b = Builder{}
|
||||||
|
_, errs := b.Prepare(config)
|
||||||
|
if errs == nil {
|
||||||
|
t.Fatalf("Non existant floppies should trigger multierror")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs.(*packer.MultiError).Errors) != 2 {
|
||||||
|
t.Fatalf("Multierror should work and report 2 errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuilderPrepare_DiskSize(t *testing.T) {
|
func TestBuilderPrepare_DiskSize(t *testing.T) {
|
||||||
var b Builder
|
var b Builder
|
||||||
config := testConfig()
|
config := testConfig()
|
||||||
|
|
|
@ -108,16 +108,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the steps.
|
// Run the steps.
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
||||||
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.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// Report any errors.
|
// Report any errors.
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
// Config is the configuration structure for the builder.
|
// Config is the configuration structure for the builder.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
parallelscommon.FloppyConfig `mapstructure:",squash"`
|
common.FloppyConfig `mapstructure:",squash"`
|
||||||
parallelscommon.OutputConfig `mapstructure:",squash"`
|
parallelscommon.OutputConfig `mapstructure:",squash"`
|
||||||
parallelscommon.PrlctlConfig `mapstructure:",squash"`
|
parallelscommon.PrlctlConfig `mapstructure:",squash"`
|
||||||
parallelscommon.PrlctlPostConfig `mapstructure:",squash"`
|
parallelscommon.PrlctlPostConfig `mapstructure:",squash"`
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package pvm
|
package pvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testConfig(t *testing.T) map[string]interface{} {
|
func testConfig(t *testing.T) map[string]interface{} {
|
||||||
|
@ -11,6 +14,7 @@ func testConfig(t *testing.T) map[string]interface{} {
|
||||||
"ssh_username": "foo",
|
"ssh_username": "foo",
|
||||||
"shutdown_command": "foo",
|
"shutdown_command": "foo",
|
||||||
"parallels_tools_flavor": "lin",
|
"parallels_tools_flavor": "lin",
|
||||||
|
"source_path": "config_test.go",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +72,29 @@ func TestNewConfig_sourcePath(t *testing.T) {
|
||||||
testConfigOk(t, warns, errs)
|
testConfigOk(t, warns, errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewConfig_FloppyFiles(t *testing.T) {
|
||||||
|
c := testConfig(t)
|
||||||
|
floppies_path := "../../../common/test-fixtures/floppies"
|
||||||
|
c["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||||
|
_, _, err := NewConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewConfig_InvalidFloppies(t *testing.T) {
|
||||||
|
c := testConfig(t)
|
||||||
|
c["floppy_files"] = []string{"nonexistant.bat", "nonexistant.ps1"}
|
||||||
|
_, _, errs := NewConfig(c)
|
||||||
|
if errs == nil {
|
||||||
|
t.Fatalf("Non existant floppies should trigger multierror")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs.(*packer.MultiError).Errors) != 2 {
|
||||||
|
t.Fatalf("Multierror should work and report 2 errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewConfig_shutdown_timeout(t *testing.T) {
|
func TestNewConfig_shutdown_timeout(t *testing.T) {
|
||||||
c := testConfig(t)
|
c := testConfig(t)
|
||||||
tf := getTempFile(t)
|
tf := getTempFile(t)
|
||||||
|
|
|
@ -82,6 +82,7 @@ type Config struct {
|
||||||
common.HTTPConfig `mapstructure:",squash"`
|
common.HTTPConfig `mapstructure:",squash"`
|
||||||
common.ISOConfig `mapstructure:",squash"`
|
common.ISOConfig `mapstructure:",squash"`
|
||||||
Comm communicator.Config `mapstructure:",squash"`
|
Comm communicator.Config `mapstructure:",squash"`
|
||||||
|
common.FloppyConfig `mapstructure:",squash"`
|
||||||
|
|
||||||
ISOSkipCache bool `mapstructure:"iso_skip_cache"`
|
ISOSkipCache bool `mapstructure:"iso_skip_cache"`
|
||||||
Accelerator string `mapstructure:"accelerator"`
|
Accelerator string `mapstructure:"accelerator"`
|
||||||
|
@ -139,6 +140,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errs *packer.MultiError
|
||||||
|
warnings := make([]string, 0)
|
||||||
|
|
||||||
if b.config.DiskSize == 0 {
|
if b.config.DiskSize == 0 {
|
||||||
b.config.DiskSize = 40000
|
b.config.DiskSize = 40000
|
||||||
}
|
}
|
||||||
|
@ -215,9 +219,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
b.config.Format = "qcow2"
|
b.config.Format = "qcow2"
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.config.FloppyFiles == nil {
|
errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...)
|
||||||
b.config.FloppyFiles = make([]string, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.NetDevice == "" {
|
if b.config.NetDevice == "" {
|
||||||
b.config.NetDevice = "virtio-net"
|
b.config.NetDevice = "virtio-net"
|
||||||
|
@ -232,9 +234,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
b.config.Comm.SSHTimeout = b.config.SSHWaitTimeout
|
b.config.Comm.SSHTimeout = b.config.SSHWaitTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
var errs *packer.MultiError
|
|
||||||
warnings := make([]string, 0)
|
|
||||||
|
|
||||||
if b.config.ISOSkipCache {
|
if b.config.ISOSkipCache {
|
||||||
b.config.ISOChecksumType = "none"
|
b.config.ISOChecksumType = "none"
|
||||||
}
|
}
|
||||||
|
@ -376,19 +375,40 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
HTTPPortMin: b.config.HTTPPortMin,
|
HTTPPortMin: b.config.HTTPPortMin,
|
||||||
HTTPPortMax: b.config.HTTPPortMax,
|
HTTPPortMax: b.config.HTTPPortMax,
|
||||||
},
|
},
|
||||||
new(stepForwardSSH),
|
)
|
||||||
|
|
||||||
|
if b.config.Comm.Type != "none" {
|
||||||
|
steps = append(steps,
|
||||||
|
new(stepForwardSSH),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
steps = append(steps,
|
||||||
new(stepConfigureVNC),
|
new(stepConfigureVNC),
|
||||||
steprun,
|
steprun,
|
||||||
&stepBootWait{},
|
&stepBootWait{},
|
||||||
&stepTypeBootCommand{},
|
&stepTypeBootCommand{},
|
||||||
&communicator.StepConnect{
|
)
|
||||||
Config: &b.config.Comm,
|
|
||||||
Host: commHost,
|
if b.config.Comm.Type != "none" {
|
||||||
SSHConfig: sshConfig,
|
steps = append(steps,
|
||||||
SSHPort: commPort,
|
&communicator.StepConnect{
|
||||||
},
|
Config: &b.config.Comm,
|
||||||
|
Host: commHost,
|
||||||
|
SSHConfig: sshConfig,
|
||||||
|
SSHPort: commPort,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
steps = append(steps,
|
||||||
new(common.StepProvision),
|
new(common.StepProvision),
|
||||||
|
)
|
||||||
|
steps = append(steps,
|
||||||
new(stepShutdown),
|
new(stepShutdown),
|
||||||
|
)
|
||||||
|
|
||||||
|
steps = append(steps,
|
||||||
new(stepConvertDisk),
|
new(stepConvertDisk),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -402,17 +422,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
state.Put("ui", ui)
|
state.Put("ui", ui)
|
||||||
|
|
||||||
// Run
|
// Run
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
||||||
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.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// If there was an error, return that
|
// If there was an error, return that
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package qemu
|
package qemu
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mitchellh/packer/packer"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testPem = `
|
var testPem = `
|
||||||
|
@ -262,6 +264,55 @@ func TestBuilderPrepare_Format(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
delete(config, "floppy_files")
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.config.FloppyFiles) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
floppies_path := "../../common/test-fixtures/floppies"
|
||||||
|
config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||||
|
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
|
||||||
|
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
config["floppy_files"] = []string{"nonexistant.bat", "nonexistant.ps1"}
|
||||||
|
b = Builder{}
|
||||||
|
_, errs := b.Prepare(config)
|
||||||
|
if errs == nil {
|
||||||
|
t.Fatalf("Non existant floppies should trigger multierror")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs.(*packer.MultiError).Errors) != 2 {
|
||||||
|
t.Fatalf("Multierror should work and report 2 errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||||
var b Builder
|
var b Builder
|
||||||
config := testConfig()
|
config := testConfig()
|
||||||
|
|
|
@ -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)
|
log.Printf("Looking for available communicator (SSH, WinRM, etc) port between %d and %d", config.SSHHostPortMin, config.SSHHostPortMax)
|
||||||
var sshHostPort uint
|
var sshHostPort uint
|
||||||
var offset uint = 0
|
|
||||||
|
|
||||||
portRange := int(config.SSHHostPortMax - config.SSHHostPortMin)
|
portRange := config.SSHHostPortMax - config.SSHHostPortMin + 1
|
||||||
if portRange > 0 {
|
offset := uint(rand.Intn(int(portRange)))
|
||||||
// Have to check if > 0 to avoid a panic
|
|
||||||
offset = uint(rand.Intn(portRange))
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
sshHostPort = offset + config.SSHHostPortMin
|
sshHostPort = offset + config.SSHHostPortMin
|
||||||
if sshHostPort >= config.SSHHostPortMax {
|
|
||||||
offset = 0
|
|
||||||
sshHostPort = config.SSHHostPortMin
|
|
||||||
}
|
|
||||||
log.Printf("Trying port: %d", sshHostPort)
|
log.Printf("Trying port: %d", sshHostPort)
|
||||||
l, err := net.Listen("tcp", fmt.Sprintf(":%d", sshHostPort))
|
l, err := net.Listen("tcp", fmt.Sprintf(":%d", sshHostPort))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -45,6 +37,9 @@ func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
offset++
|
offset++
|
||||||
|
if offset == portRange {
|
||||||
|
offset = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ui.Say(fmt.Sprintf("Found port for communicator (SSH, WinRM, etc): %d.", sshHostPort))
|
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)
|
command, err := getCommandArgs(s.BootDrive, state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error processing QemuArggs: %s", err)
|
err := fmt.Errorf("Error processing QemuArgs: %s", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,6 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
||||||
isoPath := state.Get("iso_path").(string)
|
isoPath := state.Get("iso_path").(string)
|
||||||
vncIP := state.Get("vnc_ip").(string)
|
vncIP := state.Get("vnc_ip").(string)
|
||||||
vncPort := state.Get("vnc_port").(uint)
|
vncPort := state.Get("vnc_port").(uint)
|
||||||
sshHostPort := state.Get("sshHostPort").(uint)
|
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
|
|
||||||
|
@ -74,10 +73,16 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
||||||
defaultArgs := make(map[string]interface{})
|
defaultArgs := make(map[string]interface{})
|
||||||
var deviceArgs []string
|
var deviceArgs []string
|
||||||
var driveArgs []string
|
var driveArgs []string
|
||||||
|
var sshHostPort uint
|
||||||
|
|
||||||
defaultArgs["-name"] = vmName
|
defaultArgs["-name"] = vmName
|
||||||
defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
|
defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
|
||||||
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0,hostfwd=tcp::%v-:%d", sshHostPort, config.Comm.Port())
|
if config.Comm.Type != "none" {
|
||||||
|
sshHostPort = state.Get("sshHostPort").(uint)
|
||||||
|
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0,hostfwd=tcp::%v-:%d", sshHostPort, config.Comm.Port())
|
||||||
|
} else {
|
||||||
|
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0")
|
||||||
|
}
|
||||||
|
|
||||||
qemuVersion, err := driver.Version()
|
qemuVersion, err := driver.Version()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -91,12 +96,12 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
||||||
if qemuMajor >= 2 {
|
if qemuMajor >= 2 {
|
||||||
if config.DiskInterface == "virtio-scsi" {
|
if config.DiskInterface == "virtio-scsi" {
|
||||||
deviceArgs = append(deviceArgs, "virtio-scsi-pci,id=scsi0", "scsi-hd,bus=scsi0.0,drive=drive0")
|
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 {
|
} 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 {
|
} 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))
|
deviceArgs = append(deviceArgs, fmt.Sprintf("%s,netdev=user.0", config.NetDevice))
|
||||||
|
|
||||||
|
@ -157,13 +162,23 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
||||||
|
|
||||||
httpPort := state.Get("http_port").(uint)
|
httpPort := state.Get("http_port").(uint)
|
||||||
ctx := config.ctx
|
ctx := config.ctx
|
||||||
ctx.Data = qemuArgsTemplateData{
|
if config.Comm.Type != "none" {
|
||||||
"10.0.2.2",
|
ctx.Data = qemuArgsTemplateData{
|
||||||
httpPort,
|
"10.0.2.2",
|
||||||
config.HTTPDir,
|
httpPort,
|
||||||
config.OutputDir,
|
config.HTTPDir,
|
||||||
config.VMName,
|
config.OutputDir,
|
||||||
sshHostPort,
|
config.VMName,
|
||||||
|
sshHostPort,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.Data = qemuArgsTemplateData{
|
||||||
|
HTTPIP: "10.0.2.2",
|
||||||
|
HTTPPort: httpPort,
|
||||||
|
HTTPDir: config.HTTPDir,
|
||||||
|
OutputDir: config.OutputDir,
|
||||||
|
Name: config.VMName,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
newQemuArgs, err := processArgs(config.QemuArgs, &ctx)
|
newQemuArgs, err := processArgs(config.QemuArgs, &ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -3,10 +3,11 @@ package qemu
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
|
||||||
"github.com/mitchellh/packer/packer"
|
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This step shuts down the machine. It first attempts to do so gracefully,
|
// This step shuts down the machine. It first attempts to do so gracefully,
|
||||||
|
@ -23,11 +24,29 @@ import (
|
||||||
type stepShutdown struct{}
|
type stepShutdown struct{}
|
||||||
|
|
||||||
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
comm := state.Get("communicator").(packer.Communicator)
|
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
if state.Get("communicator") == nil {
|
||||||
|
cancelCh := make(chan struct{}, 1)
|
||||||
|
go func() {
|
||||||
|
defer close(cancelCh)
|
||||||
|
<-time.After(config.shutdownTimeout)
|
||||||
|
}()
|
||||||
|
ui.Say("Waiting for shutdown...")
|
||||||
|
if ok := driver.WaitForShutdown(cancelCh); ok {
|
||||||
|
log.Println("VM shut down.")
|
||||||
|
return multistep.ActionContinue
|
||||||
|
} else {
|
||||||
|
err := fmt.Errorf("Failed to shutdown")
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
comm := state.Get("communicator").(packer.Communicator)
|
||||||
if config.ShutdownCommand != "" {
|
if config.ShutdownCommand != "" {
|
||||||
ui.Say("Gracefully halting virtual machine...")
|
ui.Say("Gracefully halting virtual machine...")
|
||||||
log.Printf("Executing shutdown command: %s", config.ShutdownCommand)
|
log.Printf("Executing shutdown command: %s", config.ShutdownCommand)
|
||||||
|
|
|
@ -136,6 +136,12 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
||||||
special["<end>"] = 0xFF57
|
special["<end>"] = 0xFF57
|
||||||
special["<pageUp>"] = 0xFF55
|
special["<pageUp>"] = 0xFF55
|
||||||
special["<pageDown>"] = 0xFF56
|
special["<pageDown>"] = 0xFF56
|
||||||
|
special["<leftAlt>"] = 0xFFE9
|
||||||
|
special["<leftCtrl>"] = 0xFFE3
|
||||||
|
special["<leftShift>"] = 0xFFE1
|
||||||
|
special["<rightAlt>"] = 0xFFEA
|
||||||
|
special["<rightCtrl>"] = 0xFFE4
|
||||||
|
special["<rightShift>"] = 0xFFE2
|
||||||
|
|
||||||
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
||||||
|
|
||||||
|
@ -144,6 +150,174 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
||||||
var keyCode uint32
|
var keyCode uint32
|
||||||
keyShift := false
|
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>") {
|
if strings.HasPrefix(original, "<wait>") {
|
||||||
log.Printf("Special code '<wait>' found, sleeping one second")
|
log.Printf("Special code '<wait>' found, sleeping one second")
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFloppyConfigPrepare(t *testing.T) {
|
|
||||||
c := new(FloppyConfig)
|
|
||||||
|
|
||||||
errs := c.Prepare(testConfigTemplate(t))
|
|
||||||
if len(errs) > 0 {
|
|
||||||
t.Fatalf("err: %#v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.FloppyFiles) > 0 {
|
|
||||||
t.Fatal("should not have floppy files")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -37,20 +37,12 @@ func (s *StepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
if !s.SkipNatMapping {
|
if !s.SkipNatMapping {
|
||||||
log.Printf("Looking for available communicator (SSH, WinRM, etc) port between %d and %d",
|
log.Printf("Looking for available communicator (SSH, WinRM, etc) port between %d and %d",
|
||||||
s.HostPortMin, s.HostPortMax)
|
s.HostPortMin, s.HostPortMax)
|
||||||
offset := 0
|
|
||||||
|
|
||||||
portRange := int(s.HostPortMax - s.HostPortMin)
|
portRange := int(s.HostPortMax - s.HostPortMin + 1)
|
||||||
if portRange > 0 {
|
offset := rand.Intn(portRange)
|
||||||
// Have to check if > 0 to avoid a panic
|
|
||||||
offset = rand.Intn(portRange)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
sshHostPort = offset + int(s.HostPortMin)
|
sshHostPort = offset + int(s.HostPortMin)
|
||||||
if sshHostPort >= int(s.HostPortMax) {
|
|
||||||
offset = 0
|
|
||||||
sshHostPort = int(s.HostPortMin)
|
|
||||||
}
|
|
||||||
log.Printf("Trying port: %d", sshHostPort)
|
log.Printf("Trying port: %d", sshHostPort)
|
||||||
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", sshHostPort))
|
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", sshHostPort))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -58,6 +50,9 @@ func (s *StepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
offset++
|
offset++
|
||||||
|
if offset == portRange {
|
||||||
|
offset = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a forwarded port mapping to the VM
|
// Create a forwarded port mapping to the VM
|
||||||
|
|
|
@ -141,6 +141,12 @@ func scancodes(message string) []string {
|
||||||
special["<end>"] = []string{"4f", "cf"}
|
special["<end>"] = []string{"4f", "cf"}
|
||||||
special["<pageUp>"] = []string{"49", "c9"}
|
special["<pageUp>"] = []string{"49", "c9"}
|
||||||
special["<pageDown>"] = []string{"51", "d1"}
|
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 := "~!@#$%^&*()_+{}|:\"<>?"
|
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
||||||
|
|
||||||
|
@ -170,6 +176,78 @@ func scancodes(message string) []string {
|
||||||
for len(message) > 0 {
|
for len(message) > 0 {
|
||||||
var scancode []string
|
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>") {
|
if strings.HasPrefix(message, "<wait>") {
|
||||||
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
|
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
|
||||||
scancode = []string{"wait"}
|
scancode = []string{"wait"}
|
||||||
|
|
|
@ -26,9 +26,9 @@ type Config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
common.HTTPConfig `mapstructure:",squash"`
|
common.HTTPConfig `mapstructure:",squash"`
|
||||||
common.ISOConfig `mapstructure:",squash"`
|
common.ISOConfig `mapstructure:",squash"`
|
||||||
|
common.FloppyConfig `mapstructure:",squash"`
|
||||||
vboxcommon.ExportConfig `mapstructure:",squash"`
|
vboxcommon.ExportConfig `mapstructure:",squash"`
|
||||||
vboxcommon.ExportOpts `mapstructure:",squash"`
|
vboxcommon.ExportOpts `mapstructure:",squash"`
|
||||||
vboxcommon.FloppyConfig `mapstructure:",squash"`
|
|
||||||
vboxcommon.OutputConfig `mapstructure:",squash"`
|
vboxcommon.OutputConfig `mapstructure:",squash"`
|
||||||
vboxcommon.RunConfig `mapstructure:",squash"`
|
vboxcommon.RunConfig `mapstructure:",squash"`
|
||||||
vboxcommon.ShutdownConfig `mapstructure:",squash"`
|
vboxcommon.ShutdownConfig `mapstructure:",squash"`
|
||||||
|
@ -275,17 +275,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
state.Put("ui", ui)
|
state.Put("ui", ui)
|
||||||
|
|
||||||
// Run
|
// Run
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
||||||
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.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// If there was an error, return that
|
// If there was an error, return that
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package iso
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/mitchellh/packer/builder/virtualbox/common"
|
"github.com/mitchellh/packer/builder/virtualbox/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func testConfig() map[string]interface{} {
|
func testConfig() map[string]interface{} {
|
||||||
|
@ -86,6 +89,55 @@ func TestBuilderPrepare_DiskSize(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
delete(config, "floppy_files")
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.config.FloppyFiles) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
floppies_path := "../../../common/test-fixtures/floppies"
|
||||||
|
config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||||
|
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
|
||||||
|
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
config["floppy_files"] = []string{"nonexistant.bat", "nonexistant.ps1"}
|
||||||
|
b = Builder{}
|
||||||
|
_, errs := b.Prepare(config)
|
||||||
|
if errs == nil {
|
||||||
|
t.Fatalf("Non existant floppies should trigger multierror")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs.(*packer.MultiError).Errors) != 2 {
|
||||||
|
t.Fatalf("Multierror should work and report 2 errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuilderPrepare_GuestAdditionsMode(t *testing.T) {
|
func TestBuilderPrepare_GuestAdditionsMode(t *testing.T) {
|
||||||
var b Builder
|
var b Builder
|
||||||
config := testConfig()
|
config := testConfig()
|
||||||
|
|
|
@ -135,16 +135,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the steps.
|
// Run the steps.
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
||||||
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.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// Report any errors.
|
// Report any errors.
|
||||||
|
|
|
@ -16,9 +16,9 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
common.HTTPConfig `mapstructure:",squash"`
|
common.HTTPConfig `mapstructure:",squash"`
|
||||||
|
common.FloppyConfig `mapstructure:",squash"`
|
||||||
vboxcommon.ExportConfig `mapstructure:",squash"`
|
vboxcommon.ExportConfig `mapstructure:",squash"`
|
||||||
vboxcommon.ExportOpts `mapstructure:",squash"`
|
vboxcommon.ExportOpts `mapstructure:",squash"`
|
||||||
vboxcommon.FloppyConfig `mapstructure:",squash"`
|
|
||||||
vboxcommon.OutputConfig `mapstructure:",squash"`
|
vboxcommon.OutputConfig `mapstructure:",squash"`
|
||||||
vboxcommon.RunConfig `mapstructure:",squash"`
|
vboxcommon.RunConfig `mapstructure:",squash"`
|
||||||
vboxcommon.SSHConfig `mapstructure:",squash"`
|
vboxcommon.SSHConfig `mapstructure:",squash"`
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
package ovf
|
package ovf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testConfig(t *testing.T) map[string]interface{} {
|
func testConfig(t *testing.T) map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"ssh_username": "foo",
|
"ssh_username": "foo",
|
||||||
"shutdown_command": "foo",
|
"shutdown_command": "foo",
|
||||||
|
"source_path": "config_test.go",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +48,29 @@ func testConfigOk(t *testing.T, warns []string, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewConfig_FloppyFiles(t *testing.T) {
|
||||||
|
c := testConfig(t)
|
||||||
|
floppies_path := "../../../common/test-fixtures/floppies"
|
||||||
|
c["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||||
|
_, _, err := NewConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewConfig_InvalidFloppies(t *testing.T) {
|
||||||
|
c := testConfig(t)
|
||||||
|
c["floppy_files"] = []string{"nonexistant.bat", "nonexistant.ps1"}
|
||||||
|
_, _, errs := NewConfig(c)
|
||||||
|
if errs == nil {
|
||||||
|
t.Fatalf("Non existant floppies should trigger multierror")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs.(*packer.MultiError).Errors) != 2 {
|
||||||
|
t.Fatalf("Multierror should work and report 2 errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewConfig_sourcePath(t *testing.T) {
|
func TestNewConfig_sourcePath(t *testing.T) {
|
||||||
// Bad
|
// Bad
|
||||||
c := testConfig(t)
|
c := testConfig(t)
|
||||||
|
|
|
@ -168,6 +168,12 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
||||||
special["<end>"] = 0xFF57
|
special["<end>"] = 0xFF57
|
||||||
special["<pageUp>"] = 0xFF55
|
special["<pageUp>"] = 0xFF55
|
||||||
special["<pageDown>"] = 0xFF56
|
special["<pageDown>"] = 0xFF56
|
||||||
|
special["<leftAlt>"] = 0xFFE9
|
||||||
|
special["<leftCtrl>"] = 0xFFE3
|
||||||
|
special["<leftShift>"] = 0xFFE1
|
||||||
|
special["<rightAlt>"] = 0xFFEA
|
||||||
|
special["<rightCtrl>"] = 0xFFE4
|
||||||
|
special["<rightShift>"] = 0xFFE2
|
||||||
|
|
||||||
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
||||||
|
|
||||||
|
@ -176,6 +182,138 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
||||||
var keyCode uint32
|
var keyCode uint32
|
||||||
keyShift := false
|
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>") {
|
if strings.HasPrefix(original, "<wait>") {
|
||||||
log.Printf("Special code '<wait>' found, sleeping one second")
|
log.Printf("Special code '<wait>' found, sleeping one second")
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
|
@ -46,13 +46,13 @@ func EncodeVMX(contents map[string]string) string {
|
||||||
// a list of VMX key fragments that the value must not be quoted
|
// a list of VMX key fragments that the value must not be quoted
|
||||||
// fragments are used to cover multliples (i.e. multiple disks)
|
// fragments are used to cover multliples (i.e. multiple disks)
|
||||||
// keys are still lowercase at this point, use lower fragments
|
// keys are still lowercase at this point, use lower fragments
|
||||||
noQuotes := []string {
|
noQuotes := []string{
|
||||||
".virtualssd",
|
".virtualssd",
|
||||||
}
|
}
|
||||||
|
|
||||||
// a list of VMX key fragments that are case sensitive
|
// a list of VMX key fragments that are case sensitive
|
||||||
// fragments are used to cover multliples (i.e. multiple disks)
|
// fragments are used to cover multliples (i.e. multiple disks)
|
||||||
caseSensitive := []string {
|
caseSensitive := []string{
|
||||||
".virtualSSD",
|
".virtualSSD",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ func EncodeVMX(contents map[string]string) string {
|
||||||
for _, q := range noQuotes {
|
for _, q := range noQuotes {
|
||||||
if strings.Contains(k, q) {
|
if strings.Contains(k, q) {
|
||||||
pat = "%s = %s\n"
|
pat = "%s = %s\n"
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
key := k
|
key := k
|
||||||
|
|
|
@ -29,8 +29,8 @@ scsi0:0.virtualSSD = 1
|
||||||
|
|
||||||
func TestEncodeVMX(t *testing.T) {
|
func TestEncodeVMX(t *testing.T) {
|
||||||
contents := map[string]string{
|
contents := map[string]string{
|
||||||
".encoding": "UTF-8",
|
".encoding": "UTF-8",
|
||||||
"config.version": "8",
|
"config.version": "8",
|
||||||
"scsi0:0.virtualssd": "1",
|
"scsi0:0.virtualssd": "1",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ type Config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
common.HTTPConfig `mapstructure:",squash"`
|
common.HTTPConfig `mapstructure:",squash"`
|
||||||
common.ISOConfig `mapstructure:",squash"`
|
common.ISOConfig `mapstructure:",squash"`
|
||||||
|
common.FloppyConfig `mapstructure:",squash"`
|
||||||
vmwcommon.DriverConfig `mapstructure:",squash"`
|
vmwcommon.DriverConfig `mapstructure:",squash"`
|
||||||
vmwcommon.OutputConfig `mapstructure:",squash"`
|
vmwcommon.OutputConfig `mapstructure:",squash"`
|
||||||
vmwcommon.RunConfig `mapstructure:",squash"`
|
vmwcommon.RunConfig `mapstructure:",squash"`
|
||||||
|
@ -40,7 +41,6 @@ type Config struct {
|
||||||
DiskName string `mapstructure:"vmdk_name"`
|
DiskName string `mapstructure:"vmdk_name"`
|
||||||
DiskSize uint `mapstructure:"disk_size"`
|
DiskSize uint `mapstructure:"disk_size"`
|
||||||
DiskTypeId string `mapstructure:"disk_type_id"`
|
DiskTypeId string `mapstructure:"disk_type_id"`
|
||||||
FloppyFiles []string `mapstructure:"floppy_files"`
|
|
||||||
Format string `mapstructure:"format"`
|
Format string `mapstructure:"format"`
|
||||||
GuestOSType string `mapstructure:"guest_os_type"`
|
GuestOSType string `mapstructure:"guest_os_type"`
|
||||||
Version string `mapstructure:"version"`
|
Version string `mapstructure:"version"`
|
||||||
|
@ -97,6 +97,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...)
|
errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(&b.config.ctx)...)
|
errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(&b.config.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(&b.config.ctx)...)
|
errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(&b.config.ctx)...)
|
||||||
|
errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...)
|
||||||
|
|
||||||
if b.config.DiskName == "" {
|
if b.config.DiskName == "" {
|
||||||
b.config.DiskName = "disk"
|
b.config.DiskName = "disk"
|
||||||
|
@ -115,10 +116,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.config.FloppyFiles == nil {
|
|
||||||
b.config.FloppyFiles = make([]string, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.GuestOSType == "" {
|
if b.config.GuestOSType == "" {
|
||||||
b.config.GuestOSType = "other"
|
b.config.GuestOSType = "other"
|
||||||
}
|
}
|
||||||
|
@ -308,17 +305,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run!
|
// Run!
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
||||||
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.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// If there was an error, return that
|
// If there was an error, return that
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package iso
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -106,7 +107,8 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
||||||
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
config["floppy_files"] = []string{"foo", "bar"}
|
floppies_path := "../../../common/test-fixtures/floppies"
|
||||||
|
config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||||
b = Builder{}
|
b = Builder{}
|
||||||
warns, err = b.Prepare(config)
|
warns, err = b.Prepare(config)
|
||||||
if len(warns) > 0 {
|
if len(warns) > 0 {
|
||||||
|
@ -116,12 +118,27 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
||||||
t.Fatalf("should not have error: %s", err)
|
t.Fatalf("should not have error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := []string{"foo", "bar"}
|
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||||
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
|
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
|
||||||
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
config["floppy_files"] = []string{"nonexistant.bat", "nonexistant.ps1"}
|
||||||
|
b = Builder{}
|
||||||
|
_, errs := b.Prepare(config)
|
||||||
|
if errs == nil {
|
||||||
|
t.Fatalf("Non existant floppies should trigger multierror")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs.(*packer.MultiError).Errors) != 2 {
|
||||||
|
t.Fatalf("Multierror should work and report 2 errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuilderPrepare_Format(t *testing.T) {
|
func TestBuilderPrepare_Format(t *testing.T) {
|
||||||
var b Builder
|
var b Builder
|
||||||
config := testConfig()
|
config := testConfig()
|
||||||
|
|
|
@ -122,16 +122,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the steps.
|
// Run the steps.
|
||||||
if b.config.PackerDebug {
|
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
||||||
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.Run(state)
|
b.runner.Run(state)
|
||||||
|
|
||||||
// Report any errors.
|
// Report any errors.
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
package vmx
|
package vmx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
||||||
|
@ -33,7 +36,8 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
||||||
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
config["floppy_files"] = []string{"foo", "bar"}
|
floppies_path := "../../../common/test-fixtures/floppies"
|
||||||
|
config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||||
b = Builder{}
|
b = Builder{}
|
||||||
warns, err = b.Prepare(config)
|
warns, err = b.Prepare(config)
|
||||||
if len(warns) > 0 {
|
if len(warns) > 0 {
|
||||||
|
@ -43,8 +47,23 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
||||||
t.Fatalf("should not have error: %s", err)
|
t.Fatalf("should not have error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := []string{"foo", "bar"}
|
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||||
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
|
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
|
||||||
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig(t)
|
||||||
|
config["floppy_files"] = []string{"nonexistant.bat", "nonexistant.ps1"}
|
||||||
|
b = Builder{}
|
||||||
|
_, errs := b.Prepare(config)
|
||||||
|
if errs == nil {
|
||||||
|
t.Fatalf("Non existant floppies should trigger multierror")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs.(*packer.MultiError).Errors) != 2 {
|
||||||
|
t.Fatalf("Multierror should work and report 2 errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
common.HTTPConfig `mapstructure:",squash"`
|
common.HTTPConfig `mapstructure:",squash"`
|
||||||
|
common.FloppyConfig `mapstructure:",squash"`
|
||||||
vmwcommon.DriverConfig `mapstructure:",squash"`
|
vmwcommon.DriverConfig `mapstructure:",squash"`
|
||||||
vmwcommon.OutputConfig `mapstructure:",squash"`
|
vmwcommon.OutputConfig `mapstructure:",squash"`
|
||||||
vmwcommon.RunConfig `mapstructure:",squash"`
|
vmwcommon.RunConfig `mapstructure:",squash"`
|
||||||
|
@ -24,7 +25,6 @@ type Config struct {
|
||||||
vmwcommon.VMXConfig `mapstructure:",squash"`
|
vmwcommon.VMXConfig `mapstructure:",squash"`
|
||||||
|
|
||||||
BootCommand []string `mapstructure:"boot_command"`
|
BootCommand []string `mapstructure:"boot_command"`
|
||||||
FloppyFiles []string `mapstructure:"floppy_files"`
|
|
||||||
RemoteType string `mapstructure:"remote_type"`
|
RemoteType string `mapstructure:"remote_type"`
|
||||||
SkipCompaction bool `mapstructure:"skip_compaction"`
|
SkipCompaction bool `mapstructure:"skip_compaction"`
|
||||||
SourcePath string `mapstructure:"source_path"`
|
SourcePath string `mapstructure:"source_path"`
|
||||||
|
@ -64,6 +64,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(&c.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.ToolsConfig.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.ToolsConfig.Prepare(&c.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(&c.ctx)...)
|
||||||
|
errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...)
|
||||||
|
|
||||||
if c.SourcePath == "" {
|
if c.SourcePath == "" {
|
||||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is blank, but is required"))
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is blank, but is required"))
|
||||||
|
|
|
@ -10,6 +10,7 @@ func testConfig(t *testing.T) map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"ssh_username": "foo",
|
"ssh_username": "foo",
|
||||||
"shutdown_command": "foo",
|
"shutdown_command": "foo",
|
||||||
|
"source_path": "config_test.go",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/helper/enumflag"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"github.com/mitchellh/packer/template"
|
"github.com/mitchellh/packer/template"
|
||||||
)
|
)
|
||||||
|
@ -20,11 +21,14 @@ type BuildCommand struct {
|
||||||
|
|
||||||
func (c BuildCommand) Run(args []string) int {
|
func (c BuildCommand) Run(args []string) int {
|
||||||
var cfgColor, cfgDebug, cfgForce, cfgParallel bool
|
var cfgColor, cfgDebug, cfgForce, cfgParallel bool
|
||||||
|
var cfgOnError string
|
||||||
flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars)
|
flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars)
|
||||||
flags.Usage = func() { c.Ui.Say(c.Help()) }
|
flags.Usage = func() { c.Ui.Say(c.Help()) }
|
||||||
flags.BoolVar(&cfgColor, "color", true, "")
|
flags.BoolVar(&cfgColor, "color", true, "")
|
||||||
flags.BoolVar(&cfgDebug, "debug", false, "")
|
flags.BoolVar(&cfgDebug, "debug", false, "")
|
||||||
flags.BoolVar(&cfgForce, "force", false, "")
|
flags.BoolVar(&cfgForce, "force", false, "")
|
||||||
|
flagOnError := enumflag.New(&cfgOnError, "cleanup", "abort", "ask")
|
||||||
|
flags.Var(flagOnError, "on-error", "")
|
||||||
flags.BoolVar(&cfgParallel, "parallel", true, "")
|
flags.BoolVar(&cfgParallel, "parallel", true, "")
|
||||||
if err := flags.Parse(args); err != nil {
|
if err := flags.Parse(args); err != nil {
|
||||||
return 1
|
return 1
|
||||||
|
@ -99,12 +103,14 @@ func (c BuildCommand) Run(args []string) int {
|
||||||
|
|
||||||
log.Printf("Build debug mode: %v", cfgDebug)
|
log.Printf("Build debug mode: %v", cfgDebug)
|
||||||
log.Printf("Force build: %v", cfgForce)
|
log.Printf("Force build: %v", cfgForce)
|
||||||
|
log.Printf("On error: %v", cfgOnError)
|
||||||
|
|
||||||
// Set the debug and force mode and prepare all the builds
|
// Set the debug and force mode and prepare all the builds
|
||||||
for _, b := range builds {
|
for _, b := range builds {
|
||||||
log.Printf("Preparing build: %s", b.Name())
|
log.Printf("Preparing build: %s", b.Name())
|
||||||
b.SetDebug(cfgDebug)
|
b.SetDebug(cfgDebug)
|
||||||
b.SetForce(cfgForce)
|
b.SetForce(cfgForce)
|
||||||
|
b.SetOnError(cfgOnError)
|
||||||
|
|
||||||
warnings, err := b.Prepare()
|
warnings, err := b.Prepare()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -284,7 +290,7 @@ Options:
|
||||||
-except=foo,bar,baz Build all builds other than these
|
-except=foo,bar,baz Build all builds other than these
|
||||||
-force Force a build to continue if artifacts exist, deletes existing artifacts
|
-force Force a build to continue if artifacts exist, deletes existing artifacts
|
||||||
-machine-readable Machine-readable output
|
-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)
|
-parallel=false Disable parallelization (on by default)
|
||||||
-var 'key=value' Variable for templates, can be used multiple times.
|
-var 'key=value' Variable for templates, can be used multiple times.
|
||||||
-var-file=path JSON file containing user variables.
|
-var-file=path JSON file containing user variables.
|
||||||
|
|
|
@ -15,50 +15,49 @@ import (
|
||||||
|
|
||||||
amazonchrootbuilder "github.com/mitchellh/packer/builder/amazon/chroot"
|
amazonchrootbuilder "github.com/mitchellh/packer/builder/amazon/chroot"
|
||||||
amazonebsbuilder "github.com/mitchellh/packer/builder/amazon/ebs"
|
amazonebsbuilder "github.com/mitchellh/packer/builder/amazon/ebs"
|
||||||
amazonimportpostprocessor "github.com/mitchellh/packer/post-processor/amazon-import"
|
|
||||||
amazoninstancebuilder "github.com/mitchellh/packer/builder/amazon/instance"
|
amazoninstancebuilder "github.com/mitchellh/packer/builder/amazon/instance"
|
||||||
ansiblelocalprovisioner "github.com/mitchellh/packer/provisioner/ansible-local"
|
|
||||||
ansibleprovisioner "github.com/mitchellh/packer/provisioner/ansible"
|
|
||||||
artificepostprocessor "github.com/mitchellh/packer/post-processor/artifice"
|
|
||||||
atlaspostprocessor "github.com/mitchellh/packer/post-processor/atlas"
|
|
||||||
azurearmbuilder "github.com/mitchellh/packer/builder/azure/arm"
|
azurearmbuilder "github.com/mitchellh/packer/builder/azure/arm"
|
||||||
checksumpostprocessor "github.com/mitchellh/packer/post-processor/checksum"
|
|
||||||
chefclientprovisioner "github.com/mitchellh/packer/provisioner/chef-client"
|
|
||||||
chefsoloprovisioner "github.com/mitchellh/packer/provisioner/chef-solo"
|
|
||||||
compresspostprocessor "github.com/mitchellh/packer/post-processor/compress"
|
|
||||||
digitaloceanbuilder "github.com/mitchellh/packer/builder/digitalocean"
|
digitaloceanbuilder "github.com/mitchellh/packer/builder/digitalocean"
|
||||||
dockerbuilder "github.com/mitchellh/packer/builder/docker"
|
dockerbuilder "github.com/mitchellh/packer/builder/docker"
|
||||||
dockerimportpostprocessor "github.com/mitchellh/packer/post-processor/docker-import"
|
|
||||||
dockerpushpostprocessor "github.com/mitchellh/packer/post-processor/docker-push"
|
|
||||||
dockersavepostprocessor "github.com/mitchellh/packer/post-processor/docker-save"
|
|
||||||
dockertagpostprocessor "github.com/mitchellh/packer/post-processor/docker-tag"
|
|
||||||
filebuilder "github.com/mitchellh/packer/builder/file"
|
filebuilder "github.com/mitchellh/packer/builder/file"
|
||||||
fileprovisioner "github.com/mitchellh/packer/provisioner/file"
|
|
||||||
googlecomputebuilder "github.com/mitchellh/packer/builder/googlecompute"
|
googlecomputebuilder "github.com/mitchellh/packer/builder/googlecompute"
|
||||||
googlecomputeexportpostprocessor "github.com/mitchellh/packer/post-processor/googlecompute-export"
|
|
||||||
manifestpostprocessor "github.com/mitchellh/packer/post-processor/manifest"
|
|
||||||
nullbuilder "github.com/mitchellh/packer/builder/null"
|
nullbuilder "github.com/mitchellh/packer/builder/null"
|
||||||
openstackbuilder "github.com/mitchellh/packer/builder/openstack"
|
openstackbuilder "github.com/mitchellh/packer/builder/openstack"
|
||||||
parallelsisobuilder "github.com/mitchellh/packer/builder/parallels/iso"
|
parallelsisobuilder "github.com/mitchellh/packer/builder/parallels/iso"
|
||||||
parallelspvmbuilder "github.com/mitchellh/packer/builder/parallels/pvm"
|
parallelspvmbuilder "github.com/mitchellh/packer/builder/parallels/pvm"
|
||||||
powershellprovisioner "github.com/mitchellh/packer/provisioner/powershell"
|
|
||||||
puppetmasterlessprovisioner "github.com/mitchellh/packer/provisioner/puppet-masterless"
|
|
||||||
puppetserverprovisioner "github.com/mitchellh/packer/provisioner/puppet-server"
|
|
||||||
qemubuilder "github.com/mitchellh/packer/builder/qemu"
|
qemubuilder "github.com/mitchellh/packer/builder/qemu"
|
||||||
saltmasterlessprovisioner "github.com/mitchellh/packer/provisioner/salt-masterless"
|
|
||||||
shelllocalpostprocessor "github.com/mitchellh/packer/post-processor/shell-local"
|
|
||||||
shelllocalprovisioner "github.com/mitchellh/packer/provisioner/shell-local"
|
|
||||||
shellprovisioner "github.com/mitchellh/packer/provisioner/shell"
|
|
||||||
vagrantcloudpostprocessor "github.com/mitchellh/packer/post-processor/vagrant-cloud"
|
|
||||||
vagrantpostprocessor "github.com/mitchellh/packer/post-processor/vagrant"
|
|
||||||
virtualboxisobuilder "github.com/mitchellh/packer/builder/virtualbox/iso"
|
virtualboxisobuilder "github.com/mitchellh/packer/builder/virtualbox/iso"
|
||||||
virtualboxovfbuilder "github.com/mitchellh/packer/builder/virtualbox/ovf"
|
virtualboxovfbuilder "github.com/mitchellh/packer/builder/virtualbox/ovf"
|
||||||
vmwareisobuilder "github.com/mitchellh/packer/builder/vmware/iso"
|
vmwareisobuilder "github.com/mitchellh/packer/builder/vmware/iso"
|
||||||
vmwarevmxbuilder "github.com/mitchellh/packer/builder/vmware/vmx"
|
vmwarevmxbuilder "github.com/mitchellh/packer/builder/vmware/vmx"
|
||||||
|
amazonimportpostprocessor "github.com/mitchellh/packer/post-processor/amazon-import"
|
||||||
|
artificepostprocessor "github.com/mitchellh/packer/post-processor/artifice"
|
||||||
|
atlaspostprocessor "github.com/mitchellh/packer/post-processor/atlas"
|
||||||
|
checksumpostprocessor "github.com/mitchellh/packer/post-processor/checksum"
|
||||||
|
compresspostprocessor "github.com/mitchellh/packer/post-processor/compress"
|
||||||
|
dockerimportpostprocessor "github.com/mitchellh/packer/post-processor/docker-import"
|
||||||
|
dockerpushpostprocessor "github.com/mitchellh/packer/post-processor/docker-push"
|
||||||
|
dockersavepostprocessor "github.com/mitchellh/packer/post-processor/docker-save"
|
||||||
|
dockertagpostprocessor "github.com/mitchellh/packer/post-processor/docker-tag"
|
||||||
|
googlecomputeexportpostprocessor "github.com/mitchellh/packer/post-processor/googlecompute-export"
|
||||||
|
manifestpostprocessor "github.com/mitchellh/packer/post-processor/manifest"
|
||||||
|
shelllocalpostprocessor "github.com/mitchellh/packer/post-processor/shell-local"
|
||||||
|
vagrantpostprocessor "github.com/mitchellh/packer/post-processor/vagrant"
|
||||||
|
vagrantcloudpostprocessor "github.com/mitchellh/packer/post-processor/vagrant-cloud"
|
||||||
vspherepostprocessor "github.com/mitchellh/packer/post-processor/vsphere"
|
vspherepostprocessor "github.com/mitchellh/packer/post-processor/vsphere"
|
||||||
|
ansibleprovisioner "github.com/mitchellh/packer/provisioner/ansible"
|
||||||
|
ansiblelocalprovisioner "github.com/mitchellh/packer/provisioner/ansible-local"
|
||||||
|
chefclientprovisioner "github.com/mitchellh/packer/provisioner/chef-client"
|
||||||
|
chefsoloprovisioner "github.com/mitchellh/packer/provisioner/chef-solo"
|
||||||
|
fileprovisioner "github.com/mitchellh/packer/provisioner/file"
|
||||||
|
powershellprovisioner "github.com/mitchellh/packer/provisioner/powershell"
|
||||||
|
puppetmasterlessprovisioner "github.com/mitchellh/packer/provisioner/puppet-masterless"
|
||||||
|
puppetserverprovisioner "github.com/mitchellh/packer/provisioner/puppet-server"
|
||||||
|
saltmasterlessprovisioner "github.com/mitchellh/packer/provisioner/salt-masterless"
|
||||||
|
shellprovisioner "github.com/mitchellh/packer/provisioner/shell"
|
||||||
|
shelllocalprovisioner "github.com/mitchellh/packer/provisioner/shell-local"
|
||||||
windowsrestartprovisioner "github.com/mitchellh/packer/provisioner/windows-restart"
|
windowsrestartprovisioner "github.com/mitchellh/packer/provisioner/windows-restart"
|
||||||
windowsshellprovisioner "github.com/mitchellh/packer/provisioner/windows-shell"
|
windowsshellprovisioner "github.com/mitchellh/packer/provisioner/windows-shell"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PluginCommand struct {
|
type PluginCommand struct {
|
||||||
|
@ -67,61 +66,58 @@ type PluginCommand struct {
|
||||||
|
|
||||||
var Builders = map[string]packer.Builder{
|
var Builders = map[string]packer.Builder{
|
||||||
"amazon-chroot": new(amazonchrootbuilder.Builder),
|
"amazon-chroot": new(amazonchrootbuilder.Builder),
|
||||||
"amazon-ebs": new(amazonebsbuilder.Builder),
|
"amazon-ebs": new(amazonebsbuilder.Builder),
|
||||||
"amazon-instance": new(amazoninstancebuilder.Builder),
|
"amazon-instance": new(amazoninstancebuilder.Builder),
|
||||||
"azure-arm": new(azurearmbuilder.Builder),
|
"azure-arm": new(azurearmbuilder.Builder),
|
||||||
"digitalocean": new(digitaloceanbuilder.Builder),
|
"digitalocean": new(digitaloceanbuilder.Builder),
|
||||||
"docker": new(dockerbuilder.Builder),
|
"docker": new(dockerbuilder.Builder),
|
||||||
"file": new(filebuilder.Builder),
|
"file": new(filebuilder.Builder),
|
||||||
"googlecompute": new(googlecomputebuilder.Builder),
|
"googlecompute": new(googlecomputebuilder.Builder),
|
||||||
"null": new(nullbuilder.Builder),
|
"null": new(nullbuilder.Builder),
|
||||||
"openstack": new(openstackbuilder.Builder),
|
"openstack": new(openstackbuilder.Builder),
|
||||||
"parallels-iso": new(parallelsisobuilder.Builder),
|
"parallels-iso": new(parallelsisobuilder.Builder),
|
||||||
"parallels-pvm": new(parallelspvmbuilder.Builder),
|
"parallels-pvm": new(parallelspvmbuilder.Builder),
|
||||||
"qemu": new(qemubuilder.Builder),
|
"qemu": new(qemubuilder.Builder),
|
||||||
"virtualbox-iso": new(virtualboxisobuilder.Builder),
|
"virtualbox-iso": new(virtualboxisobuilder.Builder),
|
||||||
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
|
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
|
||||||
"vmware-iso": new(vmwareisobuilder.Builder),
|
"vmware-iso": new(vmwareisobuilder.Builder),
|
||||||
"vmware-vmx": new(vmwarevmxbuilder.Builder),
|
"vmware-vmx": new(vmwarevmxbuilder.Builder),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var Provisioners = map[string]packer.Provisioner{
|
var Provisioners = map[string]packer.Provisioner{
|
||||||
"ansible": new(ansibleprovisioner.Provisioner),
|
"ansible": new(ansibleprovisioner.Provisioner),
|
||||||
"ansible-local": new(ansiblelocalprovisioner.Provisioner),
|
"ansible-local": new(ansiblelocalprovisioner.Provisioner),
|
||||||
"chef-client": new(chefclientprovisioner.Provisioner),
|
"chef-client": new(chefclientprovisioner.Provisioner),
|
||||||
"chef-solo": new(chefsoloprovisioner.Provisioner),
|
"chef-solo": new(chefsoloprovisioner.Provisioner),
|
||||||
"file": new(fileprovisioner.Provisioner),
|
"file": new(fileprovisioner.Provisioner),
|
||||||
"powershell": new(powershellprovisioner.Provisioner),
|
"powershell": new(powershellprovisioner.Provisioner),
|
||||||
"puppet-masterless": new(puppetmasterlessprovisioner.Provisioner),
|
"puppet-masterless": new(puppetmasterlessprovisioner.Provisioner),
|
||||||
"puppet-server": new(puppetserverprovisioner.Provisioner),
|
"puppet-server": new(puppetserverprovisioner.Provisioner),
|
||||||
"salt-masterless": new(saltmasterlessprovisioner.Provisioner),
|
"salt-masterless": new(saltmasterlessprovisioner.Provisioner),
|
||||||
"shell": new(shellprovisioner.Provisioner),
|
"shell": new(shellprovisioner.Provisioner),
|
||||||
"shell-local": new(shelllocalprovisioner.Provisioner),
|
"shell-local": new(shelllocalprovisioner.Provisioner),
|
||||||
"windows-restart": new(windowsrestartprovisioner.Provisioner),
|
"windows-restart": new(windowsrestartprovisioner.Provisioner),
|
||||||
"windows-shell": new(windowsshellprovisioner.Provisioner),
|
"windows-shell": new(windowsshellprovisioner.Provisioner),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var PostProcessors = map[string]packer.PostProcessor{
|
var PostProcessors = map[string]packer.PostProcessor{
|
||||||
"amazon-import": new(amazonimportpostprocessor.PostProcessor),
|
"amazon-import": new(amazonimportpostprocessor.PostProcessor),
|
||||||
"artifice": new(artificepostprocessor.PostProcessor),
|
"artifice": new(artificepostprocessor.PostProcessor),
|
||||||
"atlas": new(atlaspostprocessor.PostProcessor),
|
"atlas": new(atlaspostprocessor.PostProcessor),
|
||||||
"checksum": new(checksumpostprocessor.PostProcessor),
|
"checksum": new(checksumpostprocessor.PostProcessor),
|
||||||
"compress": new(compresspostprocessor.PostProcessor),
|
"compress": new(compresspostprocessor.PostProcessor),
|
||||||
"docker-import": new(dockerimportpostprocessor.PostProcessor),
|
"docker-import": new(dockerimportpostprocessor.PostProcessor),
|
||||||
"docker-push": new(dockerpushpostprocessor.PostProcessor),
|
"docker-push": new(dockerpushpostprocessor.PostProcessor),
|
||||||
"docker-save": new(dockersavepostprocessor.PostProcessor),
|
"docker-save": new(dockersavepostprocessor.PostProcessor),
|
||||||
"docker-tag": new(dockertagpostprocessor.PostProcessor),
|
"docker-tag": new(dockertagpostprocessor.PostProcessor),
|
||||||
"googlecompute-export": new(googlecomputeexportpostprocessor.PostProcessor),
|
"googlecompute-export": new(googlecomputeexportpostprocessor.PostProcessor),
|
||||||
"manifest": new(manifestpostprocessor.PostProcessor),
|
"manifest": new(manifestpostprocessor.PostProcessor),
|
||||||
"shell-local": new(shelllocalpostprocessor.PostProcessor),
|
"shell-local": new(shelllocalpostprocessor.PostProcessor),
|
||||||
"vagrant": new(vagrantpostprocessor.PostProcessor),
|
"vagrant": new(vagrantpostprocessor.PostProcessor),
|
||||||
"vagrant-cloud": new(vagrantcloudpostprocessor.PostProcessor),
|
"vagrant-cloud": new(vagrantcloudpostprocessor.PostProcessor),
|
||||||
"vsphere": new(vspherepostprocessor.PostProcessor),
|
"vsphere": new(vspherepostprocessor.PostProcessor),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var pluginRegexp = regexp.MustCompile("packer-(builder|post-processor|provisioner)-(.+)")
|
var pluginRegexp = regexp.MustCompile("packer-(builder|post-processor|provisioner)-(.+)")
|
||||||
|
|
||||||
func (c *PluginCommand) Run(args []string) int {
|
func (c *PluginCommand) Run(args []string) int {
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
const archiveTemplateEntry = ".packer-template"
|
const archiveTemplateEntry = ".packer-template"
|
||||||
|
|
||||||
var (
|
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())
|
errInvalidName = fmt.Errorf("Your build name can only contain these characters: %s", reName.String())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,28 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/mitchellh/packer/template/interpolate"
|
"github.com/mitchellh/packer/template/interpolate"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FloppyConfig is configuration related to created floppy disks and attaching
|
|
||||||
// them to a VirtualBox machine.
|
|
||||||
type FloppyConfig struct {
|
type FloppyConfig struct {
|
||||||
FloppyFiles []string `mapstructure:"floppy_files"`
|
FloppyFiles []string `mapstructure:"floppy_files"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *FloppyConfig) Prepare(ctx *interpolate.Context) []error {
|
func (c *FloppyConfig) Prepare(ctx *interpolate.Context) []error {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
if c.FloppyFiles == nil {
|
if c.FloppyFiles == nil {
|
||||||
c.FloppyFiles = make([]string, 0)
|
c.FloppyFiles = make([]string, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
for _, path := range c.FloppyFiles {
|
||||||
|
if _, err := os.Stat(path); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Bad Floppy disk file '%s': %s", path, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
}
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNilFloppies(t *testing.T) {
|
||||||
|
c := FloppyConfig{}
|
||||||
|
errs := c.Prepare(nil)
|
||||||
|
if len(errs) != 0 {
|
||||||
|
t.Fatal("nil floppies array should not fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.FloppyFiles) > 0 {
|
||||||
|
t.Fatal("struct should not have floppy files")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyArrayFloppies(t *testing.T) {
|
||||||
|
c := FloppyConfig{
|
||||||
|
FloppyFiles: make([]string, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := c.Prepare(nil)
|
||||||
|
if len(errs) != 0 {
|
||||||
|
t.Fatal("empty floppies array should never fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.FloppyFiles) > 0 {
|
||||||
|
t.Fatal("struct should not have floppy files")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExistingFloppyFile(t *testing.T) {
|
||||||
|
c := FloppyConfig{
|
||||||
|
FloppyFiles: []string{"floppy_config.go"},
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := c.Prepare(nil)
|
||||||
|
if len(errs) != 0 {
|
||||||
|
t.Fatal("array with existing floppies should not fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNonExistingFloppyFile(t *testing.T) {
|
||||||
|
c := FloppyConfig{
|
||||||
|
FloppyFiles: []string{"floppy_config.foo"},
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := c.Prepare(nil)
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Fatal("array with non existing floppies should return errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiErrorFloppyFiles(t *testing.T) {
|
||||||
|
c := FloppyConfig{
|
||||||
|
FloppyFiles: []string{"floppy_config.foo", "floppy_config.go", "floppy_config.bar", "floppy_config_test.go", "floppy_config.baz"},
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := c.Prepare(nil)
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Fatal("array with non existing floppies should return errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedErrors := 3
|
||||||
|
if count := len(errs); count != expectedErrors {
|
||||||
|
t.Fatalf("array with %v non existing floppy should return %v errors but it is returning %v", expectedErrors, expectedErrors, count)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"`
|
PackerBuilderType string `mapstructure:"packer_builder_type"`
|
||||||
PackerDebug bool `mapstructure:"packer_debug"`
|
PackerDebug bool `mapstructure:"packer_debug"`
|
||||||
PackerForce bool `mapstructure:"packer_force"`
|
PackerForce bool `mapstructure:"packer_force"`
|
||||||
|
PackerOnError string `mapstructure:"packer_on_error"`
|
||||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables"`
|
PackerUserVars map[string]string `mapstructure:"packer_user_variables"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package googlecompute
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -25,7 +25,7 @@ func Retry(initialInterval float64, maxInterval float64, numTries uint, function
|
||||||
done := false
|
done := false
|
||||||
interval := initialInterval
|
interval := initialInterval
|
||||||
for i := uint(0); !done && (numTries == 0 || i < numTries); i++ {
|
for i := uint(0); !done && (numTries == 0 || i < numTries); i++ {
|
||||||
done, err = function()
|
done, err = function()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -33,12 +33,12 @@ func Retry(initialInterval float64, maxInterval float64, numTries uint, function
|
||||||
if !done {
|
if !done {
|
||||||
// Retry after delay. Calculate next delay.
|
// Retry after delay. Calculate next delay.
|
||||||
time.Sleep(time.Duration(interval) * time.Second)
|
time.Sleep(time.Duration(interval) * time.Second)
|
||||||
interval = math.Min(interval * 2, maxInterval)
|
interval = math.Min(interval*2, maxInterval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !done {
|
if !done {
|
||||||
return RetryExhaustedError
|
return RetryExhaustedError
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package googlecompute
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -0,0 +1 @@
|
||||||
|
Echo I am a floppy with a batch file
|
|
@ -0,0 +1 @@
|
||||||
|
Write-Host "I am a floppy with some Powershell"
|
|
@ -13,4 +13,3 @@ func TestCommIsCommunicator(t *testing.T) {
|
||||||
t.Fatalf("comm must be a communicator")
|
t.Fatalf("comm must be a communicator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,7 @@ func (c *comm) UploadDir(dst string, src string, excl []string) error {
|
||||||
func (c *comm) DownloadDir(src string, dst string, excl []string) error {
|
func (c *comm) DownloadDir(src string, dst string, excl []string) error {
|
||||||
log.Printf("Download dir '%s' to '%s'", src, dst)
|
log.Printf("Download dir '%s' to '%s'", src, dst)
|
||||||
scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error {
|
scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error {
|
||||||
|
dirStack := []string{dst}
|
||||||
for {
|
for {
|
||||||
fmt.Fprint(w, "\x00")
|
fmt.Fprint(w, "\x00")
|
||||||
|
|
||||||
|
@ -178,6 +179,13 @@ func (c *comm) DownloadDir(src string, dst string, excl []string) error {
|
||||||
return fmt.Errorf("%s", fi[1:len(fi)])
|
return fmt.Errorf("%s", fi[1:len(fi)])
|
||||||
case 'C', 'D':
|
case 'C', 'D':
|
||||||
break
|
break
|
||||||
|
case 'E':
|
||||||
|
dirStack = dirStack[:len(dirStack)-1]
|
||||||
|
if len(dirStack) == 1 {
|
||||||
|
fmt.Fprint(w, "\x00")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
continue
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unexpected server response (%x)", fi[0])
|
return fmt.Errorf("unexpected server response (%x)", fi[0])
|
||||||
}
|
}
|
||||||
|
@ -195,14 +203,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)
|
log.Printf("Download dir mode:%s size:%d name:%s", mode, size, name)
|
||||||
|
|
||||||
|
dst = filepath.Join(dirStack...)
|
||||||
switch fi[0] {
|
switch fi[0] {
|
||||||
case 'D':
|
case 'D':
|
||||||
err = os.MkdirAll(filepath.Join(dst, name), os.FileMode(0755))
|
err = os.MkdirAll(filepath.Join(dst, name), os.FileMode(0755))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, "\x00")
|
dirStack = append(dirStack, name)
|
||||||
return nil
|
continue
|
||||||
case 'C':
|
case 'C':
|
||||||
fmt.Fprint(w, "\x00")
|
fmt.Fprint(w, "\x00")
|
||||||
err = scpDownloadFile(filepath.Join(dst, name), stdoutR, size, os.FileMode(0644))
|
err = scpDownloadFile(filepath.Join(dst, name), stdoutR, size, os.FileMode(0644))
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue