Compare commits

..

7 Commits

Author SHA1 Message Date
Matthew Hooker 9e8a3fa3ea bump gem deps and www version 2016-09-29 20:21:23 -07:00
Matthew Hooker fb1c968d57 Cut version 0.10.2 2016-09-20 16:42:06 -07:00
Matthew Hooker 141440147f Cut version 0.10.2 2016-09-20 16:38:01 -07:00
Matthew Hooker bb92eef95e Cut version 0.10.2 2016-09-20 16:34:31 -07:00
Sean Chittenden 489c75a363 Go's -X linker flag now requires only one argument (#3540)
This fixes building `packer` with Go >1.6.  From https://golang.org/cmd/link/:

```
-X importpath.name=value
	Set the value of the string variable in importpath named name to value.
	Note that before Go 1.5 this option took two separate arguments.
	Now it takes one argument split on the first = sign.
```
2016-09-20 16:34:30 -07:00
Chris Bednarski 7bbc29d4b2 Fix go vet casing issue 2016-09-20 16:28:55 -07:00
Chris Bednarski 8e7bb5ec23 Change version.go to 0.10.1 release 2016-09-20 16:28:55 -07:00
1123 changed files with 218756 additions and 53625 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ FOR BUGS:
Describe the problem and include the following information:
- Packer version from `packer version`
- Packer Version
- Host platform
- Debug log output from `PACKER_LOG=1 packer build template.json`.
Please paste this in a gist https://gist.github.com
-4
View File
@@ -9,12 +9,8 @@
.idea
test/.env
*~
*.received.*
website/.bundle
website/vendor
packer-test*.log
*.received.txt
*.received.json
+197 -375
View File
@@ -1,186 +1,4 @@
## 0.11.0 (October 21, 2016)
BACKWARDS INCOMPATIBILITIES:
* 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
builder docs for more info.
* Docker builder requires Docker > 1.3
* provisioner/chef-solo: default staging directory renamed to
`packer-chef-solo`. [GH-3971]
FEATURES:
* **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]
* **New Google Compute Export post-processor**: exports an image from
a Packer googlecompute builder run and uploads it to Google Cloud
Storage. [GH-3760]
* **New Manifest post-processor**: writes metadata about packer's output
artifacts data to a JSON file. [GH-3651]
IMPROVEMENTS:
* builder/amazon: Added `disable_stop_instance` option to prevent automatic
shutdown when the build is complete. [GH-3352]
* builder/amazon: Added `shutdown_behavior` option to support `stop` or
`terminate` at the end of the build. [GH-3556]
* builder/amazon: Added `skip_region_validation` option to allow newer or
custom AWS regions. [GH-3598]
* builder/amazon: Added `us-east-2` and `ap-south-1` regions. [GH-4021]
[GH-3663]
* builder/amazon: Support building from scratch with amazon-chroot builder.
[GH-3855] [GH-3895]
* builder/amazon: Support create an AMI with an `encrypted_boot` volume.
[GH-3382]
* builder/azure: Add `os_disk_size_gb`. [GH-3995]
* builder/azure: Add location to setup script. [GH-3803]
* builder/azure: Allow user to set custom data. [GH-3996]
* builder/azure: Made `tenant_id` optional. [GH-3643]
* builder/azure: Now pre-validates `capture_container_name` and
`capture_name_prefix` [GH-3537]
* builder/azure: Removed superfluous polling code for deployments. [GH-3638]
* builder/azure: Support for a user defined VNET. [GH-3683]
* builder/azure: Support for custom images. [GH-3575]
* builder/azure: tag all resources. [GH-3764]
* builder/digitalocean: Added `user_data_file` support. [GH-3933]
* builder/digitalocean: Fixes timeout waiting for snapshot. [GH-3868]
* builder/digitalocean: Use `state_timeout` for unlock and off transitions.
[GH-3444]
* builder/docker: Improved support for Docker pull from Amazon ECR. [GH-3856]
* builder/google: Add `-force` option to delete old image before creating new
one. [GH-3918]
* builder/google: Add image license metadata. [GH-3873]
* builder/google: Added support for `image_family` [GH-3531]
* builder/google: Added support for startup scripts. [GH-3639]
* builder/google: Create passwords for Windows instances. [GH-3932]
* builder/google: Enable to select NVMe images. [GH-3338]
* builder/google: Signal that startup script fished via metadata. [GH-3873]
* builder/google: Use gcloud application default credentials. [GH-3655]
* builder/google: provision VM without external IP address. [GH-3774]
* builder/null: Can now be used with WinRM. [GH-2525]
* builder/openstack: Added support for `ssh_password` instead of generating
ssh keys. [GH-3976]
* builder/parallels: Add support for ctrl, shift and alt keys in
`boot_command`. [GH-3767]
* builder/parallels: Copy directories recursively with `floppy_dirs`.
[GH-2919]
* builder/parallels: Now pauses between `boot_command` entries when running
with `-debug` [GH-3547]
* builder/parallels: Support future versions of Parallels by using the latest
driver. [GH-3673]
* builder/qemu: Add support for ctrl, shift and alt keys in `boot_command`.
[GH-3767]
* builder/qemu: Added `vnc_bind_address` option. [GH-3574]
* builder/qemu: Copy directories recursively with `floppy_dirs`. [GH-2919]
* builder/qemu: Now pauses between `boot_command` entries when running with
`-debug` [GH-3547]
* builder/qemu: Specify disk format when starting qemu. [GH-3888]
* builder/virtualbox-iso: Added `hard_drive_nonrotational` and
`hard_drive_discard` options to enable trim/discard. [GH-4013]
* builder/virtualbox-iso: Added `keep_registed` option to skip cleaning up
the image. [GH-3954]
* builder/virtualbox: Add support for ctrl, shift and alt keys in
`boot_command`. [GH-3767]
* builder/virtualbox: Added `post_shutdown_delay` option to wait after
shutting down to prevent issues removing floppy drive. [GH-3952]
* builder/virtualbox: Added `vrdp_bind_address` option. [GH-3566]
* builder/virtualbox: Copy directories recursively with `floppy_dirs`.
[GH-2919]
* builder/virtualbox: Now pauses between `boot_command` entries when running
with `-debug` [GH-3542]
* builder/vmware-vmx: Added `tools_upload_flavor` and `tools_upload_path` to
docs.
* builder/vmware: Add support for ctrl, shift and alt keys in `boot_command`.
[GH-3767]
* builder/vmware: Added `vnc_bind_address` option. [GH-3565]
* builder/vmware: Adds passwords for VNC. [GH-2325]
* builder/vmware: Copy directories recursively with `floppy_dirs`. [GH-2919]
* builder/vmware: Handle connection to VM with more than one NIC on ESXi
[GH-3347]
* builder/vmware: Now paused between `boot_command` entries when running with
`-debug` [GH-3542]
* core: Supress plugin discovery from plugins. [GH-4002]
* core: Test floppy disk files actually exist. [GH-3756]
* core: setting `PACKER_LOG=0` now disables logging. [GH-3964]
* post-processor/amazon-import: Support `ami_name` for naming imported AMI.
[GH-3941]
* post-processor/compress: Added support for bgzf compression. [GH-3501]
* post-processor/docker: Improved support for Docker push to Amazon ECR.
[GH-3856]
* post-processor/docker: Preserve tags when running docker push. [GH-3631]
* post-processor/vagrant: Added vsphere-esx hosts to supported machine types.
[GH-3967]
* provisioner/ansible-local: Support for ansible-galaxy. [GH-3350] [GH-3836]
* provisioner/ansible: Improved logging and error handling. [GH-3477]
* provisioner/ansible: Support scp. [GH-3861]
* provisioner/chef: Added `knife_command` option and added a correct default
value for Windows. [GH-3622]
* provisioner/chef: Installs 64bit chef on Windows if available. [GH-3848]
* provisioner/file: Now makes destination directory. [GH-3692]
* provisioner/puppet: Added `execute_command` option. [GH-3614]
* provisioner/salt: Added `custom_state` to specify state to run instead of
`highstate`. [GH-3776]
* provisioner/shell: Added `expect_disconnect` flag to fail if remote
unexpectedly disconnects. [GH-4034]
* scripts: Added `help` target to Makefile. [GH-3290]
* vendor: Moving from Godep to govendor. See `CONTRIBUTING.md` for details.
[GH-3956]
* website: code examples now use inconsolata. Improve code font rendering on
linux.
BUG FIXES:
* builder/amazon: Add 0.5 cents to discovered spot price. [GH-3662]
* builder/amazon: Allow using `ssh_private_key_file` and `ssh_password`.
[GH-3953]
* builder/amazon: Fix packer crash when waiting for SSH. [GH-3865]
* builder/amazon: Honor ssh_private_ip flag in EC2-Classic. [GH-3752]
* builder/amazon: Properly clean up EBS volumes on failure. [GH-3789]
* builder/amazon: Use `temporary_key_pair_name` when specified. [GH-3739]
* builder/amazon: retry creating tags on images since the images might take
some time to become available. [GH-3938]
* builder/azure: Fix authorization setup script failing to creating service
principal. [GH-3812]
* builder/azure: check for empty resource group. [GH-3606]
* builder/azure: fix token validity test. [GH-3609]
* builder/docker: Fix file provisioner dotfile matching. [GH-3800]
* builder/docker: fix docker builder with ansible provisioner. [GH-3476]
* builder/qemu: Don't fail on communicator set to `none`. [GH-3681]
* builder/qemu: Make `ssh_host_port_max` an inclusive bound. [GH-2784]
* builder/virtualbox: Make `ssh_host_port_max` an inclusive bound. [GH-2784]
* builder/virtualbox: Respect `ssh_host` [GH-3617]
* builder/vmware: Do not add remotedisplay.vnc.ip to VMX data on ESXi
[GH-3740]
* builder/vmware: Don't check for poweron errors on ESXi. [GH-3195]
* builder/vmware: Re-introduce case sensitive VMX keys. [GH-2707]
* builder/vmware: Respect `ssh_host`/`winrm_host` on ESXi. [GH-3738]
* command/push: Allows dot (`.`) in image names. [GH-3937]
* common/iso_config: fix potential panic when iso checksum url was given but
not the iso url. [GH-4004]
* communicator/ssh: fixed possible panic when reconnecting fails. [GH-4008]
* communicator/ssh: handle error case where server closes the connection but
doesn't give us an error code. [GH-3966]
* 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]
* provisioner/file: Fix directory download. [GH-3899]
* provisioner/powershell: fixed issue with setting environment variables.
[GH-2785]
* website: improved rendering on iPad. [GH-3780]
## 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)
## (Unreleased)
FEATURES:
@@ -191,24 +9,28 @@ IMPROVEMENTS:
* core: Added support for `ATLAS_CAFILE` and `ATLAS_CAPATH` [GH-3494]
* builder/azure: Improved build cancellation and cleanup of partially-
provisioned resources. [GH-3461]
* builder/azure: Improved logging. [GH-3461]
* builder/azure: Added support for US Government and China clouds. [GH-3461]
* builder/azure: Users may now specify an image version. [GH-3461]
* builder/azure: Added device login. [GH-3461]
* builder/docker: Added `privileged` build option. [GH-3475]
* builder/google: Packer now identifies its version to the service. [GH-3465]
provisioned resources [GH-3461]
* builder/azure: Improved logging [GH-3461]
* builder/azure: Added support for US Government and China clouds [GH-3461]
* builder/azure: Users may now specify an image version [GH-3461]
* builder/azure: Added device login [GH-3461]
* builder/docker: Added `privileged` build option [GH-3475]
* builder/google: Packer now identifies its version to the service [GH-3465]
* provisioner/shell: Added `remote_folder` and `remote_file` options
[GH-3462]
* post-processor/compress: Added support for `bgzf` format and added
`format` option. [GH-3501]
BUG FIXES:
* core: Fix hang after pressing enter key in `-debug` mode. [GH-3346]
* core: Fix hang after pressing enter key in `-debug` mode [GH-3346]
* provisioner/chef: Use custom values for remote validation key path
[GH-3468]
## 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.0 (March 14, 2016)
BACKWARDS INCOMPATIBILITIES:
@@ -226,12 +48,12 @@ IMPROVEMENTS:
* builder/google: Added support for `disk_type` [GH-2830]
* builder/openstack: Added support for retrieving the Administrator password
when using WinRM if no `winrm_password` is set. [GH-3209]
* provisioner/ansible: Added the `empty_groups` parameter. [GH-3232]
* provisioner/ansible: Added the `user` parameter. [GH-3276]
when using WinRM if no `winrm_password` is set [GH-3209]
* provisioner/ansible: Added the `empty_groups` parameter [GH-3232]
* provisioner/ansible: Added the `user` parameter [GH-3276]
* provisioner/ansible: Don't use deprecated ssh option with Ansible 2.0
[GH-3291]
* provisioner/puppet-masterless: Add `ignore_exit_codes` parameter. [GH-3349]
* provisioner/puppet-masterless: Add `ignore_exit_codes` parameter [GH-3349]
BUG FIXES:
@@ -251,25 +73,25 @@ BACKWARDS INCOMPATIBILITIES:
* Release binaries are now provided via <https://releases.hashicorp.com>.
* Packer 0.9.0 is now built with Go 1.6.
* core: Plugins that implement the Communicator interface must now implement
a DownloadDir method. [GH-2618]
a DownloadDir method [GH-2618]
* builder/amazon: Inline `user_data` for EC2 is now base64 encoded
automatically. [GH-2539]
automatically [GH-2539]
* builder/parallels: `parallels_tools_host_path` and `guest_os_distribution`
have been replaced by `guest_os_type`; use `packer fix` to update your
templates. [GH-2751]
templates [GH-2751]
FEATURES:
* **Chef on Windows**: The chef provisioner now has native support for
Windows using Powershell and WinRM. [GH-1215]
Windows using Powershell and WinRM [GH-1215]
* **New `vmware-esxi` feature**: Packer can now export images from vCloud or
vSphere during the build. [GH-1921]
vSphere during the build [GH-1921]
* **New Ansible Provisioner**: `ansible` provisioner supports remote
provisioning to keep your build image cleaner. [GH-1969]
* **New Amazon Import post-processor**: `amazon-import` allows you to upload an OVA-based VM to Amazon EC2. [GH-2962]
provisioning to keep your build image cleaner [GH-1969]
* **New Amazon Import post-processor**: `amazon-import` allows you to upload an OVA-based VM to Amazon EC2 [GH-2962]
* **Shell Local post-processor**: `shell-local` allows you to run shell
commands on the host after a build has completed for custom packaging or
publishing of your artifacts. [GH-2706]
publishing of your artifacts [GH-2706]
* **Artifice post-processor**: Override packer artifacts during post-
processing. This allows you to extract artifacts from a packer builder and
use them with other post-processors like compress, docker, and Atlas.
@@ -280,69 +102,69 @@ IMPROVEMENTS:
size and build times, and making packer easier to install. The overall
plugin architecture has not changed and third-party plugins can still be
loaded from disk. Please make sure your plugins are up-to-date! [GH-2854]
* core: Packer now indicates line numbers for template parse errors. [GH-2742]
* core: Packer now indicates line numbers for template parse errors [GH-2742]
* core: Scripts are executed via `/usr/bin/env bash` instead of `/bin/bash`
for broader compatibility. [GH-2913]
* core: `target_path` for builder downloads can now be specified. [GH-2600]
* core: WinRM communicator now supports HTTPS protocol. [GH-3061]
* core: Template syntax errors now show line, column, offset. [GH-3180]
* core: SSH communicator now supports downloading directories. [GH-2618]
* core: WinRM communicator now supports HTTPS protocol [GH-3061]
* core: Template syntax errors now show line, column, offset [GH-3180]
* core: SSH communicator now supports downloading directories [GH-2618]
* builder/amazon: Add support for `ebs_optimized` [GH-2806]
* builder/amazon: You can now specify `0` for `spot_price` to switch to on
demand instances. [GH-2845]
demand instances [GH-2845]
* builder/amazon: Added `ap-northeast-2` (Seoul) [GH-3056]
* builder/amazon: packer will try to derive the AZ if only a subnet is
specified. [GH-3037]
specified [GH-3037]
* builder/digitalocean: doubled instance wait timeouts to power off or
shutdown (now 4 minutes) and to complete a snapshot (now 20 minutes)
[GH-2939]
* builder/google: `account_file` can now be provided as a JSON string
[GH-2811]
* builder/google: added support for `preemptible` instances. [GH-2982]
* builder/google: added support for `preemptible` instances [GH-2982]
* builder/google: added support for static external IPs via `address` option
[GH-3030]
* builder/openstack: added retry on WaitForImage 404. [GH-3009]
* builder/openstack: added retry on WaitForImage 404 [GH-3009]
* builder/openstack: Can specify `source_image_name` instead of the ID
[GH-2577]
* builder/openstack: added support for SSH over IPv6. [GH-3197]
* builder/parallels: Improve support for Parallels 11. [GH-2662]
* builder/parallels: Parallels disks are now compacted by default. [GH-2731]
* builder/openstack: added support for SSH over IPv6 [GH-3197]
* builder/parallels: Improve support for Parallels 11 [GH-2662]
* builder/parallels: Parallels disks are now compacted by default [GH-2731]
* builder/parallels: Packer will look for Parallels in
`/Applications/Parallels Desktop.app` if it is not detected automatically
[GH-2839]
* builder/qemu: qcow2 images are now compacted by default. [GH-2748]
* builder/qemu: qcow2 images can now be compressed. [GH-2748]
* builder/qemu: Now specifies `virtio-scsi` by default. [GH-2422]
* builder/qemu: Now checks for version-specific options. [GH-2376]
* builder/qemu: qcow2 images are now compacted by default [GH-2748]
* builder/qemu: qcow2 images can now be compressed [GH-2748]
* builder/qemu: Now specifies `virtio-scsi` by default [GH-2422]
* builder/qemu: Now checks for version-specific options [GH-2376]
* builder/qemu: Can now bypass disk cache using `iso_skip_cache` [GH-3105]
* builder/qemu: `<wait>` in `boot_command` now accepts an arbitrary duration
like <wait1m30s> [GH-3129]
* builder/qemu: Expose `{{ .SSHHostPort }}` in templates. [GH-2884]
* builder/virtualbox: Added VRDP for debugging. [GH-3188]
* builder/qemu: Expose `{{ .SSHHostPort }}` in templates [GH-2884]
* builder/virtualbox: Added VRDP for debugging [GH-3188]
* builder/vmware-esxi: Added private key auth for remote builds via
`remote_private_key_file` [GH-2912]
* post-processor/atlas: Added support for compile ID. [GH-2775]
* post-processor/docker-import: Can now import Artifice artifacts. [GH-2718]
* provisioner/chef: Added `encrypted_data_bag_secret_path` option. [GH-2653]
* provisioner/puppet: Added the `extra_arguments` parameter. [GH-2635]
* post-processor/docker-import: Can now import Artifice artifacts [GH-2718]
* provisioner/chef: Added `encrypted_data_bag_secret_path` option [GH-2653]
* provisioner/puppet: Added the `extra_arguments` parameter [GH-2635]
* provisioner/salt: Added `no_exit_on_failure`, `log_level`, and improvements
to salt command invocation. [GH-2660]
to salt command invocation [GH-2660]
BUG FIXES:
* core: Random number generator is now seeded. [GH-2640]
* core: Packer should now have a lot less race conditions. [GH-2824]
* builder/amazon: The `no_device` option for block device mappings is now handled correctly. [GH-2398]
* builder/amazon: AMI name validation now matches Amazon's spec. [GH-2774]
* builder/amazon: Use snapshot size when volume size is unspecified. [GH-2480]
* core: Packer should now have a lot less race conditions [GH-2824]
* builder/amazon: The `no_device` option for block device mappings is now handled correctly [GH-2398]
* builder/amazon: AMI name validation now matches Amazon's spec [GH-2774]
* builder/amazon: Use snapshot size when volume size is unspecified [GH-2480]
* builder/amazon: Pass AccessKey and SecretKey when uploading bundles for
instance-backed AMIs. [GH-2596]
instance-backed AMIs [GH-2596]
* builder/parallels: Added interpolation in `prlctl_post` [GH-2828]
* builder/vmware: `format` option is now read correctly. [GH-2892]
* builder/vmware: `format` option is now read correctly [GH-2892]
* builder/vmware-esxi: Correct endless loop in destroy validation logic
[GH-2911]
* provisioner/shell: No longer leaves temp scripts behind. [GH-1536]
* provisioner/winrm: Now waits for reboot to complete before continuing with provisioning. [GH-2568]
* provisioner/shell: No longer leaves temp scripts behind [GH-1536]
* provisioner/winrm: Now waits for reboot to complete before continuing with provisioning [GH-2568]
* post-processor/artifice: Fix truncation of files downloaded from Docker. [GH-2793]
@@ -355,7 +177,7 @@ IMPROVEMENTS:
* builder/docker: Now verifies that the artifact will be used before the build
starts, unless the `discard` option is specified. This prevent failures
after the build completes. [GH-2626]
* post-processor/artifice: Now supports glob-like syntax for filenames. [GH-2619]
* post-processor/artifice: Now supports glob-like syntax for filenames [GH-2619]
* post-processor/vagrant: Like the compress post-processor, vagrant now uses a
parallel gzip algorithm to compress vagrant boxes. [GH-2590]
@@ -376,29 +198,29 @@ FEATURES:
IMPROVEMENTS:
* Many docs have been updated and corrected; big thanks to our contributors!
* builder/openstack: Add debug logging for IP addresses used for SSH. [GH-2513]
* builder/openstack: Add option to use existing SSH keypair. [GH-2512]
* builder/openstack: Add support for Glance metadata. [GH-2434]
* builder/openstack: Add debug logging for IP addresses used for SSH [GH-2513]
* builder/openstack: Add option to use existing SSH keypair [GH-2512]
* builder/openstack: Add support for Glance metadata [GH-2434]
* builder/qemu and builder/vmware: Packer's VNC connection no longer asks for
an exclusive connection. [GH-2522]
* provisioner/salt-masterless: Can now customize salt remote directories. [GH-2519]
an exclusive connection [GH-2522]
* provisioner/salt-masterless: Can now customize salt remote directories [GH-2519]
BUG FIXES:
* builder/amazon: Improve instance cleanup by storing id sooner. [GH-2404]
* builder/amazon: Only fetch windows password when using WinRM communicator. [GH-2538]
* builder/openstack: Support IPv6 SSH address. [GH-2450]
* builder/openstack: Track new IP address discovered during RackConnect. [GH-2514]
* builder/amazon: Improve instance cleanup by storing id sooner [GH-2404]
* builder/amazon: Only fetch windows password when using WinRM communicator [GH-2538]
* builder/openstack: Support IPv6 SSH address [GH-2450]
* builder/openstack: Track new IP address discovered during RackConnect [GH-2514]
* builder/qemu: Add 100ms delay between VNC key events. [GH-2415]
* post-processor/atlas: atlas_url configuration option works now. [GH-2478]
* post-processor/compress: Now supports interpolation in output config. [GH-2414]
* provisioner/powershell: Elevated runs now receive environment variables. [GH-2378]
* post-processor/atlas: atlas_url configuration option works now [GH-2478]
* post-processor/compress: Now supports interpolation in output config [GH-2414]
* provisioner/powershell: Elevated runs now receive environment variables [GH-2378]
* provisioner/salt-masterless: Clarify error messages when we can't create or
write to the temp directory. [GH-2518]
* provisioner/salt-masterless: Copy state even if /srv/salt exists already. [GH-1699]
* provisioner/salt-masterless: Make sure /etc/salt exists before writing to it. [GH-2520]
write to the temp directory [GH-2518]
* provisioner/salt-masterless: Copy state even if /srv/salt exists already [GH-1699]
* provisioner/salt-masterless: Make sure /etc/salt exists before writing to it [GH-2520]
* provisioner/winrm: Connect to the correct port when using NAT with
VirtualBox / VMware. [GH-2399]
VirtualBox / VMware [GH-2399]
Note: 0.8.3 was pulled and 0.8.4 was skipped.
@@ -406,49 +228,49 @@ Note: 0.8.3 was pulled and 0.8.4 was skipped.
IMPROVEMENTS:
* builder/docker: Add option to use a Pty. [GH-2425]
* builder/docker: Add option to use a Pty [GH-2425]
BUG FIXES:
* core: Fix crash when `min_packer_version` is specified in a template. [GH-2385]
* builder/amazon: Fix EC2 devices being included in EBS mappings. [GH-2459]
* builder/googlecompute: Fix default name for GCE images. [GH-2400]
* builder/null: Fix error message with missing ssh_host. [GH-2407]
* builder/virtualbox: Use --portcount on VirtualBox 5.x. [GH-2438]
* provisioner/puppet: Packer now correctly handles a directory for manifest_file. [GH-2463]
* provisioner/winrm: Fix potential crash with WinRM. [GH-2416]
* builder/amazon: Fix EC2 devices being included in EBS mappings [GH-2459]
* builder/googlecompute: Fix default name for GCE images [GH-2400]
* builder/null: Fix error message with missing ssh_host [GH-2407]
* builder/virtualbox: Use --portcount on VirtualBox 5.x [GH-2438]
* provisioner/puppet: Packer now correctly handles a directory for manifest_file [GH-2463]
* provisioner/winrm: Fix potential crash with WinRM [GH-2416]
## 0.8.1 (July 2, 2015)
IMPROVEMENTS:
* builder/amazon: When debug mode is enabled, the Windows administrator
password for Windows instances will be shown. [GH-2351]
password for Windows instances will be shown [GH-2351]
BUG FIXES:
* core: `min_packer_version` field in configs work. [GH-2356]
* core: The `build_name` and `build_type` functions work in provisioners. [GH-2367]
* core: Handle timeout in SSH handshake. [GH-2333]
* command/build: Fix reading configuration from stdin. [GH-2366]
* core: `min_packer_version` field in configs work [GH-2356]
* core: The `build_name` and `build_type` functions work in provisioners [GH-2367]
* core: Handle timeout in SSH handshake [GH-2333]
* command/build: Fix reading configuration from stdin [GH-2366]
* builder/amazon: Fix issue with sharing AMIs when using `ami_users` [GH-2308]
* builder/amazon: Fix issue when using multiple Security Groups. [GH-2381]
* builder/amazon: Fix for tag creation when creating new ec2 instance. [GH-2317]
* builder/amazon: Fix issue with creating AMIs with multiple device mappings. [GH-2320]
* builder/amazon: Fix issue when using multiple Security Groups [GH-2381]
* builder/amazon: Fix for tag creation when creating new ec2 instance [GH-2317]
* builder/amazon: Fix issue with creating AMIs with multiple device mappings [GH-2320]
* builder/amazon: Fix failing AMI snapshot tagging when copying to other
regions. [GH-2316]
* builder/amazon: Fix setting AMI launch permissions. [GH-2348]
* builder/amazon: Fix spot instance cleanup to remove the correct request. [GH-2327]
regions [GH-2316]
* builder/amazon: Fix setting AMI launch permissions [GH-2348]
* builder/amazon: Fix spot instance cleanup to remove the correct request [GH-2327]
* builder/amazon: Fix `bundle_prefix` not interpolating `timestamp` [GH-2352]
* builder/amazon-instance: Fix issue with creating AMIs without specifying a
virtualization type. [GH-2330]
* builder/digitalocean: Fix builder using private IP instead of public IP. [GH-2339]
* builder/google: Set default communicator settings properly. [GH-2353]
virtualization type [GH-2330]
* builder/digitalocean: Fix builder using private IP instead of public IP [GH-2339]
* builder/google: Set default communicator settings properly [GH-2353]
* builder/vmware-iso: Setting `checksum_type` to `none` for ESX builds
now works. [GH-2323]
now works [GH-2323]
* provisioner/chef: Use knife config file vs command-line params to
clean up nodes so full set of features can be used. [GH-2306]
* post-processor/compress: Fixed crash in compress post-processor plugin. [GH-2311]
clean up nodes so full set of features can be used [GH-2306]
* post-processor/compress: Fixed crash in compress post-processor plugin [GH-2311]
## 0.8.0 (June 23, 2015)
@@ -502,22 +324,22 @@ FEATURES:
IMPROVEMENTS:
* core: Interrupt handling for SIGTERM signal as well. [GH-1858]
* core: HTTP downloads support resuming. [GH-2106]
* core: HTTP downloads support resuming [GH-2106]
* builder/*: Add `ssh_handshake_attempts` to configure the number of
handshake attempts done before failure. [GH-2237]
handshake attempts done before failure [GH-2237]
* builder/amazon: Add `force_deregister` option for automatic AMI
deregistration. [GH-2221]
* builder/amazon: Now applies tags to EBS snapshots. [GH-2212]
* builder/amazon: Clean up orphaned volumes from Source AMIs. [GH-1783]
* builder/amazon: Support custom keypairs. [GH-1837]
deregistration [GH-2221]
* builder/amazon: Now applies tags to EBS snapshots [GH-2212]
* builder/amazon: Clean up orphaned volumes from Source AMIs [GH-1783]
* builder/amazon: Support custom keypairs [GH-1837]
* builder/amazon-chroot: Can now resize the root volume of the resulting
AMI with the `root_volume_size` option. [GH-2289]
AMI with the `root_volume_size` option [GH-2289]
* builder/amazon-chroot: Add `mount_options` configuration option for providing
options to the `mount` command. [GH-2296]
options to the `mount` command [GH-2296]
* builder/digitalocean: Save SSH key to pwd if debug mode is on. [GH-1829]
* builder/digitalocean: User data support. [GH-2113]
* builder/digitalocean: User data support [GH-2113]
* builder/googlecompute: Option to use internal IP for connections. [GH-2152]
* builder/parallels: Support Parallels Desktop 11. [GH-2199]
* builder/parallels: Support Parallels Desktop 11 [GH-2199]
* builder/openstack: Add `rackconnect_wait` for Rackspace customers to wait for
RackConnect data to appear
* buidler/openstack: Add `ssh_interface` option for rackconnect for users that
@@ -526,61 +348,61 @@ IMPROVEMENTS:
* builder/openstack: Add `availability_zone` [GH-2016]
* builder/openstack: Machine will be stopped prior to imaging if the
cluster supports the `startstop` extension. [GH-2223]
* builder/openstack: Support for user data. [GH-2224]
* builder/qemu: Default accelerator to "tcg" on Windows. [GH-2291]
* builder/openstack: Support for user data [GH-2224]
* builder/qemu: Default accelerator to "tcg" on Windows [GH-2291]
* builder/virtualbox: Added option: `ssh_skip_nat_mapping` to skip the
automatic port forward for SSH and to use the guest port directly. [GH-1078]
* builder/virtualbox: Added SCSI support
* builder/vmware: Support for additional disks. [GH-1382]
* builder/vmware: Can now customize the template used for adding disks. [GH-2254]
* command/fix: After fixing, the template is validated. [GH-2228]
* command/push: Add `-name` flag for specifying name from CLI. [GH-2042]
* command/push: Push configuration in templates supports variables. [GH-1861]
* post-processor/docker-save: Can be chained. [GH-2179]
* post-processor/docker-tag: Support `force` option. [GH-2055]
* post-processor/docker-tag: Can be chained. [GH-2179]
* builder/vmware: Support for additional disks [GH-1382]
* builder/vmware: Can now customize the template used for adding disks [GH-2254]
* command/fix: After fixing, the template is validated [GH-2228]
* command/push: Add `-name` flag for specifying name from CLI [GH-2042]
* command/push: Push configuration in templates supports variables [GH-1861]
* post-processor/docker-save: Can be chained [GH-2179]
* post-processor/docker-tag: Support `force` option [GH-2055]
* post-processor/docker-tag: Can be chained [GH-2179]
* post-processor/vsphere: Make more fields optional, support empty
resource pools. [GH-1868]
* provisioner/puppet-masterless: `working_directory` option. [GH-1831]
* provisioner/puppet-masterless: `working_directory` option [GH-1831]
* provisioner/puppet-masterless: `packer_build_name` and
`packer_build_type` are default facts. [GH-1878]
* provisioner/puppet-server: `ignore_exit_codes` option added. [GH-2280]
* provisioner/puppet-server: `ignore_exit_codes` option added [GH-2280]
BUG FIXES:
* core: Fix potential panic for post-processor plugin exits. [GH-2098]
* core: `PACKER_CONFIG` may point to a non-existent file. [GH-2226]
* core: Fix potential panic for post-processor plugin exits [GH-2098]
* core: `PACKER_CONFIG` may point to a non-existent file [GH-2226]
* builder/amazon: Allow spaces in AMI names when using `clean_ami_name` [GH-2182]
* builder/amazon: Remove deprecated ec2-upload-bundle paramger. [GH-1931]
* builder/amazon: Use IAM Profile to upload bundle if provided. [GH-1985]
* builder/amazon: Use correct exit code after SSH authentication failed. [GH-2004]
* builder/amazon: Remove deprecated ec2-upload-bundle paramger [GH-1931]
* builder/amazon: Use IAM Profile to upload bundle if provided [GH-1985]
* builder/amazon: Use correct exit code after SSH authentication failed [GH-2004]
* builder/amazon: Retry finding created instance for eventual
consistency. [GH-2129]
* builder/amazon: If no AZ is specified, use AZ chosen automatically by
AWS for spot instance. [GH-2017]
* builder/amazon: Private key file (only available in debug mode)
is deleted on cleanup. [GH-1801]
* builder/amazon: AMI copy won't copy to the source region. [GH-2123]
* builder/amazon: Validate AMI doesn't exist with name prior to build. [GH-1774]
* builder/amazon: AMI copy won't copy to the source region [GH-2123]
* builder/amazon: Validate AMI doesn't exist with name prior to build [GH-1774]
* builder/amazon: Improved retry logic around waiting for instances. [GH-1764]
* builder/amazon: Fix issues with creating Block Devices. [GH-2195]
* builder/amazon/chroot: Retry waiting for disk attachments. [GH-2046]
* builder/amazon/chroot: Only unmount path if it is mounted. [GH-2054]
* builder/amazon/chroot: Retry waiting for disk attachments [GH-2046]
* builder/amazon/chroot: Only unmount path if it is mounted [GH-2054]
* builder/amazon/instance: Use `-i` in sudo commands so PATH is inherited. [GH-1930]
* builder/amazon/instance: Use `--region` flag for bundle upload command. [GH-1931]
* builder/digitalocean: Wait for droplet to unlock before changing state,
should lower the "pending event" errors.
* builder/digitalocean: Ignore invalid fields from the ever-changing v2 API
* builder/digitalocean: Private images can be used as a source. [GH-1792]
* builder/digitalocean: Private images can be used as a source [GH-1792]
* builder/docker: Fixed hang on prompt while copying script
* builder/docker: Use `docker exec` for newer versions of Docker for
running scripts. [GH-1993]
* builder/docker: Fix crash that could occur at certain timed ctrl-c. [GH-1838]
* builder/docker: validate that `export_path` is not a directory. [GH-2105]
* builder/google: `ssh_timeout` is respected. [GH-1781]
running scripts [GH-1993]
* builder/docker: Fix crash that could occur at certain timed ctrl-c [GH-1838]
* builder/docker: validate that `export_path` is not a directory [GH-2105]
* builder/google: `ssh_timeout` is respected [GH-1781]
* builder/openstack: `ssh_interface` can be used to specify the interface
to retrieve the SSH IP from. [GH-2220]
* builder/qemu: Add `disk_discard` option. [GH-2120]
* builder/qemu: Add `disk_discard` option [GH-2120]
* builder/qemu: Use proper SSH port, not hardcoded to 22. [GH-2236]
* builder/qemu: Find unused SSH port if SSH port is taken. [GH-2032]
* builder/virtualbox: Bind HTTP server to IPv4, which is more compatible with
@@ -596,27 +418,27 @@ BUG FIXES:
timing issues in most cases. [GH-1663]
* builder/vmware: Bind HTTP server to IPv4, which is more compatible with
OS installers. [GH-1709]
* builder/vmware: Case-insensitive match of MAC address to find IP. [GH-1989]
* builder/vmware: More robust IP parsing from ifconfig output. [GH-1999]
* builder/vmware: Nested output directories for ESXi work. [GH-2174]
* builder/vmware: Case-insensitive match of MAC address to find IP [GH-1989]
* builder/vmware: More robust IP parsing from ifconfig output [GH-1999]
* builder/vmware: Nested output directories for ESXi work [GH-2174]
* builder/vmware: Output dir is verified at runtime, not template
validation time. [GH-2233]
* command/fix: For the `virtualbox` to `virtualbox-iso` builder rename,
provisioner overrides are now also fixed. [GH-2231]
* command/validate: don't crash for invalid builds. [GH-2139]
* post-processor/atlas: Find common archive prefix for Windows. [GH-1874]
* post-processor/atlas: Fix index out of range panic. [GH-1959]
provisioner overrides are now also fixed [GH-2231]
* command/validate: don't crash for invalid builds [GH-2139]
* post-processor/atlas: Find common archive prefix for Windows [GH-1874]
* post-processor/atlas: Fix index out of range panic [GH-1959]
* post-processor/vagrant-cloud: Fixed failing on response
* post-processor/vagrant-cloud: Don't delete version on error. [GH-2014]
* post-processor/vagrant-cloud: Don't delete version on error [GH-2014]
* post-processor/vagrant-cloud: Retry failed uploads a few times
* provisioner/chef-client: Fix permissions issues on default dir. [GH-2255]
* provisioner/chef-client: Fix permissions issues on default dir [GH-2255]
* provisioner/chef-client: Node cleanup works now. [GH-2257]
* provisioner/puppet-masterless: Allow manifest_file to be a directory
* provisioner/salt-masterless: Add `--retcode-passthrough` to salt-call
* provisioner/shell: chmod executable script to 0755, not 0777. [GH-1708]
* provisioner/shell: inline commands failing will fail the provisioner. [GH-2069]
* provisioner/shell: single quotes in env vars are escaped. [GH-2229]
* provisioner/shell: Temporary file is deleted after run. [GH-2259]
* provisioner/shell: chmod executable script to 0755, not 0777 [GH-1708]
* provisioner/shell: inline commands failing will fail the provisioner [GH-2069]
* provisioner/shell: single quotes in env vars are escaped [GH-2229]
* provisioner/shell: Temporary file is deleted after run [GH-2259]
* provisioner/shell: Randomize default script name to avoid strange
race issues from Windows. [GH-2270]
@@ -662,7 +484,7 @@ BUG FIXES:
FEATURES:
* builder/digitalocean: API V2 support. [GH-1463]
* builder/parallels: Don't depend on _prl-utils_. [GH-1499]
* builder/parallels: Don't depend on _prl-utils_ [GH-1499]
IMPROVEMENTS:
@@ -690,15 +512,15 @@ BUG FIXES:
* builder/vmware: Wait some time so that post-VMX update aren't
overwritten. [GH-1504]
* builder/vmware/esxi: Retry power on if it fails. [GH-1334]
* builder/vmware-vmx: Fix issue with order of boot command support. [GH-1492]
* builder/amazon: Extend timeout and allow user override. [GH-1533]
* builder/vmware-vmx: Fix issue with order of boot command support [GH-1492]
* builder/amazon: Extend timeout and allow user override [GH-1533]
* builder/parallels: Ignore 'The fdd0 device does not exist' [GH-1501]
* builder/parallels: Rely on Cleanup functions to detach devices. [GH-1502]
* builder/parallels: Create VM without hdd and then add it later. [GH-1548]
* builder/parallels: Disconnect cdrom0. [GH-1605]
* builder/parallels: Rely on Cleanup functions to detach devices [GH-1502]
* builder/parallels: Create VM without hdd and then add it later [GH-1548]
* builder/parallels: Disconnect cdrom0 [GH-1605]
* builder/qemu: Don't use `-redir` flag anymore, replace with
`hostfwd` options. [GH-1561]
* builder/qemu: Use `pc` as default machine type instead of `pc-1.0`.
* builder/qmeu: Use `pc` as default machine type instead of `pc-1.0`.
* providers/aws: Ignore transient network errors. [GH-1579]
* provisioner/ansible: Don't buffer output so output streams in. [GH-1585]
* provisioner/ansible: Use inventory file always to avoid potentially
@@ -807,9 +629,9 @@ BUG FIXES:
* builder/googlecompute: Auth works with latest formats on Google Cloud
Console. [GH-1344]
* builder/openstack: Region is not required. [GH-1418]
* builder/parallels-iso: ISO not removed from VM after install. [GH-1338]
* builder/parallels/all: Add support for Parallels Desktop 10. [GH-1438]
* builder/parallels/all: Added some navigation keys. [GH-1442]
* builder/parallels-iso: ISO not removed from VM after install [GH-1338]
* builder/parallels/all: Add support for Parallels Desktop 10 [GH-1438]
* builder/parallels/all: Added some navigation keys [GH-1442]
* builder/qemu: If headless, sdl display won't be used. [GH-1395]
* builder/qemu: Use `512M` as `-m` default. [GH-1444]
* builder/virtualbox/all: Search `VBOX_MSI_INSTALL_PATH` for path to
@@ -825,7 +647,7 @@ BUG FIXES:
VM. [GH-1300]
* builder/vmware/all: `ssh_host` accepts templates. [GH-1396]
* builder/vmware/all: Don't remount floppy in VMX post step. [GH-1239]
* builder/vmware/vmx: Do not re-add floppy disk files to VMX. [GH-1361]
* builder/vmware/vmx: Do not re-add floppy disk files to VMX [GH-1361]
* builder/vmware-iso: Fix crash when `vnc_port_min` and max were the
same value. [GH-1288]
* builder/vmware-iso: Finding an available VNC port on Windows works. [GH-1372]
@@ -846,7 +668,7 @@ FEATURES:
IMPROVEMENTS:
* builder/amazon: Support for enhanced networking on HVM images. [GH-1228]
* builder/amazon-ebs: Support encrypted EBS volumes. [GH-1194]
* builder/amazon-ebs: Support encrypted EBS volumes [GH-1194]
* builder/ansible: Add `playbook_dir` option. [GH-1000]
* builder/openstack: Add ability to configure networks. [GH-1261]
* builder/openstack: Skip certificate verification. [GH-1121]
@@ -855,7 +677,7 @@ IMPROVEMENTS:
* builder/virtualbox/all: Attempt to use local guest additions ISO
before downloading from internet. [GH-1123]
* builder/virtualbox/ovf: Supports `guest_additions_mode` [GH-1035]
* builder/vmware/all: Increase cleanup timeout to 120 seconds. [GH-1167]
* builder/vmware/all: Increase cleanup timeout to 120 seconds [GH-1167]
* builder/vmware/all: Add `vmx_data_post` for modifying VMX data
after shutdown. [GH-1149]
* builder/vmware/vmx: Supports tools uploading. [GH-1154]
@@ -863,12 +685,12 @@ IMPROVEMENTS:
BUG FIXES:
* core: `isotime` is the same time during the entire build. [GH-1153]
* builder/amazon-common: Sort AMI strings before outputting. [GH-1305]
* builder/amazon-common: Sort AMI strings before outputting [GH-1305]
* builder/amazon: User data can use templates/variables. [GH-1343]
* builder/amazon: Can now build AMIs in GovCloud.
* builder/null: SSH info can use templates/variables. [GH-1343]
* builder/openstack: Workaround for gophercloud.ServerById crashing. [GH-1257]
* builder/openstack: Force IPv4 addresses from address pools. [GH-1258]
* builder/openstack: Workaround for gophercloud.ServerById crashing [GH-1257]
* builder/openstack: Force IPv4 addresses from address pools [GH-1258]
* builder/parallels: Do not delete entire CDROM device. [GH-1115]
* builder/parallels: Errors while creating floppy disk. [GH-1225]
* builder/parallels: Errors while removing floppy drive. [GH-1226]
@@ -877,8 +699,8 @@ BUG FIXES:
* builder/vmware: Remote ESXi builder now uploads floppy. [GH-1106]
* builder/vmware: Remote ESXi builder no longer re-uploads ISO every
time. [GH-1244]
* post-processor/vsphere: Accept DOMAIN\account usernames. [GH-1178]
* provisioner/chef-*: Fix remotePaths for Windows. [GH-394]
* post-processor/vsphere: Accept DOMAIN\account usernames [GH-1178]
* provisioner/chef-*: Fix remotePaths for Windows [GH-394]
## 0.6.0 (May 2, 2014)
@@ -901,8 +723,8 @@ IMPROVEMENTS:
* core: Templates array values can now be comma separated strings.
Most importantly, this allows for user variables to fill
array configurations. [GH-950]
* builder/amazon: Added `ssh_private_key_file` option. [GH-971]
* builder/amazon: Added `ami_virtualization_type` option. [GH-1021]
* builder/amazon: Added `ssh_private_key_file` option [GH-971]
* builder/amazon: Added `ami_virtualization_type` option [GH-1021]
* builder/digitalocean: Regions, image names, and sizes can be
names that are looked up for their valid ID. [GH-960]
* builder/googlecompute: Configurable instance name. [GH-1065]
@@ -917,17 +739,17 @@ IMPROVEMENTS:
* builder/virtualbox: Support an `export_opts` option which allows
specifying arbitrary arguments when exporting the VM. [GH-945]
* builder/virtualbox: Added `vboxmanage_post` option to run vboxmanage
commands just before exporting. [GH-664]
commands just before exporting [GH-664]
* builder/virtualbox: Floppy disk files list can also include globs
and directories. [GH-1086]
* builder/vmware: Workstation 10 support for Linux. [GH-900]
* builder/vmware: add cloning support on Windows. [GH-824]
* builder/vmware: add cloning support on Windows [GH-824]
* builder/vmware: Floppy disk files list can also include globs
and directories. [GH-1086]
* command/build: Added `-parallel` flag so you can disable parallelization
with `-no-parallel`. [GH-924]
* post-processors/vsphere: `disk_mode` option. [GH-778]
* provisioner/ansible: Add `inventory_file` option. [GH-1006]
* provisioner/ansible: Add `inventory_file` option [GH-1006]
* provisioner/chef-client: Add `validation_client_name` option. [GH-1056]
BUG FIXES:
@@ -937,21 +759,21 @@ BUG FIXES:
* core: Create Cache directory only when it is needed. [GH-367]
* builder/amazon-instance: Use S3Endpoint for ec2-upload-bundle arg,
which works for every region. [GH-904]
* builder/digitalocean: updated default image_id. [GH-1032]
* builder/digitalocean: updated default image_id [GH-1032]
* builder/googlecompute: Create persistent disk as boot disk via
API v1. [GH-1001]
* builder/openstack: Return proper error on invalid instance states. [GH-1018]
API v1 [GH-1001]
* builder/openstack: Return proper error on invalid instance states [GH-1018]
* builder/virtualbox-iso: Retry unregister a few times to deal with
VBoxManage randomness. [GH-915]
* provisioner/ansible: Fix paths when provisioning Linux from
Windows. [GH-963]
* provisioner/ansible: set cwd to staging directory. [GH-1016]
Windows [GH-963]
* provisioner/ansible: set cwd to staging directory [GH-1016]
* provisioners/chef-client: Don't chown directory with Ubuntu. [GH-939]
* provisioners/chef-solo: Deeply nested JSON works properly. [GH-1076]
* provisioners/shell: Env var values can have equal signs. [GH-1045]
* provisioners/shell: chmod the uploaded script file to 0777. [GH-994]
* post-processor/docker-push: Allow repositories with ports. [GH-923]
* post-processor/vagrant: Create parent directories for `output` path. [GH-1059]
* post-processor/vagrant: Create parent directories for `output` path [GH-1059]
* post-processor/vsphere: datastore, network, and folder are no longer
required. [GH-1091]
@@ -996,7 +818,7 @@ BUG FIXES:
BUG FIXES:
* core: If a stream ID loops around, don't let it use stream ID 0. [GH-767]
* core: If a stream ID loops around, don't let it use stream ID 0 [GH-767]
* core: Fix issue where large writes to plugins would result in stream
corruption. [GH-727]
* builders/virtualbox-ovf: `shutdown_timeout` config works. [GH-772]
@@ -1110,7 +932,7 @@ BUG FIXES:
* builder/amazon/chroot: Block when obtaining file lock to allow
parallel builds. [GH-689]
* builder/amazon/instance: Add location flag to upload bundle command
so that building AMIs works out of us-east-1. [GH-679]
so that building AMIs works out of us-east-1 [GH-679]
* builder/qemu: Qemu arguments are templated. [GH-688]
* builder/vmware: Cleanup of VMX keys works properly so cd-rom won't
get stuck with ISO. [GH-685]
@@ -1140,7 +962,7 @@ IMPROVEMENTS:
* builder/virtualbox: Use VBOX\_INSTALL\_PATH env var on Windows to find
VBoxManage. [GH-628]
* post-processor/vagrant: skips gzip compression when compression_level=0
* provisioner/chef-solo: Encrypted data bag support. [GH-625]
* provisioner/chef-solo: Encrypted data bag support [GH-625]
BUG FIXES:
@@ -1168,7 +990,7 @@ IMPROVEMENTS:
* builder/digitalocean: Can now specify a droplet name
* builder/virtualbox: Can now disable guest addition download entirely
by setting "guest_additions_mode" to "disable" [GH-580]
* builder/virtualbox,vmware: ISO urls can now be https. [GH-587]
* builder/virtualbox,vmware: ISO urls can now be https [GH-587]
* builder/virtualbox,vmware: Warning if shutdown command is not specified,
since it is a common case of data loss.
@@ -1177,7 +999,7 @@ BUG FIXES:
* core: Won't panic when writing to a bad pipe. [GH-560]
* builder/amazon/all: Properly scrub access key and secret key from logs.
[GH-554]
* builder/openstack: Properly scrub password from logs. [GH-554]
* builder/openstack: Properly scrub password from logs [GH-554]
* builder/virtualbox: No panic if SSH host port min/max is the same. [GH-594]
* builder/vmware: checks if `ifconfig` is in `/sbin` [GH-591]
* builder/vmware: Host IP lookup works for non-C locales. [GH-592]
@@ -1208,12 +1030,12 @@ BUG FIXES:
before copy, fixing issues with dangling symlinks. [GH-500]
* builder/digitalocean: don't panic if erroneous API response doesn't
contain error message. [GH-492]
* builder/digitalocean: scrub API keys from config debug output. [GH-516]
* builder/digitalocean: scrub API keys from config debug output [GH-516]
* builder/virtualbox: error if VirtualBox version cant be detected. [GH-488]
* builder/virtualbox: detect if vboxdrv isn't properly setup. [GH-488]
* builder/virtualbox: sleep a bit before export to ensure the sesssion
is unlocked. [GH-512]
* builder/virtualbox: create SATA drives properly on VirtualBox 4.3. [GH-547]
* builder/virtualbox: create SATA drives properly on VirtualBox 4.3 [GH-547]
* builder/virtualbox: support user templates in SSH key path. [GH-539]
* builder/vmware: support user templates in SSH key path. [GH-539]
* communicator/ssh: Fix issue where a panic could arise from a nil
@@ -1408,7 +1230,7 @@ BUG FIXES:
IMPROVEMENTS:
* post-processor/vagrant: the file being compressed will be shown
in the UI. [GH-314]
in the UI [GH-314]
BUG FIXES:
@@ -1434,7 +1256,7 @@ IMPROVEMENTS:
BUG FIXES:
* core: TCP connection between plugin processes will keep-alive. [GH-312]
* core: No more "unused key keep_input_artifact" for post processors. [GH-310]
* core: No more "unused key keep_input_artifact" for post processors [GH-310]
* post-processor/vagrant: `output_path` templates now work again.
## 0.3.2 (August 18, 2013)
@@ -1462,7 +1284,7 @@ IMPROVEMENTS:
* builder/vmware: Do not check for VMware as part of template validation;
only check at execution.
* command/build: A path of "-" will read the template from stdin.
* builder/amazon: add block device mappings. [GH-90]
* builder/amazon: add block device mappings [GH-90]
BUG FIXES:
@@ -1544,7 +1366,7 @@ BUG FIXES:
IMPROVEMENTS:
* builder/amazon/all: Added Amazon AMI tag support. [GH-233]
* builder/amazon/all: Added Amazon AMI tag support [GH-233]
BUG FIXES:
@@ -1589,7 +1411,7 @@ FEATURES:
IMPROVEMENTS:
* builder/amazon/all: Ctrl-C while waiting for state change works
* builder/amazon/ebs: Can now launch instances into a VPC for added protection. [GH-210]
* builder/amazon/ebs: Can now launch instances into a VPC for added protection [GH-210]
* builder/virtualbox,vmware: Add backspace, delete, and F1-F12 keys to the boot
command.
* builder/virtualbox: massive performance improvements with big ISO files because
@@ -1706,13 +1528,13 @@ IMPROVEMENTS:
* core: Template syntax errors now show line and character number. [GH-56]
* amazon-ebs: Access key and secret access key default to
environmental variables. [GH-40]
* virtualbox: Send password for keyboard-interactive auth. [GH-121]
* vmware: Send password for keyboard-interactive auth. [GH-121]
* virtualbox: Send password for keyboard-interactive auth [GH-121]
* vmware: Send password for keyboard-interactive auth [GH-121]
BUG FIXES:
* vmware: Wait until shut down cleans up properly to avoid corrupt
disk files. [GH-111]
disk files [GH-111]
## 0.1.3 (July 1, 2013)
@@ -1728,8 +1550,8 @@ IMPROVEMENTS:
BUG FIXES:
* core: More plugin server fixes that avoid hangs on OS X 10.7. [GH-87]
* vagrant: AWS boxes will keep the AMI artifact around. [GH-55]
* core: More plugin server fixes that avoid hangs on OS X 10.7 [GH-87]
* vagrant: AWS boxes will keep the AMI artifact around [GH-55]
* virtualbox: More robust version parsing for uploading guest additions. [GH-69]
* virtualbox: Output dir and VM name defaults depend on build name,
avoiding collisions. [GH-91]
@@ -1749,7 +1571,7 @@ BUG FIXES:
* core: Plugin servers consider a port in use if there is any
error listening to it. This fixes I18n issues and Windows. [GH-58]
* amazon-ebs: Sleep between checking instance state to avoid
RequestLimitExceeded. [GH-50]
RequestLimitExceeded [GH-50]
* vagrant: Rename VirtualBox ovf to "box.ovf" [GH-64]
* vagrant: VMware boxes have the correct provider type.
* vmware: Properly populate files in artifact so that the Vagrant
@@ -1761,13 +1583,13 @@ BUG FIXES:
* core: plugins listen explicitly on 127.0.0.1, fixing odd hangs. [GH-37]
* core: fix race condition on verifying checksum of large ISOs which
could cause panics. [GH-52]
could cause panics [GH-52]
* virtualbox: `boot_wait` defaults to "10s" rather than 0. [GH-44]
* virtualbox: if `http_port_min` and max are the same, it will no longer
panic. [GH-53]
panic [GH-53]
* vmware: `boot_wait` defaults to "10s" rather than 0. [GH-44]
* vmware: if `http_port_min` and max are the same, it will no longer
panic. [GH-53]
panic [GH-53]
## 0.1.0 (June 28, 2013)
+12 -31
View File
@@ -69,8 +69,7 @@ following steps in order to be able to compile and test Packer. These instructio
`$GOPATH/src/github.com/mitchellh/packer`.
4. When working on packer `cd $GOPATH/src/github.com/mitchellh/packer` so you
can run `make` and easily access other files. Run `make help` to get
information about make targets.
can run `make` and easily access other files.
5. Make your changes to the Packer source. You can run `make` in
`$GOPATH/src/github.com/mitchellh/packer` to run tests and build the packer
@@ -84,34 +83,11 @@ following steps in order to be able to compile and test Packer. These instructio
7. If everything works well and the tests pass, run `go fmt` on your code
before submitting a pull-request.
### Opening an Pull Request
When you are ready to open a pull-request, you will need to [fork packer](https://github.com/mitchellh/packer#fork-destination-box), push your changes to your fork, and then open a pull-request.
For example, my github username is `cbednarski` so I would do the following:
git checkout -b f-my-feature
// develop a patch
git push https://github.com/cbednarski/packer f-my-feature
From there, open your fork in your browser to open a new pull-request.
**Note** Go infers package names from their filepaths. This means `go build` will break if you `git clone` your fork instead of using `go get` on the main packer project.
### Tips for Working on Packer
#### Govendor
#### Godeps
If you are submitting a change that requires new or updated dependencies, please include them in `vendor/vendor.json` and in the `vendor/` folder. This helps everything get tested properly in CI.
Note that you will need to use [govendor](https://github.com/kardianos/govendor) to do this. This step is recommended but not required; if you don't use govendor please indicate in your PR which dependencies have changed and to what versions.
Use `govendor fetch <project>` to add dependencies to the project. See
[govendor quick
start(https://github.com/kardianos/govendor#quick-start-also-see-the-faq) for
examples.
Please only apply the minimal vendor changes to get your PR to work. Packer does not attempt to track the latest version for each dependency.
If you are submitting a change that requires a change in dependencies, DO NOT update the `vendor/` folder. This keeps the PR smaller and easier to review. Instead, please indicate which upstream has changed and which version we should be using. You _may_ do this using `Godeps/Godeps.json` but this is not required.
#### Running Unit Tests
@@ -125,11 +101,16 @@ Packer has [acceptance tests](https://en.wikipedia.org/wiki/Acceptance_testing)
for various builders. These typically require an API key (AWS, GCE), or
additional software to be installed on your computer (VirtualBox, VMware).
If you're working on a new builder or builder feature and want verify it is functioning (and also hasn't broken anything else), we recommend running the
If you're working on a feature of a builder or a new builder and want verify it
is functioning (and also hasn't broken anything else), we recommend running the
acceptance tests.
**Warning:** The acceptance tests create/destroy/modify *real resources*, which
may incur costs for real money. In the presence of a bug, it is possible that resources may be left behind, which can cost money even though you were not using them. We recommend running tests in an account used only for that purpose so it is easy to see if there are any dangling resources, and so production resources are not accidentally destroyed or overwritten during testing.
may incur real costs in some cases. In the presence of a bug, it is technically
possible that broken backends could leave dangling data behind. Therefore,
please run the acceptance tests at your own risk. At the very least, we
recommend running them in their own private account for whatever builder you're
testing.
To run the acceptance tests, invoke `make testacc`:
@@ -142,5 +123,5 @@ down to a specific resource to test, since testing all of them at once can
sometimes take a very long time.
Acceptance tests typically require other environment variables to be set for
things such as API tokens and keys. Each test should error and tell you which
credentials are missing, so those are not documented here.
things such as access keys. The test itself should error early and tell you
what to set, so it is not documented here.
+633
View File
@@ -0,0 +1,633 @@
{
"ImportPath": "github.com/mitchellh/packer",
"GoVersion": "go1.6",
"GodepVersion": "v62",
"Deps": [
{
"ImportPath": "github.com/ActiveState/tail",
"Comment": "v0-41-g1a0242e",
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/compute",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/network",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/resources/resources",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/resources/subscriptions",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/storage",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/storage",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest",
"Comment": "v7.0.4",
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/azure",
"Comment": "v7.0.4",
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/date",
"Comment": "v7.0.4",
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/to",
"Comment": "v7.0.4",
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
},
{
"ImportPath": "github.com/Azure/go-ntlmssp",
"Rev": "e0b63eb299a769ea4b04dadfe530f6074b277afb"
},
{
"ImportPath": "github.com/armon/go-radix",
"Rev": "4239b77079c7b5d1243b7b4736304ce8ddb6f0f2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/awserr",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/awsutil",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/client",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/client/metadata",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/corehandlers",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/defaults",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/ec2metadata",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/request",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/session",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/endpoints",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/restxml",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/signer/v4",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/waiter",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/ec2",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/s3",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3iface",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3manager",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/sts",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/bgentry/speakeasy",
"Rev": "36e9cfdd690967f4f690c6edcc9ffacd006014a0"
},
{
"ImportPath": "github.com/dgrijalva/jwt-go",
"Comment": "v2.4.0-11-gf219341",
"Rev": "f2193411bd642f7db03249fd79d5292c9b34916a"
},
{
"ImportPath": "github.com/digitalocean/godo",
"Comment": "v0.9.0-24-g6ca5b77",
"Rev": "6ca5b770f203b82a0fca68d0941736458efa8a4f"
},
{
"ImportPath": "github.com/dylanmei/iso8601",
"Rev": "2075bf119b58e5576c6ed9f867b8f3d17f2e54d4"
},
{
"ImportPath": "github.com/dylanmei/winrmtest",
"Rev": "025617847eb2cf9bd1d851bc3b22ed28e6245ce5"
},
{
"ImportPath": "github.com/go-ini/ini",
"Comment": "v1.8.6",
"Rev": "afbd495e5aaea13597b5e14fe514ddeaa4d76fc3"
},
{
"ImportPath": "github.com/golang/protobuf/proto",
"Rev": "b982704f8bb716bb608144408cff30e15fbde841"
},
{
"ImportPath": "github.com/google/go-querystring/query",
"Rev": "2a60fc2ba6c19de80291203597d752e9ba58e4c0"
},
{
"ImportPath": "github.com/hashicorp/atlas-go/archive",
"Comment": "20141209094003-92-g95fa852",
"Rev": "95fa852edca41c06c4ce526af4bb7dec4eaad434"
},
{
"ImportPath": "github.com/hashicorp/atlas-go/v1",
"Comment": "20141209094003-92-g95fa852",
"Rev": "95fa852edca41c06c4ce526af4bb7dec4eaad434"
},
{
"ImportPath": "github.com/hashicorp/errwrap",
"Rev": "7554cd9344cec97297fa6649b055a8c98c2a1e55"
},
{
"ImportPath": "github.com/hashicorp/go-checkpoint",
"Rev": "e4b2dc34c0f698ee04750bf2035d8b9384233e1b"
},
{
"ImportPath": "github.com/hashicorp/go-cleanhttp",
"Rev": "875fb671b3ddc66f8e2f0acc33829c8cb989a38d"
},
{
"ImportPath": "github.com/hashicorp/go-multierror",
"Rev": "d30f09973e19c1dfcd120b2d9c4f168e68d6b5d5"
},
{
"ImportPath": "github.com/hashicorp/go-rootcerts",
"Rev": "6bb64b370b90e7ef1fa532be9e591a81c3493e00"
},
{
"ImportPath": "github.com/hashicorp/go-version",
"Rev": "7e3c02b30806fa5779d3bdfc152ce4c6f40e7b38"
},
{
"ImportPath": "github.com/hashicorp/yamux",
"Rev": "df949784da9ed028ee76df44652e42d37a09d7e4"
},
{
"ImportPath": "github.com/hpcloud/tail/ratelimiter",
"Comment": "v0-41-g1a0242e",
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
},
{
"ImportPath": "github.com/hpcloud/tail/util",
"Comment": "v0-41-g1a0242e",
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
},
{
"ImportPath": "github.com/hpcloud/tail/watch",
"Comment": "v0-41-g1a0242e",
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
},
{
"ImportPath": "github.com/hpcloud/tail/winfile",
"Comment": "v0-41-g1a0242e",
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
},
{
"ImportPath": "github.com/jmespath/go-jmespath",
"Comment": "0.2.2-2-gc01cf91",
"Rev": "c01cf91b011868172fdcd9f41838e80c9d716264"
},
{
"ImportPath": "github.com/kardianos/osext",
"Rev": "29ae4ffbc9a6fe9fb2bc5029050ce6996ea1d3bc"
},
{
"ImportPath": "github.com/klauspost/compress/flate",
"Rev": "f86d2e6d8a77c6a2c4e42a87ded21c6422f7557e"
},
{
"ImportPath": "github.com/klauspost/cpuid",
"Rev": "349c675778172472f5e8f3a3e0fe187e302e5a10"
},
{
"ImportPath": "github.com/klauspost/crc32",
"Rev": "999f3125931f6557b991b2f8472172bdfa578d38"
},
{
"ImportPath": "github.com/klauspost/pgzip",
"Rev": "47f36e165cecae5382ecf1ec28ebf7d4679e307d"
},
{
"ImportPath": "github.com/kr/fs",
"Rev": "2788f0dbd16903de03cb8186e5c7d97b69ad387b"
},
{
"ImportPath": "github.com/masterzen/simplexml/dom",
"Rev": "95ba30457eb1121fa27753627c774c7cd4e90083"
},
{
"ImportPath": "github.com/masterzen/winrm/soap",
"Rev": "54ea5d01478cfc2afccec1504bd0dfcd8c260cfa"
},
{
"ImportPath": "github.com/masterzen/winrm/winrm",
"Rev": "54ea5d01478cfc2afccec1504bd0dfcd8c260cfa"
},
{
"ImportPath": "github.com/masterzen/xmlpath",
"Rev": "13f4951698adc0fa9c1dda3e275d489a24201161"
},
{
"ImportPath": "github.com/mattn/go-isatty",
"Rev": "56b76bdf51f7708750eac80fa38b952bb9f32639"
},
{
"ImportPath": "github.com/mitchellh/cli",
"Rev": "5c87c51cedf76a1737bf5ca3979e8644871598a6"
},
{
"ImportPath": "github.com/mitchellh/go-fs",
"Rev": "a34c1b9334e86165685a9449b782f20465eb8c69"
},
{
"ImportPath": "github.com/mitchellh/go-fs/fat",
"Rev": "a34c1b9334e86165685a9449b782f20465eb8c69"
},
{
"ImportPath": "github.com/mitchellh/go-homedir",
"Rev": "d682a8f0cf139663a984ff12528da460ca963de9"
},
{
"ImportPath": "github.com/mitchellh/go-vnc",
"Rev": "723ed9867aed0f3209a81151e52ddc61681f0b01"
},
{
"ImportPath": "github.com/mitchellh/iochan",
"Rev": "87b45ffd0e9581375c491fef3d32130bb15c5bd7"
},
{
"ImportPath": "github.com/mitchellh/mapstructure",
"Rev": "281073eb9eb092240d33ef253c404f1cca550309"
},
{
"ImportPath": "github.com/mitchellh/multistep",
"Rev": "162146fc57112954184d90266f4733e900ed05a5"
},
{
"ImportPath": "github.com/mitchellh/panicwrap",
"Rev": "a1e50bc201f387747a45ffff020f1af2d8759e88"
},
{
"ImportPath": "github.com/mitchellh/prefixedio",
"Rev": "6e6954073784f7ee67b28f2d22749d6479151ed7"
},
{
"ImportPath": "github.com/mitchellh/reflectwalk",
"Rev": "eecf4c70c626c7cfbb95c90195bc34d386c74ac6"
},
{
"ImportPath": "github.com/nu7hatch/gouuid",
"Rev": "179d4d0c4d8d407a32af483c2354df1d2c91e6c3"
},
{
"ImportPath": "github.com/packer-community/winrmcp/winrmcp",
"Rev": "f1bcf36a69fa2945e65dd099eee11b560fbd3346"
},
{
"ImportPath": "github.com/pierrec/lz4",
"Rev": "383c0d87b5dd7c090d3cddefe6ff0c2ffbb88470"
},
{
"ImportPath": "github.com/pierrec/xxHash/xxHash32",
"Rev": "5a004441f897722c627870a981d02b29924215fa"
},
{
"ImportPath": "github.com/pkg/sftp",
"Rev": "e84cc8c755ca39b7b64f510fe1fffc1b51f210a5"
},
{
"ImportPath": "github.com/rackspace/gophercloud",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/common/extensions",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/flavors",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/images",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/servers",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tenants",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tokens",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v3/tokens",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/utils",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/pagination",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/testhelper",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/testhelper/client",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/satori/go.uuid",
"Rev": "d41af8bb6a7704f00bc3b7cba9355ae6a5a80048"
},
{
"ImportPath": "github.com/tent/http-link-go",
"Rev": "ac974c61c2f990f4115b119354b5e0b47550e888"
},
{
"ImportPath": "github.com/ugorji/go/codec",
"Rev": "646ae4a518c1c3be0739df898118d9bccf993858"
},
{
"ImportPath": "golang.org/x/crypto/curve25519",
"Rev": "1f22c0103821b9390939b6776727195525381532"
},
{
"ImportPath": "golang.org/x/crypto/md4",
"Rev": "1f22c0103821b9390939b6776727195525381532"
},
{
"ImportPath": "golang.org/x/crypto/ssh",
"Rev": "1f22c0103821b9390939b6776727195525381532"
},
{
"ImportPath": "golang.org/x/crypto/ssh/agent",
"Rev": "1f22c0103821b9390939b6776727195525381532"
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
},
{
"ImportPath": "golang.org/x/net/context/ctxhttp",
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
},
{
"ImportPath": "golang.org/x/net/html",
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
},
{
"ImportPath": "golang.org/x/net/html/atom",
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
},
{
"ImportPath": "golang.org/x/oauth2",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/oauth2/google",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/oauth2/internal",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/oauth2/jws",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/oauth2/jwt",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/sys/unix",
"Rev": "50c6bc5e4292a1d4e65c6e9be5f53be28bcbe28e"
},
{
"ImportPath": "google.golang.org/api/compute/v1",
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
},
{
"ImportPath": "google.golang.org/api/gensupport",
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
},
{
"ImportPath": "google.golang.org/api/googleapi",
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
},
{
"ImportPath": "google.golang.org/api/googleapi/internal/uritemplates",
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
},
{
"ImportPath": "google.golang.org/appengine",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/app_identity",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/base",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/datastore",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/log",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/modules",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/remote_api",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/cloud/compute/metadata",
"Rev": "5a3b06f8b5da3b7c3a93da43163b872c86c509ef"
},
{
"ImportPath": "google.golang.org/cloud/internal",
"Rev": "5a3b06f8b5da3b7c3a93da43163b872c86c509ef"
},
{
"ImportPath": "gopkg.in/fsnotify.v1",
"Comment": "v1.2.9",
"Rev": "8611c35ab31c1c28aa903d33cf8b6e44a399b09e"
},
{
"ImportPath": "gopkg.in/tomb.v1",
"Rev": "dd632973f1e7218eb1089048e0798ec9ae7dceb8"
},
{
"ImportPath": "gopkg.in/xmlpath.v2",
"Rev": "860cbeca3ebcc600db0b213c0e83ad6ce91f5739"
}
]
}
+20 -14
View File
@@ -9,9 +9,9 @@ default: deps generate test dev
ci: deps test
release: deps test releasebin package ## Build a release build
release: deps test releasebin package
bin: deps ## Build debug/test build
bin: deps
@echo "WARN: 'make bin' is for debug / test builds only. Use 'make release' for release builds."
@GO15VENDOREXPERIMENT=1 sh -c "$(CURDIR)/scripts/build.sh"
@@ -29,17 +29,20 @@ package:
deps:
go get github.com/mitchellh/gox
go get golang.org/x/tools/cmd/stringer
go get github.com/kardianos/govendor
govendor sync
@go version | grep 1.4 ; if [ $$? -eq 0 ]; then \
echo "Installing godep and restoring dependencies"; \
go get github.com/tools/godep; \
godep restore; \
fi
dev: deps ## Build and install a development build
dev: deps
@grep 'const VersionPrerelease = ""' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \
echo "ERROR: You must add prerelease tags to version/version.go prior to making a dev build."; \
exit 1; \
fi
@PACKER_DEV=1 GO15VENDOREXPERIMENT=1 sh -c "$(CURDIR)/scripts/build.sh"
fmt: ## Format Go code
fmt:
go fmt `go list ./... | grep -v vendor`
# Install js-beautify with npm install -g js-beautify
@@ -48,11 +51,11 @@ fmt-examples:
# generate runs `go generate` to build the dynamically generated
# source files.
generate: deps ## Generate dynamically generated code
generate: deps
go generate .
gofmt -w command/plugin.go
go fmt command/plugin.go
test: deps ## Run unit tests
test: deps
@go test $(TEST) $(TESTARGS) -timeout=2m
@go tool vet $(VET) ; if [ $$? -eq 1 ]; then \
echo "ERROR: Vet found problems in the code."; \
@@ -60,19 +63,22 @@ test: deps ## Run unit tests
fi
# testacc runs acceptance tests
testacc: deps generate ## Run acceptance tests
testacc: deps generate
@echo "WARN: Acceptance tests will take a long time to run and may cost money. Ctrl-C if you want to cancel."
PACKER_ACC=1 go test -v $(TEST) $(TESTARGS) -timeout=45m
testrace: deps ## Test for race conditions
testrace: deps
@go test -race $(TEST) $(TESTARGS) -timeout=2m
updatedeps:
go get -u github.com/mitchellh/gox
go get -u golang.org/x/tools/cmd/stringer
@echo "INFO: Packer deps are managed by govendor. See CONTRIBUTING.md"
@echo "INFO: Packer deps are managed by godep. See CONTRIBUTING.md"
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
# This is used to add new dependencies to packer. If you are submitting a PR
# that includes new dependencies you will need to run this.
vendor:
godep restore
godep save
.PHONY: bin checkversion ci default deps fmt fmt-examples generate releasebin test testacc testrace updatedeps
-1
View File
@@ -15,7 +15,6 @@ performant, creating machine images for multiple platforms in parallel. Packer
comes out of the box with support for the following platforms:
* Amazon EC2 (AMI). Both EBS-backed and instance-store AMIs
* Azure
* DigitalOcean
* Docker
* Google Compute Engine
+33 -81
View File
@@ -25,24 +25,19 @@ const BuilderId = "mitchellh.amazon.chroot"
// Config is the configuration that is chained through the steps and
// settable from the template.
type Config struct {
common.PackerConfig `mapstructure:",squash"`
awscommon.AMIBlockDevices `mapstructure:",squash"`
awscommon.AMIConfig `mapstructure:",squash"`
awscommon.AccessConfig `mapstructure:",squash"`
common.PackerConfig `mapstructure:",squash"`
awscommon.AccessConfig `mapstructure:",squash"`
awscommon.AMIConfig `mapstructure:",squash"`
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
CommandWrapper string `mapstructure:"command_wrapper"`
CopyFiles []string `mapstructure:"copy_files"`
DevicePath string `mapstructure:"device_path"`
FromScratch bool `mapstructure:"from_scratch"`
MountOptions []string `mapstructure:"mount_options"`
MountPartition int `mapstructure:"mount_partition"`
MountPath string `mapstructure:"mount_path"`
PostMountCommands []string `mapstructure:"post_mount_commands"`
PreMountCommands []string `mapstructure:"pre_mount_commands"`
RootDeviceName string `mapstructure:"root_device_name"`
RootVolumeSize int64 `mapstructure:"root_volume_size"`
SourceAmi string `mapstructure:"source_ami"`
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
CommandWrapper string `mapstructure:"command_wrapper"`
CopyFiles []string `mapstructure:"copy_files"`
DevicePath string `mapstructure:"device_path"`
MountPath string `mapstructure:"mount_path"`
SourceAmi string `mapstructure:"source_ami"`
RootVolumeSize int64 `mapstructure:"root_volume_size"`
MountOptions []string `mapstructure:"mount_options"`
MountPartition int `mapstructure:"mount_partition"`
ctx interpolate.Context
}
@@ -64,8 +59,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"command_wrapper",
"post_mount_commands",
"pre_mount_commands",
"mount_path",
},
},
@@ -93,7 +86,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
}
if len(b.config.CopyFiles) == 0 && !b.config.FromScratch {
if len(b.config.CopyFiles) == 0 {
b.config.CopyFiles = []string{"/etc/resolv.conf"}
}
@@ -109,10 +102,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.MountPartition = 1
}
// Accumulate any errors or warnings
// Accumulate any errors
var errs *packer.MultiError
var warns []string
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...)
@@ -124,49 +115,16 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
}
if b.config.FromScratch {
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 b.config.SourceAmi == "" {
errs = packer.MultiErrorAppend(errs, errors.New("source_ami is required."))
}
if errs != nil && len(errs.Errors) > 0 {
return warns, errs
return nil, errs
}
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
return warns, nil
return nil, nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
@@ -203,19 +161,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ForceDeregister: b.config.AMIForceDeregister,
},
&StepInstanceInfo{},
}
if !b.config.FromScratch {
steps = append(steps,
&awscommon.StepSourceAMIInfo{
SourceAmi: b.config.SourceAmi,
EnhancedNetworking: b.config.AMIEnhancedNetworking,
},
&StepCheckRootDevice{},
)
}
steps = append(steps,
&awscommon.StepSourceAMIInfo{
SourceAmi: b.config.SourceAmi,
EnhancedNetworking: b.config.AMIEnhancedNetworking,
},
&StepCheckRootDevice{},
&StepFlock{},
&StepPrepareDevice{},
&StepCreateVolume{
@@ -223,16 +173,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
&StepAttachVolume{},
&StepEarlyUnflock{},
&StepPreMountCommands{
Commands: b.config.PreMountCommands,
},
&StepMountDevice{
MountOptions: b.config.MountOptions,
MountPartition: b.config.MountPartition,
},
&StepPostMountCommands{
Commands: b.config.PostMountCommands,
},
&StepMountExtra{},
&StepCopyFiles{},
&StepChrootProvision{},
@@ -259,10 +203,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
},
)
}
// Run!
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state)
// If there was an error, return that
@@ -1,37 +0,0 @@
package chroot
import (
"fmt"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/post-processor/shell-local"
"github.com/mitchellh/packer/template/interpolate"
)
func RunLocalCommands(commands []string, wrappedCommand CommandWrapper, ctx interpolate.Context, ui packer.Ui) error {
for _, rawCmd := range commands {
intCmd, err := interpolate.Render(rawCmd, &ctx)
if err != nil {
return fmt.Errorf("Error interpolating: %s", err)
}
command, err := wrappedCommand(intCmd)
if err != nil {
return fmt.Errorf("Error wrapping command: %s", err)
}
ui.Say(fmt.Sprintf("Executing command: %s", command))
comm := &shell_local.Communicator{}
cmd := &packer.RemoteCmd{Command: command}
if err := cmd.StartWithUi(comm, ui); err != nil {
return fmt.Errorf("Error executing command: %s", err)
}
if cmd.ExitStatus != 0 {
return fmt.Errorf(
"Received non-zero exit code %d from command: %s",
cmd.ExitStatus,
command)
}
}
return nil
}
+27 -39
View File
@@ -22,52 +22,40 @@ type StepCreateVolume struct {
}
func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
image := state.Get("source_image").(*ec2.Image)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
var createVolume *ec2.CreateVolumeInput
if config.FromScratch {
createVolume = &ec2.CreateVolumeInput{
AvailabilityZone: instance.Placement.AvailabilityZone,
Size: aws.Int64(s.RootVolumeSize),
VolumeType: aws.String(ec2.VolumeTypeGp2),
}
} else {
// Determine the root device snapshot
image := state.Get("source_image").(*ec2.Image)
log.Printf("Searching for root device of the image (%s)", *image.RootDeviceName)
var rootDevice *ec2.BlockDeviceMapping
for _, device := range image.BlockDeviceMappings {
if *device.DeviceName == *image.RootDeviceName {
rootDevice = device
break
}
}
if rootDevice == nil {
err := fmt.Errorf("Couldn't find root device!")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Creating the root volume...")
vs := *rootDevice.Ebs.VolumeSize
if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize {
vs = s.RootVolumeSize
}
createVolume = &ec2.CreateVolumeInput{
AvailabilityZone: instance.Placement.AvailabilityZone,
Size: aws.Int64(vs),
SnapshotId: rootDevice.Ebs.SnapshotId,
VolumeType: rootDevice.Ebs.VolumeType,
Iops: rootDevice.Ebs.Iops,
// Determine the root device snapshot
log.Printf("Searching for root device of the image (%s)", *image.RootDeviceName)
var rootDevice *ec2.BlockDeviceMapping
for _, device := range image.BlockDeviceMappings {
if *device.DeviceName == *image.RootDeviceName {
rootDevice = device
break
}
}
if rootDevice == nil {
err := fmt.Errorf("Couldn't find root device!")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Creating the root volume...")
vs := *rootDevice.Ebs.VolumeSize
if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize {
vs = s.RootVolumeSize
}
createVolume := &ec2.CreateVolumeInput{
AvailabilityZone: instance.Placement.AvailabilityZone,
Size: aws.Int64(vs),
SnapshotId: rootDevice.Ebs.SnapshotId,
VolumeType: rootDevice.Ebs.VolumeType,
Iops: rootDevice.Ebs.Iops,
}
log.Printf("Create args: %+v", createVolume)
createVolumeResp, err := ec2conn.CreateVolume(createVolume)
+3 -10
View File
@@ -33,18 +33,10 @@ type StepMountDevice struct {
func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
image := state.Get("source_image").(*ec2.Image)
device := state.Get("device").(string)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
var virtualizationType string
if config.FromScratch {
virtualizationType = config.AMIVirtType
} else {
image := state.Get("source_image").(*ec2.Image)
virtualizationType = *image.VirtualizationType
log.Printf("Source image virtualization type is: %s", virtualizationType)
}
ctx := config.ctx
ctx.Data = &mountPathData{Device: filepath.Base(device)}
mountPath, err := interpolate.Render(config.MountPath, &ctx)
@@ -73,8 +65,9 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt
}
log.Printf("Source image virtualization type is: %s", *image.VirtualizationType)
deviceMount := device
if virtualizationType == "hvm" {
if *image.VirtualizationType == "hvm" {
deviceMount = fmt.Sprintf("%s%d", device, s.MountPartition)
}
state.Put("deviceMount", deviceMount)
@@ -1,45 +0,0 @@
package chroot
import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type postMountCommandsData struct {
Device string
MountPath string
}
// StepPostMountCommands allows running arbitrary commands after mounting the
// device, but prior to the bind mount and copy steps.
type StepPostMountCommands struct {
Commands []string
}
func (s *StepPostMountCommands) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
device := state.Get("device").(string)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
if len(s.Commands) == 0 {
return multistep.ActionContinue
}
ctx := config.ctx
ctx.Data = &postMountCommandsData{
Device: device,
MountPath: mountPath,
}
ui.Say("Running post-mount commands...")
if err := RunLocalCommands(s.Commands, wrappedCommand, ctx, ui); err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepPostMountCommands) Cleanup(state multistep.StateBag) {}
@@ -1,39 +0,0 @@
package chroot
import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type preMountCommandsData struct {
Device string
}
// StepPreMountCommands sets up the a new block device when building from scratch
type StepPreMountCommands struct {
Commands []string
}
func (s *StepPreMountCommands) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
device := state.Get("device").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
if len(s.Commands) == 0 {
return multistep.ActionContinue
}
ctx := config.ctx
ctx.Data = &preMountCommandsData{Device: device}
ui.Say("Running device setup commands...")
if err := RunLocalCommands(s.Commands, wrappedCommand, ctx, ui); err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepPreMountCommands) Cleanup(state multistep.StateBag) {}
+10 -35
View File
@@ -18,38 +18,22 @@ type StepRegisterAMI struct {
func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
image := state.Get("source_image").(*ec2.Image)
snapshotId := state.Get("snapshot_id").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Registering the AMI...")
var (
registerOpts *ec2.RegisterImageInput
mappings []*ec2.BlockDeviceMapping
image *ec2.Image
rootDeviceName string
)
if config.FromScratch {
mappings = config.AMIBlockDevices.BuildAMIDevices()
rootDeviceName = config.RootDeviceName
} else {
image = state.Get("source_image").(*ec2.Image)
mappings = image.BlockDeviceMappings
rootDeviceName = *image.RootDeviceName
}
newMappings := make([]*ec2.BlockDeviceMapping, len(mappings))
for i, device := range mappings {
blockDevices := make([]*ec2.BlockDeviceMapping, len(image.BlockDeviceMappings))
for i, device := range image.BlockDeviceMappings {
newDevice := device
if *newDevice.DeviceName == rootDeviceName {
if *newDevice.DeviceName == *image.RootDeviceName {
if newDevice.Ebs != nil {
newDevice.Ebs.SnapshotId = aws.String(snapshotId)
} else {
newDevice.Ebs = &ec2.EbsBlockDevice{SnapshotId: aws.String(snapshotId)}
}
if config.FromScratch || s.RootVolumeSize > *newDevice.Ebs.VolumeSize {
if s.RootVolumeSize > *newDevice.Ebs.VolumeSize {
newDevice.Ebs.VolumeSize = aws.Int64(s.RootVolumeSize)
}
}
@@ -60,20 +44,10 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
newDevice.Ebs.Encrypted = nil
}
newMappings[i] = newDevice
blockDevices[i] = newDevice
}
if config.FromScratch {
registerOpts = &ec2.RegisterImageInput{
Name: &config.AMIName,
Architecture: aws.String(ec2.ArchitectureValuesX8664),
RootDeviceName: aws.String(rootDeviceName),
VirtualizationType: aws.String(config.AMIVirtType),
BlockDeviceMappings: newMappings,
}
} else {
registerOpts = buildRegisterOpts(config, image, newMappings)
}
registerOpts := buildRegisterOpts(config, image, blockDevices)
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
if config.AMIEnhancedNetworking {
@@ -114,12 +88,12 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {}
func buildRegisterOpts(config *Config, image *ec2.Image, mappings []*ec2.BlockDeviceMapping) *ec2.RegisterImageInput {
func buildRegisterOpts(config *Config, image *ec2.Image, blockDevices []*ec2.BlockDeviceMapping) *ec2.RegisterImageInput {
registerOpts := &ec2.RegisterImageInput{
Name: &config.AMIName,
Architecture: image.Architecture,
RootDeviceName: image.RootDeviceName,
BlockDeviceMappings: mappings,
BlockDeviceMappings: blockDevices,
VirtualizationType: image.VirtualizationType,
}
@@ -131,5 +105,6 @@ func buildRegisterOpts(config *Config, image *ec2.Image, mappings []*ec2.BlockDe
registerOpts.KernelId = image.KernelId
registerOpts.RamdiskId = image.RamdiskId
}
return registerOpts
}
+8 -11
View File
@@ -17,12 +17,11 @@ import (
// AccessConfig is for common configuration related to AWS access
type AccessConfig struct {
AccessKey string `mapstructure:"access_key"`
SecretKey string `mapstructure:"secret_key"`
RawRegion string `mapstructure:"region"`
SkipValidation bool `mapstructure:"skip_region_validation"`
Token string `mapstructure:"token"`
ProfileName string `mapstructure:"profile"`
AccessKey string `mapstructure:"access_key"`
SecretKey string `mapstructure:"secret_key"`
RawRegion string `mapstructure:"region"`
Token string `mapstructure:"token"`
ProfileName string `mapstructure:"profile"`
}
// Config returns a valid aws.Config object for access to AWS services, or
@@ -66,10 +65,8 @@ func (c *AccessConfig) Config() (*aws.Config, error) {
// the region from the instance metadata if possible.
func (c *AccessConfig) Region() (string, error) {
if c.RawRegion != "" {
if !c.SkipValidation {
if valid := ValidateRegion(c.RawRegion); valid == false {
return "", fmt.Errorf("Not a valid region: %s", c.RawRegion)
}
if valid := ValidateRegion(c.RawRegion); valid == false {
return "", fmt.Errorf("Not a valid region: %s", c.RawRegion)
}
return c.RawRegion, nil
}
@@ -85,7 +82,7 @@ func (c *AccessConfig) Region() (string, error) {
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if c.RawRegion != "" && !c.SkipValidation {
if c.RawRegion != "" {
if valid := ValidateRegion(c.RawRegion); valid == false {
errs = append(errs, fmt.Errorf("Unknown region: %s", c.RawRegion))
}
@@ -24,17 +24,4 @@ func TestAccessConfigPrepare_Region(t *testing.T) {
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.RawRegion = "custom"
if err := c.Prepare(nil); err == nil {
t.Fatalf("should have err")
}
c.RawRegion = "custom"
c.SkipValidation = true
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.SkipValidation = false
}
+14 -22
View File
@@ -8,18 +8,16 @@ import (
// AMIConfig is for common configuration related to creating AMIs.
type AMIConfig struct {
AMIName string `mapstructure:"ami_name"`
AMIDescription string `mapstructure:"ami_description"`
AMIVirtType string `mapstructure:"ami_virtualization_type"`
AMIUsers []string `mapstructure:"ami_users"`
AMIGroups []string `mapstructure:"ami_groups"`
AMIProductCodes []string `mapstructure:"ami_product_codes"`
AMIRegions []string `mapstructure:"ami_regions"`
AMISkipRegionValidation bool `mapstructure:"skip_region_validation"`
AMITags map[string]string `mapstructure:"tags"`
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
AMIForceDeregister bool `mapstructure:"force_deregister"`
AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"`
AMIName string `mapstructure:"ami_name"`
AMIDescription string `mapstructure:"ami_description"`
AMIVirtType string `mapstructure:"ami_virtualization_type"`
AMIUsers []string `mapstructure:"ami_users"`
AMIGroups []string `mapstructure:"ami_groups"`
AMIProductCodes []string `mapstructure:"ami_product_codes"`
AMIRegions []string `mapstructure:"ami_regions"`
AMITags map[string]string `mapstructure:"tags"`
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
AMIForceDeregister bool `mapstructure:"force_deregister"`
}
func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
@@ -41,12 +39,10 @@ func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
// Mark that we saw the region
regionSet[region] = struct{}{}
if !c.AMISkipRegionValidation {
// Verify the region is real
if valid := ValidateRegion(region); valid == false {
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
continue
}
// Verify the region is real
if valid := ValidateRegion(region); valid == false {
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
continue
}
regions = append(regions, region)
@@ -55,10 +51,6 @@ func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
c.AMIRegions = regions
}
if len(c.AMIUsers) > 0 && c.AMIEncryptBootVolume {
errs = append(errs, fmt.Errorf("Cannot share AMI with encrypted boot volume"))
}
if len(errs) > 0 {
return errs
}
-17
View File
@@ -49,21 +49,4 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
if !reflect.DeepEqual(c.AMIRegions, expected) {
t.Fatalf("bad: %#v", c.AMIRegions)
}
c.AMIRegions = []string{"custom"}
c.AMISkipRegionValidation = true
if err := c.Prepare(nil); err != nil {
t.Fatal("shouldn't have error")
}
c.AMISkipRegionValidation = false
}
func TestAMIConfigPrepare_EncryptBoot(t *testing.T) {
c := testAMIConfig()
c.AMIUsers = []string{"testAccountID"}
c.AMIEncryptBootVolume = true
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
}
+3 -11
View File
@@ -22,15 +22,7 @@ type BlockDevice struct {
}
type BlockDevices struct {
AMIBlockDevices `mapstructure:",squash"`
LaunchBlockDevices `mapstructure:",squash"`
}
type AMIBlockDevices struct {
AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings"`
}
type LaunchBlockDevices struct {
AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings"`
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings"`
}
@@ -85,10 +77,10 @@ func (b *BlockDevices) Prepare(ctx *interpolate.Context) []error {
return nil
}
func (b *AMIBlockDevices) BuildAMIDevices() []*ec2.BlockDeviceMapping {
func (b *BlockDevices) BuildAMIDevices() []*ec2.BlockDeviceMapping {
return buildBlockDevices(b.AMIMappings)
}
func (b *LaunchBlockDevices) BuildLaunchDevices() []*ec2.BlockDeviceMapping {
func (b *BlockDevices) BuildLaunchDevices() []*ec2.BlockDeviceMapping {
return buildBlockDevices(b.LaunchMappings)
}
+8 -12
View File
@@ -124,26 +124,22 @@ func TestBlockDevice(t *testing.T) {
}
for _, tc := range cases {
amiBlockDevices := AMIBlockDevices{
AMIMappings: []BlockDevice{*tc.Config},
}
launchBlockDevices := LaunchBlockDevices{
blockDevices := BlockDevices{
AMIMappings: []BlockDevice{*tc.Config},
LaunchMappings: []BlockDevice{*tc.Config},
}
expected := []*ec2.BlockDeviceMapping{tc.Result}
amiResults := amiBlockDevices.BuildAMIDevices()
if !reflect.DeepEqual(expected, amiResults) {
got := blockDevices.BuildAMIDevices()
if !reflect.DeepEqual(expected, got) {
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
expected, amiResults)
expected, got)
}
launchResults := launchBlockDevices.BuildLaunchDevices()
if !reflect.DeepEqual(expected, launchResults) {
if !reflect.DeepEqual(expected, blockDevices.BuildLaunchDevices()) {
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
expected, launchResults)
expected,
blockDevices.BuildLaunchDevices())
}
}
}
-2
View File
@@ -4,7 +4,6 @@ func listEC2Regions() []string {
return []string{
"ap-northeast-1",
"ap-northeast-2",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"cn-north-1",
@@ -12,7 +11,6 @@ func listEC2Regions() []string {
"eu-west-1",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-gov-west-1",
"us-west-1",
"us-west-2",
+21 -36
View File
@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"os"
"regexp"
"time"
"github.com/mitchellh/packer/common/uuid"
@@ -12,30 +11,26 @@ import (
"github.com/mitchellh/packer/template/interpolate"
)
var reShutdownBehavior = regexp.MustCompile("^(stop|terminate)$")
// RunConfig contains configuration for running an instance from a source
// AMI and details on how to access that launched image.
type RunConfig struct {
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
AvailabilityZone string `mapstructure:"availability_zone"`
EbsOptimized bool `mapstructure:"ebs_optimized"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceType string `mapstructure:"instance_type"`
RunTags map[string]string `mapstructure:"run_tags"`
SourceAmi string `mapstructure:"source_ami"`
SpotPrice string `mapstructure:"spot_price"`
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids"`
SubnetId string `mapstructure:"subnet_id"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"`
VpcId string `mapstructure:"vpc_id"`
InstanceInitiatedShutdownBehavior string `mapstructure:"shutdown_behaviour"`
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
AvailabilityZone string `mapstructure:"availability_zone"`
EbsOptimized bool `mapstructure:"ebs_optimized"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceType string `mapstructure:"instance_type"`
RunTags map[string]string `mapstructure:"run_tags"`
SourceAmi string `mapstructure:"source_ami"`
SpotPrice string `mapstructure:"spot_price"`
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids"`
SubnetId string `mapstructure:"subnet_id"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"`
VpcId string `mapstructure:"vpc_id"`
// Communicator settings
Comm communicator.Config `mapstructure:",squash"`
@@ -44,14 +39,10 @@ type RunConfig struct {
}
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
// If we are not given an explicit ssh_keypair_name or
// ssh_private_key_file, then create a temporary one, but only if the
// temporary_key_pair_name has not been provided and we are not using
// ssh_password.
if c.SSHKeyPairName == "" && c.TemporaryKeyPairName == "" &&
c.Comm.SSHPrivateKey == "" && c.Comm.SSHPassword == "" {
c.TemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
// if we are not given an explicit keypairname, create a temporary one
if c.SSHKeyPairName == "" {
c.TemporaryKeyPairName = fmt.Sprintf(
"packer %s", uuid.TimeOrderedUUID())
}
if c.WindowsPasswordTimeout == 0 {
@@ -92,11 +83,5 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
}
}
if c.InstanceInitiatedShutdownBehavior == "" {
c.InstanceInitiatedShutdownBehavior = "stop"
} else if !reShutdownBehavior.MatchString(c.InstanceInitiatedShutdownBehavior) {
errs = append(errs, fmt.Errorf("shutdown_behaviour only accepts 'stop' or 'terminate' values."))
}
return errs
}
+1 -17
View File
@@ -3,7 +3,6 @@ package common
import (
"io/ioutil"
"os"
"regexp"
"testing"
"github.com/mitchellh/packer/helper/communicator"
@@ -141,21 +140,6 @@ func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
}
if c.TemporaryKeyPairName == "" {
t.Fatal("keypair name is empty")
}
// Match prefix and UUID, e.g. "packer_5790d491-a0b8-c84c-c9d2-2aea55086550".
r := regexp.MustCompile(`\Apacker_(?:(?i)[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}?)\z`)
if !r.MatchString(c.TemporaryKeyPairName) {
t.Fatal("keypair name is not valid")
}
c.TemporaryKeyPairName = "ssh-key-123"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.TemporaryKeyPairName != "ssh-key-123" {
t.Fatal("keypair name does not match")
t.Fatal("keypair empty")
}
}
+19 -44
View File
@@ -7,36 +7,22 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mitchellh/multistep"
packerssh "github.com/mitchellh/packer/communicator/ssh"
"golang.org/x/crypto/ssh"
)
type ec2Describer interface {
DescribeInstances(*ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
}
var (
// modified in tests
sshHostSleepDuration = time.Second
)
// SSHHost returns a function that can be given to the SSH communicator
// for determining the SSH address based on the instance DNS name.
func SSHHost(e ec2Describer, private bool) func(multistep.StateBag) (string, error) {
func SSHHost(e *ec2.EC2, private bool) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
const tries = 2
// <= with current structure to check result of describing `tries` times
for j := 0; j <= tries; j++ {
for j := 0; j < 2; j++ {
var host string
i := state.Get("instance").(*ec2.Instance)
if i.VpcId != nil && *i.VpcId != "" {
if i.PublicIpAddress != nil && *i.PublicIpAddress != "" && !private {
host = *i.PublicIpAddress
} else if i.PrivateIpAddress != nil && *i.PrivateIpAddress != "" {
} else {
host = *i.PrivateIpAddress
}
} else if private && i.PrivateIpAddress != nil && *i.PrivateIpAddress != "" {
host = *i.PrivateIpAddress
} else if i.PublicDnsName != nil && *i.PublicDnsName != "" {
host = *i.PublicDnsName
}
@@ -56,8 +42,8 @@ func SSHHost(e ec2Describer, private bool) func(multistep.StateBag) (string, err
return "", fmt.Errorf("instance not found: %s", *i.InstanceId)
}
state.Put("instance", r.Reservations[0].Instances[0])
time.Sleep(sshHostSleepDuration)
state.Put("instance", &r.Reservations[0].Instances[0])
time.Sleep(1 * time.Second)
}
return "", errors.New("couldn't determine IP address for instance")
@@ -65,33 +51,22 @@ func SSHHost(e ec2Describer, private bool) func(multistep.StateBag) (string, err
}
// SSHConfig returns a function that can be used for the SSH communicator
// config for connecting to the instance created over SSH using the private key
// or password.
func SSHConfig(username, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
// config for connecting to the instance created over SSH using the generated
// private key.
func SSHConfig(username string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
privateKey := state.Get("privateKey").(string)
privateKey, hasKey := state.GetOk("privateKey")
if hasKey {
signer, err := ssh.ParsePrivateKey([]byte(privateKey.(string)))
if err != nil {
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
}
return &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
}, nil
} else {
return &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.Password(password),
ssh.KeyboardInteractive(
packerssh.PasswordKeyboardInteractive(password)),
}}, nil
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
}
return &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
}, nil
}
}
-118
View File
@@ -1,118 +0,0 @@
package common
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mitchellh/multistep"
)
const (
privateIP = "10.0.0.1"
publicIP = "192.168.1.1"
publicDNS = "public.dns.test"
)
func TestSSHHost(t *testing.T) {
origSshHostSleepDuration := sshHostSleepDuration
defer func() { sshHostSleepDuration = origSshHostSleepDuration }()
sshHostSleepDuration = 0
var cases = []struct {
allowTries int
vpcId string
private bool
ok bool
wantHost string
}{
{1, "", false, true, publicDNS},
{1, "", true, true, privateIP},
{1, "vpc-id", false, true, publicIP},
{1, "vpc-id", true, true, privateIP},
{2, "", false, true, publicDNS},
{2, "", true, true, privateIP},
{2, "vpc-id", false, true, publicIP},
{2, "vpc-id", true, true, privateIP},
{3, "", false, false, ""},
{3, "", true, false, ""},
{3, "vpc-id", false, false, ""},
{3, "vpc-id", true, false, ""},
}
for _, c := range cases {
testSSHHost(t, c.allowTries, c.vpcId, c.private, c.ok, c.wantHost)
}
}
func testSSHHost(t *testing.T, allowTries int, vpcId string, private, ok bool, wantHost string) {
t.Logf("allowTries=%d vpcId=%s private=%t ok=%t wantHost=%q", allowTries, vpcId, private, ok, wantHost)
e := &fakeEC2Describer{
allowTries: allowTries,
vpcId: vpcId,
privateIP: privateIP,
publicIP: publicIP,
publicDNS: publicDNS,
}
f := SSHHost(e, private)
st := &multistep.BasicStateBag{}
st.Put("instance", &ec2.Instance{
InstanceId: aws.String("instance-id"),
})
host, err := f(st)
if e.tries > allowTries {
t.Fatalf("got %d ec2 DescribeInstances tries, want %d", e.tries, allowTries)
}
switch {
case ok && err != nil:
t.Fatalf("expected no error, got %+v", err)
case !ok && err == nil:
t.Fatalf("expected error, got none and host %s", host)
}
if host != wantHost {
t.Fatalf("got host %s, want %s", host, wantHost)
}
}
type fakeEC2Describer struct {
allowTries int
tries int
vpcId string
privateIP, publicIP, publicDNS string
}
func (d *fakeEC2Describer) DescribeInstances(in *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
d.tries++
instance := &ec2.Instance{
InstanceId: aws.String("instance-id"),
}
if d.vpcId != "" {
instance.VpcId = aws.String(d.vpcId)
}
if d.tries >= d.allowTries {
instance.PublicIpAddress = aws.String(d.publicIP)
instance.PrivateIpAddress = aws.String(d.privateIP)
instance.PublicDnsName = aws.String(d.publicDNS)
}
out := &ec2.DescribeInstancesOutput{
Reservations: []*ec2.Reservation{
{
Instances: []*ec2.Instance{instance},
},
},
}
return out, nil
}
+3 -18
View File
@@ -4,11 +4,9 @@ import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mitchellh/multistep"
retry "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
)
@@ -73,22 +71,9 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
}
}
// Retry creating tags for about 2.5 minutes
err = retry.Retry(0.2, 30, 11, func() (bool, error) {
_, err := regionconn.CreateTags(&ec2.CreateTagsInput{
Resources: resourceIds,
Tags: ec2Tags,
})
if err == nil {
return true, nil
}
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidAMIID.NotFound" ||
awsErr.Code() == "InvalidSnapshot.NotFound" {
return false, nil
}
}
return true, err
_, err = regionconn.CreateTags(&ec2.CreateTagsInput{
Resources: resourceIds,
Tags: ec2Tags,
})
if err != nil {
+2 -10
View File
@@ -22,10 +22,7 @@ type StepKeyPair struct {
}
func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if s.PrivateKeyFile != "" {
ui.Say("Using existing ssh private key")
privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile)
if err != nil {
state.Put("error", fmt.Errorf(
@@ -39,13 +36,8 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
if s.TemporaryKeyPairName == "" {
ui.Say("Not using temporary keypair")
state.Put("keyPair", "")
return multistep.ActionContinue
}
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.TemporaryKeyPairName))
keyResp, err := ec2conn.CreateKeyPair(&ec2.CreateKeyPairInput{
@@ -95,7 +87,7 @@ func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
// If no key name is set, then we never created it, so just return
// If we used an SSH private key file, do not go about deleting
// keypairs
if s.PrivateKeyFile != "" || s.KeyPairName == "" {
if s.PrivateKeyFile != "" {
return
}
@@ -16,22 +16,21 @@ import (
)
type StepRunSourceInstance struct {
AssociatePublicIpAddress bool
AvailabilityZone string
BlockDevices BlockDevices
Debug bool
EbsOptimized bool
ExpectedRootDevice string
InstanceType string
IamInstanceProfile string
SourceAMI string
SpotPrice string
SpotPriceProduct string
SubnetId string
Tags map[string]string
UserData string
UserDataFile string
InstanceInitiatedShutdownBehavior string
AssociatePublicIpAddress bool
AvailabilityZone string
BlockDevices BlockDevices
Debug bool
EbsOptimized bool
ExpectedRootDevice string
InstanceType string
IamInstanceProfile string
SourceAMI string
SpotPrice string
SpotPriceProduct string
SubnetId string
Tags map[string]string
UserData string
UserDataFile string
instanceId string
spotRequest *ec2.SpotInstanceRequest
@@ -45,25 +44,7 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
securityGroupIds := make([]*string, len(tempSecurityGroupIds))
for i, sg := range tempSecurityGroupIds {
found := false
for i := 0; i < 5; i++ {
time.Sleep(time.Duration(i) * 5 * time.Second)
log.Printf("[DEBUG] Describing tempSecurityGroup to ensure it is available: %s", sg)
_, err := ec2conn.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{
GroupIds: []*string{aws.String(sg)},
})
if err == nil {
log.Printf("[DEBUG] Found security group %s", sg)
found = true
break
}
log.Printf("[DEBUG] Error in querying security group %s", err)
}
if found {
securityGroupIds[i] = aws.String(sg)
} else {
state.Put("error", fmt.Errorf("Timeout waiting for security group %s to become available", sg))
}
securityGroupIds[i] = aws.String(sg)
}
userData := s.UserData
@@ -147,10 +128,6 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
} else {
// Add 0.5 cents to minimum spot bid to ensure capacity will be available
// Avoids price-too-low error in active markets which can fluctuate
price = price + 0.005
}
spotPrice = strconv.FormatFloat(price, 'f', -1, 64)
@@ -160,6 +137,7 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
if spotPrice == "" || spotPrice == "0" {
runOpts := &ec2.RunInstancesInput{
KeyName: &keyName,
ImageId: &s.SourceAMI,
InstanceType: &s.InstanceType,
UserData: &userData,
@@ -171,10 +149,6 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
EbsOptimized: &s.EbsOptimized,
}
if keyName != "" {
runOpts.KeyName = &keyName
}
if s.SubnetId != "" && s.AssociatePublicIpAddress {
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
&ec2.InstanceNetworkInterfaceSpecification{
@@ -190,10 +164,6 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
runOpts.SecurityGroupIds = securityGroupIds
}
if s.ExpectedRootDevice == "ebs" {
runOpts.InstanceInitiatedShutdownBehavior = &s.InstanceInitiatedShutdownBehavior
}
runResp, err := ec2conn.RunInstances(runOpts)
if err != nil {
err := fmt.Errorf("Error launching source instance: %s", err)
@@ -206,35 +176,29 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
ui.Message(fmt.Sprintf(
"Requesting spot instance '%s' for: %s",
s.InstanceType, spotPrice))
runOpts := &ec2.RequestSpotLaunchSpecification{
ImageId: &s.SourceAMI,
InstanceType: &s.InstanceType,
UserData: &userData,
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
NetworkInterfaces: []*ec2.InstanceNetworkInterfaceSpecification{
&ec2.InstanceNetworkInterfaceSpecification{
DeviceIndex: aws.Int64(0),
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
SubnetId: &s.SubnetId,
Groups: securityGroupIds,
DeleteOnTermination: aws.Bool(true),
},
},
Placement: &ec2.SpotPlacement{
AvailabilityZone: &availabilityZone,
},
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
EbsOptimized: &s.EbsOptimized,
}
if keyName != "" {
runOpts.KeyName = &keyName
}
runSpotResp, err := ec2conn.RequestSpotInstances(&ec2.RequestSpotInstancesInput{
SpotPrice: &spotPrice,
LaunchSpecification: runOpts,
SpotPrice: &spotPrice,
LaunchSpecification: &ec2.RequestSpotLaunchSpecification{
KeyName: &keyName,
ImageId: &s.SourceAMI,
InstanceType: &s.InstanceType,
UserData: &userData,
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
NetworkInterfaces: []*ec2.InstanceNetworkInterfaceSpecification{
&ec2.InstanceNetworkInterfaceSpecification{
DeviceIndex: aws.Int64(0),
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
SubnetId: &s.SubnetId,
Groups: securityGroupIds,
DeleteOnTermination: aws.Bool(true),
},
},
Placement: &ec2.SpotPlacement{
AvailabilityZone: &availabilityZone,
},
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
EbsOptimized: &s.EbsOptimized,
},
})
if err != nil {
err := fmt.Errorf("Error launching source spot instance: %s", err)
@@ -22,7 +22,7 @@ func (s *StepSourceAMIInfo) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Inspecting the source AMI (%s)...", s.SourceAmi))
ui.Say("Inspecting the source AMI...")
imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{&s.SourceAmi}})
if err != nil {
err := fmt.Errorf("Error querying AMI: %s", err)
+11 -9
View File
@@ -132,7 +132,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
},
&stepTagEBSVolumes{
VolumeRunTags: b.config.VolumeRunTags,
@@ -148,14 +147,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ec2conn,
b.config.SSHPrivateIp),
SSHConfig: awscommon.SSHConfig(
b.config.RunConfig.Comm.SSHUsername,
b.config.RunConfig.Comm.SSHPassword),
b.config.RunConfig.Comm.SSHUsername),
},
&common.StepProvision{},
&stepStopInstance{
SpotPrice: b.config.SpotPrice,
DisableStopInstance: b.config.DisableStopInstance,
},
&stepStopInstance{SpotPrice: b.config.SpotPrice},
// TODO(mitchellh): verify works with spots
&stepModifyInstance{},
&awscommon.StepDeregisterAMI{
@@ -163,7 +158,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
AMIName: b.config.AMIName,
},
&stepCreateAMI{},
&stepCreateEncryptedAMICopy{},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,
@@ -181,7 +175,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Run!
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state)
// If there was an error, return that
-59
View File
@@ -55,15 +55,6 @@ func TestBuilderAcc_amiSharing(t *testing.T) {
})
}
func TestBuilderAcc_encryptedBoot(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccEncrypted,
Check: checkBootEncrypted(),
})
}
func checkAMISharing(count int, uid, group string) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
if len(artifacts) > 1 {
@@ -153,42 +144,6 @@ func checkRegionCopy(regions []string) builderT.TestCheckFunc {
}
}
func checkBootEncrypted() builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
// Get the actual *Artifact pointer so we can access the AMIs directly
artifactRaw := artifacts[0]
artifact, ok := artifactRaw.(*common.Artifact)
if !ok {
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
}
// describe the image, get block devices with a snapshot
ec2conn, _ := testEC2Conn()
imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: []*string{aws.String(artifact.Amis["us-east-1"])},
})
if err != nil {
return fmt.Errorf("Error retrieving Image Attributes for AMI (%s) in AMI Encrypted Boot Test: %s", artifact, err)
}
image := imageResp.Images[0] // Only requested a single AMI ID
rootDeviceName := image.RootDeviceName
for _, bd := range image.BlockDeviceMappings {
if *bd.DeviceName == *rootDeviceName {
if *bd.Ebs.Encrypted != true {
return fmt.Errorf("volume not encrypted: %s", *bd.Ebs.SnapshotId)
}
}
}
return nil
}
}
func testAccPreCheck(t *testing.T) {
if v := os.Getenv("AWS_ACCESS_KEY_ID"); v == "" {
t.Fatal("AWS_ACCESS_KEY_ID must be set for acceptance tests")
@@ -267,20 +222,6 @@ const testBuilderAccSharing = `
}
`
const testBuilderAccEncrypted = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami":"ami-c15bebaa",
"ssh_username": "ubuntu",
"ami_name": "packer-enc-test {{timestamp}}",
"encrypt_boot": true
}]
}
`
func buildForceDeregisterConfig(name, flag string) string {
return fmt.Sprintf(testBuilderAccForceDeregister, name, flag)
}
-35
View File
@@ -92,38 +92,3 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_InvalidShutdownBehaviour(t *testing.T) {
var b Builder
config := testConfig()
// Test good
config["shutdown_behaviour"] = "terminate"
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test good
config["shutdown_behaviour"] = "stop"
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["shutdown_behaviour"] = "foobar"
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
@@ -30,6 +30,12 @@ func (s *stepCleanupVolumes) Cleanup(state multistep.StateBag) {
instance = instanceRaw.(*ec2.Instance)
}
ui := state.Get("ui").(packer.Ui)
amisRaw := state.Get("amis")
if amisRaw == nil {
ui.Say("No AMIs to cleanup")
return
}
if instance == nil {
ui.Say("No volumes to clean up, skipping")
return
-142
View File
@@ -1,142 +0,0 @@
package ebs
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
)
type stepCreateEncryptedAMICopy struct {
image *ec2.Image
}
func (s *stepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
// Encrypt boot not set, so skip step
if !config.AMIConfig.AMIEncryptBootVolume {
return multistep.ActionContinue
}
ui.Say("Creating Encrypted AMI Copy")
amis := state.Get("amis").(map[string]string)
var region, id string
if amis != nil {
for region, id = range amis {
break // Only get the first
}
}
ui.Say(fmt.Sprintf("Copying AMI: %s(%s)", region, id))
copyOpts := &ec2.CopyImageInput{
Name: &config.AMIName, // Try to overwrite existing AMI
SourceImageId: aws.String(id),
SourceRegion: aws.String(region),
Encrypted: aws.Bool(true),
}
copyResp, err := ec2conn.CopyImage(copyOpts)
if err != nil {
err := fmt.Errorf("Error copying AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Wait for the copy to become ready
stateChange := awscommon.StateChangeConf{
Pending: []string{"pending"},
Target: "available",
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *copyResp.ImageId),
StepState: state,
}
ui.Say("Waiting for AMI copy to become ready...")
if _, err := awscommon.WaitForState(&stateChange); err != nil {
err := fmt.Errorf("Error waiting for AMI Copy: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Get the unencrypted AMI image
unencImagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{aws.String(id)}})
if err != nil {
err := fmt.Errorf("Error searching for AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
unencImage := unencImagesResp.Images[0]
// Remove unencrypted AMI
ui.Say("Deregistering unecrypted AMI")
deregisterOpts := &ec2.DeregisterImageInput{ImageId: aws.String(id)}
if _, err := ec2conn.DeregisterImage(deregisterOpts); err != nil {
ui.Error(fmt.Sprintf("Error deregistering AMI, may still be around: %s", err))
return multistep.ActionHalt
}
// Remove associated unencrypted snapshot(s)
ui.Say("Deleting unencrypted snapshots")
for _, blockDevice := range unencImage.BlockDeviceMappings {
if blockDevice.Ebs != nil {
if blockDevice.Ebs.SnapshotId != nil {
ui.Message(fmt.Sprintf("Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
deleteSnapOpts := &ec2.DeleteSnapshotInput{
SnapshotId: aws.String(*blockDevice.Ebs.SnapshotId),
}
if _, err := ec2conn.DeleteSnapshot(deleteSnapOpts); err != nil {
ui.Error(fmt.Sprintf("Error deleting snapshot, may still be around: %s", err))
return multistep.ActionHalt
}
}
}
}
// Replace original AMI ID with Encrypted ID in state
amis[region] = *copyResp.ImageId
state.Put("amis", amis)
imagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{copyResp.ImageId}})
if err != nil {
err := fmt.Errorf("Error searching for AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.image = imagesResp.Images[0]
return multistep.ActionContinue
}
func (s *stepCreateEncryptedAMICopy) Cleanup(state multistep.StateBag) {
if s.image == nil {
return
}
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deregistering the AMI because cancelation or error...")
deregisterOpts := &ec2.DeregisterImageInput{ImageId: s.image.ImageId}
if _, err := ec2conn.DeregisterImage(deregisterOpts); err != nil {
ui.Error(fmt.Sprintf("Error deregistering AMI, may still be around: %s", err))
return
}
}
+11 -18
View File
@@ -10,8 +10,7 @@ import (
)
type stepStopInstance struct {
SpotPrice string
DisableStopInstance bool
SpotPrice string
}
func (s *stepStopInstance) Run(state multistep.StateBag) multistep.StepAction {
@@ -24,22 +23,16 @@ func (s *stepStopInstance) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
var err error
if !s.DisableStopInstance {
// Stop the instance so we can create an AMI from it
ui.Say("Stopping the source instance...")
_, err = ec2conn.StopInstances(&ec2.StopInstancesInput{
InstanceIds: []*string{instance.InstanceId},
})
if err != nil {
err := fmt.Errorf("Error stopping instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
} else {
ui.Say("Automatic instance stop disabled. Please stop instance manually.")
// Stop the instance so we can create an AMI from it
ui.Say("Stopping the source instance...")
_, err := ec2conn.StopInstances(&ec2.StopInstancesInput{
InstanceIds: []*string{instance.InstanceId},
})
if err != nil {
err := fmt.Errorf("Error stopping instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Wait for the instance to actual stop
+10 -3
View File
@@ -230,8 +230,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ec2conn,
b.config.SSHPrivateIp),
SSHConfig: awscommon.SSHConfig(
b.config.RunConfig.Comm.SSHUsername,
b.config.RunConfig.Comm.SSHPassword),
b.config.RunConfig.Comm.SSHUsername),
},
&common.StepProvision{},
&StepUploadX509Cert{},
@@ -263,7 +262,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Run!
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state)
// If there was an error, return that
-15
View File
@@ -1,15 +0,0 @@
Here's a list of things we like to get done in no particular order:
- [ ] Blob/image copy post-processor
- [ ] Blob/image rename post-processor
- [ ] SSH to private ip through subnet
- [ ] chroot builder
- [ ] support cross-storage account image source (ie. pre-build blob copy)
- [ ] look up object id when using device code (graph api /me ?)
- [ ] device flow support for Windows
- [x] look up tenant id in all cases (see device flow code)
- [ ] look up resource group of storage account
- [ ] include all _data_ disks in artifact too
- [ ] windows sysprep provisioner (since it seems to generate a certain issue volume)
- [ ] allow arbitrary json patching for deployment document
- [ ] tag all resources with user-supplied tag
+191
View File
@@ -0,0 +1,191 @@
# packer-azure-arm
The ARM flavor of packer-azure utilizes the
[Azure Resource Manager APIs](https://msdn.microsoft.com/en-us/library/azure/dn790568.aspx).
Please see the
[overview](https://azure.microsoft.com/en-us/documentation/articles/resource-group-overview/)
for more information about ARM as well as the benefit of ARM.
## Device Login vs. Service Principal Name (SPN)
There are two ways to get started with packer-azure. The simplest is device login, and only requires a Subscription ID.
Device login is only supported for Linux based VMs. The second is the use of an SPN. We recommend the device login
approach for those who are first time users, and just want to ''kick the tires.'' We recommend the SPN approach if you
intend to automate Packer, or you are deploying Windows VMs.
## Device Login
A sample template for device login is show below. There are three pieces of information
you must provide to enable device login mode.
1. SubscriptionID
1. Resource Group - parent resource group that Packer uses to build an image.
1. Storage Account - storage account where the image will be placed.
> Device login mode is enabled by not setting client_id, client_secret, and tenant_id.
The device login flow asks that you open a web browser, navigate to http://aka.ms/devicelogin, and input the supplied
code. This authorizes the Packer for Azure application to act on your behalf. An OAuth token will be created, and
stored in the user's home directory (~/.azure/packer/oauth-TenantID.json, and TenantID will be replaced with the actual
Tenant ID). This token is used if it exists, and refreshed as necessary.
```json
{
"variables": {
"sid": "your_subscription_id",
"rgn": "your_resource_group",
"sa": "your_storage_account"
},
"builders": [
{
"type": "azure-arm",
"subscription_id": "{{user `sid`}}",
"resource_group_name": "{{user `rgn`}}",
"storage_account": "{{user `sa`}}",
"capture_container_name": "images",
"capture_name_prefix": "packer",
"os_type": "Linux",
"image_publisher": "Canonical",
"image_offer": "UbuntuServer",
"image_sku": "14.04.3-LTS",
"location": "South Central US",
"vm_size": "Standard_A2"
}
],
"provisioners": [
{
"execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'",
"inline": [
"apt-get update",
"apt-get upgrade -y",
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
],
"inline_shebang": "/bin/sh -x",
"type": "shell"
}
]
}
```
## Service Principal Name
The ARM APIs use OAUTH to authenticate, and requires an SPN. The following articles
are a good starting points for creating a new SPN.
* [Automating Azure on your CI server using a Service Principal](http://blog.davidebbo.com/2014/12/azure-service-principal.html)
* [Authenticating a service principal with Azure Resource Manager](https://azure.microsoft.com/en-us/documentation/articles/resource-group-authenticate-service-principal/)
There are three (four in the case of Windows) pieces of configuration you need to note
after creating an SPN.
1. Client ID (aka Service Principal ID)
1. Client Secret (aka Service Principal generated key)
1. Client Tenant (aka Azure Active Directory tenant that owns the
Service Principal)
1. Object ID (Windows only) - a certificate is used to authenticate WinRM access, and the certificate is injected into
the VM using Azure Key Vault. Access to the key vault is protected by an ACL associated with the SPN's ObjectID.
Linux does not need nor use a key vault, so there's no need to know the ObjectID.
You will also need the following.
1. Subscription ID
1. Resource Group
1. Storage Account
Resource Group is where your storage account is located, and Storage
Account is where the created packer image will be stored.
The Service Principal has been tested with the following [permissions](https://azure.microsoft.com/en-us/documentation/articles/role-based-access-control-configure/).
Please review the document for the [built in roles](https://azure.microsoft.com/en-gb/documentation/articles/role-based-access-built-in-roles/)
for more details.
* Owner
> NOTE: the Owner role is too powerful, and more explicit set of roles
> is TBD. Issue #183 is tracking this work. Permissions can be scoped to
> a specific resource group to further limit access.
### Sample Ubuntu
The following is a sample Packer template for use with the Packer
Azure for ARM builder.
```json
{
"variables": {
"cid": "your_client_id",
"cst": "your_client_secret",
"tid": "your_client_tenant",
"sid": "your_subscription_id",
"rgn": "your_resource_group",
"sa": "your_storage_account"
},
"builders": [
{
"type": "azure-arm",
"client_id": "{{user `cid`}}",
"client_secret": "{{user `cst`}}",
"subscription_id": "{{user `sid`}}",
"tenant_id": "{{user `tid`}}",
"resource_group_name": "{{user `rgn`}}",
"storage_account": "{{user `sa`}}",
"capture_container_name": "images",
"capture_name_prefix": "packer",
"os_type": "Linux",
"image_publisher": "Canonical",
"image_offer": "UbuntuServer",
"image_sku": "14.04.3-LTS",
"location": "South Central US",
"vm_size": "Standard_A2"
}
],
"provisioners": [
{
"execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'",
"inline": [
"apt-get update",
"apt-get upgrade -y",
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
],
"inline_shebang": "/bin/sh -x",
"type": "shell"
}
]
}
```
Using the above template, Packer would be invoked as follows.
> NOTE: the following variables must be **changed** based on your
> subscription. These values are just dummy values, but they match
> format of expected, e.g. if the value is a GUID the sample is a
> GUID.
```bat
packer build^
-var cid="593c4dc4-9cd7-49af-9fe0-1ea5055ac1e4"^
-var cst="GbzJfsfrVkqL/TLfZY8TXA=="^
-var sid="ce323e74-56fc-4bd6-aa18-83b6dc262748"^
-var tid="da3847b4-8e69-40bd-a2c2-41da6982c5e2"^
-var rgn="My Resource Group"^
-var sa="mystorageaccount"^
c:\packer\ubuntu_14_LTS.json
```
Please see the
[config_sameples/arm](https://github.com/Azure/packer-azure/tree/master/config_examples)
directory for more examples of usage.
@@ -1,120 +0,0 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [],
"location": "[variables('location')]",
"name": "[variables('nicName')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"location": "[variables('location')]",
"name": "[parameters('vmName')]",
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"osProfile": {
"adminPassword": "[parameters('adminPassword')]",
"adminUsername": "[parameters('adminUsername')]",
"computerName": "[parameters('vmName')]",
"linuxConfiguration": {
"ssh": {
"publicKeys": [
{
"keyData": "",
"path": "[variables('sshKeyPath')]"
}
]
}
}
},
"storageProfile": {
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"image": {
"uri": "https://localhost/custom.vhd"
},
"name": "osdisk",
"osType": "Linux",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
}
}
}
},
"type": "Microsoft.Compute/virtualMachines"
}
],
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "ignore",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "ignore",
"virtualNetworkResourceGroup": "ignore",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}
+4 -4
View File
@@ -16,7 +16,7 @@ func getFakeSasUrl(name string) string {
func TestArtifactString(t *testing.T) {
template := CaptureTemplate{
Resources: []CaptureResources{
{
CaptureResources{
Properties: CaptureProperties{
StorageProfile: CaptureStorageProfile{
OSDisk: CaptureDisk{
@@ -57,7 +57,7 @@ func TestArtifactString(t *testing.T) {
func TestArtifactProperties(t *testing.T) {
template := CaptureTemplate{
Resources: []CaptureResources{
{
CaptureResources{
Properties: CaptureProperties{
StorageProfile: CaptureStorageProfile{
OSDisk: CaptureDisk{
@@ -97,7 +97,7 @@ func TestArtifactProperties(t *testing.T) {
func TestArtifactOverHypenatedCaptureUri(t *testing.T) {
template := CaptureTemplate{
Resources: []CaptureResources{
{
CaptureResources{
Properties: CaptureProperties{
StorageProfile: CaptureStorageProfile{
OSDisk: CaptureDisk{
@@ -134,7 +134,7 @@ func TestArtifactRejectMalformedTemplates(t *testing.T) {
func TestArtifactRejectMalformedStorageUri(t *testing.T) {
template := CaptureTemplate{
Resources: []CaptureResources{
{
CaptureResources{
Properties: CaptureProperties{
StorageProfile: CaptureStorageProfile{
OSDisk: CaptureDisk{
+5 -1
View File
@@ -26,7 +26,7 @@ func (a *Authenticate) getServicePrincipalToken() (*azure.ServicePrincipalToken,
}
func (a *Authenticate) getServicePrincipalTokenWithResource(resource string) (*azure.ServicePrincipalToken, error) {
oauthConfig, err := a.env.OAuthConfigForTenant(a.tenantID)
oauthConfig, err := newOAuthConfigWithTenant(a.tenantID)
if err != nil {
return nil, err
}
@@ -39,3 +39,7 @@ func (a *Authenticate) getServicePrincipalTokenWithResource(resource string) (*a
return spt, err
}
func newOAuthConfigWithTenant(tenantID string) (*azure.OAuthConfig, error) {
return azure.PublicCloud.OAuthConfigForTenant(tenantID)
}
+7 -41
View File
@@ -8,7 +8,6 @@ import (
"fmt"
"math"
"net/http"
"net/url"
"os"
"strconv"
@@ -36,9 +35,6 @@ type AzureClient struct {
resources.DeploymentsClient
resources.GroupsClient
network.PublicIPAddressesClient
network.InterfacesClient
network.SubnetsClient
network.VirtualNetworksClient
compute.VirtualMachinesClient
common.VaultClient
armStorage.AccountsClient
@@ -106,67 +102,43 @@ func byConcatDecorators(decorators ...autorest.RespondDecorator) autorest.Respon
}
func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string,
cloud *azure.Environment,
servicePrincipalToken, servicePrincipalTokenVault *azure.ServicePrincipalToken) (*AzureClient, error) {
var azureClient = &AzureClient{}
maxlen := getInspectorMaxLength()
azureClient.DeploymentsClient = resources.NewDeploymentsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.DeploymentsClient = resources.NewDeploymentsClient(subscriptionID)
azureClient.DeploymentsClient.Authorizer = servicePrincipalToken
azureClient.DeploymentsClient.RequestInspector = withInspection(maxlen)
azureClient.DeploymentsClient.ResponseInspector = byInspecting(maxlen)
azureClient.DeploymentsClient.UserAgent += packerUserAgent
azureClient.GroupsClient = resources.NewGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.GroupsClient = resources.NewGroupsClient(subscriptionID)
azureClient.GroupsClient.Authorizer = servicePrincipalToken
azureClient.GroupsClient.RequestInspector = withInspection(maxlen)
azureClient.GroupsClient.ResponseInspector = byInspecting(maxlen)
azureClient.GroupsClient.UserAgent += packerUserAgent
azureClient.InterfacesClient = network.NewInterfacesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.InterfacesClient.Authorizer = servicePrincipalToken
azureClient.InterfacesClient.RequestInspector = withInspection(maxlen)
azureClient.InterfacesClient.ResponseInspector = byInspecting(maxlen)
azureClient.InterfacesClient.UserAgent += packerUserAgent
azureClient.SubnetsClient = network.NewSubnetsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.SubnetsClient.Authorizer = servicePrincipalToken
azureClient.SubnetsClient.RequestInspector = withInspection(maxlen)
azureClient.SubnetsClient.ResponseInspector = byInspecting(maxlen)
azureClient.SubnetsClient.UserAgent += packerUserAgent
azureClient.VirtualNetworksClient = network.NewVirtualNetworksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.VirtualNetworksClient.Authorizer = servicePrincipalToken
azureClient.VirtualNetworksClient.RequestInspector = withInspection(maxlen)
azureClient.VirtualNetworksClient.ResponseInspector = byInspecting(maxlen)
azureClient.VirtualNetworksClient.UserAgent += packerUserAgent
azureClient.PublicIPAddressesClient = network.NewPublicIPAddressesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.PublicIPAddressesClient = network.NewPublicIPAddressesClient(subscriptionID)
azureClient.PublicIPAddressesClient.Authorizer = servicePrincipalToken
azureClient.PublicIPAddressesClient.RequestInspector = withInspection(maxlen)
azureClient.PublicIPAddressesClient.ResponseInspector = byInspecting(maxlen)
azureClient.PublicIPAddressesClient.UserAgent += packerUserAgent
azureClient.VirtualMachinesClient = compute.NewVirtualMachinesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.VirtualMachinesClient = compute.NewVirtualMachinesClient(subscriptionID)
azureClient.VirtualMachinesClient.Authorizer = servicePrincipalToken
azureClient.VirtualMachinesClient.RequestInspector = withInspection(maxlen)
azureClient.VirtualMachinesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), templateCapture(azureClient))
azureClient.VirtualMachinesClient.UserAgent += packerUserAgent
azureClient.AccountsClient = armStorage.NewAccountsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.AccountsClient = armStorage.NewAccountsClient(subscriptionID)
azureClient.AccountsClient.Authorizer = servicePrincipalToken
azureClient.AccountsClient.RequestInspector = withInspection(maxlen)
azureClient.AccountsClient.ResponseInspector = byInspecting(maxlen)
azureClient.AccountsClient.UserAgent += packerUserAgent
keyVaultURL, err := url.Parse(cloud.KeyVaultEndpoint)
if err != nil {
return nil, err
}
azureClient.VaultClient = common.NewVaultClient(*keyVaultURL)
azureClient.VaultClient = common.VaultClient{}
azureClient.VaultClient.Authorizer = servicePrincipalTokenVault
azureClient.VaultClient.RequestInspector = withInspection(maxlen)
azureClient.VaultClient.ResponseInspector = byInspecting(maxlen)
@@ -177,13 +149,7 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
return nil, err
}
storageClient, err := storage.NewClient(
storageAccountName,
*(*accountKeys.Keys)[0].Value,
cloud.StorageEndpointSuffix,
storage.DefaultAPIVersion,
true /*useHttps*/)
storageClient, err := storage.NewBasicClient(storageAccountName, *accountKeys.Key1)
if err != nil {
return nil, err
}
+27 -37
View File
@@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"log"
"strings"
"time"
packerAzureCommon "github.com/mitchellh/packer/builder/azure/common"
@@ -30,7 +29,6 @@ type Builder struct {
}
const (
DefaultNicName = "packerNic"
DefaultPublicIPAddressName = "packerPublicIP"
DefaultSasBlobContainer = "system/Microsoft.Compute"
DefaultSasBlobPermission = "r"
@@ -53,11 +51,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
ui.Say("Running builder ...")
if err := newConfigRetriever().FillParameters(b.config); err != nil {
return nil, err
}
ui.Say("Preparing builder ...")
log.Print(":: Configuration")
packerAzureCommon.DumpConfig(b.config, func(s string) { log.Print(s) })
@@ -75,7 +69,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.config.SubscriptionID,
b.config.ResourceGroupName,
b.config.StorageAccount,
b.config.cloudEnvironment,
spnCloud,
spnKeyVault)
@@ -83,30 +76,21 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
return nil, err
}
resolver := newResourceResolver(azureClient)
if err := resolver.Resolve(b.config); err != nil {
return nil, err
}
b.config.storageAccountBlobEndpoint, err = b.getBlobEndpoint(azureClient, b.config.ResourceGroupName, b.config.StorageAccount)
if err != nil {
return nil, err
}
endpointConnectType := PublicEndpoint
if b.isPrivateNetworkCommunication() {
endpointConnectType = PrivateEndpoint
}
b.setTemplateParameters(b.stateBag)
var steps []multistep.Step
if b.config.OSType == constants.Target_Linux {
steps = []multistep.Step{
NewStepCreateResourceGroup(azureClient, ui),
NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
NewStepDeployTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
NewStepGetIPAddress(azureClient, ui, endpointConnectType),
NewStepValidateTemplate(azureClient, ui, Linux),
NewStepDeployTemplate(azureClient, ui, Linux),
NewStepGetIPAddress(azureClient, ui),
&communicator.StepConnectSSH{
Config: &b.config.Comm,
Host: lin.SSHHost,
@@ -122,13 +106,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} else if b.config.OSType == constants.Target_Windows {
steps = []multistep.Step{
NewStepCreateResourceGroup(azureClient, ui),
NewStepValidateTemplate(azureClient, ui, b.config, GetKeyVaultDeployment),
NewStepDeployTemplate(azureClient, ui, b.config, GetKeyVaultDeployment),
NewStepValidateTemplate(azureClient, ui, KeyVault),
NewStepDeployTemplate(azureClient, ui, KeyVault),
NewStepGetCertificate(azureClient, ui),
NewStepSetCertificate(b.config, ui),
NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
NewStepDeployTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
NewStepGetIPAddress(azureClient, ui, endpointConnectType),
NewStepValidateTemplate(azureClient, ui, Windows),
NewStepDeployTemplate(azureClient, ui, Windows),
NewStepGetIPAddress(azureClient, ui),
&communicator.StepConnectWinRM{
Config: &b.config.Comm,
Host: func(stateBag multistep.StateBag) (string, error) {
@@ -157,7 +141,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ui.Message(fmt.Sprintf("temp admin password: '%s'", b.config.Password))
}
b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
b.runner = b.createRunner(&steps, ui)
b.runner.Run(b.stateBag)
// Report any errors.
@@ -187,10 +171,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
return &Artifact{}, nil
}
func (b *Builder) isPrivateNetworkCommunication() bool {
return b.config.VirtualNetworkName != ""
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
@@ -198,6 +178,19 @@ func (b *Builder) Cancel() {
}
}
func (b *Builder) createRunner(steps *[]multistep.Step, ui packer.Ui) multistep.Runner {
if b.config.PackerDebug {
return &multistep.DebugRunner{
Steps: *steps,
PauseFn: packerCommon.MultistepDebugFn(ui),
}
}
return &multistep.BasicRunner{
Steps: *steps,
}
}
func (b *Builder) getBlobEndpoint(client *AzureClient, resourceGroupName string, storageAccountName string) (string, error) {
account, err := client.AccountsClient.GetProperties(resourceGroupName, storageAccountName)
if err != nil {
@@ -211,18 +204,17 @@ func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
stateBag.Put(constants.AuthorizedKey, b.config.sshAuthorizedKey)
stateBag.Put(constants.PrivateKey, b.config.sshPrivateKey)
stateBag.Put(constants.ArmTags, &b.config.AzureTags)
stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
stateBag.Put(constants.ArmKeyVaultName, b.config.tmpKeyVaultName)
stateBag.Put(constants.ArmLocation, b.config.Location)
stateBag.Put(constants.ArmNicName, DefaultNicName)
stateBag.Put(constants.ArmPublicIPAddressName, DefaultPublicIPAddressName)
stateBag.Put(constants.ArmResourceGroupName, b.config.tmpResourceGroupName)
stateBag.Put(constants.ArmStorageAccountName, b.config.StorageAccount)
}
func (b *Builder) setTemplateParameters(stateBag multistep.StateBag) {
stateBag.Put(constants.ArmTemplateParameters, b.config.toTemplateParameters())
stateBag.Put(constants.ArmVirtualMachineCaptureParameters, b.config.toVirtualMachineCaptureParameters())
}
@@ -233,7 +225,7 @@ func (b *Builder) getServicePrincipalTokens(say func(string)) (*azure.ServicePri
var err error
if b.config.useDeviceLogin {
servicePrincipalToken, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.TenantID, say)
servicePrincipalToken, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.SubscriptionID, say)
if err != nil {
return nil, nil, err
}
@@ -245,9 +237,7 @@ func (b *Builder) getServicePrincipalTokens(say func(string)) (*azure.ServicePri
return nil, nil, err
}
servicePrincipalTokenVault, err = auth.getServicePrincipalTokenWithResource(
strings.TrimRight(b.config.cloudEnvironment.KeyVaultEndpoint, "/"))
servicePrincipalTokenVault, err = auth.getServicePrincipalTokenWithResource(packerAzureCommon.AzureVaultScope)
if err != nil {
return nil, nil, err
}
+2 -4
View File
@@ -4,9 +4,8 @@
package arm
import (
"testing"
"github.com/mitchellh/packer/builder/azure/common/constants"
"testing"
)
func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
@@ -20,13 +19,12 @@ func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
constants.AuthorizedKey,
constants.PrivateKey,
constants.ArmTags,
constants.ArmComputeName,
constants.ArmDeploymentName,
constants.ArmLocation,
constants.ArmNicName,
constants.ArmResourceGroupName,
constants.ArmStorageAccountName,
constants.ArmTemplateParameters,
constants.ArmVirtualMachineCaptureParameters,
constants.ArmPublicIPAddressName,
}
+76 -155
View File
@@ -14,24 +14,23 @@ import (
"io/ioutil"
"math/big"
"net/http"
"regexp"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"github.com/Azure/go-ntlmssp"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/packer/builder/azure/pkcs12"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
"github.com/Azure/go-autorest/autorest/azure"
"golang.org/x/crypto/ssh"
"strings"
)
const (
@@ -41,11 +40,6 @@ const (
DefaultVMSize = "Standard_A1"
)
var (
reCaptureContainerName = regexp.MustCompile("^[a-z0-9][a-z0-9\\-]{2,62}$")
reCaptureNamePrefix = regexp.MustCompile("^[A-Za-z0-9][A-Za-z0-9_\\-\\.]{0,23}$")
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
@@ -65,26 +59,18 @@ type Config struct {
ImageOffer string `mapstructure:"image_offer"`
ImageSku string `mapstructure:"image_sku"`
ImageVersion string `mapstructure:"image_version"`
ImageUrl string `mapstructure:"image_url"`
Location string `mapstructure:"location"`
VMSize string `mapstructure:"vm_size"`
// Deployment
AzureTags map[string]*string `mapstructure:"azure_tags"`
ResourceGroupName string `mapstructure:"resource_group_name"`
StorageAccount string `mapstructure:"storage_account"`
storageAccountBlobEndpoint string
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
cloudEnvironment *azure.Environment
VirtualNetworkName string `mapstructure:"virtual_network_name"`
VirtualNetworkSubnetName string `mapstructure:"virtual_network_subnet_name"`
VirtualNetworkResourceGroupName string `mapstructure:"virtual_network_resource_group_name"`
CustomDataFile string `mapstructure:"custom_data_file"`
customData string
ResourceGroupName string `mapstructure:"resource_group_name"`
StorageAccount string `mapstructure:"storage_account"`
storageAccountBlobEndpoint string
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
cloudEnvironment *azure.Environment
// OS
OSType string `mapstructure:"os_type"`
OSDiskSizeGB int32 `mapstructure:"os_disk_size_gb"`
OSType string `mapstructure:"os_type"`
// Runtime Values
UserName string
@@ -117,6 +103,38 @@ type keyVaultCertificate struct {
Password string `json:"password,omitempty"`
}
// If we ever feel the need to support more templates consider moving this
// method to its own factory class.
func (c *Config) toTemplateParameters() *TemplateParameters {
templateParameters := &TemplateParameters{
AdminUsername: &TemplateParameter{c.UserName},
AdminPassword: &TemplateParameter{c.Password},
DnsNameForPublicIP: &TemplateParameter{c.tmpComputeName},
ImageOffer: &TemplateParameter{c.ImageOffer},
ImagePublisher: &TemplateParameter{c.ImagePublisher},
ImageSku: &TemplateParameter{c.ImageSku},
ImageVersion: &TemplateParameter{c.ImageVersion},
OSDiskName: &TemplateParameter{c.tmpOSDiskName},
StorageAccountBlobEndpoint: &TemplateParameter{c.storageAccountBlobEndpoint},
VMSize: &TemplateParameter{c.VMSize},
VMName: &TemplateParameter{c.tmpComputeName},
}
switch c.OSType {
case constants.Target_Linux:
templateParameters.SshAuthorizedKey = &TemplateParameter{c.sshAuthorizedKey}
case constants.Target_Windows:
templateParameters.TenantId = &TemplateParameter{c.TenantID}
templateParameters.ObjectId = &TemplateParameter{c.ObjectID}
templateParameters.KeyVaultName = &TemplateParameter{c.tmpKeyVaultName}
templateParameters.KeyVaultSecretValue = &TemplateParameter{c.winrmCertificate}
templateParameters.WinRMCertificateUrl = &TemplateParameter{c.tmpWinRMCertificateUrl}
}
return templateParameters
}
func (c *Config) toVirtualMachineCaptureParameters() *compute.VirtualMachineCaptureParameters {
return &compute.VirtualMachineCaptureParameters{
DestinationContainerName: &c.CaptureContainerName,
@@ -128,17 +146,17 @@ func (c *Config) toVirtualMachineCaptureParameters() *compute.VirtualMachineCapt
func (c *Config) createCertificate() (string, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
err = fmt.Errorf("Failed to Generate Private Key: %s", err)
err := fmt.Errorf("Failed to Generate Private Key: %s", err)
return "", err
}
host := fmt.Sprintf("%s.cloudapp.net", c.tmpComputeName)
notBefore := time.Now()
notAfter := notBefore.Add(24 * time.Hour)
notAfter := notBefore.Add(365 * 24 * time.Hour)
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
err = fmt.Errorf("Failed to Generate Serial Number: %v", err)
err := fmt.Errorf("Failed to Generate Serial Number: %v", err)
return "", err
}
@@ -205,33 +223,20 @@ func newConfig(raws ...interface{}) (*Config, []string, error) {
return nil, nil, err
}
err = setCustomData(&c)
err = setSshValues(&c)
if err != nil {
return nil, nil, err
}
// NOTE: if the user did not specify a communicator, then default to both
// SSH and WinRM. This is for backwards compatibility because the code did
// not specifically force the user to set a communicator.
if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "ssh") {
err = setSshValues(&c)
if err != nil {
return nil, nil, err
}
}
if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "winrm") {
err = setWinRMCertificate(&c)
if err != nil {
return nil, nil, err
}
err = setWinRMCertificate(&c)
if err != nil {
return nil, nil, err
}
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(c.ctx)...)
assertRequiredParametersSet(&c, errs)
assertTagProperties(&c, errs)
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}
@@ -271,14 +276,14 @@ func setSshValues(c *Config) error {
c.sshPrivateKey = sshKeyPair.PrivateKey()
}
return nil
}
func setWinRMCertificate(c *Config) error {
c.Comm.WinRMTransportDecorator = func(t *http.Transport) http.RoundTripper {
return &ntlmssp.Negotiator{RoundTripper: t}
}
return nil
}
func setWinRMCertificate(c *Config) error {
cert, err := c.createCertificate()
c.winrmCertificate = cert
@@ -312,50 +317,18 @@ func setUserNamePassword(c *Config) {
}
func setCloudEnvironment(c *Config) error {
lookup := map[string]string{
"CHINA": "AzureChinaCloud",
"CHINACLOUD": "AzureChinaCloud",
"AZURECHINACLOUD": "AzureChinaCloud",
"GERMAN": "AzureGermanCloud",
"GERMANCLOUD": "AzureGermanCloud",
"AZUREGERMANCLOUD": "AzureGermanCloud",
"GERMANY": "AzureGermanCloud",
"GERMANYCLOUD": "AzureGermanCloud",
"AZUREGERMANYCLOUD": "AzureGermanCloud",
"PUBLIC": "AzurePublicCloud",
"PUBLICCLOUD": "AzurePublicCloud",
"AZUREPUBLICCLOUD": "AzurePublicCloud",
"USGOVERNMENT": "AzureUSGovernmentCloud",
"USGOVERNMENTCLOUD": "AzureUSGovernmentCloud",
"AZUREUSGOVERNMENTCLOUD": "AzureUSGovernmentCloud",
}
name := strings.ToUpper(c.CloudEnvironmentName)
envName, ok := lookup[name]
if !ok {
switch name {
case "CHINA", "CHINACLOUD", "AZURECHINACLOUD":
c.cloudEnvironment = &azure.ChinaCloud
case "PUBLIC", "PUBLICCLOUD", "AZUREPUBLICCLOUD":
c.cloudEnvironment = &azure.PublicCloud
case "USGOVERNMENT", "USGOVERNMENTCLOUD", "AZUREUSGOVERNMENTCLOUD":
c.cloudEnvironment = &azure.USGovernmentCloud
default:
return fmt.Errorf("There is no cloud envionment matching the name '%s'!", c.CloudEnvironmentName)
}
env, err := azure.EnvironmentFromName(envName)
c.cloudEnvironment = &env
return err
}
func setCustomData(c *Config) error {
if c.CustomDataFile == "" {
return nil
}
b, err := ioutil.ReadFile(c.CustomDataFile)
if err != nil {
return err
}
c.customData = base64.StdEncoding.EncodeToString(b)
return nil
}
@@ -364,7 +337,7 @@ func provideDefaultValues(c *Config) {
c.VMSize = DefaultVMSize
}
if c.ImageUrl == "" && c.ImageVersion == "" {
if c.ImageVersion == "" {
c.ImageVersion = DefaultImageVersion
}
@@ -373,21 +346,6 @@ func provideDefaultValues(c *Config) {
}
}
func assertTagProperties(c *Config, errs *packer.MultiError) {
if len(c.AzureTags) > 15 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("a max of 15 tags are supported, but %d were provided", len(c.AzureTags)))
}
for k, v := range c.AzureTags {
if len(k) > 512 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 512 character limit", k, len(k)))
}
if len(*v) > 256 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", v, len(*v)))
}
}
}
func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
/////////////////////////////////////////////
// Authentication via OAUTH
@@ -422,6 +380,10 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_secret must be specified"))
}
if c.TenantID == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A tenant_id must be specified"))
}
if c.SubscriptionID == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A subscription_id must be specified"))
}
@@ -430,51 +392,25 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
/////////////////////////////////////////////
// Capture
if c.CaptureContainerName == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must be specified"))
}
if !reCaptureContainerName.MatchString(c.CaptureContainerName) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must satisfy the regular expression %q.", reCaptureContainerName.String()))
}
if strings.HasSuffix(c.CaptureContainerName, "-") {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not end with a hyphen, e.g. '-'."))
}
if strings.Contains(c.CaptureContainerName, "--") {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not contain consecutive hyphens, e.g. '--'."))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An capture_container_name must be specified"))
}
if c.CaptureNamePrefix == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must be specified"))
}
if !reCaptureNamePrefix.MatchString(c.CaptureNamePrefix) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must satisfy the regular expression %q.", reCaptureNamePrefix.String()))
}
if strings.HasSuffix(c.CaptureNamePrefix, "-") || strings.HasSuffix(c.CaptureNamePrefix, ".") {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must not end with a hyphen or period."))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An capture_name_prefix must be specified"))
}
/////////////////////////////////////////////
// Compute
if c.ImageUrl == "" {
if c.ImagePublisher == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_publisher must be specified"))
}
if c.ImagePublisher == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A image_publisher must be specified"))
}
if c.ImageOffer == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_offer must be specified"))
}
if c.ImageOffer == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A image_offer must be specified"))
}
if c.ImageSku == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_sku must be specified"))
}
} else {
if c.ImagePublisher != "" || c.ImageOffer != "" || c.ImageSku != "" || c.ImageVersion != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_url must not be specified if image_publisher, image_offer, image_sku, or image_version is specified"))
}
if c.ImageSku == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A image_sku must be specified"))
}
if c.Location == "" {
@@ -486,25 +422,10 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
if c.StorageAccount == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A storage_account must be specified"))
}
if c.ResourceGroupName == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A resource_group_name must be specified"))
}
if c.VirtualNetworkName == "" && c.VirtualNetworkResourceGroupName != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_resource_group_name is specified, so must virtual_network_name"))
}
if c.VirtualNetworkName == "" && c.VirtualNetworkSubnetName != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_subnet_name is specified, so must virtual_network_name"))
}
/////////////////////////////////////////////
// OS
if strings.EqualFold(c.OSType, constants.Target_Linux) {
c.OSType = constants.Target_Linux
} else if strings.EqualFold(c.OSType, constants.Target_Windows) {
c.OSType = constants.Target_Windows
} else if c.OSType == "" {
if c.OSType != constants.Target_Linux && c.OSType != constants.Target_Windows {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An os_type must be specified"))
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The os_type %q is invalid", c.OSType))
}
}
-36
View File
@@ -1,36 +0,0 @@
package arm
// Method to resolve information about the user so that a client can be
// constructed to communicated with Azure.
//
// The following data are resolved.
//
// 1. TenantID
import (
"github.com/Azure/go-autorest/autorest/azure"
"github.com/mitchellh/packer/builder/azure/common"
)
type configRetriever struct {
// test seams
findTenantID func(azure.Environment, string) (string, error)
}
func newConfigRetriever() configRetriever {
return configRetriever{
common.FindTenantID,
}
}
func (cr configRetriever) FillParameters(c *Config) error {
if c.TenantID == "" {
tenantID, err := cr.findTenantID(*c.cloudEnvironment, c.SubscriptionID)
if err != nil {
return err
}
c.TenantID = tenantID
}
return nil
}
@@ -1,62 +0,0 @@
package arm
import (
"errors"
"testing"
"github.com/Azure/go-autorest/autorest/azure"
)
func TestConfigRetrieverFillsTenantIDWhenEmpty(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
if expected := ""; c.TenantID != expected {
t.Errorf("Expected TenantID to be %q but got %q", expected, c.TenantID)
}
sut := newTestConfigRetriever()
retrievedTid := "my-tenant-id"
sut.findTenantID = func(azure.Environment, string) (string, error) { return retrievedTid, nil }
if err := sut.FillParameters(c); err != nil {
t.Errorf("Unexpected error when calling sut.FillParameters: %v", err)
}
if expected := retrievedTid; c.TenantID != expected {
t.Errorf("Expected TenantID to be %q but got %q", expected, c.TenantID)
}
}
func TestConfigRetrieverLeavesTenantIDWhenNotEmpty(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
userSpecifiedTid := "not-empty"
c.TenantID = userSpecifiedTid
sut := newTestConfigRetriever()
sut.findTenantID = nil // assert that this not even called
if err := sut.FillParameters(c); err != nil {
t.Errorf("Unexpected error when calling sut.FillParameters: %v", err)
}
if expected := userSpecifiedTid; c.TenantID != expected {
t.Errorf("Expected TenantID to be %q but got %q", expected, c.TenantID)
}
}
func TestConfigRetrieverReturnsErrorWhenTenantIDEmptyAndRetrievalFails(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
if expected := ""; c.TenantID != expected {
t.Errorf("Expected TenantID to be %q but got %q", expected, c.TenantID)
}
sut := newTestConfigRetriever()
errorString := "sorry, I failed"
sut.findTenantID = func(azure.Environment, string) (string, error) { return "", errors.New(errorString) }
if err := sut.FillParameters(c); err != nil && err.Error() != errorString {
t.Errorf("Unexpected error when calling sut.FillParameters: %v", err)
}
}
func newTestConfigRetriever() configRetriever {
return configRetriever{
findTenantID: func(azure.Environment, string) (string, error) { return "findTenantID is mocked", nil },
}
}
+114 -479
View File
@@ -4,6 +4,7 @@
package arm
import (
"encoding/json"
"fmt"
"strings"
"testing"
@@ -25,24 +26,24 @@ var requiredConfigValues = []string{
"location",
"os_type",
"storage_account",
"resource_group_name",
"subscription_id",
"tenant_id",
}
func TestConfigShouldProvideReasonableDefaultValues(t *testing.T) {
c, _, err := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
if err != nil {
t.Error("Expected configuration creation to succeed, but it failed!\n")
t.Errorf("Expected configuration creation to succeed, but it failed!\n")
t.Fatalf(" errors: %s\n", err)
}
if c.UserName == "" {
t.Error("Expected 'UserName' to be populated, but it was empty!")
t.Errorf("Expected 'UserName' to be populated, but it was empty!")
}
if c.VMSize == "" {
t.Error("Expected 'VMSize' to be populated, but it was empty!")
t.Errorf("Expected 'VMSize' to be populated, but it was empty!")
}
if c.ObjectID != "" {
@@ -55,7 +56,6 @@ func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) {
builderValues["ssh_password"] = "override_password"
builderValues["ssh_username"] = "override_username"
builderValues["vm_size"] = "override_vm_size"
builderValues["communicator"] = "ssh"
c, _, err := newConfig(builderValues, getPackerConfiguration())
@@ -100,162 +100,6 @@ func TestConfigShouldDefaultImageVersionToLatest(t *testing.T) {
}
}
func TestConfigShouldNotDefaultImageVersionIfCustomImage(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
}
c, _, _ := newConfig(config, getPackerConfiguration())
if c.ImageVersion != "" {
t.Errorf("Expected 'ImageVersion' to empty, but got '%s'.", c.ImageVersion)
}
}
func TestConfigShouldNormalizeOSTypeCase(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
}
os_types := map[string][]string{
constants.Target_Linux: {"linux", "LiNuX"},
constants.Target_Windows: {"windows", "WiNdOWs"},
}
for k, v := range os_types {
for _, os_type := range v {
config["os_type"] = os_type
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatalf("Expected config to accept the value %q, but it did not", os_type)
}
if c.OSType != k {
t.Fatalf("Expected config to normalize the value %q to %q, but it did not", os_type, constants.Target_Linux)
}
}
}
bad_os_types := []string{"", "does-not-exist"}
for _, os_type := range bad_os_types {
config["os_type"] = os_type
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Fatalf("Expected config to not accept the value %q, but it did", os_type)
}
}
}
func TestConfigShouldRejectCustomImageAndMarketPlace(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "ignore",
"resource_group_name": "ignore",
"storage_account": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
}
packerConfiguration := getPackerConfiguration()
marketPlace := []string{"image_publisher", "image_offer", "image_sku"}
for _, x := range marketPlace {
config[x] = "ignore"
_, _, err := newConfig(config, packerConfiguration)
if err == nil {
t.Errorf("Expected Config to reject image_url and %s, but it did not", x)
}
}
}
func TestConfigVirtualNetworkNameIsOptional(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
"virtual_network_name": "MyVirtualNetwork",
}
c, _, _ := newConfig(config, getPackerConfiguration())
if c.VirtualNetworkName != "MyVirtualNetwork" {
t.Errorf("Expected Config to set virtual_network_name to MyVirtualNetwork, but got %q", c.VirtualNetworkName)
}
if c.VirtualNetworkResourceGroupName != "" {
t.Errorf("Expected Config to leave virtual_network_resource_group_name to '', but got %q", c.VirtualNetworkResourceGroupName)
}
if c.VirtualNetworkSubnetName != "" {
t.Errorf("Expected Config to leave virtual_network_subnet_name to '', but got %q", c.VirtualNetworkSubnetName)
}
}
// The user can pass the value virtual_network_resource_group_name to avoid the lookup of
// a virtual network's resource group, or to help with disambiguation. The value should
// only be set if virtual_network_name was set.
func TestConfigVirtualNetworkResourceGroupNameMustBeSetWithVirtualNetworkName(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
"virtual_network_resource_group_name": "MyVirtualNetworkRG",
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Error("Expected Config to reject virtual_network_resource_group_name, if virtual_network_name is not set.")
}
}
// The user can pass the value virtual_network_subnet_name to avoid the lookup of
// a virtual network subnet's name, or to help with disambiguation. The value should
// only be set if virtual_network_name was set.
func TestConfigVirtualNetworkSubnetNameMustBeSetWithVirtualNetworkName(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
"virtual_network_subnet_name": "MyVirtualNetworkRG",
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Error("Expected Config to reject virtual_network_subnet_name, if virtual_network_name is not set.")
}
}
func TestConfigShouldDefaultToPublicCloud(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
@@ -277,10 +121,8 @@ func TestConfigInstantiatesCorrectAzureEnvironment(t *testing.T) {
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
}
// user input is fun :)
@@ -308,10 +150,7 @@ func TestConfigInstantiatesCorrectAzureEnvironment(t *testing.T) {
for _, x := range table {
config["cloud_environment_name"] = x.name
c, _, err := newConfig(config, packerConfiguration)
if err != nil {
t.Fatal(err)
}
c, _, _ := newConfig(config, packerConfiguration)
if c.cloudEnvironment == nil || c.cloudEnvironment.Name != x.environmentName {
t.Errorf("Expected 'cloudEnvironment' to be set to '%s', but got '%s'.", x.environmentName, c.cloudEnvironment)
@@ -325,7 +164,7 @@ func TestUserShouldProvideRequiredValues(t *testing.T) {
// Ensure we can successfully create a config.
_, _, err := newConfig(builderValues, getPackerConfiguration())
if err != nil {
t.Error("Expected configuration creation to succeed, but it failed!\n")
t.Errorf("Expected configuration creation to succeed, but it failed!\n")
t.Fatalf(" -> %+v\n", builderValues)
}
@@ -336,7 +175,7 @@ func TestUserShouldProvideRequiredValues(t *testing.T) {
_, _, err := newConfig(builderValues, getPackerConfiguration())
if err == nil {
t.Error("Expected configuration creation to fail, but it succeeded!\n")
t.Errorf("Expected configuration creation to fail, but it succeeded!\n")
t.Fatalf(" -> %+v\n", builderValues)
}
@@ -368,6 +207,91 @@ func TestSystemShouldDefineRuntimeValues(t *testing.T) {
}
}
func TestConfigShouldTransformToTemplateParameters(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
templateParameters := c.toTemplateParameters()
if templateParameters.AdminUsername.Value != c.UserName {
t.Errorf("Expected AdminUsername to be equal to config's AdminUsername, but they were '%s' and '%s' respectively.", templateParameters.AdminUsername.Value, c.UserName)
}
if templateParameters.DnsNameForPublicIP.Value != c.tmpComputeName {
t.Errorf("Expected DnsNameForPublicIP to be equal to config's DnsNameForPublicIP, but they were '%s' and '%s' respectively.", templateParameters.DnsNameForPublicIP.Value, c.tmpComputeName)
}
if templateParameters.ImageOffer.Value != c.ImageOffer {
t.Errorf("Expected ImageOffer to be equal to config's ImageOffer, but they were '%s' and '%s' respectively.", templateParameters.ImageOffer.Value, c.ImageOffer)
}
if templateParameters.ImagePublisher.Value != c.ImagePublisher {
t.Errorf("Expected ImagePublisher to be equal to config's ImagePublisher, but they were '%s' and '%s' respectively.", templateParameters.ImagePublisher.Value, c.ImagePublisher)
}
if templateParameters.ImageSku.Value != c.ImageSku {
t.Errorf("Expected ImageSku to be equal to config's ImageSku, but they were '%s' and '%s' respectively.", templateParameters.ImageSku.Value, c.ImageSku)
}
if templateParameters.OSDiskName.Value != c.tmpOSDiskName {
t.Errorf("Expected OSDiskName to be equal to config's OSDiskName, but they were '%s' and '%s' respectively.", templateParameters.OSDiskName.Value, c.tmpOSDiskName)
}
if templateParameters.StorageAccountBlobEndpoint.Value != c.storageAccountBlobEndpoint {
t.Errorf("Expected StorageAccountBlobEndpoint to be equal to config's storageAccountBlobEndpoint, but they were '%s' and '%s' respectively.", templateParameters.StorageAccountBlobEndpoint.Value, c.storageAccountBlobEndpoint)
}
if templateParameters.VMName.Value != c.tmpComputeName {
t.Errorf("Expected VMName to be equal to config's VMName, but they were '%s' and '%s' respectively.", templateParameters.VMName.Value, c.tmpComputeName)
}
if templateParameters.VMSize.Value != c.VMSize {
t.Errorf("Expected VMSize to be equal to config's VMSize, but they were '%s' and '%s' respectively.", templateParameters.VMSize.Value, c.VMSize)
}
}
func TestConfigShouldTransformToTemplateParametersLinux(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
c.OSType = constants.Target_Linux
templateParameters := c.toTemplateParameters()
if templateParameters.KeyVaultSecretValue != nil {
t.Errorf("Expected KeyVaultSecretValue to be empty for an os_type == '%s', but it was not.", c.OSType)
}
if templateParameters.ObjectId != nil {
t.Errorf("Expected ObjectId to be empty for an os_type == '%s', but it was not.", c.OSType)
}
if templateParameters.TenantId != nil {
t.Errorf("Expected TenantId to be empty for an os_type == '%s', but it was not.", c.OSType)
}
}
func TestConfigShouldTransformToTemplateParametersWindows(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
c.OSType = constants.Target_Windows
templateParameters := c.toTemplateParameters()
if templateParameters.SshAuthorizedKey != nil {
t.Errorf("Expected SshAuthorizedKey to be empty for an os_type == '%s', but it was not.", c.OSType)
}
if templateParameters.KeyVaultName == nil {
t.Errorf("Expected KeyVaultName to not be empty for an os_type == '%s', but it was not.", c.OSType)
}
if templateParameters.KeyVaultSecretValue == nil {
t.Errorf("Expected KeyVaultSecretValue to not be empty for an os_type == '%s', but it was not.", c.OSType)
}
if templateParameters.ObjectId == nil {
t.Errorf("Expected ObjectId to not be empty for an os_type == '%s', but it was not.", c.OSType)
}
if templateParameters.TenantId == nil {
t.Errorf("Expected TenantId to not be empty for an os_type == '%s', but it was not.", c.OSType)
}
}
func TestConfigShouldTransformToVirtualMachineCaptureParameters(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
parameters := c.toVirtualMachineCaptureParameters()
@@ -404,22 +328,6 @@ func TestConfigShouldSupportPackersConfigElements(t *testing.T) {
}
}
func TestWinRMConfigShouldSetRoundTripDecorator(t *testing.T) {
config := getArmBuilderConfiguration()
config["communicator"] = "winrm"
config["winrm_username"] = "username"
config["winrm_password"] = "password"
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
if c.Comm.WinRMTransportDecorator == nil {
t.Error("Expected WinRMTransportDecorator to be set, but it was nil")
}
}
func TestUserDeviceLoginIsEnabledForLinux(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
@@ -429,10 +337,8 @@ func TestUserDeviceLoginIsEnabledForLinux(t *testing.T) {
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
}
_, _, err := newConfig(config, getPackerConfiguration())
@@ -450,326 +356,55 @@ func TestUseDeviceLoginIsDisabledForWindows(t *testing.T) {
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Windows,
"communicator": "none",
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Fatal("Expected test to fail, but it succeeded")
t.Fatalf("Expected test to fail, but it succeeded")
}
multiError, _ := err.(*packer.MultiError)
if len(multiError.Errors) != 2 {
t.Errorf("Expected to find 2 errors, but found %d errors", len(multiError.Errors))
if len(multiError.Errors) != 3 {
t.Errorf("Expected to find 3 errors, but found %d errors", len(multiError.Errors))
}
if !strings.Contains(err.Error(), "client_id must be specified") {
t.Error("Expected to find error for 'client_id must be specified")
t.Errorf("Expected to find error for 'client_id must be specified")
}
if !strings.Contains(err.Error(), "client_secret must be specified") {
t.Error("Expected to find error for 'client_secret must be specified")
t.Errorf("Expected to find error for 'client_secret must be specified")
}
}
func TestConfigShouldRejectMalformedCaptureNamePrefix(t *testing.T) {
config := map[string]string{
"capture_container_name": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
}
wellFormedCaptureNamePrefix := []string{
"packer",
"AbcdefghijklmnopqrstuvwX",
"hypen-hypen",
"0leading-number",
"v1.core.local",
}
for _, x := range wellFormedCaptureNamePrefix {
config["capture_name_prefix"] = x
_, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Errorf("Expected test to pass, but it failed with the well-formed capture_name_prefix set to %q.", x)
}
}
malformedCaptureNamePrefix := []string{
"-leading-hypen",
"trailing-hypen-",
"trailing-period.",
"_leading-underscore",
"punc-!@#$%^&*()_+-=-punc",
"There-are-too-many-characters-in-the-name-and-the-limit-is-twenty-four",
}
for _, x := range malformedCaptureNamePrefix {
config["capture_name_prefix"] = x
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Errorf("Expected test to fail, but it succeeded with the malformed capture_name_prefix set to %q.", x)
}
}
}
func TestConfigShouldRejectMalformedCaptureContainerName(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
}
wellFormedCaptureContainerName := []string{
"0leading",
"aleading",
"hype-hypen",
"abcdefghijklmnopqrstuvwxyz0123456789-abcdefghijklmnopqrstuvwxyz", // 63 characters
}
for _, x := range wellFormedCaptureContainerName {
config["capture_container_name"] = x
_, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Errorf("Expected test to pass, but it failed with the well-formed capture_container_name set to %q.", x)
}
}
malformedCaptureContainerName := []string{
"No-Capitals",
"double--hypens",
"-leading-hypen",
"trailing-hypen-",
"punc-!@#$%^&*()_+-=-punc",
"there-are-over-63-characters-in-this-string-and-that-is-a-bad-container-name",
}
for _, x := range malformedCaptureContainerName {
config["capture_container_name"] = x
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Errorf("Expected test to fail, but it succeeded with the malformed capture_container_name set to %q.", x)
}
}
}
func TestConfigShouldAcceptTags(t *testing.T) {
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
"azure_tags": map[string]string{
"tag01": "value01",
"tag02": "value02",
},
}
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
if len(c.AzureTags) != 2 {
t.Fatalf("expected to find 2 tags, but got %d", len(c.AzureTags))
}
if _, ok := c.AzureTags["tag01"]; !ok {
t.Error("expected to find key=\"tag01\", but did not")
}
if _, ok := c.AzureTags["tag02"]; !ok {
t.Error("expected to find key=\"tag02\", but did not")
}
value := c.AzureTags["tag01"]
if *value != "value01" {
t.Errorf("expected AzureTags[\"tag01\"] to have value \"value01\", but got %q", value)
}
value = c.AzureTags["tag02"]
if *value != "value02" {
t.Errorf("expected AzureTags[\"tag02\"] to have value \"value02\", but got %q", value)
}
}
func TestConfigShouldRejectTagsInExcessOf15AcceptTags(t *testing.T) {
tooManyTags := map[string]string{}
for i := 0; i < 16; i++ {
tooManyTags[fmt.Sprintf("tag%.2d", i)] = "ignored"
}
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
"azure_tags": tooManyTags,
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Fatal("expected config to reject based on an excessive amount of tags (> 15)")
}
}
func TestConfigShouldRejectExcessiveTagNameLength(t *testing.T) {
nameTooLong := make([]byte, 513)
for i := range nameTooLong {
nameTooLong[i] = 'a'
}
tags := map[string]string{}
tags[string(nameTooLong)] = "ignored"
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
"azure_tags": tags,
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Fatal("expected config to reject tag name based on length (> 512)")
}
}
func TestConfigShouldRejectExcessiveTagValueLength(t *testing.T) {
valueTooLong := make([]byte, 257)
for i := range valueTooLong {
valueTooLong[i] = 'a'
}
tags := map[string]string{}
tags["tag01"] = string(valueTooLong)
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
"azure_tags": tags,
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Fatal("expected config to reject tag value based on length (> 256)")
}
}
func TestConfigShouldRejectMissingCustomDataFile(t *testing.T) {
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
"custom_data_file": "/this/file/does/not/exist",
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Fatal("expected config to reject missing custom data file")
if !strings.Contains(err.Error(), "tenant_id must be specified") {
t.Errorf("Expected to find error for 'tenant_id must be specified")
}
}
func getArmBuilderConfiguration() map[string]string {
m := make(map[string]string)
for _, v := range requiredConfigValues {
m[v] = "ignored00"
m[v] = fmt.Sprintf("%s00", v)
}
m["communicator"] = "none"
m["os_type"] = constants.Target_Linux
return m
}
func getArmBuilderConfigurationWithWindows() map[string]string {
m := make(map[string]string)
for _, v := range requiredConfigValues {
m[v] = "ignored00"
}
m["object_id"] = "ignored00"
m["tenant_id"] = "ignored00"
m["winrm_username"] = "ignored00"
m["communicator"] = "winrm"
m["os_type"] = constants.Target_Windows
return m
}
func getPackerConfiguration() interface{} {
config := map[string]interface{}{
"packer_build_name": "azure-arm-vm",
"packer_builder_type": "azure-arm-vm",
"packer_debug": "false",
"packer_force": "false",
"packer_template_path": "/home/jenkins/azure-arm-vm/template.json",
}
var doc = `{
"packer_user_variables": {
"sa": "my_storage_account"
},
"packer_build_name": "azure-arm-vm",
"packer_builder_type": "azure-arm-vm",
"packer_debug": "false",
"packer_force": "false",
"packer_template_path": "/home/jenkins/azure-arm-vm/template.json"
}`
var config interface{}
json.Unmarshal([]byte(doc), &config)
return config
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"encoding/json"
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
)
type DeploymentFactory struct {
template string
}
func newDeploymentFactory(template string) DeploymentFactory {
return DeploymentFactory{
template: template,
}
}
func (f *DeploymentFactory) create(templateParameters TemplateParameters) (*resources.Deployment, error) {
template, err := f.getTemplate(templateParameters)
if err != nil {
return nil, err
}
parameters, err := f.getTemplateParameters(templateParameters)
if err != nil {
return nil, err
}
return &resources.Deployment{
Properties: &resources.DeploymentProperties{
Mode: resources.Incremental,
Template: template,
Parameters: parameters,
},
}, nil
}
func (f *DeploymentFactory) getTemplate(templateParameters TemplateParameters) (*map[string]interface{}, error) {
var t map[string]interface{}
err := json.Unmarshal([]byte(f.template), &t)
if err != nil {
return nil, err
}
return &t, nil
}
func (f *DeploymentFactory) getTemplateParameters(templateParameters TemplateParameters) (*map[string]interface{}, error) {
b, err := json.Marshal(templateParameters)
if err != nil {
return nil, err
}
var t map[string]interface{}
err = json.Unmarshal(b, &t)
if err != nil {
return nil, err
}
return &t, nil
}
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"testing"
)
func TestDeploymentFactoryShouldBeIncremental(t *testing.T) {
var testSubject = newDeploymentFactory(Linux)
deployment, err := testSubject.create(getTemplateParameters())
if err != nil {
t.Fatalf("ERROR: %s\n", err)
}
if deployment.Properties.Mode != "Incremental" {
t.Fatalf("Expected the mode to be 'Incremental', but got '%s'.", deployment.Properties.Mode)
}
}
// Either {Template,Parameter} are set or {Template,Parameter}Link values are
// set, but never both.
func TestDeploymentFactoryShouldNotSetLinks(t *testing.T) {
testSubject := newDeploymentFactory(Linux)
deployment, err := testSubject.create(getTemplateParameters())
if err != nil {
t.Fatalf("ERROR: %s\n", err)
}
if deployment.Properties.ParametersLink != nil {
t.Fatalf("Expected the ParametersLink to be nil!")
}
if deployment.Properties.TemplateLink != nil {
t.Fatalf("Expected the TemplateLink to be nil!")
}
if deployment.Properties.Parameters == nil {
t.Fatalf("Expected the Parameters to not be nil!")
}
if deployment.Properties.Template == nil {
t.Fatalf("Expected the Template to not be nil!")
}
}
func TestFactoryShouldCreateDeploymentInstance(t *testing.T) {
testSubject := newDeploymentFactory(Linux)
deployment, err := testSubject.create(getTemplateParameters())
if err != nil {
t.Fatalf("ERROR: %s\n", err)
}
// spot check well known values to ensure correct serialization.
parametersMap := *deployment.Properties.Parameters
if _, ok := parametersMap["adminUsername"]; ok == false {
t.Fatalf("Expected the parameter value 'adminUsername' to be set, but it was not")
}
templateMap := *deployment.Properties.Template
if _, ok := templateMap["contentVersion"]; ok == false {
t.Fatalf("Expected the parameter value 'contentVersion' to be set, but it was not")
}
}
func TestMalformedTemplatesShouldReturnError(t *testing.T) {
testSubject := newDeploymentFactory("")
_, err := testSubject.create(getTemplateParameters())
if err == nil {
t.Fatalf("Expected an error, but did not receive one!\n")
}
}
func getTemplateParameters() TemplateParameters {
templateParameters := TemplateParameters{
AdminUsername: &TemplateParameter{"adminusername00"},
DnsNameForPublicIP: &TemplateParameter{"dnsnameforpublicip00"},
OSDiskName: &TemplateParameter{"osdiskname00"},
SshAuthorizedKey: &TemplateParameter{"sshkeydata00"},
StorageAccountBlobEndpoint: &TemplateParameter{"storageaccountblobendpoint00"},
VMName: &TemplateParameter{"vmname00"},
VMSize: &TemplateParameter{"vmsize00"},
}
return templateParameters
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"time"
)
const (
DeployCanceled = "Canceled"
DeployFailed = "Failed"
DeployDeleted = "Deleted"
DeploySucceeded = "Succeeded"
)
type DeploymentPoller struct {
getProvisioningState func() (string, error)
pause func()
}
func NewDeploymentPoller(getProvisioningState func() (string, error)) *DeploymentPoller {
pollDuration := time.Second * 15
return &DeploymentPoller{
getProvisioningState: getProvisioningState,
pause: func() { time.Sleep(pollDuration) },
}
}
func (t *DeploymentPoller) PollAsNeeded() (string, error) {
for {
res, err := t.getProvisioningState()
if err != nil {
return res, err
}
switch res {
case DeployCanceled, DeployDeleted, DeployFailed, DeploySucceeded:
return res, nil
default:
break
}
t.pause()
}
}
+108
View File
@@ -0,0 +1,108 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"testing"
)
func TestCanceledShouldImmediatelyStopPolling(t *testing.T) {
var testSubject = NewDeploymentPoller(func() (string, error) { return "Canceled", nil })
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
res, err := testSubject.PollAsNeeded()
if err != nil {
t.Errorf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
}
if res != "Canceled" {
t.Fatalf("Expected PollAsNeeded to return a result of 'Canceled', but got '%s' instead.", res)
}
}
func TestFailedShouldImmediatelyStopPolling(t *testing.T) {
var testSubject = NewDeploymentPoller(func() (string, error) { return "Failed", nil })
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
res, err := testSubject.PollAsNeeded()
if err != nil {
t.Fatalf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
}
if res != "Failed" {
t.Fatalf("Expected PollAsNeeded to return a result of 'Failed', but got '%s' instead.", res)
}
}
func TestDeletedShouldImmediatelyStopPolling(t *testing.T) {
var testSubject = NewDeploymentPoller(func() (string, error) { return "Deleted", nil })
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
res, err := testSubject.PollAsNeeded()
if err != nil {
t.Fatalf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
}
if res != "Deleted" {
t.Fatalf("Expected PollAsNeeded to return a result of 'Deleted', but got '%s' instead.", res)
}
}
func TestSucceededShouldImmediatelyStopPolling(t *testing.T) {
var testSubject = NewDeploymentPoller(func() (string, error) { return "Succeeded", nil })
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
res, err := testSubject.PollAsNeeded()
if err != nil {
t.Fatalf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
}
if res != "Succeeded" {
t.Fatalf("Expected PollAsNeeded to return a result of 'Succeeded', but got '%s' instead.", res)
}
}
func TestPollerShouldPollOnNonStoppingStatus(t *testing.T) {
count := 0
var testSubject = NewDeploymentPoller(func() (string, error) { return "Succeeded", nil })
testSubject.pause = func() { count += 1 }
testSubject.getProvisioningState = func() (string, error) {
count += 1
switch count {
case 0, 1:
return "Working", nil
default:
return "Succeeded", nil
}
}
res, err := testSubject.PollAsNeeded()
if err != nil {
t.Fatalf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
}
if res != "Succeeded" {
t.Fatalf("Expected PollAsNeeded to return a result of 'Succeeded', but got '%s' instead.", res)
}
if count != 3 {
t.Fatal("Expected DeploymentPoller to poll until 'Succeeded', but it did not.")
}
}
func TestPollerShouldReturnErrorImmediately(t *testing.T) {
var testSubject = NewDeploymentPoller(func() (string, error) { return "bad-bad-bad", fmt.Errorf("BOOM") })
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
res, err := testSubject.PollAsNeeded()
if err == nil {
t.Fatal("Expected PollAsNeeded to return an error, but it did not.")
}
if res != "bad-bad-bad" {
t.Fatalf("Expected PollAsNeeded to return a result of 'bad-bad-bad', but got '%s' instead.", res)
}
}
-101
View File
@@ -1,101 +0,0 @@
package arm
// Code to resolve resources that are required by the API. These resources
// can most likely be resolved without asking the user, thereby reducing the
// amount of configuration they need to provide.
//
// Resource resolver differs from config retriever because resource resolver
// requires a client to communicate with the Azure API. A config retriever is
// used to determine values without use of a client.
import (
"fmt"
"strings"
)
type resourceResolver struct {
client *AzureClient
findVirtualNetworkResourceGroup func(*AzureClient, string) (string, error)
findVirtualNetworkSubnet func(*AzureClient, string, string) (string, error)
}
func newResourceResolver(client *AzureClient) *resourceResolver {
return &resourceResolver{
client: client,
findVirtualNetworkResourceGroup: findVirtualNetworkResourceGroup,
findVirtualNetworkSubnet: findVirtualNetworkSubnet,
}
}
func (s *resourceResolver) Resolve(c *Config) error {
if s.shouldResolveResourceGroup(c) {
resourceGroupName, err := s.findVirtualNetworkResourceGroup(s.client, c.VirtualNetworkName)
if err != nil {
return err
}
subnetName, err := s.findVirtualNetworkSubnet(s.client, resourceGroupName, c.VirtualNetworkName)
if err != nil {
return err
}
c.VirtualNetworkResourceGroupName = resourceGroupName
c.VirtualNetworkSubnetName = subnetName
}
return nil
}
func (s *resourceResolver) shouldResolveResourceGroup(c *Config) bool {
return c.VirtualNetworkName != "" && c.VirtualNetworkResourceGroupName == ""
}
func getResourceGroupNameFromId(id string) string {
// "/subscriptions/3f499422-dd76-4114-8859-86d526c9deb6/resourceGroups/packer-Resource-Group-yylnwsl30j/providers/...
xs := strings.Split(id, "/")
return xs[4]
}
func findVirtualNetworkResourceGroup(client *AzureClient, name string) (string, error) {
virtualNetworks, err := client.VirtualNetworksClient.ListAll()
if err != nil {
return "", err
}
resourceGroupNames := make([]string, 0)
for _, virtualNetwork := range *virtualNetworks.Value {
if strings.EqualFold(name, *virtualNetwork.Name) {
rgn := getResourceGroupNameFromId(*virtualNetwork.ID)
resourceGroupNames = append(resourceGroupNames, rgn)
}
}
if len(resourceGroupNames) == 0 {
return "", fmt.Errorf("Cannot find a resource group with a virtual network called %q", name)
}
if len(resourceGroupNames) > 1 {
return "", fmt.Errorf("Found multiple resource groups with a virtual network called %q, please use virtual_network_subnet_name and virtual_network_resource_group_name to disambiguate", name)
}
return resourceGroupNames[0], nil
}
func findVirtualNetworkSubnet(client *AzureClient, resourceGroupName string, name string) (string, error) {
subnets, err := client.SubnetsClient.List(resourceGroupName, name)
if err != nil {
return "", err
}
if len(*subnets.Value) == 0 {
return "", fmt.Errorf("Cannot find a subnet in the resource group %q associated with the virtual network called %q", resourceGroupName, name)
}
if len(*subnets.Value) > 1 {
return "", fmt.Errorf("Found multiple subnets in the resource group %q associated with the virtual network called %q, please use virtual_network_subnet_name and virtual_network_resource_group_name to disambiguate", resourceGroupName, name)
}
subnet := (*subnets.Value)[0]
return *subnet.Name, nil
}
@@ -1,76 +0,0 @@
package arm
import (
"testing"
)
func TestResourceResolverIgnoresEmptyVirtualNetworkName(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
if c.VirtualNetworkName != "" {
t.Fatalf("Expected VirtualNetworkName to be empty by default")
}
sut := newTestResourceResolver()
sut.findVirtualNetworkResourceGroup = nil // assert that this is not even called
sut.Resolve(c)
if c.VirtualNetworkName != "" {
t.Fatalf("Expected VirtualNetworkName to be empty")
}
if c.VirtualNetworkResourceGroupName != "" {
t.Fatalf("Expected VirtualNetworkResourceGroupName to be empty")
}
}
// If the user fully specified the virtual network name and resource group then
// there is no need to do a lookup.
func TestResourceResolverIgnoresSetVirtualNetwork(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
c.VirtualNetworkName = "--virtual-network-name--"
c.VirtualNetworkResourceGroupName = "--virtual-network-resource-group-name--"
c.VirtualNetworkSubnetName = "--virtual-network-subnet-name--"
sut := newTestResourceResolver()
sut.findVirtualNetworkResourceGroup = nil // assert that this is not even called
sut.findVirtualNetworkSubnet = nil // assert that this is not even called
sut.Resolve(c)
if c.VirtualNetworkName != "--virtual-network-name--" {
t.Fatalf("Expected VirtualNetworkName to be --virtual-network-name--")
}
if c.VirtualNetworkResourceGroupName != "--virtual-network-resource-group-name--" {
t.Fatalf("Expected VirtualNetworkResourceGroupName to be --virtual-network-resource-group-name--")
}
if c.VirtualNetworkSubnetName != "--virtual-network-subnet-name--" {
t.Fatalf("Expected VirtualNetworkSubnetName to be --virtual-network-subnet-name--")
}
}
// If the user set virtual network name then the code should resolve virtual network
// resource group name.
func TestResourceResolverSetVirtualNetworkResourceGroupName(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
c.VirtualNetworkName = "--virtual-network-name--"
sut := newTestResourceResolver()
sut.Resolve(c)
if c.VirtualNetworkResourceGroupName != "findVirtualNetworkResourceGroup is mocked" {
t.Fatalf("Expected VirtualNetworkResourceGroupName to be 'findVirtualNetworkResourceGroup is mocked'")
}
if c.VirtualNetworkSubnetName != "findVirtualNetworkSubnet is mocked" {
t.Fatalf("Expected findVirtualNetworkSubnet to be 'findVirtualNetworkSubnet is mocked'")
}
}
func newTestResourceResolver() resourceResolver {
return resourceResolver{
client: nil,
findVirtualNetworkResourceGroup: func(*AzureClient, string) (string, error) {
return "findVirtualNetworkResourceGroup is mocked", nil
},
findVirtualNetworkSubnet: func(*AzureClient, string, string) (string, error) {
return "findVirtualNetworkSubnet is mocked", nil
},
}
}
+1 -1
View File
@@ -4,9 +4,9 @@
package arm
import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func processInterruptibleResult(
+1 -1
View File
@@ -7,9 +7,9 @@ import (
"fmt"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
+5 -5
View File
@@ -8,8 +8,8 @@ import (
"testing"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepCaptureImageShouldFailIfCaptureFails(t *testing.T) {
@@ -97,19 +97,19 @@ func TestStepCaptureImageShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var expectedCaptureTemplate = stateBag.Get(constants.ArmCaptureTemplate).(*CaptureTemplate)
if actualComputeName != expectedComputeName {
t.Fatal("Expected StepCaptureImage to source 'constants.ArmComputeName' from the state bag, but it did not.")
t.Fatalf("Expected StepCaptureImage to source 'constants.ArmComputeName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
t.Fatal("Expected StepCaptureImage to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
t.Fatalf("Expected StepCaptureImage to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualVirtualMachineCaptureParameters != expectedVirtualMachineCaptureParameters {
t.Fatal("Expected StepCaptureImage to source 'constants.ArmVirtualMachineCaptureParameters' from the state bag, but it did not.")
t.Fatalf("Expected StepCaptureImage to source 'constants.ArmVirtualMachineCaptureParameters' from the state bag, but it did not.")
}
if actualCaptureTemplate != expectedCaptureTemplate {
t.Fatal("Expected StepCaptureImage to source 'constants.ArmCaptureTemplate' from the state bag, but it did not.")
t.Fatalf("Expected StepCaptureImage to source 'constants.ArmCaptureTemplate' from the state bag, but it did not.")
}
}
@@ -7,14 +7,14 @@ import (
"fmt"
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepCreateResourceGroup struct {
client *AzureClient
create func(resourceGroupName string, location string, tags *map[string]*string) error
create func(resourceGroupName string, location string) error
say func(message string)
error func(e error)
}
@@ -30,10 +30,9 @@ func NewStepCreateResourceGroup(client *AzureClient, ui packer.Ui) *StepCreateRe
return step
}
func (s *StepCreateResourceGroup) createResourceGroup(resourceGroupName string, location string, tags *map[string]*string) error {
func (s *StepCreateResourceGroup) createResourceGroup(resourceGroupName string, location string) error {
_, err := s.client.GroupsClient.CreateOrUpdate(resourceGroupName, resources.ResourceGroup{
Location: &location,
Tags: tags,
})
return err
@@ -44,16 +43,11 @@ func (s *StepCreateResourceGroup) Run(state multistep.StateBag) multistep.StepAc
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var location = state.Get(constants.ArmLocation).(string)
var tags = state.Get(constants.ArmTags).(*map[string]*string)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> Location : '%s'", location))
s.say(fmt.Sprintf(" -> Tags :"))
for k, v := range *tags {
s.say(fmt.Sprintf(" ->> %s : %s", k, *v))
}
err := s.create(resourceGroupName, location, tags)
err := s.create(resourceGroupName, location)
if err == nil {
state.Put(constants.ArmIsResourceGroupCreated, true)
}
@@ -62,8 +56,8 @@ func (s *StepCreateResourceGroup) Run(state multistep.StateBag) multistep.StepAc
}
func (s *StepCreateResourceGroup) Cleanup(state multistep.StateBag) {
isCreated, ok := state.GetOk(constants.ArmIsResourceGroupCreated)
if !ok || !isCreated.(bool) {
_, ok := state.GetOk(constants.ArmIsResourceGroupCreated)
if !ok {
return
}
@@ -7,13 +7,13 @@ import (
"fmt"
"testing"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepCreateResourceGroupShouldFailIfCreateFails(t *testing.T) {
var testSubject = &StepCreateResourceGroup{
create: func(string, string, *map[string]*string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
create: func(string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
say: func(message string) {},
error: func(e error) {},
}
@@ -32,7 +32,7 @@ func TestStepCreateResourceGroupShouldFailIfCreateFails(t *testing.T) {
func TestStepCreateResourceGroupShouldPassIfCreatePasses(t *testing.T) {
var testSubject = &StepCreateResourceGroup{
create: func(string, string, *map[string]*string) error { return nil },
create: func(string, string) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
@@ -52,13 +52,11 @@ func TestStepCreateResourceGroupShouldPassIfCreatePasses(t *testing.T) {
func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualLocation string
var actualTags *map[string]*string
var testSubject = &StepCreateResourceGroup{
create: func(resourceGroupName string, location string, tags *map[string]*string) error {
create: func(resourceGroupName string, location string) error {
actualResourceGroupName = resourceGroupName
actualLocation = location
actualTags = tags
return nil
},
say: func(message string) {},
@@ -72,25 +70,20 @@ func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
var expectedLocation = stateBag.Get(constants.ArmLocation).(string)
var expectedTags = stateBag.Get(constants.ArmTags).(*map[string]*string)
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
if actualResourceGroupName != expectedResourceGroupName {
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualLocation != expectedLocation {
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if len(*expectedTags) != len(*actualTags) && *(*expectedTags)["tag01"] != *(*actualTags)["tag01"] {
t.Fatal("Expected the step to source 'constants.ArmTags' from the state bag, but it did not.")
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
_, ok := stateBag.GetOk(constants.ArmIsResourceGroupCreated)
if !ok {
t.Fatal("Expected the step to add item to stateBag['constants.ArmIsResourceGroupCreated'], but it did not.")
t.Fatalf("Expected the step to add item to stateBag['constants.ArmIsResourceGroupCreated'], but it did not.")
}
}
@@ -100,12 +93,5 @@ func createTestStateBagStepCreateResourceGroup() multistep.StateBag {
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
value := "Unit Test: Tags"
tags := map[string]*string{
"tag01": &value,
}
stateBag.Put(constants.ArmTags, &tags)
return stateBag
}
+1 -1
View File
@@ -40,7 +40,7 @@ func (s *StepDeleteOSDisk) Run(state multistep.StateBag) multistep.StepAction {
s.say("Deleting the temporary OS disk ...")
var osDisk = state.Get(constants.ArmOSDiskVhd).(string)
s.say(fmt.Sprintf(" -> OS Disk : '%s'", osDisk))
s.say(fmt.Sprintf(" -> OS Disk : '%s'", osDisk))
u, err := url.Parse(osDisk)
if err != nil {
@@ -7,8 +7,8 @@ import (
"fmt"
"testing"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepDeleteOSDiskShouldFailIfGetFails(t *testing.T) {
@@ -6,9 +6,9 @@ package arm
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
@@ -46,10 +46,7 @@ func (s *StepDeleteResourceGroup) Run(state multistep.StateBag) multistep.StepAc
func() bool { return common.IsStateCancelled(state) },
func(cancelCh <-chan struct{}) error { return s.delete(resourceGroupName, cancelCh) })
stepAction := processInterruptibleResult(result, s.error, state)
state.Put(constants.ArmIsResourceGroupCreated, false)
return stepAction
return processInterruptibleResult(result, s.error, state)
}
func (*StepDeleteResourceGroup) Cleanup(multistep.StateBag) {
@@ -7,8 +7,8 @@ import (
"fmt"
"testing"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepDeleteResourceGroupShouldFailIfDeleteFails(t *testing.T) {
@@ -49,9 +49,12 @@ func TestStepDeleteResourceGroupShouldPassIfDeletePasses(t *testing.T) {
}
}
func TestStepDeleteResourceGroupShouldDeleteStateBagArmResourceGroupCreated(t *testing.T) {
func TestStepDeleteResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var testSubject = &StepDeleteResourceGroup{
delete: func(resourceGroupName string, cancelCh <-chan struct{}) error {
actualResourceGroupName = resourceGroupName
return nil
},
say: func(message string) {},
@@ -59,22 +62,22 @@ func TestStepDeleteResourceGroupShouldDeleteStateBagArmResourceGroupCreated(t *t
}
stateBag := DeleteTestStateBagStepDeleteResourceGroup()
testSubject.Run(stateBag)
var result = testSubject.Run(stateBag)
value, ok := stateBag.GetOk(constants.ArmIsResourceGroupCreated)
if !ok {
t.Fatal("Expected the resource bag value arm.IsResourceGroupCreated to exist")
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if value.(bool) {
t.Fatalf("Expected arm.IsResourceGroupCreated to be false, but got %q", value)
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
if actualResourceGroupName != expectedResourceGroupName {
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
}
func DeleteTestStateBagStepDeleteResourceGroup() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
stateBag.Put(constants.ArmIsResourceGroupCreated, "Unit Test: IsResourceGroupCreated")
return stateBag
}
+39 -17
View File
@@ -6,42 +6,63 @@ package arm
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepDeployTemplate struct {
client *AzureClient
deploy func(resourceGroupName string, deploymentName string, cancelCh <-chan struct{}) error
say func(message string)
error func(e error)
config *Config
factory templateFactoryFunc
client *AzureClient
template string
deploy func(resourceGroupName string, deploymentName string, templateParameters *TemplateParameters, cancelCh <-chan struct{}) error
say func(message string)
error func(e error)
}
func NewStepDeployTemplate(client *AzureClient, ui packer.Ui, config *Config, factory templateFactoryFunc) *StepDeployTemplate {
func NewStepDeployTemplate(client *AzureClient, ui packer.Ui, template string) *StepDeployTemplate {
var step = &StepDeployTemplate{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
config: config,
factory: factory,
client: client,
template: template,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.deploy = step.deployTemplate
return step
}
func (s *StepDeployTemplate) deployTemplate(resourceGroupName string, deploymentName string, cancelCh <-chan struct{}) error {
deployment, err := s.factory(s.config)
func (s *StepDeployTemplate) deployTemplate(resourceGroupName string, deploymentName string, templateParameters *TemplateParameters, cancelCh <-chan struct{}) error {
factory := newDeploymentFactory(s.template)
deployment, err := factory.create(*templateParameters)
if err != nil {
return err
}
_, err = s.client.DeploymentsClient.CreateOrUpdate(resourceGroupName, deploymentName, *deployment, cancelCh)
return err
if err != nil {
return err
}
poller := NewDeploymentPoller(func() (string, error) {
r, e := s.client.DeploymentsClient.Get(resourceGroupName, deploymentName)
if r.Properties != nil && r.Properties.ProvisioningState != nil {
return *r.Properties.ProvisioningState, e
}
return "UNKNOWN", e
})
pollStatus, err := poller.PollAsNeeded()
if err != nil {
return err
}
if pollStatus != DeploySucceeded {
return fmt.Errorf("Deployment failed with a status of '%s'.", pollStatus)
}
return nil
}
func (s *StepDeployTemplate) Run(state multistep.StateBag) multistep.StepAction {
@@ -49,6 +70,7 @@ func (s *StepDeployTemplate) Run(state multistep.StateBag) multistep.StepAction
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var deploymentName = state.Get(constants.ArmDeploymentName).(string)
var templateParameters = state.Get(constants.ArmTemplateParameters).(*TemplateParameters)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", deploymentName))
@@ -56,7 +78,7 @@ func (s *StepDeployTemplate) Run(state multistep.StateBag) multistep.StepAction
result := common.StartInterruptibleTask(
func() bool { return common.IsStateCancelled(state) },
func(cancelCh <-chan struct{}) error {
return s.deploy(resourceGroupName, deploymentName, cancelCh)
return s.deploy(resourceGroupName, deploymentName, templateParameters, cancelCh)
},
)
+19 -8
View File
@@ -7,13 +7,14 @@ import (
"fmt"
"testing"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepDeployTemplateShouldFailIfDeployFails(t *testing.T) {
var testSubject = &StepDeployTemplate{
deploy: func(string, string, <-chan struct{}) error {
template: Linux,
deploy: func(string, string, *TemplateParameters, <-chan struct{}) error {
return fmt.Errorf("!! Unit Test FAIL !!")
},
say: func(message string) {},
@@ -34,9 +35,10 @@ func TestStepDeployTemplateShouldFailIfDeployFails(t *testing.T) {
func TestStepDeployTemplateShouldPassIfDeployPasses(t *testing.T) {
var testSubject = &StepDeployTemplate{
deploy: func(string, string, <-chan struct{}) error { return nil },
say: func(message string) {},
error: func(e error) {},
template: Linux,
deploy: func(string, string, *TemplateParameters, <-chan struct{}) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepDeployTemplate()
@@ -54,11 +56,14 @@ func TestStepDeployTemplateShouldPassIfDeployPasses(t *testing.T) {
func TestStepDeployTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualDeploymentName string
var actualTemplateParameters *TemplateParameters
var testSubject = &StepDeployTemplate{
deploy: func(resourceGroupName string, deploymentName string, cancelCh <-chan struct{}) error {
template: Linux,
deploy: func(resourceGroupName string, deploymentName string, templateParameter *TemplateParameters, cancelCh <-chan struct{}) error {
actualResourceGroupName = resourceGroupName
actualDeploymentName = deploymentName
actualTemplateParameters = templateParameter
return nil
},
@@ -75,13 +80,18 @@ func TestStepDeployTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var expectedDeploymentName = stateBag.Get(constants.ArmDeploymentName).(string)
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
var expectedTemplateParameters = stateBag.Get(constants.ArmTemplateParameters).(*TemplateParameters)
if actualDeploymentName != expectedDeploymentName {
t.Fatal("Expected StepValidateTemplate to source 'constants.ArmDeploymentName' from the state bag, but it did not.")
t.Fatalf("Expected StepValidateTemplate to source 'constants.ArmDeploymentName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualTemplateParameters != expectedTemplateParameters {
t.Fatalf("Expected the step to source 'constants.ArmTemplateParameters' from the state bag, but it did not.")
}
}
@@ -90,6 +100,7 @@ func createTestStateBagStepDeployTemplate() multistep.StateBag {
stateBag.Put(constants.ArmDeploymentName, "Unit Test: DeploymentName")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
stateBag.Put(constants.ArmTemplateParameters, &TemplateParameters{})
return stateBag
}
+1 -1
View File
@@ -7,8 +7,8 @@ import (
"fmt"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
@@ -7,8 +7,8 @@ import (
"fmt"
"testing"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepGetCertificateShouldFailIfGetFails(t *testing.T) {
@@ -77,10 +77,10 @@ func TestStepGetCertificateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var expectedKeyVaultName = stateBag.Get(constants.ArmKeyVaultName).(string)
if actualKeyVaultName != expectedKeyVaultName {
t.Fatal("Expected StepGetCertificate to source 'constants.ArmKeyVaultName' from the state bag, but it did not.")
t.Fatalf("Expected StepGetCertificate to source 'constants.ArmKeyVaultName' from the state bag, but it did not.")
}
if actualSecretName != DefaultSecretName {
t.Fatal("Expected StepGetCertificate to use default value for secret, but it did not.")
t.Fatalf("Expected StepGetCertificate to use default value for secret, but it did not.")
}
expectedCertificateUrl, ok := stateBag.GetOk(constants.ArmCertificateUrl)
+17 -51
View File
@@ -6,82 +6,48 @@ package arm
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type EndpointType int
const (
PublicEndpoint EndpointType = iota
PrivateEndpoint
)
var (
EndpointCommunicationText = map[EndpointType]string{
PublicEndpoint: "PublicEndpoint",
PrivateEndpoint: "PrivateEndpoint",
}
)
type StepGetIPAddress struct {
client *AzureClient
endpoint EndpointType
get func(resourceGroupName string, ipAddressName string, interfaceName string) (string, error)
say func(message string)
error func(e error)
client *AzureClient
get func(resourceGroupName string, ipAddressName string) (string, error)
say func(message string)
error func(e error)
}
func NewStepGetIPAddress(client *AzureClient, ui packer.Ui, endpoint EndpointType) *StepGetIPAddress {
func NewStepGetIPAddress(client *AzureClient, ui packer.Ui) *StepGetIPAddress {
var step = &StepGetIPAddress{
client: client,
endpoint: endpoint,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
switch endpoint {
case PrivateEndpoint:
step.get = step.getPrivateIP
case PublicEndpoint:
step.get = step.getPublicIP
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.get = step.getIPAddress
return step
}
func (s *StepGetIPAddress) getPrivateIP(resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
resp, err := s.client.InterfacesClient.Get(resourceGroupName, interfaceName, "")
func (s *StepGetIPAddress) getIPAddress(resourceGroupName string, ipAddressName string) (string, error) {
res, err := s.client.PublicIPAddressesClient.Get(resourceGroupName, ipAddressName, "")
if err != nil {
return "", err
return "", nil
}
return *(*resp.Properties.IPConfigurations)[0].Properties.PrivateIPAddress, nil
}
func (s *StepGetIPAddress) getPublicIP(resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
resp, err := s.client.PublicIPAddressesClient.Get(resourceGroupName, ipAddressName, "")
if err != nil {
return "", err
}
return *resp.Properties.IPAddress, nil
return *res.Properties.IPAddress, nil
}
func (s *StepGetIPAddress) Run(state multistep.StateBag) multistep.StepAction {
s.say("Getting the VM's IP address ...")
s.say("Getting the public IP address ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var ipAddressName = state.Get(constants.ArmPublicIPAddressName).(string)
var nicName = state.Get(constants.ArmNicName).(string)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> PublicIPAddressName : '%s'", ipAddressName))
s.say(fmt.Sprintf(" -> NicName : '%s'", nicName))
s.say(fmt.Sprintf(" -> Network Connection : '%s'", EndpointCommunicationText[s.endpoint]))
address, err := s.get(resourceGroupName, ipAddressName, nicName)
address, err := s.get(resourceGroupName, ipAddressName)
if err != nil {
state.Put(constants.Error, err)
s.error(err)
@@ -89,8 +55,8 @@ func (s *StepGetIPAddress) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt
}
s.say(fmt.Sprintf(" -> Public IP : '%s'", address))
state.Put(constants.SSHHost, address)
s.say(fmt.Sprintf(" -> IP Address : '%s'", address))
return multistep.ActionContinue
}
+12 -23
View File
@@ -7,16 +7,15 @@ import (
"fmt"
"testing"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepGetIPAddressShouldFailIfGetFails(t *testing.T) {
var testSubject = &StepGetIPAddress{
get: func(string, string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
endpoint: PublicEndpoint,
say: func(message string) {},
error: func(e error) {},
get: func(string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepGetIPAddress()
@@ -33,10 +32,9 @@ func TestStepGetIPAddressShouldFailIfGetFails(t *testing.T) {
func TestStepGetIPAddressShouldPassIfGetPasses(t *testing.T) {
var testSubject = &StepGetIPAddress{
get: func(string, string, string) (string, error) { return "", nil },
endpoint: PublicEndpoint,
say: func(message string) {},
error: func(e error) {},
get: func(string, string) (string, error) { return "", nil },
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepGetIPAddress()
@@ -54,19 +52,16 @@ func TestStepGetIPAddressShouldPassIfGetPasses(t *testing.T) {
func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualIPAddressName string
var actualNicName string
var testSubject = &StepGetIPAddress{
get: func(resourceGroupName string, ipAddressName string, nicName string) (string, error) {
get: func(resourceGroupName string, ipAddressName string) (string, error) {
actualResourceGroupName = resourceGroupName
actualIPAddressName = ipAddressName
actualNicName = nicName
return "127.0.0.1", nil
},
endpoint: PublicEndpoint,
say: func(message string) {},
error: func(e error) {},
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepGetIPAddress()
@@ -78,18 +73,13 @@ func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
var expectedIPAddressName = stateBag.Get(constants.ArmPublicIPAddressName).(string)
var expectedNicName = stateBag.Get(constants.ArmNicName).(string)
if actualIPAddressName != expectedIPAddressName {
t.Fatal("Expected StepGetIPAddress to source 'constants.ArmIPAddressName' from the state bag, but it did not.")
t.Fatalf("Expected StepGetIPAddress to source 'constants.ArmIPAddressName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
t.Fatal("Expected StepGetIPAddress to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualNicName != expectedNicName {
t.Fatalf("Expected StepGetIPAddress to source 'constants.ArmNetworkInterfaceName' from the state bag, but it did not.")
t.Fatalf("Expected StepGetIPAddress to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
expectedIPAddress, ok := stateBag.GetOk(constants.SSHHost)
@@ -106,7 +96,6 @@ func createTestStateBagStepGetIPAddress() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmPublicIPAddressName, "Unit Test: PublicIPAddressName")
stateBag.Put(constants.ArmNicName, "Unit Test: NicName")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
return stateBag
+2 -2
View File
@@ -82,11 +82,11 @@ func TestStepGetOSDiskShouldTakeValidateArgumentsFromStateBag(t *testing.T) {
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
if actualComputeName != expectedComputeName {
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
expectedOSDiskVhd, ok := stateBag.GetOk(constants.ArmOSDiskVhd)
+1 -1
View File
@@ -6,9 +6,9 @@ package arm
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
@@ -7,8 +7,8 @@ import (
"fmt"
"testing"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepPowerOffComputeShouldFailIfPowerOffFails(t *testing.T) {
@@ -75,11 +75,11 @@ func TestStepPowerOffComputeShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
if actualComputeName != expectedComputeName {
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
}
+2 -1
View File
@@ -4,8 +4,8 @@
package arm
import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
@@ -31,6 +31,7 @@ func (s *StepSetCertificate) Run(state multistep.StateBag) multistep.StepAction
var winRMCertificateUrl = state.Get(constants.ArmCertificateUrl).(string)
s.config.tmpWinRMCertificateUrl = winRMCertificateUrl
state.Put(constants.ArmTemplateParameters, s.config.toTemplateParameters())
return multistep.ActionContinue
}
@@ -6,8 +6,8 @@ package arm
import (
"testing"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepSetCertificateShouldPassIfGetPasses(t *testing.T) {
@@ -30,9 +30,8 @@ func TestStepSetCertificateShouldPassIfGetPasses(t *testing.T) {
}
func TestStepSetCertificateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
config := new(Config)
var testSubject = &StepSetCertificate{
config: config,
config: new(Config),
say: func(message string) {},
error: func(e error) {},
}
@@ -44,8 +43,9 @@ func TestStepSetCertificateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if config.tmpWinRMCertificateUrl != stateBag.Get(constants.ArmCertificateUrl) {
t.Fatalf("Expected config.tmpWinRMCertificateUrl to be %s, but got %s'", stateBag.Get(constants.ArmCertificateUrl), config.tmpWinRMCertificateUrl)
_, ok := stateBag.GetOk(constants.ArmTemplateParameters)
if !ok {
t.Fatalf("Expected the state bag to have a value for '%s', but it did not.", constants.ArmTemplateParameters)
}
}
+4 -4
View File
@@ -5,16 +5,16 @@ package arm
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"testing"
)
func TestProcessStepResultShouldContinueForNonErrors(t *testing.T) {
stateBag := new(multistep.BasicStateBag)
code := processStepResult(nil, func(error) { t.Fatal("Should not be called!") }, stateBag)
code := processStepResult(nil, func(error) { t.Fatalf("Should not be called!") }, stateBag)
if _, ok := stateBag.GetOk(constants.Error); ok {
t.Errorf("Error was nil, but was still in the state bag.")
}
@@ -49,7 +49,7 @@ func TestProcessStepResultShouldContinueOnSuccessfulTask(t *testing.T) {
Err: nil,
}
code := processInterruptibleResult(result, func(error) { t.Fatal("Should not be called!") }, stateBag)
code := processInterruptibleResult(result, func(error) { t.Fatalf("Should not be called!") }, stateBag)
if _, ok := stateBag.GetOk(constants.Error); ok {
t.Errorf("Error was nil, but was still in the state bag.")
}
@@ -66,7 +66,7 @@ func TestProcessStepResultShouldHaltWhenTaskIsCancelled(t *testing.T) {
Err: nil,
}
code := processInterruptibleResult(result, func(error) { t.Fatal("Should not be called!") }, stateBag)
code := processInterruptibleResult(result, func(error) { t.Fatalf("Should not be called!") }, stateBag)
if _, ok := stateBag.GetOk(constants.Error); ok {
t.Errorf("Error was nil, but was still in the state bag.")
}
+14 -13
View File
@@ -6,35 +6,35 @@ package arm
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepValidateTemplate struct {
client *AzureClient
validate func(resourceGroupName string, deploymentName string) error
template string
validate func(resourceGroupName string, deploymentName string, templateParameters *TemplateParameters) error
say func(message string)
error func(e error)
config *Config
factory templateFactoryFunc
}
func NewStepValidateTemplate(client *AzureClient, ui packer.Ui, config *Config, factory templateFactoryFunc) *StepValidateTemplate {
func NewStepValidateTemplate(client *AzureClient, ui packer.Ui, template string) *StepValidateTemplate {
var step = &StepValidateTemplate{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
config: config,
factory: factory,
client: client,
template: template,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.validate = step.validateTemplate
return step
}
func (s *StepValidateTemplate) validateTemplate(resourceGroupName string, deploymentName string) error {
deployment, err := s.factory(s.config)
func (s *StepValidateTemplate) validateTemplate(resourceGroupName string, deploymentName string, templateParameters *TemplateParameters) error {
factory := newDeploymentFactory(s.template)
deployment, err := factory.create(*templateParameters)
if err != nil {
return err
}
@@ -48,11 +48,12 @@ func (s *StepValidateTemplate) Run(state multistep.StateBag) multistep.StepActio
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var deploymentName = state.Get(constants.ArmDeploymentName).(string)
var templateParameters = state.Get(constants.ArmTemplateParameters).(*TemplateParameters)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", deploymentName))
err := s.validate(resourceGroupName, deploymentName)
err := s.validate(resourceGroupName, deploymentName, templateParameters)
return processStepResult(err, s.error, state)
}
@@ -7,13 +7,15 @@ import (
"fmt"
"testing"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepValidateTemplateShouldFailIfValidateFails(t *testing.T) {
var testSubject = &StepValidateTemplate{
validate: func(string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
template: Linux,
validate: func(string, string, *TemplateParameters) error { return fmt.Errorf("!! Unit Test FAIL !!") },
say: func(message string) {},
error: func(e error) {},
}
@@ -32,7 +34,8 @@ func TestStepValidateTemplateShouldFailIfValidateFails(t *testing.T) {
func TestStepValidateTemplateShouldPassIfValidatePasses(t *testing.T) {
var testSubject = &StepValidateTemplate{
validate: func(string, string) error { return nil },
template: Linux,
validate: func(string, string, *TemplateParameters) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
@@ -52,11 +55,14 @@ func TestStepValidateTemplateShouldPassIfValidatePasses(t *testing.T) {
func TestStepValidateTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualDeploymentName string
var actualTemplateParameters *TemplateParameters
var testSubject = &StepValidateTemplate{
validate: func(resourceGroupName string, deploymentName string) error {
template: Linux,
validate: func(resourceGroupName string, deploymentName string, templateParameter *TemplateParameters) error {
actualResourceGroupName = resourceGroupName
actualDeploymentName = deploymentName
actualTemplateParameters = templateParameter
return nil
},
@@ -73,13 +79,18 @@ func TestStepValidateTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var expectedDeploymentName = stateBag.Get(constants.ArmDeploymentName).(string)
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
var expectedTemplateParameters = stateBag.Get(constants.ArmTemplateParameters).(*TemplateParameters)
if actualDeploymentName != expectedDeploymentName {
t.Fatal("Expected the step to source 'constants.ArmDeploymentName' from the state bag, but it did not.")
t.Fatalf("Expected the step to source 'constants.ArmDeploymentName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualTemplateParameters != expectedTemplateParameters {
t.Fatalf("Expected the step to source 'constants.ArmTemplateParameters' from the state bag, but it did not.")
}
}
@@ -88,6 +99,7 @@ func createTestStateBagStepValidateTemplate() multistep.StateBag {
stateBag.Put(constants.ArmDeploymentName, "Unit Test: DeploymentName")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
stateBag.Put(constants.ArmTemplateParameters, &TemplateParameters{})
return stateBag
}
+489
View File
@@ -0,0 +1,489 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
// See https://github.com/Azure/azure-quickstart-templates for a extensive list of templates.
const Linux = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminUsername": {
"type": "string"
},
"adminPassword": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"imagePublisher": {
"type": "string"
},
"imageOffer": {
"type": "string"
},
"imageSku": {
"type": "string"
},
"imageVersion": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"sshAuthorizedKey": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmSize": {
"type": "string"
},
"vmName": {
"type": "string"
}
},
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetName": "packerSubnet",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/publicIPAddresses",
"name": "[variables('publicIPAddressName')]",
"location": "[variables('location')]",
"properties": {
"publicIPAllocationMethod": "[variables('publicIPAddressType')]",
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
}
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/virtualNetworks",
"name": "[variables('virtualNetworkName')]",
"location": "[variables('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]"
}
}
]
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/networkInterfaces",
"name": "[variables('nicName')]",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Compute/virtualMachines",
"name": "[parameters('vmName')]",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]",
"linuxConfiguration": {
"disablePasswordAuthentication": "false",
"ssh": {
"publicKeys": [
{
"path": "[variables('sshKeyPath')]",
"keyData": "[parameters('sshAuthorizedKey')]"
}
]
}
}
},
"storageProfile": {
"imageReference": {
"publisher": "[parameters('imagePublisher')]",
"offer": "[parameters('imageOffer')]",
"sku": "[parameters('imageSku')]",
"version": "[parameters('imageVersion')]"
},
"osDisk": {
"name": "osdisk",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
},
"caching": "ReadWrite",
"createOption": "FromImage"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": "false"
}
}
}
}
]
}`
// Template to deploy a KeyVault.
//
// NOTE: the parameters for the KeyVault template are identical to Windows
// template. Keeping these values in sync simplifies the code at the expense
// of template bloat. This bloat may be addressed in the future.
const KeyVault = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminUserName": {
"type": "string"
},
"adminPassword": {
"type": "securestring"
},
"dnsNameForPublicIP": {
"type": "string"
},
"imageOffer": {
"type": "string"
},
"imagePublisher": {
"type": "string"
},
"imageSku": {
"type": "string"
},
"imageVersion": {
"type": "string"
},
"keyVaultName": {
"type": "string"
},
"keyVaultSecretValue": {
"type": "securestring"
},
"objectId": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"tenantId": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
},
"winRMCertificateUrl": {
"type": "string"
}
},
"variables": {
"apiVersion": "2015-06-01",
"location": "[resourceGroup().location]",
"keyVaultSecretName": "packerKeyVaultSecret"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.KeyVault/vaults",
"name": "[parameters('keyVaultName')]",
"location": "[variables('location')]",
"properties": {
"enabledForDeployment": "true",
"enabledForTemplateDeployment": "true",
"tenantId": "[parameters('tenantId')]",
"accessPolicies": [
{
"tenantId": "[parameters('tenantId')]",
"objectId": "[parameters('objectId')]",
"permissions": {
"keys": [ "all" ],
"secrets": [ "all" ]
}
}
],
"sku": {
"name": "standard",
"family": "A"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "secrets",
"name": "[variables('keyVaultSecretName')]",
"dependsOn": [
"[concat('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]"
],
"properties": {
"value": "[parameters('keyVaultSecretValue')]"
}
}
]
}
]
}`
const Windows = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminUserName": {
"type": "string"
},
"adminPassword": {
"type": "securestring"
},
"dnsNameForPublicIP": {
"type": "string"
},
"imageOffer": {
"type": "string"
},
"imagePublisher": {
"type": "string"
},
"imageSku": {
"type": "string"
},
"imageVersion": {
"type": "string"
},
"keyVaultName": {
"type": "string"
},
"keyVaultSecretValue": {
"type": "securestring"
},
"objectId": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"tenantId": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
},
"winRMCertificateUrl": {
"type": "string"
}
},
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"subnetName": "packerSubnet",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/publicIPAddresses",
"name": "[variables('publicIPAddressName')]",
"location": "[variables('location')]",
"properties": {
"publicIPAllocationMethod": "[variables('publicIPAddressType')]",
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
}
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/virtualNetworks",
"name": "[variables('virtualNetworkName')]",
"location": "[variables('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]"
}
}
]
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/networkInterfaces",
"name": "[variables('nicName')]",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Compute/virtualMachines",
"name": "[parameters('vmName')]",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]",
"secrets": [
{
"sourceVault": {
"id": "[resourceId(resourceGroup().name, 'Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
},
"vaultCertificates": [
{
"certificateUrl": "[parameters('winRMCertificateUrl')]",
"certificateStore": "My"
}
]
}
],
"windowsConfiguration": {
"provisionVMAgent": "true",
"winRM": {
"listeners": [{
"protocol": "https",
"certificateUrl": "[parameters('winRMCertificateUrl')]"
}
]
},
"enableAutomaticUpdates": "true"
}
},
"storageProfile": {
"imageReference": {
"publisher": "[parameters('imagePublisher')]",
"offer": "[parameters('imageOffer')]",
"sku": "[parameters('imageSku')]",
"version": "[parameters('imageVersion')]"
},
"osDisk": {
"name": "osdisk",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
},
"caching": "ReadWrite",
"createOption": "FromImage"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": "false"
}
}
}
}
]
}`
-103
View File
@@ -1,103 +0,0 @@
package arm
import (
"encoding/json"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/packer/builder/azure/common/template"
)
type templateFactoryFunc func(*Config) (*resources.Deployment, error)
func GetKeyVaultDeployment(config *Config) (*resources.Deployment, error) {
params := &template.TemplateParameters{
KeyVaultName: &template.TemplateParameter{Value: config.tmpKeyVaultName},
KeyVaultSecretValue: &template.TemplateParameter{Value: config.winrmCertificate},
ObjectId: &template.TemplateParameter{Value: config.ObjectID},
TenantId: &template.TemplateParameter{Value: config.TenantID},
}
builder, _ := template.NewTemplateBuilder(template.KeyVault)
builder.SetTags(&config.AzureTags)
doc, _ := builder.ToJSON()
return createDeploymentParameters(*doc, params)
}
func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error) {
params := &template.TemplateParameters{
AdminUsername: &template.TemplateParameter{Value: config.UserName},
AdminPassword: &template.TemplateParameter{Value: config.Password},
DnsNameForPublicIP: &template.TemplateParameter{Value: config.tmpComputeName},
OSDiskName: &template.TemplateParameter{Value: config.tmpOSDiskName},
StorageAccountBlobEndpoint: &template.TemplateParameter{Value: config.storageAccountBlobEndpoint},
VMSize: &template.TemplateParameter{Value: config.VMSize},
VMName: &template.TemplateParameter{Value: config.tmpComputeName},
}
builder, _ := template.NewTemplateBuilder(template.BasicTemplate)
osType := compute.Linux
switch config.OSType {
case constants.Target_Linux:
builder.BuildLinux(config.sshAuthorizedKey)
case constants.Target_Windows:
osType = compute.Windows
builder.BuildWindows(config.tmpKeyVaultName, config.tmpWinRMCertificateUrl)
}
if config.ImageUrl != "" {
builder.SetImageUrl(config.ImageUrl, osType)
} else {
builder.SetMarketPlaceImage(config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion)
}
if config.OSDiskSizeGB > 0 {
builder.SetOSDiskSizeGB(config.OSDiskSizeGB)
}
if config.customData != "" {
builder.SetCustomData(config.customData)
}
if config.VirtualNetworkName != "" {
builder.SetVirtualNetwork(
config.VirtualNetworkResourceGroupName,
config.VirtualNetworkName,
config.VirtualNetworkSubnetName)
}
builder.SetTags(&config.AzureTags)
doc, _ := builder.ToJSON()
return createDeploymentParameters(*doc, params)
}
func createDeploymentParameters(doc string, parameters *template.TemplateParameters) (*resources.Deployment, error) {
var template map[string]interface{}
err := json.Unmarshal(([]byte)(doc), &template)
if err != nil {
return nil, err
}
bs, err := json.Marshal(*parameters)
if err != nil {
return nil, err
}
var templateParameters map[string]interface{}
err = json.Unmarshal(bs, &templateParameters)
if err != nil {
return nil, err
}
return &resources.Deployment{
Properties: &resources.DeploymentProperties{
Mode: resources.Incremental,
Template: &template,
Parameters: &templateParameters,
},
}, nil
}
@@ -1,72 +0,0 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"keyVaultName": {
"type": "string"
},
"keyVaultSecretValue": {
"type": "securestring"
},
"objectId": {
"type": "string"
},
"tenantId": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"name": "[parameters('keyVaultName')]",
"properties": {
"accessPolicies": [
{
"objectId": "[parameters('objectId')]",
"permissions": {
"keys": [
"all"
],
"secrets": [
"all"
]
},
"tenantId": "[parameters('tenantId')]"
}
],
"enabledForDeployment": "true",
"enabledForTemplateDeployment": "true",
"sku": {
"family": "A",
"name": "standard"
},
"tenantId": "[parameters('tenantId')]"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]"
],
"name": "[variables('keyVaultSecretName')]",
"properties": {
"value": "[parameters('keyVaultSecretValue')]"
},
"type": "secrets"
}
],
"tags": {
"tag01": "value01",
"tag02": "value02",
"tag03": "value03"
},
"type": "Microsoft.KeyVault/vaults"
}
],
"variables": {
"apiVersion": "2015-06-01",
"keyVaultSecretName": "packerKeyVaultSecret",
"location": "[resourceGroup().location]"
}
}
@@ -1,161 +0,0 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"name": "[variables('publicIPAddressName')]",
"properties": {
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
},
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
},
"type": "Microsoft.Network/publicIPAddresses"
},
{
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"name": "[variables('virtualNetworkName')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]"
}
}
]
},
"type": "Microsoft.Network/virtualNetworks"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"location": "[variables('location')]",
"name": "[variables('nicName')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"location": "[variables('location')]",
"name": "[parameters('vmName')]",
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"osProfile": {
"adminPassword": "[parameters('adminPassword')]",
"adminUsername": "[parameters('adminUsername')]",
"computerName": "[parameters('vmName')]",
"linuxConfiguration": {
"ssh": {
"publicKeys": [
{
"keyData": "",
"path": "[variables('sshKeyPath')]"
}
]
}
}
},
"storageProfile": {
"imageReference": {
"offer": "ImageOffer",
"publisher": "ImagePublisher",
"sku": "ImageSku",
"version": "ImageVersion"
},
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"name": "osdisk",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
}
}
}
},
"type": "Microsoft.Compute/virtualMachines"
}
],
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "packerSubnet",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}
@@ -1,159 +0,0 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"name": "[variables('publicIPAddressName')]",
"properties": {
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
},
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
},
"type": "Microsoft.Network/publicIPAddresses"
},
{
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"name": "[variables('virtualNetworkName')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]"
}
}
]
},
"type": "Microsoft.Network/virtualNetworks"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"location": "[variables('location')]",
"name": "[variables('nicName')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"location": "[variables('location')]",
"name": "[parameters('vmName')]",
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"osProfile": {
"adminPassword": "[parameters('adminPassword')]",
"adminUsername": "[parameters('adminUsername')]",
"computerName": "[parameters('vmName')]",
"linuxConfiguration": {
"ssh": {
"publicKeys": [
{
"keyData": "",
"path": "[variables('sshKeyPath')]"
}
]
}
}
},
"storageProfile": {
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"image": {
"uri": "https://localhost/custom.vhd"
},
"name": "osdisk",
"osType": "Linux",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
}
}
}
},
"type": "Microsoft.Compute/virtualMachines"
}
],
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "packerSubnet",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}
@@ -1,120 +0,0 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [],
"location": "[variables('location')]",
"name": "[variables('nicName')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"location": "[variables('location')]",
"name": "[parameters('vmName')]",
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"osProfile": {
"adminPassword": "[parameters('adminPassword')]",
"adminUsername": "[parameters('adminUsername')]",
"computerName": "[parameters('vmName')]",
"linuxConfiguration": {
"ssh": {
"publicKeys": [
{
"keyData": "",
"path": "[variables('sshKeyPath')]"
}
]
}
}
},
"storageProfile": {
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"image": {
"uri": "https://localhost/custom.vhd"
},
"name": "osdisk",
"osType": "Linux",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
}
}
}
},
"type": "Microsoft.Compute/virtualMachines"
}
],
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "virtualNetworkSubnetName",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "virtualNetworkName",
"virtualNetworkResourceGroup": "virtualNetworkResourceGroupName",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}
@@ -1,179 +0,0 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"name": "[variables('publicIPAddressName')]",
"properties": {
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
},
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
},
"tags": {
"tag01": "value01",
"tag02": "value02",
"tag03": "value03"
},
"type": "Microsoft.Network/publicIPAddresses"
},
{
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"name": "[variables('virtualNetworkName')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]"
}
}
]
},
"tags": {
"tag01": "value01",
"tag02": "value02",
"tag03": "value03"
},
"type": "Microsoft.Network/virtualNetworks"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"location": "[variables('location')]",
"name": "[variables('nicName')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
},
"tags": {
"tag01": "value01",
"tag02": "value02",
"tag03": "value03"
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"location": "[variables('location')]",
"name": "[parameters('vmName')]",
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"osProfile": {
"adminPassword": "[parameters('adminPassword')]",
"adminUsername": "[parameters('adminUsername')]",
"computerName": "[parameters('vmName')]",
"linuxConfiguration": {
"ssh": {
"publicKeys": [
{
"keyData": "",
"path": "[variables('sshKeyPath')]"
}
]
}
}
},
"storageProfile": {
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"image": {
"uri": "https://localhost/custom.vhd"
},
"name": "osdisk",
"osType": "Linux",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
}
}
}
},
"tags": {
"tag01": "value01",
"tag02": "value02",
"tag03": "value03"
},
"type": "Microsoft.Compute/virtualMachines"
}
],
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "packerSubnet",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}
@@ -1,160 +0,0 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"name": "[variables('publicIPAddressName')]",
"properties": {
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
},
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
},
"type": "Microsoft.Network/publicIPAddresses"
},
{
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"name": "[variables('virtualNetworkName')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]"
}
}
]
},
"type": "Microsoft.Network/virtualNetworks"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"location": "[variables('location')]",
"name": "[variables('nicName')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"location": "[variables('location')]",
"name": "[parameters('vmName')]",
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"osProfile": {
"adminPassword": "[parameters('adminPassword')]",
"adminUsername": "[parameters('adminUsername')]",
"computerName": "[parameters('vmName')]",
"customData": "I2Nsb3VkLWNvbmZpZwpncm93cGFydDoKICBtb2RlOiBvZmYK",
"linuxConfiguration": {
"ssh": {
"publicKeys": [
{
"keyData": "",
"path": "[variables('sshKeyPath')]"
}
]
}
}
},
"storageProfile": {
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"image": {
"uri": "https://localhost/custom.vhd"
},
"name": "osdisk",
"osType": "Linux",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
}
}
}
},
"type": "Microsoft.Compute/virtualMachines"
}
],
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "packerSubnet",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}
-358
View File
@@ -1,358 +0,0 @@
package arm
import (
"encoding/base64"
"encoding/json"
"testing"
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
"github.com/approvals/go-approval-tests"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/packer/builder/azure/common/template"
)
// Ensure the link values are not set, and the concrete values are set.
func TestVirtualMachineDeployment00(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
deployment, err := GetVirtualMachineDeployment(c)
if err != nil {
t.Fatal(err)
}
if deployment.Properties.Mode != resources.Incremental {
t.Errorf("Expected deployment.Properties.Mode to be %s, but got %s", resources.Incremental, deployment.Properties.Mode)
}
if deployment.Properties.ParametersLink != nil {
t.Error("Expected the ParametersLink to be nil!")
}
if deployment.Properties.TemplateLink != nil {
t.Error("Expected the TemplateLink to be nil!")
}
if deployment.Properties.Parameters == nil {
t.Error("Expected the Parameters to not be nil!")
}
if deployment.Properties.Template == nil {
t.Error("Expected the Template to not be nil!")
}
}
// Ensure the Virtual Machine template is a valid JSON document.
func TestVirtualMachineDeployment01(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
deployment, err := GetVirtualMachineDeployment(c)
if err != nil {
t.Fatal(err)
}
_, err = json.Marshal(deployment.Properties.Template)
if err != nil {
t.Fatal(err)
}
}
// Ensure the Virtual Machine template parameters are correct.
func TestVirtualMachineDeployment02(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
deployment, err := GetVirtualMachineDeployment(c)
if err != nil {
t.Fatal(err)
}
bs, err := json.Marshal(deployment.Properties.Parameters)
if err != nil {
t.Fatal(err)
}
var params template.TemplateParameters
err = json.Unmarshal(bs, &params)
if err != nil {
t.Fatal(err)
}
if params.AdminUsername.Value != c.UserName {
t.Errorf("Expected template parameter 'AdminUsername' to be %s, but got %s.", params.AdminUsername.Value, c.UserName)
}
if params.AdminPassword.Value != c.tmpAdminPassword {
t.Errorf("Expected template parameter 'AdminPassword' to be %s, but got %s.", params.AdminPassword.Value, c.tmpAdminPassword)
}
if params.DnsNameForPublicIP.Value != c.tmpComputeName {
t.Errorf("Expected template parameter 'DnsNameForPublicIP' to be %s, but got %s.", params.DnsNameForPublicIP.Value, c.tmpComputeName)
}
if params.OSDiskName.Value != c.tmpOSDiskName {
t.Errorf("Expected template parameter 'OSDiskName' to be %s, but got %s.", params.OSDiskName.Value, c.tmpOSDiskName)
}
if params.StorageAccountBlobEndpoint.Value != c.storageAccountBlobEndpoint {
t.Errorf("Expected template parameter 'StorageAccountBlobEndpoint' to be %s, but got %s.", params.StorageAccountBlobEndpoint.Value, c.storageAccountBlobEndpoint)
}
if params.VMSize.Value != c.VMSize {
t.Errorf("Expected template parameter 'VMSize' to be %s, but got %s.", params.VMSize.Value, c.VMSize)
}
if params.VMName.Value != c.tmpComputeName {
t.Errorf("Expected template parameter 'VMName' to be %s, but got %s.", params.VMName.Value, c.tmpComputeName)
}
}
// Ensure the VM template is correct when using a market place image.
func TestVirtualMachineDeployment03(t *testing.T) {
m := getArmBuilderConfiguration()
m["image_publisher"] = "ImagePublisher"
m["image_offer"] = "ImageOffer"
m["image_sku"] = "ImageSku"
m["image_version"] = "ImageVersion"
c, _, _ := newConfig(m, getPackerConfiguration())
deployment, err := GetVirtualMachineDeployment(c)
if err != nil {
t.Fatal(err)
}
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
if err != nil {
t.Fatal(err)
}
}
// Ensure the VM template is correct when using a custom image.
func TestVirtualMachineDeployment04(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "https://localhost/custom.vhd",
"resource_group_name": "ignore",
"storage_account": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
}
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
deployment, err := GetVirtualMachineDeployment(c)
if err != nil {
t.Fatal(err)
}
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
if err != nil {
t.Fatal(err)
}
}
func TestVirtualMachineDeployment05(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "https://localhost/custom.vhd",
"resource_group_name": "ignore",
"storage_account": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
"virtual_network_name": "virtualNetworkName",
"virtual_network_resource_group_name": "virtualNetworkResourceGroupName",
"virtual_network_subnet_name": "virtualNetworkSubnetName",
}
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
deployment, err := GetVirtualMachineDeployment(c)
if err != nil {
t.Fatal(err)
}
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
if err != nil {
t.Fatal(err)
}
}
// Verify that tags are properly applied to every resource
func TestVirtualMachineDeployment06(t *testing.T) {
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "https://localhost/custom.vhd",
"resource_group_name": "ignore",
"storage_account": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
"azure_tags": map[string]string{
"tag01": "value01",
"tag02": "value02",
"tag03": "value03",
},
}
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
deployment, err := GetVirtualMachineDeployment(c)
if err != nil {
t.Fatal(err)
}
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
if err != nil {
t.Fatal(err)
}
}
// Verify that custom data are properly inserted
func TestVirtualMachineDeployment07(t *testing.T) {
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "https://localhost/custom.vhd",
"resource_group_name": "ignore",
"storage_account": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
}
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
// The user specifies a configuration value for the setting custom_data_file.
// The config type will read that file, and base64 encode it. The encoded
// contents are then assigned to Config's customData property, which are directly
// injected into the template.
//
// I am not aware of an easy to mimic this situation in a test without having
// a file on disk, which I am loathe to do. The alternative is to inject base64
// encoded data myself, which is what I am doing here.
customData := `#cloud-config
growpart:
mode: off
`
base64CustomData := base64.StdEncoding.EncodeToString([]byte(customData))
c.customData = base64CustomData
deployment, err := GetVirtualMachineDeployment(c)
if err != nil {
t.Fatal(err)
}
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
if err != nil {
t.Fatal(err)
}
}
// Ensure the link values are not set, and the concrete values are set.
func TestKeyVaultDeployment00(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
deployment, err := GetKeyVaultDeployment(c)
if err != nil {
t.Fatal(err)
}
if deployment.Properties.Mode != resources.Incremental {
t.Errorf("Expected deployment.Properties.Mode to be %s, but got %s", resources.Incremental, deployment.Properties.Mode)
}
if deployment.Properties.ParametersLink != nil {
t.Error("Expected the ParametersLink to be nil!")
}
if deployment.Properties.TemplateLink != nil {
t.Error("Expected the TemplateLink to be nil!")
}
if deployment.Properties.Parameters == nil {
t.Error("Expected the Parameters to not be nil!")
}
if deployment.Properties.Template == nil {
t.Error("Expected the Template to not be nil!")
}
}
// Ensure the KeyVault template is a valid JSON document.
func TestKeyVaultDeployment01(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
deployment, err := GetKeyVaultDeployment(c)
if err != nil {
t.Fatal(err)
}
_, err = json.Marshal(deployment.Properties.Template)
if err != nil {
t.Fatal(err)
}
}
// Ensure the KeyVault template parameters are correct.
func TestKeyVaultDeployment02(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfigurationWithWindows(), getPackerConfiguration())
deployment, err := GetKeyVaultDeployment(c)
if err != nil {
t.Fatal(err)
}
bs, err := json.Marshal(deployment.Properties.Parameters)
if err != nil {
t.Fatal(err)
}
var params template.TemplateParameters
err = json.Unmarshal(bs, &params)
if err != nil {
t.Fatal(err)
}
if params.ObjectId.Value != c.ObjectID {
t.Errorf("Expected template parameter 'ObjectId' to be %s, but got %s.", params.ObjectId.Value, c.ObjectID)
}
if params.TenantId.Value != c.TenantID {
t.Errorf("Expected template parameter 'TenantId' to be %s, but got %s.", params.TenantId.Value, c.TenantID)
}
if params.KeyVaultName.Value != c.tmpKeyVaultName {
t.Errorf("Expected template parameter 'KeyVaultName' to be %s, but got %s.", params.KeyVaultName.Value, c.tmpKeyVaultName)
}
if params.KeyVaultSecretValue.Value != c.winrmCertificate {
t.Errorf("Expected template parameter 'KeyVaultSecretValue' to be %s, but got %s.", params.KeyVaultSecretValue.Value, c.winrmCertificate)
}
}
// Ensure the KeyVault template is correct when tags are supplied.
func TestKeyVaultDeployment03(t *testing.T) {
tags := map[string]interface{}{
"azure_tags": map[string]string{
"tag01": "value01",
"tag02": "value02",
"tag03": "value03",
},
}
c, _, _ := newConfig(tags, getArmBuilderConfigurationWithWindows(), getPackerConfiguration())
deployment, err := GetKeyVaultDeployment(c)
if err != nil {
t.Fatal(err)
}
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
if err != nil {
t.Fatal(err)
}
}
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package template
package arm
// The intent of these types to facilitate interchange with Azure in the
// appropriate JSON format. A sample format is below. Each parameter listed
@@ -24,12 +24,18 @@ type TemplateParameters struct {
AdminUsername *TemplateParameter `json:"adminUsername,omitempty"`
AdminPassword *TemplateParameter `json:"adminPassword,omitempty"`
DnsNameForPublicIP *TemplateParameter `json:"dnsNameForPublicIP,omitempty"`
ImageOffer *TemplateParameter `json:"imageOffer,omitempty"`
ImagePublisher *TemplateParameter `json:"imagePublisher,omitempty"`
ImageSku *TemplateParameter `json:"imageSku,omitempty"`
ImageVersion *TemplateParameter `json:"imageVersion,omitempty"`
KeyVaultName *TemplateParameter `json:"keyVaultName,omitempty"`
KeyVaultSecretValue *TemplateParameter `json:"keyVaultSecretValue,omitempty"`
ObjectId *TemplateParameter `json:"objectId,omitempty"`
OSDiskName *TemplateParameter `json:"osDiskName,omitempty"`
SshAuthorizedKey *TemplateParameter `json:"sshAuthorizedKey,omitempty"`
StorageAccountBlobEndpoint *TemplateParameter `json:"storageAccountBlobEndpoint,omitempty"`
TenantId *TemplateParameter `json:"tenantId,omitempty"`
VMSize *TemplateParameter `json:"vmSize,omitempty"`
VMName *TemplateParameter `json:"vmName,omitempty"`
WinRMCertificateUrl *TemplateParameter `json:"winRMCertificateUrl,omitempty"`
}
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package template
package arm
import (
"encoding/json"
@@ -12,13 +12,17 @@ import (
func TestTemplateParametersShouldHaveExpectedKeys(t *testing.T) {
params := TemplateParameters{
AdminUsername: &TemplateParameter{Value: "sentinel"},
AdminPassword: &TemplateParameter{Value: "sentinel"},
DnsNameForPublicIP: &TemplateParameter{Value: "sentinel"},
OSDiskName: &TemplateParameter{Value: "sentinel"},
StorageAccountBlobEndpoint: &TemplateParameter{Value: "sentinel"},
VMName: &TemplateParameter{Value: "sentinel"},
VMSize: &TemplateParameter{Value: "sentinel"},
AdminUsername: &TemplateParameter{"sentinel"},
AdminPassword: &TemplateParameter{"sentinel"},
DnsNameForPublicIP: &TemplateParameter{"sentinel"},
ImageOffer: &TemplateParameter{"sentinel"},
ImagePublisher: &TemplateParameter{"sentinel"},
ImageSku: &TemplateParameter{"sentinel"},
OSDiskName: &TemplateParameter{"sentinel"},
SshAuthorizedKey: &TemplateParameter{"sentinel"},
StorageAccountBlobEndpoint: &TemplateParameter{"sentinel"},
VMName: &TemplateParameter{"sentinel"},
VMSize: &TemplateParameter{"sentinel"},
}
bs, err := json.Marshal(params)
@@ -37,7 +41,11 @@ func TestTemplateParametersShouldHaveExpectedKeys(t *testing.T) {
"adminUsername",
"adminPassword",
"dnsNameForPublicIP",
"imageOffer",
"imagePublisher",
"imageSku",
"osDiskName",
"sshAuthorizedKey",
"storageAccountBlobEndpoint",
"vmSize",
"vmName",
@@ -53,13 +61,17 @@ func TestTemplateParametersShouldHaveExpectedKeys(t *testing.T) {
func TestParameterValuesShouldBeSet(t *testing.T) {
params := TemplateParameters{
AdminUsername: &TemplateParameter{Value: "adminusername00"},
AdminPassword: &TemplateParameter{Value: "adminpassword00"},
DnsNameForPublicIP: &TemplateParameter{Value: "dnsnameforpublicip00"},
OSDiskName: &TemplateParameter{Value: "osdiskname00"},
StorageAccountBlobEndpoint: &TemplateParameter{Value: "storageaccountblobendpoint00"},
VMName: &TemplateParameter{Value: "vmname00"},
VMSize: &TemplateParameter{Value: "vmsize00"},
AdminUsername: &TemplateParameter{"adminusername00"},
AdminPassword: &TemplateParameter{"adminpassword00"},
DnsNameForPublicIP: &TemplateParameter{"dnsnameforpublicip00"},
ImageOffer: &TemplateParameter{"imageoffer00"},
ImagePublisher: &TemplateParameter{"imagepublisher00"},
ImageSku: &TemplateParameter{"imagesku00"},
OSDiskName: &TemplateParameter{"osdiskname00"},
SshAuthorizedKey: &TemplateParameter{"sshauthorizedkey00"},
StorageAccountBlobEndpoint: &TemplateParameter{"storageaccountblobendpoint00"},
VMName: &TemplateParameter{"vmname00"},
VMSize: &TemplateParameter{"vmsize00"},
}
bs, err := json.Marshal(params)
@@ -89,7 +101,7 @@ func TestParameterValuesShouldBeSet(t *testing.T) {
func TestEmptyValuesShouldBeOmitted(t *testing.T) {
params := TemplateParameters{
AdminUsername: &TemplateParameter{Value: "adminusername00"},
AdminUsername: &TemplateParameter{"adminusername00"},
}
bs, err := json.Marshal(params)
+10
View File
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package constants
const (
GOOS_Linux string = "linux"
GOOS_Windows string = "windows"
GOOS_Darwin string = "darwin"
)
+24 -9
View File
@@ -5,20 +5,35 @@ package constants
// complete flags
const (
AuthorizedKey string = "authorizedKey"
Certificate string = "certificate"
Error string = "error"
PrivateKey string = "privateKey"
SSHHost string = "sshHost"
Thumbprint string = "thumbprint"
Ui string = "ui"
CertInstalled string = "certInstalled"
CertUploaded string = "certUploaded"
DiskExists string = "diskExists"
ImageCreated string = "imageCreated"
SrvExists string = "srvExists"
VmExists string = "vmExists"
VmRunning string = "vmRunning"
)
const (
AuthorizedKey string = "authorizedKey"
Certificate string = "certificate"
Config string = "config"
Error string = "error"
HardDiskName string = "hardDiskName"
MediaLink string = "mediaLink"
OSImageName string = "osImageName"
PrivateKey string = "privateKey"
RequestManager string = "requestManager"
ServicePrincipalToken string = "servicePrincipalToken"
SSHHost string = "sshHost"
Thumbprint string = "thumbprint"
Ui string = "ui"
)
const (
ArmBlobEndpoint string = "arm.BlobEndpoint"
ArmCaptureTemplate string = "arm.CaptureTemplate"
ArmComputeName string = "arm.ComputeName"
ArmCertificateUrl string = "arm.CertificateUrl"
ArmDeploymentName string = "arm.DeploymentName"
ArmNicName string = "arm.NicName"
ArmKeyVaultName string = "arm.KeyVaultName"
ArmLocation string = "arm.Location"
ArmOSDiskVhd string = "arm.OSDiskVhd"
@@ -26,6 +41,6 @@ const (
ArmResourceGroupName string = "arm.ResourceGroupName"
ArmIsResourceGroupCreated string = "arm.IsResourceGroupCreated"
ArmStorageAccountName string = "arm.StorageAccountName"
ArmTags string = "arm.Tags"
ArmTemplateParameters string = "arm.TemplateParameters"
ArmVirtualMachineCaptureParameters string = "arm.VirtualMachineCaptureParameters"
)
+21 -10
View File
@@ -11,8 +11,8 @@ import (
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"github.com/mitchellh/go-homedir"
"github.com/mitchellh/packer/version"
"github.com/mitchellh/go-homedir"
)
var (
@@ -39,12 +39,21 @@ var (
// Authenticate fetches a token from the local file cache or initiates a consent
// flow and waits for token to be obtained.
func Authenticate(env azure.Environment, tenantID string, say func(string)) (*azure.ServicePrincipalToken, error) {
func Authenticate(env azure.Environment, subscriptionID string, say func(string)) (*azure.ServicePrincipalToken, error) {
clientID, ok := clientIDs[env.Name]
if !ok {
return nil, fmt.Errorf("packer-azure application not set up for Azure environment %q", env.Name)
}
// First we locate the tenant ID of the subscription as we store tokens per
// tenant (which could have multiple subscriptions)
say(fmt.Sprintf("Looking up AAD Tenant ID: subscriptionID=%s.", subscriptionID))
tenantID, err := findTenantID(env, subscriptionID)
if err != nil {
return nil, err
}
say(fmt.Sprintf("Found AAD Tenant ID: tenantID=%s", tenantID))
oauthCfg, err := env.OAuthConfigForTenant(tenantID)
if err != nil {
return nil, fmt.Errorf("Failed to obtain oauth config for azure environment: %v", err)
@@ -81,7 +90,7 @@ func Authenticate(env azure.Environment, tenantID string, say func(string)) (*az
// will go stale every 14 days and we will delete the token file,
// re-initiate the device flow.
say("Validating the token.")
if err = validateToken(env, spt); err != nil {
if err := validateToken(env, spt); err != nil {
say(fmt.Sprintf("Error: %v", err))
say("Stored Azure credentials expired. Please reauthenticate.")
say(fmt.Sprintf("Deleting %s", tokenPath))
@@ -96,7 +105,7 @@ func Authenticate(env azure.Environment, tenantID string, say func(string)) (*az
// Start an OAuth 2.0 device flow
say(fmt.Sprintf("Initiating device flow: %s", tokenPath))
spt, err = tokenFromDeviceFlow(say, *oauthCfg, clientID, apiScope)
spt, err = tokenFromDeviceFlow(say, *oauthCfg, tokenPath, clientID, apiScope)
if err != nil {
return nil, err
}
@@ -136,7 +145,7 @@ func tokenFromFile(say func(string), oauthCfg azure.OAuthConfig, tokenPath, clie
// consent application on a browser and in the meanwhile the authentication
// endpoint is polled until user gives consent, denies or the flow times out.
// Returned token must be saved.
func tokenFromDeviceFlow(say func(string), oauthCfg azure.OAuthConfig, clientID, resource string) (*azure.ServicePrincipalToken, error) {
func tokenFromDeviceFlow(say func(string), oauthCfg azure.OAuthConfig, tokenPath, clientID, resource string) (*azure.ServicePrincipalToken, error) {
cl := autorest.NewClientWithUserAgent(userAgent)
deviceCode, err := azure.InitiateDeviceAuth(&cl, oauthCfg, clientID, resource)
if err != nil {
@@ -188,7 +197,8 @@ func mkTokenCallback(path string) azure.TokenRefreshCallback {
// expired). This check is essentially to make sure refresh_token is good.
func validateToken(env azure.Environment, token *azure.ServicePrincipalToken) error {
c := subscriptionsClient(env.ResourceManagerEndpoint)
c.Authorizer = token
// WTF(chrboum)
//c.Authorizer = token
_, err := c.List()
if err != nil {
return fmt.Errorf("Token validity check failed: %v", err)
@@ -196,10 +206,10 @@ func validateToken(env azure.Environment, token *azure.ServicePrincipalToken) er
return nil
}
// FindTenantID figures out the AAD tenant ID of the subscription by making an
// findTenantID figures out the AAD tenant ID of the subscription by making an
// unauthenticated request to the Get Subscription Details endpoint and parses
// the value from WWW-Authenticate header.
func FindTenantID(env azure.Environment, subscriptionID string) (string, error) {
func findTenantID(env azure.Environment, subscriptionID string) (string, error) {
const hdrKey = "WWW-Authenticate"
c := subscriptionsClient(env.ResourceManagerEndpoint)
@@ -231,6 +241,7 @@ func FindTenantID(env azure.Environment, subscriptionID string) (string, error)
}
func subscriptionsClient(baseURI string) subscriptions.Client {
client := subscriptions.NewClientWithBaseURI(baseURI)
return client
c := subscriptions.NewClientWithBaseURI(baseURI, "") // used only for unauthenticated requests for generic subs IDs
c.Client.UserAgent += userAgent
return c
}
@@ -20,7 +20,7 @@ func TestInterruptibleTaskShouldImmediatelyEndOnCancel(t *testing.T) {
result := testSubject.Run()
if result.IsCancelled != true {
t.Fatal("Expected the task to be cancelled, but it was not.")
t.Fatalf("Expected the task to be cancelled, but it was not.")
}
}
+1 -1
View File
@@ -5,8 +5,8 @@ package lin
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"golang.org/x/crypto/ssh"
)
+7 -7
View File
@@ -32,7 +32,7 @@ func (s *StepCreateCert) Run(state multistep.StateBag) multistep.StepAction {
err := s.createCert(state)
if err != nil {
err = fmt.Errorf("Error creating temporary certificate: %s", err)
err := fmt.Errorf("Error creating temporary certificate: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
@@ -45,11 +45,11 @@ func (s *StepCreateCert) Cleanup(state multistep.StateBag) {}
func (s *StepCreateCert) createCert(state multistep.StateBag) error {
log.Println("createCert: Generating RSA key pair...")
log.Printf("createCert: Generating RSA key pair...")
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
err = fmt.Errorf("Failed to Generate Private Key: %s", err)
err := fmt.Errorf("Failed to Generate Private Key: %s", err)
return err
}
@@ -59,11 +59,11 @@ func (s *StepCreateCert) createCert(state multistep.StateBag) error {
Bytes: x509.MarshalPKCS1PrivateKey(priv),
}))
// Set the private key in the state bag for later
// Set the private key in the statebag for later
state.Put(constants.PrivateKey, privkey)
log.Printf("createCert: Private key:\n%s", privkey)
log.Println("createCert: Creating certificate...")
log.Printf("createCert: Creating certificate...")
host := fmt.Sprintf("%s.cloudapp.net", s.TmpServiceName)
notBefore := time.Now()
@@ -71,7 +71,7 @@ func (s *StepCreateCert) createCert(state multistep.StateBag) error {
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
err = fmt.Errorf("Failed to Generate Serial Number: %v", err)
err := fmt.Errorf("Failed to Generate Serial Number: %v", err)
return err
}
@@ -93,7 +93,7 @@ func (s *StepCreateCert) createCert(state multistep.StateBag) error {
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
err = fmt.Errorf("Failed to Create Certificate: %s", err)
err := fmt.Errorf("Failed to Create Certificate: %s", err)
return err
}
@@ -29,7 +29,7 @@ func (s *StepGeneralizeOS) Run(state multistep.StateBag) multistep.StepAction {
}
if err := comm.Start(cmd); err != nil {
err = fmt.Errorf("Failed executing OS generalization command: %s", err)
err := fmt.Errorf("Failed executing OS generalization command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
@@ -1,120 +0,0 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "--subnet-name--",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "--virtual-network--",
"virtualNetworkResourceGroup": "--virtual-network-resource-group--",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"name": "[variables('nicName')]",
"type": "Microsoft.Network/networkInterfaces",
"location": "[variables('location')]",
"dependsOn": [],
"properties": {
"ipConfigurations": [
{
"properties": {
"privateIPAllocationMethod": "Dynamic",
"subnet": {
"id": "[variables('subnetRef')]"
}
},
"name": "ipconfig"
}
]
}
},
{
"apiVersion": "[variables('apiVersion')]",
"name": "[parameters('vmName')]",
"type": "Microsoft.Compute/virtualMachines",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]",
"linuxConfiguration": {
"ssh": {
"publicKeys": [
{
"path": "[variables('sshKeyPath')]",
"keyData": "--test-ssh-authorized-key--"
}
]
}
}
},
"storageProfile": {
"osDisk": {
"osType": "Linux",
"name": "osdisk",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
},
"image": {
"uri": "http://azure/custom.vhd"
},
"caching": "ReadWrite",
"createOption": "FromImage"
}
}
}
}
]
}
-73
View File
@@ -1,73 +0,0 @@
package template
import (
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/Azure/azure-sdk-for-go/arm/network"
)
/////////////////////////////////////////////////
// Template
type Template struct {
Schema *string `json:"$schema"`
ContentVersion *string `json:"contentVersion"`
Parameters *map[string]Parameters `json:"parameters"`
Variables *map[string]string `json:"variables"`
Resources *[]Resource `json:"resources"`
}
/////////////////////////////////////////////////
// Template > Parameters
type Parameters struct {
Type *string `json:"type"`
DefaultValue *string `json:"defaultValue,omitempty"`
}
/////////////////////////////////////////////////
// Template > Resource
type Resource struct {
ApiVersion *string `json:"apiVersion"`
Name *string `json:"name"`
Type *string `json:"type"`
Location *string `json:"location,omitempty"`
DependsOn *[]string `json:"dependsOn,omitempty"`
Properties *Properties `json:"properties,omitempty"`
Tags *map[string]*string `json:"tags,omitempty"`
Resources *[]Resource `json:"resources,omitempty"`
}
/////////////////////////////////////////////////
// Template > Resource > Properties
type Properties struct {
AccessPolicies *[]AccessPolicies `json:"accessPolicies,omitempty"`
AddressSpace *network.AddressSpace `json:"addressSpace,omitempty"`
DiagnosticsProfile *compute.DiagnosticsProfile `json:"diagnosticsProfile,omitempty"`
DNSSettings *network.PublicIPAddressDNSSettings `json:"dnsSettings,omitempty"`
EnabledForDeployment *string `json:"enabledForDeployment,omitempty"`
EnabledForTemplateDeployment *string `json:"enabledForTemplateDeployment,omitempty"`
HardwareProfile *compute.HardwareProfile `json:"hardwareProfile,omitempty"`
IPConfigurations *[]network.IPConfiguration `json:"ipConfigurations,omitempty"`
NetworkProfile *compute.NetworkProfile `json:"networkProfile,omitempty"`
OsProfile *compute.OSProfile `json:"osProfile,omitempty"`
PublicIPAllocatedMethod *network.IPAllocationMethod `json:"publicIPAllocationMethod,omitempty"`
Sku *Sku `json:"sku,omitempty"`
StorageProfile *compute.StorageProfile `json:"storageProfile,omitempty"`
Subnets *[]network.Subnet `json:"subnets,omitempty"`
TenantId *string `json:"tenantId,omitempty"`
Value *string `json:"value,omitempty"`
}
type AccessPolicies struct {
ObjectId *string `json:"objectId,omitempty"`
TenantId *string `json:"tenantId,omitempty"`
Permissions *Permissions `json:"permissions,omitempty"`
}
type Permissions struct {
Keys *[]string `json:"keys,omitempty"`
Secrets *[]string `json:"secrets,omitempty"`
}
type Sku struct {
Family *string `json:"family,omitempty"`
Name *string `json:"name,omitempty"`
}
@@ -1,466 +0,0 @@
package template
import (
"encoding/json"
"fmt"
"strings"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/Azure/go-autorest/autorest/to"
)
const (
jsonPrefix = ""
jsonIndent = " "
resourceKeyVaults = "Microsoft.KeyVault/vaults"
resourceNetworkInterfaces = "Microsoft.Network/networkInterfaces"
resourcePublicIPAddresses = "Microsoft.Network/publicIPAddresses"
resourceVirtualMachine = "Microsoft.Compute/virtualMachines"
resourceVirtualNetworks = "Microsoft.Network/virtualNetworks"
variableSshKeyPath = "sshKeyPath"
)
type TemplateBuilder struct {
template *Template
}
func NewTemplateBuilder(template string) (*TemplateBuilder, error) {
var t Template
err := json.Unmarshal([]byte(template), &t)
if err != nil {
return nil, err
}
return &TemplateBuilder{
template: &t,
}, nil
}
func (s *TemplateBuilder) BuildLinux(sshAuthorizedKey string) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.OsProfile
profile.LinuxConfiguration = &compute.LinuxConfiguration{
SSH: &compute.SSHConfiguration{
PublicKeys: &[]compute.SSHPublicKey{
{
Path: to.StringPtr(s.toVariable(variableSshKeyPath)),
KeyData: to.StringPtr(sshAuthorizedKey),
},
},
},
}
return nil
}
func (s *TemplateBuilder) BuildWindows(keyVaultName, winRMCertificateUrl string) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.OsProfile
profile.Secrets = &[]compute.VaultSecretGroup{
{
SourceVault: &compute.SubResource{
ID: to.StringPtr(s.toResourceID(resourceKeyVaults, keyVaultName)),
},
VaultCertificates: &[]compute.VaultCertificate{
{
CertificateStore: to.StringPtr("My"),
CertificateURL: to.StringPtr(winRMCertificateUrl),
},
},
},
}
profile.WindowsConfiguration = &compute.WindowsConfiguration{
ProvisionVMAgent: to.BoolPtr(true),
WinRM: &compute.WinRMConfiguration{
Listeners: &[]compute.WinRMListener{
{
Protocol: "https",
CertificateURL: to.StringPtr(winRMCertificateUrl),
},
},
},
}
return nil
}
func (s *TemplateBuilder) SetMarketPlaceImage(publisher, offer, sku, version string) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.StorageProfile
profile.ImageReference = &compute.ImageReference{
Publisher: to.StringPtr(publisher),
Offer: to.StringPtr(offer),
Sku: to.StringPtr(sku),
Version: to.StringPtr(version),
}
return nil
}
func (s *TemplateBuilder) SetImageUrl(imageUrl string, osType compute.OperatingSystemTypes) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.StorageProfile
profile.OsDisk.OsType = osType
profile.OsDisk.Image = &compute.VirtualHardDisk{
URI: to.StringPtr(imageUrl),
}
return nil
}
func (s *TemplateBuilder) SetOSDiskSizeGB(diskSizeGB int32) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.StorageProfile
profile.OsDisk.DiskSizeGB = to.Int32Ptr(diskSizeGB)
return nil
}
func (s *TemplateBuilder) SetCustomData(customData string) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.OsProfile
profile.CustomData = to.StringPtr(customData)
return nil
}
func (s *TemplateBuilder) SetVirtualNetwork(virtualNetworkResourceGroup, virtualNetworkName, subnetName string) error {
s.setVariable("virtualNetworkResourceGroup", virtualNetworkResourceGroup)
s.setVariable("virtualNetworkName", virtualNetworkName)
s.setVariable("subnetName", subnetName)
s.deleteResourceByType(resourceVirtualNetworks)
s.deleteResourceByType(resourcePublicIPAddresses)
resource, err := s.getResourceByType(resourceNetworkInterfaces)
if err != nil {
return err
}
s.deleteResourceDependency(resource, func(s string) bool {
return strings.Contains(s, "Microsoft.Network/virtualNetworks") ||
strings.Contains(s, "Microsoft.Network/publicIPAddresses")
})
(*resource.Properties.IPConfigurations)[0].Properties.PublicIPAddress = nil
return nil
}
func (s *TemplateBuilder) SetTags(tags *map[string]*string) error {
if tags == nil || len(*tags) == 0 {
return nil
}
for i := range *s.template.Resources {
(*s.template.Resources)[i].Tags = tags
}
return nil
}
func (s *TemplateBuilder) ToJSON() (*string, error) {
bs, err := json.MarshalIndent(s.template, jsonPrefix, jsonIndent)
if err != nil {
return nil, err
}
return to.StringPtr(string(bs)), err
}
func (s *TemplateBuilder) getResourceByType(t string) (*Resource, error) {
for _, x := range *s.template.Resources {
if strings.EqualFold(*x.Type, t) {
return &x, nil
}
}
return nil, fmt.Errorf("template: could not find a resource of type %s", t)
}
func (s *TemplateBuilder) setVariable(name string, value string) {
(*s.template.Variables)[name] = value
}
func (s *TemplateBuilder) toKeyVaultID(name string) string {
return s.toResourceID(resourceKeyVaults, name)
}
func (s *TemplateBuilder) toResourceID(id, name string) string {
return fmt.Sprintf("[resourceId(resourceGroup().name, '%s', '%s')]", id, name)
}
func (s *TemplateBuilder) toVariable(name string) string {
return fmt.Sprintf("[variables('%s')]", name)
}
func (s *TemplateBuilder) deleteResourceByType(resourceType string) {
resources := make([]Resource, 0)
for _, resource := range *s.template.Resources {
if *resource.Type == resourceType {
continue
}
resources = append(resources, resource)
}
s.template.Resources = &resources
}
func (s *TemplateBuilder) deleteResourceDependency(resource *Resource, predicate func(string) bool) {
deps := make([]string, 0)
for _, dep := range *resource.DependsOn {
if !predicate(dep) {
deps = append(deps, dep)
}
}
*resource.DependsOn = deps
}
// See https://github.com/Azure/azure-quickstart-templates for a extensive list of templates.
// Template to deploy a KeyVault.
//
// This template is still hard-coded unlike the ARM templates used for VMs for
// a couple of reasons.
//
// 1. The SDK defines no types for a Key Vault
// 2. The Key Vault template is relatively simple, and is static.
//
const KeyVault = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"keyVaultName": {
"type": "string"
},
"keyVaultSecretValue": {
"type": "securestring"
},
"objectId": {
"type": "string"
},
"tenantId": {
"type": "string"
}
},
"variables": {
"apiVersion": "2015-06-01",
"location": "[resourceGroup().location]",
"keyVaultSecretName": "packerKeyVaultSecret"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.KeyVault/vaults",
"name": "[parameters('keyVaultName')]",
"location": "[variables('location')]",
"properties": {
"enabledForDeployment": "true",
"enabledForTemplateDeployment": "true",
"tenantId": "[parameters('tenantId')]",
"accessPolicies": [
{
"tenantId": "[parameters('tenantId')]",
"objectId": "[parameters('objectId')]",
"permissions": {
"keys": [ "all" ],
"secrets": [ "all" ]
}
}
],
"sku": {
"name": "standard",
"family": "A"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "secrets",
"name": "[variables('keyVaultSecretName')]",
"dependsOn": [
"[concat('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]"
],
"properties": {
"value": "[parameters('keyVaultSecretValue')]"
}
}
]
}
]
}`
const BasicTemplate = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminUsername": {
"type": "string"
},
"adminPassword": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmSize": {
"type": "string"
},
"vmName": {
"type": "string"
}
},
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetName": "packerSubnet",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/publicIPAddresses",
"name": "[variables('publicIPAddressName')]",
"location": "[variables('location')]",
"properties": {
"publicIPAllocationMethod": "[variables('publicIPAddressType')]",
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
}
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/virtualNetworks",
"name": "[variables('virtualNetworkName')]",
"location": "[variables('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]"
}
}
]
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/networkInterfaces",
"name": "[variables('nicName')]",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Compute/virtualMachines",
"name": "[parameters('vmName')]",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]"
},
"storageProfile": {
"osDisk": {
"name": "osdisk",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
},
"caching": "ReadWrite",
"createOption": "FromImage"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
}
}
}
]
}`
@@ -1,161 +0,0 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"name": "[variables('publicIPAddressName')]",
"properties": {
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
},
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
},
"type": "Microsoft.Network/publicIPAddresses"
},
{
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"name": "[variables('virtualNetworkName')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]"
}
}
]
},
"type": "Microsoft.Network/virtualNetworks"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"location": "[variables('location')]",
"name": "[variables('nicName')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"location": "[variables('location')]",
"name": "[parameters('vmName')]",
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"osProfile": {
"adminPassword": "[parameters('adminPassword')]",
"adminUsername": "[parameters('adminUsername')]",
"computerName": "[parameters('vmName')]",
"linuxConfiguration": {
"ssh": {
"publicKeys": [
{
"keyData": "--test-ssh-authorized-key--",
"path": "[variables('sshKeyPath')]"
}
]
}
}
},
"storageProfile": {
"imageReference": {
"offer": "UbuntuServer",
"publisher": "Canonical",
"sku": "16.04",
"version": "latest"
},
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"name": "osdisk",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
}
}
}
},
"type": "Microsoft.Compute/virtualMachines"
}
],
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "packerSubnet",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}

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