Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e8a3fa3ea | |||
| fb1c968d57 | |||
| 141440147f | |||
| bb92eef95e | |||
| 489c75a363 | |||
| 7bbc29d4b2 | |||
| 8e7bb5ec23 |
@@ -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
|
||||
|
||||
@@ -9,12 +9,8 @@
|
||||
.idea
|
||||
test/.env
|
||||
*~
|
||||
*.received.*
|
||||
|
||||
website/.bundle
|
||||
website/vendor
|
||||
|
||||
packer-test*.log
|
||||
|
||||
*.received.txt
|
||||
*.received.json
|
||||
|
||||
+197
-375
@@ -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
@@ -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.
|
||||
Generated
+633
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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'))]"
|
||||
}
|
||||
}
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}`
|
||||
@@ -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'))]"
|
||||
}
|
||||
}
|
||||
@@ -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, ¶ms)
|
||||
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, ¶ms)
|
||||
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)
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -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"`
|
||||
}
|
||||
+28
-16
@@ -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)
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user