Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e5f6958803 |
+2
-4
@@ -1,4 +1,2 @@
|
||||
* text=auto
|
||||
*.go text eol=lf
|
||||
*.sh text eol=lf
|
||||
common/test-fixtures/root/* eol=lf
|
||||
|
||||
common/test-fixtures/root/* eol=lf
|
||||
+19
-16
@@ -48,42 +48,45 @@ can quickly merge or address your contributions.
|
||||
|
||||
5. The issue is closed.
|
||||
|
||||
## Setting up Go
|
||||
## Setting up Go to work on Packer
|
||||
|
||||
If you have never worked with Go before, you will have to install its
|
||||
runtime in order to build packer.
|
||||
|
||||
1. [Install go](https://golang.org/doc/install#install)
|
||||
|
||||
## Setting up Packer for dev
|
||||
|
||||
If/when you have go installed you can already `go get` packer and `make` in
|
||||
order to compile and test Packer. These instructions target
|
||||
If you have never worked with Go before, you will have to complete the following
|
||||
steps in order to be able to compile and test Packer. These instructions target
|
||||
POSIX-like environments (Mac OS X, Linux, Cygwin, etc.) so you may need to
|
||||
adjust them for Windows or other shells.
|
||||
The instructions below are for go 1.7. or later.
|
||||
|
||||
1. [Download](https://golang.org/dl) and install Go. The instructions below are
|
||||
for go 1.7. Earlier versions of Go are no longer supported.
|
||||
|
||||
1. Download the Packer source (and its dependencies) by running
|
||||
2. Set and export the `GOPATH` environment variable and update your `PATH`. For
|
||||
example, you can add the following to your `.bash_profile` (or comparable
|
||||
shell startup scripts):
|
||||
|
||||
```
|
||||
export GOPATH=$HOME/go
|
||||
export PATH=$PATH:$GOPATH/bin
|
||||
```
|
||||
|
||||
3. Download the Packer source (and its dependencies) by running
|
||||
`go get github.com/hashicorp/packer`. This will download the Packer source to
|
||||
`$GOPATH/src/github.com/hashicorp/packer`.
|
||||
|
||||
2. When working on Packer, first `cd $GOPATH/src/github.com/hashicorp/packer`
|
||||
4. When working on Packer, first `cd $GOPATH/src/github.com/hashicorp/packer`
|
||||
so you can run `make` and easily access other files. Run `make help` to get
|
||||
information about make targets.
|
||||
|
||||
3. Make your changes to the Packer source. You can run `make` in
|
||||
5. Make your changes to the Packer source. You can run `make` in
|
||||
`$GOPATH/src/github.com/hashicorp/packer` to run tests and build the Packer
|
||||
binary. Any compilation errors will be shown when the binaries are
|
||||
rebuilding. If you don't have `make` you can simply run
|
||||
`go build -o bin/packer .` from the project root.
|
||||
|
||||
4. After running building Packer successfully, use
|
||||
6. After running building Packer successfully, use
|
||||
`$GOPATH/src/github.com/hashicorp/packer/bin/packer` to build a machine and
|
||||
verify your changes work. For instance:
|
||||
`$GOPATH/src/github.com/hashicorp/packer/bin/packer build template.json`.
|
||||
|
||||
5. If everything works well and the tests pass, run `go fmt` on your code before
|
||||
7. If everything works well and the tests pass, run `go fmt` on your code before
|
||||
submitting a pull-request.
|
||||
|
||||
### Opening an Pull Request
|
||||
|
||||
+1
-2
@@ -23,5 +23,4 @@ packer-test*.log
|
||||
.idea/
|
||||
*.iml
|
||||
Thumbs.db
|
||||
/packer.exe
|
||||
.project
|
||||
/packer.exe
|
||||
+3
-4
@@ -6,8 +6,9 @@ sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
- master
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.x
|
||||
|
||||
install:
|
||||
- make deps
|
||||
@@ -20,6 +21,4 @@ branches:
|
||||
- master
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
fast_finish: true
|
||||
|
||||
+5
-222
@@ -1,233 +1,16 @@
|
||||
## 1.2.6 (Unreleased)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
* builder/alicloud: Support source image coming from marketplace [GH-6588]
|
||||
* builder/amazon-chroot: New feature `root_volume_tags` to tag the created
|
||||
volumes. [GH-6504]
|
||||
* builder/azure: Implement clean_image_name template engine. [GH-6558]
|
||||
* builder/cloudstack: Add option to use a fixed port via public_port. [GH-6532]
|
||||
* builder/digitalocean: Add support for tagging to instances [GH-6546]
|
||||
* builder/googlecompute: Add new `min_cpu_platform` feature [GH-6607]
|
||||
* builder/lxc: Allow unplivileged LXC containers. [GH-6279]
|
||||
* builder/oci: Add `metadata` feature to Packer config. [GH-6498]
|
||||
* builder/openstack: Add support for getting config from clouds-public.yaml.
|
||||
[GH-6595]
|
||||
* builder/openstack: Add support for ports. [GH-6570]
|
||||
* builder/openstack: Add support for source_image_filter. [GH-6490]
|
||||
* builder/openstack: Migrate floating IP usage to Network v2 API from Compute
|
||||
API. [GH-6373]
|
||||
* builder/openstack: Support Block Storage volumes as boot volume. [GH-6596]
|
||||
* builder/qemu: add ssh agent support. [GH-6541]
|
||||
* builder/qemu: New `use_backing_file` feature [GH-6249]
|
||||
* builder/vmware-iso: Try to use ISO files uploaded to the datastore when
|
||||
building remotely instead of uploading them freshly every time [GH-5165]
|
||||
* command/validate: Warn users if config needs fixing. [GH-6423]
|
||||
* core: Add progress-bar to download step. [GH-5851]
|
||||
* core: Deduplicate ui and log lines that stream to terminal [GH-6611]
|
||||
* post-processor/vagrant: Support for Docker images. [GH-6494]
|
||||
* postprocessor/vagrant: Add support for Azure. [GH-6576]
|
||||
* provisioner/ansible: Enable {{.WinRMPassword}} template engine. [GH-6450]
|
||||
* provisioner/shell-local: Create PACKER_HTTP_ADDR environment variable
|
||||
[GH-6503]
|
||||
|
||||
### BUG FIXES:
|
||||
* builder/amazon-ebssurrogate: Clean up volumes at end of build. [GH-6514]
|
||||
* builder/azure: Generated password satisfies Azure password requirements
|
||||
[GH-6480]
|
||||
* builder/lxc: Correctly pass "config" option to "lxc launch". [GH-6563]
|
||||
* builder/vmware-iso: Fix crash caused by invalid datacenter url. [GH-6529]
|
||||
* core: Better error handling in downloader when connection error occurs.
|
||||
[GH-6557]
|
||||
* core: Fix broken pathing checks in checksum files. [GH-6525]
|
||||
* provisioner/windows-restart: Provisioner now works when used in conjuction
|
||||
with SSH communicator [GH-6606]
|
||||
|
||||
### BACKWARDS INCOMPATIBILITIES:
|
||||
* builder/amazon: "owners" field on source_ami_filter is now required for
|
||||
secuirty reasons. [GH-6585]
|
||||
|
||||
## 1.2.5 (July 16, 2018)
|
||||
|
||||
### BUG FIXES:
|
||||
* builder/alickoud: Fix issue where internet_max_bandwidth_out template option
|
||||
was not being passed to builder. [GH-6416]
|
||||
* builder/alicloud: Fix an issue with VPC cleanup. [GH-6418]
|
||||
* builder/amazon-chroot: Fix communicator bug that broke chroot builds.
|
||||
[GH-6363]
|
||||
* builder/amazon: Replace packer's waiters with those from the AWS sdk, solving
|
||||
several timeout bugs. [GH-6332]
|
||||
* builder/azure: update azure-sdk-for-go, fixing 32-bit build errors. [GH-6479]
|
||||
* builder/azure: update the max length of managed_image_resource_group to match
|
||||
new increased length of 90 characters. [GH-6477]
|
||||
* builder/hyper-v: Fix secure boot template feature so that it properly passes
|
||||
the temolate for MicrosoftUEFICertificateAuthority. [GH-6415]
|
||||
* builder/hyperv: Fix bug in HyperV IP lookups that was causing breakages in
|
||||
FreeBSD/OpenBSD builds. [GH-6416]
|
||||
* builder/qemu: Fix error race condition in qemu builder that caused convert to
|
||||
fail on ubuntu 18.x [GH-6437]
|
||||
* builder/qemu: vnc_bind_address was not being passed to qemu. [GH-6467]
|
||||
* builder/virtualbox: Allow iso_url to be a symlink. [GH-6370]
|
||||
* builder/vmware: Don't fail on DHCP lease files that cannot be read, fixing
|
||||
bug where builder failed on NAT networks that don't serve DHCP. [GH-6415]
|
||||
* builder/vmware: Fix bug where we couldn't discover IP if vm_name differed
|
||||
from the vmx displayName. [GH-6448]
|
||||
* builder/vmware: Fix validation to prevent hang when remopte_password is not
|
||||
sent but vmware is building on esxi. [GH-6424]
|
||||
* builder/vmware:Correctly default the vm export format to ovf; this is what
|
||||
the docs claimed we already did, but we didn't. [GH-4538]
|
||||
* communicator/winrm: Revert an attempt to determine whether remote upload
|
||||
destinations were files or directories, as this broke uploads on systems
|
||||
without Powershell installed. [GH-6481]
|
||||
* core: Fix bug in parsing of iso checksum files that arose when setting
|
||||
iso_url to a relative filepath. [GH-6488]
|
||||
* core: Fix Packer crash caused by improper error handling in the downloader.
|
||||
[GH-6381]
|
||||
* fix: Fix bug where fixer for ssh_private_ip that failed when boolean values
|
||||
are passed as strings. [GH-6458]
|
||||
* provisioner/powershell: Make upload of powershell variables retryable, in
|
||||
case of system restarts. [GH-6388]
|
||||
|
||||
### IMPROVEMENTS:
|
||||
* builder/amazon: Add the ap-northeast-3 region. [GH-6385]
|
||||
* builder/amazon: Spot requests may now have tags applied using the `spot_tags`
|
||||
option [GH-5452]
|
||||
* builder/cloudstack: Add support for Projectid and new config option
|
||||
prevent_firewall_changes. [GH-6487]
|
||||
* builder/openstack: Add support for token authorization and cloud.yaml.
|
||||
[GH-6362]
|
||||
* builder/oracle-oci: Add new "instance_name" template option. [GH-6408]
|
||||
* builder/scaleway: Add new "bootscript" parameter, allowing the user to not
|
||||
use the default local bootscript [GH-6439]
|
||||
* builder/vmware: Add support for linked clones to vmware-vmx. [GH-6394]
|
||||
* debug: The -debug flag will now cause Packer to pause between provisioner
|
||||
scripts in addition to Packer steps. [GH-4663]
|
||||
* post-processor/googlecompute-import: Added new googlecompute-import post-
|
||||
processor [GH-6451]
|
||||
* provisioner/ansible: Add new "playbook_files" option to execute multiple
|
||||
playbooks within one provisioner call. [GH-5086]
|
||||
|
||||
## 1.2.4 (May 29, 2018)
|
||||
## (UNRELEASED)
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
* builder/amazon: Can now force the chroot builder to mount an entire block
|
||||
device instead of a partition [GH-6194]
|
||||
* builder/azure: windows-sql-cloud is now in the default list of projects to
|
||||
check for provided images. [GH-6210]
|
||||
* builder/chroot: A new template option, `nvme_device_path` has been added to
|
||||
provide a workaround for users who need the amazon-chroot builder to mount
|
||||
a NVMe volume on their instances. [GH-6295]
|
||||
* builder/hyper-v: Fix command for mounting multiple disks [GH-6267]
|
||||
* builder/hyperv: Enable IP retrieval for Server 2008 R2 hosts. [GH-6219]
|
||||
* builder/hyperv: Fix bug in MAC address specification on Hyper-V. [GH-6187]
|
||||
* builder/parallels-pvm: Add missing disk compaction step. [GH-6202]
|
||||
* builder/vmware-esxi: Remove floppy files from the remote server on cleanup.
|
||||
[GH-6206]
|
||||
* communicator/winrm: Updated dependencies to fix a race condition [GH-6261]
|
||||
* core: When using `-on-error=[abort|ask]`, output the error to the user.
|
||||
[GH-6252]
|
||||
* provisioner/puppet: Extra-Arguments are no longer prematurely
|
||||
interpolated.[GH-6215]
|
||||
* provisioner/shell: Remove file stat that was causing problems uploading files
|
||||
[GH-6239]
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
* builder/amazon: Amazon builders other than `chroot` now support T2 unlimited
|
||||
instances [GH-6265]
|
||||
* builder/azure: Allow device login for US government cloud. [GH-6105]
|
||||
* builder/azure: Devicelogin Support for Windows [GH-6285]
|
||||
* builder/azure: Enable simultaneous builds within one resource group.
|
||||
[GH-6231]
|
||||
* builder/azure: Faster deletion of Azure Resource Groups. [GH-6269]
|
||||
* builder/azure: Updated Azure SDK to v15.0.0 [GH-6224]
|
||||
* builder/hyper-v: Hyper-V builds now connect to vnc display by default when
|
||||
building [GH-6243]
|
||||
* builder/hyper-v: New `use_fixed_vhd_format` allows vm export in an Azure-
|
||||
compatible format [GH-6101]
|
||||
* builder/hyperv: New config option for specifying what secure boot template to
|
||||
use, allowing secure boot of linux vms. [GH-5883]
|
||||
* builder/qemu: Add support for hvf accelerator. [GH-6193]
|
||||
* builder/scaleway: Fix SSH communicator connection issue. [GH-6238]
|
||||
* core: Add opt-in Packer top-level command autocomplete [GH-5454]
|
||||
* post-processor/shell-local: New options have been added to create feature
|
||||
parity with the shell-local provisioner. This feature now works on Windows
|
||||
hosts. [GH-5956]
|
||||
* provisioner/chef: New config option allows user to skip cleanup of chef
|
||||
client staging directory. [GH-4300]
|
||||
* provisioner/shell-local: Can now access automatically-generated WinRM
|
||||
password as variable [GH-6251]
|
||||
* provisoner/shell-local: New options have been added to create feature parity
|
||||
with the shell-local post-processor. This feature now works on Windows
|
||||
hosts. [GH-5956]
|
||||
* builder/virtualbox: Use HTTPS to download guest editions, now that it's
|
||||
available. [GH-6406]
|
||||
|
||||
## 1.2.3 (April 25, 2018)
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
* builder/azure: Azure CLI may now be logged into several accounts. [GH-6087]
|
||||
* builder/ebssurrogate: Snapshot all launch devices. [GH-6056]
|
||||
* builder/hyper-v: Fix CopyExportedVirtualMachine script so it works with
|
||||
links. [GH-6082]
|
||||
* builder/oracle-classic: Fix panics when cleaning up resources that haven't
|
||||
been created. [GH-6095]
|
||||
* builder/parallels: Allow user to cancel build while the OS is starting up.
|
||||
[GH-6166]
|
||||
* builder/qemu: Avoid warning when using raw format. [GH-6080]
|
||||
* post-processor/vagrant: Large VMDKs should no longer show a 0-byte size on OS X. [GH-6084]
|
||||
* builder/scaleway: Fix compilation issues on solaris/amd64. [GH-6069]
|
||||
* builder/virtualbox: Fix broken scancodes in boot_command. [GH-6067]
|
||||
* builder/vmware-iso: Fail in validation if user gives wrong remote_type value.
|
||||
[GH-4563]
|
||||
* builder/vmware: Fixed a case-sensitivity issue when determing the network
|
||||
type during the cloning step in the vmware-vmx builder. [GH-6057]
|
||||
* builder/vmware: Fixes the DHCP lease and configuration pathfinders for VMware
|
||||
Player. [GH-6096]
|
||||
* builder/vmware: Multi-disk VM's can be properly handled by the compacting
|
||||
stage. [GH-6074]
|
||||
* common/bootcommand: Fix numerous bugs in the boot command code, and make
|
||||
supported features consistent across builders. [GH-6129]
|
||||
* communicator/ssh: Stop trying to discover whether destination is a directory
|
||||
from uploader. [GH-6124]
|
||||
* post-processor/vagrant: Large VMDKs should no longer show a 0-byte size on OS
|
||||
X. [GH-6084]
|
||||
* post-processor/vsphere: Fix encoding of spaces in passwords for upload.
|
||||
[GH-6110]
|
||||
* provisioner/ansible: Pass the inventory_directory configuration option to
|
||||
ansible -i when it is set. [GH-6065]
|
||||
* provisioner/powershell: fix bug with SSH communicator + cygwin. [GH-6160]
|
||||
* provisioner/powershell: The {{.WinRMPassword}} template variable now works
|
||||
with parallel builders. [GH-6144]
|
||||
* common/bootcommand: Fix numerous bugs in the boot command code, and make supported features consistent across builders. [GH-6129]
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
* builder/alicloud: Update aliyungo common package. [GH-6157]
|
||||
* builder/amazon: Expose more source ami data as template variables. [GH-6088]
|
||||
* builder/amazon: Setting `force_delete` will only delete AMIs owned by the
|
||||
user. This should prevent failures where we try to delete an AMI with a
|
||||
matching name, but owned by someone else. [GH-6111]
|
||||
* builder/azure: Users of Powershell provisioner may access the randomly-
|
||||
generated winrm password using the template variable {{.WinRMPassword}}.
|
||||
[GH-6113]
|
||||
* builder/google: Users of Powershell provisioner may access the randomly-
|
||||
generated winrm password using the template variable {{.WinRMPassword}}.
|
||||
[GH-6141]
|
||||
* builder/hyper-v: User can now configure hyper-v disk block size. [GH-5941]
|
||||
* builder/openstack: Add configuration option for `instance_name`. [GH-6041]
|
||||
* builder/oracle-classic: Better validation of destination image name.
|
||||
[GH-6089]
|
||||
* builder/oracle-oci: New config options for user data and user data file.
|
||||
[GH-6079]
|
||||
* builder/oracle-oci: use the official OCI sdk instead of handcrafted client.
|
||||
[GH-6142]
|
||||
* builder/triton: Add support to Skip TLS Verification of Triton Certificate.
|
||||
[GH-6039]
|
||||
* provisioner/ansible: Ansible users may provide a custom inventory file.
|
||||
[GH-6107]
|
||||
* provisioner/file: New `generated` tag allows users to upload files created
|
||||
during Packer run. [GH-3891]
|
||||
user. This should prevent failures where we try to delete an AMI with
|
||||
a matching name, but owned by someone else. [GH-6111]
|
||||
|
||||
## 1.2.2 (March 26, 2018)
|
||||
|
||||
|
||||
@@ -4,13 +4,11 @@ VET?=$(shell ls -d */ | grep -v vendor | grep -v website)
|
||||
GITSHA:=$(shell git rev-parse HEAD)
|
||||
# Get the current local branch name from git (if we can, this may be blank)
|
||||
GITBRANCH:=$(shell git symbolic-ref --short HEAD 2>/dev/null)
|
||||
GOFMT_FILES?=$$(find . -not -path "./vendor/*" -name "*.go")
|
||||
GOOS=$(shell go env GOOS)
|
||||
GOARCH=$(shell go env GOARCH)
|
||||
GOPATH=$(shell go env GOPATH)
|
||||
|
||||
# gofmt
|
||||
UNFORMATTED_FILES=$(shell find . -not -path "./vendor/*" -name "*.go" | xargs gofmt -s -l)
|
||||
|
||||
# Get the git commit
|
||||
GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
||||
GIT_COMMIT=$(shell git rev-parse --short HEAD)
|
||||
@@ -43,11 +41,10 @@ package:
|
||||
@sh -c "$(CURDIR)/scripts/dist.sh $(VERSION)"
|
||||
|
||||
deps:
|
||||
@go get golang.org/x/tools/cmd/goimports
|
||||
@go get golang.org/x/tools/cmd/stringer
|
||||
@go get -u github.com/mna/pigeon
|
||||
@go get github.com/kardianos/govendor
|
||||
@go get golang.org/x/tools/cmd/goimports
|
||||
@govendor sync
|
||||
|
||||
dev: deps ## Build and install a development build
|
||||
@grep 'const VersionPrerelease = ""' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \
|
||||
@@ -61,18 +58,10 @@ dev: deps ## Build and install a development build
|
||||
@cp $(GOPATH)/bin/packer pkg/$(GOOS)_$(GOARCH)
|
||||
|
||||
fmt: ## Format Go code
|
||||
@gofmt -w -s main.go $(UNFORMATTED_FILES)
|
||||
@gofmt -w -s $(GOFMT_FILES)
|
||||
|
||||
fmt-check: ## Check go code formatting
|
||||
@echo "==> Checking that code complies with gofmt requirements..."
|
||||
@if [ ! -z "$(UNFORMATTED_FILES)" ]; then \
|
||||
echo "gofmt needs to be run on the following files:"; \
|
||||
echo "$(UNFORMATTED_FILES)" | xargs -n1; \
|
||||
echo "You can use the command: \`make fmt\` to reformat code."; \
|
||||
exit 1; \
|
||||
else \
|
||||
echo "Check passed."; \
|
||||
fi
|
||||
$(CURDIR)/scripts/gofmtcheck.sh $(GOFMT_FILES)
|
||||
|
||||
fmt-docs:
|
||||
@find ./website/source/docs -name "*.md" -exec pandoc --wrap auto --columns 79 --atx-headers -s -f "markdown_github+yaml_metadata_block" -t "markdown_github+yaml_metadata_block" {} -o {} \;
|
||||
|
||||
@@ -24,7 +24,7 @@ from a single source configuration.
|
||||
Packer is lightweight, runs on every major operating system, and is highly
|
||||
performant, creating machine images for multiple platforms in parallel. Packer
|
||||
comes out of the box with support for many platforms, the full list of which can
|
||||
be found at https://www.packer.io/docs/builders/index.html.
|
||||
be found at https://www.packer.io/docs/builders/index.html.
|
||||
|
||||
Support for other platforms can be added via plugins.
|
||||
|
||||
@@ -32,6 +32,10 @@ The images that Packer creates can easily be turned into
|
||||
[Vagrant](http://www.vagrantup.com) boxes.
|
||||
|
||||
## Quick Start
|
||||
Download and install packages and dependencies
|
||||
```
|
||||
go get github.com/hashicorp/packer
|
||||
```
|
||||
|
||||
**Note:** There is a great
|
||||
[introduction and getting started guide](https://www.packer.io/intro)
|
||||
|
||||
Vendored
-4
@@ -5,10 +5,6 @@ LINUX_BASE_BOX = "bento/ubuntu-16.04"
|
||||
FREEBSD_BASE_BOX = "jen20/FreeBSD-12.0-CURRENT"
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
if Vagrant.has_plugin?("vagrant-cachier")
|
||||
config.cache.scope = :box
|
||||
end
|
||||
|
||||
# Compilation and development boxes
|
||||
config.vm.define "linux", autostart: true, primary: true do |vmCfg|
|
||||
vmCfg.vm.box = LINUX_BASE_BOX
|
||||
|
||||
@@ -16,55 +16,8 @@ environment:
|
||||
clone_folder: c:\gopath\src\github.com\hashicorp\packer
|
||||
|
||||
install:
|
||||
- ps: |
|
||||
# Installs golang on Windows.
|
||||
#
|
||||
# # Run script:
|
||||
# .\install-go.ps1
|
||||
#
|
||||
# # Download and run script:
|
||||
# $env:GOVERSION = '1.5.3'
|
||||
# iex ((new-object net.webclient).DownloadString('SCRIPT_URL_HERE'))
|
||||
|
||||
$version = '1.11'
|
||||
|
||||
$downloadDir = $env:TEMP
|
||||
$packageName = 'golang'
|
||||
$url32 = 'https://storage.googleapis.com/golang/go' + $version + '.windows-386.zip'
|
||||
$url64 = 'https://storage.googleapis.com/golang/go' + $version + '.windows-amd64.zip'
|
||||
$goroot = "C:\go$version"
|
||||
|
||||
# Determine type of system
|
||||
if ($ENV:PROCESSOR_ARCHITECTURE -eq "AMD64") {
|
||||
$url = $url64
|
||||
} else {
|
||||
$url = $url32
|
||||
}
|
||||
|
||||
if (Test-Path "$goroot\bin\go") {
|
||||
Write-Host "Go is installed to $goroot"
|
||||
exit
|
||||
}
|
||||
|
||||
echo "Downloading $url"
|
||||
$zip = "$downloadDir\golang-$version.zip"
|
||||
if (!(Test-Path "$zip")) {
|
||||
$downloader = new-object System.Net.WebClient
|
||||
$downloader.DownloadFile($url, $zip)
|
||||
}
|
||||
|
||||
echo "Extracting $zip to $goroot"
|
||||
if (Test-Path "$downloadDir\go") {
|
||||
rm -Force -Recurse -Path "$downloadDir\go"
|
||||
}
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory("$zip", $downloadDir)
|
||||
mv "$downloadDir\go" $goroot
|
||||
- set GO15VENDOREXPERIMENT=1
|
||||
- set GOROOT=C:\go1.11
|
||||
- set Path=%GOROOT%\bin;%PATH%
|
||||
- echo %Path%
|
||||
- echo %GOROOT%
|
||||
- go version
|
||||
- go env
|
||||
- go get github.com/mitchellh/gox
|
||||
|
||||
@@ -67,8 +67,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AlicloudAccessKey, b.config.AlicloudSecretKey)
|
||||
log.Println(b.config)
|
||||
log.Println(common.ScrubConfig(b.config, b.config.AlicloudAccessKey, b.config.AlicloudSecretKey))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -95,11 +94,14 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
&stepCheckAlicloudSourceImage{
|
||||
SourceECSImageId: b.config.AlicloudSourceImage,
|
||||
},
|
||||
&stepConfigAlicloudKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.Comm,
|
||||
DebugKeyPath: fmt.Sprintf("ecs_%s.pem", b.config.PackerBuildName),
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
&StepConfigAlicloudKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
KeyPairName: b.config.SSHKeyPairName,
|
||||
PrivateKeyFile: b.config.Comm.SSHPrivateKey,
|
||||
TemporaryKeyPairName: b.config.TemporaryKeyPairName,
|
||||
SSHAgentAuth: b.config.Comm.SSHAgentAuth,
|
||||
DebugKeyPath: fmt.Sprintf("ecs_%s.pem", b.config.PackerBuildName),
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
},
|
||||
}
|
||||
if b.chooseNetworkType() == VpcNet {
|
||||
@@ -134,11 +136,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
ZoneId: b.config.ZoneId,
|
||||
})
|
||||
if b.chooseNetworkType() == VpcNet {
|
||||
steps = append(steps, &stepConfigAlicloudEIP{
|
||||
steps = append(steps, &setpConfigAlicloudEIP{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
InternetChargeType: b.config.InternetChargeType,
|
||||
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
|
||||
})
|
||||
} else {
|
||||
steps = append(steps, &stepConfigAlicloudPublicIP{
|
||||
@@ -146,7 +147,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
})
|
||||
}
|
||||
steps = append(steps,
|
||||
&stepAttachKeyPair{},
|
||||
&stepAttachKeyPar{},
|
||||
&stepRunAlicloudInstance{},
|
||||
&stepMountAlicloudDisk{},
|
||||
&communicator.StepConnect{
|
||||
@@ -154,7 +155,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
Host: SSHHost(
|
||||
client,
|
||||
b.config.SSHPrivateIp),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
SSHConfig: SSHConfig(
|
||||
b.config.RunConfig.Comm.SSHAgentAuth,
|
||||
b.config.RunConfig.Comm.SSHUsername,
|
||||
b.config.RunConfig.Comm.SSHPassword),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&stepStopAlicloudInstance{
|
||||
@@ -166,12 +170,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
AlicloudImageName: b.config.AlicloudImageName,
|
||||
},
|
||||
&stepCreateAlicloudImage{},
|
||||
&stepRegionCopyAlicloudImage{
|
||||
&setpRegionCopyAlicloudImage{
|
||||
AlicloudImageDestinationRegions: b.config.AlicloudImageDestinationRegions,
|
||||
AlicloudImageDestinationNames: b.config.AlicloudImageDestinationNames,
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
},
|
||||
&stepShareAlicloudImage{
|
||||
&setpShareAlicloudImage{
|
||||
AlicloudImageShareAccounts: b.config.AlicloudImageShareAccounts,
|
||||
AlicloudImageUNShareAccounts: b.config.AlicloudImageUNShareAccounts,
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
@@ -227,7 +231,7 @@ func (b *Builder) isVpcSpecified() bool {
|
||||
|
||||
func (b *Builder) isUserDataNeeded() bool {
|
||||
// Public key setup requires userdata
|
||||
if b.config.RunConfig.Comm.SSHPrivateKeyFile != "" {
|
||||
if b.config.RunConfig.Comm.SSHPrivateKey != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -235,5 +239,5 @@ func (b *Builder) isUserDataNeeded() bool {
|
||||
}
|
||||
|
||||
func (b *Builder) isKeyPairNeeded() bool {
|
||||
return b.config.Comm.SSHKeyPairName != "" || b.config.Comm.SSHTemporaryKeyPairName != ""
|
||||
return b.config.SSHKeyPairName != "" || b.config.TemporaryKeyPairName != ""
|
||||
}
|
||||
|
||||
@@ -31,17 +31,19 @@ type RunConfig struct {
|
||||
InstanceName string `mapstructure:"instance_name"`
|
||||
InternetChargeType string `mapstructure:"internet_charge_type"`
|
||||
InternetMaxBandwidthOut int `mapstructure:"internet_max_bandwidth_out"`
|
||||
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
|
||||
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHKeyPairName string `mapstructure:"ssh_keypair_name"`
|
||||
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
if c.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" &&
|
||||
c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" && c.Comm.WinRMPassword == "" {
|
||||
if c.SSHKeyPairName == "" && c.TemporaryKeyPairName == "" &&
|
||||
c.Comm.SSHPrivateKey == "" && c.Comm.SSHPassword == "" && c.Comm.WinRMPassword == "" {
|
||||
|
||||
c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
c.TemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
// Validation
|
||||
|
||||
@@ -2,7 +2,6 @@ package ecs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
@@ -69,7 +68,6 @@ func TestRunConfigPrepare_UserData(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
c.UserData = "foo"
|
||||
@@ -94,7 +92,6 @@ func TestRunConfigPrepare_UserDataFile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
c.UserDataFile = tf.Name()
|
||||
@@ -105,21 +102,21 @@ func TestRunConfigPrepare_UserDataFile(t *testing.T) {
|
||||
|
||||
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.Comm.SSHTemporaryKeyPairName = ""
|
||||
c.TemporaryKeyPairName = ""
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.Comm.SSHTemporaryKeyPairName == "" {
|
||||
if c.TemporaryKeyPairName == "" {
|
||||
t.Fatal("keypair name is empty")
|
||||
}
|
||||
|
||||
c.Comm.SSHTemporaryKeyPairName = "ssh-key-123"
|
||||
c.TemporaryKeyPairName = "ssh-key-123"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.Comm.SSHTemporaryKeyPairName != "ssh-key-123" {
|
||||
if c.TemporaryKeyPairName != "ssh-key-123" {
|
||||
t.Fatal("keypair name does not match")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
packerssh "github.com/hashicorp/packer/communicator/ssh"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -21,3 +27,57 @@ func SSHHost(e alicloudSSHHelper, private bool) func(multistep.StateBag) (string
|
||||
return ipAddress, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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(useAgent bool, username, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
if useAgent {
|
||||
authSock := os.Getenv("SSH_AUTH_SOCK")
|
||||
if authSock == "" {
|
||||
return nil, fmt.Errorf("SSH_AUTH_SOCK is not set")
|
||||
}
|
||||
|
||||
sshAgent, err := net.Dial("unix", authSock)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot connect to SSH Agent socket %q: %s", authSock, err)
|
||||
}
|
||||
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers),
|
||||
},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
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),
|
||||
},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}, nil
|
||||
|
||||
} else {
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password(password),
|
||||
ssh.KeyboardInteractive(
|
||||
packerssh.PasswordKeyboardInteractive(password)),
|
||||
},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,19 +12,19 @@ import (
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepAttachKeyPair struct {
|
||||
type stepAttachKeyPar struct {
|
||||
}
|
||||
|
||||
func (s *stepAttachKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *stepAttachKeyPar) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
keyPairName := state.Get("keyPair").(string)
|
||||
if keyPairName == "" {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(Config)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
timeoutPoint := time.Now().Add(120 * time.Second)
|
||||
keyPairName := config.Comm.SSHKeyPairName
|
||||
if keyPairName == "" {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
for {
|
||||
err := client.AttachKeyPair(&ecs.AttachKeyPairArgs{RegionId: common.Region(config.AlicloudRegion),
|
||||
KeyPairName: keyPairName, InstanceIds: "[\"" + instance.InstanceId + "\"]"})
|
||||
@@ -50,15 +50,15 @@ func (s *stepAttachKeyPair) Run(_ context.Context, state multistep.StateBag) mul
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepAttachKeyPair) Cleanup(state multistep.StateBag) {
|
||||
func (s *stepAttachKeyPar) Cleanup(state multistep.StateBag) {
|
||||
keyPairName := state.Get("keyPair").(string)
|
||||
if keyPairName == "" {
|
||||
return
|
||||
}
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
keyPairName := config.Comm.SSHKeyPairName
|
||||
if keyPairName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
err := client.DetachKeyPair(&ecs.DetachKeyPairArgs{RegionId: common.Region(config.AlicloudRegion),
|
||||
KeyPairName: keyPairName, InstanceIds: "[\"" + instance.InstanceId + "\"]"})
|
||||
|
||||
@@ -18,12 +18,8 @@ func (s *stepCheckAlicloudSourceImage) Run(_ context.Context, state multistep.St
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
args := &ecs.DescribeImagesArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
ImageId: config.AlicloudSourceImage,
|
||||
}
|
||||
args.PageSize = 50
|
||||
images, _, err := client.DescribeImages(args)
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{RegionId: common.Region(config.AlicloudRegion),
|
||||
ImageId: config.AlicloudSourceImage})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying alicloud image: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -31,19 +27,6 @@ func (s *stepCheckAlicloudSourceImage) Run(_ context.Context, state multistep.St
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Describe markerplace image
|
||||
args.ImageOwnerAlias = ecs.ImageOwnerMarketplace
|
||||
imageMarkets, _, err := client.DescribeImages(args)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying alicloud marketplace image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if len(imageMarkets) > 0 {
|
||||
images = append(images, imageMarkets...)
|
||||
}
|
||||
|
||||
if len(images) == 0 {
|
||||
err := fmt.Errorf("No alicloud image was found matching filters: %v", config.AlicloudSourceImage)
|
||||
state.Put("error", err)
|
||||
|
||||
@@ -10,22 +10,20 @@ import (
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepConfigAlicloudEIP struct {
|
||||
type setpConfigAlicloudEIP struct {
|
||||
AssociatePublicIpAddress bool
|
||||
RegionId string
|
||||
InternetChargeType string
|
||||
InternetMaxBandwidthOut int
|
||||
allocatedId string
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudEIP) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *setpConfigAlicloudEIP) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
ui.Say("Allocating eip")
|
||||
ipaddress, allocateId, err := client.AllocateEipAddress(&ecs.AllocateEipAddressArgs{
|
||||
RegionId: common.Region(s.RegionId), InternetChargeType: common.InternetChargeType(s.InternetChargeType),
|
||||
Bandwidth: s.InternetMaxBandwidthOut,
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
@@ -57,7 +55,7 @@ func (s *stepConfigAlicloudEIP) Run(_ context.Context, state multistep.StateBag)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudEIP) Cleanup(state multistep.StateBag) {
|
||||
func (s *setpConfigAlicloudEIP) Cleanup(state multistep.StateBag) {
|
||||
if len(s.allocatedId) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -9,58 +9,62 @@ import (
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepConfigAlicloudKeyPair struct {
|
||||
Debug bool
|
||||
Comm *communicator.Config
|
||||
DebugKeyPath string
|
||||
RegionId string
|
||||
type StepConfigAlicloudKeyPair struct {
|
||||
Debug bool
|
||||
SSHAgentAuth bool
|
||||
DebugKeyPath string
|
||||
TemporaryKeyPairName string
|
||||
KeyPairName string
|
||||
PrivateKeyFile string
|
||||
RegionId string
|
||||
|
||||
keyName string
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepConfigAlicloudKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if s.Comm.SSHPrivateKeyFile != "" {
|
||||
if s.PrivateKeyFile != "" {
|
||||
ui.Say("Using existing SSH private key")
|
||||
privateKeyBytes, err := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile)
|
||||
privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf(
|
||||
"Error loading configured private key file: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.Comm.SSHPrivateKey = privateKeyBytes
|
||||
state.Put("keyPair", s.KeyPairName)
|
||||
state.Put("privateKey", string(privateKeyBytes))
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName == "" {
|
||||
if s.SSHAgentAuth && s.KeyPairName == "" {
|
||||
ui.Say("Using SSH Agent with key pair in source image")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName != "" {
|
||||
ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.Comm.SSHKeyPairName))
|
||||
if s.SSHAgentAuth && s.KeyPairName != "" {
|
||||
ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.KeyPairName))
|
||||
state.Put("keyPair", s.KeyPairName)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHTemporaryKeyPairName == "" {
|
||||
if s.TemporaryKeyPairName == "" {
|
||||
ui.Say("Not using temporary keypair")
|
||||
s.Comm.SSHKeyPairName = ""
|
||||
state.Put("keyPair", "")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
|
||||
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.TemporaryKeyPairName))
|
||||
keyResp, err := client.CreateKeyPair(&ecs.CreateKeyPairArgs{
|
||||
KeyPairName: s.Comm.SSHTemporaryKeyPairName,
|
||||
KeyPairName: s.TemporaryKeyPairName,
|
||||
RegionId: common.Region(s.RegionId),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -69,11 +73,11 @@ func (s *stepConfigAlicloudKeyPair) Run(_ context.Context, state multistep.State
|
||||
}
|
||||
|
||||
// Set the keyname so we know to delete it later
|
||||
s.keyName = s.Comm.SSHTemporaryKeyPairName
|
||||
s.keyName = s.TemporaryKeyPairName
|
||||
|
||||
// Set some state data for use in future steps
|
||||
s.Comm.SSHKeyPairName = s.keyName
|
||||
s.Comm.SSHPrivateKey = []byte(keyResp.PrivateKeyBody)
|
||||
state.Put("keyPair", s.keyName)
|
||||
state.Put("privateKey", keyResp.PrivateKeyBody)
|
||||
|
||||
// If we're in debug mode, output the private key to the working
|
||||
// directory.
|
||||
@@ -104,11 +108,11 @@ func (s *stepConfigAlicloudKeyPair) Run(_ context.Context, state multistep.State
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudKeyPair) Cleanup(state multistep.StateBag) {
|
||||
func (s *StepConfigAlicloudKeyPair) 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.Comm.SSHPrivateKeyFile != "" || (s.Comm.SSHKeyPairName == "" && s.keyName == "") {
|
||||
if s.PrivateKeyFile != "" || (s.KeyPairName == "" && s.keyName == "") {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -85,8 +85,7 @@ func (s *stepConfigAlicloudVPC) Cleanup(state multistep.StateBag) {
|
||||
e, _ := err.(*common.Error)
|
||||
if (e.Code == "DependencyViolation.Instance" || e.Code == "DependencyViolation.RouteEntry" ||
|
||||
e.Code == "DependencyViolation.VSwitch" ||
|
||||
e.Code == "DependencyViolation.SecurityGroup" ||
|
||||
e.Code == "Forbbiden") && time.Now().Before(timeoutPoint) {
|
||||
e.Code == "DependencyViolation.SecurityGroup") && time.Now().Before(timeoutPoint) {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepRegionCopyAlicloudImage struct {
|
||||
type setpRegionCopyAlicloudImage struct {
|
||||
AlicloudImageDestinationRegions []string
|
||||
AlicloudImageDestinationNames []string
|
||||
RegionId string
|
||||
}
|
||||
|
||||
func (s *stepRegionCopyAlicloudImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *setpRegionCopyAlicloudImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
if len(s.AlicloudImageDestinationRegions) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
@@ -52,7 +52,7 @@ func (s *stepRegionCopyAlicloudImage) Run(_ context.Context, state multistep.Sta
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepRegionCopyAlicloudImage) Cleanup(state multistep.StateBag) {
|
||||
func (s *setpRegionCopyAlicloudImage) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if cancelled || halted {
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepShareAlicloudImage struct {
|
||||
type setpShareAlicloudImage struct {
|
||||
AlicloudImageShareAccounts []string
|
||||
AlicloudImageUNShareAccounts []string
|
||||
RegionId string
|
||||
}
|
||||
|
||||
func (s *stepShareAlicloudImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *setpShareAlicloudImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
alicloudImages := state.Get("alicloudimages").(map[string]string)
|
||||
@@ -37,7 +37,7 @@ func (s *stepShareAlicloudImage) Run(_ context.Context, state multistep.StateBag
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepShareAlicloudImage) Cleanup(state multistep.StateBag) {
|
||||
func (s *setpShareAlicloudImage) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if cancelled || halted {
|
||||
|
||||
@@ -33,10 +33,9 @@ type Config struct {
|
||||
CommandWrapper string `mapstructure:"command_wrapper"`
|
||||
CopyFiles []string `mapstructure:"copy_files"`
|
||||
DevicePath string `mapstructure:"device_path"`
|
||||
NVMEDevicePath string `mapstructure:"nvme_device_path"`
|
||||
FromScratch bool `mapstructure:"from_scratch"`
|
||||
MountOptions []string `mapstructure:"mount_options"`
|
||||
MountPartition string `mapstructure:"mount_partition"`
|
||||
MountPartition int `mapstructure:"mount_partition"`
|
||||
MountPath string `mapstructure:"mount_path"`
|
||||
PostMountCommands []string `mapstructure:"post_mount_commands"`
|
||||
PreMountCommands []string `mapstructure:"pre_mount_commands"`
|
||||
@@ -44,7 +43,6 @@ type Config struct {
|
||||
RootVolumeSize int64 `mapstructure:"root_volume_size"`
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
SourceAmiFilter awscommon.AmiFilterOptions `mapstructure:"source_ami_filter"`
|
||||
RootVolumeTags awscommon.TagMap `mapstructure:"root_volume_tags"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
@@ -68,7 +66,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
"ami_description",
|
||||
"snapshot_tags",
|
||||
"tags",
|
||||
"root_volume_tags",
|
||||
"command_wrapper",
|
||||
"post_mount_commands",
|
||||
"pre_mount_commands",
|
||||
@@ -115,8 +112,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
b.config.MountPath = "/mnt/packer-amazon-chroot-volumes/{{.Device}}"
|
||||
}
|
||||
|
||||
if b.config.MountPartition == "" {
|
||||
b.config.MountPartition = "1"
|
||||
if b.config.MountPartition == 0 {
|
||||
b.config.MountPartition = 1
|
||||
}
|
||||
|
||||
// Accumulate any errors or warnings
|
||||
@@ -176,8 +173,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
return warns, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
log.Println(b.config)
|
||||
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey, b.config.Token))
|
||||
return warns, nil
|
||||
}
|
||||
|
||||
@@ -233,8 +229,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
&StepPrepareDevice{},
|
||||
&StepCreateVolume{
|
||||
RootVolumeSize: b.config.RootVolumeSize,
|
||||
RootVolumeTags: b.config.RootVolumeTags,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
&StepAttachVolume{},
|
||||
&StepEarlyUnflock{},
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
@@ -24,10 +23,8 @@ type Communicator struct {
|
||||
}
|
||||
|
||||
func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
|
||||
// need extra escapes for the command since we're wrapping it in quotes
|
||||
cmd.Command = strconv.Quote(cmd.Command)
|
||||
command, err := c.CmdWrapper(
|
||||
fmt.Sprintf("chroot %s /bin/sh -c %s", c.Chroot, cmd.Command))
|
||||
fmt.Sprintf("chroot %s /bin/sh -c \"%s\"", c.Chroot, cmd.Command))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package chroot
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sl "github.com/hashicorp/packer/common/shell-local"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/post-processor/shell-local"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
@@ -21,9 +21,7 @@ func RunLocalCommands(commands []string, wrappedCommand CommandWrapper, ctx inte
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Executing command: %s", command))
|
||||
comm := &sl.Communicator{
|
||||
ExecuteCommand: []string{"sh", "-c", 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)
|
||||
|
||||
@@ -2,10 +2,11 @@ package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
@@ -23,7 +24,7 @@ type StepAttachVolume struct {
|
||||
volumeId string
|
||||
}
|
||||
|
||||
func (s *StepAttachVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepAttachVolume) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
device := state.Get("device").(string)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
@@ -51,7 +52,35 @@ func (s *StepAttachVolume) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
s.volumeId = volumeId
|
||||
|
||||
// Wait for the volume to become attached
|
||||
err = awscommon.WaitUntilVolumeAttached(ctx, ec2conn, s.volumeId)
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"attaching"},
|
||||
StepState: state,
|
||||
Target: "attached",
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
attempts := 0
|
||||
for attempts < 30 {
|
||||
resp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesInput{VolumeIds: []*string{&volumeId}})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if len(resp.Volumes[0].Attachments) > 0 {
|
||||
a := resp.Volumes[0].Attachments[0]
|
||||
return a, *a.State, nil
|
||||
}
|
||||
// When Attachment on volume is not present sleep for 2s and retry
|
||||
attempts += 1
|
||||
ui.Say(fmt.Sprintf(
|
||||
"Volume %s show no attachments. Attempt %d/30. Sleeping for 2s and will retry.",
|
||||
volumeId, attempts))
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
// Attachment on volume is not present after all attempts
|
||||
return nil, "", errors.New("No attachments on volume.")
|
||||
},
|
||||
}
|
||||
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for volume: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -87,7 +116,26 @@ func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error {
|
||||
s.attached = false
|
||||
|
||||
// Wait for the volume to detach
|
||||
err = awscommon.WaitUntilVolumeDetached(aws.BackgroundContext(), ec2conn, s.volumeId)
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"attaching", "attached", "detaching"},
|
||||
StepState: state,
|
||||
Target: "detached",
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
resp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesInput{VolumeIds: []*string{&s.volumeId}})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
v := resp.Volumes[0]
|
||||
if len(v.Attachments) > 0 {
|
||||
return v, *v.Attachments[0].State, nil
|
||||
} else {
|
||||
return v, "detached", nil
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error waiting for volume: %s", err)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// StepCreateVolume creates a new volume from the snapshot of the root
|
||||
@@ -21,36 +20,14 @@ import (
|
||||
type StepCreateVolume struct {
|
||||
volumeId string
|
||||
RootVolumeSize int64
|
||||
RootVolumeTags awscommon.TagMap
|
||||
Ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepCreateVolume) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
volTags, err := s.RootVolumeTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error tagging volumes: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Collect tags for tagging on resource creation
|
||||
var tagSpecs []*ec2.TagSpecification
|
||||
|
||||
if len(volTags) > 0 {
|
||||
runVolTags := &ec2.TagSpecification{
|
||||
ResourceType: aws.String("volume"),
|
||||
Tags: volTags,
|
||||
}
|
||||
|
||||
tagSpecs = append(tagSpecs, runVolTags)
|
||||
}
|
||||
|
||||
var createVolume *ec2.CreateVolumeInput
|
||||
if config.FromScratch {
|
||||
createVolume = &ec2.CreateVolumeInput{
|
||||
@@ -92,10 +69,6 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
}
|
||||
}
|
||||
|
||||
if len(tagSpecs) > 0 {
|
||||
createVolume.SetTagSpecifications(tagSpecs)
|
||||
volTags.Report(ui)
|
||||
}
|
||||
log.Printf("Create args: %+v", createVolume)
|
||||
|
||||
createVolumeResp, err := ec2conn.CreateVolume(createVolume)
|
||||
@@ -111,7 +84,22 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
log.Printf("Volume ID: %s", s.volumeId)
|
||||
|
||||
// Wait for the volume to become ready
|
||||
err = awscommon.WaitUntilVolumeAvailable(ctx, ec2conn, s.volumeId)
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"creating"},
|
||||
StepState: state,
|
||||
Target: "available",
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
resp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesInput{VolumeIds: []*string{&s.volumeId}})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
v := resp.Volumes[0]
|
||||
return v, *v.State, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for volume: %s", err)
|
||||
state.Put("error", err)
|
||||
|
||||
@@ -26,7 +26,7 @@ type mountPathData struct {
|
||||
// mount_device_cleanup CleanupFunc - To perform early cleanup
|
||||
type StepMountDevice struct {
|
||||
MountOptions []string
|
||||
MountPartition string
|
||||
MountPartition int
|
||||
|
||||
mountPath string
|
||||
}
|
||||
@@ -35,10 +35,6 @@ func (s *StepMountDevice) Run(_ context.Context, state multistep.StateBag) multi
|
||||
config := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
device := state.Get("device").(string)
|
||||
if config.NVMEDevicePath != "" {
|
||||
// customizable device path for mounting NVME block devices on c5 and m5 HVM
|
||||
device = config.NVMEDevicePath
|
||||
}
|
||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||
|
||||
var virtualizationType string
|
||||
@@ -51,7 +47,6 @@ func (s *StepMountDevice) Run(_ context.Context, state multistep.StateBag) multi
|
||||
}
|
||||
|
||||
ctx := config.ctx
|
||||
|
||||
ctx.Data = &mountPathData{Device: filepath.Base(device)}
|
||||
mountPath, err := interpolate.Render(config.MountPath, &ctx)
|
||||
|
||||
@@ -80,9 +75,8 @@ func (s *StepMountDevice) Run(_ context.Context, state multistep.StateBag) multi
|
||||
}
|
||||
|
||||
deviceMount := device
|
||||
|
||||
if virtualizationType == "hvm" && s.MountPartition != "0" {
|
||||
deviceMount = fmt.Sprintf("%s%s", device, s.MountPartition)
|
||||
if virtualizationType == "hvm" {
|
||||
deviceMount = fmt.Sprintf("%s%d", device, s.MountPartition)
|
||||
}
|
||||
state.Put("deviceMount", deviceMount)
|
||||
|
||||
@@ -103,7 +97,7 @@ func (s *StepMountDevice) Run(_ context.Context, state multistep.StateBag) multi
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
log.Printf("[DEBUG] (step mount) mount command is %s", mountCommand)
|
||||
|
||||
cmd := ShellCommand(mountCommand)
|
||||
cmd.Stderr = stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
|
||||
@@ -18,7 +18,7 @@ type StepRegisterAMI struct {
|
||||
EnableAMISriovNetSupport bool
|
||||
}
|
||||
|
||||
func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepRegisterAMI) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
snapshotId := state.Get("snapshot_id").(string)
|
||||
@@ -102,8 +102,16 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
amis[*ec2conn.Config.Region] = *registerResp.ImageId
|
||||
state.Put("amis", amis)
|
||||
|
||||
// Wait for the image to become ready
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
Target: "available",
|
||||
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *registerResp.ImageId),
|
||||
StepState: state,
|
||||
}
|
||||
|
||||
ui.Say("Waiting for AMI to become ready...")
|
||||
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
|
||||
if _, err := awscommon.WaitForState(&stateChange); err != nil {
|
||||
err := fmt.Errorf("Error waiting for AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
|
||||
@@ -2,6 +2,7 @@ package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -19,7 +20,7 @@ type StepSnapshot struct {
|
||||
snapshotId string
|
||||
}
|
||||
|
||||
func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
volumeId := state.Get("volume_id").(string)
|
||||
@@ -43,7 +44,26 @@ func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
|
||||
ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId))
|
||||
|
||||
// Wait for the snapshot to be ready
|
||||
err = awscommon.WaitUntilSnapshotDone(ctx, ec2conn, s.snapshotId)
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
StepState: state,
|
||||
Target: "completed",
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{SnapshotIds: []*string{&s.snapshotId}})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(resp.Snapshots) == 0 {
|
||||
return nil, "", errors.New("No snapshots found.")
|
||||
}
|
||||
|
||||
s := resp.Snapshots[0]
|
||||
return s, *s.State, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for snapshot: %s", err)
|
||||
state.Put("error", err)
|
||||
|
||||
@@ -5,7 +5,6 @@ func listEC2Regions() []string {
|
||||
return []string{
|
||||
"ap-northeast-1",
|
||||
"ap-northeast-2",
|
||||
"ap-northeast-3",
|
||||
"ap-south-1",
|
||||
"ap-southeast-1",
|
||||
"ap-southeast-2",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/common/uuid"
|
||||
@@ -25,39 +25,35 @@ func (d *AmiFilterOptions) Empty() bool {
|
||||
return len(d.Owners) == 0 && len(d.Filters) == 0
|
||||
}
|
||||
|
||||
func (d *AmiFilterOptions) NoOwner() bool {
|
||||
return len(d.Owners) == 0
|
||||
}
|
||||
|
||||
// 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"`
|
||||
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
|
||||
EbsOptimized bool `mapstructure:"ebs_optimized"`
|
||||
EnableT2Unlimited bool `mapstructure:"enable_t2_unlimited"`
|
||||
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
|
||||
InstanceInitiatedShutdownBehavior string `mapstructure:"shutdown_behavior"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
RunTags map[string]string `mapstructure:"run_tags"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SecurityGroupIds []string `mapstructure:"security_group_ids"`
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
SourceAmiFilter AmiFilterOptions `mapstructure:"source_ami_filter"`
|
||||
SpotPrice string `mapstructure:"spot_price"`
|
||||
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
|
||||
SpotTags map[string]string `mapstructure:"spot_tags"`
|
||||
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SecurityGroupIds []string `mapstructure:"security_group_ids"`
|
||||
TemporarySGSourceCidr string `mapstructure:"temporary_security_group_source_cidr"`
|
||||
SubnetId string `mapstructure:"subnet_id"`
|
||||
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
|
||||
TemporarySGSourceCidr string `mapstructure:"temporary_security_group_source_cidr"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
UserDataFile string `mapstructure:"user_data_file"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
InstanceInitiatedShutdownBehavior string `mapstructure:"shutdown_behavior"`
|
||||
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHKeyPairName string `mapstructure:"ssh_keypair_name"`
|
||||
SSHInterface string `mapstructure:"ssh_interface"`
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
@@ -65,10 +61,10 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
// 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.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" &&
|
||||
c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" {
|
||||
if c.SSHKeyPairName == "" && c.TemporaryKeyPairName == "" &&
|
||||
c.Comm.SSHPrivateKey == "" && c.Comm.SSHPassword == "" {
|
||||
|
||||
c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
c.TemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
if c.WindowsPasswordTimeout == 0 {
|
||||
@@ -83,55 +79,37 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
errs := c.Comm.Prepare(ctx)
|
||||
|
||||
// Validating ssh_interface
|
||||
if c.Comm.SSHInterface != "public_ip" &&
|
||||
c.Comm.SSHInterface != "private_ip" &&
|
||||
c.Comm.SSHInterface != "public_dns" &&
|
||||
c.Comm.SSHInterface != "private_dns" &&
|
||||
c.Comm.SSHInterface != "" {
|
||||
errs = append(errs, fmt.Errorf("Unknown interface type: %s", c.Comm.SSHInterface))
|
||||
if c.SSHInterface != "public_ip" &&
|
||||
c.SSHInterface != "private_ip" &&
|
||||
c.SSHInterface != "public_dns" &&
|
||||
c.SSHInterface != "private_dns" &&
|
||||
c.SSHInterface != "" {
|
||||
errs = append(errs, errors.New(fmt.Sprintf("Unknown interface type: %s", c.SSHInterface)))
|
||||
}
|
||||
|
||||
if c.Comm.SSHKeyPairName != "" {
|
||||
if c.Comm.Type == "winrm" && c.Comm.WinRMPassword == "" && c.Comm.SSHPrivateKeyFile == "" {
|
||||
errs = append(errs, fmt.Errorf("ssh_private_key_file must be provided to retrieve the winrm password when using ssh_keypair_name."))
|
||||
} else if c.Comm.SSHPrivateKeyFile == "" && !c.Comm.SSHAgentAuth {
|
||||
errs = append(errs, fmt.Errorf("ssh_private_key_file must be provided or ssh_agent_auth enabled when ssh_keypair_name is specified."))
|
||||
if c.SSHKeyPairName != "" {
|
||||
if c.Comm.Type == "winrm" && c.Comm.WinRMPassword == "" && c.Comm.SSHPrivateKey == "" {
|
||||
errs = append(errs, errors.New("ssh_private_key_file must be provided to retrieve the winrm password when using ssh_keypair_name."))
|
||||
} else if c.Comm.SSHPrivateKey == "" && !c.Comm.SSHAgentAuth {
|
||||
errs = append(errs, errors.New("ssh_private_key_file must be provided or ssh_agent_auth enabled when ssh_keypair_name is specified."))
|
||||
}
|
||||
}
|
||||
|
||||
if c.SourceAmi == "" && c.SourceAmiFilter.Empty() {
|
||||
errs = append(errs, fmt.Errorf("A source_ami or source_ami_filter must be specified"))
|
||||
}
|
||||
|
||||
if c.SourceAmi == "" && c.SourceAmiFilter.NoOwner() {
|
||||
errs = append(errs, fmt.Errorf("For security reasons, your source AMI filter must declare an owner."))
|
||||
errs = append(errs, errors.New("A source_ami or source_ami_filter must be specified"))
|
||||
}
|
||||
|
||||
if c.InstanceType == "" {
|
||||
errs = append(errs, fmt.Errorf("An instance_type must be specified"))
|
||||
errs = append(errs, errors.New("An instance_type must be specified"))
|
||||
}
|
||||
|
||||
if c.SpotPrice == "auto" {
|
||||
if c.SpotPriceAutoProduct == "" {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
errs = append(errs, errors.New(
|
||||
"spot_price_auto_product must be specified when spot_price is auto"))
|
||||
}
|
||||
}
|
||||
|
||||
if c.SpotPriceAutoProduct != "" {
|
||||
if c.SpotPrice != "auto" {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"spot_price should be set to auto when spot_price_auto_product is specified"))
|
||||
}
|
||||
}
|
||||
|
||||
if c.SpotTags != nil {
|
||||
if c.SpotPrice == "" || c.SpotPrice == "0" {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"spot_tags should not be set when not requesting a spot instance"))
|
||||
}
|
||||
}
|
||||
|
||||
if c.UserData != "" && c.UserDataFile != "" {
|
||||
errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
|
||||
} else if c.UserDataFile != "" {
|
||||
@@ -163,18 +141,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
errs = append(errs, fmt.Errorf("shutdown_behavior only accepts 'stop' or 'terminate' values."))
|
||||
}
|
||||
|
||||
if c.EnableT2Unlimited {
|
||||
if c.SpotPrice != "" {
|
||||
errs = append(errs, fmt.Errorf("Error: T2 Unlimited cannot be used in conjuction with Spot Instances"))
|
||||
}
|
||||
firstDotIndex := strings.Index(c.InstanceType, ".")
|
||||
if firstDotIndex == -1 {
|
||||
errs = append(errs, fmt.Errorf("Error determining main Instance Type from: %s", c.InstanceType))
|
||||
} else if c.InstanceType[0:firstDotIndex] != "t2" {
|
||||
errs = append(errs, fmt.Errorf("Error: T2 Unlimited enabled with a non-T2 Instance Type: %s", c.InstanceType))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
|
||||
@@ -48,32 +48,22 @@ func TestRunConfigPrepare_InstanceType(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.InstanceType = ""
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if an instance_type is not specified")
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceAmi(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.SourceAmi = ""
|
||||
if err := c.Prepare(nil); len(err) != 2 {
|
||||
t.Fatalf("Should error if a source_ami (or source_ami_filter) is not specified")
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceAmiFilterBlank(t *testing.T) {
|
||||
c := testConfigFilter()
|
||||
if err := c.Prepare(nil); len(err) != 2 {
|
||||
t.Fatalf("Should error if source_ami_filter is empty or not specified (and source_ami is not specified)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceAmiFilterOwnersBlank(t *testing.T) {
|
||||
c := testConfigFilter()
|
||||
filter_key := "name"
|
||||
filter_value := "foo"
|
||||
c.SourceAmiFilter = AmiFilterOptions{Filters: map[*string]*string{&filter_key: &filter_value}}
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if Owners is not specified)")
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,57 +79,16 @@ func TestRunConfigPrepare_SourceAmiFilterGood(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_EnableT2UnlimitedGood(t *testing.T) {
|
||||
c := testConfig()
|
||||
// Must have a T2 instance type if T2 Unlimited is enabled
|
||||
c.InstanceType = "t2.micro"
|
||||
c.EnableT2Unlimited = true
|
||||
err := c.Prepare(nil)
|
||||
if len(err) > 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_EnableT2UnlimitedBadInstanceType(t *testing.T) {
|
||||
c := testConfig()
|
||||
// T2 Unlimited cannot be used with instance types other than T2
|
||||
c.InstanceType = "m5.large"
|
||||
c.EnableT2Unlimited = true
|
||||
err := c.Prepare(nil)
|
||||
if len(err) != 1 {
|
||||
t.Fatalf("Should error if T2 Unlimited is enabled with non-T2 instance_type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_EnableT2UnlimitedBadWithSpotInstanceRequest(t *testing.T) {
|
||||
c := testConfig()
|
||||
// T2 Unlimited cannot be used with Spot Instances
|
||||
c.InstanceType = "t2.micro"
|
||||
c.EnableT2Unlimited = true
|
||||
c.SpotPrice = "auto"
|
||||
c.SpotPriceAutoProduct = "Linux/UNIX"
|
||||
err := c.Prepare(nil)
|
||||
if len(err) != 1 {
|
||||
t.Fatalf("Should error if T2 Unlimited has been used in conjuntion with a Spot Price request")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SpotAuto(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.SpotPrice = "auto"
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if spot_price_auto_product is not set and spot_price is set to auto")
|
||||
}
|
||||
|
||||
// Good - SpotPrice and SpotPriceAutoProduct are correctly set
|
||||
c.SpotPriceAutoProduct = "foo"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c.SpotPrice = ""
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if spot_price is not set to auto and spot_price_auto_product is set")
|
||||
c.SpotPriceAutoProduct = "foo"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,13 +119,12 @@ func TestRunConfigPrepare_UserData(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
c.UserData = "foo"
|
||||
c.UserDataFile = tf.Name()
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if user_data string and user_data_file have both been specified")
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,14 +136,13 @@ func TestRunConfigPrepare_UserDataFile(t *testing.T) {
|
||||
|
||||
c.UserDataFile = "idontexistidontthink"
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("Should error if the file specified by user_data_file does not exist")
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
c.UserDataFile = tf.Name()
|
||||
@@ -206,27 +153,27 @@ func TestRunConfigPrepare_UserDataFile(t *testing.T) {
|
||||
|
||||
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.Comm.SSHTemporaryKeyPairName = ""
|
||||
c.TemporaryKeyPairName = ""
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.Comm.SSHTemporaryKeyPairName == "" {
|
||||
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.Comm.SSHTemporaryKeyPairName) {
|
||||
if !r.MatchString(c.TemporaryKeyPairName) {
|
||||
t.Fatal("keypair name is not valid")
|
||||
}
|
||||
|
||||
c.Comm.SSHTemporaryKeyPairName = "ssh-key-123"
|
||||
c.TemporaryKeyPairName = "ssh-key-123"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.Comm.SSHTemporaryKeyPairName != "ssh-key-123" {
|
||||
if c.TemporaryKeyPairName != "ssh-key-123" {
|
||||
t.Fatal("keypair name does not match")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,15 @@ package common
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
packerssh "github.com/hashicorp/packer/communicator/ssh"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
type ec2Describer interface {
|
||||
@@ -80,3 +85,56 @@ func SSHHost(e ec2Describer, sshInterface string) func(multistep.StateBag) (stri
|
||||
return "", errors.New("couldn't determine address for instance")
|
||||
}
|
||||
}
|
||||
|
||||
// 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(useAgent bool, username, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
if useAgent {
|
||||
authSock := os.Getenv("SSH_AUTH_SOCK")
|
||||
if authSock == "" {
|
||||
return nil, fmt.Errorf("SSH_AUTH_SOCK is not set")
|
||||
}
|
||||
|
||||
sshAgent, err := net.Dial("unix", authSock)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot connect to SSH Agent socket %q: %s", authSock, err)
|
||||
}
|
||||
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers),
|
||||
},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
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),
|
||||
},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}, nil
|
||||
|
||||
} else {
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password(password),
|
||||
ssh.KeyboardInteractive(
|
||||
packerssh.PasswordKeyboardInteractive(password)),
|
||||
}}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+188
-267
@@ -1,13 +1,16 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
@@ -32,310 +35,228 @@ type StateChangeConf struct {
|
||||
Target string
|
||||
}
|
||||
|
||||
// Following are wrapper functions that use Packer's environment-variables to
|
||||
// determing retry logic, then call the AWS SDK's built-in waiters.
|
||||
// AMIStateRefreshFunc returns a StateRefreshFunc that is used to watch
|
||||
// an AMI for state changes.
|
||||
func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc {
|
||||
return func() (interface{}, string, error) {
|
||||
resp, err := conn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{&imageId},
|
||||
})
|
||||
if err != nil {
|
||||
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidAMIID.NotFound" {
|
||||
// Set this to nil as if we didn't find anything.
|
||||
resp = nil
|
||||
} else if isTransientNetworkError(err) {
|
||||
// Transient network error, treat it as if we didn't find anything
|
||||
resp = nil
|
||||
} else {
|
||||
log.Printf("Error on AMIStateRefresh: %s", err)
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
|
||||
func WaitUntilAMIAvailable(ctx aws.Context, conn *ec2.EC2, imageId string) error {
|
||||
imageInput := ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{&imageId},
|
||||
}
|
||||
if resp == nil || len(resp.Images) == 0 {
|
||||
// Sometimes AWS has consistency issues and doesn't see the
|
||||
// AMI. Return an empty state.
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
waitOpts := getWaiterOptions()
|
||||
if len(waitOpts) == 0 {
|
||||
// Bump this default to 30 minutes because the aws default
|
||||
// of ten minutes doesn't work for some of our long-running copies.
|
||||
waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(120))
|
||||
i := resp.Images[0]
|
||||
return i, *i.State, nil
|
||||
}
|
||||
err := conn.WaitUntilImageAvailableWithContext(
|
||||
ctx,
|
||||
&imageInput,
|
||||
waitOpts...)
|
||||
return err
|
||||
}
|
||||
|
||||
func WaitUntilInstanceTerminated(ctx aws.Context, conn *ec2.EC2, instanceId string) error {
|
||||
// InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch
|
||||
// an EC2 instance.
|
||||
func InstanceStateRefreshFunc(conn *ec2.EC2, instanceId string) StateRefreshFunc {
|
||||
return func() (interface{}, string, error) {
|
||||
resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{&instanceId},
|
||||
})
|
||||
if err != nil {
|
||||
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" {
|
||||
// Set this to nil as if we didn't find anything.
|
||||
resp = nil
|
||||
} else if isTransientNetworkError(err) {
|
||||
// Transient network error, treat it as if we didn't find anything
|
||||
resp = nil
|
||||
} else {
|
||||
log.Printf("Error on InstanceStateRefresh: %s", err)
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
|
||||
instanceInput := ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{&instanceId},
|
||||
if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
|
||||
// Sometimes AWS just has consistency issues and doesn't see
|
||||
// our instance yet. Return an empty state.
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
i := resp.Reservations[0].Instances[0]
|
||||
return i, *i.State.Name, nil
|
||||
}
|
||||
|
||||
err := conn.WaitUntilInstanceTerminatedWithContext(
|
||||
ctx,
|
||||
&instanceInput,
|
||||
getWaiterOptions()...)
|
||||
return err
|
||||
}
|
||||
|
||||
// This function works for both requesting and cancelling spot instances.
|
||||
func WaitUntilSpotRequestFulfilled(ctx aws.Context, conn *ec2.EC2, spotRequestId string) error {
|
||||
spotRequestInput := ec2.DescribeSpotInstanceRequestsInput{
|
||||
SpotInstanceRequestIds: []*string{&spotRequestId},
|
||||
}
|
||||
// SpotRequestStateRefreshFunc returns a StateRefreshFunc that is used to watch
|
||||
// a spot request for state changes.
|
||||
func SpotRequestStateRefreshFunc(conn *ec2.EC2, spotRequestId string) StateRefreshFunc {
|
||||
return func() (interface{}, string, error) {
|
||||
resp, err := conn.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{
|
||||
SpotInstanceRequestIds: []*string{&spotRequestId},
|
||||
})
|
||||
|
||||
err := conn.WaitUntilSpotInstanceRequestFulfilledWithContext(
|
||||
ctx,
|
||||
&spotRequestInput,
|
||||
getWaiterOptions()...)
|
||||
return err
|
||||
if err != nil {
|
||||
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSpotInstanceRequestID.NotFound" {
|
||||
// Set this to nil as if we didn't find anything.
|
||||
resp = nil
|
||||
} else if isTransientNetworkError(err) {
|
||||
// Transient network error, treat it as if we didn't find anything
|
||||
resp = nil
|
||||
} else {
|
||||
log.Printf("Error on SpotRequestStateRefresh: %s", err)
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
|
||||
if resp == nil || len(resp.SpotInstanceRequests) == 0 {
|
||||
// Sometimes AWS has consistency issues and doesn't see the
|
||||
// SpotRequest. Return an empty state.
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
i := resp.SpotInstanceRequests[0]
|
||||
return i, *i.State, nil
|
||||
}
|
||||
}
|
||||
|
||||
func WaitUntilVolumeAvailable(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
|
||||
volumeInput := ec2.DescribeVolumesInput{
|
||||
VolumeIds: []*string{&volumeId},
|
||||
}
|
||||
|
||||
err := conn.WaitUntilVolumeAvailableWithContext(
|
||||
ctx,
|
||||
&volumeInput,
|
||||
getWaiterOptions()...)
|
||||
return err
|
||||
}
|
||||
|
||||
func WaitUntilSnapshotDone(ctx aws.Context, conn *ec2.EC2, snapshotID string) error {
|
||||
snapInput := ec2.DescribeSnapshotsInput{
|
||||
SnapshotIds: []*string{&snapshotID},
|
||||
}
|
||||
|
||||
err := conn.WaitUntilSnapshotCompletedWithContext(
|
||||
ctx,
|
||||
&snapInput,
|
||||
getWaiterOptions()...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Wrappers for our custom AWS waiters
|
||||
|
||||
func WaitUntilVolumeAttached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
|
||||
volumeInput := ec2.DescribeVolumesInput{
|
||||
VolumeIds: []*string{&volumeId},
|
||||
}
|
||||
|
||||
err := WaitForVolumeToBeAttached(conn,
|
||||
ctx,
|
||||
&volumeInput,
|
||||
getWaiterOptions()...)
|
||||
return err
|
||||
}
|
||||
|
||||
func WaitUntilVolumeDetached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
|
||||
volumeInput := ec2.DescribeVolumesInput{
|
||||
VolumeIds: []*string{&volumeId},
|
||||
}
|
||||
|
||||
err := WaitForVolumeToBeDetached(conn,
|
||||
ctx,
|
||||
&volumeInput,
|
||||
getWaiterOptions()...)
|
||||
return err
|
||||
}
|
||||
|
||||
func WaitUntilImageImported(ctx aws.Context, conn *ec2.EC2, taskID string) error {
|
||||
importInput := ec2.DescribeImportImageTasksInput{
|
||||
ImportTaskIds: []*string{&taskID},
|
||||
}
|
||||
|
||||
err := WaitForImageToBeImported(conn,
|
||||
ctx,
|
||||
&importInput,
|
||||
getWaiterOptions()...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Custom waiters using AWS's request.Waiter
|
||||
|
||||
func WaitForVolumeToBeAttached(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeVolumesInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "DescribeVolumes",
|
||||
MaxAttempts: 40,
|
||||
Delay: request.ConstantWaiterDelay(5 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.PathAllWaiterMatch,
|
||||
Argument: "Volumes[].Attachments[].State",
|
||||
Expected: "attached",
|
||||
func ImportImageRefreshFunc(conn *ec2.EC2, importTaskId string) StateRefreshFunc {
|
||||
return func() (interface{}, string, error) {
|
||||
resp, err := conn.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{
|
||||
ImportTaskIds: []*string{
|
||||
&importTaskId,
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *ec2.DescribeVolumesInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
)
|
||||
if err != nil {
|
||||
if ec2err, ok := err.(awserr.Error); ok && strings.HasPrefix(ec2err.Code(), "InvalidConversionTaskId") {
|
||||
resp = nil
|
||||
} else if isTransientNetworkError(err) {
|
||||
resp = nil
|
||||
} else {
|
||||
log.Printf("Error on ImportImageRefresh: %s", err)
|
||||
return nil, "", err
|
||||
}
|
||||
req, _ := c.DescribeVolumesRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
|
||||
if resp == nil || len(resp.ImportImageTasks) == 0 {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
i := resp.ImportImageTasks[0]
|
||||
return i, *i.Status, nil
|
||||
}
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
||||
|
||||
func WaitForVolumeToBeDetached(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeVolumesInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "DescribeVolumes",
|
||||
MaxAttempts: 40,
|
||||
Delay: request.ConstantWaiterDelay(5 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.PathAllWaiterMatch,
|
||||
Argument: "length(Volumes[].Attachments[]) == `0`",
|
||||
Expected: true,
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *ec2.DescribeVolumesInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
// WaitForState watches an object and waits for it to achieve a certain
|
||||
// state.
|
||||
func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
|
||||
log.Printf("Waiting for state to become: %s", conf.Target)
|
||||
|
||||
sleepSeconds := SleepSeconds()
|
||||
maxTicks := TimeoutSeconds()/sleepSeconds + 1
|
||||
notfoundTick := 0
|
||||
|
||||
for {
|
||||
var currentState string
|
||||
i, currentState, err = conf.Refresh()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if i == nil {
|
||||
// If we didn't find the resource, check if we have been
|
||||
// not finding it for awhile, and if so, report an error.
|
||||
notfoundTick += 1
|
||||
if notfoundTick > maxTicks {
|
||||
return nil, errors.New("couldn't find resource")
|
||||
}
|
||||
req, _ := c.DescribeVolumesRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
||||
} else {
|
||||
// Reset the counter for when a resource isn't found
|
||||
notfoundTick = 0
|
||||
|
||||
func WaitForImageToBeImported(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeImportImageTasksInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "DescribeImages",
|
||||
MaxAttempts: 300,
|
||||
Delay: request.ConstantWaiterDelay(5 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.PathAllWaiterMatch,
|
||||
Argument: "ImportImageTasks[].Status",
|
||||
Expected: "completed",
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *ec2.DescribeImportImageTasksInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
if currentState == conf.Target {
|
||||
return
|
||||
}
|
||||
req, _ := c.DescribeImportImageTasksRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
|
||||
if conf.StepState != nil {
|
||||
if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
|
||||
return nil, errors.New("interrupted")
|
||||
}
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, allowed := range conf.Pending {
|
||||
if currentState == allowed {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
err := fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(sleepSeconds) * time.Second)
|
||||
}
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
||||
|
||||
// This helper function uses the environment variables AWS_TIMEOUT_SECONDS and
|
||||
// AWS_POLL_DELAY_SECONDS to generate waiter options that can be passed into any
|
||||
// request.Waiter function. These options will control how many times the waiter
|
||||
// will retry the request, as well as how long to wait between the retries.
|
||||
func isTransientNetworkError(err error) bool {
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
|
||||
return true
|
||||
}
|
||||
|
||||
// DEFAULTING BEHAVIOR:
|
||||
// if AWS_POLL_DELAY_SECONDS is set but the others are not, Packer will set this
|
||||
// poll delay and use the waiter-specific default
|
||||
|
||||
// if AWS_TIMEOUT_SECONDS is set but AWS_MAX_ATTEMPTS is not, Packer will use
|
||||
// AWS_TIMEOUT_SECONDS and _either_ AWS_POLL_DELAY_SECONDS _or_ 2 if the user has not set AWS_POLL_DELAY_SECONDS, to determine a max number of attempts to make.
|
||||
|
||||
// if AWS_TIMEOUT_SECONDS, _and_ AWS_MAX_ATTEMPTS are both set,
|
||||
// AWS_TIMEOUT_SECONDS will be ignored.
|
||||
|
||||
// if AWS_MAX_ATTEMPTS is set but AWS_POLL_DELAY_SECONDS is not, then we will
|
||||
// use waiter-specific defaults.
|
||||
|
||||
type envInfo struct {
|
||||
envKey string
|
||||
Val int
|
||||
overridden bool
|
||||
return false
|
||||
}
|
||||
|
||||
type overridableWaitVars struct {
|
||||
awsPollDelaySeconds envInfo
|
||||
awsMaxAttempts envInfo
|
||||
awsTimeoutSeconds envInfo
|
||||
}
|
||||
// Returns 300 seconds (5 minutes) by default
|
||||
// Some AWS operations, like copying an AMI to a distant region, take a very long time
|
||||
// Allow user to override with AWS_TIMEOUT_SECONDS environment variable
|
||||
func TimeoutSeconds() (seconds int) {
|
||||
seconds = 300
|
||||
|
||||
func getWaiterOptions() []request.WaiterOption {
|
||||
envOverrides := getEnvOverrides()
|
||||
waitOpts := applyEnvOverrides(envOverrides)
|
||||
return waitOpts
|
||||
}
|
||||
|
||||
func getOverride(varInfo envInfo) envInfo {
|
||||
override := os.Getenv(varInfo.envKey)
|
||||
override := os.Getenv("AWS_TIMEOUT_SECONDS")
|
||||
if override != "" {
|
||||
n, err := strconv.Atoi(override)
|
||||
if err != nil {
|
||||
log.Printf("Invalid %s '%s', using default", varInfo.envKey, override)
|
||||
log.Printf("Invalid timeout seconds '%s', using default", override)
|
||||
} else {
|
||||
varInfo.overridden = true
|
||||
varInfo.Val = n
|
||||
seconds = n
|
||||
}
|
||||
}
|
||||
|
||||
return varInfo
|
||||
}
|
||||
func getEnvOverrides() overridableWaitVars {
|
||||
// Load env vars from environment, and use them to override defaults
|
||||
envValues := overridableWaitVars{
|
||||
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
|
||||
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
|
||||
envInfo{"AWS_TIMEOUT_SECONDS", 300, false},
|
||||
}
|
||||
|
||||
envValues.awsMaxAttempts = getOverride(envValues.awsMaxAttempts)
|
||||
envValues.awsPollDelaySeconds = getOverride(envValues.awsPollDelaySeconds)
|
||||
envValues.awsTimeoutSeconds = getOverride(envValues.awsTimeoutSeconds)
|
||||
|
||||
return envValues
|
||||
log.Printf("Allowing %ds to complete (change with AWS_TIMEOUT_SECONDS)", seconds)
|
||||
return seconds
|
||||
}
|
||||
|
||||
func applyEnvOverrides(envOverrides overridableWaitVars) []request.WaiterOption {
|
||||
waitOpts := make([]request.WaiterOption, 0)
|
||||
// If user has set poll delay seconds, overwrite it. If user has NOT,
|
||||
// default to a poll delay of 2 seconds
|
||||
if envOverrides.awsPollDelaySeconds.overridden {
|
||||
delaySeconds := request.ConstantWaiterDelay(time.Duration(envOverrides.awsPollDelaySeconds.Val) * time.Second)
|
||||
waitOpts = append(waitOpts, request.WithWaiterDelay(delaySeconds))
|
||||
}
|
||||
// Returns 2 seconds by default
|
||||
// AWS async operations sometimes takes long times, if there are multiple parallel builds,
|
||||
// polling at 2 second frequency will exceed the request limit. Allow 2 seconds to be
|
||||
// overwritten with AWS_POLL_DELAY_SECONDS
|
||||
func SleepSeconds() (seconds int) {
|
||||
seconds = 2
|
||||
|
||||
// If user has set max attempts, overwrite it. If user hasn't set max
|
||||
// attempts, default to whatever the waiter has set as a default.
|
||||
if envOverrides.awsMaxAttempts.overridden {
|
||||
waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(envOverrides.awsMaxAttempts.Val))
|
||||
}
|
||||
|
||||
if envOverrides.awsMaxAttempts.overridden && envOverrides.awsTimeoutSeconds.overridden {
|
||||
log.Printf("WARNING: AWS_MAX_ATTEMPTS and AWS_TIMEOUT_SECONDS are" +
|
||||
" both set. Packer will be using AWS_MAX_ATTEMPTS and discarding " +
|
||||
"AWS_TIMEOUT_SECONDS. If you have not set AWS_POLL_DELAY_SECONDS, " +
|
||||
"Packer will default to a 2 second poll delay.")
|
||||
} else if envOverrides.awsTimeoutSeconds.overridden {
|
||||
log.Printf("DEPRECATION WARNING: env var AWS_TIMEOUT_SECONDS is " +
|
||||
"deprecated in favor of AWS_MAX_ATTEMPTS. If you have not " +
|
||||
"explicitly set AWS_POLL_DELAY_SECONDS, we are defaulting to a " +
|
||||
"poll delay of 2 seconds, regardless of the AWS waiter's default.")
|
||||
maxAttempts := envOverrides.awsTimeoutSeconds.Val / envOverrides.awsPollDelaySeconds.Val
|
||||
// override the delay so we can get the timeout right
|
||||
if !envOverrides.awsPollDelaySeconds.overridden {
|
||||
delaySeconds := request.ConstantWaiterDelay(time.Duration(envOverrides.awsPollDelaySeconds.Val) * time.Second)
|
||||
waitOpts = append(waitOpts, request.WithWaiterDelay(delaySeconds))
|
||||
override := os.Getenv("AWS_POLL_DELAY_SECONDS")
|
||||
if override != "" {
|
||||
n, err := strconv.Atoi(override)
|
||||
if err != nil {
|
||||
log.Printf("Invalid sleep seconds '%s', using default", override)
|
||||
} else {
|
||||
seconds = n
|
||||
}
|
||||
waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(maxAttempts))
|
||||
}
|
||||
if len(waitOpts) == 0 {
|
||||
log.Printf("No AWS timeout and polling overrides have been set. " +
|
||||
"Packer will default to waiter-specific delays and timeouts. If you would " +
|
||||
"like to customize the length of time between retries and max " +
|
||||
"number of retries you may do so by setting the environment " +
|
||||
"variables AWS_POLL_DELAY_SECONDS and AWS_MAX_ATTEMPTS to your " +
|
||||
"desired values.")
|
||||
}
|
||||
|
||||
return waitOpts
|
||||
log.Printf("Using %ds as polling delay (change with AWS_POLL_DELAY_SECONDS)", seconds)
|
||||
return seconds
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
)
|
||||
|
||||
func testGetWaiterOptions(t *testing.T) {
|
||||
// no vars are set
|
||||
envValues := overridableWaitVars{
|
||||
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
|
||||
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
|
||||
envInfo{"AWS_TIMEOUT_SECONDS", 300, false},
|
||||
}
|
||||
options := applyEnvOverrides(envValues)
|
||||
if len(options) > 0 {
|
||||
t.Fatalf("Did not expect any waiter options to be generated; actual: %#v", options)
|
||||
}
|
||||
|
||||
// all vars are set
|
||||
envValues = overridableWaitVars{
|
||||
envInfo{"AWS_POLL_DELAY_SECONDS", 1, true},
|
||||
envInfo{"AWS_MAX_ATTEMPTS", 800, true},
|
||||
envInfo{"AWS_TIMEOUT_SECONDS", 20, true},
|
||||
}
|
||||
options = applyEnvOverrides(envValues)
|
||||
expected := []request.WaiterOption{
|
||||
request.WithWaiterDelay(request.ConstantWaiterDelay(time.Duration(1) * time.Second)),
|
||||
request.WithWaiterMaxAttempts(800),
|
||||
}
|
||||
if !reflect.DeepEqual(options, expected) {
|
||||
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
|
||||
}
|
||||
|
||||
// poll delay is not set
|
||||
envValues = overridableWaitVars{
|
||||
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
|
||||
envInfo{"AWS_MAX_ATTEMPTS", 800, true},
|
||||
envInfo{"AWS_TIMEOUT_SECONDS", 300, false},
|
||||
}
|
||||
options = applyEnvOverrides(envValues)
|
||||
expected = []request.WaiterOption{
|
||||
request.WithWaiterMaxAttempts(800),
|
||||
}
|
||||
if !reflect.DeepEqual(options, expected) {
|
||||
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
|
||||
}
|
||||
|
||||
// poll delay is not set but timeout seconds is
|
||||
envValues = overridableWaitVars{
|
||||
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
|
||||
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
|
||||
envInfo{"AWS_TIMEOUT_SECONDS", 20, true},
|
||||
}
|
||||
options = applyEnvOverrides(envValues)
|
||||
expected = []request.WaiterOption{
|
||||
request.WithWaiterDelay(request.ConstantWaiterDelay(time.Duration(2) * time.Second)),
|
||||
request.WithWaiterMaxAttempts(10),
|
||||
}
|
||||
if !reflect.DeepEqual(options, expected) {
|
||||
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ type StepAMIRegionCopy struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepAMIRegionCopy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
amis := state.Get("amis").(map[string]string)
|
||||
@@ -53,7 +53,7 @@ func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) m
|
||||
|
||||
go func(region string) {
|
||||
defer wg.Done()
|
||||
id, snapshotIds, err := amiRegionCopy(ctx, state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region, regKeyID)
|
||||
id, snapshotIds, err := amiRegionCopy(state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region, regKeyID)
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
amis[region] = id
|
||||
@@ -85,7 +85,7 @@ func (s *StepAMIRegionCopy) Cleanup(state multistep.StateBag) {
|
||||
|
||||
// amiRegionCopy does a copy for the given AMI to the target region and
|
||||
// returns the resulting ID and snapshot IDs, or error.
|
||||
func amiRegionCopy(ctx context.Context, state multistep.StateBag, config *AccessConfig, name string, imageId string,
|
||||
func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string, imageId string,
|
||||
target string, source string, keyID string) (string, []string, error) {
|
||||
snapshotIds := []string{}
|
||||
isEncrypted := false
|
||||
@@ -116,8 +116,14 @@ func amiRegionCopy(ctx context.Context, state multistep.StateBag, config *Access
|
||||
imageId, target, err)
|
||||
}
|
||||
|
||||
// Wait for the image to become ready
|
||||
if err := WaitUntilAMIAvailable(ctx, regionconn, *resp.ImageId); err != nil {
|
||||
stateChange := StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
Target: "available",
|
||||
Refresh: AMIStateRefreshFunc(regionconn, *resp.ImageId),
|
||||
StepState: state,
|
||||
}
|
||||
|
||||
if _, err := WaitForState(&stateChange); err != nil {
|
||||
return "", snapshotIds, fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s",
|
||||
*resp.ImageId, target, err)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ type StepCreateEncryptedAMICopy struct {
|
||||
AMIMappings []BlockDevice
|
||||
}
|
||||
|
||||
func (s *StepCreateEncryptedAMICopy) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepCreateEncryptedAMICopy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
kmsKeyId := s.KeyID
|
||||
@@ -65,8 +65,15 @@ func (s *StepCreateEncryptedAMICopy) Run(ctx context.Context, state multistep.St
|
||||
}
|
||||
|
||||
// Wait for the copy to become ready
|
||||
stateChange := StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
Target: "available",
|
||||
Refresh: AMIStateRefreshFunc(ec2conn, *copyResp.ImageId),
|
||||
StepState: state,
|
||||
}
|
||||
|
||||
ui.Say("Waiting for AMI copy to become ready...")
|
||||
if err := WaitUntilAMIAvailable(ctx, ec2conn, *copyResp.ImageId); err != nil {
|
||||
if _, err := WaitForState(&stateChange); err != nil {
|
||||
err := fmt.Errorf("Error waiting for AMI Copy: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -20,10 +21,9 @@ import (
|
||||
// StepGetPassword reads the password from a Windows server and sets it
|
||||
// on the WinRM config.
|
||||
type StepGetPassword struct {
|
||||
Debug bool
|
||||
Comm *communicator.Config
|
||||
Timeout time.Duration
|
||||
BuildName string
|
||||
Debug bool
|
||||
Comm *communicator.Config
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func (s *StepGetPassword) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
@@ -94,19 +94,19 @@ WaitLoop:
|
||||
"Password (since debug is enabled): %s", s.Comm.WinRMPassword))
|
||||
}
|
||||
// store so that we can access this later during provisioning
|
||||
state.Put("winrm_password", s.Comm.WinRMPassword)
|
||||
packer.LogSecretFilter.Set(s.Comm.WinRMPassword)
|
||||
commonhelper.SetSharedState("winrm_password", s.Comm.WinRMPassword)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepGetPassword) Cleanup(multistep.StateBag) {
|
||||
commonhelper.RemoveSharedStateFile("winrm_password")
|
||||
}
|
||||
|
||||
func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
privateKey := s.Comm.SSHPrivateKey
|
||||
privateKey := state.Get("privateKey").(string)
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -8,15 +8,17 @@ import (
|
||||
"runtime"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepKeyPair struct {
|
||||
Debug bool
|
||||
Comm *communicator.Config
|
||||
DebugKeyPath string
|
||||
Debug bool
|
||||
SSHAgentAuth bool
|
||||
DebugKeyPath string
|
||||
TemporaryKeyPairName string
|
||||
KeyPairName string
|
||||
PrivateKeyFile string
|
||||
|
||||
doCleanup bool
|
||||
}
|
||||
@@ -24,41 +26,43 @@ type StepKeyPair struct {
|
||||
func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if s.Comm.SSHPrivateKeyFile != "" {
|
||||
if s.PrivateKeyFile != "" {
|
||||
ui.Say("Using existing SSH private key")
|
||||
privateKeyBytes, err := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile)
|
||||
privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf(
|
||||
"Error loading configured private key file: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.Comm.SSHPrivateKey = privateKeyBytes
|
||||
state.Put("keyPair", s.KeyPairName)
|
||||
state.Put("privateKey", string(privateKeyBytes))
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName == "" {
|
||||
if s.SSHAgentAuth && s.KeyPairName == "" {
|
||||
ui.Say("Using SSH Agent with key pair in Source AMI")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName != "" {
|
||||
ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.Comm.SSHKeyPairName))
|
||||
if s.SSHAgentAuth && s.KeyPairName != "" {
|
||||
ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.KeyPairName))
|
||||
state.Put("keyPair", s.KeyPairName)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHTemporaryKeyPairName == "" {
|
||||
if s.TemporaryKeyPairName == "" {
|
||||
ui.Say("Not using temporary keypair")
|
||||
s.Comm.SSHKeyPairName = ""
|
||||
state.Put("keyPair", "")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
|
||||
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.TemporaryKeyPairName))
|
||||
keyResp, err := ec2conn.CreateKeyPair(&ec2.CreateKeyPairInput{
|
||||
KeyName: &s.Comm.SSHTemporaryKeyPairName})
|
||||
KeyName: &s.TemporaryKeyPairName})
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
|
||||
return multistep.ActionHalt
|
||||
@@ -66,9 +70,9 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
|
||||
|
||||
s.doCleanup = true
|
||||
|
||||
// Set some data for use in future steps
|
||||
s.Comm.SSHKeyPairName = s.Comm.SSHTemporaryKeyPairName
|
||||
s.Comm.SSHPrivateKey = []byte(*keyResp.KeyMaterial)
|
||||
// Set some state data for use in future steps
|
||||
state.Put("keyPair", s.TemporaryKeyPairName)
|
||||
state.Put("privateKey", *keyResp.KeyMaterial)
|
||||
|
||||
// If we're in debug mode, output the private key to the working
|
||||
// directory.
|
||||
@@ -109,10 +113,10 @@ func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
|
||||
|
||||
// Remove the keypair
|
||||
ui.Say("Deleting temporary keypair...")
|
||||
_, err := ec2conn.DeleteKeyPair(&ec2.DeleteKeyPairInput{KeyName: &s.Comm.SSHTemporaryKeyPairName})
|
||||
_, err := ec2conn.DeleteKeyPair(&ec2.DeleteKeyPairInput{KeyName: &s.TemporaryKeyPairName})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error cleaning up keypair. Please delete the key manually: %s", s.Comm.SSHTemporaryKeyPairName))
|
||||
"Error cleaning up keypair. Please delete the key manually: %s", s.TemporaryKeyPairName))
|
||||
}
|
||||
|
||||
// Also remove the physical key if we're debugging.
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
|
||||
retry "github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
@@ -22,11 +21,9 @@ type StepRunSourceInstance struct {
|
||||
AssociatePublicIpAddress bool
|
||||
AvailabilityZone string
|
||||
BlockDevices BlockDevices
|
||||
Comm *communicator.Config
|
||||
Ctx interpolate.Context
|
||||
Debug bool
|
||||
EbsOptimized bool
|
||||
EnableT2Unlimited bool
|
||||
ExpectedRootDevice string
|
||||
IamInstanceProfile string
|
||||
InstanceInitiatedShutdownBehavior string
|
||||
@@ -42,9 +39,12 @@ type StepRunSourceInstance struct {
|
||||
instanceId string
|
||||
}
|
||||
|
||||
func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepRunSourceInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
|
||||
var keyName string
|
||||
if name, ok := state.GetOk("keyPair"); ok {
|
||||
keyName = name.(string)
|
||||
}
|
||||
securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string))
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
@@ -116,11 +116,6 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
EbsOptimized: &s.EbsOptimized,
|
||||
}
|
||||
|
||||
if s.EnableT2Unlimited {
|
||||
creditOption := "unlimited"
|
||||
runOpts.CreditSpecification = &ec2.CreditSpecificationRequest{CpuCredits: &creditOption}
|
||||
}
|
||||
|
||||
// Collect tags for tagging on resource creation
|
||||
var tagSpecs []*ec2.TagSpecification
|
||||
|
||||
@@ -149,8 +144,8 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
volTags.Report(ui)
|
||||
}
|
||||
|
||||
if s.Comm.SSHKeyPairName != "" {
|
||||
runOpts.KeyName = &s.Comm.SSHKeyPairName
|
||||
if keyName != "" {
|
||||
runOpts.KeyName = &keyName
|
||||
}
|
||||
|
||||
if s.SubnetId != "" && s.AssociatePublicIpAddress {
|
||||
@@ -190,7 +185,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
describeInstance := &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{aws.String(instanceId)},
|
||||
}
|
||||
if err := ec2conn.WaitUntilInstanceRunningWithContext(ctx, describeInstance); err != nil {
|
||||
if err := ec2conn.WaitUntilInstanceRunning(describeInstance); err != nil {
|
||||
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
@@ -302,8 +297,14 @@ func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) {
|
||||
ui.Error(fmt.Sprintf("Error terminating instance, may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
stateChange := StateChangeConf{
|
||||
Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
|
||||
Refresh: InstanceStateRefreshFunc(ec2conn, s.instanceId),
|
||||
Target: "terminated",
|
||||
}
|
||||
|
||||
if err := WaitUntilInstanceTerminated(aws.BackgroundContext(), ec2conn, s.instanceId); err != nil {
|
||||
_, err := WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
|
||||
retry "github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
@@ -25,7 +24,6 @@ type StepRunSpotInstance struct {
|
||||
AvailabilityZone string
|
||||
BlockDevices BlockDevices
|
||||
Debug bool
|
||||
Comm *communicator.Config
|
||||
EbsOptimized bool
|
||||
ExpectedRootDevice string
|
||||
IamInstanceProfile string
|
||||
@@ -34,7 +32,6 @@ type StepRunSpotInstance struct {
|
||||
SourceAMI string
|
||||
SpotPrice string
|
||||
SpotPriceProduct string
|
||||
SpotTags TagMap
|
||||
SubnetId string
|
||||
Tags TagMap
|
||||
VolumeTags TagMap
|
||||
@@ -46,8 +43,12 @@ type StepRunSpotInstance struct {
|
||||
spotRequest *ec2.SpotInstanceRequest
|
||||
}
|
||||
|
||||
func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepRunSpotInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
var keyName string
|
||||
if name, ok := state.GetOk("keyPair"); ok {
|
||||
keyName = name.(string)
|
||||
}
|
||||
securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string))
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
@@ -182,8 +183,8 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
runOpts.SecurityGroupIds = securityGroupIds
|
||||
}
|
||||
|
||||
if s.Comm.SSHKeyPairName != "" {
|
||||
runOpts.KeyName = &s.Comm.SSHKeyPairName
|
||||
if keyName != "" {
|
||||
runOpts.KeyName = &keyName
|
||||
}
|
||||
|
||||
runSpotResp, err := ec2conn.RequestSpotInstances(&ec2.RequestSpotInstancesInput{
|
||||
@@ -201,7 +202,13 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
|
||||
spotRequestId := s.spotRequest.SpotInstanceRequestId
|
||||
ui.Message(fmt.Sprintf("Waiting for spot request (%s) to become active...", *spotRequestId))
|
||||
err = WaitUntilSpotRequestFulfilled(ctx, ec2conn, *spotRequestId)
|
||||
stateChange := StateChangeConf{
|
||||
Pending: []string{"open"},
|
||||
Target: "active",
|
||||
Refresh: SpotRequestStateRefreshFunc(ec2conn, *spotRequestId),
|
||||
StepState: state,
|
||||
}
|
||||
_, err = WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for spot request (%s) to become ready: %s", *spotRequestId, err)
|
||||
state.Put("error", err)
|
||||
@@ -220,33 +227,6 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
}
|
||||
instanceId = *spotResp.SpotInstanceRequests[0].InstanceId
|
||||
|
||||
// Tag spot instance request
|
||||
spotTags, err := s.SpotTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error tagging spot request: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
spotTags.Report(ui)
|
||||
|
||||
if len(spotTags) > 0 && s.SpotTags.IsSet() {
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
|
||||
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||
Tags: spotTags,
|
||||
Resources: []*string{spotRequestId},
|
||||
})
|
||||
return true, err
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error tagging spot request: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
// Set the instance ID so that the cleanup works properly
|
||||
s.instanceId = instanceId
|
||||
|
||||
@@ -255,7 +235,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
describeInstance := &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{aws.String(instanceId)},
|
||||
}
|
||||
if err := ec2conn.WaitUntilInstanceRunningWithContext(ctx, describeInstance); err != nil {
|
||||
if err := ec2conn.WaitUntilInstanceRunning(describeInstance); err != nil {
|
||||
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
@@ -364,8 +344,13 @@ func (s *StepRunSpotInstance) Cleanup(state multistep.StateBag) {
|
||||
ui.Error(fmt.Sprintf("Error cancelling the spot request, may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
stateChange := StateChangeConf{
|
||||
Pending: []string{"active", "open"},
|
||||
Refresh: SpotRequestStateRefreshFunc(ec2conn, *s.spotRequest.SpotInstanceRequestId),
|
||||
Target: "cancelled",
|
||||
}
|
||||
|
||||
err := WaitUntilSpotRequestFulfilled(aws.BackgroundContext(), ec2conn, *s.spotRequest.SpotInstanceRequestId)
|
||||
_, err := WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
}
|
||||
@@ -379,8 +364,14 @@ func (s *StepRunSpotInstance) Cleanup(state multistep.StateBag) {
|
||||
ui.Error(fmt.Sprintf("Error terminating instance, may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
stateChange := StateChangeConf{
|
||||
Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
|
||||
Refresh: InstanceStateRefreshFunc(ec2conn, s.instanceId),
|
||||
Target: "terminated",
|
||||
}
|
||||
|
||||
if err := WaitUntilInstanceTerminated(aws.BackgroundContext(), ec2conn, s.instanceId); err != nil {
|
||||
_, err := WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ type StepStopEBSBackedInstance struct {
|
||||
DisableStopInstance bool
|
||||
}
|
||||
|
||||
func (s *StepStopEBSBackedInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepStopEBSBackedInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
@@ -78,11 +78,9 @@ func (s *StepStopEBSBackedInstance) Run(ctx context.Context, state multistep.Sta
|
||||
|
||||
// Wait for the instance to actually stop
|
||||
ui.Say("Waiting for the instance to stop...")
|
||||
err = ec2conn.WaitUntilInstanceStoppedWithContext(ctx,
|
||||
&ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{instance.InstanceId},
|
||||
},
|
||||
getWaiterOptions()...)
|
||||
err = ec2conn.WaitUntilInstanceStopped(&ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{instance.InstanceId},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for instance to stop: %s", err)
|
||||
|
||||
@@ -48,7 +48,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
"ami_description",
|
||||
"run_tags",
|
||||
"run_volume_tags",
|
||||
"spot_tags",
|
||||
"snapshot_tags",
|
||||
"tags",
|
||||
},
|
||||
@@ -81,8 +80,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
log.Println(b.config)
|
||||
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey, b.config.Token))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -136,7 +134,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||
SpotTags: b.config.SpotTags,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
UserData: b.config.UserData,
|
||||
@@ -148,11 +145,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
Debug: b.config.PackerDebug,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
EnableT2Unlimited: b.config.EnableT2Unlimited,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
@@ -180,32 +175,37 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
},
|
||||
&awscommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
Debug: b.config.PackerDebug,
|
||||
SSHAgentAuth: b.config.Comm.SSHAgentAuth,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
KeyPairName: b.config.SSHKeyPairName,
|
||||
TemporaryKeyPairName: b.config.TemporaryKeyPairName,
|
||||
PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey,
|
||||
},
|
||||
&awscommon.StepSecurityGroup{
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
VpcId: b.config.VpcId,
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
VpcId: b.config.VpcId,
|
||||
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
|
||||
},
|
||||
&awscommon.StepCleanupVolumes{
|
||||
&stepCleanupVolumes{
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
},
|
||||
instanceStep,
|
||||
&awscommon.StepGetPassword{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Timeout: b.config.WindowsPasswordTimeout,
|
||||
BuildName: b.config.PackerBuildName,
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Timeout: b.config.WindowsPasswordTimeout,
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: awscommon.SSHHost(
|
||||
ec2conn,
|
||||
b.config.Comm.SSHInterface),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
b.config.SSHInterface),
|
||||
SSHConfig: awscommon.SSHConfig(
|
||||
b.config.RunConfig.Comm.SSHAgentAuth,
|
||||
b.config.RunConfig.Comm.SSHUsername,
|
||||
b.config.RunConfig.Comm.SSHPassword),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&awscommon.StepStopEBSBackedInstance{
|
||||
@@ -256,6 +256,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
// Run!
|
||||
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(state)
|
||||
|
||||
// If there was an error, return that
|
||||
if rawErr, ok := state.GetOk("error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
|
||||
+6
-5
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package ebs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
@@ -13,16 +14,16 @@ import (
|
||||
// stepCleanupVolumes cleans up any orphaned volumes that were not designated to
|
||||
// remain after termination of the instance. These volumes are typically ones
|
||||
// that are marked as "delete on terminate:false" in the source_ami of a build.
|
||||
type StepCleanupVolumes struct {
|
||||
BlockDevices BlockDevices
|
||||
type stepCleanupVolumes struct {
|
||||
BlockDevices common.BlockDevices
|
||||
}
|
||||
|
||||
func (s *StepCleanupVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *stepCleanupVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
// stepCleanupVolumes is for Cleanup only
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
||||
func (s *stepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instanceRaw := state.Get("instance")
|
||||
var instance *ec2.Instance
|
||||
@@ -3,7 +3,6 @@ package ebs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
@@ -15,7 +14,7 @@ type stepCreateAMI struct {
|
||||
image *ec2.Image
|
||||
}
|
||||
|
||||
func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *stepCreateAMI) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(Config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
@@ -44,18 +43,16 @@ func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multi
|
||||
state.Put("amis", amis)
|
||||
|
||||
// Wait for the image to become ready
|
||||
ui.Say("Waiting for AMI to become ready...")
|
||||
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *createResp.ImageId); err != nil {
|
||||
log.Printf("Error waiting for AMI: %s", err)
|
||||
imagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{createResp.ImageId}})
|
||||
if err != nil {
|
||||
log.Printf("Unable to determine reason waiting for AMI failed: %s", err)
|
||||
err = fmt.Errorf("Unknown error waiting for AMI.")
|
||||
} else {
|
||||
stateReason := imagesResp.Images[0].StateReason
|
||||
err = fmt.Errorf("Error waiting for AMI. Reason: %s", stateReason)
|
||||
}
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
Target: "available",
|
||||
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *createResp.ImageId),
|
||||
StepState: state,
|
||||
}
|
||||
|
||||
ui.Say("Waiting for AMI to become ready...")
|
||||
if _, err := awscommon.WaitForState(&stateChange); err != nil {
|
||||
err := fmt.Errorf("Error waiting for AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
|
||||
@@ -48,7 +48,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
"run_tags",
|
||||
"run_volume_tags",
|
||||
"snapshot_tags",
|
||||
"spot_tags",
|
||||
"tags",
|
||||
},
|
||||
},
|
||||
@@ -96,8 +95,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
log.Println(b.config)
|
||||
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey, b.config.Token))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -150,7 +148,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||
SpotTags: b.config.SpotTags,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
UserData: b.config.UserData,
|
||||
@@ -162,11 +159,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
Debug: b.config.PackerDebug,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
EnableT2Unlimited: b.config.EnableT2Unlimited,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
@@ -197,32 +192,34 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
},
|
||||
&awscommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
Debug: b.config.PackerDebug,
|
||||
SSHAgentAuth: b.config.Comm.SSHAgentAuth,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
KeyPairName: b.config.SSHKeyPairName,
|
||||
TemporaryKeyPairName: b.config.TemporaryKeyPairName,
|
||||
PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey,
|
||||
},
|
||||
&awscommon.StepSecurityGroup{
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
VpcId: b.config.VpcId,
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
VpcId: b.config.VpcId,
|
||||
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
|
||||
},
|
||||
&awscommon.StepCleanupVolumes{
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
},
|
||||
instanceStep,
|
||||
&awscommon.StepGetPassword{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Timeout: b.config.WindowsPasswordTimeout,
|
||||
BuildName: b.config.PackerBuildName,
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Timeout: b.config.WindowsPasswordTimeout,
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: awscommon.SSHHost(
|
||||
ec2conn,
|
||||
b.config.Comm.SSHInterface),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
b.config.SSHInterface),
|
||||
SSHConfig: awscommon.SSHConfig(
|
||||
b.config.RunConfig.Comm.SSHAgentAuth,
|
||||
b.config.RunConfig.Comm.SSHUsername,
|
||||
b.config.RunConfig.Comm.SSHPassword),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&awscommon.StepStopEBSBackedInstance{
|
||||
|
||||
@@ -21,7 +21,7 @@ type StepRegisterAMI struct {
|
||||
image *ec2.Image
|
||||
}
|
||||
|
||||
func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepRegisterAMI) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
snapshotIds := state.Get("snapshot_ids").(map[string]string)
|
||||
@@ -63,8 +63,15 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
state.Put("amis", amis)
|
||||
|
||||
// Wait for the image to become ready
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
Target: "available",
|
||||
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *registerResp.ImageId),
|
||||
StepState: state,
|
||||
}
|
||||
|
||||
ui.Say("Waiting for AMI to become ready...")
|
||||
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
|
||||
if _, err := awscommon.WaitForState(&stateChange); err != nil {
|
||||
err := fmt.Errorf("Error waiting for AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
|
||||
@@ -2,6 +2,7 @@ package ebssurrogate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -22,7 +23,7 @@ type StepSnapshotVolumes struct {
|
||||
snapshotIds map[string]string
|
||||
}
|
||||
|
||||
func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName string, state multistep.StateBag) error {
|
||||
func (s *StepSnapshotVolumes) snapshotVolume(deviceName string, state multistep.StateBag) error {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
@@ -51,12 +52,33 @@ func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName str
|
||||
// Set the snapshot ID so we can delete it later
|
||||
s.snapshotIds[deviceName] = *createSnapResp.SnapshotId
|
||||
|
||||
// Wait for snapshot to be created
|
||||
err = awscommon.WaitUntilSnapshotDone(ctx, ec2conn, *createSnapResp.SnapshotId)
|
||||
// Wait for the snapshot to be ready
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
StepState: state,
|
||||
Target: "completed",
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{
|
||||
SnapshotIds: []*string{createSnapResp.SnapshotId},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(resp.Snapshots) == 0 {
|
||||
return nil, "", errors.New("No snapshots found.")
|
||||
}
|
||||
|
||||
s := resp.Snapshots[0]
|
||||
return s, *s.State, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepSnapshotVolumes) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepSnapshotVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
s.snapshotIds = map[string]string{}
|
||||
@@ -67,7 +89,7 @@ func (s *StepSnapshotVolumes) Run(ctx context.Context, state multistep.StateBag)
|
||||
wg.Add(1)
|
||||
go func(device *ec2.BlockDeviceMapping) {
|
||||
defer wg.Done()
|
||||
if err := s.snapshotVolume(ctx, *device.DeviceName, state); err != nil {
|
||||
if err := s.snapshotVolume(*device.DeviceName, state); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
}(device)
|
||||
|
||||
@@ -44,7 +44,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"run_tags",
|
||||
"spot_tags",
|
||||
"ebs_volumes",
|
||||
},
|
||||
},
|
||||
@@ -81,8 +80,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
log.Println(b.config)
|
||||
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey, b.config.Token))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -134,7 +132,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SpotPrice: b.config.SpotPrice,
|
||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||
SpotTags: b.config.SpotTags,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
UserData: b.config.UserData,
|
||||
@@ -145,11 +142,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: b.config.launchBlockDevices,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
Debug: b.config.PackerDebug,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
EnableT2Unlimited: b.config.EnableT2Unlimited,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
@@ -172,14 +167,17 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
},
|
||||
&awscommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
Debug: b.config.PackerDebug,
|
||||
SSHAgentAuth: b.config.Comm.SSHAgentAuth,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
KeyPairName: b.config.SSHKeyPairName,
|
||||
TemporaryKeyPairName: b.config.TemporaryKeyPairName,
|
||||
PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey,
|
||||
},
|
||||
&awscommon.StepSecurityGroup{
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
VpcId: b.config.VpcId,
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
VpcId: b.config.VpcId,
|
||||
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
|
||||
},
|
||||
instanceStep,
|
||||
@@ -188,17 +186,19 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
&awscommon.StepGetPassword{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Timeout: b.config.WindowsPasswordTimeout,
|
||||
BuildName: b.config.PackerBuildName,
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Timeout: b.config.WindowsPasswordTimeout,
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: awscommon.SSHHost(
|
||||
ec2conn,
|
||||
b.config.Comm.SSHInterface),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
b.config.SSHInterface),
|
||||
SSHConfig: awscommon.SSHConfig(
|
||||
b.config.RunConfig.Comm.SSHAgentAuth,
|
||||
b.config.RunConfig.Comm.SSHUsername,
|
||||
b.config.RunConfig.Comm.SSHPassword),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&awscommon.StepStopEBSBackedInstance{
|
||||
|
||||
@@ -69,7 +69,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
"run_volume_tags",
|
||||
"snapshot_tags",
|
||||
"tags",
|
||||
"spot_tags",
|
||||
},
|
||||
},
|
||||
}, configs...)
|
||||
@@ -166,8 +165,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||
log.Println(b.config)
|
||||
|
||||
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey, b.config.Token))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -220,7 +219,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
SpotTags: b.config.SpotTags,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
}
|
||||
@@ -229,11 +227,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
AvailabilityZone: b.config.AvailabilityZone,
|
||||
BlockDevices: b.config.BlockDevices,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Ctx: b.config.ctx,
|
||||
Debug: b.config.PackerDebug,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
EnableT2Unlimited: b.config.EnableT2Unlimited,
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
@@ -258,29 +254,34 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
AmiFilters: b.config.SourceAmiFilter,
|
||||
},
|
||||
&awscommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
Debug: b.config.PackerDebug,
|
||||
SSHAgentAuth: b.config.Comm.SSHAgentAuth,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
KeyPairName: b.config.SSHKeyPairName,
|
||||
PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey,
|
||||
TemporaryKeyPairName: b.config.TemporaryKeyPairName,
|
||||
},
|
||||
&awscommon.StepSecurityGroup{
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
VpcId: b.config.VpcId,
|
||||
CommConfig: &b.config.RunConfig.Comm,
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
VpcId: b.config.VpcId,
|
||||
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
|
||||
},
|
||||
instanceStep,
|
||||
&awscommon.StepGetPassword{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Timeout: b.config.WindowsPasswordTimeout,
|
||||
BuildName: b.config.PackerBuildName,
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
Timeout: b.config.WindowsPasswordTimeout,
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: awscommon.SSHHost(
|
||||
ec2conn,
|
||||
b.config.Comm.SSHInterface),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
b.config.SSHInterface),
|
||||
SSHConfig: awscommon.SSHConfig(
|
||||
b.config.RunConfig.Comm.SSHAgentAuth,
|
||||
b.config.RunConfig.Comm.SSHUsername,
|
||||
b.config.RunConfig.Comm.SSHPassword),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&StepUploadX509Cert{},
|
||||
|
||||
@@ -8,13 +8,13 @@ import (
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func testConfig() (config map[string]interface{}, tf *os.File) {
|
||||
func testConfig() map[string]interface{} {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
config = map[string]interface{}{
|
||||
return map[string]interface{}{
|
||||
"account_id": "foo",
|
||||
"ami_name": "foo",
|
||||
"instance_type": "m1.small",
|
||||
@@ -26,8 +26,6 @@ func testConfig() (config map[string]interface{}, tf *os.File) {
|
||||
"x509_key_path": tf.Name(),
|
||||
"x509_upload_path": "/foo",
|
||||
}
|
||||
|
||||
return config, tf
|
||||
}
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
@@ -40,9 +38,7 @@ func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
|
||||
func TestBuilderPrepare_AccountId(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
config := testConfig()
|
||||
|
||||
config["account_id"] = ""
|
||||
warnings, err := b.Prepare(config)
|
||||
@@ -78,9 +74,7 @@ func TestBuilderPrepare_AccountId(t *testing.T) {
|
||||
|
||||
func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
var b Builder
|
||||
config, tempfile := testConfig()
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["ami_name"] = "foo"
|
||||
@@ -117,9 +111,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
|
||||
func TestBuilderPrepare_BundleDestination(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
config := testConfig()
|
||||
|
||||
config["bundle_destination"] = ""
|
||||
warnings, err := b.Prepare(config)
|
||||
@@ -137,9 +129,7 @@ func TestBuilderPrepare_BundleDestination(t *testing.T) {
|
||||
|
||||
func TestBuilderPrepare_BundlePrefix(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
config := testConfig()
|
||||
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
@@ -156,9 +146,7 @@ func TestBuilderPrepare_BundlePrefix(t *testing.T) {
|
||||
|
||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
var b Builder
|
||||
config, tempfile := testConfig()
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
config := testConfig()
|
||||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
@@ -173,9 +161,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
|
||||
func TestBuilderPrepare_S3Bucket(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
config := testConfig()
|
||||
|
||||
config["s3_bucket"] = ""
|
||||
warnings, err := b.Prepare(config)
|
||||
@@ -198,9 +184,7 @@ func TestBuilderPrepare_S3Bucket(t *testing.T) {
|
||||
|
||||
func TestBuilderPrepare_X509CertPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
config := testConfig()
|
||||
|
||||
config["x509_cert_path"] = ""
|
||||
warnings, err := b.Prepare(config)
|
||||
@@ -225,7 +209,6 @@ func TestBuilderPrepare_X509CertPath(t *testing.T) {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
config["x509_cert_path"] = tf.Name()
|
||||
warnings, err = b.Prepare(config)
|
||||
@@ -239,9 +222,7 @@ func TestBuilderPrepare_X509CertPath(t *testing.T) {
|
||||
|
||||
func TestBuilderPrepare_X509KeyPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
config := testConfig()
|
||||
|
||||
config["x509_key_path"] = ""
|
||||
warnings, err := b.Prepare(config)
|
||||
@@ -266,7 +247,6 @@ func TestBuilderPrepare_X509KeyPath(t *testing.T) {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
config["x509_key_path"] = tf.Name()
|
||||
warnings, err = b.Prepare(config)
|
||||
@@ -280,9 +260,7 @@ func TestBuilderPrepare_X509KeyPath(t *testing.T) {
|
||||
|
||||
func TestBuilderPrepare_X509UploadPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
config := testConfig()
|
||||
|
||||
config["x509_upload_path"] = ""
|
||||
warnings, err := b.Prepare(config)
|
||||
|
||||
@@ -16,7 +16,7 @@ type StepRegisterAMI struct {
|
||||
EnableAMISriovNetSupport bool
|
||||
}
|
||||
|
||||
func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepRegisterAMI) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
manifestPath := state.Get("remote_manifest_path").(string)
|
||||
@@ -58,8 +58,15 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
state.Put("amis", amis)
|
||||
|
||||
// Wait for the image to become ready
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
Target: "available",
|
||||
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *registerResp.ImageId),
|
||||
StepState: state,
|
||||
}
|
||||
|
||||
ui.Say("Waiting for AMI to become ready...")
|
||||
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
|
||||
if _, err := awscommon.WaitForState(&stateChange); err != nil {
|
||||
err := fmt.Errorf("Error waiting for AMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
"image": {
|
||||
"uri": "https://localhost/custom.vhd"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"name": "osdisk",
|
||||
"osType": "Linux",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
|
||||
@@ -18,9 +18,6 @@ type AdditionalDiskArtifact struct {
|
||||
}
|
||||
|
||||
type Artifact struct {
|
||||
// OS type: Linux, Windows
|
||||
OSType string
|
||||
|
||||
// VHD
|
||||
StorageAccountLocation string
|
||||
OSDiskUri string
|
||||
@@ -32,23 +29,20 @@ type Artifact struct {
|
||||
ManagedImageResourceGroupName string
|
||||
ManagedImageName string
|
||||
ManagedImageLocation string
|
||||
ManagedImageId string
|
||||
|
||||
// Additional Disks
|
||||
AdditionalDisks *[]AdditionalDiskArtifact
|
||||
}
|
||||
|
||||
func NewManagedImageArtifact(osType, resourceGroup, name, location, id string) (*Artifact, error) {
|
||||
func NewManagedImageArtifact(resourceGroup, name, location string) (*Artifact, error) {
|
||||
return &Artifact{
|
||||
ManagedImageResourceGroupName: resourceGroup,
|
||||
ManagedImageName: name,
|
||||
ManagedImageLocation: location,
|
||||
ManagedImageId: id,
|
||||
OSType: osType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewArtifact(template *CaptureTemplate, getSasUrl func(name string) string, osType string) (*Artifact, error) {
|
||||
func NewArtifact(template *CaptureTemplate, getSasUrl func(name string) string) (*Artifact, error) {
|
||||
if template == nil {
|
||||
return nil, fmt.Errorf("nil capture template")
|
||||
}
|
||||
@@ -82,7 +76,6 @@ func NewArtifact(template *CaptureTemplate, getSasUrl func(name string) string,
|
||||
}
|
||||
|
||||
return &Artifact{
|
||||
OSType: osType,
|
||||
OSDiskUri: vhdUri.String(),
|
||||
OSDiskUriReadOnlySas: getSasUrl(getStorageUrlPath(vhdUri)),
|
||||
TemplateUri: templateUri.String(),
|
||||
@@ -149,11 +142,9 @@ func (a *Artifact) String() string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
buf.WriteString(fmt.Sprintf("%s:\n\n", a.BuilderId()))
|
||||
buf.WriteString(fmt.Sprintf("OSType: %s\n", a.OSType))
|
||||
if a.isManagedImage() {
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageResourceGroupName: %s\n", a.ManagedImageResourceGroupName))
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageName: %s\n", a.ManagedImageName))
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageId: %s\n", a.ManagedImageId))
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageLocation: %s\n", a.ManagedImageLocation))
|
||||
} else {
|
||||
buf.WriteString(fmt.Sprintf("StorageAccountLocation: %s\n", a.StorageAccountLocation))
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestArtifactId(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
artifact, err := NewArtifact(&template, getFakeSasUrl, "Linux")
|
||||
artifact, err := NewArtifact(&template, getFakeSasUrl)
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func TestArtifactString(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
artifact, err := NewArtifact(&template, getFakeSasUrl, "Linux")
|
||||
artifact, err := NewArtifact(&template, getFakeSasUrl)
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
@@ -80,9 +80,6 @@ func TestArtifactString(t *testing.T) {
|
||||
if !strings.Contains(testSubject, "StorageAccountLocation: southcentralus") {
|
||||
t.Errorf("Expected String() output to contain StorageAccountLocation")
|
||||
}
|
||||
if !strings.Contains(testSubject, "OSType: Linux") {
|
||||
t.Errorf("Expected String() output to contain OSType")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdditionalDiskArtifactString(t *testing.T) {
|
||||
@@ -110,7 +107,7 @@ func TestAdditionalDiskArtifactString(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
artifact, err := NewArtifact(&template, getFakeSasUrl, "Linux")
|
||||
artifact, err := NewArtifact(&template, getFakeSasUrl)
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
@@ -131,9 +128,6 @@ func TestAdditionalDiskArtifactString(t *testing.T) {
|
||||
if !strings.Contains(testSubject, "StorageAccountLocation: southcentralus") {
|
||||
t.Errorf("Expected String() output to contain StorageAccountLocation")
|
||||
}
|
||||
if !strings.Contains(testSubject, "OSType: Linux") {
|
||||
t.Errorf("Expected String() output to contain OSType")
|
||||
}
|
||||
if !strings.Contains(testSubject, "AdditionalDiskUri (datadisk-1): https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-datadisk-1.4085bb15-3644-4641-b9cd-f575918640b4.vhd") {
|
||||
t.Errorf("Expected String() output to contain AdditionalDiskUri")
|
||||
}
|
||||
@@ -160,7 +154,7 @@ func TestArtifactProperties(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
testSubject, err := NewArtifact(&template, getFakeSasUrl, "Linux")
|
||||
testSubject, err := NewArtifact(&template, getFakeSasUrl)
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
@@ -180,9 +174,6 @@ func TestArtifactProperties(t *testing.T) {
|
||||
if testSubject.StorageAccountLocation != "southcentralus" {
|
||||
t.Errorf("Expected StorageAccountLocation to be 'southcentral', but got %s", testSubject.StorageAccountLocation)
|
||||
}
|
||||
if testSubject.OSType != "Linux" {
|
||||
t.Errorf("Expected OSType to be 'Linux', but got %s", testSubject.OSType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdditionalDiskArtifactProperties(t *testing.T) {
|
||||
@@ -210,7 +201,7 @@ func TestAdditionalDiskArtifactProperties(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
testSubject, err := NewArtifact(&template, getFakeSasUrl, "Linux")
|
||||
testSubject, err := NewArtifact(&template, getFakeSasUrl)
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
@@ -230,9 +221,6 @@ func TestAdditionalDiskArtifactProperties(t *testing.T) {
|
||||
if testSubject.StorageAccountLocation != "southcentralus" {
|
||||
t.Errorf("Expected StorageAccountLocation to be 'southcentral', but got %s", testSubject.StorageAccountLocation)
|
||||
}
|
||||
if testSubject.OSType != "Linux" {
|
||||
t.Errorf("Expected OSType to be 'Linux', but got %s", testSubject.OSType)
|
||||
}
|
||||
if testSubject.AdditionalDisks == nil {
|
||||
t.Errorf("Expected AdditionalDisks to be not nil")
|
||||
}
|
||||
@@ -265,7 +253,7 @@ func TestArtifactOverHyphenatedCaptureUri(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
testSubject, err := NewArtifact(&template, getFakeSasUrl, "Linux")
|
||||
testSubject, err := NewArtifact(&template, getFakeSasUrl)
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
@@ -278,7 +266,7 @@ func TestArtifactOverHyphenatedCaptureUri(t *testing.T) {
|
||||
func TestArtifactRejectMalformedTemplates(t *testing.T) {
|
||||
template := CaptureTemplate{}
|
||||
|
||||
_, err := NewArtifact(&template, getFakeSasUrl, "Linux")
|
||||
_, err := NewArtifact(&template, getFakeSasUrl)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected artifact creation to fail, but it succeeded.")
|
||||
}
|
||||
@@ -301,7 +289,7 @@ func TestArtifactRejectMalformedStorageUri(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := NewArtifact(&template, getFakeSasUrl, "Linux")
|
||||
_, err := NewArtifact(&template, getFakeSasUrl)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected artifact creation to fail, but it succeeded.")
|
||||
}
|
||||
|
||||
@@ -16,25 +16,25 @@ func TestNewAuthenticate(t *testing.T) {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if spn.Token().AccessToken != "" {
|
||||
t.Errorf("spn.Token().AccessToken: expected=\"\", actual=%s", spn.Token().AccessToken)
|
||||
if spn.Token.AccessToken != "" {
|
||||
t.Errorf("spn.Token.AccessToken: expected=\"\", actual=%s", spn.Token.AccessToken)
|
||||
}
|
||||
if spn.Token().RefreshToken != "" {
|
||||
t.Errorf("spn.Token().RefreshToken: expected=\"\", actual=%s", spn.Token().RefreshToken)
|
||||
if spn.Token.RefreshToken != "" {
|
||||
t.Errorf("spn.Token.RefreshToken: expected=\"\", actual=%s", spn.Token.RefreshToken)
|
||||
}
|
||||
if spn.Token().ExpiresIn != "" {
|
||||
t.Errorf("spn.Token().ExpiresIn: expected=\"\", actual=%s", spn.Token().ExpiresIn)
|
||||
if spn.Token.ExpiresIn != "" {
|
||||
t.Errorf("spn.Token.ExpiresIn: expected=\"\", actual=%s", spn.Token.ExpiresIn)
|
||||
}
|
||||
if spn.Token().ExpiresOn != "" {
|
||||
t.Errorf("spn.Token().ExpiresOn: expected=\"\", actual=%s", spn.Token().ExpiresOn)
|
||||
if spn.Token.ExpiresOn != "" {
|
||||
t.Errorf("spn.Token.ExpiresOn: expected=\"\", actual=%s", spn.Token.ExpiresOn)
|
||||
}
|
||||
if spn.Token().NotBefore != "" {
|
||||
t.Errorf("spn.Token().NotBefore: expected=\"\", actual=%s", spn.Token().NotBefore)
|
||||
if spn.Token.NotBefore != "" {
|
||||
t.Errorf("spn.Token.NotBefore: expected=\"\", actual=%s", spn.Token.NotBefore)
|
||||
}
|
||||
if spn.Token().Resource != "" {
|
||||
t.Errorf("spn.Token().Resource: expected=\"\", actual=%s", spn.Token().Resource)
|
||||
if spn.Token.Resource != "" {
|
||||
t.Errorf("spn.Token.Resource: expected=\"\", actual=%s", spn.Token.Resource)
|
||||
}
|
||||
if spn.Token().Type != "" {
|
||||
t.Errorf("spn.Token().Type: expected=\"\", actual=%s", spn.Token().Type)
|
||||
if spn.Token.Type != "" {
|
||||
t.Errorf("spn.Token.Type: expected=\"\", actual=%s", spn.Token.Type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
@@ -10,10 +9,11 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-01-01/network"
|
||||
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-02-01/resources"
|
||||
armStorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2017-10-01/storage"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/disk"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/network"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
|
||||
armStorage "github.com/Azure/azure-sdk-for-go/arm/storage"
|
||||
"github.com/Azure/azure-sdk-for-go/storage"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
@@ -39,7 +39,7 @@ type AzureClient struct {
|
||||
compute.VirtualMachinesClient
|
||||
common.VaultClient
|
||||
armStorage.AccountsClient
|
||||
compute.DisksClient
|
||||
disk.DisksClient
|
||||
|
||||
InspectorMaxLength int
|
||||
Template *CaptureTemplate
|
||||
@@ -141,7 +141,7 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
|
||||
azureClient.DeploymentOperationsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.DeploymentOperationsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DeploymentOperationsClient.UserAgent)
|
||||
|
||||
azureClient.DisksClient = compute.NewDisksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.DisksClient = disk.NewDisksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.DisksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.DisksClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.DisksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
@@ -222,7 +222,7 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
|
||||
|
||||
// If this is a managed disk build, this should be ignored.
|
||||
if resourceGroupName != "" && storageAccountName != "" {
|
||||
accountKeys, err := azureClient.AccountsClient.ListKeys(context.TODO(), resourceGroupName, storageAccountName)
|
||||
accountKeys, err := azureClient.AccountsClient.ListKeys(resourceGroupName, storageAccountName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -10,13 +9,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
armstorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2017-10-01/storage"
|
||||
"github.com/Azure/azure-sdk-for-go/storage"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
packerAzureCommon "github.com/hashicorp/packer/builder/azure/common"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/builder/azure/common/lin"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/storage"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
packerCommon "github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
@@ -24,15 +23,15 @@ import (
|
||||
)
|
||||
|
||||
type Builder struct {
|
||||
config *Config
|
||||
stateBag multistep.StateBag
|
||||
runner multistep.Runner
|
||||
ctxCancel context.CancelFunc
|
||||
config *Config
|
||||
stateBag multistep.StateBag
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultSasBlobContainer = "system/Microsoft.Compute"
|
||||
DefaultSecretName = "packerKeyVaultSecret"
|
||||
DefaultSasBlobContainer = "system/Microsoft.Compute"
|
||||
DefaultSasBlobPermission = "r"
|
||||
DefaultSecretName = "packerKeyVaultSecret"
|
||||
)
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
@@ -52,13 +51,8 @@ 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 ...")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
b.ctxCancel = cancel
|
||||
defer cancel()
|
||||
|
||||
if err := newConfigRetriever().FillParameters(b.config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -91,18 +85,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
if err := resolver.Resolve(b.config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.config.ObjectID == "" {
|
||||
b.config.ObjectID = getObjectIdFromToken(ui, spnCloud)
|
||||
} else {
|
||||
ui.Message("You have provided Object_ID which is no longer needed, azure packer builder determines this dynamically from the authentication token")
|
||||
}
|
||||
|
||||
if b.config.ObjectID == "" && b.config.OSType != constants.Target_Linux {
|
||||
return nil, fmt.Errorf("could not determine the ObjectID for the user, which is required for Windows builds")
|
||||
}
|
||||
|
||||
if b.config.isManagedImage() {
|
||||
group, err := azureClient.GroupsClient.Get(ctx, b.config.ManagedImageResourceGroupName)
|
||||
group, err := azureClient.GroupsClient.Get(b.config.ManagedImageResourceGroupName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot locate the managed image resource group %s.", b.config.ManagedImageResourceGroupName)
|
||||
}
|
||||
@@ -110,14 +95,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
b.config.manageImageLocation = *group.Location
|
||||
|
||||
// If a managed image already exists it cannot be overwritten.
|
||||
_, err = azureClient.ImagesClient.Get(ctx, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, "")
|
||||
_, err = azureClient.ImagesClient.Get(b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, "")
|
||||
if err == nil {
|
||||
if b.config.PackerForce {
|
||||
_, errChan := azureClient.ImagesClient.Delete(b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, nil)
|
||||
ui.Say(fmt.Sprintf("the managed image named %s already exists, but deleting it due to -force flag", b.config.ManagedImageName))
|
||||
f, err := azureClient.ImagesClient.Delete(ctx, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletion(ctx, azureClient.ImagesClient.Client)
|
||||
}
|
||||
err = <-errChan
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to delete the managed image named %s : %s", b.config.ManagedImageName, azureClient.LastError.Error())
|
||||
}
|
||||
@@ -125,13 +108,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
return nil, fmt.Errorf("the managed image named %s already exists in the resource group %s, use the -force option to automatically delete it.", b.config.ManagedImageName, b.config.ManagedImageResourceGroupName)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// User is not using Managed Images to build, warning message here that this path is being deprecated
|
||||
ui.Error("Warning: You are using Azure Packer Builder to create VHDs which is being deprecated, consider using Managed Images. Learn more http://aka.ms/packermanagedimage")
|
||||
}
|
||||
|
||||
if b.config.BuildResourceGroupName != "" {
|
||||
group, err := azureClient.GroupsClient.Get(ctx, b.config.BuildResourceGroupName)
|
||||
group, err := azureClient.GroupsClient.Get(b.config.BuildResourceGroupName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot locate the existing build resource resource group %s.", b.config.BuildResourceGroupName)
|
||||
}
|
||||
@@ -140,7 +120,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
}
|
||||
|
||||
if b.config.StorageAccount != "" {
|
||||
account, err := b.getBlobAccount(ctx, azureClient, b.config.ResourceGroupName, b.config.StorageAccount)
|
||||
account, err := b.getBlobAccount(azureClient, b.config.ResourceGroupName, b.config.StorageAccount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -174,7 +154,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
&communicator.StepConnectSSH{
|
||||
Config: &b.config.Comm,
|
||||
Host: lin.SSHHost,
|
||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
||||
SSHConfig: lin.SSHConfig(b.config.UserName),
|
||||
},
|
||||
&packerCommon.StepProvision{},
|
||||
NewStepGetOSDisk(azureClient, ui),
|
||||
@@ -197,8 +177,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
NewStepDeployTemplate(azureClient, ui, b.config, deploymentName, GetVirtualMachineDeployment),
|
||||
NewStepGetIPAddress(azureClient, ui, endpointConnectType),
|
||||
&StepSaveWinRMPassword{
|
||||
Password: b.config.tmpAdminPassword,
|
||||
BuildName: b.config.PackerBuildName,
|
||||
Password: b.config.tmpAdminPassword,
|
||||
},
|
||||
&communicator.StepConnectWinRM{
|
||||
Config: &b.config.Comm,
|
||||
@@ -229,7 +208,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
ui.Message(fmt.Sprintf("temp admin user: '%s'", b.config.UserName))
|
||||
ui.Message(fmt.Sprintf("temp admin password: '%s'", b.config.Password))
|
||||
|
||||
if len(b.config.Comm.SSHPrivateKey) != 0 {
|
||||
if b.config.sshPrivateKey != "" {
|
||||
debugKeyPath := fmt.Sprintf("%s-%s.pem", b.config.PackerBuildName, b.config.tmpComputeName)
|
||||
ui.Message(fmt.Sprintf("temp ssh key: %s", debugKeyPath))
|
||||
|
||||
@@ -255,20 +234,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
}
|
||||
|
||||
if b.config.isManagedImage() {
|
||||
managedImageID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/images/%s", b.config.SubscriptionID, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName)
|
||||
return NewManagedImageArtifact(b.config.OSType, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, b.config.manageImageLocation, managedImageID)
|
||||
return NewManagedImageArtifact(b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, b.config.manageImageLocation)
|
||||
} else if template, ok := b.stateBag.GetOk(constants.ArmCaptureTemplate); ok {
|
||||
return NewArtifact(
|
||||
template.(*CaptureTemplate),
|
||||
func(name string) string {
|
||||
month := time.Now().AddDate(0, 1, 0).UTC()
|
||||
blob := azureClient.BlobStorageClient.GetContainerReference(DefaultSasBlobContainer).GetBlobReference(name)
|
||||
options := storage.BlobSASOptions{}
|
||||
options.BlobServiceSASPermissions.Read = true
|
||||
options.Expiry = time.Now().AddDate(0, 1, 0).UTC() // one month
|
||||
sasUrl, _ := blob.GetSASURI(options)
|
||||
sasUrl, _ := blob.GetSASURI(month, DefaultSasBlobPermission)
|
||||
return sasUrl
|
||||
},
|
||||
b.config.OSType)
|
||||
})
|
||||
}
|
||||
|
||||
return &Artifact{}, nil
|
||||
@@ -282,7 +257,7 @@ func (b *Builder) writeSSHPrivateKey(ui packer.Ui, debugKeyPath string) {
|
||||
defer f.Close()
|
||||
|
||||
// Write the key out
|
||||
if _, err := f.Write(b.config.Comm.SSHPrivateKey); err != nil {
|
||||
if _, err := f.Write([]byte(b.config.sshPrivateKey)); err != nil {
|
||||
ui.Say(fmt.Sprintf("Error saving debug key: %s", err))
|
||||
return
|
||||
}
|
||||
@@ -304,10 +279,6 @@ func (b *Builder) isPrivateNetworkCommunication() bool {
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.ctxCancel != nil {
|
||||
log.Printf("Cancelling Azure builder...")
|
||||
b.ctxCancel()
|
||||
}
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
@@ -322,8 +293,8 @@ func canonicalizeLocation(location string) string {
|
||||
return strings.Replace(location, " ", "", -1)
|
||||
}
|
||||
|
||||
func (b *Builder) getBlobAccount(ctx context.Context, client *AzureClient, resourceGroupName string, storageAccountName string) (*armstorage.Account, error) {
|
||||
account, err := client.AccountsClient.GetProperties(ctx, resourceGroupName, storageAccountName)
|
||||
func (b *Builder) getBlobAccount(client *AzureClient, resourceGroupName string, storageAccountName string) (*storage.Account, error) {
|
||||
account, err := client.AccountsClient.GetProperties(resourceGroupName, storageAccountName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -333,8 +304,9 @@ func (b *Builder) getBlobAccount(ctx context.Context, client *AzureClient, resou
|
||||
|
||||
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.ArmTags, &b.config.AzureTags)
|
||||
stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
|
||||
stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
|
||||
if b.config.OSType == constants.Target_Windows {
|
||||
@@ -358,7 +330,6 @@ func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
|
||||
stateBag.Put(constants.ArmIsManagedImage, b.config.isManagedImage())
|
||||
stateBag.Put(constants.ArmManagedImageResourceGroupName, b.config.ManagedImageResourceGroupName)
|
||||
stateBag.Put(constants.ArmManagedImageName, b.config.ManagedImageName)
|
||||
stateBag.Put(constants.ArmAsyncResourceGroupDelete, b.config.AsyncResourceGroupDelete)
|
||||
}
|
||||
|
||||
// Parameters that are only known at runtime after querying Azure.
|
||||
@@ -382,17 +353,10 @@ func (b *Builder) getServicePrincipalTokens(say func(string)) (*adal.ServicePrin
|
||||
var err error
|
||||
|
||||
if b.config.useDeviceLogin {
|
||||
say("Getting auth token for Service management endpoint")
|
||||
servicePrincipalToken, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.TenantID, say, b.config.cloudEnvironment.ServiceManagementEndpoint)
|
||||
servicePrincipalToken, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.TenantID, say)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
say("Getting token for Vault resource")
|
||||
servicePrincipalTokenVault, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.TenantID, say, strings.TrimRight(b.config.cloudEnvironment.KeyVaultEndpoint, "/"))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
auth := NewAuthenticate(*b.config.cloudEnvironment, b.config.ClientID, b.config.ClientSecret, b.config.TenantID)
|
||||
|
||||
@@ -403,39 +367,11 @@ func (b *Builder) getServicePrincipalTokens(say func(string)) (*adal.ServicePrin
|
||||
|
||||
servicePrincipalTokenVault, err = auth.getServicePrincipalTokenWithResource(
|
||||
strings.TrimRight(b.config.cloudEnvironment.KeyVaultEndpoint, "/"))
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = servicePrincipalToken.EnsureFresh()
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = servicePrincipalTokenVault.EnsureFresh()
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return servicePrincipalToken, servicePrincipalTokenVault, nil
|
||||
}
|
||||
|
||||
func getObjectIdFromToken(ui packer.Ui, token *adal.ServicePrincipalToken) string {
|
||||
claims := jwt.MapClaims{}
|
||||
var p jwt.Parser
|
||||
|
||||
var err error
|
||||
|
||||
_, _, err = p.ParseUnverified(token.OAuthToken(), claims)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Failed to parse the token,Error: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
return claims["oid"].(string)
|
||||
|
||||
}
|
||||
|
||||
@@ -1,322 +0,0 @@
|
||||
package arm
|
||||
|
||||
// these tests require the following variables to be set,
|
||||
// although some test will only use a subset:
|
||||
//
|
||||
// * ARM_CLIENT_ID
|
||||
// * ARM_CLIENT_SECRET
|
||||
// * ARM_SUBSCRIPTION_ID
|
||||
// * ARM_OBJECT_ID
|
||||
// * ARM_STORAGE_ACCOUNT
|
||||
//
|
||||
// The subscription in question should have a resource group
|
||||
// called "packer-acceptance-test" in "South Central US" region. The
|
||||
// storage account refered to in the above variable should
|
||||
// be inside this resource group and in "South Central US" as well.
|
||||
//
|
||||
// In addition, the PACKER_ACC variable should also be set to
|
||||
// a non-empty value to enable Packer acceptance tests and the
|
||||
// options "-v -timeout 90m" should be provided to the test
|
||||
// command, e.g.:
|
||||
// go test -v -timeout 90m -run TestBuilderAcc_.*
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fmt"
|
||||
builderT "github.com/hashicorp/packer/helper/builder/testing"
|
||||
"os"
|
||||
)
|
||||
|
||||
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Windows(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccManagedDiskWindows,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Windows_Build_Resource_Group(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccManagedDiskWindowsBuildResourceGroup,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Windows_DeviceLogin(t *testing.T) {
|
||||
if os.Getenv(DeviceLoginAcceptanceTest) == "" {
|
||||
t.Skip(fmt.Sprintf(
|
||||
"Device Login Acceptance tests skipped unless env '%s' set, as its requires manual step during execution",
|
||||
DeviceLoginAcceptanceTest))
|
||||
return
|
||||
}
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccManagedDiskWindowsDeviceLogin,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Linux(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccManagedDiskLinux,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Linux_DeviceLogin(t *testing.T) {
|
||||
if os.Getenv(DeviceLoginAcceptanceTest) == "" {
|
||||
t.Skip(fmt.Sprintf(
|
||||
"Device Login Acceptance tests skipped unless env '%s' set, as its requires manual step during execution",
|
||||
DeviceLoginAcceptanceTest))
|
||||
return
|
||||
}
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccManagedDiskLinuxDeviceLogin,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_Blob_Windows(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccBlobWindows,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_Blob_Linux(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccBlobLinux,
|
||||
})
|
||||
}
|
||||
|
||||
func testAccPreCheck(*testing.T) {}
|
||||
|
||||
const testBuilderAccManagedDiskWindows = `
|
||||
{
|
||||
"variables": {
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskWindows-{{timestamp}}",
|
||||
|
||||
"os_type": "Windows",
|
||||
"image_publisher": "MicrosoftWindowsServer",
|
||||
"image_offer": "WindowsServer",
|
||||
"image_sku": "2012-R2-Datacenter",
|
||||
|
||||
"communicator": "winrm",
|
||||
"winrm_use_ssl": "true",
|
||||
"winrm_insecure": "true",
|
||||
"winrm_timeout": "3m",
|
||||
"winrm_username": "packer",
|
||||
"async_resourcegroup_delete": "true",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_DS2_v2"
|
||||
}]
|
||||
}
|
||||
`
|
||||
const testBuilderAccManagedDiskWindowsBuildResourceGroup = `
|
||||
{
|
||||
"variables": {
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"build_resource_group_name" : "packer-acceptance-test",
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskWindows-{{timestamp}}",
|
||||
|
||||
"os_type": "Windows",
|
||||
"image_publisher": "MicrosoftWindowsServer",
|
||||
"image_offer": "WindowsServer",
|
||||
"image_sku": "2012-R2-Datacenter",
|
||||
|
||||
"communicator": "winrm",
|
||||
"winrm_use_ssl": "true",
|
||||
"winrm_insecure": "true",
|
||||
"winrm_timeout": "3m",
|
||||
"winrm_username": "packer",
|
||||
"async_resourcegroup_delete": "true",
|
||||
|
||||
"vm_size": "Standard_DS2_v2"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccManagedDiskWindowsDeviceLogin = `
|
||||
{
|
||||
"variables": {
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskWindowsDeviceLogin-{{timestamp}}",
|
||||
|
||||
"os_type": "Windows",
|
||||
"image_publisher": "MicrosoftWindowsServer",
|
||||
"image_offer": "WindowsServer",
|
||||
"image_sku": "2012-R2-Datacenter",
|
||||
|
||||
"communicator": "winrm",
|
||||
"winrm_use_ssl": "true",
|
||||
"winrm_insecure": "true",
|
||||
"winrm_timeout": "3m",
|
||||
"winrm_username": "packer",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_DS2_v2"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccManagedDiskLinux = `
|
||||
{
|
||||
"variables": {
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskLinux-{{timestamp}}",
|
||||
|
||||
"os_type": "Linux",
|
||||
"image_publisher": "Canonical",
|
||||
"image_offer": "UbuntuServer",
|
||||
"image_sku": "16.04-LTS",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_DS2_v2"
|
||||
}]
|
||||
}
|
||||
`
|
||||
const testBuilderAccManagedDiskLinuxDeviceLogin = `
|
||||
{
|
||||
"variables": {
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskLinuxDeviceLogin-{{timestamp}}",
|
||||
|
||||
"os_type": "Linux",
|
||||
"image_publisher": "Canonical",
|
||||
"image_offer": "UbuntuServer",
|
||||
"image_sku": "16.04-LTS",
|
||||
"async_resourcegroup_delete": "true",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_DS2_v2"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccBlobWindows = `
|
||||
{
|
||||
"variables": {
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}",
|
||||
"object_id": "{{env ` + "`ARM_OBJECT_ID`" + `}}",
|
||||
"storage_account": "{{env ` + "`ARM_STORAGE_ACCOUNT`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
"object_id": "{{user ` + "`object_id`" + `}}",
|
||||
|
||||
"storage_account": "{{user ` + "`storage_account`" + `}}",
|
||||
"resource_group_name": "packer-acceptance-test",
|
||||
"capture_container_name": "test",
|
||||
"capture_name_prefix": "testBuilderAccBlobWin",
|
||||
|
||||
"os_type": "Windows",
|
||||
"image_publisher": "MicrosoftWindowsServer",
|
||||
"image_offer": "WindowsServer",
|
||||
"image_sku": "2012-R2-Datacenter",
|
||||
|
||||
"communicator": "winrm",
|
||||
"winrm_use_ssl": "true",
|
||||
"winrm_insecure": "true",
|
||||
"winrm_timeout": "3m",
|
||||
"winrm_username": "packer",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_DS2_v2"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccBlobLinux = `
|
||||
{
|
||||
"variables": {
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}",
|
||||
"storage_account": "{{env ` + "`ARM_STORAGE_ACCOUNT`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"storage_account": "{{user ` + "`storage_account`" + `}}",
|
||||
"resource_group_name": "packer-acceptance-test",
|
||||
"capture_container_name": "test",
|
||||
"capture_name_prefix": "testBuilderAccBlobLinux",
|
||||
|
||||
"os_type": "Linux",
|
||||
"image_publisher": "Canonical",
|
||||
"image_offer": "UbuntuServer",
|
||||
"image_sku": "16.04-LTS",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_DS2_v2"
|
||||
}]
|
||||
}
|
||||
`
|
||||
@@ -15,6 +15,7 @@ func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
|
||||
|
||||
var expectedStateBagKeys = []string{
|
||||
constants.AuthorizedKey,
|
||||
constants.PrivateKey,
|
||||
|
||||
constants.ArmTags,
|
||||
constants.ArmComputeName,
|
||||
@@ -24,7 +25,6 @@ func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
|
||||
constants.ArmStorageAccountName,
|
||||
constants.ArmVirtualMachineCaptureParameters,
|
||||
constants.ArmPublicIPAddressName,
|
||||
constants.ArmAsyncResourceGroupDelete,
|
||||
}
|
||||
|
||||
for _, v := range expectedStateBagKeys {
|
||||
|
||||
+25
-21
@@ -14,7 +14,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"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/masterzen/winrm"
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/builder/azure/pkcs12"
|
||||
"github.com/hashicorp/packer/common"
|
||||
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -46,7 +47,7 @@ const (
|
||||
// -> ^[^_\W][\w-._]{0,79}(?<![-.])$
|
||||
//
|
||||
// This is not an exhaustive match, but it should be extremely close.
|
||||
validResourceGroupNameRe = "^[^_\\W][\\w-._\\(\\)]{0,89}$"
|
||||
validResourceGroupNameRe = "^[^_\\W][\\w-._\\(\\)]{0,63}$"
|
||||
validManagedDiskName = "^[^_\\W][\\w-._)]{0,79}$"
|
||||
)
|
||||
|
||||
@@ -143,15 +144,13 @@ type Config struct {
|
||||
|
||||
// Authentication with the VM via SSH
|
||||
sshAuthorizedKey string
|
||||
sshPrivateKey string
|
||||
|
||||
// Authentication with the VM via WinRM
|
||||
winrmCertificate string
|
||||
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
ctx interpolate.Context
|
||||
|
||||
//Cleanup
|
||||
AsyncResourceGroupDelete bool `mapstructure:"async_resourcegroup_delete"`
|
||||
ctx *interpolate.Context
|
||||
}
|
||||
|
||||
type keyVaultCertificate struct {
|
||||
@@ -190,7 +189,7 @@ func (c *Config) toImageParameters() *compute.Image {
|
||||
},
|
||||
},
|
||||
Location: to.StringPtr(c.Location),
|
||||
Tags: c.AzureTags,
|
||||
Tags: &c.AzureTags,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,10 +255,10 @@ func (c *Config) createCertificate() (string, error) {
|
||||
|
||||
func newConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
var c Config
|
||||
c.ctx.Funcs = TemplateFuncs
|
||||
|
||||
err := config.Decode(&c, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &c.ctx,
|
||||
InterpolateContext: c.ctx,
|
||||
}, raws...)
|
||||
|
||||
if err != nil {
|
||||
@@ -297,7 +296,7 @@ func newConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(c.ctx)...)
|
||||
|
||||
assertRequiredParametersSet(&c, errs)
|
||||
assertTagProperties(&c, errs)
|
||||
@@ -313,8 +312,8 @@ func setSshValues(c *Config) error {
|
||||
c.Comm.SSHTimeout = 20 * time.Minute
|
||||
}
|
||||
|
||||
if c.Comm.SSHPrivateKeyFile != "" {
|
||||
privateKeyBytes, err := ioutil.ReadFile(c.Comm.SSHPrivateKeyFile)
|
||||
if c.Comm.SSHPrivateKey != "" {
|
||||
privateKeyBytes, err := ioutil.ReadFile(c.Comm.SSHPrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -328,7 +327,7 @@ func setSshValues(c *Config) error {
|
||||
publicKey.Type(),
|
||||
base64.StdEncoding.EncodeToString(publicKey.Marshal()),
|
||||
time.Now().Format(time.RFC3339))
|
||||
c.Comm.SSHPrivateKey = privateKeyBytes
|
||||
c.sshPrivateKey = string(privateKeyBytes)
|
||||
|
||||
} else {
|
||||
sshKeyPair, err := NewOpenSshKeyPair()
|
||||
@@ -337,7 +336,7 @@ func setSshValues(c *Config) error {
|
||||
}
|
||||
|
||||
c.sshAuthorizedKey = sshKeyPair.AuthorizedKey()
|
||||
c.Comm.SSHPrivateKey = sshKeyPair.PrivateKey()
|
||||
c.sshPrivateKey = sshKeyPair.PrivateKey()
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -359,7 +358,9 @@ func setRuntimeValues(c *Config) {
|
||||
var tempName = NewTempName()
|
||||
|
||||
c.tmpAdminPassword = tempName.AdminPassword
|
||||
packer.LogSecretFilter.Set(c.tmpAdminPassword)
|
||||
// store so that we can access this later during provisioning
|
||||
commonhelper.SetSharedState("winrm_password", c.tmpAdminPassword)
|
||||
|
||||
c.tmpCertificatePassword = tempName.CertificatePassword
|
||||
if c.TempComputeName == "" {
|
||||
c.tmpComputeName = tempName.ComputeName
|
||||
@@ -449,7 +450,7 @@ func provideDefaultValues(c *Config) {
|
||||
}
|
||||
|
||||
if c.ManagedImageStorageAccountType == "" {
|
||||
c.managedImageStorageAccountType = compute.StorageAccountTypesStandardLRS
|
||||
c.managedImageStorageAccountType = compute.StandardLRS
|
||||
}
|
||||
|
||||
if c.ImagePublisher != "" && c.ImageVersion == "" {
|
||||
@@ -471,7 +472,7 @@ func assertTagProperties(c *Config, errs *packer.MultiError) {
|
||||
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)))
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", v, len(*v)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,6 +490,9 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
|
||||
// readable by the ObjectID of the App. There may be another way to handle
|
||||
// this case, but I am not currently aware of it - send feedback.
|
||||
isUseDeviceLogin := func(c *Config) bool {
|
||||
if c.OSType == constants.Target_Windows {
|
||||
return false
|
||||
}
|
||||
|
||||
return c.SubscriptionID != "" &&
|
||||
c.ClientID == "" &&
|
||||
@@ -693,10 +697,10 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
|
||||
}
|
||||
|
||||
switch c.ManagedImageStorageAccountType {
|
||||
case "", string(compute.StorageAccountTypesStandardLRS):
|
||||
c.managedImageStorageAccountType = compute.StorageAccountTypesStandardLRS
|
||||
case string(compute.StorageAccountTypesPremiumLRS):
|
||||
c.managedImageStorageAccountType = compute.StorageAccountTypesPremiumLRS
|
||||
case "", string(compute.StandardLRS):
|
||||
c.managedImageStorageAccountType = compute.StandardLRS
|
||||
case string(compute.PremiumLRS):
|
||||
c.managedImageStorageAccountType = compute.PremiumLRS
|
||||
default:
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The managed_image_storage_account_type %q is invalid", c.ManagedImageStorageAccountType))
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ package arm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// List of configuration parameters that are required by the ARM builder.
|
||||
@@ -84,7 +86,7 @@ func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) {
|
||||
t.Errorf("Expected 'vm_size' to be set to 'override_vm_size', but found %q!", c.VMSize)
|
||||
}
|
||||
|
||||
if c.managedImageStorageAccountType != compute.StorageAccountTypesPremiumLRS {
|
||||
if c.managedImageStorageAccountType != compute.PremiumLRS {
|
||||
t.Errorf("Expected 'managed_image_storage_account_type' to be set to 'Premium_LRS', but found %q!", c.managedImageStorageAccountType)
|
||||
}
|
||||
}
|
||||
@@ -446,6 +448,39 @@ func TestUserDeviceLoginIsEnabledForLinux(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUseDeviceLoginIsDisabledForWindows(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"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",
|
||||
"os_type": constants.Target_Windows,
|
||||
"communicator": "none",
|
||||
}
|
||||
|
||||
_, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err == nil {
|
||||
t.Fatal("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 !strings.Contains(err.Error(), "client_id must be specified") {
|
||||
t.Error("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")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldRejectMalformedCaptureNamePrefix(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"capture_container_name": "ignore",
|
||||
@@ -586,12 +621,12 @@ func TestConfigShouldAcceptTags(t *testing.T) {
|
||||
|
||||
value := c.AzureTags["tag01"]
|
||||
if *value != "value01" {
|
||||
t.Errorf("expected AzureTags[\"tag01\"] to have value \"value01\", but got %q", *value)
|
||||
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)
|
||||
t.Errorf("expected AzureTags[\"tag02\"] to have value \"value02\", but got %q", value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -799,7 +834,7 @@ func TestConfigShouldRejectCustomAndPlatformManagedImageBuild(t *testing.T) {
|
||||
|
||||
func TestConfigShouldRejectCustomAndImageUrlForManagedImageBuild(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_url": "ignore",
|
||||
"image_url": "ignore",
|
||||
"custom_managed_image_resource_group_name": "ignore",
|
||||
"custom_managed_image_name": "ignore",
|
||||
"location": "ignore",
|
||||
@@ -1229,73 +1264,6 @@ func TestConfigShouldAllowTempNameOverrides(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldAllowAsyncResourceGroupOverride(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
"os_type": "linux",
|
||||
"managed_image_name": "ignore",
|
||||
"managed_image_resource_group_name": "ignore",
|
||||
"async_resourcegroup_delete": "true",
|
||||
}
|
||||
|
||||
c, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Errorf("newConfig failed with %q", err)
|
||||
}
|
||||
|
||||
if c.AsyncResourceGroupDelete != true {
|
||||
t.Errorf("expected async_resourcegroup_delete to be %q, but got %t", "async_resourcegroup_delete", c.AsyncResourceGroupDelete)
|
||||
}
|
||||
}
|
||||
func TestConfigShouldAllowAsyncResourceGroupOverrideNoValue(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
"os_type": "linux",
|
||||
"managed_image_name": "ignore",
|
||||
"managed_image_resource_group_name": "ignore",
|
||||
}
|
||||
|
||||
c, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Errorf("newConfig failed with %q", err)
|
||||
}
|
||||
|
||||
if c.AsyncResourceGroupDelete != false {
|
||||
t.Errorf("expected async_resourcegroup_delete to be %q, but got %t", "async_resourcegroup_delete", c.AsyncResourceGroupDelete)
|
||||
}
|
||||
}
|
||||
func TestConfigShouldAllowAsyncResourceGroupOverrideBadValue(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"image_offer": "ignore",
|
||||
"image_publisher": "ignore",
|
||||
"image_sku": "ignore",
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"communicator": "none",
|
||||
"os_type": "linux",
|
||||
"managed_image_name": "ignore",
|
||||
"managed_image_resource_group_name": "ignore",
|
||||
"async_resourcegroup_delete": "asdasda",
|
||||
}
|
||||
|
||||
c, _, err := newConfig(config, getPackerConfiguration())
|
||||
if err != nil && c == nil {
|
||||
t.Log("newConfig failed which is expected ", err)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getArmBuilderConfiguration() map[string]string {
|
||||
m := make(map[string]string)
|
||||
for _, v := range requiredConfigValues {
|
||||
|
||||
@@ -49,11 +49,11 @@ func (s *OpenSshKeyPair) AuthorizedKey() string {
|
||||
time.Now().Format(time.RFC3339))
|
||||
}
|
||||
|
||||
func (s *OpenSshKeyPair) PrivateKey() []byte {
|
||||
privateKey := pem.EncodeToMemory(&pem.Block{
|
||||
func (s *OpenSshKeyPair) PrivateKey() string {
|
||||
privateKey := string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(s.privateKey),
|
||||
})
|
||||
}))
|
||||
|
||||
return privateKey
|
||||
}
|
||||
|
||||
@@ -9,11 +9,10 @@ package arm
|
||||
// used to determine values without use of a client.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
)
|
||||
|
||||
type resourceResolver struct {
|
||||
@@ -24,7 +23,7 @@ type resourceResolver struct {
|
||||
|
||||
func newResourceResolver(client *AzureClient) *resourceResolver {
|
||||
return &resourceResolver{
|
||||
client: client,
|
||||
client: client,
|
||||
findVirtualNetworkResourceGroup: findVirtualNetworkResourceGroup,
|
||||
findVirtualNetworkSubnet: findVirtualNetworkSubnet,
|
||||
}
|
||||
@@ -73,40 +72,33 @@ func getResourceGroupNameFromId(id string) string {
|
||||
}
|
||||
|
||||
func findManagedImageByName(client *AzureClient, name, resourceGroupName string) (*compute.Image, error) {
|
||||
images, err := client.ImagesClient.ListByResourceGroupComplete(context.TODO(), resourceGroupName)
|
||||
images, err := client.ImagesClient.ListByResourceGroup(resourceGroupName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for images.NotDone() {
|
||||
image := images.Value()
|
||||
for _, image := range *images.Value {
|
||||
if strings.EqualFold(name, *image.Name) {
|
||||
return &image, nil
|
||||
}
|
||||
if err = images.Next(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Cannot find an image named '%s' in the resource group '%s'", name, resourceGroupName)
|
||||
}
|
||||
|
||||
func findVirtualNetworkResourceGroup(client *AzureClient, name string) (string, error) {
|
||||
virtualNetworks, err := client.VirtualNetworksClient.ListAllComplete(context.TODO())
|
||||
virtualNetworks, err := client.VirtualNetworksClient.ListAll()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resourceGroupNames := make([]string, 0)
|
||||
for virtualNetworks.NotDone() {
|
||||
virtualNetwork := virtualNetworks.Value()
|
||||
|
||||
for _, virtualNetwork := range *virtualNetworks.Value {
|
||||
if strings.EqualFold(name, *virtualNetwork.Name) {
|
||||
rgn := getResourceGroupNameFromId(*virtualNetwork.ID)
|
||||
resourceGroupNames = append(resourceGroupNames, rgn)
|
||||
}
|
||||
if err = virtualNetworks.Next(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if len(resourceGroupNames) == 0 {
|
||||
@@ -121,21 +113,19 @@ func findVirtualNetworkResourceGroup(client *AzureClient, name string) (string,
|
||||
}
|
||||
|
||||
func findVirtualNetworkSubnet(client *AzureClient, resourceGroupName string, name string) (string, error) {
|
||||
subnets, err := client.SubnetsClient.List(context.TODO(), resourceGroupName, name)
|
||||
subnets, err := client.SubnetsClient.List(resourceGroupName, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
subnetList := subnets.Values() // only first page of subnets, but only interested in ==0 or >1
|
||||
|
||||
if len(subnetList) == 0 {
|
||||
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(subnetList) > 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)
|
||||
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 := subnetList[0]
|
||||
subnet := (*subnets.Value)[0]
|
||||
return *subnet.Name, nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer/builder/azure/common"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
func processInterruptibleResult(
|
||||
result common.InterruptibleTaskResult, sayError func(error), state multistep.StateBag) multistep.StepAction {
|
||||
if result.IsCancelled {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return processStepResult(result.Err, sayError, state)
|
||||
}
|
||||
|
||||
func processStepResult(
|
||||
err error, sayError func(error), state multistep.StateBag) multistep.StepAction {
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
"github.com/hashicorp/packer/builder/azure/common"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -13,8 +14,8 @@ import (
|
||||
type StepCaptureImage struct {
|
||||
client *AzureClient
|
||||
generalizeVM func(resourceGroupName, computeName string) error
|
||||
captureVhd func(ctx context.Context, resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters) error
|
||||
captureManagedImage func(ctx context.Context, resourceGroupName string, computeName string, parameters *compute.Image) error
|
||||
captureVhd func(resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters, cancelCh <-chan struct{}) error
|
||||
captureManagedImage func(resourceGroupName string, computeName string, parameters *compute.Image, cancelCh <-chan struct{}) error
|
||||
get func(client *AzureClient) *CaptureTemplate
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
@@ -42,30 +43,32 @@ func NewStepCaptureImage(client *AzureClient, ui packer.Ui) *StepCaptureImage {
|
||||
}
|
||||
|
||||
func (s *StepCaptureImage) generalize(resourceGroupName string, computeName string) error {
|
||||
_, err := s.client.Generalize(context.TODO(), resourceGroupName, computeName)
|
||||
_, err := s.client.Generalize(resourceGroupName, computeName)
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepCaptureImage) captureImageFromVM(ctx context.Context, resourceGroupName string, imageName string, image *compute.Image) error {
|
||||
f, err := s.client.ImagesClient.CreateOrUpdate(ctx, resourceGroupName, imageName, *image)
|
||||
func (s *StepCaptureImage) captureImageFromVM(resourceGroupName string, imageName string, image *compute.Image, cancelCh <-chan struct{}) error {
|
||||
_, errChan := s.client.ImagesClient.CreateOrUpdate(resourceGroupName, imageName, *image, cancelCh)
|
||||
err := <-errChan
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return f.WaitForCompletion(ctx, s.client.ImagesClient.Client)
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
func (s *StepCaptureImage) captureImage(ctx context.Context, resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters) error {
|
||||
f, err := s.client.VirtualMachinesClient.Capture(ctx, resourceGroupName, computeName, *parameters)
|
||||
func (s *StepCaptureImage) captureImage(resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters, cancelCh <-chan struct{}) error {
|
||||
_, errChan := s.client.Capture(resourceGroupName, computeName, *parameters, cancelCh)
|
||||
err := <-errChan
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return f.WaitForCompletion(ctx, s.client.VirtualMachinesClient.Client)
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
func (s *StepCaptureImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepCaptureImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Capturing image ...")
|
||||
|
||||
var computeName = state.Get(constants.ArmComputeName).(string)
|
||||
@@ -83,37 +86,38 @@ func (s *StepCaptureImage) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
s.say(fmt.Sprintf(" -> Compute Name : '%s'", computeName))
|
||||
s.say(fmt.Sprintf(" -> Compute Location : '%s'", location))
|
||||
|
||||
err := s.generalizeVM(resourceGroupName, computeName)
|
||||
result := common.StartInterruptibleTask(
|
||||
func() bool {
|
||||
return common.IsStateCancelled(state)
|
||||
},
|
||||
func(cancelCh <-chan struct{}) error {
|
||||
err := s.generalizeVM(resourceGroupName, computeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if isManagedImage {
|
||||
s.say(fmt.Sprintf(" -> Image ResourceGroupName : '%s'", targetManagedImageResourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> Image Name : '%s'", targetManagedImageName))
|
||||
s.say(fmt.Sprintf(" -> Image Location : '%s'", targetManagedImageLocation))
|
||||
err = s.captureManagedImage(ctx, targetManagedImageResourceGroupName, targetManagedImageName, imageParameters)
|
||||
} else {
|
||||
err = s.captureVhd(ctx, resourceGroupName, computeName, vmCaptureParameters)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
state.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if isManagedImage {
|
||||
s.say(fmt.Sprintf(" -> Image ResourceGroupName : '%s'", targetManagedImageResourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> Image Name : '%s'", targetManagedImageName))
|
||||
s.say(fmt.Sprintf(" -> Image Location : '%s'", targetManagedImageLocation))
|
||||
return s.captureManagedImage(targetManagedImageResourceGroupName, targetManagedImageName, imageParameters, cancelCh)
|
||||
} else {
|
||||
return s.captureVhd(resourceGroupName, computeName, vmCaptureParameters, cancelCh)
|
||||
}
|
||||
})
|
||||
|
||||
// HACK(chrboum): I do not like this. The capture method should be returning this value
|
||||
// instead having to pass in another lambda.
|
||||
// instead having to pass in another lambda. I'm in this pickle because I am using
|
||||
// common.StartInterruptibleTask which is not parametric, and only returns a type of error.
|
||||
// I could change it to interface{}, but I do not like that solution either.
|
||||
//
|
||||
// Having to resort to capturing the template via an inspector is hack, and once I can
|
||||
// resolve that I can cleanup this code too. See the comments in azure_client.go for more
|
||||
// details.
|
||||
// [paulmey]: autorest.Future now has access to the last http.Response, but I'm not sure if
|
||||
// the body is still accessible.
|
||||
template := s.get(s.client)
|
||||
state.Put(constants.ArmCaptureTemplate, template)
|
||||
|
||||
return multistep.ActionContinue
|
||||
return processInterruptibleResult(result, s.error, state)
|
||||
}
|
||||
|
||||
func (*StepCaptureImage) Cleanup(multistep.StateBag) {
|
||||
|
||||
@@ -5,14 +5,14 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
func TestStepCaptureImageShouldFailIfCaptureFails(t *testing.T) {
|
||||
var testSubject = &StepCaptureImage{
|
||||
captureVhd: func(context.Context, string, string, *compute.VirtualMachineCaptureParameters) error {
|
||||
captureVhd: func(string, string, *compute.VirtualMachineCaptureParameters, <-chan struct{}) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
generalizeVM: func(string, string) error {
|
||||
@@ -39,7 +39,7 @@ func TestStepCaptureImageShouldFailIfCaptureFails(t *testing.T) {
|
||||
|
||||
func TestStepCaptureImageShouldPassIfCapturePasses(t *testing.T) {
|
||||
var testSubject = &StepCaptureImage{
|
||||
captureVhd: func(context.Context, string, string, *compute.VirtualMachineCaptureParameters) error { return nil },
|
||||
captureVhd: func(string, string, *compute.VirtualMachineCaptureParameters, <-chan struct{}) error { return nil },
|
||||
generalizeVM: func(string, string) error {
|
||||
return nil
|
||||
},
|
||||
@@ -74,7 +74,7 @@ func TestStepCaptureImageShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
}
|
||||
|
||||
var testSubject = &StepCaptureImage{
|
||||
captureVhd: func(_ context.Context, resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters) error {
|
||||
captureVhd: func(resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters, cancelCh <-chan struct{}) error {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualComputeName = computeName
|
||||
actualVirtualMachineCaptureParameters = parameters
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-02-01/resources"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -13,10 +13,10 @@ import (
|
||||
|
||||
type StepCreateResourceGroup struct {
|
||||
client *AzureClient
|
||||
create func(ctx context.Context, resourceGroupName string, location string, tags map[string]*string) error
|
||||
create func(resourceGroupName string, location string, tags *map[string]*string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
exists func(ctx context.Context, resourceGroupName string) (bool, error)
|
||||
exists func(resourceGroupName string) (bool, error)
|
||||
}
|
||||
|
||||
func NewStepCreateResourceGroup(client *AzureClient, ui packer.Ui) *StepCreateResourceGroup {
|
||||
@@ -31,8 +31,8 @@ func NewStepCreateResourceGroup(client *AzureClient, ui packer.Ui) *StepCreateRe
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepCreateResourceGroup) createResourceGroup(ctx context.Context, resourceGroupName string, location string, tags map[string]*string) error {
|
||||
_, err := s.client.GroupsClient.CreateOrUpdate(ctx, resourceGroupName, resources.Group{
|
||||
func (s *StepCreateResourceGroup) createResourceGroup(resourceGroupName string, location string, tags *map[string]*string) error {
|
||||
_, err := s.client.GroupsClient.CreateOrUpdate(resourceGroupName, resources.Group{
|
||||
Location: &location,
|
||||
Tags: tags,
|
||||
})
|
||||
@@ -43,8 +43,8 @@ func (s *StepCreateResourceGroup) createResourceGroup(ctx context.Context, resou
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepCreateResourceGroup) doesResourceGroupExist(ctx context.Context, resourceGroupName string) (bool, error) {
|
||||
exists, err := s.client.GroupsClient.CheckExistence(ctx, resourceGroupName)
|
||||
func (s *StepCreateResourceGroup) doesResourceGroupExist(resourceGroupName string) (bool, error) {
|
||||
exists, err := s.client.GroupsClient.CheckExistence(resourceGroupName)
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
@@ -52,7 +52,7 @@ func (s *StepCreateResourceGroup) doesResourceGroupExist(ctx context.Context, re
|
||||
return exists.Response.StatusCode != 404, err
|
||||
}
|
||||
|
||||
func (s *StepCreateResourceGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepCreateResourceGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
var doubleResource, ok = state.GetOk(constants.ArmDoubleResourceGroupNameSet)
|
||||
if ok && doubleResource.(bool) {
|
||||
err := errors.New("You have filled in both temp_resource_group_name and build_resource_group_name. Please choose one.")
|
||||
@@ -61,9 +61,9 @@ func (s *StepCreateResourceGroup) Run(ctx context.Context, state multistep.State
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var location = state.Get(constants.ArmLocation).(string)
|
||||
var tags = state.Get(constants.ArmTags).(map[string]*string)
|
||||
var tags = state.Get(constants.ArmTags).(*map[string]*string)
|
||||
|
||||
exists, err := s.exists(ctx, resourceGroupName)
|
||||
exists, err := s.exists(resourceGroupName)
|
||||
if err != nil {
|
||||
return processStepResult(err, s.error, state)
|
||||
}
|
||||
@@ -84,10 +84,10 @@ func (s *StepCreateResourceGroup) Run(ctx context.Context, state multistep.State
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> Location : '%s'", location))
|
||||
s.say(fmt.Sprintf(" -> Tags :"))
|
||||
for k, v := range tags {
|
||||
for k, v := range *tags {
|
||||
s.say(fmt.Sprintf(" ->> %s : %s", k, *v))
|
||||
}
|
||||
err = s.create(ctx, resourceGroupName, location, tags)
|
||||
err = s.create(resourceGroupName, location, tags)
|
||||
if err == nil {
|
||||
state.Put(constants.ArmIsResourceGroupCreated, true)
|
||||
}
|
||||
@@ -115,23 +115,15 @@ func (s *StepCreateResourceGroup) Cleanup(state multistep.StateBag) {
|
||||
ui.Say("\nCleanup requested, deleting resource group ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
ctx := context.TODO()
|
||||
f, err := s.client.GroupsClient.Delete(ctx, resourceGroupName)
|
||||
if err == nil {
|
||||
if state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
|
||||
s.say(fmt.Sprintf("\n Not waiting for Resource Group delete as requested by user. Resource Group Name is %s", resourceGroupName))
|
||||
} else {
|
||||
err = f.WaitForCompletion(ctx, s.client.GroupsClient.Client)
|
||||
}
|
||||
}
|
||||
_, errChan := s.client.GroupsClient.Delete(resourceGroupName, nil)
|
||||
err := <-errChan
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting resource group. Please delete it manually.\n\n"+
|
||||
"Name: %s\n"+
|
||||
"Error: %s", resourceGroupName, err))
|
||||
return
|
||||
}
|
||||
if !state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
|
||||
ui.Say("Resource group has been deleted.")
|
||||
}
|
||||
|
||||
ui.Say("Resource group has been deleted.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ func TestStepCreateResourceGroupShouldFailIfBothGroupNames(t *testing.T) {
|
||||
"tag01": &value,
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
stateBag.Put(constants.ArmTags, &tags)
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error { return nil },
|
||||
create: func(string, string, *map[string]*string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, nil },
|
||||
exists: func(string) (bool, error) { return false, nil },
|
||||
}
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
@@ -39,12 +39,10 @@ func TestStepCreateResourceGroupShouldFailIfBothGroupNames(t *testing.T) {
|
||||
|
||||
func TestStepCreateResourceGroupShouldFailIfCreateFails(t *testing.T) {
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
create: func(string, string, *map[string]*string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, nil },
|
||||
exists: func(string) (bool, error) { return false, nil },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateResourceGroup()
|
||||
@@ -61,10 +59,10 @@ func TestStepCreateResourceGroupShouldFailIfCreateFails(t *testing.T) {
|
||||
|
||||
func TestStepCreateResourceGroupShouldFailIfExistsFails(t *testing.T) {
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error { return nil },
|
||||
create: func(string, string, *map[string]*string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, errors.New("FAIL") },
|
||||
exists: func(string) (bool, error) { return false, errors.New("FAIL") },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateResourceGroup()
|
||||
@@ -81,10 +79,10 @@ func TestStepCreateResourceGroupShouldFailIfExistsFails(t *testing.T) {
|
||||
|
||||
func TestStepCreateResourceGroupShouldPassIfCreatePasses(t *testing.T) {
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error { return nil },
|
||||
create: func(string, string, *map[string]*string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, nil },
|
||||
exists: func(string) (bool, error) { return false, nil },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateResourceGroup()
|
||||
@@ -102,10 +100,10 @@ func TestStepCreateResourceGroupShouldPassIfCreatePasses(t *testing.T) {
|
||||
func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualResourceGroupName string
|
||||
var actualLocation string
|
||||
var actualTags map[string]*string
|
||||
var actualTags *map[string]*string
|
||||
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(_ context.Context, resourceGroupName string, location string, tags map[string]*string) error {
|
||||
create: func(resourceGroupName string, location string, tags *map[string]*string) error {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualLocation = location
|
||||
actualTags = tags
|
||||
@@ -113,7 +111,7 @@ func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, nil },
|
||||
exists: func(string) (bool, error) { return false, nil },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateResourceGroup()
|
||||
@@ -125,7 +123,7 @@ func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T
|
||||
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
var expectedLocation = stateBag.Get(constants.ArmLocation).(string)
|
||||
var expectedTags = stateBag.Get(constants.ArmTags).(map[string]*string)
|
||||
var expectedTags = stateBag.Get(constants.ArmTags).(*map[string]*string)
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
@@ -135,7 +133,7 @@ func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T
|
||||
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"] {
|
||||
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.")
|
||||
}
|
||||
|
||||
@@ -147,12 +145,10 @@ func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T
|
||||
|
||||
func TestStepCreateResourceGroupMarkShouldFailIfTryingExistingButDoesntExist(t *testing.T) {
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
create: func(string, string, *map[string]*string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, nil },
|
||||
exists: func(string) (bool, error) { return false, nil },
|
||||
}
|
||||
|
||||
stateBag := createTestExistingStateBagStepCreateResourceGroup()
|
||||
@@ -169,12 +165,10 @@ func TestStepCreateResourceGroupMarkShouldFailIfTryingExistingButDoesntExist(t *
|
||||
|
||||
func TestStepCreateResourceGroupMarkShouldFailIfTryingTempButExist(t *testing.T) {
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
create: func(string, string, *map[string]*string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return true, nil },
|
||||
exists: func(string) (bool, error) { return true, nil },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateResourceGroup()
|
||||
@@ -201,7 +195,7 @@ func createTestStateBagStepCreateResourceGroup() multistep.StateBag {
|
||||
"tag01": &value,
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
stateBag.Put(constants.ArmTags, &tags)
|
||||
return stateBag
|
||||
}
|
||||
|
||||
@@ -217,6 +211,6 @@ func createTestExistingStateBagStepCreateResourceGroup() multistep.StateBag {
|
||||
"tag01": &value,
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
stateBag.Put(constants.ArmTags, &tags)
|
||||
return stateBag
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
type StepDeleteAdditionalDisk struct {
|
||||
client *AzureClient
|
||||
delete func(string, string) error
|
||||
deleteManaged func(context.Context, string, string) error
|
||||
deleteManaged func(string, string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
@@ -43,17 +43,15 @@ func (s *StepDeleteAdditionalDisk) deleteBlob(storageContainerName string, blobN
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepDeleteAdditionalDisk) deleteManagedDisk(ctx context.Context, resourceGroupName string, diskName string) error {
|
||||
xs := strings.Split(diskName, "/")
|
||||
diskName = xs[len(xs)-1]
|
||||
f, err := s.client.DisksClient.Delete(ctx, resourceGroupName, diskName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletion(ctx, s.client.DisksClient.Client)
|
||||
}
|
||||
func (s *StepDeleteAdditionalDisk) deleteManagedDisk(resourceGroupName string, imageName string) error {
|
||||
xs := strings.Split(imageName, "/")
|
||||
diskName := xs[len(xs)-1]
|
||||
_, errChan := s.client.DisksClient.Delete(resourceGroupName, diskName, nil)
|
||||
err := <-errChan
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepDeleteAdditionalDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepDeleteAdditionalDisk) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Deleting the temporary Additional disk ...")
|
||||
|
||||
var dataDisks = state.Get(constants.ArmAdditionalDiskVhds).([]string)
|
||||
@@ -75,7 +73,7 @@ func (s *StepDeleteAdditionalDisk) Run(ctx context.Context, state multistep.Stat
|
||||
s.say(fmt.Sprintf(" -> Additional Disk %d: '%s'", i+1, additionaldisk))
|
||||
var err error
|
||||
if isManagedDisk {
|
||||
err = s.deleteManaged(ctx, resourceGroupName, additionaldisk)
|
||||
err = s.deleteManaged(resourceGroupName, additionaldisk)
|
||||
if err != nil {
|
||||
s.say("Failed to delete the managed Additional Disk!")
|
||||
return processStepResult(err, s.error, state)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
func TestStepDeleteAdditionalDiskShouldFailIfGetFails(t *testing.T) {
|
||||
var testSubject = &StepDeleteAdditionalDisk{
|
||||
delete: func(string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||
deleteManaged: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func TestStepDeleteAdditionalDiskShouldFailIfVHDNameCannotBeURLParsed(t *testing
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||
deleteManaged: func(string, string) error { return nil },
|
||||
}
|
||||
|
||||
// Invalid URL per https://golang.org/src/net/url/url_test.go
|
||||
@@ -130,7 +130,7 @@ func TestStepDeleteAdditionalDiskShouldFailIfVHDNameIsTooShort(t *testing.T) {
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||
deleteManaged: func(string, string) error { return nil },
|
||||
}
|
||||
|
||||
stateBag := DeleteTestStateBagStepDeleteAdditionalDisk([]string{"storage.blob.core.windows.net/abc"})
|
||||
@@ -173,7 +173,7 @@ func TestStepDeleteAdditionalDiskShouldFailIfManagedDiskInExistingResourceGroupF
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
deleteManaged: func(context.Context, string, string) error { return errors.New("UNIT TEST FAIL!") },
|
||||
deleteManaged: func(string, string) error { return errors.New("UNIT TEST FAIL!") },
|
||||
}
|
||||
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
@@ -197,7 +197,7 @@ func TestStepDeleteAdditionalDiskShouldFailIfManagedDiskInExistingResourceGroupI
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||
deleteManaged: func(string, string) error { return nil },
|
||||
}
|
||||
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
type StepDeleteOSDisk struct {
|
||||
client *AzureClient
|
||||
delete func(string, string) error
|
||||
deleteManaged func(context.Context, string, string) error
|
||||
deleteManaged func(string, string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
@@ -43,17 +43,15 @@ func (s *StepDeleteOSDisk) deleteBlob(storageContainerName string, blobName stri
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepDeleteOSDisk) deleteManagedDisk(ctx context.Context, resourceGroupName string, imageName string) error {
|
||||
func (s *StepDeleteOSDisk) deleteManagedDisk(resourceGroupName string, imageName string) error {
|
||||
xs := strings.Split(imageName, "/")
|
||||
diskName := xs[len(xs)-1]
|
||||
f, err := s.client.DisksClient.Delete(ctx, resourceGroupName, diskName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletion(ctx, s.client.DisksClient.Client)
|
||||
}
|
||||
_, errChan := s.client.DisksClient.Delete(resourceGroupName, diskName, nil)
|
||||
err := <-errChan
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepDeleteOSDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepDeleteOSDisk) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Deleting the temporary OS disk ...")
|
||||
|
||||
var osDisk = state.Get(constants.ArmOSDiskVhd).(string)
|
||||
@@ -70,7 +68,7 @@ func (s *StepDeleteOSDisk) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
|
||||
var err error
|
||||
if isManagedDisk {
|
||||
err = s.deleteManaged(ctx, resourceGroupName, osDisk)
|
||||
err = s.deleteManaged(resourceGroupName, osDisk)
|
||||
if err != nil {
|
||||
s.say("Failed to delete the managed OS Disk!")
|
||||
return processStepResult(err, s.error, state)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
func TestStepDeleteOSDiskShouldFailIfGetFails(t *testing.T) {
|
||||
var testSubject = &StepDeleteOSDisk{
|
||||
delete: func(string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||
deleteManaged: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func TestStepDeleteOSDiskShouldFailIfVHDNameCannotBeURLParsed(t *testing.T) {
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||
deleteManaged: func(string, string) error { return nil },
|
||||
}
|
||||
|
||||
// Invalid URL per https://golang.org/src/net/url/url_test.go
|
||||
@@ -130,7 +130,7 @@ func TestStepDeleteOSDiskShouldFailIfVHDNameIsTooShort(t *testing.T) {
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||
deleteManaged: func(string, string) error { return nil },
|
||||
}
|
||||
|
||||
stateBag := DeleteTestStateBagStepDeleteOSDisk("storage.blob.core.windows.net/abc")
|
||||
@@ -173,7 +173,7 @@ func TestStepDeleteOSDiskShouldFailIfManagedDiskInExistingResourceGroupFailsToDe
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
deleteManaged: func(context.Context, string, string) error { return errors.New("UNIT TEST FAIL!") },
|
||||
deleteManaged: func(string, string) error { return errors.New("UNIT TEST FAIL!") },
|
||||
}
|
||||
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
@@ -197,7 +197,7 @@ func TestStepDeleteOSDiskShouldFailIfManagedDiskInExistingResourceGroupIsDeleted
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||
deleteManaged: func(string, string) error { return nil },
|
||||
}
|
||||
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/hashicorp/packer/builder/azure/common"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -15,7 +17,7 @@ const (
|
||||
|
||||
type StepDeleteResourceGroup struct {
|
||||
client *AzureClient
|
||||
delete func(ctx context.Context, state multistep.StateBag, resourceGroupName string) error
|
||||
delete func(state multistep.StateBag, resourceGroupName string, cancelCh <-chan struct{}) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
@@ -31,18 +33,18 @@ func NewStepDeleteResourceGroup(client *AzureClient, ui packer.Ui) *StepDeleteRe
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepDeleteResourceGroup) deleteResourceGroup(ctx context.Context, state multistep.StateBag, resourceGroupName string) error {
|
||||
func (s *StepDeleteResourceGroup) deleteResourceGroup(state multistep.StateBag, resourceGroupName string, cancelCh <-chan struct{}) error {
|
||||
var err error
|
||||
if state.Get(constants.ArmIsExistingResourceGroup).(bool) {
|
||||
s.say("\nThe resource group was not created by Packer, only deleting individual resources ...")
|
||||
var deploymentName = state.Get(constants.ArmDeploymentName).(string)
|
||||
err = s.deleteDeploymentResources(ctx, deploymentName, resourceGroupName)
|
||||
err = s.deleteDeploymentResources(deploymentName, resourceGroupName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if keyVaultDeploymentName, ok := state.GetOk(constants.ArmKeyVaultDeploymentName); ok {
|
||||
err = s.deleteDeploymentResources(ctx, keyVaultDeploymentName.(string), resourceGroupName)
|
||||
err = s.deleteDeploymentResources(keyVaultDeploymentName.(string), resourceGroupName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -51,16 +53,8 @@ func (s *StepDeleteResourceGroup) deleteResourceGroup(ctx context.Context, state
|
||||
return nil
|
||||
} else {
|
||||
s.say("\nThe resource group was created by Packer, deleting ...")
|
||||
f, err := s.client.GroupsClient.Delete(ctx, resourceGroupName)
|
||||
if err == nil {
|
||||
if state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
|
||||
// No need to wait for the complition for delete if request is Accepted
|
||||
s.say(fmt.Sprintf("\nResource Group is being deleted, not waiting for deletion due to config. Resource Group Name '%s'", resourceGroupName))
|
||||
} else {
|
||||
f.WaitForCompletion(ctx, s.client.GroupsClient.Client)
|
||||
}
|
||||
|
||||
}
|
||||
_, errChan := s.client.GroupsClient.Delete(resourceGroupName, cancelCh)
|
||||
err = <-errChan
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
@@ -69,37 +63,46 @@ func (s *StepDeleteResourceGroup) deleteResourceGroup(ctx context.Context, state
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepDeleteResourceGroup) deleteDeploymentResources(ctx context.Context, deploymentName, resourceGroupName string) error {
|
||||
func (s *StepDeleteResourceGroup) deleteDeploymentResources(deploymentName, resourceGroupName string) error {
|
||||
maxResources := int32(maxResourcesToDelete)
|
||||
|
||||
deploymentOperations, err := s.client.DeploymentOperationsClient.ListComplete(ctx, resourceGroupName, deploymentName, &maxResources)
|
||||
deploymentOperations, err := s.client.DeploymentOperationsClient.List(resourceGroupName, deploymentName, &maxResources)
|
||||
if err != nil {
|
||||
s.reportIfError(err, resourceGroupName)
|
||||
return err
|
||||
}
|
||||
|
||||
for deploymentOperations.NotDone() {
|
||||
deploymentOperation := deploymentOperations.Value()
|
||||
for _, deploymentOperation := range *deploymentOperations.Value {
|
||||
// Sometimes an empty operation is added to the list by Azure
|
||||
if deploymentOperation.Properties.TargetResource == nil {
|
||||
deploymentOperations.Next()
|
||||
continue
|
||||
}
|
||||
|
||||
resourceName := *deploymentOperation.Properties.TargetResource.ResourceName
|
||||
resourceType := *deploymentOperation.Properties.TargetResource.ResourceType
|
||||
|
||||
s.say(fmt.Sprintf(" -> %s : '%s'",
|
||||
resourceType,
|
||||
resourceName))
|
||||
*deploymentOperation.Properties.TargetResource.ResourceType,
|
||||
*deploymentOperation.Properties.TargetResource.ResourceName))
|
||||
|
||||
err := deleteResource(ctx, s.client,
|
||||
resourceType,
|
||||
resourceName,
|
||||
resourceGroupName)
|
||||
s.reportIfError(err, resourceName)
|
||||
if err = deploymentOperations.Next(); err != nil {
|
||||
return err
|
||||
var networkDeleteFunction func(string, string, <-chan struct{}) (<-chan autorest.Response, <-chan error)
|
||||
resourceName := *deploymentOperation.Properties.TargetResource.ResourceName
|
||||
|
||||
switch *deploymentOperation.Properties.TargetResource.ResourceType {
|
||||
case "Microsoft.Compute/virtualMachines":
|
||||
_, errChan := s.client.VirtualMachinesClient.Delete(resourceGroupName, resourceName, nil)
|
||||
err := <-errChan
|
||||
s.reportIfError(err, resourceName)
|
||||
case "Microsoft.KeyVault/vaults":
|
||||
_, err := s.client.VaultClientDelete.Delete(resourceGroupName, resourceName)
|
||||
s.reportIfError(err, resourceName)
|
||||
case "Microsoft.Network/networkInterfaces":
|
||||
networkDeleteFunction = s.client.InterfacesClient.Delete
|
||||
case "Microsoft.Network/virtualNetworks":
|
||||
networkDeleteFunction = s.client.VirtualNetworksClient.Delete
|
||||
case "Microsoft.Network/publicIPAddresses":
|
||||
networkDeleteFunction = s.client.PublicIPAddressesClient.Delete
|
||||
}
|
||||
if networkDeleteFunction != nil {
|
||||
_, errChan := networkDeleteFunction(resourceGroupName, resourceName, nil)
|
||||
err := <-errChan
|
||||
s.reportIfError(err, resourceName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,23 +118,19 @@ func (s *StepDeleteResourceGroup) reportIfError(err error, resourceName string)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepDeleteResourceGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepDeleteResourceGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Deleting resource group ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
|
||||
err := s.delete(ctx, state, resourceGroupName)
|
||||
if err != nil {
|
||||
state.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
result := common.StartInterruptibleTask(
|
||||
func() bool { return common.IsStateCancelled(state) },
|
||||
func(cancelCh <-chan struct{}) error { return s.delete(state, resourceGroupName, cancelCh) })
|
||||
stepAction := processInterruptibleResult(result, s.error, state)
|
||||
state.Put(constants.ArmIsResourceGroupCreated, false)
|
||||
|
||||
return multistep.ActionContinue
|
||||
return stepAction
|
||||
}
|
||||
|
||||
func (*StepDeleteResourceGroup) Cleanup(multistep.StateBag) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func TestStepDeleteResourceGroupShouldFailIfDeleteFails(t *testing.T) {
|
||||
var testSubject = &StepDeleteResourceGroup{
|
||||
delete: func(context.Context, multistep.StateBag, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
delete: func(multistep.StateBag, string, <-chan struct{}) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func TestStepDeleteResourceGroupShouldFailIfDeleteFails(t *testing.T) {
|
||||
|
||||
func TestStepDeleteResourceGroupShouldPassIfDeletePasses(t *testing.T) {
|
||||
var testSubject = &StepDeleteResourceGroup{
|
||||
delete: func(context.Context, multistep.StateBag, string) error { return nil },
|
||||
delete: func(multistep.StateBag, string, <-chan struct{}) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func TestStepDeleteResourceGroupShouldPassIfDeletePasses(t *testing.T) {
|
||||
|
||||
func TestStepDeleteResourceGroupShouldDeleteStateBagArmResourceGroupCreated(t *testing.T) {
|
||||
var testSubject = &StepDeleteResourceGroup{
|
||||
delete: func(context.Context, multistep.StateBag, string) error {
|
||||
delete: func(s multistep.StateBag, resourceGroupName string, cancelCh <-chan struct{}) error {
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/hashicorp/packer/builder/azure/common"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -14,10 +16,10 @@ import (
|
||||
|
||||
type StepDeployTemplate struct {
|
||||
client *AzureClient
|
||||
deploy func(ctx context.Context, resourceGroupName string, deploymentName string) error
|
||||
delete func(ctx context.Context, client *AzureClient, resourceType string, resourceName string, resourceGroupName string) error
|
||||
disk func(ctx context.Context, resourceGroupName string, computeName string) (string, string, error)
|
||||
deleteDisk func(ctx context.Context, imageType string, imageName string, resourceGroupName string) error
|
||||
deploy func(resourceGroupName string, deploymentName string, cancelCh <-chan struct{}) error
|
||||
delete func(resourceType string, resourceName string, resourceGroupName string) error
|
||||
disk func(resourceGroupName string, computeName string) (string, string, error)
|
||||
deleteDisk func(imageType string, imageName string, resourceGroupName string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
config *Config
|
||||
@@ -36,29 +38,28 @@ func NewStepDeployTemplate(client *AzureClient, ui packer.Ui, config *Config, de
|
||||
}
|
||||
|
||||
step.deploy = step.deployTemplate
|
||||
step.delete = deleteResource
|
||||
step.delete = step.deleteOperationResource
|
||||
step.disk = step.getImageDetails
|
||||
step.deleteDisk = step.deleteImage
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) deployTemplate(ctx context.Context, resourceGroupName string, deploymentName string) error {
|
||||
func (s *StepDeployTemplate) deployTemplate(resourceGroupName string, deploymentName string, cancelCh <-chan struct{}) error {
|
||||
deployment, err := s.factory(s.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := s.client.DeploymentsClient.CreateOrUpdate(ctx, resourceGroupName, deploymentName, *deployment)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletion(ctx, s.client.DeploymentsClient.Client)
|
||||
}
|
||||
_, errChan := s.client.DeploymentsClient.CreateOrUpdate(resourceGroupName, deploymentName, *deployment, cancelCh)
|
||||
|
||||
err = <-errChan
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepDeployTemplate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Deploying deployment template ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
@@ -66,16 +67,21 @@ func (s *StepDeployTemplate) Run(ctx context.Context, state multistep.StateBag)
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", s.name))
|
||||
|
||||
return processStepResult(
|
||||
s.deploy(ctx, resourceGroupName, s.name),
|
||||
s.error, state)
|
||||
result := common.StartInterruptibleTask(
|
||||
func() bool { return common.IsStateCancelled(state) },
|
||||
func(cancelCh <-chan struct{}) error {
|
||||
return s.deploy(resourceGroupName, s.name, cancelCh)
|
||||
},
|
||||
)
|
||||
|
||||
return processInterruptibleResult(result, s.error, state)
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) getImageDetails(ctx context.Context, resourceGroupName string, computeName string) (string, string, error) {
|
||||
func (s *StepDeployTemplate) getImageDetails(resourceGroupName string, computeName string) (string, string, error) {
|
||||
//We can't depend on constants.ArmOSDiskVhd being set
|
||||
var imageName string
|
||||
var imageType string
|
||||
vm, err := s.client.VirtualMachinesClient.Get(ctx, resourceGroupName, computeName, "")
|
||||
vm, err := s.client.VirtualMachinesClient.Get(resourceGroupName, computeName, "")
|
||||
if err != nil {
|
||||
return imageName, imageType, err
|
||||
} else {
|
||||
@@ -90,50 +96,44 @@ func (s *StepDeployTemplate) getImageDetails(ctx context.Context, resourceGroupN
|
||||
return imageType, imageName, nil
|
||||
}
|
||||
|
||||
//TODO(paulmey): move to helpers file
|
||||
func deleteResource(ctx context.Context, client *AzureClient, resourceType string, resourceName string, resourceGroupName string) error {
|
||||
func (s *StepDeployTemplate) deleteOperationResource(resourceType string, resourceName string, resourceGroupName string) error {
|
||||
var networkDeleteFunction func(string, string, <-chan struct{}) (<-chan autorest.Response, <-chan error)
|
||||
switch resourceType {
|
||||
case "Microsoft.Compute/virtualMachines":
|
||||
f, err := client.VirtualMachinesClient.Delete(ctx, resourceGroupName, resourceName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletion(ctx, client.VirtualMachinesClient.Client)
|
||||
_, errChan := s.client.VirtualMachinesClient.Delete(resourceGroupName,
|
||||
resourceName, nil)
|
||||
err := <-errChan
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
return err
|
||||
case "Microsoft.KeyVault/vaults":
|
||||
// TODO(paulmey): not sure why VaultClient doesn't do cancellation
|
||||
_, err := client.VaultClientDelete.Delete(resourceGroupName, resourceName)
|
||||
_, err := s.client.VaultClientDelete.Delete(resourceGroupName, resourceName)
|
||||
return err
|
||||
case "Microsoft.Network/networkInterfaces":
|
||||
f, err := client.InterfacesClient.Delete(ctx, resourceGroupName, resourceName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletion(ctx, client.InterfacesClient.Client)
|
||||
}
|
||||
return err
|
||||
networkDeleteFunction = s.client.InterfacesClient.Delete
|
||||
case "Microsoft.Network/virtualNetworks":
|
||||
f, err := client.VirtualNetworksClient.Delete(ctx, resourceGroupName, resourceName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletion(ctx, client.VirtualNetworksClient.Client)
|
||||
}
|
||||
return err
|
||||
networkDeleteFunction = s.client.VirtualNetworksClient.Delete
|
||||
case "Microsoft.Network/publicIPAddresses":
|
||||
f, err := client.PublicIPAddressesClient.Delete(ctx, resourceGroupName, resourceName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletion(ctx, client.PublicIPAddressesClient.Client)
|
||||
networkDeleteFunction = s.client.PublicIPAddressesClient.Delete
|
||||
}
|
||||
if networkDeleteFunction != nil {
|
||||
_, errChan := networkDeleteFunction(resourceGroupName, resourceName, nil)
|
||||
err := <-errChan
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) deleteImage(ctx context.Context, imageType string, imageName string, resourceGroupName string) error {
|
||||
func (s *StepDeployTemplate) deleteImage(imageType string, imageName string, resourceGroupName string) error {
|
||||
// Managed disk
|
||||
if imageType == "Microsoft.Compute/disks" {
|
||||
xs := strings.Split(imageName, "/")
|
||||
diskName := xs[len(xs)-1]
|
||||
f, err := s.client.DisksClient.Delete(ctx, resourceGroupName, diskName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletion(ctx, s.client.DisksClient.Client)
|
||||
}
|
||||
_, errChan := s.client.DisksClient.Delete(resourceGroupName, diskName, nil)
|
||||
err := <-errChan
|
||||
return err
|
||||
}
|
||||
// VHD image
|
||||
@@ -167,7 +167,7 @@ func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var computeName = state.Get(constants.ArmComputeName).(string)
|
||||
var deploymentName = s.name
|
||||
imageType, imageName, err := s.disk(context.TODO(), resourceGroupName, computeName)
|
||||
imageType, imageName, err := s.disk(resourceGroupName, computeName)
|
||||
if err != nil {
|
||||
ui.Error("Could not retrieve OS Image details")
|
||||
}
|
||||
@@ -175,24 +175,21 @@ func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
|
||||
ui.Say(" -> Deployment: " + deploymentName)
|
||||
if deploymentName != "" {
|
||||
maxResources := int32(50)
|
||||
deploymentOperations, err := s.client.DeploymentOperationsClient.ListComplete(context.TODO(), resourceGroupName, deploymentName, &maxResources)
|
||||
deploymentOperations, err := s.client.DeploymentOperationsClient.List(resourceGroupName, deploymentName, &maxResources)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting resources. Please delete them manually.\n\n"+
|
||||
"Name: %s\n"+
|
||||
"Error: %s", resourceGroupName, err))
|
||||
}
|
||||
for deploymentOperations.NotDone() {
|
||||
deploymentOperation := deploymentOperations.Value()
|
||||
for _, deploymentOperation := range *deploymentOperations.Value {
|
||||
// Sometimes an empty operation is added to the list by Azure
|
||||
if deploymentOperation.Properties.TargetResource == nil {
|
||||
deploymentOperations.Next()
|
||||
continue
|
||||
}
|
||||
ui.Say(fmt.Sprintf(" -> %s : '%s'",
|
||||
*deploymentOperation.Properties.TargetResource.ResourceType,
|
||||
*deploymentOperation.Properties.TargetResource.ResourceName))
|
||||
err = s.delete(context.TODO(), s.client,
|
||||
*deploymentOperation.Properties.TargetResource.ResourceType,
|
||||
err = s.delete(*deploymentOperation.Properties.TargetResource.ResourceType,
|
||||
*deploymentOperation.Properties.TargetResource.ResourceName,
|
||||
resourceGroupName)
|
||||
if err != nil {
|
||||
@@ -200,18 +197,12 @@ func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
|
||||
"Name: %s\n"+
|
||||
"Error: %s", *deploymentOperation.Properties.TargetResource.ResourceName, err))
|
||||
}
|
||||
if err = deploymentOperations.Next(); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting resources. Please delete them manually.\n\n"+
|
||||
"Name: %s\n"+
|
||||
"Error: %s", resourceGroupName, err))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The disk is not defined as an operation in the template so has to be
|
||||
// deleted separately
|
||||
ui.Say(fmt.Sprintf(" -> %s : '%s'", imageType, imageName))
|
||||
err = s.deleteDisk(context.TODO(), imageType, imageName, resourceGroupName)
|
||||
err = s.deleteDisk(imageType, imageName, resourceGroupName)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
|
||||
"Name: %s\n"+
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func TestStepDeployTemplateShouldFailIfDeployFails(t *testing.T) {
|
||||
var testSubject = &StepDeployTemplate{
|
||||
deploy: func(context.Context, string, string) error {
|
||||
deploy: func(string, string, <-chan struct{}) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
@@ -32,7 +32,7 @@ func TestStepDeployTemplateShouldFailIfDeployFails(t *testing.T) {
|
||||
|
||||
func TestStepDeployTemplateShouldPassIfDeployPasses(t *testing.T) {
|
||||
var testSubject = &StepDeployTemplate{
|
||||
deploy: func(context.Context, string, string) error { return nil },
|
||||
deploy: func(string, string, <-chan struct{}) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func TestStepDeployTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualDeploymentName string
|
||||
|
||||
var testSubject = &StepDeployTemplate{
|
||||
deploy: func(_ context.Context, resourceGroupName string, deploymentName string) error {
|
||||
deploy: func(resourceGroupName string, deploymentName string, cancelCh <-chan struct{}) error {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualDeploymentName = deploymentName
|
||||
|
||||
@@ -90,7 +90,7 @@ func TestStepDeployTemplateDeleteImageShouldFailWhenImageUrlCannotBeParsed(t *te
|
||||
name: "--deployment-name--",
|
||||
}
|
||||
// Invalid URL per https://golang.org/src/net/url/url_test.go
|
||||
err := testSubject.deleteImage(context.TODO(), "image", "http://[fe80::1%en0]/", "Unit Test: ResourceGroupName")
|
||||
err := testSubject.deleteImage("image", "http://[fe80::1%en0]/", "Unit Test: ResourceGroupName")
|
||||
if err == nil {
|
||||
t.Fatal("Expected a failure because of the failed image name")
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func TestStepDeployTemplateDeleteImageShouldFailWithInvalidImage(t *testing.T) {
|
||||
error: func(e error) {},
|
||||
name: "--deployment-name--",
|
||||
}
|
||||
err := testSubject.deleteImage(context.TODO(), "image", "storage.blob.core.windows.net/abc", "Unit Test: ResourceGroupName")
|
||||
err := testSubject.deleteImage("image", "storage.blob.core.windows.net/abc", "Unit Test: ResourceGroupName")
|
||||
if err == nil {
|
||||
t.Fatal("Expected a failure because of the failed image name")
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
type StepGetDataDisk struct {
|
||||
client *AzureClient
|
||||
query func(ctx context.Context, resourceGroupName string, computeName string) (compute.VirtualMachine, error)
|
||||
query func(resourceGroupName string, computeName string) (compute.VirtualMachine, error)
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
@@ -30,15 +30,15 @@ func NewStepGetAdditionalDisks(client *AzureClient, ui packer.Ui) *StepGetDataDi
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepGetDataDisk) queryCompute(ctx context.Context, resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
|
||||
vm, err := s.client.VirtualMachinesClient.Get(ctx, resourceGroupName, computeName, "")
|
||||
func (s *StepGetDataDisk) queryCompute(resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
|
||||
vm, err := s.client.VirtualMachinesClient.Get(resourceGroupName, computeName, "")
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return vm, err
|
||||
}
|
||||
|
||||
func (s *StepGetDataDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepGetDataDisk) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Querying the machine's additional disks properties ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
@@ -47,7 +47,7 @@ func (s *StepGetDataDisk) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> ComputeName : '%s'", computeName))
|
||||
|
||||
vm, err := s.query(ctx, resourceGroupName, computeName)
|
||||
vm, err := s.query(resourceGroupName, computeName)
|
||||
if err != nil {
|
||||
state.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
func TestStepGetAdditionalDiskShouldFailIfGetFails(t *testing.T) {
|
||||
var testSubject = &StepGetDataDisk{
|
||||
query: func(context.Context, string, string) (compute.VirtualMachine, error) {
|
||||
query: func(string, string) (compute.VirtualMachine, error) {
|
||||
return createVirtualMachineWithDataDisksFromUri("test.vhd"), fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
@@ -35,7 +35,7 @@ func TestStepGetAdditionalDiskShouldFailIfGetFails(t *testing.T) {
|
||||
|
||||
func TestStepGetAdditionalDiskShouldPassIfGetPasses(t *testing.T) {
|
||||
var testSubject = &StepGetDataDisk{
|
||||
query: func(context.Context, string, string) (compute.VirtualMachine, error) {
|
||||
query: func(string, string) (compute.VirtualMachine, error) {
|
||||
return createVirtualMachineWithDataDisksFromUri("test.vhd"), nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
@@ -59,7 +59,7 @@ func TestStepGetAdditionalDiskShouldTakeValidateArgumentsFromStateBag(t *testing
|
||||
var actualComputeName string
|
||||
|
||||
var testSubject = &StepGetDataDisk{
|
||||
query: func(ctx context.Context, resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
|
||||
query: func(resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualComputeName = computeName
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ var (
|
||||
type StepGetIPAddress struct {
|
||||
client *AzureClient
|
||||
endpoint EndpointType
|
||||
get func(ctx context.Context, resourceGroupName string, ipAddressName string, interfaceName string) (string, error)
|
||||
get func(resourceGroupName string, ipAddressName string, interfaceName string) (string, error)
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
@@ -53,8 +53,8 @@ func NewStepGetIPAddress(client *AzureClient, ui packer.Ui, endpoint EndpointTyp
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepGetIPAddress) getPrivateIP(ctx context.Context, resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
|
||||
resp, err := s.client.InterfacesClient.Get(ctx, resourceGroupName, interfaceName, "")
|
||||
func (s *StepGetIPAddress) getPrivateIP(resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
|
||||
resp, err := s.client.InterfacesClient.Get(resourceGroupName, interfaceName, "")
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return "", err
|
||||
@@ -63,8 +63,8 @@ func (s *StepGetIPAddress) getPrivateIP(ctx context.Context, resourceGroupName s
|
||||
return *(*resp.IPConfigurations)[0].PrivateIPAddress, nil
|
||||
}
|
||||
|
||||
func (s *StepGetIPAddress) getPublicIP(ctx context.Context, resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
|
||||
resp, err := s.client.PublicIPAddressesClient.Get(ctx, resourceGroupName, ipAddressName, "")
|
||||
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
|
||||
}
|
||||
@@ -72,12 +72,12 @@ func (s *StepGetIPAddress) getPublicIP(ctx context.Context, resourceGroupName st
|
||||
return *resp.IPAddress, nil
|
||||
}
|
||||
|
||||
func (s *StepGetIPAddress) getPublicIPInPrivateNetwork(ctx context.Context, resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
|
||||
s.getPrivateIP(ctx, resourceGroupName, ipAddressName, interfaceName)
|
||||
return s.getPublicIP(ctx, resourceGroupName, ipAddressName, interfaceName)
|
||||
func (s *StepGetIPAddress) getPublicIPInPrivateNetwork(resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
|
||||
s.getPrivateIP(resourceGroupName, ipAddressName, interfaceName)
|
||||
return s.getPublicIP(resourceGroupName, ipAddressName, interfaceName)
|
||||
}
|
||||
|
||||
func (s *StepGetIPAddress) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepGetIPAddress) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Getting the VM's IP address ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
@@ -89,7 +89,7 @@ func (s *StepGetIPAddress) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
s.say(fmt.Sprintf(" -> NicName : '%s'", nicName))
|
||||
s.say(fmt.Sprintf(" -> Network Connection : '%s'", EndpointCommunicationText[s.endpoint]))
|
||||
|
||||
address, err := s.get(ctx, resourceGroupName, ipAddressName, nicName)
|
||||
address, err := s.get(resourceGroupName, ipAddressName, nicName)
|
||||
if err != nil {
|
||||
state.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
@@ -14,9 +14,7 @@ func TestStepGetIPAddressShouldFailIfGetFails(t *testing.T) {
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
var testSubject = &StepGetIPAddress{
|
||||
get: func(context.Context, string, string, string) (string, error) {
|
||||
return "", fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
get: func(string, string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
endpoint: endpoint,
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
@@ -40,7 +38,7 @@ func TestStepGetIPAddressShouldPassIfGetPasses(t *testing.T) {
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
var testSubject = &StepGetIPAddress{
|
||||
get: func(context.Context, string, string, string) (string, error) { return "", nil },
|
||||
get: func(string, string, string) (string, error) { return "", nil },
|
||||
endpoint: endpoint,
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
@@ -67,7 +65,7 @@ func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
var testSubject = &StepGetIPAddress{
|
||||
get: func(ctx context.Context, resourceGroupName string, ipAddressName string, nicName string) (string, error) {
|
||||
get: func(resourceGroupName string, ipAddressName string, nicName string) (string, error) {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualIPAddressName = ipAddressName
|
||||
actualNicName = nicName
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
type StepGetOSDisk struct {
|
||||
client *AzureClient
|
||||
query func(ctx context.Context, resourceGroupName string, computeName string) (compute.VirtualMachine, error)
|
||||
query func(resourceGroupName string, computeName string) (compute.VirtualMachine, error)
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
@@ -30,15 +30,15 @@ func NewStepGetOSDisk(client *AzureClient, ui packer.Ui) *StepGetOSDisk {
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepGetOSDisk) queryCompute(ctx context.Context, resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
|
||||
vm, err := s.client.VirtualMachinesClient.Get(ctx, resourceGroupName, computeName, "")
|
||||
func (s *StepGetOSDisk) queryCompute(resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
|
||||
vm, err := s.client.VirtualMachinesClient.Get(resourceGroupName, computeName, "")
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return vm, err
|
||||
}
|
||||
|
||||
func (s *StepGetOSDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepGetOSDisk) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Querying the machine's properties ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
@@ -47,7 +47,7 @@ func (s *StepGetOSDisk) Run(ctx context.Context, state multistep.StateBag) multi
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> ComputeName : '%s'", computeName))
|
||||
|
||||
vm, err := s.query(ctx, resourceGroupName, computeName)
|
||||
vm, err := s.query(resourceGroupName, computeName)
|
||||
if err != nil {
|
||||
state.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
func TestStepGetOSDiskShouldFailIfGetFails(t *testing.T) {
|
||||
var testSubject = &StepGetOSDisk{
|
||||
query: func(context.Context, string, string) (compute.VirtualMachine, error) {
|
||||
query: func(string, string) (compute.VirtualMachine, error) {
|
||||
return createVirtualMachineFromUri("test.vhd"), fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
@@ -35,7 +35,7 @@ func TestStepGetOSDiskShouldFailIfGetFails(t *testing.T) {
|
||||
|
||||
func TestStepGetOSDiskShouldPassIfGetPasses(t *testing.T) {
|
||||
var testSubject = &StepGetOSDisk{
|
||||
query: func(context.Context, string, string) (compute.VirtualMachine, error) {
|
||||
query: func(string, string) (compute.VirtualMachine, error) {
|
||||
return createVirtualMachineFromUri("test.vhd"), nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
@@ -59,7 +59,7 @@ func TestStepGetOSDiskShouldTakeValidateArgumentsFromStateBag(t *testing.T) {
|
||||
var actualComputeName string
|
||||
|
||||
var testSubject = &StepGetOSDisk{
|
||||
query: func(ctx context.Context, resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
|
||||
query: func(resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualComputeName = computeName
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
|
||||
type StepPowerOffCompute struct {
|
||||
client *AzureClient
|
||||
powerOff func(ctx context.Context, resourceGroupName string, computeName string) error
|
||||
powerOff func(resourceGroupName string, computeName string, cancelCh <-chan struct{}) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
@@ -27,18 +28,17 @@ func NewStepPowerOffCompute(client *AzureClient, ui packer.Ui) *StepPowerOffComp
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepPowerOffCompute) powerOffCompute(ctx context.Context, resourceGroupName string, computeName string) error {
|
||||
f, err := s.client.VirtualMachinesClient.PowerOff(ctx, resourceGroupName, computeName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletion(ctx, s.client.VirtualMachinesClient.Client)
|
||||
}
|
||||
func (s *StepPowerOffCompute) powerOffCompute(resourceGroupName string, computeName string, cancelCh <-chan struct{}) error {
|
||||
_, errChan := s.client.PowerOff(resourceGroupName, computeName, cancelCh)
|
||||
|
||||
err := <-errChan
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepPowerOffCompute) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepPowerOffCompute) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Powering off machine ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
@@ -47,9 +47,11 @@ func (s *StepPowerOffCompute) Run(ctx context.Context, state multistep.StateBag)
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> ComputeName : '%s'", computeName))
|
||||
|
||||
err := s.powerOff(ctx, resourceGroupName, computeName)
|
||||
result := common.StartInterruptibleTask(
|
||||
func() bool { return common.IsStateCancelled(state) },
|
||||
func(cancelCh <-chan struct{}) error { return s.powerOff(resourceGroupName, computeName, cancelCh) })
|
||||
|
||||
return processStepResult(err, s.error, state)
|
||||
return processInterruptibleResult(result, s.error, state)
|
||||
}
|
||||
|
||||
func (*StepPowerOffCompute) Cleanup(multistep.StateBag) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func TestStepPowerOffComputeShouldFailIfPowerOffFails(t *testing.T) {
|
||||
var testSubject = &StepPowerOffCompute{
|
||||
powerOff: func(context.Context, string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
powerOff: func(string, string, <-chan struct{}) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func TestStepPowerOffComputeShouldFailIfPowerOffFails(t *testing.T) {
|
||||
|
||||
func TestStepPowerOffComputeShouldPassIfPowerOffPasses(t *testing.T) {
|
||||
var testSubject = &StepPowerOffCompute{
|
||||
powerOff: func(context.Context, string, string) error { return nil },
|
||||
powerOff: func(string, string, <-chan struct{}) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
@@ -52,7 +52,7 @@ func TestStepPowerOffComputeShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualComputeName string
|
||||
|
||||
var testSubject = &StepPowerOffCompute{
|
||||
powerOff: func(ctx context.Context, resourceGroupName string, computeName string) error {
|
||||
powerOff: func(resourceGroupName string, computeName string, cancelCh <-chan struct{}) error {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualComputeName = computeName
|
||||
|
||||
|
||||
@@ -3,21 +3,20 @@ package arm
|
||||
import (
|
||||
"context"
|
||||
|
||||
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepSaveWinRMPassword struct {
|
||||
Password string
|
||||
BuildName string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (s *StepSaveWinRMPassword) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
// store so that we can access this later during provisioning
|
||||
state.Put("winrm_password", s.Password)
|
||||
packer.LogSecretFilter.Set(s.Password)
|
||||
commonhelper.SetSharedState("winrm_password", s.Password)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepSaveWinRMPassword) Cleanup(multistep.StateBag) {
|
||||
commonhelper.RemoveSharedStateFile("winrm_password")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
@@ -38,3 +39,59 @@ func TestProcessStepResultShouldHaltOnError(t *testing.T) {
|
||||
t.Errorf("Expected ActionHalt(%d), but got=%d", multistep.ActionHalt, code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessStepResultShouldContinueOnSuccessfulTask(t *testing.T) {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
result := common.InterruptibleTaskResult{
|
||||
IsCancelled: false,
|
||||
Err: nil,
|
||||
}
|
||||
|
||||
code := processInterruptibleResult(result, func(error) { t.Fatal("Should not be called!") }, stateBag)
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok {
|
||||
t.Errorf("Error was nil, but was still in the state bag.")
|
||||
}
|
||||
|
||||
if code != multistep.ActionContinue {
|
||||
t.Errorf("Expected ActionContinue(%d), but got=%d", multistep.ActionContinue, code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessStepResultShouldHaltWhenTaskIsCancelled(t *testing.T) {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
result := common.InterruptibleTaskResult{
|
||||
IsCancelled: true,
|
||||
Err: nil,
|
||||
}
|
||||
|
||||
code := processInterruptibleResult(result, func(error) { t.Fatal("Should not be called!") }, stateBag)
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok {
|
||||
t.Errorf("Error was nil, but was still in the state bag.")
|
||||
}
|
||||
|
||||
if code != multistep.ActionHalt {
|
||||
t.Errorf("Expected ActionHalt(%d), but got=%d", multistep.ActionHalt, code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessStepResultShouldHaltOnTaskError(t *testing.T) {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
isSaidError := false
|
||||
result := common.InterruptibleTaskResult{
|
||||
IsCancelled: false,
|
||||
Err: fmt.Errorf("boom"),
|
||||
}
|
||||
|
||||
code := processInterruptibleResult(result, func(error) { isSaidError = true }, stateBag)
|
||||
if _, ok := stateBag.GetOk(constants.Error); !ok {
|
||||
t.Errorf("Error was *not* nil, but was not in the state bag.")
|
||||
}
|
||||
|
||||
if !isSaidError {
|
||||
t.Errorf("Expected error to be said, but it was not.")
|
||||
}
|
||||
|
||||
if code != multistep.ActionHalt {
|
||||
t.Errorf("Expected ActionHalt(%d), but got=%d", multistep.ActionHalt, code)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
type StepValidateTemplate struct {
|
||||
client *AzureClient
|
||||
validate func(ctx context.Context, resourceGroupName string, deploymentName string) error
|
||||
validate func(resourceGroupName string, deploymentName string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
config *Config
|
||||
@@ -31,20 +31,20 @@ func NewStepValidateTemplate(client *AzureClient, ui packer.Ui, config *Config,
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepValidateTemplate) validateTemplate(ctx context.Context, resourceGroupName string, deploymentName string) error {
|
||||
func (s *StepValidateTemplate) validateTemplate(resourceGroupName string, deploymentName string) error {
|
||||
deployment, err := s.factory(s.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.client.DeploymentsClient.Validate(ctx, resourceGroupName, deploymentName, *deployment)
|
||||
_, err = s.client.Validate(resourceGroupName, deploymentName, *deployment)
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepValidateTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepValidateTemplate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Validating deployment template ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
@@ -53,7 +53,7 @@ func (s *StepValidateTemplate) Run(ctx context.Context, state multistep.StateBag
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", deploymentName))
|
||||
|
||||
err := s.validate(ctx, resourceGroupName, deploymentName)
|
||||
err := s.validate(resourceGroupName, deploymentName)
|
||||
return processStepResult(err, s.error, state)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func TestStepValidateTemplateShouldFailIfValidateFails(t *testing.T) {
|
||||
var testSubject = &StepValidateTemplate{
|
||||
validate: func(context.Context, string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
validate: func(string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func TestStepValidateTemplateShouldFailIfValidateFails(t *testing.T) {
|
||||
|
||||
func TestStepValidateTemplateShouldPassIfValidatePasses(t *testing.T) {
|
||||
var testSubject = &StepValidateTemplate{
|
||||
validate: func(context.Context, string, string) error { return nil },
|
||||
validate: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
@@ -52,7 +52,7 @@ func TestStepValidateTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualDeploymentName string
|
||||
|
||||
var testSubject = &StepValidateTemplate{
|
||||
validate: func(ctx context.Context, resourceGroupName string, deploymentName string) error {
|
||||
validate: func(resourceGroupName string, deploymentName string) error {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualDeploymentName = deploymentName
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ package arm
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-02-01/resources"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
|
||||
|
||||
"fmt"
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"name": "osdisk",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"name": "osdisk",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
|
||||
+1
-1
@@ -144,7 +144,7 @@
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"name": "osdisk",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@
|
||||
"image": {
|
||||
"uri": "https://localhost/custom.vhd"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"name": "osdisk",
|
||||
"osType": "Linux",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
|
||||
+1
-1
@@ -102,7 +102,7 @@
|
||||
"image": {
|
||||
"uri": "https://localhost/custom.vhd"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"name": "osdisk",
|
||||
"osType": "Linux",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@
|
||||
"image": {
|
||||
"uri": "https://localhost/custom.vhd"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"name": "osdisk",
|
||||
"osType": "Linux",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
|
||||
+1
-1
@@ -142,7 +142,7 @@
|
||||
"image": {
|
||||
"uri": "https://localhost/custom.vhd"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"name": "osdisk",
|
||||
"osType": "Linux",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
|
||||
+2
-2
@@ -140,11 +140,11 @@
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"createOption": "fromImage",
|
||||
"managedDisk": {
|
||||
"storageAccountType": "Standard_LRS"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"name": "osdisk",
|
||||
"osType": "Linux"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -143,11 +143,11 @@
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"createOption": "fromImage",
|
||||
"managedDisk": {
|
||||
"storageAccountType": "Standard_LRS"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"name": "osdisk",
|
||||
"osType": "Linux"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -121,11 +121,11 @@
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"createOption": "fromImage",
|
||||
"managedDisk": {
|
||||
"storageAccountType": "Standard_LRS"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"name": "osdisk",
|
||||
"osType": "Linux"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"name": "osdisk",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user