Merge remote-tracking branch 'origin/master' into dynamic-source-ami

This commit is contained in:
Chris Lundquist 2016-10-01 22:20:17 +00:00
commit 1b4895c684
264 changed files with 31416 additions and 4500 deletions

View File

@ -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)

178
Godeps/Godeps.json generated
View File

@ -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"
} }
] ]
} }

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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) {}

View File

@ -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) {}

View File

@ -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
} }

View File

@ -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)
} }

View File

@ -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())
} }
} }
} }

View File

@ -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")

View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -157,7 +157,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ui.Message(fmt.Sprintf("temp admin password: '%s'", b.config.Password)) 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 {

View File

@ -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]

View File

@ -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

View File

@ -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": ""
}] }]
} }
` `

View 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
} }

View File

@ -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)

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -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
}

View File

@ -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,

View File

@ -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
} }

View File

@ -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,
} }

View File

@ -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) {
@ -191,4 +192,4 @@ func (c *Config) CalcTimeout() error {
} }
c.stateTimeout = stateTimeout c.stateTimeout = stateTimeout
return nil return nil
} }

View File

@ -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,26 +22,36 @@ 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)
// GetNatIP gets the NAT IP address for the instance. // GetNatIP gets the NAT IP address for the instance.
GetNatIP(zone, name string) (string, error) GetNatIP(zone, name string) (string, error)
// 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"`
}

View File

@ -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 {
@ -207,10 +259,17 @@ func (d *driverGCE) GetSerialPortOutput(zone, name string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
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

View File

@ -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
@ -38,47 +54,58 @@ type DriverMock struct {
GetInternalIPName string GetInternalIPName string
GetInternalIPResult string GetInternalIPResult string
GetInternalIPErr error GetInternalIPErr error
GetSerialPortOutputZone string GetSerialPortOutputZone string
GetSerialPortOutputName string GetSerialPortOutputName string
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
}

View File

@ -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
}

View File

@ -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())
}

View File

@ -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 = ""

View File

@ -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
} }

View File

@ -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:

View File

@ -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")
}
} }

View File

@ -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))
} }
} }

View File

@ -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")
}
} }

View File

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

View File

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

View File

@ -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))
} }

View File

@ -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 {

View File

@ -1,34 +1,42 @@
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)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
instanceName := state.Get("instance_name").(string) instanceName := state.Get("instance_name").(string)
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...")
} }

View File

@ -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 GetInstanceMetadata was called properly.
assert.Equal(t, d.GetInstanceMetadataZone, testZone, "Incorrect zone passed to GetInstanceMetadata.")
// Check that GetSerialPortOutput was called properly. assert.Equal(t, d.GetInstanceMetadataName, testInstanceName, "Incorrect instance name passed to GetInstanceMetadata.")
if driver.GetSerialPortOutputZone != testZone { }
t.Fatalf(
"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)
}
}

View File

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

View File

@ -46,15 +46,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("ui", ui) 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

View File

@ -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

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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"}

View File

@ -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

View File

@ -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()

View File

@ -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.

View File

@ -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"`

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -24,20 +24,12 @@ func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
log.Printf("Looking for available communicator (SSH, WinRM, etc) port between %d and %d", config.SSHHostPortMin, config.SSHHostPortMax) 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))

View File

@ -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 {

View File

@ -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)

View File

@ -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)

View File

@ -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")
}
}

View File

@ -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

View File

@ -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"}

View File

@ -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

View File

@ -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()

View File

@ -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.

View File

@ -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"`

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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",
} }

View File

@ -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

View File

@ -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()

View File

@ -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.

View File

@ -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")
}
}

View File

@ -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"))

View File

@ -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",
} }
} }

View File

@ -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.

View File

@ -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 {

View File

@ -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())
) )

View File

@ -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
} }

View File

@ -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)
}
}

154
common/multistep_runner.go Normal file
View File

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

View File

@ -8,5 +8,6 @@ type PackerConfig struct {
PackerBuilderType string `mapstructure:"packer_builder_type"` 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"`
} }

View File

@ -1,4 +1,4 @@
package googlecompute package common
import ( import (
"fmt" "fmt"
@ -25,20 +25,20 @@ 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
} }
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
} }

View File

@ -1,4 +1,4 @@
package googlecompute package common
import ( import (
"fmt" "fmt"
@ -18,7 +18,7 @@ func TestRetry(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Passing function should not have returned a retry error. Error: %s", err) t.Fatalf("Passing function should not have returned a retry error. Error: %s", err)
} }
// Test that a failing function gets retried (once in this example). // Test that a failing function gets retried (once in this example).
numTries = 0 numTries = 0
results := []bool{false, true} results := []bool{false, true}
@ -33,7 +33,7 @@ func TestRetry(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Successful retried function should not have returned a retry error. Error: %s", err) t.Fatalf("Successful retried function should not have returned a retry error. Error: %s", err)
} }
// Test that a function error gets returned, and the function does not get called again. // Test that a function error gets returned, and the function does not get called again.
numTries = 0 numTries = 0
funcErr := fmt.Errorf("This function had an error!") funcErr := fmt.Errorf("This function had an error!")
@ -47,7 +47,7 @@ func TestRetry(t *testing.T) {
if err != funcErr { if err != funcErr {
t.Fatalf("Errant function did not return the right error %s. Error: %s", funcErr, err) t.Fatalf("Errant function did not return the right error %s. Error: %s", funcErr, err)
} }
// Test when a function exhausts its retries. // Test when a function exhausts its retries.
numTries = 0 numTries = 0
expectedTries := uint(3) expectedTries := uint(3)
@ -61,4 +61,4 @@ func TestRetry(t *testing.T) {
if err != RetryExhaustedError { if err != RetryExhaustedError {
t.Fatalf("Unsuccessful retry function should have returned a retry exhausted error. Actual error: %s", err) t.Fatalf("Unsuccessful retry function should have returned a retry exhausted error. Actual error: %s", err)
} }
} }

View File

@ -0,0 +1 @@
Echo I am a floppy with a batch file

View File

@ -0,0 +1 @@
Write-Host "I am a floppy with some Powershell"

View File

@ -13,4 +13,3 @@ func TestCommIsCommunicator(t *testing.T) {
t.Fatalf("comm must be a communicator") t.Fatalf("comm must be a communicator")
} }
} }

View File

@ -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))

View File

@ -0,0 +1,44 @@
{
"variables": {
"client_id": "{{env `ARM_CLIENT_ID`}}",
"client_secret": "{{env `ARM_CLIENT_SECRET`}}",
"resource_group": "{{env `ARM_RESOURCE_GROUP`}}",
"storage_account": "{{env `ARM_STORAGE_ACCOUNT`}}",
"subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}"
},
"builders": [
{
"type": "azure-arm",
"client_id": "{{user `client_id`}}",
"client_secret": "{{user `client_secret`}}",
"resource_group_name": "{{user `resource_group`}}",
"storage_account": "{{user `storage_account`}}",
"subscription_id": "{{user `subscription_id`}}",
"capture_container_name": "images",
"capture_name_prefix": "packer",
"os_type": "Linux",
"image_url": "https://my-storage-account.blob.core.windows.net/path/to/your/custom/image.vhd",
"azure_tags": {
"dept": "engineering",
"task": "image deployment"
},
"location": "West US",
"vm_size": "Standard_A2"
}
],
"provisioners": [{
"execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'",
"inline": [
"apt-get update",
"apt-get upgrade -y",
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
],
"inline_shebang": "/bin/sh -x",
"type": "shell"
}]
}

View File

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

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

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

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