Compare commits

..

221 Commits

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

```
-X importpath.name=value
	Set the value of the string variable in importpath named name to value.
	Note that before Go 1.5 this option took two separate arguments.
	Now it takes one argument split on the first = sign.
```
2016-09-20 16:34:30 -07:00
Chris Bednarski 7bbc29d4b2 Fix go vet casing issue 2016-09-20 16:28:55 -07:00
Chris Bednarski 8e7bb5ec23 Change version.go to 0.10.1 release 2016-09-20 16:28:55 -07:00
Chris Bednarski aa47c90ddd Udpdated test/race timeouts to 2m because AWS seems to be taking a while 2016-05-06 23:15:05 -07:00
Christopher Boumenot a91156b134 Dump Azure configuration values.
Dump the top level variables in the config object. Any values with
'secret' or 'password' in the name are masked.
2016-05-06 23:14:18 -07:00
Chris Bednarski d2921a11ab Remove winrmcp update from changelog 2016-05-06 23:02:44 -07:00
Chris Bednarski 32b94a2398 Revert "Update winrmcp revision to include Windows path fix (#3488)"
This reverts commit dcf9f661ee.
2016-05-06 23:02:15 -07:00
Chris Bednarski d458f81dd8 Revert "Updated vendor pacakge for winrmcp"
This reverts commit e38c1122ab.
2016-05-06 23:02:09 -07:00
Chris Bednarski e38c1122ab Updated vendor pacakge for winrmcp 2016-05-06 22:57:29 -07:00
Chris Bednarski da4491f65e Added Azure and other improvements to the changelog 2016-05-06 21:59:12 -07:00
Chris Bednarski b30689b7b5 Updated documentation for Azure builder 2016-05-06 21:56:27 -07:00
Scott Pynn dcf9f661ee Update winrmcp revision to include Windows path fix (#3488)
On Windows, packer's 'file' provisioner fails to copy files/directories that include both a volume identifier (e.g. C:) and spaces in the path.

The root cause of this was found to be embedded single quotes in the 'destination' value, added by the github.com/packer-community/winrmcp/winrmcp project.

This updated winrmcp revision includes the fix to resolve this issue.
2016-05-06 21:19:18 -07:00
Chris Bednarski e3b9ec62ac Merge pull request #3507 from mitchellh/f-azure-windows
Add Support for Building Windows Images in Azure
2016-05-06 20:34:31 -07:00
Chris Bednarski 5ed3c8d563 Updated license information to reference builder/azure 2016-05-06 20:32:18 -07:00
Christopher Boumenot 902b2583fa Update samples. 2016-05-05 13:40:17 -07:00
Christopher Boumenot c7018a00c8 Add support for Windows to Azure.
This is last merge that will happen from the github.com/Azure/packer-Azure
repository.  All development is being over to this repository.

The biggest change in this merge is support for Windows.  There are a few other
fixes as well.

 * If the user cancels the build, clean up any resources.
 * Output a reasonable build artifact.
 * Log requests and responses with Azure.
 * Support for US Government and the China clouds.
 * Support interrupting long running tasks.
 * Allow the user to set the image version.
 * Device login support.
2016-05-05 13:40:17 -07:00
Christopher Boumenot 0abf0bbab0 Update Azure dependencies.
Add go-ntlmssp.

blah
2016-05-05 13:40:17 -07:00
Seth Vargo 52f934b328 Do not allow referrer to modify the sender (#3469)
http://mathiasbynens.github.io/rel-noopener/
2016-05-05 13:12:56 -07:00
Christopher Boumenot b813a529bf Build script for Windows. (#3481)
A re-implementation of build.sh for Windows ala PowerShell.  This is as
close to one-to-one that I could make it.
2016-05-05 13:12:17 -07:00
Christopher Boumenot 2d1347c1ec Insert Packer's version into the User Agent. (#3465) 2016-05-05 13:10:55 -07:00
Jake Champlin 2bcd276528 Fix spelling error in atlas post processor (#3499) 2016-05-04 23:53:08 -07:00
Christopher Boumenot accd97cf0f Use the default version of Go. (#3498)
AppVeyor install Go 1.6 c:\go by default, and it is already a part of the path.
2016-05-04 15:53:36 -07:00
Chris Bednarski 2d216b6e13 Added ATLAS_CA* to changelog 2016-05-03 18:44:01 -07:00
Chris Bednarski f28d8a18c5 Added documentation for ATLAS_CA* environment variables 2016-05-03 17:19:12 -07:00
Paul Hinze adcae08cd5 Update atlas-go to latest version that uses go-rootcerts (#3494)
Fixes issues on OS X w/ verifying against an Atlas using a custom certificate.
2016-05-03 16:41:59 -07:00
Seth Vargo 344f98ebd3 Track enhanced links (#3491) 2016-05-03 16:16:03 -07:00
Chris Bednarski 3b6435ec8d Merge branch 'master' of https://github.com/mitchellh/packer 2016-04-29 19:25:41 -07:00
Chris Bednarski 7a9a1d8025 Updated the changelog to include chef validation key fix, add remote_* options for ssh, and put docker privileged in the features section 2016-04-29 19:25:30 -07:00
Justin Nauman f2b0fabe66 Fixes #3466 - Remote path needs to be set prior to config interpolation (#3468) 2016-04-29 19:21:27 -07:00
Chris Bednarski 3b2a4834c5 Added docker privileged to the changelog 2016-04-29 19:13:41 -07:00
Hao 5139b853fa privilege enabled docker container (#3475)
Issue #2724
2016-04-29 19:12:20 -07:00
Chris Bednarski 700966fc27 Added changelog link to the download page (#3485) 2016-04-29 19:09:52 -07:00
Mark Campbell 6658ec7ea3 Fix extra backslash in front of dollar signs in website documentation (#3479)
The dollar signs do not need to be escaped. The backslashes show up on
the website right now. This commit makes it so that they do not show up
on the website.
2016-04-28 10:25:42 -07:00
Ian Duffy 7e653370b7 Seperate remote_path into remote_folder and remote_script. (#3462)
Commonly /tmp is set as noexec, as a result packer scripts
fail to run. In order to get around this one can set a
remote_path, whoever, remote path requires full filename
and path.

By making remote_path a combination of
remote_folder/remote_script we can change remote_folder
and keep the default script_nnn.sh

Signed-off-by: Ian Duffy <ian@ianduffy.ie>
2016-04-26 16:04:29 -07:00
Christopher Boumenot d296260164 RFC: fix appveyor build (#3464)
No sure if this will work...

Appveyor builds appear to always choke on go vet, so I am removing it. The
Travis build already does it, so I do not think there's much value in
repeating it.
2016-04-22 20:42:01 -07:00
Khalid Saifullah b11e888f6c typo (#3467) 2016-04-22 16:42:13 -07:00
Christopher Boumenot bbf286dcbe Fix go fmt issues. (#3463) 2016-04-22 10:44:54 -07:00
Christopher Boumenot 728c496370 Move version to its own package. (#3460) 2016-04-21 13:19:43 -07:00
mansunkuo 864be9d991 Modify documents of extra_arguments for both ansible(remote) and ansible-local. (#3423) 2016-04-20 20:49:46 -07:00
Chris Bednarski 66c3dabf2f Merge pull request #3459 from mitchellh/docs-windows-restart-timeout
Docs for windows restart timeout
2016-04-20 12:36:08 -07:00
Chris Bednarski 90a12f7043 Clarified that windows must be fully booted in order for the restart provisioner to succeed 2016-04-20 12:34:53 -07:00
Chris Bednarski b92efe1262 Merge pull request #3455 from mitchellh/docs-azure-env
Change AZURE to ARM to be consistent with Terraform
2016-04-19 16:58:28 -07:00
Chris Bednarski 1aaa129918 Change AZURE to ARM to be consistent with Terraform 2016-04-19 16:57:51 -07:00
Chris Bednarski e868f9b69c Ignore JetBrains project files 2016-04-14 13:39:15 -07:00
Chris Bednarski 39cb194e0b Merge branch 'master' of https://github.com/mitchellh/packer 2016-04-12 15:09:54 -07:00
Chris Bednarski ac69ca8f68 Updated contrib instructions to reflect go 1.6 requirements 2016-04-12 15:09:46 -07:00
Chris Bednarski e30d0ea56b Merge pull request #3431 from mitchellh/sethvargo/ws_md
Update website push script to fix metadata
2016-04-12 12:15:00 -07:00
Seth Vargo 1eb9419665 Update website push script to fix metadata 2016-04-12 20:12:15 +01:00
Rickard von Essen f353ef00b5 Accept only bug reports and feature requests 2016-04-11 12:55:11 +02:00
Chris Bednarski a5b3e7884e Merge branch 'master' of https://github.com/mitchellh/packer 2016-04-07 16:35:02 -07:00
Chris Bednarski e9d0c8980c Change to the vet built-in because cmd/vet is no longer available 2016-04-07 16:34:54 -07:00
Chris Bednarski 4108c7bdbe Merge pull request #3425 from phinze/phinze/website-mime-types
website: force JS/CSS mime-types on deploy
2016-04-07 15:15:30 -07:00
Paul Hinze bcec4c4ffb website: force mime-types for some assets on deploy
Should fix occassional issues with application/octet-stream mime type
assets breaking JS/CSS on site.
2016-04-07 12:51:26 -05:00
Chris Bednarski b605c88c18 Merge pull request #3418 from boumenot/pr-say-artifact
Use Fprint instead of Fprintf
2016-04-06 14:49:46 -07:00
Chris Bednarski 9f6de246c2 Merge pull request #3422 from alexpbrown/swap-pty-geometry
Swap width and height when allocating a pty
2016-04-06 14:17:46 -07:00
Alex Brown 0cea0e5c24 Swap width and height when allocating a pty
RequestPty from crypto/ssh looks like this:

func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error

So arguments 2 and 3 are height and width, respectively. I'm not sure if
the original was just a typo, or if there's an actual reason we want a
40 column terminal. Either way, having a terminal this narrow led me to
a really "fun" bug[0], where `wget` in my shell provisioner scripts
would segfault when trying to display the progress bar.

[0] http://lists.gnu.org/archive/html/bug-wget/2016-02/msg00041.html
2016-04-06 15:40:19 -04:00
Rickard von Essen 4f0f05fb8e Merge pull request #3419 from tylert/vmw-iso-doc-fix
Put vmware-iso builder config options in 'packer fix' order and menti…
2016-04-06 16:36:03 +02:00
Tyler Tidman 2472e6bf06 Put vmware-iso builder config options in 'packer fix' order and mention boolean defaults 2016-04-06 09:24:03 -04:00
Christopher Boumenot ad3d674919 Do not use Fprintf, use Fprint. 2016-04-05 22:41:26 -07:00
Chris Bednarski d0ddf34e6e Merge pull request #3410 from srhaber/chef_ssl_verify_mode_doc
Add documentation for ssl_verify_mode
2016-04-05 15:04:49 -07:00
Shaun Haber efa642fe04 Add documentation for ssl_verify_mode 2016-04-01 12:35:05 -07:00
Chris Bednarski 9825687443 Merge pull request #3390 from cure/fix-azure-arm-examples
Fix up azure-arm examples: make sure the variable names line up.
2016-03-31 17:46:22 -07:00
Ward Vandewege 5e2d361774 Fix up azure-arm examples: make sure the variable names line up. 2016-03-27 15:02:02 -04:00
Chris Bednarski b199a6e546 Merge pull request #3376 from kikitux/master
add -color to build -h command
2016-03-24 11:07:27 -07:00
Alvaro Miranda 6237df566f add -color to build -h command 2016-03-23 04:40:24 +13:00
Rickard von Essen f38b0ba2cd Merge pull request #3374 from mhlias/master
Simple documentation typo fix from earlier PR for WinRM SSL default port behaviour
2016-03-22 13:06:10 +01:00
Ilias Bertsimas f05e18c58b Merge branch 'master' of github.com:mhlias/packer 2016-03-22 11:32:19 +00:00
Ilias Bertsimas 8bb3541fc3 Fix WinRM default SSL port typo. 2016-03-22 11:32:03 +00:00
Ilias Bertsimas 8604fdcf37 Fix WinRM default SSL port typo. 2016-03-22 11:29:31 +00:00
Chris Bednarski 8040317f77 Merge pull request #3363 from mitchellh/f-remove-plugins
Cleanup plugin code that is no longer used
2016-03-17 17:57:15 -07:00
Chris Bednarski cf87a33a63 Merge branch 'master' of https://github.com/mitchellh/packer 2016-03-17 14:17:55 -07:00
Chris Bednarski 5e898c90c2 Update appveyor spec to go 1.6 2016-03-17 14:17:49 -07:00
Chris Bednarski 172184b2f2 Merge pull request #3360 from thomasv314/bugfix/fix-documentation-errors
Update documentation to work out of box
2016-03-17 14:16:56 -07:00
Chris Bednarski d7cfd5d01c Remove explicit boolean comparison 2016-03-17 14:14:03 -07:00
Thomas Vendetta e362cd6b8b Use new Ubuntu AMI thats t2.micro & us-east-1 eligible
Uses this free tier eligible ebs/hvm AMI:

  Ubuntu Server 14.04 LTS (HVM), SSD Volume Type - ami-fce3c696
2016-03-16 22:28:10 -04:00
Thomas Vendetta af3bc4f12b Fix README build example to work out of box.
- secret_key was set to use the access_key env variable
2016-03-16 22:16:35 -04:00
Chris Bednarski 0fb97a1d55 Add example plugin for third-party plugin use 2016-03-16 16:42:24 -07:00
Chris Bednarski 39a1b88e3a Remove plugin files since these are no longer used 2016-03-16 16:42:09 -07:00
kopernikus f3011698ba document keep_registered switch 2016-03-16 23:31:50 +01:00
kopernikus 84bd2ff754 option to keep the VM registered with esxi 2016-03-16 23:17:35 +01:00
Chris Bednarski b50036c817 Change version to non-release version 2016-03-16 10:06:33 -07:00
Chris Bednarski 439f85b177 erge branch 'master' of https://github.com/mitchellh/packer 2016-03-15 16:06:25 -07:00
Chris Bednarski d97aee90a8 Updated changelog 2016-03-15 16:06:19 -07:00
Chris Bednarski ba5166da8c Merge pull request #3346 from theang/master
Fix -debug command line option misbehaviour: forbid stdin redirection…
2016-03-15 16:04:52 -07:00
Chris Bednarski 8b93c91974 Added BC break note regarding go 1.4 2016-03-14 22:17:45 -07:00
Chris Bednarski 2384cb3f49 Bump download version to 0.10.0 2016-03-14 21:30:04 -07:00
Chris Bednarski 2085263694 Fix problems in release script 2016-03-14 21:24:51 -07:00
Chris Bednarski fedb0e21f0 Cut version 0.10.0 2016-03-14 21:07:07 -07:00
Chris Bednarski 0d4ee09ea5 Remove go 1.4 from the build matrix 2016-03-14 21:00:23 -07:00
Chris Bednarski 71b593a265 Increase test timeout to 30s to accomodate longer Azure test 2016-03-14 20:46:22 -07:00
Chris Bednarski 3693dd8879 Update packer version to 0.10.0 2016-03-14 20:12:26 -07:00
Chris Bednarski e5f755dd37 Update changelog to include Azure 2016-03-14 20:11:42 -07:00
Chris Bednarski 209603ec88 Vendor Azure dependencies 2016-03-14 20:08:41 -07:00
Chris Bednarski e2dd57837d Added azure setup instructions for new accounts 2016-03-14 20:08:41 -07:00
Chris Bednarski 7b9fa48a1b Added initial azure documentation 2016-03-14 20:08:36 -07:00
Chris Bednarski f25be250c7 Increase test -race timeout 2016-03-14 20:08:36 -07:00
Chris Bednarski 4d7418a2dc Added sleeps to the azure-setup script so it is more likely to converge before running the next command 2016-03-14 20:08:36 -07:00
Chris Bednarski 133048e86e Update LICENSE notice 2016-03-14 20:08:36 -07:00
Chris Bednarski d77b1612d4 Added example templates for Azure 2016-03-14 20:08:35 -07:00
Chris Bednarski 17c365e45f Added MIT license 2016-03-14 20:08:35 -07:00
Chris Bednarski c84ae85ac1 Alphabetize azure configuration so it looks nicer 2016-03-14 20:08:35 -07:00
Chris Bednarski 5b5888b230 Initial import of Microsoft contributed code 2016-03-14 20:08:35 -07:00
Chris Bednarski 507efda4f4 Finalized azure-setup for creating all of the requisite resources in the target subscription 2016-03-14 20:08:28 -07:00
Chris Bednarski 3ad8bf266a Initial work on azure-setup script 2016-03-14 20:08:27 -07:00
Chris Bednarski 7e0fe0cddd Added merge script to automatically pull in and fix the upstream repo 2016-03-14 20:08:12 -07:00
Chris Bednarski 7ac30135fc Merge pull request #3351 from mitchellh/kikitux-doc-home-var
Add note for $HOME and ~
2016-03-14 10:36:19 -07:00
Alvaro Miranda 33a225eb92 Add note for $HOME and ~ 2016-03-15 06:34:26 +13:00
Rickard von Essen 427cbef249 Updated CHANGELOG.md 2016-03-14 08:11:18 +01:00
Rickard von Essen 9d1a6b08d6 Merge pull request #3349 from keyan/master
provisioner/puppet-masterless: Add ignore exit codes option
2016-03-14 08:08:55 +01:00
Keyan Pishdadian aee8490f68 Add docs for ignore_exit_codes param 2016-03-13 23:39:29 -04:00
Keyan Pishdadian f3811cb99d Add ignore_exit_codes param for puppet-masterless provisioner 2016-03-13 23:39:18 -04:00
wrc 9674a30227 Fix -debug command line option misbehaviour: forbid stdin redirection to a pipe when run as plugin server 2016-03-13 13:14:44 +03:00
Chris Bednarski e464a8b8d8 Correct margins for inline code blocks 2016-03-11 17:29:25 -08:00
Chris Bednarski 4588c34efb Fix formatting for fix docs 2016-03-11 17:29:04 -08:00
Chris Bednarski 1256babce3 Change .markdown to .md because it's shorter 2016-03-11 17:06:36 -08:00
Chris Bednarski d62d6f588d Merge pull request #3343 from mitchellh/docs-upgrade-warning
Add note to download page to remove old plugins
2016-03-11 11:14:53 -08:00
Chris Bednarski 1994793126 Added upgrade warning to remove old plugins 2016-03-11 11:13:32 -08:00
Rickard von Essen a84f4fffae Merge pull request #3339 from SanderKnape/packer-documentation
Fix incorrect amazon-ebs property in documentation
2016-03-11 12:46:24 +01:00
Sander Knape e75ce84176 Fix incorrect amazon-ebs property in documentation 2016-03-11 11:04:19 +01:00
Chris Bednarski 0f319fde08 Add debug logging for the credentials used for S3 in amazon-import 2016-03-10 16:52:16 -08:00
Chris Bednarski ab36418729 Udpated winrmcp dep 2016-03-10 15:05:34 -08:00
Chris Bednarski 2970038204 Merge pull request #3126 from boumenot/pr-rfc-TransportDecorator
RFC: expose a seam for TransportDecorator.
2016-03-10 15:03:01 -08:00
Chris Bednarski 4e456cdf09 Added google disk_type to changelog 2016-03-10 15:00:48 -08:00
Chris Bednarski 0a97874cdd Merge pull request #3336 from mitchellh/f-google-disk-type
Add disk_type for google compute, courtesy @meatballhat
2016-03-10 14:58:30 -08:00
Chris Bednarski 118fcb27b8 Added docs for google disk_type 2016-03-10 14:53:37 -08:00
Chris Bednarski 1b1bbb604e Merge branch 'googlecompute-disk-type' of https://github.com/meatballhat/packer into f-google-disk-type 2016-03-10 14:51:30 -08:00
Chris Bednarski 8d54da737f Merge pull request #3335 from davebiffuk/patch-1
Update setup.html.markdown
2016-03-10 14:23:43 -08:00
Christopher Boumenot b57ed27352 Add support for NTLM the WinRM communicator.
WinRM exposes an HTTP transport decorator that can be used for different
authentication schemes.  Windows on Azures requires this if one is to use
the out of the box configuration.
2016-03-10 10:53:38 -08:00
Dave Holland 81cf23052f Update setup.html.markdown
zip file now contains a single binary
2016-03-10 17:24:40 +00:00
Rickard von Essen cd83c3f2eb Merge pull request #3330 from mansunkuo/master
Add example of extra_arguments
2016-03-10 15:24:55 +01:00
Mansun Kuo 5134faf0cc Add example of extra_arguments 2016-03-10 21:49:04 +08:00
Rickard von Essen b214a175f2 Fixed OpenStack cli install example
Closes #3326
2016-03-09 12:29:31 +01:00
Chris Bednarski ff7ce8e082 Merge pull request #3324 from dmikurube/add-timezone-description-for-isotime
Add a note about timezone "-0700" in "isotime".
2016-03-08 10:16:59 -08:00
Dai MIKURUBE 2d4c31f82b Add a note about timezone "-0700" in "isotime". 2016-03-08 17:35:06 +00:00
Chris Bednarski 39ca3c292e Merge pull request #3299 from tylert/shell-doc-order
Put shell provisioner doc in 'packer fix' order
2016-03-07 18:30:29 -08:00
Rickard von Essen 084c922176 Updated CHANGELOG.md 2016-03-07 13:49:12 +01:00
Rickard von Essen 44fb729df8 Merge pull request #3276 from rickard-von-essen/issue-3273
Ansible: add the support for specifying ansible_user
2016-03-07 13:45:44 +01:00
Rickard von Essen d59844f0d1 Determine current user without CGO. 2016-03-07 13:35:24 +01:00
Rickard von Essen 7369841e63 Ansible: add the support for specifying ansible_user
Closes #3273
2016-03-07 13:35:24 +01:00
Rickard von Essen 96ac0d0e90 Merge pull request #3291 from rickard-von-essen/issue-3275
Ansible: don't use deprecated ssh options when ver above 2.0
2016-03-07 13:32:00 +01:00
Rickard von Essen d6716f6ee1 Ansible: don't use deprecated ssh options when ver above 2.0
Ansible 2.0 deprecated ansible_ssh_user, ansible_ssh_port, ansible_ssh_host
instead use ansible_user, ansible_port, and ansible_host in the
inventory file.

Closes #3275
2016-03-07 13:26:22 +01:00
Rickard von Essen 51884ac31b Delete the PR template before submitting. 2016-03-07 12:09:01 +01:00
Rickard von Essen 209b7bc86d Emphasise that the template should be removed before submitting. 2016-03-07 12:05:59 +01:00
Rickard von Essen 744c0a8eec Merge pull request #3305 from Thingee/master
Fixing broken link in OpenStack builder docs
2016-03-04 13:39:50 +01:00
Mike Perez dfeaa32134 Fixing broken link in OpenStack builder docs 2016-03-03 22:56:39 -08:00
Chris Bednarski 14fc7da43a Merge pull request #3282 from mhlias/master
Make communicator's WinRM defaults more intuitive when using SSL.
2016-03-02 15:54:36 -08:00
Tyler Tidman b6eaf60fda Put shell provisioner doc in 'packer fix' order 2016-03-02 14:37:30 -05:00
Ilias Bertsimas 918c88ac38 Add 4 testing scenarios for WinRM helper communicator config. 2016-03-02 11:48:20 +00:00
Chris Bednarski 18f03ec070 Trim the pull request template 2016-03-01 19:31:07 -08:00
Chris Bednarski 6a38303030 Make the build name error message show the original regexp 2016-03-01 19:01:46 -08:00
Chris Bednarski 17e5076f06 Merge pull request #3292 from vtolstov/issue-3192
fix assign to driveArgs
2016-03-01 13:22:15 -08:00
Vasiliy Tolstov e5276b31a3 fix assign to driveArgs
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2016-03-01 21:15:51 +00:00
Chris Bednarski fee00baf6a Revert "close #3192"
This reverts commit 3b8f0023e5.
2016-02-29 13:51:47 -08:00
Chris Bednarski bf17e091b7 Merge pull request #3272 from vtolstov/issue-3192
builder/qemu: fix drive args
2016-02-29 11:15:30 -08:00
Ilias Bertsimas 883e4bcae2 Update documentation to reflect the new default behaviour. 2016-02-29 16:06:23 +00:00
Ilias Bertsimas 85e4865368 Make communicator's WinRM defaults more intuitive when using SSL. 2016-02-29 14:26:28 +00:00
Rickard von Essen 6743fd5a07 Updated CHANGELOG 2016-02-26 22:04:49 +01:00
Rickard von Essen b5b1ecaef6 Merge pull request #3274 from rickard-von-essen/issue-3271
Ansible: os.Environ() should always be passed to the ansible command.
2016-02-26 21:10:07 +01:00
Rickard von Essen f823725e53 Ansible: os.Environ() should always be passed to the ansible command.
Fixes GH-3271

Regression introduced in GH-3203
2016-02-26 20:49:37 +01:00
Chris Bednarski cc8974754f Merge branch 'master' of https://github.com/mitchellh/packer 2016-02-26 10:47:34 -08:00
Chris Bednarski 3a14f555e1 Remove errant e 2016-02-26 10:47:29 -08:00
Vasiliy Tolstov 3b8f0023e5 close #3192
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2016-02-26 14:05:43 +00:00
Rickard von Essen c430daed14 Updated CHANGELOG 2016-02-26 11:42:21 +01:00
Chris Bednarski 39f7eec3e1 Merge pull request #3267 from aaronharnly/patch-1
"environmental variable" -> "environment variable"
2016-02-25 15:32:55 -08:00
Aaron Harnly f47d58f92e "environmental variable" -> "environment variable"
They are called environment variables, rather than environmental variables[1].
This small fix corrects that in the docs.

[1] http://man7.org/linux/man-pages/man7/environ.7.html
2016-02-25 17:21:18 -05:00
Chris Bednarski 25fe234f9f Merge branch 'master' of https://github.com/mitchellh/packer 2016-02-24 17:42:01 -08:00
Chris Bednarski bb9d29b5cf Added github issue and PR templates 2016-02-24 17:41:57 -08:00
Rickard von Essen 13a0d3c675 Merge pull request #3239 from rickard-von-essen/issue-3238
Parallels: Support relative paths in output_directory
2016-02-24 12:12:54 +01:00
Chris Bednarski cfbd7b35d3 If go version is 1.4 use godeps to restore dependencies 2016-02-23 23:26:45 -08:00
Chris Bednarski 0db59e055c Revert "Set a custom travis install so godeps doesn't get called"
This reverts commit c23749afe0.
2016-02-23 23:01:32 -08:00
Chris Bednarski c23749afe0 Set a custom travis install so godeps doesn't get called 2016-02-23 22:32:35 -08:00
Chris Bednarski 24a234f9ac Update travis versions 2016-02-23 22:23:22 -08:00
Chris Bednarski bb12c87a7e Maybe not going to do this since the compiler is getting slower 2016-02-23 14:17:04 -08:00
Chris Bednarski 7ea815024d Removed beta warning; this is live now 2016-02-22 17:58:41 -08:00
Chris Bednarski 588f9f30b2 Remove unused dependencies; see godep v54 2016-02-22 16:21:39 -08:00
Chris Bednarski 484404f223 Merge pull request #3248 from clstokes/docs-pp-shell-local
website/docs: Add `shell-local` post-processor to sidebar.
2016-02-22 12:57:24 -08:00
Chris Bednarski d5dd48d1e2 Bump version.go to 0.9.1 so dev builds don't tell you to update 2016-02-22 12:39:21 -08:00
Cameron Stokes 04386435de docs/website: Add shell-local post-processor to sidebar. 2016-02-22 12:35:35 -08:00
Chris Bednarski fe59eb9019 Merge pull request #3246 from mitchellh/sethvargo/dist
Update dist script to sign signature file
2016-02-22 11:57:47 -08:00
Seth Vargo 4b60f17e4f Update dist script to sign signature file 2016-02-22 14:56:45 -05:00
Chris Bednarski 8a53385cbc Revert "Remove a bunch of unused dependencies (godep v54+ required)"
This reverts commit 9ed133ea01.
2016-02-22 11:44:12 -08:00
Chris Bednarski 9ed133ea01 Remove a bunch of unused dependencies (godep v54+ required) 2016-02-22 11:40:02 -08:00
Chris Bednarski 48a6c17d91 Merge pull request #3240 from dave2/docs-missing-amazon-import
Minor amazon-import doc fixes (formatting, doc sidebar)
2016-02-22 10:45:09 -08:00
Rickard von Essen e486eba9cb Merge pull request #3244 from clstokes/docs-pp-shell-local
website/docs: Fix `shell-local` post-processor example.
2016-02-22 18:50:06 +01:00
Cameron Stokes 8fbe7a4182 Fix shell-local post-processor exmaple. 2016-02-22 09:23:24 -08:00
Jack Pearkes 11c1ac45f8 website: update to 0.9.0 2016-02-22 09:15:56 -08:00
Rickard von Essen 0cffcb0c39 Updated vendor after update of gophercloud in 88a84f5 2016-02-22 09:45:21 +01:00
David Zanetti 9b3a011cfa Minor amazon-import doc fixes (formatting, doc sidebar)
* Add amazon-import to documentation sidebar
 * Fix formatting error in amazon-import doc

Signed-off-by: David Zanetti <dave2@wetstring.net>
2016-02-22 09:26:37 +13:00
Rickard von Essen 46f74d8347 Parallels: Support relative paths in output_directory
Fixes #3238
2016-02-21 16:52:51 +01:00
Rickard von Essen ba2b438001 Updated CHANGELOG.md 2016-02-21 08:57:54 +01:00
Rickard von Essen 88a84f5fb2 Merge pull request #3209 from rickard-von-essen/openstack-get-password
Get admin password of booted instance when using WinRM
2016-02-20 10:29:38 +01:00
Rickard von Essen da1e9f4976 Merge pull request #3232 from vespian/ansible_provisioner_empty_groups_support
Add empty_groups param to ansible provisioner
2016-02-20 10:28:38 +01:00
Chris Bednarski d5ef665a7e We're back in development mode now 2016-02-19 19:11:02 -08:00
Chris Bednarski 3c569e75ca Update 0.9.0 release date in changelog 2016-02-19 19:04:50 -08:00
Chris Bednarski a643faae67 Bump version to 0.9.0 2016-02-19 18:31:30 -08:00
Chris Bednarski 5ed25603f5 Merge pull request #3235 from mitchellh/b-amazon-credentials-crash
Create a session for EC2RoleProvider; prevents crash; fixes #3123
2016-02-19 18:28:12 -08:00
Chris Bednarski d0ab8658bc Create a session for EC2RoleProvider; prevents crash; fixes #3123 2016-02-19 17:10:05 -08:00
Vespian 4750da3457 Add empty_groups param to ansible provisioner 2016-02-19 23:46:50 +01:00
Rickard von Essen 344ed08f49 Merge pull request #3231 from tylert/doc-ssh-comm
List ssh communicator doc options in 'packer fix' (alphabetical) order
2016-02-19 19:49:19 +01:00
Tyler Tidman 8f44bd7575 List ssh communicator doc options in 'packer fix' (alphabetical) order 2016-02-19 09:46:24 -05:00
Chris Bednarski 906e6d3a96 Merge branch 'master' of https://github.com/mitchellh/packer 2016-02-18 15:26:40 -08:00
Chris Bednarski 063e7a71e1 Added DownloadDir interface change to changelog 2016-02-18 15:26:32 -08:00
Chris Bednarski d4ba250cb9 Merge pull request #3229 from infonaut/infonaut-Ansible-Doc
Docs: add Ansible Remote to index
2016-02-18 11:40:40 -08:00
David McNicholl c831bf2c43 Add Ansible Remote to website documentation
Add Ansible Remote to website documentation and appropriately rename Ansible Local link.
2016-02-18 10:41:59 +00:00
Rickard von Essen 1c61c06ff8 Merge pull request #3225 from mohae/openstack-doc-a-z
docs: alphabetize openstack settings; username and password are left …
2016-02-18 10:08:59 +01:00
Rickard von Essen 23bc6c3a47 Merge pull request #3226 from mohae/google-compute-doc-a-z
docs: alphabetize google-compute settings
2016-02-18 10:07:37 +01:00
Rickard von Essen 995a8b864b Merge pull request #3227 from mohae/parallels-iso-doc-a-z
docs: alphabetize parallels-iso settings
2016-02-18 10:05:56 +01:00
Rickard von Essen 6429d4136b Merge pull request #3228 from mohae/parallels-pvm-doc-a-z
docs: alphabetize parallels-pvm settings
2016-02-18 10:04:53 +01:00
Rickard von Essen 4da0bebbce Updated gopercloud to include GH-534 get-password. 2016-02-18 10:01:04 +01:00
Joel Scoble 46a42fe910 docs: alphabetize parallels-pvm settings 2016-02-17 23:52:32 -06:00
Joel Scoble dae88620b5 docs: alphabetize parallels-iso settings 2016-02-17 23:48:24 -06:00
Joel Scoble 609131e0e9 docs: alphabetize openstack settings; username and password are left in their current location (not alphabetized 2016-02-17 23:45:14 -06:00
Joel Scoble 70d16132de docs: alphabetize google-compute settings 2016-02-17 23:40:31 -06:00
Chris Bednarski 236d5297f3 Added shell-local post-processor to the changelog 2016-02-17 20:45:24 -08:00
Rickard von Essen 5368697413 Get admin password of booted instance when using WinRM
Fixes #2663
2016-02-16 11:06:22 +01:00
Dan Buch 81fed4556f Merge remote-tracking branch 'upstream/master' into googlecompute-disk-type 2015-12-08 19:19:13 -05:00
Dan Buch cac93d311c builder/googlecompute: accept disk type, default pd-standard 2015-10-13 20:18:26 -04:00
873 changed files with 216684 additions and 34752 deletions
+20
View File
@@ -0,0 +1,20 @@
**DELETE THIS TEMPLATE BEFORE SUBMITTING**
_Only use Github issues to report bugs or feature requests, see https://www.packer.io/community/_
If you are planning to open a pull-request just open the pull-request instead of making an issue first.
FOR FEATURES:
Describe the feature you want and your use case _clearly_.
FOR BUGS:
Describe the problem and include the following information:
- Packer Version
- Host platform
- Debug log output from `PACKER_LOG=1 packer build template.json`.
Please paste this in a gist https://gist.github.com
- The _simplest example template and scripts_ needed to reproduce the bug.
Include these in your gist https://gist.github.com
+13
View File
@@ -0,0 +1,13 @@
**DELETE THIS TEMPLATE BEFORE SUBMITTING**
Describe the change you are making here!
Please include tests. Check out these examples:
- https://github.com/mitchellh/packer/blob/master/builder/virtualbox/common/ssh_config_test.go#L19-L37
- https://github.com/mitchellh/packer/blob/master/post-processor/compress/post-processor_test.go#L153-L182
If your PR resolves any open issue(s), please indicate them like this so they will be closed when your PR is merged:
Closes #xxx
Closes #xxx
+3 -1
View File
@@ -6,9 +6,11 @@
/website/build
.DS_Store
.vagrant
.idea
test/.env
*~
website/.bundle
website/vendor
packer-test*.log
packer-test*.log
+5 -4
View File
@@ -3,9 +3,10 @@ sudo: false
language: go
go:
- 1.4
- 1.5.3
- tip
- 1.6
install:
- make deps
script:
- GOMAXPROCS=2 make ci
@@ -17,4 +18,4 @@ branches:
matrix:
fast_finish: true
allow_failures:
- go: tip
- go: 1.4.3
+71 -3
View File
@@ -1,4 +1,68 @@
## 0.9.0 (Unreleased)
## (Unreleased)
FEATURES:
* `azure-arm` builder: Can now build Windows images, and supports additional
configuration. Please refer to the documentation for details.
IMPROVEMENTS:
* core: Added support for `ATLAS_CAFILE` and `ATLAS_CAPATH` [GH-3494]
* builder/azure: Improved build cancellation and cleanup of partially-
provisioned resources [GH-3461]
* builder/azure: Improved logging [GH-3461]
* builder/azure: Added support for US Government and China clouds [GH-3461]
* builder/azure: Users may now specify an image version [GH-3461]
* builder/azure: Added device login [GH-3461]
* builder/docker: Added `privileged` build option [GH-3475]
* builder/google: Packer now identifies its version to the service [GH-3465]
* provisioner/shell: Added `remote_folder` and `remote_file` options
[GH-3462]
BUG FIXES:
* core: Fix hang after pressing enter key in `-debug` mode [GH-3346]
* provisioner/chef: Use custom values for remote validation key path
[GH-3468]
## 0.10.2 (September 20, 2016)
BUG FIXES:
* Rebuilding with OS X Sierra and go 1.7.1 to fix bug in Sierra
## 0.10.0 (March 14, 2016)
BACKWARDS INCOMPATIBILITIES:
* Building Packer now requires go >= 1.5 (>= 1.6 is recommended). If you want
to continue building with go 1.4 you can remove the `azurearmbuilder` line
from `command/plugin.go`.
FEATURES:
* **New `azure-arm` builder**: Build virtual machines in Azure Resource
Manager
IMPROVEMENTS:
* builder/google: Added support for `disk_type` [GH-2830]
* builder/openstack: Added support for retrieving the Administrator password
when using WinRM if no `winrm_password` is set [GH-3209]
* provisioner/ansible: Added the `empty_groups` parameter [GH-3232]
* provisioner/ansible: Added the `user` parameter [GH-3276]
* provisioner/ansible: Don't use deprecated ssh option with Ansible 2.0
[GH-3291]
* provisioner/puppet-masterless: Add `ignore_exit_codes` parameter [GH-3349]
BUG FIXES:
* builders/parallels: Handle `output_directory` containing `.` and `..`
[GH-3239]
* provisioner/ansible: os.Environ() should always be passed to the ansible
command. [GH-3274]
## 0.9.0 (February 19, 2016)
BACKWARDS INCOMPATIBILITIES:
@@ -7,8 +71,9 @@ BACKWARDS INCOMPATIBILITIES:
the packer-* plugin files** or packer will load out-of-date plugins from
disk.
* Release binaries are now provided via <https://releases.hashicorp.com>.
* Packer 0.9.0 is now built with Go 1.6. Future versions will drop support
for building with Go 1.4.
* Packer 0.9.0 is now built with Go 1.6.
* core: Plugins that implement the Communicator interface must now implement
a DownloadDir method [GH-2618]
* builder/amazon: Inline `user_data` for EC2 is now base64 encoded
automatically [GH-2539]
* builder/parallels: `parallels_tools_host_path` and `guest_os_distribution`
@@ -24,6 +89,9 @@ FEATURES:
* **New Ansible Provisioner**: `ansible` provisioner supports remote
provisioning to keep your build image cleaner [GH-1969]
* **New Amazon Import post-processor**: `amazon-import` allows you to upload an OVA-based VM to Amazon EC2 [GH-2962]
* **Shell Local post-processor**: `shell-local` allows you to run shell
commands on the host after a build has completed for custom packaging or
publishing of your artifacts [GH-2706]
* **Artifice post-processor**: Override packer artifacts during post-
processing. This allows you to extract artifacts from a packer builder and
use them with other post-processors like compress, docker, and Atlas.
+9 -12
View File
@@ -51,19 +51,16 @@ it raises the chances we can quickly merge or address your contributions.
## Setting up Go to work on Packer
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.
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.
1. [Download](https://golang.org/dl) and install Go. Go 1.6 or higher is
preferred. Packer _may_ work with versions of Go older than 1.5 but these
are not supported.
1. [Download](https://golang.org/dl) and install Go. The instructions below
are for go 1.6. Earlier versions of Go are no longer supported.
2. Set and export the `GOPATH` environment variable and update your `PATH`. For
example, you can add to your `.bash_profile`. If you're using Go 1.5 also
set `GO15VENDOREXPERIMENT`. This is not necessary for Go 1.6 and up.
example, you can add to your `.bash_profile`.
```
export GOPATH=$HOME/Documents/golang
export GO15VENDOREXPERIMENT=1
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
```
@@ -72,16 +69,16 @@ following steps in order to be able to compile and test Packer.
`$GOPATH/src/github.com/mitchellh/packer`.
4. When working on packer `cd $GOPATH/src/github.com/mitchellh/packer` so you
can run make and easily access other files.
can run `make` and easily access other files.
5. Make your changes to the Packer source. You can run `make` in
`$GOPATH/src/github.com/mitchellh/packer` to run tests and build the packer
binary. Any compilation errors will be shown when the binaries are
rebuilding.
rebuilding. If you don't have `make` you can simply run `go build -o bin/packer .` from the project root.
6. After running `make` successfully, use
6. After running building packer successfully, use
`$GOPATH/src/github.com/mitchellh/packer/bin/packer` to build a machine and
verify your changes work.
verify your changes work. For instance: `$GOPATH/src/github.com/mitchellh/packer/bin/packer build template.json`.
7. If everything works well and the tests pass, run `go fmt` on your code
before submitting a pull-request.
+315 -8
View File
@@ -1,12 +1,67 @@
{
"ImportPath": "github.com/mitchellh/packer",
"GoVersion": "go1.5",
"GoVersion": "go1.6",
"GodepVersion": "v62",
"Deps": [
{
"ImportPath": "github.com/ActiveState/tail",
"Comment": "v0-41-g1a0242e",
"Rev": "1a0242e795eeefe54261ff308dc685f7d29cc58c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/compute",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/network",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/resources/resources",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/resources/subscriptions",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/storage",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/storage",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest",
"Comment": "v7.0.4",
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/azure",
"Comment": "v7.0.4",
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/date",
"Comment": "v7.0.4",
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/to",
"Comment": "v7.0.4",
"Rev": "b01ec2b60f95678fa759f796bac3c6b9bceaead4"
},
{
"ImportPath": "github.com/Azure/go-ntlmssp",
"Rev": "e0b63eb299a769ea4b04dadfe530f6074b277afb"
},
{
"ImportPath": "github.com/armon/go-radix",
"Rev": "4239b77079c7b5d1243b7b4736304ce8ddb6f0f2"
@@ -16,6 +71,61 @@
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/awserr",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/awsutil",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/client",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/client/metadata",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/corehandlers",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/defaults",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/ec2metadata",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/request",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/session",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/endpoints",
"Comment": "v1.1.2",
@@ -26,6 +136,36 @@
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/restxml",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/signer/v4",
"Comment": "v1.1.2",
@@ -46,6 +186,16 @@
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3iface",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/s3/s3manager",
"Comment": "v1.1.2",
"Rev": "8041be5461786460d86b4358305fbdf32d37cfb2"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/sts",
"Comment": "v1.1.2",
@@ -55,6 +205,11 @@
"ImportPath": "github.com/bgentry/speakeasy",
"Rev": "36e9cfdd690967f4f690c6edcc9ffacd006014a0"
},
{
"ImportPath": "github.com/dgrijalva/jwt-go",
"Comment": "v2.4.0-11-gf219341",
"Rev": "f2193411bd642f7db03249fd79d5292c9b34916a"
},
{
"ImportPath": "github.com/digitalocean/godo",
"Comment": "v0.9.0-24-g6ca5b77",
@@ -83,13 +238,13 @@
},
{
"ImportPath": "github.com/hashicorp/atlas-go/archive",
"Comment": "20141209094003-90-g0008886",
"Rev": "0008886ebfa3b424bed03e2a5cbe4a2568ea0ff6"
"Comment": "20141209094003-92-g95fa852",
"Rev": "95fa852edca41c06c4ce526af4bb7dec4eaad434"
},
{
"ImportPath": "github.com/hashicorp/atlas-go/v1",
"Comment": "20141209094003-90-g0008886",
"Rev": "0008886ebfa3b424bed03e2a5cbe4a2568ea0ff6"
"Comment": "20141209094003-92-g95fa852",
"Rev": "95fa852edca41c06c4ce526af4bb7dec4eaad434"
},
{
"ImportPath": "github.com/hashicorp/errwrap",
@@ -107,6 +262,10 @@
"ImportPath": "github.com/hashicorp/go-multierror",
"Rev": "d30f09973e19c1dfcd120b2d9c4f168e68d6b5d5"
},
{
"ImportPath": "github.com/hashicorp/go-rootcerts",
"Rev": "6bb64b370b90e7ef1fa532be9e591a81c3493e00"
},
{
"ImportPath": "github.com/hashicorp/go-version",
"Rev": "7e3c02b30806fa5779d3bdfc152ce4c6f40e7b38"
@@ -192,6 +351,10 @@
"ImportPath": "github.com/mitchellh/go-fs",
"Rev": "a34c1b9334e86165685a9449b782f20465eb8c69"
},
{
"ImportPath": "github.com/mitchellh/go-fs/fat",
"Rev": "a34c1b9334e86165685a9449b782f20465eb8c69"
},
{
"ImportPath": "github.com/mitchellh/go-homedir",
"Rev": "d682a8f0cf139663a984ff12528da460ca963de9"
@@ -230,7 +393,7 @@
},
{
"ImportPath": "github.com/packer-community/winrmcp/winrmcp",
"Rev": "3d184cea22ee1c41ec1697e0d830ff0c78f7ea97"
"Rev": "f1bcf36a69fa2945e65dd099eee11b560fbd3346"
},
{
"ImportPath": "github.com/pierrec/lz4",
@@ -246,8 +409,88 @@
},
{
"ImportPath": "github.com/rackspace/gophercloud",
"Comment": "v1.0.0-774-g680aa02",
"Rev": "680aa02616313d8399abc91f17a444cf9292f0e1"
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/common/extensions",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/flavors",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/images",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/servers",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tenants",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tokens",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v3/tokens",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/utils",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/pagination",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/testhelper",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/rackspace/gophercloud/testhelper/client",
"Comment": "v1.0.0-810-g53d1dc4",
"Rev": "53d1dc4400e1ebcd37a0e01d8c1fe2f4db3b99d2"
},
{
"ImportPath": "github.com/satori/go.uuid",
@@ -265,22 +508,54 @@
"ImportPath": "golang.org/x/crypto/curve25519",
"Rev": "1f22c0103821b9390939b6776727195525381532"
},
{
"ImportPath": "golang.org/x/crypto/md4",
"Rev": "1f22c0103821b9390939b6776727195525381532"
},
{
"ImportPath": "golang.org/x/crypto/ssh",
"Rev": "1f22c0103821b9390939b6776727195525381532"
},
{
"ImportPath": "golang.org/x/crypto/ssh/agent",
"Rev": "1f22c0103821b9390939b6776727195525381532"
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
},
{
"ImportPath": "golang.org/x/net/context/ctxhttp",
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
},
{
"ImportPath": "golang.org/x/net/html",
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
},
{
"ImportPath": "golang.org/x/net/html/atom",
"Rev": "6ccd6698c634f5d835c40c1c31848729e0cecda1"
},
{
"ImportPath": "golang.org/x/oauth2",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/oauth2/google",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/oauth2/internal",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/oauth2/jws",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/oauth2/jwt",
"Rev": "8a57ed94ffd43444c0879fe75701732a38afc985"
},
{
"ImportPath": "golang.org/x/sys/unix",
"Rev": "50c6bc5e4292a1d4e65c6e9be5f53be28bcbe28e"
@@ -297,10 +572,42 @@
"ImportPath": "google.golang.org/api/googleapi",
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
},
{
"ImportPath": "google.golang.org/api/googleapi/internal/uritemplates",
"Rev": "ddff2aff599105a55549cf173852507dfa094b7f"
},
{
"ImportPath": "google.golang.org/appengine",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/app_identity",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/base",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/datastore",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/log",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/modules",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/appengine/internal/remote_api",
"Rev": "6bde959377a90acb53366051d7d587bfd7171354"
},
{
"ImportPath": "google.golang.org/cloud/compute/metadata",
"Rev": "5a3b06f8b5da3b7c3a93da43163b872c86c509ef"
+21 -13
View File
@@ -1,4 +1,5 @@
TEST?=$(shell go list ./... | grep -v vendor)
VET?=$(shell ls -d */ | grep -v vendor | grep -v website)
# Get the current full sha from git
GITSHA:=$(shell git rev-parse HEAD)
# Get the current local branch name from git (if we can, this may be blank)
@@ -12,14 +13,14 @@ release: deps test releasebin package
bin: deps
@echo "WARN: 'make bin' is for debug / test builds only. Use 'make release' for release builds."
@sh -c "$(CURDIR)/scripts/build.sh"
@GO15VENDOREXPERIMENT=1 sh -c "$(CURDIR)/scripts/build.sh"
releasebin: deps
@grep 'const VersionPrerelease = "dev"' version.go > /dev/null ; if [ $$? -eq 0 ]; then \
echo "ERROR: You must remove prerelease tags from version.go prior to release."; \
@grep 'const VersionPrerelease = "dev"' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \
echo "ERROR: You must remove prerelease tags from version/version.go prior to release."; \
exit 1; \
fi
@sh -c "$(CURDIR)/scripts/build.sh"
@GO15VENDOREXPERIMENT=1 sh -c "$(CURDIR)/scripts/build.sh"
package:
$(if $(VERSION),,@echo 'VERSION= needed to release; Use make package skip compilation'; exit 1)
@@ -28,18 +29,26 @@ package:
deps:
go get github.com/mitchellh/gox
go get golang.org/x/tools/cmd/stringer
go get golang.org/x/tools/cmd/vet
@go version | grep 1.4 ; if [ $$? -eq 0 ]; then \
echo "Installing godep and restoring dependencies"; \
go get github.com/tools/godep; \
godep restore; \
fi
dev: deps
@grep 'const VersionPrerelease = ""' version.go > /dev/null ; if [ $$? -eq 0 ]; then \
echo "ERROR: You must add prerelease tags to version.go prior to making a dev build."; \
@grep 'const VersionPrerelease = ""' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \
echo "ERROR: You must add prerelease tags to version/version.go prior to making a dev build."; \
exit 1; \
fi
@PACKER_DEV=1 sh -c "$(CURDIR)/scripts/build.sh"
@PACKER_DEV=1 GO15VENDOREXPERIMENT=1 sh -c "$(CURDIR)/scripts/build.sh"
fmt:
go fmt `go list ./... | grep -v vendor`
# Install js-beautify with npm install -g js-beautify
fmt-examples:
find examples -name *.json | xargs js-beautify -r -s 2 -n -eol "\n"
# generate runs `go generate` to build the dynamically generated
# source files.
generate: deps
@@ -47,8 +56,8 @@ generate: deps
go fmt command/plugin.go
test: deps
@go test $(TEST) $(TESTARGS) -timeout=15s
@go vet $(TEST) ; if [ $$? -eq 1 ]; then \
@go test $(TEST) $(TESTARGS) -timeout=2m
@go tool vet $(VET) ; if [ $$? -eq 1 ]; then \
echo "ERROR: Vet found problems in the code."; \
exit 1; \
fi
@@ -59,12 +68,11 @@ testacc: deps generate
PACKER_ACC=1 go test -v $(TEST) $(TESTARGS) -timeout=45m
testrace: deps
@go test -race $(TEST) $(TESTARGS) -timeout=15s
@go test -race $(TEST) $(TESTARGS) -timeout=2m
updatedeps:
go get -u github.com/mitchellh/gox
go get -u golang.org/x/tools/cmd/stringer
go get -u golang.org/x/tools/cmd/vet
@echo "INFO: Packer deps are managed by godep. See CONTRIBUTING.md"
# This is used to add new dependencies to packer. If you are submitting a PR
@@ -73,4 +81,4 @@ vendor:
godep restore
godep save
.PHONY: bin checkversion ci default deps generate releasebin test testacc testrace updatedeps
.PHONY: bin checkversion ci default deps fmt fmt-examples generate releasebin test testacc testrace updatedeps
+1 -1
View File
@@ -55,7 +55,7 @@ file as `quick-start.json`. Export your AWS credentials as the
"builders": [{
"type": "amazon-ebs",
"access_key": "{{user `access_key`}}",
"secret_key": "{{user `access_key`}}",
"secret_key": "{{user `secret_key`}}",
"region": "us-east-1",
"source_ami": "ami-de0d9eb7",
"instance_type": "t1.micro",
+7 -11
View File
@@ -12,30 +12,26 @@ os: Windows Server 2012 R2
environment:
GOPATH: c:\gopath
matrix:
- GOARCH: 386
GOVERSION: 1.6rc2
- GOARCH: amd64
GOVERSION: 1.6rc2
clone_folder: c:\gopath\src\github.com\mitchellh\packer
install:
- set Path=c:\go\bin;%Path%
- set GO15VENDOREXPERIMENT=1
- echo %Path%
- appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-%GOARCH%.msi
- msiexec /i go%GOVERSION%.windows-%GOARCH%.msi /q
- go version
- go env
- go get github.com/mitchellh/gox
- go get golang.org/x/tools/cmd/stringer
- go get golang.org/x/tools/cmd/vet
build_script:
- git rev-parse HEAD
- go test -v ./...
- go vet ./...
# go test $(go list ./... | grep -v vendor)
- ps: |
go.exe test (go.exe list ./... `
|? { -not $_.Contains('/vendor/') } `
|? { $_ -ne 'github.com/mitchellh/packer/builder/parallels/common' } `
|? { $_ -ne 'github.com/mitchellh/packer/common' }`
|? { $_ -ne 'github.com/mitchellh/packer/provisioner/ansible' })
test: off
Executable
+18
View File
@@ -0,0 +1,18 @@
PACKER=$GOPATH/src/github.com/mitchellh/packer
AZURE=/tmp/packer-azure
ls $AZURE >/dev/null || git clone https://github.com/Azure/packer-azure /tmp/packer-azure
PWD=`pwd`
cd $AZURE && git pull
cd $PWD
# copy things
cp -r $AZURE/packer/builder/azure $PACKER/builder/
cp -r $AZURE/packer/communicator/* $PACKER/communicator/
cp -r $AZURE/packer/provisioner/azureVmCustomScriptExtension $PACKER/provisioner/
# remove legacy API client
rm -rf $PACKER/builder/azure/smapi
# fix imports
find $PACKER/builder/azure/ -type f | grep ".go" | xargs sed -e 's/Azure\/packer-azure\/packer\/builder\/azure/mitchellh\/packer\/builder\/azure/g' -i ''
+6 -1
View File
@@ -10,6 +10,8 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/mitchellh/packer/template/interpolate"
)
@@ -42,6 +44,7 @@ func (c *AccessConfig) Config() (*aws.Config, error) {
return nil, err
}
} else {
sess := session.New(config)
creds = credentials.NewChainCredentials([]credentials.Provider{
&credentials.StaticProvider{Value: credentials.Value{
AccessKeyID: c.AccessKey,
@@ -50,7 +53,9 @@ func (c *AccessConfig) Config() (*aws.Config, error) {
}},
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
&ec2rolecreds.EC2RoleProvider{},
&ec2rolecreds.EC2RoleProvider{
Client: ec2metadata.New(sess),
},
})
}
return config.WithCredentials(creds), nil
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Microsoft Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+191
View File
@@ -0,0 +1,191 @@
# packer-azure-arm
The ARM flavor of packer-azure utilizes the
[Azure Resource Manager APIs](https://msdn.microsoft.com/en-us/library/azure/dn790568.aspx).
Please see the
[overview](https://azure.microsoft.com/en-us/documentation/articles/resource-group-overview/)
for more information about ARM as well as the benefit of ARM.
## Device Login vs. Service Principal Name (SPN)
There are two ways to get started with packer-azure. The simplest is device login, and only requires a Subscription ID.
Device login is only supported for Linux based VMs. The second is the use of an SPN. We recommend the device login
approach for those who are first time users, and just want to ''kick the tires.'' We recommend the SPN approach if you
intend to automate Packer, or you are deploying Windows VMs.
## Device Login
A sample template for device login is show below. There are three pieces of information
you must provide to enable device login mode.
1. SubscriptionID
1. Resource Group - parent resource group that Packer uses to build an image.
1. Storage Account - storage account where the image will be placed.
> Device login mode is enabled by not setting client_id, client_secret, and tenant_id.
The device login flow asks that you open a web browser, navigate to http://aka.ms/devicelogin, and input the supplied
code. This authorizes the Packer for Azure application to act on your behalf. An OAuth token will be created, and
stored in the user's home directory (~/.azure/packer/oauth-TenantID.json, and TenantID will be replaced with the actual
Tenant ID). This token is used if it exists, and refreshed as necessary.
```json
{
"variables": {
"sid": "your_subscription_id",
"rgn": "your_resource_group",
"sa": "your_storage_account"
},
"builders": [
{
"type": "azure-arm",
"subscription_id": "{{user `sid`}}",
"resource_group_name": "{{user `rgn`}}",
"storage_account": "{{user `sa`}}",
"capture_container_name": "images",
"capture_name_prefix": "packer",
"os_type": "Linux",
"image_publisher": "Canonical",
"image_offer": "UbuntuServer",
"image_sku": "14.04.3-LTS",
"location": "South Central US",
"vm_size": "Standard_A2"
}
],
"provisioners": [
{
"execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'",
"inline": [
"apt-get update",
"apt-get upgrade -y",
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
],
"inline_shebang": "/bin/sh -x",
"type": "shell"
}
]
}
```
## Service Principal Name
The ARM APIs use OAUTH to authenticate, and requires an SPN. The following articles
are a good starting points for creating a new SPN.
* [Automating Azure on your CI server using a Service Principal](http://blog.davidebbo.com/2014/12/azure-service-principal.html)
* [Authenticating a service principal with Azure Resource Manager](https://azure.microsoft.com/en-us/documentation/articles/resource-group-authenticate-service-principal/)
There are three (four in the case of Windows) pieces of configuration you need to note
after creating an SPN.
1. Client ID (aka Service Principal ID)
1. Client Secret (aka Service Principal generated key)
1. Client Tenant (aka Azure Active Directory tenant that owns the
Service Principal)
1. Object ID (Windows only) - a certificate is used to authenticate WinRM access, and the certificate is injected into
the VM using Azure Key Vault. Access to the key vault is protected by an ACL associated with the SPN's ObjectID.
Linux does not need nor use a key vault, so there's no need to know the ObjectID.
You will also need the following.
1. Subscription ID
1. Resource Group
1. Storage Account
Resource Group is where your storage account is located, and Storage
Account is where the created packer image will be stored.
The Service Principal has been tested with the following [permissions](https://azure.microsoft.com/en-us/documentation/articles/role-based-access-control-configure/).
Please review the document for the [built in roles](https://azure.microsoft.com/en-gb/documentation/articles/role-based-access-built-in-roles/)
for more details.
* Owner
> NOTE: the Owner role is too powerful, and more explicit set of roles
> is TBD. Issue #183 is tracking this work. Permissions can be scoped to
> a specific resource group to further limit access.
### Sample Ubuntu
The following is a sample Packer template for use with the Packer
Azure for ARM builder.
```json
{
"variables": {
"cid": "your_client_id",
"cst": "your_client_secret",
"tid": "your_client_tenant",
"sid": "your_subscription_id",
"rgn": "your_resource_group",
"sa": "your_storage_account"
},
"builders": [
{
"type": "azure-arm",
"client_id": "{{user `cid`}}",
"client_secret": "{{user `cst`}}",
"subscription_id": "{{user `sid`}}",
"tenant_id": "{{user `tid`}}",
"resource_group_name": "{{user `rgn`}}",
"storage_account": "{{user `sa`}}",
"capture_container_name": "images",
"capture_name_prefix": "packer",
"os_type": "Linux",
"image_publisher": "Canonical",
"image_offer": "UbuntuServer",
"image_sku": "14.04.3-LTS",
"location": "South Central US",
"vm_size": "Standard_A2"
}
],
"provisioners": [
{
"execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'",
"inline": [
"apt-get update",
"apt-get upgrade -y",
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
],
"inline_shebang": "/bin/sh -x",
"type": "shell"
}
]
}
```
Using the above template, Packer would be invoked as follows.
> NOTE: the following variables must be **changed** based on your
> subscription. These values are just dummy values, but they match
> format of expected, e.g. if the value is a GUID the sample is a
> GUID.
```bat
packer build^
-var cid="593c4dc4-9cd7-49af-9fe0-1ea5055ac1e4"^
-var cst="GbzJfsfrVkqL/TLfZY8TXA=="^
-var sid="ce323e74-56fc-4bd6-aa18-83b6dc262748"^
-var tid="da3847b4-8e69-40bd-a2c2-41da6982c5e2"^
-var rgn="My Resource Group"^
-var sa="mystorageaccount"^
c:\packer\ubuntu_14_LTS.json
```
Please see the
[config_sameples/arm](https://github.com/Azure/packer-azure/tree/master/config_examples)
directory for more examples of usage.
+112
View File
@@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"bytes"
"fmt"
"net/url"
"path"
"strings"
)
const (
BuilderId = "Azure.ResourceManagement.VMImage"
)
type Artifact struct {
StorageAccountLocation string
OSDiskUri string
TemplateUri string
OSDiskUriReadOnlySas string
TemplateUriReadOnlySas string
}
func NewArtifact(template *CaptureTemplate, getSasUrl func(name string) string) (*Artifact, error) {
if template == nil {
return nil, fmt.Errorf("nil capture template")
}
if len(template.Resources) != 1 {
return nil, fmt.Errorf("malformed capture template, expected one resource")
}
vhdUri, err := url.Parse(template.Resources[0].Properties.StorageProfile.OSDisk.Image.Uri)
if err != nil {
return nil, err
}
templateUri, err := storageUriToTemplateUri(vhdUri)
if err != nil {
return nil, err
}
return &Artifact{
OSDiskUri: vhdUri.String(),
OSDiskUriReadOnlySas: getSasUrl(getStorageUrlPath(vhdUri)),
TemplateUri: templateUri.String(),
TemplateUriReadOnlySas: getSasUrl(getStorageUrlPath(templateUri)),
StorageAccountLocation: template.Resources[0].Location,
}, nil
}
func getStorageUrlPath(u *url.URL) string {
parts := strings.Split(u.Path, "/")
return strings.Join(parts[3:], "/")
}
func storageUriToTemplateUri(su *url.URL) (*url.URL, error) {
// packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd -> 4085bb15-3644-4641-b9cd-f575918640b4
filename := path.Base(su.Path)
parts := strings.Split(filename, ".")
if len(parts) < 3 {
return nil, fmt.Errorf("malformed URL")
}
// packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd -> packer
prefixParts := strings.Split(parts[0], "-")
prefix := strings.Join(prefixParts[:len(prefixParts)-1], "-")
templateFilename := fmt.Sprintf("%s-vmTemplate.%s.json", prefix, parts[1])
// https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd"
// ->
// https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json"
return url.Parse(strings.Replace(su.String(), filename, templateFilename, 1))
}
func (*Artifact) BuilderId() string {
return BuilderId
}
func (*Artifact) Files() []string {
return []string{}
}
func (*Artifact) Id() string {
return ""
}
func (*Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) String() string {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("%s:\n\n", a.BuilderId()))
buf.WriteString(fmt.Sprintf("StorageAccountLocation: %s\n", a.StorageAccountLocation))
buf.WriteString(fmt.Sprintf("OSDiskUri: %s\n", a.OSDiskUri))
buf.WriteString(fmt.Sprintf("OSDiskUriReadOnlySas: %s\n", a.OSDiskUriReadOnlySas))
buf.WriteString(fmt.Sprintf("TemplateUri: %s\n", a.TemplateUri))
buf.WriteString(fmt.Sprintf("TemplateUriReadOnlySas: %s\n", a.TemplateUriReadOnlySas))
return buf.String()
}
func (*Artifact) Destroy() error {
return nil
}
+155
View File
@@ -0,0 +1,155 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"strings"
"testing"
)
func getFakeSasUrl(name string) string {
return fmt.Sprintf("SAS-%s", name)
}
func TestArtifactString(t *testing.T) {
template := CaptureTemplate{
Resources: []CaptureResources{
CaptureResources{
Properties: CaptureProperties{
StorageProfile: CaptureStorageProfile{
OSDisk: CaptureDisk{
Image: CaptureUri{
Uri: "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd",
},
},
},
},
Location: "southcentralus",
},
},
}
artifact, err := NewArtifact(&template, getFakeSasUrl)
if err != nil {
t.Fatalf("err=%s", err)
}
testSubject := artifact.String()
if !strings.Contains(testSubject, "OSDiskUri: https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd") {
t.Errorf("Expected String() output to contain OSDiskUri")
}
if !strings.Contains(testSubject, "OSDiskUriReadOnlySas: SAS-Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd") {
t.Errorf("Expected String() output to contain OSDiskUriReadOnlySas")
}
if !strings.Contains(testSubject, "TemplateUri: https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json") {
t.Errorf("Expected String() output to contain TemplateUri")
}
if !strings.Contains(testSubject, "TemplateUriReadOnlySas: SAS-Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json") {
t.Errorf("Expected String() output to contain TemplateUriReadOnlySas")
}
if !strings.Contains(testSubject, "StorageAccountLocation: southcentralus") {
t.Errorf("Expected String() output to contain StorageAccountLocation")
}
}
func TestArtifactProperties(t *testing.T) {
template := CaptureTemplate{
Resources: []CaptureResources{
CaptureResources{
Properties: CaptureProperties{
StorageProfile: CaptureStorageProfile{
OSDisk: CaptureDisk{
Image: CaptureUri{
Uri: "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd",
},
},
},
},
Location: "southcentralus",
},
},
}
testSubject, err := NewArtifact(&template, getFakeSasUrl)
if err != nil {
t.Fatalf("err=%s", err)
}
if testSubject.OSDiskUri != "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd" {
t.Errorf("Expected template to be 'https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd', but got %s", testSubject.OSDiskUri)
}
if testSubject.OSDiskUriReadOnlySas != "SAS-Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd" {
t.Errorf("Expected template to be 'SAS-Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd', but got %s", testSubject.OSDiskUriReadOnlySas)
}
if testSubject.TemplateUri != "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json" {
t.Errorf("Expected template to be 'https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json', but got %s", testSubject.TemplateUri)
}
if testSubject.TemplateUriReadOnlySas != "SAS-Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json" {
t.Errorf("Expected template to be 'SAS-Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json', but got %s", testSubject.TemplateUriReadOnlySas)
}
if testSubject.StorageAccountLocation != "southcentralus" {
t.Errorf("Expected StorageAccountLocation to be 'southcentral', but got %s", testSubject.StorageAccountLocation)
}
}
func TestArtifactOverHypenatedCaptureUri(t *testing.T) {
template := CaptureTemplate{
Resources: []CaptureResources{
CaptureResources{
Properties: CaptureProperties{
StorageProfile: CaptureStorageProfile{
OSDisk: CaptureDisk{
Image: CaptureUri{
Uri: "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/pac-ker-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd",
},
},
},
},
Location: "southcentralus",
},
},
}
testSubject, err := NewArtifact(&template, getFakeSasUrl)
if err != nil {
t.Fatalf("err=%s", err)
}
if testSubject.TemplateUri != "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/pac-ker-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json" {
t.Errorf("Expected template to be 'https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/pac-ker-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json', but got %s", testSubject.TemplateUri)
}
}
func TestArtifactRejectMalformedTemplates(t *testing.T) {
template := CaptureTemplate{}
_, err := NewArtifact(&template, getFakeSasUrl)
if err == nil {
t.Fatalf("Expected artifact creation to fail, but it succeeded.")
}
}
func TestArtifactRejectMalformedStorageUri(t *testing.T) {
template := CaptureTemplate{
Resources: []CaptureResources{
CaptureResources{
Properties: CaptureProperties{
StorageProfile: CaptureStorageProfile{
OSDisk: CaptureDisk{
Image: CaptureUri{
Uri: "bark",
},
},
},
},
},
},
}
_, err := NewArtifact(&template, getFakeSasUrl)
if err == nil {
t.Fatalf("Expected artifact creation to fail, but it succeeded.")
}
}
+45
View File
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import "github.com/Azure/go-autorest/autorest/azure"
type Authenticate struct {
env azure.Environment
clientID string
clientSecret string
tenantID string
}
func NewAuthenticate(env azure.Environment, clientID, clientSecret, tenantID string) *Authenticate {
return &Authenticate{
env: env,
clientID: clientID,
clientSecret: clientSecret,
tenantID: tenantID,
}
}
func (a *Authenticate) getServicePrincipalToken() (*azure.ServicePrincipalToken, error) {
return a.getServicePrincipalTokenWithResource(a.env.ResourceManagerEndpoint)
}
func (a *Authenticate) getServicePrincipalTokenWithResource(resource string) (*azure.ServicePrincipalToken, error) {
oauthConfig, err := newOAuthConfigWithTenant(a.tenantID)
if err != nil {
return nil, err
}
spt, err := azure.NewServicePrincipalToken(
*oauthConfig,
a.clientID,
a.clientSecret,
resource)
return spt, err
}
func newOAuthConfigWithTenant(tenantID string) (*azure.OAuthConfig, error) {
return azure.PublicCloud.OAuthConfigForTenant(tenantID)
}
+39
View File
@@ -0,0 +1,39 @@
package arm
import (
"github.com/Azure/go-autorest/autorest/azure"
"testing"
)
// Behavior is the most important thing to assert for ServicePrincipalToken, but
// that cannot be done in a unit test because it involves network access. Instead,
// I assert the expected intertness of this class.
func TestNewAuthenticate(t *testing.T) {
testSubject := NewAuthenticate(azure.PublicCloud, "clientID", "clientString", "tenantID")
spn, err := testSubject.getServicePrincipalToken()
if err != nil {
t.Fatalf(err.Error())
}
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.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.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.Type != "" {
t.Errorf("spn.Token.Type: expected=\"\", actual=%s", spn.Token.Type)
}
}
+177
View File
@@ -0,0 +1,177 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"encoding/json"
"fmt"
"math"
"net/http"
"os"
"strconv"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"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/azure"
"github.com/mitchellh/packer/builder/azure/common"
"github.com/mitchellh/packer/version"
)
const (
EnvPackerLogAzureMaxLen = "PACKER_LOG_AZURE_MAXLEN"
)
var (
packerUserAgent = fmt.Sprintf(";packer/%s", version.FormattedVersion())
)
type AzureClient struct {
storage.BlobStorageClient
resources.DeploymentsClient
resources.GroupsClient
network.PublicIPAddressesClient
compute.VirtualMachinesClient
common.VaultClient
armStorage.AccountsClient
InspectorMaxLength int
Template *CaptureTemplate
}
func getCaptureResponse(body string) *CaptureTemplate {
var operation CaptureOperation
err := json.Unmarshal([]byte(body), &operation)
if err != nil {
return nil
}
if operation.Properties != nil && operation.Properties.Output != nil {
return operation.Properties.Output
}
return nil
}
// HACK(chrboum): This method is a hack. It was written to work around this issue
// (https://github.com/Azure/azure-sdk-for-go/issues/307) and to an extent this
// issue (https://github.com/Azure/azure-rest-api-specs/issues/188).
//
// Capturing a VM is a long running operation that requires polling. There are
// couple different forms of polling, and the end result of a poll operation is
// discarded by the SDK. It is expected that any discarded data can be re-fetched,
// so discarding it has minimal impact. Unfortunately, there is no way to re-fetch
// the template returned by a capture call that I am aware of.
//
// If the second issue were fixed the VM ID would be included when GET'ing a VM. The
// VM ID could be used to locate the captured VHD, and captured template.
// Unfortunately, the VM ID is not included so this method cannot be used either.
//
// This code captures the template and saves it to the client (the AzureClient type).
// It expects that the capture API is called only once, or rather you only care that the
// last call's value is important because subsequent requests are not persisted. There
// is no care given to multiple threads writing this value because for our use case
// it does not matter.
func templateCapture(client *AzureClient) autorest.RespondDecorator {
return func(r autorest.Responder) autorest.Responder {
return autorest.ResponderFunc(func(resp *http.Response) error {
body, bodyString := handleBody(resp.Body, math.MaxInt64)
resp.Body = body
captureTemplate := getCaptureResponse(bodyString)
if captureTemplate != nil {
client.Template = captureTemplate
}
return r.Respond(resp)
})
}
}
// WAITING(chrboum): I have logged https://github.com/Azure/azure-sdk-for-go/issues/311 to get this
// method included in the SDK. It has been accepted, and I'll cut over to the official way
// once it ships.
func byConcatDecorators(decorators ...autorest.RespondDecorator) autorest.RespondDecorator {
return func(r autorest.Responder) autorest.Responder {
return autorest.DecorateResponder(r, decorators...)
}
}
func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string,
servicePrincipalToken, servicePrincipalTokenVault *azure.ServicePrincipalToken) (*AzureClient, error) {
var azureClient = &AzureClient{}
maxlen := getInspectorMaxLength()
azureClient.DeploymentsClient = resources.NewDeploymentsClient(subscriptionID)
azureClient.DeploymentsClient.Authorizer = servicePrincipalToken
azureClient.DeploymentsClient.RequestInspector = withInspection(maxlen)
azureClient.DeploymentsClient.ResponseInspector = byInspecting(maxlen)
azureClient.DeploymentsClient.UserAgent += packerUserAgent
azureClient.GroupsClient = resources.NewGroupsClient(subscriptionID)
azureClient.GroupsClient.Authorizer = servicePrincipalToken
azureClient.GroupsClient.RequestInspector = withInspection(maxlen)
azureClient.GroupsClient.ResponseInspector = byInspecting(maxlen)
azureClient.GroupsClient.UserAgent += packerUserAgent
azureClient.PublicIPAddressesClient = network.NewPublicIPAddressesClient(subscriptionID)
azureClient.PublicIPAddressesClient.Authorizer = servicePrincipalToken
azureClient.PublicIPAddressesClient.RequestInspector = withInspection(maxlen)
azureClient.PublicIPAddressesClient.ResponseInspector = byInspecting(maxlen)
azureClient.PublicIPAddressesClient.UserAgent += packerUserAgent
azureClient.VirtualMachinesClient = compute.NewVirtualMachinesClient(subscriptionID)
azureClient.VirtualMachinesClient.Authorizer = servicePrincipalToken
azureClient.VirtualMachinesClient.RequestInspector = withInspection(maxlen)
azureClient.VirtualMachinesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), templateCapture(azureClient))
azureClient.VirtualMachinesClient.UserAgent += packerUserAgent
azureClient.AccountsClient = armStorage.NewAccountsClient(subscriptionID)
azureClient.AccountsClient.Authorizer = servicePrincipalToken
azureClient.AccountsClient.RequestInspector = withInspection(maxlen)
azureClient.AccountsClient.ResponseInspector = byInspecting(maxlen)
azureClient.AccountsClient.UserAgent += packerUserAgent
azureClient.VaultClient = common.VaultClient{}
azureClient.VaultClient.Authorizer = servicePrincipalTokenVault
azureClient.VaultClient.RequestInspector = withInspection(maxlen)
azureClient.VaultClient.ResponseInspector = byInspecting(maxlen)
azureClient.VaultClient.UserAgent += packerUserAgent
accountKeys, err := azureClient.AccountsClient.ListKeys(resourceGroupName, storageAccountName)
if err != nil {
return nil, err
}
storageClient, err := storage.NewBasicClient(storageAccountName, *accountKeys.Key1)
if err != nil {
return nil, err
}
azureClient.BlobStorageClient = storageClient.GetBlobService()
return azureClient, nil
}
func getInspectorMaxLength() int64 {
value, ok := os.LookupEnv(EnvPackerLogAzureMaxLen)
if !ok {
return math.MaxInt64
}
i, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return 0
}
if i < 0 {
return 0
}
return i
}
+247
View File
@@ -0,0 +1,247 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"errors"
"fmt"
"log"
"time"
packerAzureCommon "github.com/mitchellh/packer/builder/azure/common"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/packer/builder/azure/common/lin"
"github.com/mitchellh/multistep"
packerCommon "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/packer"
)
type Builder struct {
config *Config
stateBag multistep.StateBag
runner multistep.Runner
}
const (
DefaultPublicIPAddressName = "packerPublicIP"
DefaultSasBlobContainer = "system/Microsoft.Compute"
DefaultSasBlobPermission = "r"
DefaultSecretName = "packerKeyVaultSecret"
)
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
c, warnings, errs := newConfig(raws...)
if errs != nil {
return warnings, errs
}
b.config = c
b.stateBag = new(multistep.BasicStateBag)
b.configureStateBag(b.stateBag)
b.setTemplateParameters(b.stateBag)
return warnings, errs
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
ui.Say("Preparing builder ...")
log.Print(":: Configuration")
packerAzureCommon.DumpConfig(b.config, func(s string) { log.Print(s) })
b.stateBag.Put("hook", hook)
b.stateBag.Put(constants.Ui, ui)
spnCloud, spnKeyVault, err := b.getServicePrincipalTokens(ui.Say)
if err != nil {
return nil, err
}
ui.Message("Creating Azure Resource Manager (ARM) client ...")
azureClient, err := NewAzureClient(
b.config.SubscriptionID,
b.config.ResourceGroupName,
b.config.StorageAccount,
spnCloud,
spnKeyVault)
if err != nil {
return nil, err
}
b.config.storageAccountBlobEndpoint, err = b.getBlobEndpoint(azureClient, b.config.ResourceGroupName, b.config.StorageAccount)
if err != nil {
return nil, err
}
b.setTemplateParameters(b.stateBag)
var steps []multistep.Step
if b.config.OSType == constants.Target_Linux {
steps = []multistep.Step{
NewStepCreateResourceGroup(azureClient, ui),
NewStepValidateTemplate(azureClient, ui, Linux),
NewStepDeployTemplate(azureClient, ui, Linux),
NewStepGetIPAddress(azureClient, ui),
&communicator.StepConnectSSH{
Config: &b.config.Comm,
Host: lin.SSHHost,
SSHConfig: lin.SSHConfig(b.config.UserName),
},
&packerCommon.StepProvision{},
NewStepGetOSDisk(azureClient, ui),
NewStepPowerOffCompute(azureClient, ui),
NewStepCaptureImage(azureClient, ui),
NewStepDeleteResourceGroup(azureClient, ui),
NewStepDeleteOSDisk(azureClient, ui),
}
} else if b.config.OSType == constants.Target_Windows {
steps = []multistep.Step{
NewStepCreateResourceGroup(azureClient, ui),
NewStepValidateTemplate(azureClient, ui, KeyVault),
NewStepDeployTemplate(azureClient, ui, KeyVault),
NewStepGetCertificate(azureClient, ui),
NewStepSetCertificate(b.config, ui),
NewStepValidateTemplate(azureClient, ui, Windows),
NewStepDeployTemplate(azureClient, ui, Windows),
NewStepGetIPAddress(azureClient, ui),
&communicator.StepConnectWinRM{
Config: &b.config.Comm,
Host: func(stateBag multistep.StateBag) (string, error) {
return stateBag.Get(constants.SSHHost).(string), nil
},
WinRMConfig: func(multistep.StateBag) (*communicator.WinRMConfig, error) {
return &communicator.WinRMConfig{
Username: b.config.UserName,
Password: b.config.tmpAdminPassword,
}, nil
},
},
&packerCommon.StepProvision{},
NewStepGetOSDisk(azureClient, ui),
NewStepPowerOffCompute(azureClient, ui),
NewStepCaptureImage(azureClient, ui),
NewStepDeleteResourceGroup(azureClient, ui),
NewStepDeleteOSDisk(azureClient, ui),
}
} else {
return nil, fmt.Errorf("Builder does not support the os_type '%s'", b.config.OSType)
}
if b.config.PackerDebug {
ui.Message(fmt.Sprintf("temp admin user: '%s'", b.config.UserName))
ui.Message(fmt.Sprintf("temp admin password: '%s'", b.config.Password))
}
b.runner = b.createRunner(&steps, ui)
b.runner.Run(b.stateBag)
// Report any errors.
if rawErr, ok := b.stateBag.GetOk(constants.Error); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := b.stateBag.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("Build was cancelled.")
}
if _, ok := b.stateBag.GetOk(multistep.StateHalted); ok {
return nil, errors.New("Build was halted.")
}
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()
sasUrl, _ := azureClient.BlobStorageClient.GetBlobSASURI(DefaultSasBlobContainer, name, month, DefaultSasBlobPermission)
return sasUrl
})
}
return &Artifact{}, nil
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}
func (b *Builder) createRunner(steps *[]multistep.Step, ui packer.Ui) multistep.Runner {
if b.config.PackerDebug {
return &multistep.DebugRunner{
Steps: *steps,
PauseFn: packerCommon.MultistepDebugFn(ui),
}
}
return &multistep.BasicRunner{
Steps: *steps,
}
}
func (b *Builder) getBlobEndpoint(client *AzureClient, resourceGroupName string, storageAccountName string) (string, error) {
account, err := client.AccountsClient.GetProperties(resourceGroupName, storageAccountName)
if err != nil {
return "", err
}
return *account.Properties.PrimaryEndpoints.Blob, nil
}
func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
stateBag.Put(constants.AuthorizedKey, b.config.sshAuthorizedKey)
stateBag.Put(constants.PrivateKey, b.config.sshPrivateKey)
stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
stateBag.Put(constants.ArmKeyVaultName, b.config.tmpKeyVaultName)
stateBag.Put(constants.ArmLocation, b.config.Location)
stateBag.Put(constants.ArmPublicIPAddressName, DefaultPublicIPAddressName)
stateBag.Put(constants.ArmResourceGroupName, b.config.tmpResourceGroupName)
stateBag.Put(constants.ArmStorageAccountName, b.config.StorageAccount)
}
func (b *Builder) setTemplateParameters(stateBag multistep.StateBag) {
stateBag.Put(constants.ArmTemplateParameters, b.config.toTemplateParameters())
stateBag.Put(constants.ArmVirtualMachineCaptureParameters, b.config.toVirtualMachineCaptureParameters())
}
func (b *Builder) getServicePrincipalTokens(say func(string)) (*azure.ServicePrincipalToken, *azure.ServicePrincipalToken, error) {
var servicePrincipalToken *azure.ServicePrincipalToken
var servicePrincipalTokenVault *azure.ServicePrincipalToken
var err error
if b.config.useDeviceLogin {
servicePrincipalToken, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.SubscriptionID, say)
if err != nil {
return nil, nil, err
}
} else {
auth := NewAuthenticate(*b.config.cloudEnvironment, b.config.ClientID, b.config.ClientSecret, b.config.TenantID)
servicePrincipalToken, err = auth.getServicePrincipalToken()
if err != nil {
return nil, nil, err
}
servicePrincipalTokenVault, err = auth.getServicePrincipalTokenWithResource(packerAzureCommon.AzureVaultScope)
if err != nil {
return nil, nil, err
}
}
return servicePrincipalToken, servicePrincipalTokenVault, nil
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"github.com/mitchellh/packer/builder/azure/common/constants"
"testing"
)
func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
var testSubject = &Builder{}
_, err := testSubject.Prepare(getArmBuilderConfiguration(), getPackerConfiguration())
if err != nil {
t.Fatalf("failed to prepare: %s", err)
}
var expectedStateBagKeys = []string{
constants.AuthorizedKey,
constants.PrivateKey,
constants.ArmComputeName,
constants.ArmDeploymentName,
constants.ArmLocation,
constants.ArmResourceGroupName,
constants.ArmStorageAccountName,
constants.ArmTemplateParameters,
constants.ArmVirtualMachineCaptureParameters,
constants.ArmPublicIPAddressName,
}
for _, v := range expectedStateBagKeys {
if _, ok := testSubject.stateBag.GetOk(v); ok == false {
t.Errorf("Expected the builder's state bag to contain '%s', but it did not.", v)
}
}
}
+86
View File
@@ -0,0 +1,86 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
type CaptureTemplateParameter struct {
Type string `json:"type"`
DefaultValue string `json:"defaultValue,omitempty"`
}
type CaptureHardwareProfile struct {
VMSize string `json:"vmSize"`
}
type CaptureUri struct {
Uri string `json:"uri"`
}
type CaptureDisk struct {
OSType string `json:"osType"`
Name string `json:"name"`
Image CaptureUri `json:"image"`
Vhd CaptureUri `json:"vhd"`
CreateOption string `json:"createOption"`
Caching string `json:"caching"`
}
type CaptureStorageProfile struct {
OSDisk CaptureDisk `json:"osDisk"`
}
type CaptureOSProfile struct {
ComputerName string `json:"computerName"`
AdminUsername string `json:"adminUsername"`
AdminPassword string `json:"adminPassword"`
}
type CaptureNetworkInterface struct {
Id string `json:"id"`
}
type CaptureNetworkProfile struct {
NetworkInterfaces []CaptureNetworkInterface `json:"networkInterfaces"`
}
type CaptureBootDiagnostics struct {
Enabled bool `json:"enabled"`
}
type CaptureDiagnosticProfile struct {
BootDiagnostics CaptureBootDiagnostics `json:"bootDiagnostics"`
}
type CaptureProperties struct {
HardwareProfile CaptureHardwareProfile `json:"hardwareProfile"`
StorageProfile CaptureStorageProfile `json:"storageProfile"`
OSProfile CaptureOSProfile `json:"osProfile"`
NetworkProfile CaptureNetworkProfile `json:"networkProfile"`
DiagnosticsProfile CaptureDiagnosticProfile `json:"diagnosticsProfile"`
ProvisioningState int `json:"provisioningState"`
}
type CaptureResources struct {
ApiVersion string `json:"apiVersion"`
Name string `json:"name"`
Type string `json:"type"`
Location string `json:"location"`
Properties CaptureProperties `json:"properties"`
}
type CaptureTemplate struct {
Schema string `json:"$schema"`
ContentVersion string `json:"contentVersion"`
Parameters map[string]CaptureTemplateParameter `json:"parameters"`
Resources []CaptureResources `json:"resources"`
}
type CaptureOperationProperties struct {
Output *CaptureTemplate `json:"output"`
}
type CaptureOperation struct {
OperationId string `json:"operationId"`
Status string `json:"status"`
Properties *CaptureOperationProperties `json:"properties"`
}
+213
View File
@@ -0,0 +1,213 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"encoding/json"
"testing"
)
var captureTemplate01 = `{
"operationId": "ac1c7c38-a591-41b3-89bd-ea39fceace1b",
"status": "Succeeded",
"startTime": "2016-04-04T21:07:25.2900874+00:00",
"endTime": "2016-04-04T21:07:26.4776321+00:00",
"properties": {
"output": {
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/VM_IP.json",
"contentVersion": "1.0.0.0",
"parameters": {
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string",
"defaultValue": "Standard_A2"
},
"adminUserName": {
"type": "string"
},
"adminPassword": {
"type": "securestring"
},
"networkInterfaceId": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "2015-06-15",
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"storageProfile": {
"osDisk": {
"osType": "Linux",
"name": "packer-osDisk.32118633-6dc9-449f-83b6-a7d2983bec14.vhd",
"createOption": "FromImage",
"image": {
"uri": "http://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.32118633-6dc9-449f-83b6-a7d2983bec14.vhd"
},
"vhd": {
"uri": "http://storage.blob.core.windows.net/vmcontainerce1a1b75-f480-47cb-8e6e-55142e4a5f68/osDisk.ce1a1b75-f480-47cb-8e6e-55142e4a5f68.vhd"
},
"caching": "ReadWrite"
}
},
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[parameters('networkInterfaceId')]"
}
]
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"provisioningState": 0
},
"name": "[parameters('vmName')]",
"type": "Microsoft.Compute/virtualMachines",
"location": "southcentralus"
}
]
}
}
}`
var captureTemplate02 = `{
"operationId": "ac1c7c38-a591-41b3-89bd-ea39fceace1b",
"status": "Succeeded",
"startTime": "2016-04-04T21:07:25.2900874+00:00",
"endTime": "2016-04-04T21:07:26.4776321+00:00"
}`
func TestCaptureParseJson(t *testing.T) {
var operation CaptureOperation
err := json.Unmarshal([]byte(captureTemplate01), &operation)
if err != nil {
t.Fatalf("failed to the sample capture operation: %s", err)
}
testSubject := operation.Properties.Output
if testSubject.Schema != "http://schema.management.azure.com/schemas/2014-04-01-preview/VM_IP.json" {
t.Errorf("Schema's value was unexpected: %s", testSubject.Schema)
}
if testSubject.ContentVersion != "1.0.0.0" {
t.Errorf("ContentVersion's value was unexpected: %s", testSubject.ContentVersion)
}
// == Parameters ====================================
if len(testSubject.Parameters) != 5 {
t.Fatalf("expected parameters to have 5 keys, but got %d", len(testSubject.Parameters))
}
if _, ok := testSubject.Parameters["vmName"]; !ok {
t.Errorf("Parameters['vmName'] was an expected parameters, but it did not exist")
}
if testSubject.Parameters["vmName"].Type != "string" {
t.Errorf("Parameters['vmName'].Type == 'string', but got '%s'", testSubject.Parameters["vmName"].Type)
}
if _, ok := testSubject.Parameters["vmSize"]; !ok {
t.Errorf("Parameters['vmSize'] was an expected parameters, but it did not exist")
}
if testSubject.Parameters["vmSize"].Type != "string" {
t.Errorf("Parameters['vmSize'].Type == 'string', but got '%s'", testSubject.Parameters["vmSize"])
}
if testSubject.Parameters["vmSize"].DefaultValue != "Standard_A2" {
t.Errorf("Parameters['vmSize'].DefaultValue == 'string', but got '%s'", testSubject.Parameters["vmSize"].DefaultValue)
}
// == Resources =====================================
if len(testSubject.Resources) != 1 {
t.Fatalf("expected resources to have length 1, but got %d", len(testSubject.Resources))
}
if testSubject.Resources[0].Name != "[parameters('vmName')]" {
t.Errorf("Resources[0].Name's value was unexpected: %s", testSubject.Resources[0].Name)
}
if testSubject.Resources[0].Type != "Microsoft.Compute/virtualMachines" {
t.Errorf("Resources[0].Type's value was unexpected: %s", testSubject.Resources[0].Type)
}
if testSubject.Resources[0].Location != "southcentralus" {
t.Errorf("Resources[0].Location's value was unexpected: %s", testSubject.Resources[0].Location)
}
// == Resources/Properties =====================================
if testSubject.Resources[0].Properties.ProvisioningState != 0 {
t.Errorf("Resources[0].Properties.ProvisioningState's value was unexpected: %d", testSubject.Resources[0].Properties.ProvisioningState)
}
// == Resources/Properties/HardwareProfile ======================
hardwareProfile := testSubject.Resources[0].Properties.HardwareProfile
if hardwareProfile.VMSize != "[parameters('vmSize')]" {
t.Errorf("Resources[0].Properties.HardwareProfile.VMSize's value was unexpected: %s", hardwareProfile.VMSize)
}
// == Resources/Properties/StorageProfile/OSDisk ================
osDisk := testSubject.Resources[0].Properties.StorageProfile.OSDisk
if osDisk.OSType != "Linux" {
t.Errorf("Resources[0].Properties.StorageProfile.OSDisk.OSDisk's value was unexpected: %s", osDisk.OSType)
}
if osDisk.Name != "packer-osDisk.32118633-6dc9-449f-83b6-a7d2983bec14.vhd" {
t.Errorf("Resources[0].Properties.StorageProfile.OSDisk.Name's value was unexpected: %s", osDisk.Name)
}
if osDisk.CreateOption != "FromImage" {
t.Errorf("Resources[0].Properties.StorageProfile.OSDisk.CreateOption's value was unexpected: %s", osDisk.CreateOption)
}
if osDisk.Image.Uri != "http://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.32118633-6dc9-449f-83b6-a7d2983bec14.vhd" {
t.Errorf("Resources[0].Properties.StorageProfile.OSDisk.Image.Uri's value was unexpected: %s", osDisk.Image.Uri)
}
if osDisk.Vhd.Uri != "http://storage.blob.core.windows.net/vmcontainerce1a1b75-f480-47cb-8e6e-55142e4a5f68/osDisk.ce1a1b75-f480-47cb-8e6e-55142e4a5f68.vhd" {
t.Errorf("Resources[0].Properties.StorageProfile.OSDisk.Vhd.Uri's value was unexpected: %s", osDisk.Vhd.Uri)
}
if osDisk.Caching != "ReadWrite" {
t.Errorf("Resources[0].Properties.StorageProfile.OSDisk.Caching's value was unexpected: %s", osDisk.Caching)
}
// == Resources/Properties/OSProfile ============================
osProfile := testSubject.Resources[0].Properties.OSProfile
if osProfile.AdminPassword != "[parameters('adminPassword')]" {
t.Errorf("Resources[0].Properties.OSProfile.AdminPassword's value was unexpected: %s", osProfile.AdminPassword)
}
if osProfile.AdminUsername != "[parameters('adminUsername')]" {
t.Errorf("Resources[0].Properties.OSProfile.AdminUsername's value was unexpected: %s", osProfile.AdminUsername)
}
if osProfile.ComputerName != "[parameters('vmName')]" {
t.Errorf("Resources[0].Properties.OSProfile.ComputerName's value was unexpected: %s", osProfile.ComputerName)
}
// == Resources/Properties/NetworkProfile =======================
networkProfile := testSubject.Resources[0].Properties.NetworkProfile
if len(networkProfile.NetworkInterfaces) != 1 {
t.Errorf("Count of Resources[0].Properties.NetworkProfile.NetworkInterfaces was expected to be 1, but go %d", len(networkProfile.NetworkInterfaces))
}
if networkProfile.NetworkInterfaces[0].Id != "[parameters('networkInterfaceId')]" {
t.Errorf("Resources[0].Properties.NetworkProfile.NetworkInterfaces[0].Id's value was unexpected: %s", networkProfile.NetworkInterfaces[0].Id)
}
// == Resources/Properties/DiagnosticsProfile ===================
diagnosticsProfile := testSubject.Resources[0].Properties.DiagnosticsProfile
if diagnosticsProfile.BootDiagnostics.Enabled != false {
t.Errorf("Resources[0].Properties.DiagnosticsProfile.BootDiagnostics.Enabled's value was unexpected: %t", diagnosticsProfile.BootDiagnostics.Enabled)
}
}
func TestCaptureEmptyOperationJson(t *testing.T) {
var operation CaptureOperation
err := json.Unmarshal([]byte(captureTemplate02), &operation)
if err != nil {
t.Fatalf("failed to the sample capture operation: %s", err)
}
if operation.Properties != nil {
t.Errorf("JSON contained no properties, but value was not nil: %+v", operation.Properties)
}
}
+431
View File
@@ -0,0 +1,431 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"time"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/Azure/go-autorest/autorest/to"
"github.com/Azure/go-ntlmssp"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/packer/builder/azure/pkcs12"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
"github.com/Azure/go-autorest/autorest/azure"
"golang.org/x/crypto/ssh"
"strings"
)
const (
DefaultCloudEnvironmentName = "Public"
DefaultImageVersion = "latest"
DefaultUserName = "packer"
DefaultVMSize = "Standard_A1"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
// Authentication via OAUTH
ClientID string `mapstructure:"client_id"`
ClientSecret string `mapstructure:"client_secret"`
ObjectID string `mapstructure:"object_id"`
TenantID string `mapstructure:"tenant_id"`
SubscriptionID string `mapstructure:"subscription_id"`
// Capture
CaptureNamePrefix string `mapstructure:"capture_name_prefix"`
CaptureContainerName string `mapstructure:"capture_container_name"`
// Compute
ImagePublisher string `mapstructure:"image_publisher"`
ImageOffer string `mapstructure:"image_offer"`
ImageSku string `mapstructure:"image_sku"`
ImageVersion string `mapstructure:"image_version"`
Location string `mapstructure:"location"`
VMSize string `mapstructure:"vm_size"`
// Deployment
ResourceGroupName string `mapstructure:"resource_group_name"`
StorageAccount string `mapstructure:"storage_account"`
storageAccountBlobEndpoint string
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
cloudEnvironment *azure.Environment
// OS
OSType string `mapstructure:"os_type"`
// Runtime Values
UserName string
Password string
tmpAdminPassword string
tmpCertificatePassword string
tmpResourceGroupName string
tmpComputeName string
tmpDeploymentName string
tmpKeyVaultName string
tmpOSDiskName string
tmpWinRMCertificateUrl string
useDeviceLogin bool
// 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
}
type keyVaultCertificate struct {
Data string `json:"data"`
DataType string `json:"dataType"`
Password string `json:"password,omitempty"`
}
// If we ever feel the need to support more templates consider moving this
// method to its own factory class.
func (c *Config) toTemplateParameters() *TemplateParameters {
templateParameters := &TemplateParameters{
AdminUsername: &TemplateParameter{c.UserName},
AdminPassword: &TemplateParameter{c.Password},
DnsNameForPublicIP: &TemplateParameter{c.tmpComputeName},
ImageOffer: &TemplateParameter{c.ImageOffer},
ImagePublisher: &TemplateParameter{c.ImagePublisher},
ImageSku: &TemplateParameter{c.ImageSku},
ImageVersion: &TemplateParameter{c.ImageVersion},
OSDiskName: &TemplateParameter{c.tmpOSDiskName},
StorageAccountBlobEndpoint: &TemplateParameter{c.storageAccountBlobEndpoint},
VMSize: &TemplateParameter{c.VMSize},
VMName: &TemplateParameter{c.tmpComputeName},
}
switch c.OSType {
case constants.Target_Linux:
templateParameters.SshAuthorizedKey = &TemplateParameter{c.sshAuthorizedKey}
case constants.Target_Windows:
templateParameters.TenantId = &TemplateParameter{c.TenantID}
templateParameters.ObjectId = &TemplateParameter{c.ObjectID}
templateParameters.KeyVaultName = &TemplateParameter{c.tmpKeyVaultName}
templateParameters.KeyVaultSecretValue = &TemplateParameter{c.winrmCertificate}
templateParameters.WinRMCertificateUrl = &TemplateParameter{c.tmpWinRMCertificateUrl}
}
return templateParameters
}
func (c *Config) toVirtualMachineCaptureParameters() *compute.VirtualMachineCaptureParameters {
return &compute.VirtualMachineCaptureParameters{
DestinationContainerName: &c.CaptureContainerName,
VhdPrefix: &c.CaptureNamePrefix,
OverwriteVhds: to.BoolPtr(false),
}
}
func (c *Config) createCertificate() (string, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
err := fmt.Errorf("Failed to Generate Private Key: %s", err)
return "", err
}
host := fmt.Sprintf("%s.cloudapp.net", c.tmpComputeName)
notBefore := time.Now()
notAfter := notBefore.Add(365 * 24 * time.Hour)
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
err := fmt.Errorf("Failed to Generate Serial Number: %v", err)
return "", err
}
template := x509.Certificate{
SerialNumber: serialNumber,
Issuer: pkix.Name{
CommonName: host,
},
Subject: pkix.Name{
CommonName: host,
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err != nil {
err = fmt.Errorf("Failed to Create Certificate: %s", err)
return "", err
}
pfxBytes, err := pkcs12.Encode(derBytes, privateKey, c.tmpCertificatePassword)
if err != nil {
err = fmt.Errorf("Failed to encode certificate as PFX: %s", err)
return "", err
}
keyVaultDescription := keyVaultCertificate{
Data: base64.StdEncoding.EncodeToString(pfxBytes),
DataType: "pfx",
Password: c.tmpCertificatePassword,
}
bytes, err := json.Marshal(keyVaultDescription)
if err != nil {
err = fmt.Errorf("Failed to marshal key vault description: %s", err)
return "", err
}
return base64.StdEncoding.EncodeToString(bytes), nil
}
func newConfig(raws ...interface{}) (*Config, []string, error) {
var c Config
err := config.Decode(&c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: c.ctx,
}, raws...)
if err != nil {
return nil, nil, err
}
provideDefaultValues(&c)
setRuntimeValues(&c)
setUserNamePassword(&c)
err = setCloudEnvironment(&c)
if err != nil {
return nil, nil, err
}
err = setSshValues(&c)
if err != nil {
return nil, nil, err
}
err = setWinRMCertificate(&c)
if err != nil {
return nil, nil, err
}
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(c.ctx)...)
assertRequiredParametersSet(&c, errs)
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}
return &c, nil, nil
}
func setSshValues(c *Config) error {
if c.Comm.SSHTimeout == 0 {
c.Comm.SSHTimeout = 20 * time.Minute
}
if c.Comm.SSHPrivateKey != "" {
privateKeyBytes, err := ioutil.ReadFile(c.Comm.SSHPrivateKey)
if err != nil {
panic(err)
}
signer, err := ssh.ParsePrivateKey(privateKeyBytes)
if err != nil {
panic(err)
}
publicKey := signer.PublicKey()
c.sshAuthorizedKey = fmt.Sprintf("%s %s packer Azure Deployment%s",
publicKey.Type(),
base64.StdEncoding.EncodeToString(publicKey.Marshal()),
time.Now().Format(time.RFC3339))
c.sshPrivateKey = string(privateKeyBytes)
} else {
sshKeyPair, err := NewOpenSshKeyPair()
if err != nil {
return err
}
c.sshAuthorizedKey = sshKeyPair.AuthorizedKey()
c.sshPrivateKey = sshKeyPair.PrivateKey()
}
c.Comm.WinRMTransportDecorator = func(t *http.Transport) http.RoundTripper {
return &ntlmssp.Negotiator{RoundTripper: t}
}
return nil
}
func setWinRMCertificate(c *Config) error {
cert, err := c.createCertificate()
c.winrmCertificate = cert
return err
}
func setRuntimeValues(c *Config) {
var tempName = NewTempName()
c.tmpAdminPassword = tempName.AdminPassword
c.tmpCertificatePassword = tempName.CertificatePassword
c.tmpComputeName = tempName.ComputeName
c.tmpDeploymentName = tempName.DeploymentName
c.tmpResourceGroupName = tempName.ResourceGroupName
c.tmpOSDiskName = tempName.OSDiskName
c.tmpKeyVaultName = tempName.KeyVaultName
}
func setUserNamePassword(c *Config) {
if c.Comm.SSHUsername == "" {
c.Comm.SSHUsername = DefaultUserName
}
c.UserName = c.Comm.SSHUsername
if c.Comm.SSHPassword != "" {
c.Password = c.Comm.SSHPassword
} else {
c.Password = c.tmpAdminPassword
}
}
func setCloudEnvironment(c *Config) error {
name := strings.ToUpper(c.CloudEnvironmentName)
switch name {
case "CHINA", "CHINACLOUD", "AZURECHINACLOUD":
c.cloudEnvironment = &azure.ChinaCloud
case "PUBLIC", "PUBLICCLOUD", "AZUREPUBLICCLOUD":
c.cloudEnvironment = &azure.PublicCloud
case "USGOVERNMENT", "USGOVERNMENTCLOUD", "AZUREUSGOVERNMENTCLOUD":
c.cloudEnvironment = &azure.USGovernmentCloud
default:
return fmt.Errorf("There is no cloud envionment matching the name '%s'!", c.CloudEnvironmentName)
}
return nil
}
func provideDefaultValues(c *Config) {
if c.VMSize == "" {
c.VMSize = DefaultVMSize
}
if c.ImageVersion == "" {
c.ImageVersion = DefaultImageVersion
}
if c.CloudEnvironmentName == "" {
c.CloudEnvironmentName = DefaultCloudEnvironmentName
}
}
func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
/////////////////////////////////////////////
// Authentication via OAUTH
// Check if device login is being asked for, and is allowed.
//
// Device login is enabled if the user only defines SubscriptionID and not
// ClientID, ClientSecret, and TenantID.
//
// Device login is not enabled for Windows because the WinRM certificate is
// 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 == "" &&
c.ClientSecret == "" &&
c.TenantID == ""
}
if isUseDeviceLogin(c) {
c.useDeviceLogin = true
} else {
if c.ClientID == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_id must be specified"))
}
if c.ClientSecret == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_secret must be specified"))
}
if c.TenantID == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A tenant_id must be specified"))
}
if c.SubscriptionID == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A subscription_id must be specified"))
}
}
/////////////////////////////////////////////
// Capture
if c.CaptureContainerName == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An capture_container_name must be specified"))
}
if c.CaptureNamePrefix == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An capture_name_prefix must be specified"))
}
/////////////////////////////////////////////
// Compute
if c.ImagePublisher == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A image_publisher must be specified"))
}
if c.ImageOffer == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A image_offer must be specified"))
}
if c.ImageSku == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A image_sku must be specified"))
}
if c.Location == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A location must be specified"))
}
/////////////////////////////////////////////
// Deployment
if c.StorageAccount == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A storage_account must be specified"))
}
/////////////////////////////////////////////
// OS
if c.OSType != constants.Target_Linux && c.OSType != constants.Target_Windows {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An os_type must be specified"))
}
}
+419
View File
@@ -0,0 +1,419 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"encoding/json"
"fmt"
"strings"
"testing"
"time"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/packer/packer"
)
// List of configuration parameters that are required by the ARM builder.
var requiredConfigValues = []string{
"capture_name_prefix",
"capture_container_name",
"client_id",
"client_secret",
"image_offer",
"image_publisher",
"image_sku",
"location",
"os_type",
"storage_account",
"subscription_id",
"tenant_id",
}
func TestConfigShouldProvideReasonableDefaultValues(t *testing.T) {
c, _, err := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
if err != nil {
t.Errorf("Expected configuration creation to succeed, but it failed!\n")
t.Fatalf(" errors: %s\n", err)
}
if c.UserName == "" {
t.Errorf("Expected 'UserName' to be populated, but it was empty!")
}
if c.VMSize == "" {
t.Errorf("Expected 'VMSize' to be populated, but it was empty!")
}
if c.ObjectID != "" {
t.Errorf("Expected 'ObjectID' to be nil, but it was '%s'!", c.ObjectID)
}
}
func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) {
builderValues := getArmBuilderConfiguration()
builderValues["ssh_password"] = "override_password"
builderValues["ssh_username"] = "override_username"
builderValues["vm_size"] = "override_vm_size"
c, _, err := newConfig(builderValues, getPackerConfiguration())
if err != nil {
t.Fatalf("newConfig failed: %s", err)
}
if c.Password != "override_password" {
t.Errorf("Expected 'Password' to be set to 'override_password', but found '%s'!", c.Password)
}
if c.Comm.SSHPassword != "override_password" {
t.Errorf("Expected 'c.Comm.SSHPassword' to be set to 'override_password', but found '%s'!", c.Comm.SSHPassword)
}
if c.UserName != "override_username" {
t.Errorf("Expected 'UserName' to be set to 'override_username', but found '%s'!", c.UserName)
}
if c.Comm.SSHUsername != "override_username" {
t.Errorf("Expected 'c.Comm.SSHUsername' to be set to 'override_username', but found '%s'!", c.Comm.SSHUsername)
}
if c.VMSize != "override_vm_size" {
t.Errorf("Expected 'vm_size' to be set to 'override_vm_size', but found '%s'!", c.VMSize)
}
}
func TestConfigShouldDefaultVMSizeToStandardA1(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
if c.VMSize != "Standard_A1" {
t.Errorf("Expected 'VMSize' to default to 'Standard_A1', but got '%s'.", c.VMSize)
}
}
func TestConfigShouldDefaultImageVersionToLatest(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
if c.ImageVersion != "latest" {
t.Errorf("Expected 'ImageVersion' to default to 'latest', but got '%s'.", c.ImageVersion)
}
}
func TestConfigShouldDefaultToPublicCloud(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
if c.CloudEnvironmentName != "Public" {
t.Errorf("Expected 'CloudEnvironmentName' to default to 'Public', but got '%s'.", c.CloudEnvironmentName)
}
if c.cloudEnvironment == nil || c.cloudEnvironment.Name != "AzurePublicCloud" {
t.Errorf("Expected 'cloudEnvironment' to be set to 'AzurePublicCloud', but got '%s'.", c.cloudEnvironment)
}
}
func TestConfigInstantiatesCorrectAzureEnvironment(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",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
}
// user input is fun :)
var table = []struct {
name string
environmentName string
}{
{"China", "AzureChinaCloud"},
{"ChinaCloud", "AzureChinaCloud"},
{"AzureChinaCloud", "AzureChinaCloud"},
{"aZuReChInAcLoUd", "AzureChinaCloud"},
{"USGovernment", "AzureUSGovernmentCloud"},
{"USGovernmentCloud", "AzureUSGovernmentCloud"},
{"AzureUSGovernmentCloud", "AzureUSGovernmentCloud"},
{"aZuReUsGoVeRnMeNtClOuD", "AzureUSGovernmentCloud"},
{"Public", "AzurePublicCloud"},
{"PublicCloud", "AzurePublicCloud"},
{"AzurePublicCloud", "AzurePublicCloud"},
{"aZuRePuBlIcClOuD", "AzurePublicCloud"},
}
packerConfiguration := getPackerConfiguration()
for _, x := range table {
config["cloud_environment_name"] = x.name
c, _, _ := newConfig(config, packerConfiguration)
if c.cloudEnvironment == nil || c.cloudEnvironment.Name != x.environmentName {
t.Errorf("Expected 'cloudEnvironment' to be set to '%s', but got '%s'.", x.environmentName, c.cloudEnvironment)
}
}
}
func TestUserShouldProvideRequiredValues(t *testing.T) {
builderValues := getArmBuilderConfiguration()
// Ensure we can successfully create a config.
_, _, err := newConfig(builderValues, getPackerConfiguration())
if err != nil {
t.Errorf("Expected configuration creation to succeed, but it failed!\n")
t.Fatalf(" -> %+v\n", builderValues)
}
// Take away a required element, and ensure construction fails.
for _, v := range requiredConfigValues {
originalValue := builderValues[v]
delete(builderValues, v)
_, _, err := newConfig(builderValues, getPackerConfiguration())
if err == nil {
t.Errorf("Expected configuration creation to fail, but it succeeded!\n")
t.Fatalf(" -> %+v\n", builderValues)
}
builderValues[v] = originalValue
}
}
func TestSystemShouldDefineRuntimeValues(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
if c.Password == "" {
t.Errorf("Expected Password to not be empty, but it was '%s'!", c.Password)
}
if c.tmpComputeName == "" {
t.Errorf("Expected tmpComputeName to not be empty, but it was '%s'!", c.tmpComputeName)
}
if c.tmpDeploymentName == "" {
t.Errorf("Expected tmpDeploymentName to not be empty, but it was '%s'!", c.tmpDeploymentName)
}
if c.tmpResourceGroupName == "" {
t.Errorf("Expected tmpResourceGroupName to not be empty, but it was '%s'!", c.tmpResourceGroupName)
}
if c.tmpOSDiskName == "" {
t.Errorf("Expected tmpOSDiskName to not be empty, but it was '%s'!", c.tmpOSDiskName)
}
}
func TestConfigShouldTransformToTemplateParameters(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
templateParameters := c.toTemplateParameters()
if templateParameters.AdminUsername.Value != c.UserName {
t.Errorf("Expected AdminUsername to be equal to config's AdminUsername, but they were '%s' and '%s' respectively.", templateParameters.AdminUsername.Value, c.UserName)
}
if templateParameters.DnsNameForPublicIP.Value != c.tmpComputeName {
t.Errorf("Expected DnsNameForPublicIP to be equal to config's DnsNameForPublicIP, but they were '%s' and '%s' respectively.", templateParameters.DnsNameForPublicIP.Value, c.tmpComputeName)
}
if templateParameters.ImageOffer.Value != c.ImageOffer {
t.Errorf("Expected ImageOffer to be equal to config's ImageOffer, but they were '%s' and '%s' respectively.", templateParameters.ImageOffer.Value, c.ImageOffer)
}
if templateParameters.ImagePublisher.Value != c.ImagePublisher {
t.Errorf("Expected ImagePublisher to be equal to config's ImagePublisher, but they were '%s' and '%s' respectively.", templateParameters.ImagePublisher.Value, c.ImagePublisher)
}
if templateParameters.ImageSku.Value != c.ImageSku {
t.Errorf("Expected ImageSku to be equal to config's ImageSku, but they were '%s' and '%s' respectively.", templateParameters.ImageSku.Value, c.ImageSku)
}
if templateParameters.OSDiskName.Value != c.tmpOSDiskName {
t.Errorf("Expected OSDiskName to be equal to config's OSDiskName, but they were '%s' and '%s' respectively.", templateParameters.OSDiskName.Value, c.tmpOSDiskName)
}
if templateParameters.StorageAccountBlobEndpoint.Value != c.storageAccountBlobEndpoint {
t.Errorf("Expected StorageAccountBlobEndpoint to be equal to config's storageAccountBlobEndpoint, but they were '%s' and '%s' respectively.", templateParameters.StorageAccountBlobEndpoint.Value, c.storageAccountBlobEndpoint)
}
if templateParameters.VMName.Value != c.tmpComputeName {
t.Errorf("Expected VMName to be equal to config's VMName, but they were '%s' and '%s' respectively.", templateParameters.VMName.Value, c.tmpComputeName)
}
if templateParameters.VMSize.Value != c.VMSize {
t.Errorf("Expected VMSize to be equal to config's VMSize, but they were '%s' and '%s' respectively.", templateParameters.VMSize.Value, c.VMSize)
}
}
func TestConfigShouldTransformToTemplateParametersLinux(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
c.OSType = constants.Target_Linux
templateParameters := c.toTemplateParameters()
if templateParameters.KeyVaultSecretValue != nil {
t.Errorf("Expected KeyVaultSecretValue to be empty for an os_type == '%s', but it was not.", c.OSType)
}
if templateParameters.ObjectId != nil {
t.Errorf("Expected ObjectId to be empty for an os_type == '%s', but it was not.", c.OSType)
}
if templateParameters.TenantId != nil {
t.Errorf("Expected TenantId to be empty for an os_type == '%s', but it was not.", c.OSType)
}
}
func TestConfigShouldTransformToTemplateParametersWindows(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
c.OSType = constants.Target_Windows
templateParameters := c.toTemplateParameters()
if templateParameters.SshAuthorizedKey != nil {
t.Errorf("Expected SshAuthorizedKey to be empty for an os_type == '%s', but it was not.", c.OSType)
}
if templateParameters.KeyVaultName == nil {
t.Errorf("Expected KeyVaultName to not be empty for an os_type == '%s', but it was not.", c.OSType)
}
if templateParameters.KeyVaultSecretValue == nil {
t.Errorf("Expected KeyVaultSecretValue to not be empty for an os_type == '%s', but it was not.", c.OSType)
}
if templateParameters.ObjectId == nil {
t.Errorf("Expected ObjectId to not be empty for an os_type == '%s', but it was not.", c.OSType)
}
if templateParameters.TenantId == nil {
t.Errorf("Expected TenantId to not be empty for an os_type == '%s', but it was not.", c.OSType)
}
}
func TestConfigShouldTransformToVirtualMachineCaptureParameters(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
parameters := c.toVirtualMachineCaptureParameters()
if *parameters.DestinationContainerName != c.CaptureContainerName {
t.Errorf("Expected DestinationContainerName to be equal to config's CaptureContainerName, but they were '%s' and '%s' respectively.", *parameters.DestinationContainerName, c.CaptureContainerName)
}
if *parameters.VhdPrefix != c.CaptureNamePrefix {
t.Errorf("Expected DestinationContainerName to be equal to config's CaptureContainerName, but they were '%s' and '%s' respectively.", *parameters.VhdPrefix, c.CaptureNamePrefix)
}
if *parameters.OverwriteVhds != false {
t.Error("Expected OverwriteVhds to be false, but it was not.")
}
}
func TestConfigShouldSupportPackersConfigElements(t *testing.T) {
c, _, err := newConfig(
getArmBuilderConfiguration(),
getPackerConfiguration(),
getPackerCommunicatorConfiguration())
if err != nil {
t.Fatal(err)
}
if c.Comm.SSHTimeout != 1*time.Hour {
t.Errorf("Expected Comm.SSHTimeout to be a duration of an hour, but got '%s' instead.", c.Comm.SSHTimeout)
}
if c.Comm.WinRMTimeout != 2*time.Hour {
t.Errorf("Expected Comm.WinRMTimeout to be a durationof two hours, but got '%s' instead.", c.Comm.WinRMTimeout)
}
}
func TestUserDeviceLoginIsEnabledForLinux(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",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
}
_, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatalf("failed to use device login for Linux: %s", err)
}
}
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",
"subscription_id": "ignore",
"os_type": constants.Target_Windows,
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Fatalf("Expected test to fail, but it succeeded")
}
multiError, _ := err.(*packer.MultiError)
if len(multiError.Errors) != 3 {
t.Errorf("Expected to find 3 errors, but found %d errors", len(multiError.Errors))
}
if !strings.Contains(err.Error(), "client_id must be specified") {
t.Errorf("Expected to find error for 'client_id must be specified")
}
if !strings.Contains(err.Error(), "client_secret must be specified") {
t.Errorf("Expected to find error for 'client_secret must be specified")
}
if !strings.Contains(err.Error(), "tenant_id must be specified") {
t.Errorf("Expected to find error for 'tenant_id must be specified")
}
}
func getArmBuilderConfiguration() map[string]string {
m := make(map[string]string)
for _, v := range requiredConfigValues {
m[v] = fmt.Sprintf("%s00", v)
}
m["os_type"] = constants.Target_Linux
return m
}
func getPackerConfiguration() interface{} {
var doc = `{
"packer_user_variables": {
"sa": "my_storage_account"
},
"packer_build_name": "azure-arm-vm",
"packer_builder_type": "azure-arm-vm",
"packer_debug": "false",
"packer_force": "false",
"packer_template_path": "/home/jenkins/azure-arm-vm/template.json"
}`
var config interface{}
json.Unmarshal([]byte(doc), &config)
return config
}
func getPackerCommunicatorConfiguration() map[string]string {
config := map[string]string{
"ssh_timeout": "1h",
"winrm_timeout": "2h",
}
return config
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"encoding/json"
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
)
type DeploymentFactory struct {
template string
}
func newDeploymentFactory(template string) DeploymentFactory {
return DeploymentFactory{
template: template,
}
}
func (f *DeploymentFactory) create(templateParameters TemplateParameters) (*resources.Deployment, error) {
template, err := f.getTemplate(templateParameters)
if err != nil {
return nil, err
}
parameters, err := f.getTemplateParameters(templateParameters)
if err != nil {
return nil, err
}
return &resources.Deployment{
Properties: &resources.DeploymentProperties{
Mode: resources.Incremental,
Template: template,
Parameters: parameters,
},
}, nil
}
func (f *DeploymentFactory) getTemplate(templateParameters TemplateParameters) (*map[string]interface{}, error) {
var t map[string]interface{}
err := json.Unmarshal([]byte(f.template), &t)
if err != nil {
return nil, err
}
return &t, nil
}
func (f *DeploymentFactory) getTemplateParameters(templateParameters TemplateParameters) (*map[string]interface{}, error) {
b, err := json.Marshal(templateParameters)
if err != nil {
return nil, err
}
var t map[string]interface{}
err = json.Unmarshal(b, &t)
if err != nil {
return nil, err
}
return &t, nil
}
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"testing"
)
func TestDeploymentFactoryShouldBeIncremental(t *testing.T) {
var testSubject = newDeploymentFactory(Linux)
deployment, err := testSubject.create(getTemplateParameters())
if err != nil {
t.Fatalf("ERROR: %s\n", err)
}
if deployment.Properties.Mode != "Incremental" {
t.Fatalf("Expected the mode to be 'Incremental', but got '%s'.", deployment.Properties.Mode)
}
}
// Either {Template,Parameter} are set or {Template,Parameter}Link values are
// set, but never both.
func TestDeploymentFactoryShouldNotSetLinks(t *testing.T) {
testSubject := newDeploymentFactory(Linux)
deployment, err := testSubject.create(getTemplateParameters())
if err != nil {
t.Fatalf("ERROR: %s\n", err)
}
if deployment.Properties.ParametersLink != nil {
t.Fatalf("Expected the ParametersLink to be nil!")
}
if deployment.Properties.TemplateLink != nil {
t.Fatalf("Expected the TemplateLink to be nil!")
}
if deployment.Properties.Parameters == nil {
t.Fatalf("Expected the Parameters to not be nil!")
}
if deployment.Properties.Template == nil {
t.Fatalf("Expected the Template to not be nil!")
}
}
func TestFactoryShouldCreateDeploymentInstance(t *testing.T) {
testSubject := newDeploymentFactory(Linux)
deployment, err := testSubject.create(getTemplateParameters())
if err != nil {
t.Fatalf("ERROR: %s\n", err)
}
// spot check well known values to ensure correct serialization.
parametersMap := *deployment.Properties.Parameters
if _, ok := parametersMap["adminUsername"]; ok == false {
t.Fatalf("Expected the parameter value 'adminUsername' to be set, but it was not")
}
templateMap := *deployment.Properties.Template
if _, ok := templateMap["contentVersion"]; ok == false {
t.Fatalf("Expected the parameter value 'contentVersion' to be set, but it was not")
}
}
func TestMalformedTemplatesShouldReturnError(t *testing.T) {
testSubject := newDeploymentFactory("")
_, err := testSubject.create(getTemplateParameters())
if err == nil {
t.Fatalf("Expected an error, but did not receive one!\n")
}
}
func getTemplateParameters() TemplateParameters {
templateParameters := TemplateParameters{
AdminUsername: &TemplateParameter{"adminusername00"},
DnsNameForPublicIP: &TemplateParameter{"dnsnameforpublicip00"},
OSDiskName: &TemplateParameter{"osdiskname00"},
SshAuthorizedKey: &TemplateParameter{"sshkeydata00"},
StorageAccountBlobEndpoint: &TemplateParameter{"storageaccountblobendpoint00"},
VMName: &TemplateParameter{"vmname00"},
VMSize: &TemplateParameter{"vmsize00"},
}
return templateParameters
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"time"
)
const (
DeployCanceled = "Canceled"
DeployFailed = "Failed"
DeployDeleted = "Deleted"
DeploySucceeded = "Succeeded"
)
type DeploymentPoller struct {
getProvisioningState func() (string, error)
pause func()
}
func NewDeploymentPoller(getProvisioningState func() (string, error)) *DeploymentPoller {
pollDuration := time.Second * 15
return &DeploymentPoller{
getProvisioningState: getProvisioningState,
pause: func() { time.Sleep(pollDuration) },
}
}
func (t *DeploymentPoller) PollAsNeeded() (string, error) {
for {
res, err := t.getProvisioningState()
if err != nil {
return res, err
}
switch res {
case DeployCanceled, DeployDeleted, DeployFailed, DeploySucceeded:
return res, nil
default:
break
}
t.pause()
}
}
+108
View File
@@ -0,0 +1,108 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"testing"
)
func TestCanceledShouldImmediatelyStopPolling(t *testing.T) {
var testSubject = NewDeploymentPoller(func() (string, error) { return "Canceled", nil })
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
res, err := testSubject.PollAsNeeded()
if err != nil {
t.Errorf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
}
if res != "Canceled" {
t.Fatalf("Expected PollAsNeeded to return a result of 'Canceled', but got '%s' instead.", res)
}
}
func TestFailedShouldImmediatelyStopPolling(t *testing.T) {
var testSubject = NewDeploymentPoller(func() (string, error) { return "Failed", nil })
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
res, err := testSubject.PollAsNeeded()
if err != nil {
t.Fatalf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
}
if res != "Failed" {
t.Fatalf("Expected PollAsNeeded to return a result of 'Failed', but got '%s' instead.", res)
}
}
func TestDeletedShouldImmediatelyStopPolling(t *testing.T) {
var testSubject = NewDeploymentPoller(func() (string, error) { return "Deleted", nil })
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
res, err := testSubject.PollAsNeeded()
if err != nil {
t.Fatalf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
}
if res != "Deleted" {
t.Fatalf("Expected PollAsNeeded to return a result of 'Deleted', but got '%s' instead.", res)
}
}
func TestSucceededShouldImmediatelyStopPolling(t *testing.T) {
var testSubject = NewDeploymentPoller(func() (string, error) { return "Succeeded", nil })
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
res, err := testSubject.PollAsNeeded()
if err != nil {
t.Fatalf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
}
if res != "Succeeded" {
t.Fatalf("Expected PollAsNeeded to return a result of 'Succeeded', but got '%s' instead.", res)
}
}
func TestPollerShouldPollOnNonStoppingStatus(t *testing.T) {
count := 0
var testSubject = NewDeploymentPoller(func() (string, error) { return "Succeeded", nil })
testSubject.pause = func() { count += 1 }
testSubject.getProvisioningState = func() (string, error) {
count += 1
switch count {
case 0, 1:
return "Working", nil
default:
return "Succeeded", nil
}
}
res, err := testSubject.PollAsNeeded()
if err != nil {
t.Fatalf("Expected PollAsNeeded to not return an error, but got '%s'.", err)
}
if res != "Succeeded" {
t.Fatalf("Expected PollAsNeeded to return a result of 'Succeeded', but got '%s' instead.", res)
}
if count != 3 {
t.Fatal("Expected DeploymentPoller to poll until 'Succeeded', but it did not.")
}
}
func TestPollerShouldReturnErrorImmediately(t *testing.T) {
var testSubject = NewDeploymentPoller(func() (string, error) { return "bad-bad-bad", fmt.Errorf("BOOM") })
testSubject.pause = func() { t.Fatal("Did not expect this to be called!") }
res, err := testSubject.PollAsNeeded()
if err == nil {
t.Fatal("Expected PollAsNeeded to return an error, but it did not.")
}
if res != "bad-bad-bad" {
t.Fatalf("Expected PollAsNeeded to return a result of 'bad-bad-bad', but got '%s' instead.", res)
}
}
+70
View File
@@ -0,0 +1,70 @@
package arm
import (
"bytes"
"io/ioutil"
"log"
"net/http"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/mitchellh/packer/builder/azure/common/logutil"
"io"
)
func chop(data []byte, maxlen int64) string {
s := string(data)
if int64(len(s)) > maxlen {
s = s[:maxlen] + "..."
}
return s
}
func handleBody(body io.ReadCloser, maxlen int64) (io.ReadCloser, string) {
if body == nil {
return nil, ""
}
defer body.Close()
b, err := ioutil.ReadAll(body)
if err != nil {
return nil, ""
}
return ioutil.NopCloser(bytes.NewReader(b)), chop(b, maxlen)
}
func withInspection(maxlen int64) autorest.PrepareDecorator {
return func(p autorest.Preparer) autorest.Preparer {
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
body, bodyString := handleBody(r.Body, maxlen)
r.Body = body
log.Print("Azure request", logutil.Fields{
"method": r.Method,
"request": r.URL.String(),
"body": bodyString,
})
return p.Prepare(r)
})
}
}
func byInspecting(maxlen int64) autorest.RespondDecorator {
return func(r autorest.Responder) autorest.Responder {
return autorest.ResponderFunc(func(resp *http.Response) error {
body, bodyString := handleBody(resp.Body, maxlen)
resp.Body = body
log.Print("Azure response", logutil.Fields{
"status": resp.Status,
"method": resp.Request.Method,
"request": resp.Request.URL.String(),
"x-ms-request-id": azure.ExtractRequestID(resp),
"body": bodyString,
})
return r.Respond(resp)
})
}
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"golang.org/x/crypto/ssh"
"time"
)
const (
KeySize = 2048
)
type OpenSshKeyPair struct {
privateKey *rsa.PrivateKey
publicKey ssh.PublicKey
}
func NewOpenSshKeyPair() (*OpenSshKeyPair, error) {
return NewOpenSshKeyPairWithSize(KeySize)
}
func NewOpenSshKeyPairWithSize(keySize int) (*OpenSshKeyPair, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
if err != nil {
return nil, err
}
publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
if err != nil {
return nil, err
}
return &OpenSshKeyPair{
privateKey: privateKey,
publicKey: publicKey,
}, nil
}
func (s *OpenSshKeyPair) AuthorizedKey() string {
return fmt.Sprintf("%s %s packer Azure Deployment%s",
s.publicKey.Type(),
base64.StdEncoding.EncodeToString(s.publicKey.Marshal()),
time.Now().Format(time.RFC3339))
}
func (s *OpenSshKeyPair) PrivateKey() string {
privateKey := string(pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(s.privateKey),
}))
return privateKey
}
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"golang.org/x/crypto/ssh"
"testing"
)
func TestFart(t *testing.T) {
}
func TestAuthorizedKeyShouldParse(t *testing.T) {
testSubject, err := NewOpenSshKeyPairWithSize(512)
if err != nil {
t.Fatalf("Failed to create a new OpenSSH key pair, err=%s.", err)
}
authorizedKey := testSubject.AuthorizedKey()
_, _, _, _, err = ssh.ParseAuthorizedKey([]byte(authorizedKey))
if err != nil {
t.Fatalf("Failed to parse the authorized key, err=%s", err)
}
}
func TestPrivateKeyShouldParse(t *testing.T) {
testSubject, err := NewOpenSshKeyPairWithSize(512)
if err != nil {
t.Fatalf("Failed to create a new OpenSSH key pair, err=%s.", err)
}
_, err = ssh.ParsePrivateKey([]byte(testSubject.PrivateKey()))
if err != nil {
t.Fatalf("Failed to parse the private key, err=%s\n", err)
}
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"github.com/mitchellh/packer/builder/azure/common"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/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 {
if err != nil {
state.Put(constants.Error, err)
sayError(err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
+81
View File
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/mitchellh/packer/builder/azure/common"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepCaptureImage struct {
client *AzureClient
capture func(resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters, cancelCh <-chan struct{}) error
get func(client *AzureClient) *CaptureTemplate
say func(message string)
error func(e error)
}
func NewStepCaptureImage(client *AzureClient, ui packer.Ui) *StepCaptureImage {
var step = &StepCaptureImage{
client: client,
get: func(client *AzureClient) *CaptureTemplate { return client.Template },
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.capture = step.captureImage
return step
}
func (s *StepCaptureImage) captureImage(resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters, cancelCh <-chan struct{}) error {
_, err := s.client.Generalize(resourceGroupName, computeName)
if err != nil {
return err
}
_, err = s.client.Capture(resourceGroupName, computeName, *parameters, cancelCh)
if err != nil {
return err
}
return nil
}
func (s *StepCaptureImage) Run(state multistep.StateBag) multistep.StepAction {
s.say("Capturing image ...")
var computeName = state.Get(constants.ArmComputeName).(string)
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var parameters = state.Get(constants.ArmVirtualMachineCaptureParameters).(*compute.VirtualMachineCaptureParameters)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> ComputeName : '%s'", computeName))
result := common.StartInterruptibleTask(
func() bool { return common.IsStateCancelled(state) },
func(cancelCh <-chan struct{}) error {
return s.capture(resourceGroupName, computeName, parameters, cancelCh)
})
// HACK(chrboum): I do not like this. The capture method should be returning this value
// 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.
template := s.get(s.client)
state.Put(constants.ArmCaptureTemplate, template)
return processInterruptibleResult(result, s.error, state)
}
func (*StepCaptureImage) Cleanup(multistep.StateBag) {
}
@@ -0,0 +1,124 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"testing"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepCaptureImageShouldFailIfCaptureFails(t *testing.T) {
var testSubject = &StepCaptureImage{
capture: func(string, string, *compute.VirtualMachineCaptureParameters, <-chan struct{}) error {
return fmt.Errorf("!! Unit Test FAIL !!")
},
get: func(client *AzureClient) *CaptureTemplate {
return nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepCaptureImage()
var result = testSubject.Run(stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
func TestStepCaptureImageShouldPassIfCapturePasses(t *testing.T) {
var testSubject = &StepCaptureImage{
capture: func(string, string, *compute.VirtualMachineCaptureParameters, <-chan struct{}) error { return nil },
get: func(client *AzureClient) *CaptureTemplate {
return nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepCaptureImage()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepCaptureImageShouldTakeStepArgumentsFromStateBag(t *testing.T) {
cancelCh := make(chan<- struct{})
defer close(cancelCh)
var actualResourceGroupName string
var actualComputeName string
var actualVirtualMachineCaptureParameters *compute.VirtualMachineCaptureParameters
actualCaptureTemplate := &CaptureTemplate{
Schema: "!! Unit Test !!",
}
var testSubject = &StepCaptureImage{
capture: func(resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters, cancelCh <-chan struct{}) error {
actualResourceGroupName = resourceGroupName
actualComputeName = computeName
actualVirtualMachineCaptureParameters = parameters
return nil
},
get: func(client *AzureClient) *CaptureTemplate {
return actualCaptureTemplate
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepCaptureImage()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
var expectedComputeName = stateBag.Get(constants.ArmComputeName).(string)
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
var expectedVirtualMachineCaptureParameters = stateBag.Get(constants.ArmVirtualMachineCaptureParameters).(*compute.VirtualMachineCaptureParameters)
var expectedCaptureTemplate = stateBag.Get(constants.ArmCaptureTemplate).(*CaptureTemplate)
if actualComputeName != expectedComputeName {
t.Fatalf("Expected StepCaptureImage to source 'constants.ArmComputeName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
t.Fatalf("Expected StepCaptureImage to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualVirtualMachineCaptureParameters != expectedVirtualMachineCaptureParameters {
t.Fatalf("Expected StepCaptureImage to source 'constants.ArmVirtualMachineCaptureParameters' from the state bag, but it did not.")
}
if actualCaptureTemplate != expectedCaptureTemplate {
t.Fatalf("Expected StepCaptureImage to source 'constants.ArmCaptureTemplate' from the state bag, but it did not.")
}
}
func createTestStateBagStepCaptureImage() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmComputeName, "Unit Test: ComputeName")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
stateBag.Put(constants.ArmVirtualMachineCaptureParameters, &compute.VirtualMachineCaptureParameters{})
return stateBag
}
@@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepCreateResourceGroup struct {
client *AzureClient
create func(resourceGroupName string, location string) error
say func(message string)
error func(e error)
}
func NewStepCreateResourceGroup(client *AzureClient, ui packer.Ui) *StepCreateResourceGroup {
var step = &StepCreateResourceGroup{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.create = step.createResourceGroup
return step
}
func (s *StepCreateResourceGroup) createResourceGroup(resourceGroupName string, location string) error {
_, err := s.client.GroupsClient.CreateOrUpdate(resourceGroupName, resources.ResourceGroup{
Location: &location,
})
return err
}
func (s *StepCreateResourceGroup) Run(state multistep.StateBag) multistep.StepAction {
s.say("Creating resource group ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var location = state.Get(constants.ArmLocation).(string)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> Location : '%s'", location))
err := s.create(resourceGroupName, location)
if err == nil {
state.Put(constants.ArmIsResourceGroupCreated, true)
}
return processStepResult(err, s.error, state)
}
func (s *StepCreateResourceGroup) Cleanup(state multistep.StateBag) {
_, ok := state.GetOk(constants.ArmIsResourceGroupCreated)
if !ok {
return
}
ui := state.Get("ui").(packer.Ui)
ui.Say("\nCleanup requested, deleting resource group ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
_, err := s.client.GroupsClient.Delete(resourceGroupName, nil)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting resource group. Please delete it manually.\n\n"+
"Name: %s\n"+
"Error: %s", resourceGroupName, err))
}
ui.Say("Resource group has been deleted.")
}
@@ -0,0 +1,97 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"testing"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepCreateResourceGroupShouldFailIfCreateFails(t *testing.T) {
var testSubject = &StepCreateResourceGroup{
create: func(string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepCreateResourceGroup()
var result = testSubject.Run(stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
func TestStepCreateResourceGroupShouldPassIfCreatePasses(t *testing.T) {
var testSubject = &StepCreateResourceGroup{
create: func(string, string) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepCreateResourceGroup()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualLocation string
var testSubject = &StepCreateResourceGroup{
create: func(resourceGroupName string, location string) error {
actualResourceGroupName = resourceGroupName
actualLocation = location
return nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepCreateResourceGroup()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
var expectedLocation = stateBag.Get(constants.ArmLocation).(string)
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
if actualResourceGroupName != expectedResourceGroupName {
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualLocation != expectedLocation {
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
_, ok := stateBag.GetOk(constants.ArmIsResourceGroupCreated)
if !ok {
t.Fatalf("Expected the step to add item to stateBag['constants.ArmIsResourceGroupCreated'], but it did not.")
}
}
func createTestStateBagStepCreateResourceGroup() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
return stateBag
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"net/url"
"strings"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepDeleteOSDisk struct {
client *AzureClient
delete func(string, string) error
say func(message string)
error func(e error)
}
func NewStepDeleteOSDisk(client *AzureClient, ui packer.Ui) *StepDeleteOSDisk {
var step = &StepDeleteOSDisk{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.delete = step.deleteBlob
return step
}
func (s *StepDeleteOSDisk) deleteBlob(storageContainerName string, blobName string) error {
return s.client.BlobStorageClient.DeleteBlob(storageContainerName, blobName, nil)
}
func (s *StepDeleteOSDisk) Run(state multistep.StateBag) multistep.StepAction {
s.say("Deleting the temporary OS disk ...")
var osDisk = state.Get(constants.ArmOSDiskVhd).(string)
s.say(fmt.Sprintf(" -> OS Disk : '%s'", osDisk))
u, err := url.Parse(osDisk)
if err != nil {
s.say("Failed to parse the OS Disk's VHD URI!")
return multistep.ActionHalt
}
xs := strings.Split(u.Path, "/")
var storageAccountName = xs[1]
var blobName = strings.Join(xs[2:], "/")
err = s.delete(storageAccountName, blobName)
return processStepResult(err, s.error, state)
}
func (*StepDeleteOSDisk) Cleanup(multistep.StateBag) {
}
@@ -0,0 +1,113 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"testing"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepDeleteOSDiskShouldFailIfGetFails(t *testing.T) {
var testSubject = &StepDeleteOSDisk{
delete: func(string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://storage.blob.core.windows.net/images/pkrvm_os.vhd")
var result = testSubject.Run(stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
func TestStepDeleteOSDiskShouldPassIfGetPasses(t *testing.T) {
var testSubject = &StepDeleteOSDisk{
delete: func(string, string) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://storage.blob.core.windows.net/images/pkrvm_os.vhd")
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepDeleteOSDiskShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualStorageContainerName string
var actualBlobName string
var testSubject = &StepDeleteOSDisk{
delete: func(storageContainerName string, blobName string) error {
actualStorageContainerName = storageContainerName
actualBlobName = blobName
return nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://storage.blob.core.windows.net/images/pkrvm_os.vhd")
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if actualStorageContainerName != "images" {
t.Fatalf("Expected the storage container name to be 'images', but found '%s'.", actualStorageContainerName)
}
if actualBlobName != "pkrvm_os.vhd" {
t.Fatalf("Expected the blob name to be 'pkrvm_os.vhd', but found '%s'.", actualBlobName)
}
}
func TestStepDeleteOSDiskShouldHandleComplexStorageContainerNames(t *testing.T) {
var actualStorageContainerName string
var actualBlobName string
var testSubject = &StepDeleteOSDisk{
delete: func(storageContainerName string, blobName string) error {
actualStorageContainerName = storageContainerName
actualBlobName = blobName
return nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://storage.blob.core.windows.net/abc/def/pkrvm_os.vhd")
testSubject.Run(stateBag)
if actualStorageContainerName != "abc" {
t.Fatalf("Expected the storage container name to be 'abc/def', but found '%s'.", actualStorageContainerName)
}
if actualBlobName != "def/pkrvm_os.vhd" {
t.Fatalf("Expected the blob name to be 'pkrvm_os.vhd', but found '%s'.", actualBlobName)
}
}
func DeleteTestStateBagStepDeleteOSDisk(osDiskVhd string) multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmOSDiskVhd, osDiskVhd)
return stateBag
}
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"github.com/mitchellh/packer/builder/azure/common"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepDeleteResourceGroup struct {
client *AzureClient
delete func(resourceGroupName string, cancelCh <-chan struct{}) error
say func(message string)
error func(e error)
}
func NewStepDeleteResourceGroup(client *AzureClient, ui packer.Ui) *StepDeleteResourceGroup {
var step = &StepDeleteResourceGroup{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.delete = step.deleteResourceGroup
return step
}
func (s *StepDeleteResourceGroup) deleteResourceGroup(resourceGroupName string, cancelCh <-chan struct{}) error {
_, err := s.client.GroupsClient.Delete(resourceGroupName, cancelCh)
return err
}
func (s *StepDeleteResourceGroup) Run(state multistep.StateBag) multistep.StepAction {
s.say("Deleting resource group ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
result := common.StartInterruptibleTask(
func() bool { return common.IsStateCancelled(state) },
func(cancelCh <-chan struct{}) error { return s.delete(resourceGroupName, cancelCh) })
return processInterruptibleResult(result, s.error, state)
}
func (*StepDeleteResourceGroup) Cleanup(multistep.StateBag) {
}
@@ -0,0 +1,83 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"testing"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepDeleteResourceGroupShouldFailIfDeleteFails(t *testing.T) {
var testSubject = &StepDeleteResourceGroup{
delete: func(string, <-chan struct{}) error { return fmt.Errorf("!! Unit Test FAIL !!") },
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteResourceGroup()
var result = testSubject.Run(stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
func TestStepDeleteResourceGroupShouldPassIfDeletePasses(t *testing.T) {
var testSubject = &StepDeleteResourceGroup{
delete: func(string, <-chan struct{}) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteResourceGroup()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepDeleteResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var testSubject = &StepDeleteResourceGroup{
delete: func(resourceGroupName string, cancelCh <-chan struct{}) error {
actualResourceGroupName = resourceGroupName
return nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := DeleteTestStateBagStepDeleteResourceGroup()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
if actualResourceGroupName != expectedResourceGroupName {
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
}
func DeleteTestStateBagStepDeleteResourceGroup() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
return stateBag
}
+89
View File
@@ -0,0 +1,89 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"github.com/mitchellh/packer/builder/azure/common"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepDeployTemplate struct {
client *AzureClient
template string
deploy func(resourceGroupName string, deploymentName string, templateParameters *TemplateParameters, cancelCh <-chan struct{}) error
say func(message string)
error func(e error)
}
func NewStepDeployTemplate(client *AzureClient, ui packer.Ui, template string) *StepDeployTemplate {
var step = &StepDeployTemplate{
client: client,
template: template,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.deploy = step.deployTemplate
return step
}
func (s *StepDeployTemplate) deployTemplate(resourceGroupName string, deploymentName string, templateParameters *TemplateParameters, cancelCh <-chan struct{}) error {
factory := newDeploymentFactory(s.template)
deployment, err := factory.create(*templateParameters)
if err != nil {
return err
}
_, err = s.client.DeploymentsClient.CreateOrUpdate(resourceGroupName, deploymentName, *deployment, cancelCh)
if err != nil {
return err
}
poller := NewDeploymentPoller(func() (string, error) {
r, e := s.client.DeploymentsClient.Get(resourceGroupName, deploymentName)
if r.Properties != nil && r.Properties.ProvisioningState != nil {
return *r.Properties.ProvisioningState, e
}
return "UNKNOWN", e
})
pollStatus, err := poller.PollAsNeeded()
if err != nil {
return err
}
if pollStatus != DeploySucceeded {
return fmt.Errorf("Deployment failed with a status of '%s'.", pollStatus)
}
return nil
}
func (s *StepDeployTemplate) Run(state multistep.StateBag) multistep.StepAction {
s.say("Deploying deployment template ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var deploymentName = state.Get(constants.ArmDeploymentName).(string)
var templateParameters = state.Get(constants.ArmTemplateParameters).(*TemplateParameters)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", deploymentName))
result := common.StartInterruptibleTask(
func() bool { return common.IsStateCancelled(state) },
func(cancelCh <-chan struct{}) error {
return s.deploy(resourceGroupName, deploymentName, templateParameters, cancelCh)
},
)
return processInterruptibleResult(result, s.error, state)
}
func (*StepDeployTemplate) Cleanup(multistep.StateBag) {
}
@@ -0,0 +1,106 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"testing"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepDeployTemplateShouldFailIfDeployFails(t *testing.T) {
var testSubject = &StepDeployTemplate{
template: Linux,
deploy: func(string, string, *TemplateParameters, <-chan struct{}) error {
return fmt.Errorf("!! Unit Test FAIL !!")
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepDeployTemplate()
var result = testSubject.Run(stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
func TestStepDeployTemplateShouldPassIfDeployPasses(t *testing.T) {
var testSubject = &StepDeployTemplate{
template: Linux,
deploy: func(string, string, *TemplateParameters, <-chan struct{}) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepDeployTemplate()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepDeployTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualDeploymentName string
var actualTemplateParameters *TemplateParameters
var testSubject = &StepDeployTemplate{
template: Linux,
deploy: func(resourceGroupName string, deploymentName string, templateParameter *TemplateParameters, cancelCh <-chan struct{}) error {
actualResourceGroupName = resourceGroupName
actualDeploymentName = deploymentName
actualTemplateParameters = templateParameter
return nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepValidateTemplate()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
var expectedDeploymentName = stateBag.Get(constants.ArmDeploymentName).(string)
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
var expectedTemplateParameters = stateBag.Get(constants.ArmTemplateParameters).(*TemplateParameters)
if actualDeploymentName != expectedDeploymentName {
t.Fatalf("Expected StepValidateTemplate to source 'constants.ArmDeploymentName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualTemplateParameters != expectedTemplateParameters {
t.Fatalf("Expected the step to source 'constants.ArmTemplateParameters' from the state bag, but it did not.")
}
}
func createTestStateBagStepDeployTemplate() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmDeploymentName, "Unit Test: DeploymentName")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
stateBag.Put(constants.ArmTemplateParameters, &TemplateParameters{})
return stateBag
}
+79
View File
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"time"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepGetCertificate struct {
client *AzureClient
template string
get func(keyVaultName string, secretName string) (string, error)
say func(message string)
error func(e error)
pause func()
}
func NewStepGetCertificate(client *AzureClient, ui packer.Ui) *StepGetCertificate {
var step = &StepGetCertificate{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
pause: func() { time.Sleep(30 * time.Second) },
}
step.get = step.getCertificateUrl
return step
}
func (s *StepGetCertificate) getCertificateUrl(keyVaultName string, secretName string) (string, error) {
secret, err := s.client.GetSecret(keyVaultName, secretName)
if err != nil {
return "", err
}
return *secret.ID, err
}
func (s *StepGetCertificate) Run(state multistep.StateBag) multistep.StepAction {
s.say("Getting the certificate's URL ...")
var keyVaultName = state.Get(constants.ArmKeyVaultName).(string)
s.say(fmt.Sprintf(" -> Key Vault Name : '%s'", keyVaultName))
s.say(fmt.Sprintf(" -> Key Vault Secret Name : '%s'", DefaultSecretName))
var err error
var url string
for i := 0; i < 5; i++ {
url, err = s.get(keyVaultName, DefaultSecretName)
if err == nil {
break
}
s.say(fmt.Sprintf(" ...failed to get certificate URL, retry(%d)", i))
s.pause()
}
if err != nil {
state.Put(constants.Error, err)
s.error(err)
return multistep.ActionHalt
}
s.say(fmt.Sprintf(" -> Certificate URL : '%s'", url))
state.Put(constants.ArmCertificateUrl, url)
return multistep.ActionContinue
}
func (*StepGetCertificate) Cleanup(multistep.StateBag) {
}
@@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"testing"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepGetCertificateShouldFailIfGetFails(t *testing.T) {
var testSubject = &StepGetCertificate{
get: func(string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
say: func(message string) {},
error: func(e error) {},
pause: func() {},
}
stateBag := createTestStateBagStepGetCertificate()
var result = testSubject.Run(stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
func TestStepGetCertificateShouldPassIfGetPasses(t *testing.T) {
var testSubject = &StepGetCertificate{
get: func(string, string) (string, error) { return "", nil },
say: func(message string) {},
error: func(e error) {},
pause: func() {},
}
stateBag := createTestStateBagStepGetCertificate()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepGetCertificateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualKeyVaultName string
var actualSecretName string
var testSubject = &StepGetCertificate{
get: func(keyVaultName string, secretName string) (string, error) {
actualKeyVaultName = keyVaultName
actualSecretName = secretName
return "http://key.vault/1", nil
},
say: func(message string) {},
error: func(e error) {},
pause: func() {},
}
stateBag := createTestStateBagStepGetCertificate()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
var expectedKeyVaultName = stateBag.Get(constants.ArmKeyVaultName).(string)
if actualKeyVaultName != expectedKeyVaultName {
t.Fatalf("Expected StepGetCertificate to source 'constants.ArmKeyVaultName' from the state bag, but it did not.")
}
if actualSecretName != DefaultSecretName {
t.Fatalf("Expected StepGetCertificate to use default value for secret, but it did not.")
}
expectedCertificateUrl, ok := stateBag.GetOk(constants.ArmCertificateUrl)
if !ok {
t.Fatalf("Expected the state bag to have a value for '%s', but it did not.", constants.ArmCertificateUrl)
}
if expectedCertificateUrl != "http://key.vault/1" {
t.Fatalf("Expected the value of stateBag[%s] to be 'http://key.vault/1', but got '%s'.", constants.ArmCertificateUrl, expectedCertificateUrl)
}
}
func createTestStateBagStepGetCertificate() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmKeyVaultName, "Unit Test: KeyVaultName")
return stateBag
}
+65
View File
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepGetIPAddress struct {
client *AzureClient
get func(resourceGroupName string, ipAddressName string) (string, error)
say func(message string)
error func(e error)
}
func NewStepGetIPAddress(client *AzureClient, ui packer.Ui) *StepGetIPAddress {
var step = &StepGetIPAddress{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.get = step.getIPAddress
return step
}
func (s *StepGetIPAddress) getIPAddress(resourceGroupName string, ipAddressName string) (string, error) {
res, err := s.client.PublicIPAddressesClient.Get(resourceGroupName, ipAddressName, "")
if err != nil {
return "", nil
}
return *res.Properties.IPAddress, nil
}
func (s *StepGetIPAddress) Run(state multistep.StateBag) multistep.StepAction {
s.say("Getting the public IP address ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var ipAddressName = state.Get(constants.ArmPublicIPAddressName).(string)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> PublicIPAddressName : '%s'", ipAddressName))
address, err := s.get(resourceGroupName, ipAddressName)
if err != nil {
state.Put(constants.Error, err)
s.error(err)
return multistep.ActionHalt
}
s.say(fmt.Sprintf(" -> Public IP : '%s'", address))
state.Put(constants.SSHHost, address)
return multistep.ActionContinue
}
func (*StepGetIPAddress) Cleanup(multistep.StateBag) {
}
@@ -0,0 +1,102 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"testing"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepGetIPAddressShouldFailIfGetFails(t *testing.T) {
var testSubject = &StepGetIPAddress{
get: func(string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepGetIPAddress()
var result = testSubject.Run(stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
func TestStepGetIPAddressShouldPassIfGetPasses(t *testing.T) {
var testSubject = &StepGetIPAddress{
get: func(string, string) (string, error) { return "", nil },
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepGetIPAddress()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualIPAddressName string
var testSubject = &StepGetIPAddress{
get: func(resourceGroupName string, ipAddressName string) (string, error) {
actualResourceGroupName = resourceGroupName
actualIPAddressName = ipAddressName
return "127.0.0.1", nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepGetIPAddress()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
var expectedIPAddressName = stateBag.Get(constants.ArmPublicIPAddressName).(string)
if actualIPAddressName != expectedIPAddressName {
t.Fatalf("Expected StepGetIPAddress to source 'constants.ArmIPAddressName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
t.Fatalf("Expected StepGetIPAddress to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
expectedIPAddress, ok := stateBag.GetOk(constants.SSHHost)
if !ok {
t.Fatalf("Expected the state bag to have a value for '%s', but it did not.", constants.SSHHost)
}
if expectedIPAddress != "127.0.0.1" {
t.Fatalf("Expected the value of stateBag[%s] to be '127.0.0.1', but got '%s'.", constants.SSHHost, expectedIPAddress)
}
}
func createTestStateBagStepGetIPAddress() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmPublicIPAddressName, "Unit Test: PublicIPAddressName")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
return stateBag
}
+63
View File
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepGetOSDisk struct {
client *AzureClient
query func(resourceGroupName string, computeName string) (compute.VirtualMachine, error)
say func(message string)
error func(e error)
}
func NewStepGetOSDisk(client *AzureClient, ui packer.Ui) *StepGetOSDisk {
var step = &StepGetOSDisk{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.query = step.queryCompute
return step
}
func (s *StepGetOSDisk) queryCompute(resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
return s.client.VirtualMachinesClient.Get(resourceGroupName, computeName, "")
}
func (s *StepGetOSDisk) Run(state multistep.StateBag) multistep.StepAction {
s.say("Querying the machine's properties ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var computeName = state.Get(constants.ArmComputeName).(string)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> ComputeName : '%s'", computeName))
vm, err := s.query(resourceGroupName, computeName)
if err != nil {
state.Put(constants.Error, err)
s.error(err)
return multistep.ActionHalt
}
s.say(fmt.Sprintf(" -> OS Disk : '%s'", *vm.Properties.StorageProfile.OsDisk.Vhd.URI))
state.Put(constants.ArmOSDiskVhd, *vm.Properties.StorageProfile.OsDisk.Vhd.URI)
return multistep.ActionContinue
}
func (*StepGetOSDisk) Cleanup(multistep.StateBag) {
}
+125
View File
@@ -0,0 +1,125 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"testing"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepGetOSDiskShouldFailIfGetFails(t *testing.T) {
var testSubject = &StepGetOSDisk{
query: func(string, string) (compute.VirtualMachine, error) {
return createVirtualMachineFromUri("test.vhd"), fmt.Errorf("!! Unit Test FAIL !!")
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepGetOSDisk()
var result = testSubject.Run(stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
func TestStepGetOSDiskShouldPassIfGetPasses(t *testing.T) {
var testSubject = &StepGetOSDisk{
query: func(string, string) (compute.VirtualMachine, error) {
return createVirtualMachineFromUri("test.vhd"), nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepGetOSDisk()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepGetOSDiskShouldTakeValidateArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualComputeName string
var testSubject = &StepGetOSDisk{
query: func(resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
actualResourceGroupName = resourceGroupName
actualComputeName = computeName
return createVirtualMachineFromUri("test.vhd"), nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepGetOSDisk()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
var expectedComputeName = stateBag.Get(constants.ArmComputeName).(string)
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
if actualComputeName != expectedComputeName {
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
expectedOSDiskVhd, ok := stateBag.GetOk(constants.ArmOSDiskVhd)
if !ok {
t.Fatalf("Expected the state bag to have a value for '%s', but it did not.", constants.ArmOSDiskVhd)
}
if expectedOSDiskVhd != "test.vhd" {
t.Fatalf("Expected the value of stateBag[%s] to be 'test.vhd', but got '%s'.", constants.ArmOSDiskVhd, expectedOSDiskVhd)
}
}
func createTestStateBagStepGetOSDisk() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmComputeName, "Unit Test: ComputeName")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
return stateBag
}
func createVirtualMachineFromUri(vhdUri string) compute.VirtualMachine {
vm := compute.VirtualMachine{
Properties: &compute.VirtualMachineProperties{
StorageProfile: &compute.StorageProfile{
OsDisk: &compute.OSDisk{
Vhd: &compute.VirtualHardDisk{
URI: &vhdUri,
},
},
},
},
}
return vm
}
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"github.com/mitchellh/packer/builder/azure/common"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepPowerOffCompute struct {
client *AzureClient
powerOff func(resourceGroupName string, computeName string, cancelCh <-chan struct{}) error
say func(message string)
error func(e error)
}
func NewStepPowerOffCompute(client *AzureClient, ui packer.Ui) *StepPowerOffCompute {
var step = &StepPowerOffCompute{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.powerOff = step.powerOffCompute
return step
}
func (s *StepPowerOffCompute) powerOffCompute(resourceGroupName string, computeName string, cancelCh <-chan struct{}) error {
_, err := s.client.PowerOff(resourceGroupName, computeName, cancelCh)
if err != nil {
return err
}
return nil
}
func (s *StepPowerOffCompute) Run(state multistep.StateBag) multistep.StepAction {
s.say("Powering off machine ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var computeName = state.Get(constants.ArmComputeName).(string)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> ComputeName : '%s'", computeName))
result := common.StartInterruptibleTask(
func() bool { return common.IsStateCancelled(state) },
func(cancelCh <-chan struct{}) error { return s.powerOff(resourceGroupName, computeName, cancelCh) })
return processInterruptibleResult(result, s.error, state)
}
func (*StepPowerOffCompute) Cleanup(multistep.StateBag) {
}
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"testing"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepPowerOffComputeShouldFailIfPowerOffFails(t *testing.T) {
var testSubject = &StepPowerOffCompute{
powerOff: func(string, string, <-chan struct{}) error { return fmt.Errorf("!! Unit Test FAIL !!") },
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepPowerOffCompute()
var result = testSubject.Run(stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
func TestStepPowerOffComputeShouldPassIfPowerOffPasses(t *testing.T) {
var testSubject = &StepPowerOffCompute{
powerOff: func(string, string, <-chan struct{}) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepPowerOffCompute()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepPowerOffComputeShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualComputeName string
var testSubject = &StepPowerOffCompute{
powerOff: func(resourceGroupName string, computeName string, cancelCh <-chan struct{}) error {
actualResourceGroupName = resourceGroupName
actualComputeName = computeName
return nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepPowerOffCompute()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
var expectedComputeName = stateBag.Get(constants.ArmComputeName).(string)
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
if actualComputeName != expectedComputeName {
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
}
func createTestStateBagStepPowerOffCompute() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmComputeName, "Unit Test: ComputeName")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
return stateBag
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepSetCertificate struct {
config *Config
say func(message string)
error func(e error)
}
func NewStepSetCertificate(config *Config, ui packer.Ui) *StepSetCertificate {
var step = &StepSetCertificate{
config: config,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
return step
}
func (s *StepSetCertificate) Run(state multistep.StateBag) multistep.StepAction {
s.say("Setting the certificate's URL ...")
var winRMCertificateUrl = state.Get(constants.ArmCertificateUrl).(string)
s.config.tmpWinRMCertificateUrl = winRMCertificateUrl
state.Put(constants.ArmTemplateParameters, s.config.toTemplateParameters())
return multistep.ActionContinue
}
func (*StepSetCertificate) Cleanup(multistep.StateBag) {
}
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"testing"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepSetCertificateShouldPassIfGetPasses(t *testing.T) {
var testSubject = &StepSetCertificate{
config: new(Config),
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepSetCertificate()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepSetCertificateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var testSubject = &StepSetCertificate{
config: new(Config),
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepSetCertificate()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
_, ok := stateBag.GetOk(constants.ArmTemplateParameters)
if !ok {
t.Fatalf("Expected the state bag to have a value for '%s', but it did not.", constants.ArmTemplateParameters)
}
}
func createTestStateBagStepSetCertificate() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmCertificateUrl, "Unit Test: Certificate URL")
return stateBag
}
+99
View File
@@ -0,0 +1,99 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"github.com/mitchellh/packer/builder/azure/common"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"testing"
)
func TestProcessStepResultShouldContinueForNonErrors(t *testing.T) {
stateBag := new(multistep.BasicStateBag)
code := processStepResult(nil, func(error) { t.Fatalf("Should not be called!") }, stateBag)
if _, ok := stateBag.GetOk(constants.Error); ok {
t.Errorf("Error was nil, but was still in the state bag.")
}
if code != multistep.ActionContinue {
t.Errorf("Expected ActionContinue(%d), but got=%d", multistep.ActionContinue, code)
}
}
func TestProcessStepResultShouldHaltOnError(t *testing.T) {
stateBag := new(multistep.BasicStateBag)
isSaidError := false
code := processStepResult(fmt.Errorf("boom"), func(error) { isSaidError = true }, stateBag)
if _, ok := stateBag.GetOk(constants.Error); !ok {
t.Errorf("Error was non 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)
}
}
func TestProcessStepResultShouldContinueOnSuccessfulTask(t *testing.T) {
stateBag := new(multistep.BasicStateBag)
result := common.InterruptibleTaskResult{
IsCancelled: false,
Err: nil,
}
code := processInterruptibleResult(result, func(error) { t.Fatalf("Should not be called!") }, stateBag)
if _, ok := stateBag.GetOk(constants.Error); ok {
t.Errorf("Error was nil, but was still in the state bag.")
}
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.Fatalf("Should not be called!") }, stateBag)
if _, ok := stateBag.GetOk(constants.Error); ok {
t.Errorf("Error was nil, but was still in the state bag.")
}
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)
}
}
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepValidateTemplate struct {
client *AzureClient
template string
validate func(resourceGroupName string, deploymentName string, templateParameters *TemplateParameters) error
say func(message string)
error func(e error)
}
func NewStepValidateTemplate(client *AzureClient, ui packer.Ui, template string) *StepValidateTemplate {
var step = &StepValidateTemplate{
client: client,
template: template,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
step.validate = step.validateTemplate
return step
}
func (s *StepValidateTemplate) validateTemplate(resourceGroupName string, deploymentName string, templateParameters *TemplateParameters) error {
factory := newDeploymentFactory(s.template)
deployment, err := factory.create(*templateParameters)
if err != nil {
return err
}
_, err = s.client.Validate(resourceGroupName, deploymentName, *deployment)
return err
}
func (s *StepValidateTemplate) Run(state multistep.StateBag) multistep.StepAction {
s.say("Validating deployment template ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var deploymentName = state.Get(constants.ArmDeploymentName).(string)
var templateParameters = state.Get(constants.ArmTemplateParameters).(*TemplateParameters)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", deploymentName))
err := s.validate(resourceGroupName, deploymentName, templateParameters)
return processStepResult(err, s.error, state)
}
func (*StepValidateTemplate) Cleanup(multistep.StateBag) {
}
@@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"testing"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
)
func TestStepValidateTemplateShouldFailIfValidateFails(t *testing.T) {
var testSubject = &StepValidateTemplate{
template: Linux,
validate: func(string, string, *TemplateParameters) error { return fmt.Errorf("!! Unit Test FAIL !!") },
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepValidateTemplate()
var result = testSubject.Run(stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
func TestStepValidateTemplateShouldPassIfValidatePasses(t *testing.T) {
var testSubject = &StepValidateTemplate{
template: Linux,
validate: func(string, string, *TemplateParameters) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepValidateTemplate()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
func TestStepValidateTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualDeploymentName string
var actualTemplateParameters *TemplateParameters
var testSubject = &StepValidateTemplate{
template: Linux,
validate: func(resourceGroupName string, deploymentName string, templateParameter *TemplateParameters) error {
actualResourceGroupName = resourceGroupName
actualDeploymentName = deploymentName
actualTemplateParameters = templateParameter
return nil
},
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepValidateTemplate()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
var expectedDeploymentName = stateBag.Get(constants.ArmDeploymentName).(string)
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
var expectedTemplateParameters = stateBag.Get(constants.ArmTemplateParameters).(*TemplateParameters)
if actualDeploymentName != expectedDeploymentName {
t.Fatalf("Expected the step to source 'constants.ArmDeploymentName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
t.Fatalf("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualTemplateParameters != expectedTemplateParameters {
t.Fatalf("Expected the step to source 'constants.ArmTemplateParameters' from the state bag, but it did not.")
}
}
func createTestStateBagStepValidateTemplate() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmDeploymentName, "Unit Test: DeploymentName")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
stateBag.Put(constants.ArmTemplateParameters, &TemplateParameters{})
return stateBag
}
+489
View File
@@ -0,0 +1,489 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
// See https://github.com/Azure/azure-quickstart-templates for a extensive list of templates.
const Linux = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminUsername": {
"type": "string"
},
"adminPassword": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"imagePublisher": {
"type": "string"
},
"imageOffer": {
"type": "string"
},
"imageSku": {
"type": "string"
},
"imageVersion": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"sshAuthorizedKey": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmSize": {
"type": "string"
},
"vmName": {
"type": "string"
}
},
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetName": "packerSubnet",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/publicIPAddresses",
"name": "[variables('publicIPAddressName')]",
"location": "[variables('location')]",
"properties": {
"publicIPAllocationMethod": "[variables('publicIPAddressType')]",
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
}
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/virtualNetworks",
"name": "[variables('virtualNetworkName')]",
"location": "[variables('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]"
}
}
]
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/networkInterfaces",
"name": "[variables('nicName')]",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Compute/virtualMachines",
"name": "[parameters('vmName')]",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]",
"linuxConfiguration": {
"disablePasswordAuthentication": "false",
"ssh": {
"publicKeys": [
{
"path": "[variables('sshKeyPath')]",
"keyData": "[parameters('sshAuthorizedKey')]"
}
]
}
}
},
"storageProfile": {
"imageReference": {
"publisher": "[parameters('imagePublisher')]",
"offer": "[parameters('imageOffer')]",
"sku": "[parameters('imageSku')]",
"version": "[parameters('imageVersion')]"
},
"osDisk": {
"name": "osdisk",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
},
"caching": "ReadWrite",
"createOption": "FromImage"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": "false"
}
}
}
}
]
}`
// Template to deploy a KeyVault.
//
// NOTE: the parameters for the KeyVault template are identical to Windows
// template. Keeping these values in sync simplifies the code at the expense
// of template bloat. This bloat may be addressed in the future.
const KeyVault = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminUserName": {
"type": "string"
},
"adminPassword": {
"type": "securestring"
},
"dnsNameForPublicIP": {
"type": "string"
},
"imageOffer": {
"type": "string"
},
"imagePublisher": {
"type": "string"
},
"imageSku": {
"type": "string"
},
"imageVersion": {
"type": "string"
},
"keyVaultName": {
"type": "string"
},
"keyVaultSecretValue": {
"type": "securestring"
},
"objectId": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"tenantId": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
},
"winRMCertificateUrl": {
"type": "string"
}
},
"variables": {
"apiVersion": "2015-06-01",
"location": "[resourceGroup().location]",
"keyVaultSecretName": "packerKeyVaultSecret"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.KeyVault/vaults",
"name": "[parameters('keyVaultName')]",
"location": "[variables('location')]",
"properties": {
"enabledForDeployment": "true",
"enabledForTemplateDeployment": "true",
"tenantId": "[parameters('tenantId')]",
"accessPolicies": [
{
"tenantId": "[parameters('tenantId')]",
"objectId": "[parameters('objectId')]",
"permissions": {
"keys": [ "all" ],
"secrets": [ "all" ]
}
}
],
"sku": {
"name": "standard",
"family": "A"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "secrets",
"name": "[variables('keyVaultSecretName')]",
"dependsOn": [
"[concat('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]"
],
"properties": {
"value": "[parameters('keyVaultSecretValue')]"
}
}
]
}
]
}`
const Windows = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminUserName": {
"type": "string"
},
"adminPassword": {
"type": "securestring"
},
"dnsNameForPublicIP": {
"type": "string"
},
"imageOffer": {
"type": "string"
},
"imagePublisher": {
"type": "string"
},
"imageSku": {
"type": "string"
},
"imageVersion": {
"type": "string"
},
"keyVaultName": {
"type": "string"
},
"keyVaultSecretValue": {
"type": "securestring"
},
"objectId": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"tenantId": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
},
"winRMCertificateUrl": {
"type": "string"
}
},
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"subnetName": "packerSubnet",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/publicIPAddresses",
"name": "[variables('publicIPAddressName')]",
"location": "[variables('location')]",
"properties": {
"publicIPAllocationMethod": "[variables('publicIPAddressType')]",
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
}
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/virtualNetworks",
"name": "[variables('virtualNetworkName')]",
"location": "[variables('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]"
}
}
]
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Network/networkInterfaces",
"name": "[variables('nicName')]",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Compute/virtualMachines",
"name": "[parameters('vmName')]",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]",
"secrets": [
{
"sourceVault": {
"id": "[resourceId(resourceGroup().name, 'Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
},
"vaultCertificates": [
{
"certificateUrl": "[parameters('winRMCertificateUrl')]",
"certificateStore": "My"
}
]
}
],
"windowsConfiguration": {
"provisionVMAgent": "true",
"winRM": {
"listeners": [{
"protocol": "https",
"certificateUrl": "[parameters('winRMCertificateUrl')]"
}
]
},
"enableAutomaticUpdates": "true"
}
},
"storageProfile": {
"imageReference": {
"publisher": "[parameters('imagePublisher')]",
"offer": "[parameters('imageOffer')]",
"sku": "[parameters('imageSku')]",
"version": "[parameters('imageVersion')]"
},
"osDisk": {
"name": "osdisk",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
},
"caching": "ReadWrite",
"createOption": "FromImage"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": "false"
}
}
}
}
]
}`
+41
View File
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
// The intent of these types to facilitate interchange with Azure in the
// appropriate JSON format. A sample format is below. Each parameter listed
// below corresponds to a parameter defined in the template.
//
// {
// "storageAccountName": {
// "value" : "my_storage_account_name"
// },
// "adminUserName" : {
// "value": "admin"
// }
// }
type TemplateParameter struct {
Value string `json:"value"`
}
type TemplateParameters struct {
AdminUsername *TemplateParameter `json:"adminUsername,omitempty"`
AdminPassword *TemplateParameter `json:"adminPassword,omitempty"`
DnsNameForPublicIP *TemplateParameter `json:"dnsNameForPublicIP,omitempty"`
ImageOffer *TemplateParameter `json:"imageOffer,omitempty"`
ImagePublisher *TemplateParameter `json:"imagePublisher,omitempty"`
ImageSku *TemplateParameter `json:"imageSku,omitempty"`
ImageVersion *TemplateParameter `json:"imageVersion,omitempty"`
KeyVaultName *TemplateParameter `json:"keyVaultName,omitempty"`
KeyVaultSecretValue *TemplateParameter `json:"keyVaultSecretValue,omitempty"`
ObjectId *TemplateParameter `json:"objectId,omitempty"`
OSDiskName *TemplateParameter `json:"osDiskName,omitempty"`
SshAuthorizedKey *TemplateParameter `json:"sshAuthorizedKey,omitempty"`
StorageAccountBlobEndpoint *TemplateParameter `json:"storageAccountBlobEndpoint,omitempty"`
TenantId *TemplateParameter `json:"tenantId,omitempty"`
VMSize *TemplateParameter `json:"vmSize,omitempty"`
VMName *TemplateParameter `json:"vmName,omitempty"`
WinRMCertificateUrl *TemplateParameter `json:"winRMCertificateUrl,omitempty"`
}
@@ -0,0 +1,124 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"encoding/json"
"fmt"
"strings"
"testing"
)
func TestTemplateParametersShouldHaveExpectedKeys(t *testing.T) {
params := TemplateParameters{
AdminUsername: &TemplateParameter{"sentinel"},
AdminPassword: &TemplateParameter{"sentinel"},
DnsNameForPublicIP: &TemplateParameter{"sentinel"},
ImageOffer: &TemplateParameter{"sentinel"},
ImagePublisher: &TemplateParameter{"sentinel"},
ImageSku: &TemplateParameter{"sentinel"},
OSDiskName: &TemplateParameter{"sentinel"},
SshAuthorizedKey: &TemplateParameter{"sentinel"},
StorageAccountBlobEndpoint: &TemplateParameter{"sentinel"},
VMName: &TemplateParameter{"sentinel"},
VMSize: &TemplateParameter{"sentinel"},
}
bs, err := json.Marshal(params)
if err != nil {
t.Fail()
}
var doc map[string]*json.RawMessage
err = json.Unmarshal(bs, &doc)
if err != nil {
t.Fail()
}
expectedKeys := []string{
"adminUsername",
"adminPassword",
"dnsNameForPublicIP",
"imageOffer",
"imagePublisher",
"imageSku",
"osDiskName",
"sshAuthorizedKey",
"storageAccountBlobEndpoint",
"vmSize",
"vmName",
}
for _, expectedKey := range expectedKeys {
_, containsKey := doc[expectedKey]
if containsKey == false {
t.Fatalf("Expected template parameters to contain the key value '%s', but it did not!", expectedKey)
}
}
}
func TestParameterValuesShouldBeSet(t *testing.T) {
params := TemplateParameters{
AdminUsername: &TemplateParameter{"adminusername00"},
AdminPassword: &TemplateParameter{"adminpassword00"},
DnsNameForPublicIP: &TemplateParameter{"dnsnameforpublicip00"},
ImageOffer: &TemplateParameter{"imageoffer00"},
ImagePublisher: &TemplateParameter{"imagepublisher00"},
ImageSku: &TemplateParameter{"imagesku00"},
OSDiskName: &TemplateParameter{"osdiskname00"},
SshAuthorizedKey: &TemplateParameter{"sshauthorizedkey00"},
StorageAccountBlobEndpoint: &TemplateParameter{"storageaccountblobendpoint00"},
VMName: &TemplateParameter{"vmname00"},
VMSize: &TemplateParameter{"vmsize00"},
}
bs, err := json.Marshal(params)
if err != nil {
t.Fail()
}
var doc map[string]map[string]interface{}
err = json.Unmarshal(bs, &doc)
if err != nil {
t.Fail()
}
for k, v := range doc {
var expectedValue = fmt.Sprintf("%s00", strings.ToLower(k))
var actualValue, exists = v["value"]
if exists != true {
t.Errorf("Expected to find a 'value' key under '%s', but it was missing!", k)
}
if expectedValue != actualValue {
t.Errorf("Expected '%s', but actual was '%s'!", expectedValue, actualValue)
}
}
}
func TestEmptyValuesShouldBeOmitted(t *testing.T) {
params := TemplateParameters{
AdminUsername: &TemplateParameter{"adminusername00"},
}
bs, err := json.Marshal(params)
if err != nil {
t.Fail()
}
var doc map[string]map[string]interface{}
err = json.Unmarshal(bs, &doc)
if err != nil {
t.Fail()
}
if len(doc) != 1 {
t.Errorf("Failed to omit empty template parameters from the JSON document!")
t.Errorf("doc=%+v", doc)
t.Fail()
}
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"fmt"
"github.com/mitchellh/packer/builder/azure/common"
)
const (
TempNameAlphabet = "0123456789bcdfghjklmnpqrstvwxyz"
TempPasswordAlphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)
type TempName struct {
AdminPassword string
CertificatePassword string
ComputeName string
DeploymentName string
KeyVaultName string
ResourceGroupName string
OSDiskName string
}
func NewTempName() *TempName {
tempName := &TempName{}
suffix := common.RandomString(TempNameAlphabet, 10)
tempName.ComputeName = fmt.Sprintf("pkrvm%s", suffix)
tempName.DeploymentName = fmt.Sprintf("pkrdp%s", suffix)
tempName.KeyVaultName = fmt.Sprintf("pkrkv%s", suffix)
tempName.OSDiskName = fmt.Sprintf("pkros%s", suffix)
tempName.ResourceGroupName = fmt.Sprintf("packer-Resource-Group-%s", suffix)
tempName.AdminPassword = common.RandomString(TempPasswordAlphabet, 32)
tempName.CertificatePassword = common.RandomString(TempPasswordAlphabet, 32)
return tempName
}
+47
View File
@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
import (
"strings"
"testing"
)
func TestTempNameShouldCreatePrefixedRandomNames(t *testing.T) {
tempName := NewTempName()
if strings.Index(tempName.ComputeName, "pkrvm") != 0 {
t.Errorf("Expected ComputeName to begin with 'pkrvm', but got '%s'!", tempName.ComputeName)
}
if strings.Index(tempName.DeploymentName, "pkrdp") != 0 {
t.Errorf("Expected ComputeName to begin with 'pkrdp', but got '%s'!", tempName.ComputeName)
}
if strings.Index(tempName.OSDiskName, "pkros") != 0 {
t.Errorf("Expected OSDiskName to begin with 'pkros', but got '%s'!", tempName.OSDiskName)
}
if strings.Index(tempName.ResourceGroupName, "packer-Resource-Group-") != 0 {
t.Errorf("Expected ResourceGroupName to begin with 'packer-Resource-Group-', but got '%s'!", tempName.ResourceGroupName)
}
}
func TestTempNameShouldHaveSameSuffix(t *testing.T) {
tempName := NewTempName()
suffix := tempName.ComputeName[5:]
if strings.HasSuffix(tempName.DeploymentName, suffix) != true {
t.Errorf("Expected DeploymentName to end with '%s', but the value is '%s'!", suffix, tempName.DeploymentName)
}
if strings.HasSuffix(tempName.OSDiskName, suffix) != true {
t.Errorf("Expected OSDiskName to end with '%s', but the value is '%s'!", suffix, tempName.OSDiskName)
}
if strings.HasSuffix(tempName.ResourceGroupName, suffix) != true {
t.Errorf("Expected ResourceGroupName to end with '%s', but the value is '%s'!", suffix, tempName.ResourceGroupName)
}
}
+10
View File
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package constants
const (
GOOS_Linux string = "linux"
GOOS_Windows string = "windows"
GOOS_Darwin string = "darwin"
)
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package constants
// complete flags
const (
CertInstalled string = "certInstalled"
CertUploaded string = "certUploaded"
DiskExists string = "diskExists"
ImageCreated string = "imageCreated"
SrvExists string = "srvExists"
VmExists string = "vmExists"
VmRunning string = "vmRunning"
)
const (
AuthorizedKey string = "authorizedKey"
Certificate string = "certificate"
Config string = "config"
Error string = "error"
HardDiskName string = "hardDiskName"
MediaLink string = "mediaLink"
OSImageName string = "osImageName"
PrivateKey string = "privateKey"
RequestManager string = "requestManager"
ServicePrincipalToken string = "servicePrincipalToken"
SSHHost string = "sshHost"
Thumbprint string = "thumbprint"
Ui string = "ui"
)
const (
ArmBlobEndpoint string = "arm.BlobEndpoint"
ArmCaptureTemplate string = "arm.CaptureTemplate"
ArmComputeName string = "arm.ComputeName"
ArmCertificateUrl string = "arm.CertificateUrl"
ArmDeploymentName string = "arm.DeploymentName"
ArmKeyVaultName string = "arm.KeyVaultName"
ArmLocation string = "arm.Location"
ArmOSDiskVhd string = "arm.OSDiskVhd"
ArmPublicIPAddressName string = "arm.PublicIPAddressName"
ArmResourceGroupName string = "arm.ResourceGroupName"
ArmIsResourceGroupCreated string = "arm.IsResourceGroupCreated"
ArmStorageAccountName string = "arm.StorageAccountName"
ArmTemplateParameters string = "arm.TemplateParameters"
ArmVirtualMachineCaptureParameters string = "arm.VirtualMachineCaptureParameters"
)
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package constants
// Target types
const (
Target_Linux string = "Linux"
Target_Windows string = "Windows"
)
+247
View File
@@ -0,0 +1,247 @@
package common
import (
"fmt"
"net/http"
"os"
"path/filepath"
"regexp"
"github.com/Azure/azure-sdk-for-go/arm/resources/subscriptions"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"github.com/mitchellh/packer/version"
"github.com/mitchellh/go-homedir"
)
var (
// AD app id for packer-azure driver.
clientIDs = map[string]string{
azure.PublicCloud.Name: "04cc58ec-51ab-4833-ac0d-ce3a7912414b",
}
userAgent = fmt.Sprintf("packer/%s", version.FormattedVersion())
)
// NOTE(ahmetalpbalkan): Azure Active Directory implements OAuth 2.0 Device Flow
// described here: https://tools.ietf.org/html/draft-denniss-oauth-device-flow-00
// Although it has some gotchas, most of the authentication logic is in Azure SDK
// for Go helper packages.
//
// Device auth prints a message to the screen telling the user to click on URL
// and approve the app on the browser, meanwhile the client polls the auth API
// for a token. Once we have token, we save it locally to a file with proper
// permissions and when the token expires (in Azure case typically 1 hour) SDK
// will automatically refresh the specified token and will call the refresh
// callback function we implement here. This way we will always be storing a
// token with a refresh_token saved on the machine.
// Authenticate fetches a token from the local file cache or initiates a consent
// flow and waits for token to be obtained.
func Authenticate(env azure.Environment, subscriptionID string, say func(string)) (*azure.ServicePrincipalToken, error) {
clientID, ok := clientIDs[env.Name]
if !ok {
return nil, fmt.Errorf("packer-azure application not set up for Azure environment %q", env.Name)
}
// First we locate the tenant ID of the subscription as we store tokens per
// tenant (which could have multiple subscriptions)
say(fmt.Sprintf("Looking up AAD Tenant ID: subscriptionID=%s.", subscriptionID))
tenantID, err := findTenantID(env, subscriptionID)
if err != nil {
return nil, err
}
say(fmt.Sprintf("Found AAD Tenant ID: tenantID=%s", tenantID))
oauthCfg, err := env.OAuthConfigForTenant(tenantID)
if err != nil {
return nil, fmt.Errorf("Failed to obtain oauth config for azure environment: %v", err)
}
// for AzurePublicCloud (https://management.core.windows.net/), this old
// Service Management scope covers both ASM and ARM.
apiScope := env.ServiceManagementEndpoint
tokenPath := tokenCachePath(tenantID)
saveToken := mkTokenCallback(tokenPath)
saveTokenCallback := func(t azure.Token) error {
say("Azure token expired. Saving the refreshed token...")
return saveToken(t)
}
// Lookup the token cache file for an existing token.
spt, err := tokenFromFile(say, *oauthCfg, tokenPath, clientID, apiScope, saveTokenCallback)
if err != nil {
return nil, err
}
if spt != nil {
say(fmt.Sprintf("Auth token found in file: %s", tokenPath))
// NOTE(ahmetalpbalkan): The token file we found may contain an
// expired access_token. In that case, the first call to Azure SDK will
// attempt to refresh the token using refresh_token, which might have
// expired[1], in that case we will get an error and we shall remove the
// token file and initiate token flow again so that the user would not
// need removing the token cache file manually.
//
// [1]: expiration date of refresh_token is not returned in AAD /token
// response, we just know it is 14 days. Therefore users token
// will go stale every 14 days and we will delete the token file,
// re-initiate the device flow.
say("Validating the token.")
if err := validateToken(env, spt); err != nil {
say(fmt.Sprintf("Error: %v", err))
say("Stored Azure credentials expired. Please reauthenticate.")
say(fmt.Sprintf("Deleting %s", tokenPath))
if err := os.RemoveAll(tokenPath); err != nil {
return nil, fmt.Errorf("Error deleting stale token file: %v", err)
}
} else {
say("Token works.")
return spt, nil
}
}
// Start an OAuth 2.0 device flow
say(fmt.Sprintf("Initiating device flow: %s", tokenPath))
spt, err = tokenFromDeviceFlow(say, *oauthCfg, tokenPath, clientID, apiScope)
if err != nil {
return nil, err
}
say("Obtained service principal token.")
if err := saveToken(spt.Token); err != nil {
say("Error occurred saving token to cache file.")
return nil, err
}
return spt, nil
}
// tokenFromFile returns a token from the specified file if it is found, otherwise
// returns nil. Any error retrieving or creating the token is returned as an error.
func tokenFromFile(say func(string), oauthCfg azure.OAuthConfig, tokenPath, clientID, resource string,
callback azure.TokenRefreshCallback) (*azure.ServicePrincipalToken, error) {
say(fmt.Sprintf("Loading auth token from file: %s", tokenPath))
if _, err := os.Stat(tokenPath); err != nil {
if os.IsNotExist(err) { // file not found
return nil, nil
}
return nil, err
}
token, err := azure.LoadToken(tokenPath)
if err != nil {
return nil, fmt.Errorf("Failed to load token from file: %v", err)
}
spt, err := azure.NewServicePrincipalTokenFromManualToken(oauthCfg, clientID, resource, *token, callback)
if err != nil {
return nil, fmt.Errorf("Error constructing service principal token: %v", err)
}
return spt, nil
}
// tokenFromDeviceFlow prints a message to the screen for user to take action to
// consent application on a browser and in the meanwhile the authentication
// endpoint is polled until user gives consent, denies or the flow times out.
// Returned token must be saved.
func tokenFromDeviceFlow(say func(string), oauthCfg azure.OAuthConfig, tokenPath, clientID, resource string) (*azure.ServicePrincipalToken, error) {
cl := autorest.NewClientWithUserAgent(userAgent)
deviceCode, err := azure.InitiateDeviceAuth(&cl, oauthCfg, clientID, resource)
if err != nil {
return nil, fmt.Errorf("Failed to start device auth: %v", err)
}
// Example message: “To sign in, open https://aka.ms/devicelogin and enter
// the code 0000000 to authenticate.”
say(fmt.Sprintf("Microsoft Azure: %s", to.String(deviceCode.Message)))
token, err := azure.WaitForUserCompletion(&cl, deviceCode)
if err != nil {
return nil, fmt.Errorf("Failed to complete device auth: %v", err)
}
spt, err := azure.NewServicePrincipalTokenFromManualToken(oauthCfg, clientID, resource, *token)
if err != nil {
return nil, fmt.Errorf("Error constructing service principal token: %v", err)
}
return spt, nil
}
// tokenCachePath returns the full path the OAuth 2.0 token should be saved at
// for given tenant ID.
func tokenCachePath(tenantID string) string {
dir, err := homedir.Dir()
if err != nil {
dir, _ = filepath.Abs(os.Args[0])
}
return filepath.Join(dir, ".azure", "packer", fmt.Sprintf("oauth-%s.json", tenantID))
}
// mkTokenCallback returns a callback function that can be used to save the
// token initially or register to the Azure SDK to be called when the token is
// refreshed.
func mkTokenCallback(path string) azure.TokenRefreshCallback {
return func(t azure.Token) error {
if err := azure.SaveToken(path, 0600, t); err != nil {
return err
}
return nil
}
}
// validateToken makes a call to Azure SDK with given token, essentially making
// sure if the access_token valid, if not it uses SDKs functionality to
// automatically refresh the token using refresh_token (which might have
// expired). This check is essentially to make sure refresh_token is good.
func validateToken(env azure.Environment, token *azure.ServicePrincipalToken) error {
c := subscriptionsClient(env.ResourceManagerEndpoint)
// WTF(chrboum)
//c.Authorizer = token
_, err := c.List()
if err != nil {
return fmt.Errorf("Token validity check failed: %v", err)
}
return nil
}
// findTenantID figures out the AAD tenant ID of the subscription by making an
// unauthenticated request to the Get Subscription Details endpoint and parses
// the value from WWW-Authenticate header.
func findTenantID(env azure.Environment, subscriptionID string) (string, error) {
const hdrKey = "WWW-Authenticate"
c := subscriptionsClient(env.ResourceManagerEndpoint)
// we expect this request to fail (err != nil), but we are only interested
// in headers, so surface the error if the Response is not present (i.e.
// network error etc)
subs, err := c.Get(subscriptionID)
if subs.Response.Response == nil {
return "", fmt.Errorf("Request failed: %v", err)
}
// Expecting 401 StatusUnauthorized here, just read the header
if subs.StatusCode != http.StatusUnauthorized {
return "", fmt.Errorf("Unexpected response from Get Subscription: %v", err)
}
hdr := subs.Header.Get(hdrKey)
if hdr == "" {
return "", fmt.Errorf("Header %v not found in Get Subscription response", hdrKey)
}
// Example value for hdr:
// Bearer authorization_uri="https://login.windows.net/996fe9d1-6171-40aa-945b-4c64b63bf655", error="invalid_token", error_description="The authentication failed because of missing 'Authorization' header."
r := regexp.MustCompile(`authorization_uri=".*/([0-9a-f\-]+)"`)
m := r.FindStringSubmatch(hdr)
if m == nil {
return "", fmt.Errorf("Could not find the tenant ID in header: %s %q", hdrKey, hdr)
}
return m[1], nil
}
func subscriptionsClient(baseURI string) subscriptions.Client {
c := subscriptions.NewClientWithBaseURI(baseURI, "") // used only for unauthenticated requests for generic subs IDs
c.Client.UserAgent += userAgent
return c
}
+69
View File
@@ -0,0 +1,69 @@
package common
import (
"fmt"
"github.com/mitchellh/reflectwalk"
"reflect"
"strings"
)
type walker struct {
depth int
say func(string)
}
func newDumpConfig(say func(string)) *walker {
return &walker{
depth: 0,
say: say,
}
}
func (s *walker) Enter(l reflectwalk.Location) error {
s.depth += 1
return nil
}
func (s *walker) Exit(l reflectwalk.Location) error {
s.depth -= 1
return nil
}
func (s *walker) Struct(v reflect.Value) error {
return nil
}
func (s *walker) StructField(f reflect.StructField, v reflect.Value) error {
if !s.shouldDump(v) {
return nil
}
switch v.Kind() {
case reflect.String:
s.say(fmt.Sprintf("%s=%s", f.Name, s.formatValue(f.Name, v.String())))
}
return nil
}
func (s *walker) shouldDump(v reflect.Value) bool {
return s.depth == 2 && v.IsValid() && v.CanInterface()
}
func (s *walker) formatValue(name, value string) string {
if s.isMaskable(name) {
return strings.Repeat("*", len(value))
}
return value
}
func (s *walker) isMaskable(name string) bool {
up := strings.ToUpper(name)
return strings.Contains(up, "SECRET") || strings.Contains(up, "PASSWORD")
}
func DumpConfig(config interface{}, say func(string)) {
walker := newDumpConfig(say)
reflectwalk.Walk(config, walker)
}
+127
View File
@@ -0,0 +1,127 @@
package common
import "testing"
func TestShouldDumpPublicValues(t *testing.T) {
type S struct {
MyString string
myString string
}
data := &S{
MyString: "value1",
}
dumps := make([]string, 0, 2)
DumpConfig(data, func(s string) { dumps = append(dumps, s) })
if len(dumps) != 1 {
t.Fatalf("Expected len(dumps) to be 1, but got %d", len(dumps))
}
if dumps[0] != "MyString=value1" {
t.Errorf("Expected dumps[0] to be 'MyString=value1', but got %s", dumps[0])
}
}
func TestShouldOnlyDumpStrings(t *testing.T) {
type S struct {
MyString string
MyInt int
MyFloat32 float32
MyStringPointer *string
}
s := "value1"
data := &S{
MyString: s,
MyInt: 1,
MyFloat32: 2.0,
MyStringPointer: &s,
}
dumps := make([]string, 0, 4)
DumpConfig(data, func(s string) { dumps = append(dumps, s) })
if len(dumps) != 1 {
t.Fatalf("Expected len(dumps) to be 1, but got %d", len(dumps))
}
if dumps[0] != "MyString=value1" {
t.Errorf("Expected dumps[0] to be 'MyString=value1', but got %s", dumps[0])
}
}
func TestDumpShouldMaskSensitiveFieldValues(t *testing.T) {
type S struct {
MyString string
MySecret string
MySecretValue string
MyPassword string
MyPasswordValue string
}
data := &S{
MyString: "my-string",
MySecret: "s3cr3t",
MySecretValue: "s3cr3t-value",
MyPassword: "p@ssw0rd",
MyPasswordValue: "p@ssw0rd-value",
}
dumps := make([]string, 0, 5)
DumpConfig(data, func(s string) { dumps = append(dumps, s) })
if len(dumps) != 5 {
t.Fatalf("Expected len(dumps) to be 5, but got %d", len(dumps))
}
if dumps[0] != "MyString=my-string" {
t.Errorf("Expected dumps[0] to be 'MyString=my-string', but got %s", dumps[0])
}
if dumps[1] != "MySecret=******" {
t.Errorf("Expected dumps[1] to be 'MySecret=******', but got %s", dumps[1])
}
if dumps[2] != "MySecretValue=************" {
t.Errorf("Expected dumps[2] to be 'MySecret=************', but got %s", dumps[2])
}
if dumps[3] != "MyPassword=********" {
t.Errorf("Expected dumps[3] to be 'MyPassword=******** but got %s", dumps[3])
}
if dumps[4] != "MyPasswordValue=**************" {
t.Errorf("Expected dumps[4] to be 'MyPasswordValue=**************' but got %s", dumps[4])
}
}
func TestDumpConfigShouldDumpTopLevelValuesOnly(t *testing.T) {
type N struct {
NestedString string
}
type S struct {
Nested1 N
MyString string
Nested2 N
}
data := &S{
Nested1: N{
NestedString: "nested-string1",
},
MyString: "my-string",
Nested2: N{
NestedString: "nested-string2",
},
}
dumps := make([]string, 0, 1)
DumpConfig(data, func(s string) { dumps = append(dumps, s) })
if len(dumps) != 1 {
t.Fatalf("Expected len(dumps) to be 1, but got %d", len(dumps))
}
if dumps[0] != "MyString=my-string" {
t.Errorf("Expected dumps[0] to be 'MyString=my-string', but got %s", dumps[0])
}
}
+22
View File
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package common
// removes overlap between the end of a and the start of b and
// glues them together
func GlueStrings(a, b string) string {
shift := 0
for shift < len(a) {
i := 0
for (i+shift < len(a)) && (i < len(b)) && (a[i+shift] == b[i]) {
i++
}
if i+shift == len(a) {
break
}
shift++
}
return string(a[:shift]) + b
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package common
import (
"testing"
)
func TestGlueStrings(t *testing.T) {
cases := []struct{ a, b, expected string }{
{
"Some log that starts in a",
"starts in a, but continues in b",
"Some log that starts in a, but continues in b",
},
{
"",
"starts in b",
"starts in b",
},
}
for _, testcase := range cases {
t.Logf("testcase: %+v\n", testcase)
result := GlueStrings(testcase.a, testcase.b)
t.Logf("result: '%s'", result)
if result != testcase.expected {
t.Errorf("expected %q, got %q", testcase.expected, result)
}
}
}
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package common
import (
"time"
)
type InterruptibleTaskResult struct {
Err error
IsCancelled bool
}
type InterruptibleTask struct {
IsCancelled func() bool
Task func(cancelCh <-chan struct{}) error
}
func NewInterruptibleTask(isCancelled func() bool, task func(cancelCh <-chan struct{}) error) *InterruptibleTask {
return &InterruptibleTask{
IsCancelled: isCancelled,
Task: task,
}
}
func StartInterruptibleTask(isCancelled func() bool, task func(cancelCh <-chan struct{}) error) InterruptibleTaskResult {
t := NewInterruptibleTask(isCancelled, task)
return t.Run()
}
func (s *InterruptibleTask) Run() InterruptibleTaskResult {
completeCh := make(chan error)
cancelCh := make(chan struct{})
defer close(cancelCh)
go func() {
err := s.Task(cancelCh)
completeCh <- err
// senders close, receivers check for close
close(completeCh)
}()
for {
if s.IsCancelled() {
return InterruptibleTaskResult{Err: nil, IsCancelled: true}
}
select {
case err := <-completeCh:
return InterruptibleTaskResult{Err: err, IsCancelled: false}
case <-time.After(100 * time.Millisecond):
}
}
}
@@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package common
import (
"fmt"
"testing"
"time"
)
func TestInterruptibleTaskShouldImmediatelyEndOnCancel(t *testing.T) {
testSubject := NewInterruptibleTask(
func() bool { return true },
func(<-chan struct{}) error {
for {
time.Sleep(time.Second * 30)
}
})
result := testSubject.Run()
if result.IsCancelled != true {
t.Fatalf("Expected the task to be cancelled, but it was not.")
}
}
func TestInterruptibleTaskShouldRunTaskUntilCompletion(t *testing.T) {
var count int
testSubject := &InterruptibleTask{
IsCancelled: func() bool {
return false
},
Task: func(<-chan struct{}) error {
for i := 0; i < 10; i++ {
count += 1
}
return nil
},
}
result := testSubject.Run()
if result.IsCancelled != false {
t.Errorf("Expected the task to *not* be cancelled, but it was.")
}
if count != 10 {
t.Errorf("Expected the task to wait for completion, but it did not.")
}
if result.Err != nil {
t.Errorf("Expected the task to return a nil error, but got=%s", result.Err)
}
}
func TestInterruptibleTaskShouldImmediatelyStopOnTaskError(t *testing.T) {
testSubject := &InterruptibleTask{
IsCancelled: func() bool {
return false
},
Task: func(cancelCh <-chan struct{}) error {
return fmt.Errorf("boom")
},
}
result := testSubject.Run()
if result.IsCancelled != false {
t.Errorf("Expected the task to *not* be cancelled, but it was.")
}
if result.Err == nil {
t.Errorf("Expected the task to return an error, but it did not.")
}
}
func TestInterruptibleTaskShouldProvideLiveChannel(t *testing.T) {
testSubject := &InterruptibleTask{
IsCancelled: func() bool {
return false
},
Task: func(cancelCh <-chan struct{}) error {
isOpen := false
select {
case _, ok := <-cancelCh:
isOpen = !ok
if !isOpen {
t.Errorf("Expected the channel to open, but it was closed.")
}
default:
isOpen = true
break
}
if !isOpen {
t.Errorf("Check for openness failed.")
}
return nil
},
}
testSubject.Run()
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package lin
import (
"fmt"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"golang.org/x/crypto/ssh"
)
func SSHHost(state multistep.StateBag) (string, error) {
host := state.Get(constants.SSHHost).(string)
return host, nil
}
// SSHConfig returns a function that can be used for the SSH communicator
// config for connecting to the instance created over SSH using the generated
// private key.
func SSHConfig(username string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
privateKey := state.Get(constants.PrivateKey).(string)
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
}
return &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
}, nil
}
}
@@ -0,0 +1,114 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package lin
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"math/big"
"time"
"github.com/mitchellh/packer/builder/azure/common/constants"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepCreateCert struct {
TmpServiceName string
}
func (s *StepCreateCert) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating temporary certificate...")
err := s.createCert(state)
if err != nil {
err := fmt.Errorf("Error creating temporary certificate: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepCreateCert) Cleanup(state multistep.StateBag) {}
func (s *StepCreateCert) createCert(state multistep.StateBag) error {
log.Printf("createCert: Generating RSA key pair...")
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
err := fmt.Errorf("Failed to Generate Private Key: %s", err)
return err
}
// ASN.1 DER encoded form
privkey := string(pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(priv),
}))
// Set the private key in the statebag for later
state.Put(constants.PrivateKey, privkey)
log.Printf("createCert: Private key:\n%s", privkey)
log.Printf("createCert: Creating certificate...")
host := fmt.Sprintf("%s.cloudapp.net", s.TmpServiceName)
notBefore := time.Now()
notAfter := notBefore.Add(365 * 24 * time.Hour)
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
err := fmt.Errorf("Failed to Generate Serial Number: %v", err)
return err
}
template := x509.Certificate{
SerialNumber: serialNumber,
Issuer: pkix.Name{
CommonName: host,
},
Subject: pkix.Name{
CommonName: host,
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
err := fmt.Errorf("Failed to Create Certificate: %s", err)
return err
}
cert := string(pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: derBytes,
}))
state.Put(constants.Certificate, cert)
log.Printf("createCert: Certificate:\n%s", cert)
h := sha1.New()
h.Write(derBytes)
thumbprint := fmt.Sprintf("%X", h.Sum(nil))
state.Put(constants.Thumbprint, thumbprint)
log.Printf("createCert: Thumbprint:\n%s", thumbprint)
return nil
}
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package lin
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
)
type StepGeneralizeOS struct {
Command string
}
func (s *StepGeneralizeOS) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
comm := state.Get("communicator").(packer.Communicator)
ui.Say("Executing OS generalization...")
var stdout, stderr bytes.Buffer
cmd := &packer.RemoteCmd{
Command: s.Command,
Stdout: &stdout,
Stderr: &stderr,
}
if err := comm.Start(cmd); err != nil {
err := fmt.Errorf("Failed executing OS generalization command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Wait for the command to run
cmd.Wait()
// If the command failed to run, notify the user in some way.
if cmd.ExitStatus != 0 {
state.Put("error", fmt.Errorf(
"OS generalization has non-zero exit status.\n\nStdout: %s\n\nStderr: %s",
stdout.String(), stderr.String()))
return multistep.ActionHalt
}
log.Printf("OS generalization stdout: %s", stdout.String())
log.Printf("OS generalization stderr: %s", stderr.String())
return multistep.ActionContinue
}
func (s *StepGeneralizeOS) Cleanup(state multistep.StateBag) {
// do nothing
}
+19
View File
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package logutil
import "fmt"
type Fields map[string]interface{}
func (f Fields) String() string {
var s string
for k, v := range f {
if sv, ok := v.(string); ok {
v = fmt.Sprintf("%q", sv)
}
s += fmt.Sprintf(" %s=%v", k, v)
}
return s
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package common
import (
"math/rand"
"os"
"time"
)
var pwSymbols = []string{
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"0123456789",
}
var rnd = rand.New(rand.NewSource(time.Now().UnixNano() + int64(os.Getpid())))
func RandomString(chooseFrom string, length int) (randomString string) {
cflen := len(chooseFrom)
for i := 0; i < length; i++ {
randomString += string(chooseFrom[rnd.Intn(cflen)])
}
return
}
func RandomPassword() (password string) {
pwlen := 15
batchsize := pwlen / len(pwSymbols)
pw := make([]byte, 0, pwlen)
// choose character set
for c := 0; len(pw) < pwlen; c++ {
s := RandomString(pwSymbols[c%len(pwSymbols)], rnd.Intn(batchsize-1)+1)
pw = append(pw, []byte(s)...)
}
// truncate
pw = pw[:pwlen]
// permute
for c := 0; c < pwlen-1; c++ {
i := rnd.Intn(pwlen-c) + c
x := pw[c]
pw[c] = pw[i]
pw[i] = x
}
return string(pw)
}
+18
View File
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package common
import (
"testing"
)
func TestRandomPassword_generates_15char_passwords(t *testing.T) {
for i := 0; i < 100; i++ {
pw := RandomPassword()
t.Logf("pw: %v", pw)
if len(pw) != 15 {
t.Fatalf("len(pw)!=15, but %v: %v (%v)", len(pw), pw, i)
}
}
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package common
import "github.com/mitchellh/multistep"
func IsStateCancelled(stateBag multistep.StateBag) bool {
_, ok := stateBag.GetOk(multistep.StateCancelled)
return ok
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
// NOTE: vault APIs do not yet exist in the SDK, but once they do this code
// should be removed.
package common
import (
"net/http"
"strings"
"github.com/Azure/go-autorest/autorest"
)
const (
AzureVaultApiVersion = "2015-06-01"
AzureVaultScope = "https://vault.azure.net"
AzureVaultSecretTemplate = "https://{vault-name}.vault.azure.net/secrets/{secret-name}"
)
type VaultClient struct {
autorest.Client
}
type Secret struct {
ID *string `json:"id,omitempty"`
Value string `json:"value"`
Attributes SecretAttributes `json:"attributes"`
}
type SecretAttributes struct {
Enabled bool `json:"enabled"`
Created *string `json:"created"`
Updated *string `json:"updated"`
}
func (client *VaultClient) GetSecret(vaultName, secretName string) (*Secret, error) {
p := map[string]interface{}{
"secret-name": secretName,
}
q := map[string]interface{}{
"api-version": AzureVaultApiVersion,
}
secretURL := strings.Replace(AzureVaultSecretTemplate, "{vault-name}", vaultName, -1)
req, err := autorest.Prepare(&http.Request{},
autorest.AsGet(),
autorest.WithBaseURL(secretURL),
autorest.WithPathParameters(p),
autorest.WithQueryParameters(q))
if err != nil {
return nil, err
}
//resp, err := v.Send(req, http.StatusOK)
resp, err := autorest.SendWithSender(client, req)
if err != nil {
return nil, err
}
var secret Secret
err = autorest.Respond(
resp,
autorest.ByUnmarshallingJSON(&secret))
if err != nil {
return nil, err
}
return &secret, nil
}
+9
View File
@@ -0,0 +1,9 @@
This is a fork of the from the original PKCS#12 parsing code
published in the Azure repository [go-pkcs12](https://github.com/Azure/go-pkcs12).
This fork adds serializing a x509 certificate and private key as PKCS#12 binary blob
(aka .PFX file). Due to the specific nature of this code it was not accepted for
inclusion in the official Go crypto repository.
The methods used for decoding PKCS#12 have been moved to the test files to further
discourage the use of this library for decoding. Please use the official
[pkcs12](https://godoc.org/golang.org/x/crypto/pkcs12) library for decode support.
+26
View File
@@ -0,0 +1,26 @@
package pkcs12
import (
"errors"
"unicode/utf16"
)
func bmpString(s string) ([]byte, error) {
// References:
// https://tools.ietf.org/html/rfc7292#appendix-B.1
// http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
// - non-BMP characters are encoded in UTF 16 by using a surrogate pair of 16-bit codes
// EncodeRune returns 0xfffd if the rune does not need special encoding
// - the above RFC provides the info that BMPStrings are NULL terminated.
rv := make([]byte, 0, 2*len(s)+2)
for _, r := range s {
if t, _ := utf16.EncodeRune(r); t != 0xfffd {
return nil, errors.New("string contains characters that cannot be encoded in UCS-2")
}
rv = append(rv, byte(r/256), byte(r%256))
}
rv = append(rv, 0, 0)
return rv, nil
}
+70
View File
@@ -0,0 +1,70 @@
package pkcs12
import (
"bytes"
"errors"
"testing"
"unicode/utf16"
)
func decodeBMPString(bmpString []byte) (string, error) {
if len(bmpString)%2 != 0 {
return "", errors.New("expected BMP byte string to be an even length")
}
// strip terminator if present
if terminator := bmpString[len(bmpString)-2:]; terminator[0] == terminator[1] && terminator[1] == 0 {
bmpString = bmpString[:len(bmpString)-2]
}
s := make([]uint16, 0, len(bmpString)/2)
for len(bmpString) > 0 {
s = append(s, uint16(bmpString[0])*265+uint16(bmpString[1]))
bmpString = bmpString[2:]
}
return string(utf16.Decode(s)), nil
}
func TestBMPStringDecode(t *testing.T) {
_, err := decodeBMPString([]byte("a"))
if err == nil {
t.Fatalf("expected decode to fail, but it succeeded")
}
}
func TestBMPString(t *testing.T) {
str, err := bmpString("")
if bytes.Compare(str, []byte{0, 0}) != 0 {
t.Errorf("expected empty string to return double 0, but found: % x", str)
}
if err != nil {
t.Errorf("err: %v", err)
}
// Example from https://tools.ietf.org/html/rfc7292#appendix-B
str, err = bmpString("Beavis")
if bytes.Compare(str, []byte{0x00, 0x42, 0x00, 0x65, 0x00, 0x61, 0x00, 0x0076, 0x00, 0x69, 0x00, 0x73, 0x00, 0x00}) != 0 {
t.Errorf("expected 'Beavis' to return 0x00 0x42 0x00 0x65 0x00 0x61 0x00 0x76 0x00 0x69 0x00 0x73 0x00 0x00, but found: % x", str)
}
if err != nil {
t.Errorf("err: %v", err)
}
// some characters from the "Letterlike Symbols Unicode block"
tst := "\u2115 - Double-struck N"
str, err = bmpString(tst)
if bytes.Compare(str, []byte{0x21, 0x15, 0x00, 0x20, 0x00, 0x2d, 0x00, 0x20, 0x00, 0x44, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x2d, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x75, 0x00, 0x63, 0x00, 0x6b, 0x00, 0x20, 0x00, 0x4e, 0x00, 0x00}) != 0 {
t.Errorf("expected '%s' to return 0x21 0x15 0x00 0x20 0x00 0x2d 0x00 0x20 0x00 0x44 0x00 0x6f 0x00 0x75 0x00 0x62 0x00 0x6c 0x00 0x65 0x00 0x2d 0x00 0x73 0x00 0x74 0x00 0x72 0x00 0x75 0x00 0x63 0x00 0x6b 0x00 0x20 0x00 0x4e 0x00 0x00, but found: % x", tst, str)
}
if err != nil {
t.Errorf("err: %v", err)
}
// some character outside the BMP should error
tst = "\U0001f000 East wind (Mahjong)"
str, err = bmpString(tst)
if err == nil {
t.Errorf("expected '%s' to throw error because the first character is not in the BMP", tst)
}
}
+82
View File
@@ -0,0 +1,82 @@
// implementation of https://tools.ietf.org/html/rfc2898#section-6.1.2
package pkcs12
import (
"bytes"
"crypto/cipher"
"crypto/des"
"crypto/rand"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"io"
"github.com/mitchellh/packer/builder/azure/pkcs12/rc2"
)
const (
pbeWithSHAAnd3KeyTripleDESCBC = "pbeWithSHAAnd3-KeyTripleDES-CBC"
pbewithSHAAnd40BitRC2CBC = "pbewithSHAAnd40BitRC2-CBC"
)
const (
pbeIterationCount = 2048
pbeSaltSizeBytes = 8
)
var (
oidPbeWithSHAAnd3KeyTripleDESCBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 12, 1, 3}
oidPbewithSHAAnd40BitRC2CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 12, 1, 6}
)
var algByOID = map[string]string{
oidPbeWithSHAAnd3KeyTripleDESCBC.String(): pbeWithSHAAnd3KeyTripleDESCBC,
oidPbewithSHAAnd40BitRC2CBC.String(): pbewithSHAAnd40BitRC2CBC,
}
var blockcodeByAlg = map[string]func(key []byte) (cipher.Block, error){
pbeWithSHAAnd3KeyTripleDESCBC: des.NewTripleDESCipher,
pbewithSHAAnd40BitRC2CBC: func(key []byte) (cipher.Block, error) {
return rc2.New(key, len(key)*8)
},
}
type pbeParams struct {
Salt []byte
Iterations int
}
func pad(src []byte, blockSize int) []byte {
paddingLength := blockSize - len(src)%blockSize
paddingText := bytes.Repeat([]byte{byte(paddingLength)}, paddingLength)
return append(src, paddingText...)
}
func pbEncrypt(plainText, salt, password []byte, iterations int) (cipherText []byte, err error) {
_, err = io.ReadFull(rand.Reader, salt)
if err != nil {
return nil, errors.New("pkcs12: failed to create a random salt value: " + err.Error())
}
key := deriveKeyByAlg[pbeWithSHAAnd3KeyTripleDESCBC](salt, password, iterations)
iv := deriveIVByAlg[pbeWithSHAAnd3KeyTripleDESCBC](salt, password, iterations)
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, errors.New("pkcs12: failed to create a block cipher: " + err.Error())
}
paddedPlainText := pad(plainText, block.BlockSize())
encrypter := cipher.NewCBCEncrypter(block, iv)
cipherText = make([]byte, len(paddedPlainText))
encrypter.CryptBlocks(cipherText, paddedPlainText)
return cipherText, nil
}
type decryptable interface {
GetAlgorithm() pkix.AlgorithmIdentifier
GetData() []byte
}
+159
View File
@@ -0,0 +1,159 @@
package pkcs12
import (
"bytes"
"crypto/cipher"
"crypto/x509/pkix"
"encoding/asn1"
"testing"
)
func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.BlockMode, error) {
algorithmName, supported := algByOID[algorithm.Algorithm.String()]
if !supported {
return nil, NotImplementedError("algorithm " + algorithm.Algorithm.String() + " is not supported")
}
var params pbeParams
if _, err := asn1.Unmarshal(algorithm.Parameters.FullBytes, &params); err != nil {
return nil, err
}
k := deriveKeyByAlg[algorithmName](params.Salt, password, params.Iterations)
iv := deriveIVByAlg[algorithmName](params.Salt, password, params.Iterations)
password = nil
code, err := blockcodeByAlg[algorithmName](k)
if err != nil {
return nil, err
}
cbc := cipher.NewCBCDecrypter(code, iv)
return cbc, nil
}
func pbDecrypt(info decryptable, password []byte) (decrypted []byte, err error) {
cbc, err := pbDecrypterFor(info.GetAlgorithm(), password)
password = nil
if err != nil {
return nil, err
}
encrypted := info.GetData()
decrypted = make([]byte, len(encrypted))
cbc.CryptBlocks(decrypted, encrypted)
if psLen := int(decrypted[len(decrypted)-1]); psLen > 0 && psLen <= cbc.BlockSize() {
m := decrypted[:len(decrypted)-psLen]
ps := decrypted[len(decrypted)-psLen:]
if bytes.Compare(ps, bytes.Repeat([]byte{byte(psLen)}, psLen)) != 0 {
return nil, ErrDecryption
}
decrypted = m
} else {
return nil, ErrDecryption
}
return
}
func TestPbDecrypterFor(t *testing.T) {
params, _ := asn1.Marshal(pbeParams{
Salt: []byte{1, 2, 3, 4, 5, 6, 7, 8},
Iterations: 2048,
})
alg := pkix.AlgorithmIdentifier{
Algorithm: asn1.ObjectIdentifier([]int{1, 2, 3}),
Parameters: asn1.RawValue{
FullBytes: params,
},
}
pass, _ := bmpString("Sesame open")
_, err := pbDecrypterFor(alg, pass)
if _, ok := err.(NotImplementedError); !ok {
t.Errorf("expected not implemented error, got: %T %s", err, err)
}
alg.Algorithm = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 3})
cbc, err := pbDecrypterFor(alg, pass)
if err != nil {
t.Errorf("err: %v", err)
}
M := []byte{1, 2, 3, 4, 5, 6, 7, 8}
expectedM := []byte{185, 73, 135, 249, 137, 1, 122, 247}
cbc.CryptBlocks(M, M)
if bytes.Compare(M, expectedM) != 0 {
t.Errorf("expected M to be '%d', but found '%d", expectedM, M)
}
}
func TestPbDecrypt(t *testing.T) {
tests := [][]byte{
[]byte("\x33\x73\xf3\x9f\xda\x49\xae\xfc\xa0\x9a\xdf\x5a\x58\xa0\xea\x46"), // 7 padding bytes
[]byte("\x33\x73\xf3\x9f\xda\x49\xae\xfc\x96\x24\x2f\x71\x7e\x32\x3f\xe7"), // 8 padding bytes
[]byte("\x35\x0c\xc0\x8d\xab\xa9\x5d\x30\x7f\x9a\xec\x6a\xd8\x9b\x9c\xd9"), // 9 padding bytes, incorrect
[]byte("\xb2\xf9\x6e\x06\x60\xae\x20\xcf\x08\xa0\x7b\xd9\x6b\x20\xef\x41"), // incorrect padding bytes: [ ... 0x04 0x02 ]
}
expected := []interface{}{
[]byte("A secret!"),
[]byte("A secret"),
ErrDecryption,
ErrDecryption,
}
for i, c := range tests {
td := testDecryptable{
data: c,
algorithm: pkix.AlgorithmIdentifier{
Algorithm: asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 3}), // SHA1/3TDES
Parameters: pbeParams{
Salt: []byte("\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8"),
Iterations: 4096,
}.RawASN1(),
},
}
p, _ := bmpString("sesame")
m, err := pbDecrypt(td, p)
switch e := expected[i].(type) {
case []byte:
if err != nil {
t.Errorf("error decrypting C=%x: %v", c, err)
}
if bytes.Compare(m, e) != 0 {
t.Errorf("expected C=%x to be decoded to M=%x, but found %x", c, e, m)
}
case error:
if err == nil || err.Error() != e.Error() {
t.Errorf("expecting error '%v' during decryption of c=%x, but found err='%v'", e, c, err)
}
}
}
}
type testDecryptable struct {
data []byte
algorithm pkix.AlgorithmIdentifier
}
func (d testDecryptable) GetAlgorithm() pkix.AlgorithmIdentifier { return d.algorithm }
func (d testDecryptable) GetData() []byte { return d.data }
func (params pbeParams) RawASN1() (raw asn1.RawValue) {
asn1Bytes, err := asn1.Marshal(params)
if err != nil {
panic(err)
}
_, err = asn1.Unmarshal(asn1Bytes, &raw)
if err != nil {
panic(err)
}
return
}
+24
View File
@@ -0,0 +1,24 @@
package pkcs12
import "errors"
var (
// ErrDecryption represents a failure to decrypt the input.
ErrDecryption = errors.New("pkcs12: decryption error, incorrect padding")
// ErrIncorrectPassword is returned when an incorrect password is detected.
// Usually, P12/PFX data is signed to be able to verify the password.
ErrIncorrectPassword = errors.New("pkcs12: decryption password incorrect")
)
// NotImplementedError indicates that the input is not currently supported.
type NotImplementedError string
type EncodeError string
func (e NotImplementedError) Error() string {
return string(e)
}
func (e EncodeError) Error() string {
return "pkcs12: encode error: " + string(e)
}
+33
View File
@@ -0,0 +1,33 @@
package pkcs12
import (
"crypto/hmac"
"crypto/sha1"
"crypto/x509/pkix"
"encoding/asn1"
)
var (
oidSha1Algorithm = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26}
)
type macData struct {
Mac digestInfo
MacSalt []byte
Iterations int `asn1:"optional,default:1"`
}
// from PKCS#7:
type digestInfo struct {
Algorithm pkix.AlgorithmIdentifier
Digest []byte
}
func computeMac(message []byte, iterations int, salt, password []byte) []byte {
key := pbkdf(sha1Sum, 20, 64, salt, password, iterations, 3, 20)
mac := hmac.New(sha1.New, key)
mac.Write(message)
return mac.Sum(nil)
}
+52
View File
@@ -0,0 +1,52 @@
package pkcs12
import (
"crypto/hmac"
"encoding/asn1"
"testing"
)
func verifyMac(macData *macData, message, password []byte) error {
if !macData.Mac.Algorithm.Algorithm.Equal(oidSha1Algorithm) {
return NotImplementedError("unknown digest algorithm: " + macData.Mac.Algorithm.Algorithm.String())
}
expectedMAC := computeMac(message, macData.Iterations, macData.MacSalt, password)
if !hmac.Equal(macData.Mac.Digest, expectedMAC) {
return ErrIncorrectPassword
}
return nil
}
func TestVerifyMac(t *testing.T) {
td := macData{
Mac: digestInfo{
Digest: []byte{0x18, 0x20, 0x3d, 0xff, 0x1e, 0x16, 0xf4, 0x92, 0xf2, 0xaf, 0xc8, 0x91, 0xa9, 0xba, 0xd6, 0xca, 0x9d, 0xee, 0x51, 0x93},
},
MacSalt: []byte{1, 2, 3, 4, 5, 6, 7, 8},
Iterations: 2048,
}
message := []byte{11, 12, 13, 14, 15}
password, _ := bmpString("")
td.Mac.Algorithm.Algorithm = asn1.ObjectIdentifier([]int{1, 2, 3})
err := verifyMac(&td, message, password)
if _, ok := err.(NotImplementedError); !ok {
t.Errorf("err: %v", err)
}
td.Mac.Algorithm.Algorithm = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26})
err = verifyMac(&td, message, password)
if err != ErrIncorrectPassword {
t.Errorf("Expected incorrect password, got err: %v", err)
}
password, _ = bmpString("Sesame open")
err = verifyMac(&td, message, password)
if err != nil {
t.Errorf("err: %v", err)
}
}
+187
View File
@@ -0,0 +1,187 @@
package pkcs12
import (
"crypto/sha1"
"math/big"
)
var (
deriveKeyByAlg = map[string]func(salt, password []byte, iterations int) []byte{
pbeWithSHAAnd3KeyTripleDESCBC: func(salt, password []byte, iterations int) []byte {
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 24)
},
pbewithSHAAnd40BitRC2CBC: func(salt, password []byte, iterations int) []byte {
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 5)
},
}
deriveIVByAlg = map[string]func(salt, password []byte, iterations int) []byte{
pbeWithSHAAnd3KeyTripleDESCBC: func(salt, password []byte, iterations int) []byte {
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8)
},
pbewithSHAAnd40BitRC2CBC: func(salt, password []byte, iterations int) []byte {
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8)
},
}
)
func sha1Sum(in []byte) []byte {
sum := sha1.Sum(in)
return sum[:]
}
func pbkdf(hash func([]byte) []byte, u, v int, salt, password []byte, r int, ID byte, size int) (key []byte) {
// implementation of https://tools.ietf.org/html/rfc7292#appendix-B.2 , RFC text verbatim in comments
// Let H be a hash function built around a compression function f:
// Z_2^u x Z_2^v -> Z_2^u
// (that is, H has a chaining variable and output of length u bits, and
// the message input to the compression function of H is v bits). The
// values for u and v are as follows:
// HASH FUNCTION VALUE u VALUE v
// MD2, MD5 128 512
// SHA-1 160 512
// SHA-224 224 512
// SHA-256 256 512
// SHA-384 384 1024
// SHA-512 512 1024
// SHA-512/224 224 1024
// SHA-512/256 256 1024
// Furthermore, let r be the iteration count.
// We assume here that u and v are both multiples of 8, as are the
// lengths of the password and salt strings (which we denote by p and s,
// respectively) and the number n of pseudorandom bits required. In
// addition, u and v are of course non-zero.
// For information on security considerations for MD5 [19], see [25] and
// [1], and on those for MD2, see [18].
// The following procedure can be used to produce pseudorandom bits for
// a particular "purpose" that is identified by a byte called "ID".
// This standard specifies 3 different values for the ID byte:
// 1. If ID=1, then the pseudorandom bits being produced are to be used
// as key material for performing encryption or decryption.
// 2. If ID=2, then the pseudorandom bits being produced are to be used
// as an IV (Initial Value) for encryption or decryption.
// 3. If ID=3, then the pseudorandom bits being produced are to be used
// as an integrity key for MACing.
// 1. Construct a string, D (the "diversifier"), by concatenating v/8
// copies of ID.
D := []byte{}
for i := 0; i < v; i++ {
D = append(D, ID)
}
// 2. Concatenate copies of the salt together to create a string S of
// length v(ceiling(s/v)) bits (the final copy of the salt may be
// truncated to create S). Note that if the salt is the empty
// string, then so is S.
S := []byte{}
{
s := len(salt)
times := s / v
if s%v > 0 {
times++
}
for len(S) < times*v {
S = append(S, salt...)
}
S = S[:times*v]
}
// 3. Concatenate copies of the password together to create a string P
// of length v(ceiling(p/v)) bits (the final copy of the password
// may be truncated to create P). Note that if the password is the
// empty string, then so is P.
P := []byte{}
{
s := len(password)
times := s / v
if s%v > 0 {
times++
}
for len(P) < times*v {
P = append(P, password...)
}
password = nil
P = P[:times*v]
}
// 4. Set I=S||P to be the concatenation of S and P.
I := append(S, P...)
// 5. Set c=ceiling(n/u).
c := size / u
if size%u > 0 {
c++
}
// 6. For i=1, 2, ..., c, do the following:
A := make([]byte, c*20)
for i := 0; i < c; i++ {
// A. Set A2=H^r(D||I). (i.e., the r-th hash of D||1,
// H(H(H(... H(D||I))))
Ai := hash(append(D, I...))
for j := 1; j < r; j++ {
Ai = hash(Ai[:])
}
copy(A[i*20:], Ai[:])
if i < c-1 { // skip on last iteration
// B. Concatenate copies of Ai to create a string B of length v
// bits (the final copy of Ai may be truncated to create B).
B := []byte{}
for len(B) < v {
B = append(B, Ai[:]...)
}
B = B[:v]
// C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit
// blocks, where k=ceiling(s/v)+ceiling(p/v), modify I by
// setting I_j=(I_j+B+1) mod 2^v for each j.
{
Bbi := new(big.Int)
Bbi.SetBytes(B)
one := big.NewInt(1)
for j := 0; j < len(I)/v; j++ {
Ij := new(big.Int)
Ij.SetBytes(I[j*v : (j+1)*v])
Ij.Add(Ij, Bbi)
Ij.Add(Ij, one)
Ijb := Ij.Bytes()
if len(Ijb) > v {
Ijb = Ijb[len(Ijb)-v:]
}
copy(I[j*v:(j+1)*v], Ijb)
}
}
}
}
// 7. Concatenate A_1, A_2, ..., A_c together to form a pseudorandom
// bit string, A.
// 8. Use the first n bits of A as the output of this entire process.
A = A[:size]
return A
// If the above process is being used to generate a DES key, the process
// should be used to create 64 random bits, and the key's parity bits
// should be set after the 64 bits have been produced. Similar concerns
// hold for 2-key and 3-key triple-DES keys, for CDMF keys, and for any
// similar keys with parity bits "built into them".
}
+18
View File
@@ -0,0 +1,18 @@
package pkcs12
import (
"bytes"
"testing"
)
func TestThatPBKDFWorksCorrectlyForLongKeys(t *testing.T) {
pbkdf := deriveKeyByAlg[pbeWithSHAAnd3KeyTripleDESCBC]
salt := []byte("\xff\xff\xff\xff\xff\xff\xff\xff")
password, _ := bmpString("sesame")
key := pbkdf(salt, password, 2048)
if expected := []byte("\x7c\xd9\xfd\x3e\x2b\x3b\xe7\x69\x1a\x44\xe3\xbe\xf0\xf9\xea\x0f\xb9\xb8\x97\xd4\xe3\x25\xd9\xd1"); bytes.Compare(key, expected) != 0 {
t.Fatalf("expected key '% x', but found '% x'", key, expected)
}
}
+263
View File
@@ -0,0 +1,263 @@
// Package pkcs12 provides some implementations of PKCS#12.
//
// This implementation is distilled from https://tools.ietf.org/html/rfc7292 and referenced documents.
// It is intended for decoding P12/PFX-stored certificate+key for use with the crypto/tls package.
package pkcs12
import (
"crypto/rand"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"io"
)
var (
oidLocalKeyID = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 21}
oidDataContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1}
localKeyId = []byte{0x01, 0x00, 0x00, 0x00}
)
type pfxPdu struct {
Version int
AuthSafe contentInfo
MacData macData `asn1:"optional"`
}
type contentInfo struct {
ContentType asn1.ObjectIdentifier
Content asn1.RawValue `asn1:"tag:0,explicit,optional"`
}
type encryptedData struct {
Version int
EncryptedContentInfo encryptedContentInfo
}
type encryptedContentInfo struct {
ContentType asn1.ObjectIdentifier
ContentEncryptionAlgorithm pkix.AlgorithmIdentifier
EncryptedContent []byte `asn1:"tag:0,optional"`
}
func (i encryptedContentInfo) GetAlgorithm() pkix.AlgorithmIdentifier {
return i.ContentEncryptionAlgorithm
}
func (i encryptedContentInfo) GetData() []byte { return i.EncryptedContent }
type safeBag struct {
Id asn1.ObjectIdentifier
Value asn1.RawValue `asn1:"tag:0,explicit"`
Attributes []pkcs12Attribute `asn1:"set,optional"`
}
type pkcs12Attribute struct {
Id asn1.ObjectIdentifier
Value asn1.RawValue `ans1:"set"`
}
type encryptedPrivateKeyInfo struct {
AlgorithmIdentifier pkix.AlgorithmIdentifier
EncryptedData []byte
}
func (i encryptedPrivateKeyInfo) GetAlgorithm() pkix.AlgorithmIdentifier { return i.AlgorithmIdentifier }
func (i encryptedPrivateKeyInfo) GetData() []byte { return i.EncryptedData }
// unmarshal calls asn1.Unmarshal, but also returns an error if there is any
// trailing data after unmarshaling.
func unmarshal(in []byte, out interface{}) error {
trailing, err := asn1.Unmarshal(in, out)
if err != nil {
return err
}
if len(trailing) != 0 {
return errors.New("pkcs12: trailing data found")
}
return nil
}
func getLocalKeyId(id []byte) (attribute pkcs12Attribute, err error) {
octetString := asn1.RawValue{Tag: 4, Class: 0, IsCompound: false, Bytes: id}
bytes, err := asn1.Marshal(octetString)
if err != nil {
return
}
attribute = pkcs12Attribute{
Id: oidLocalKeyID,
Value: asn1.RawValue{Tag: 17, Class: 0, IsCompound: true, Bytes: bytes},
}
return attribute, nil
}
func convertToRawVal(val interface{}) (raw asn1.RawValue, err error) {
bytes, err := asn1.Marshal(val)
if err != nil {
return
}
_, err = asn1.Unmarshal(bytes, &raw)
return raw, nil
}
func makeSafeBags(oid asn1.ObjectIdentifier, value []byte) ([]safeBag, error) {
attribute, err := getLocalKeyId(localKeyId)
if err != nil {
return nil, EncodeError("local key id: " + err.Error())
}
bag := make([]safeBag, 1)
bag[0] = safeBag{
Id: oid,
Value: asn1.RawValue{Tag: 0, Class: 2, IsCompound: true, Bytes: value},
Attributes: []pkcs12Attribute{attribute},
}
return bag, nil
}
func makeCertBagContentInfo(derBytes []byte) (*contentInfo, error) {
certBag1 := certBag{
Id: oidCertTypeX509Certificate,
Data: derBytes,
}
bytes, err := asn1.Marshal(certBag1)
if err != nil {
return nil, EncodeError("encoding cert bag: " + err.Error())
}
certSafeBags, err := makeSafeBags(oidCertBagType, bytes)
if err != nil {
return nil, EncodeError("safe bags: " + err.Error())
}
return makeContentInfo(certSafeBags)
}
func makeShroudedKeyBagContentInfo(privateKey interface{}, password []byte) (*contentInfo, error) {
shroudedKeyBagBytes, err := encodePkcs8ShroudedKeyBag(privateKey, password)
if err != nil {
return nil, EncodeError("encode PKCS#8 shrouded key bag: " + err.Error())
}
safeBags, err := makeSafeBags(oidPkcs8ShroudedKeyBagType, shroudedKeyBagBytes)
if err != nil {
return nil, EncodeError("safe bags: " + err.Error())
}
return makeContentInfo(safeBags)
}
func makeContentInfo(val interface{}) (*contentInfo, error) {
fullBytes, err := asn1.Marshal(val)
if err != nil {
return nil, EncodeError("contentInfo raw value marshal: " + err.Error())
}
octetStringVal := asn1.RawValue{Tag: 4, Class: 0, IsCompound: false, Bytes: fullBytes}
octetStringFullBytes, err := asn1.Marshal(octetStringVal)
if err != nil {
return nil, EncodeError("raw contentInfo to octet string: " + err.Error())
}
contentInfo := contentInfo{ContentType: oidDataContentType}
contentInfo.Content = asn1.RawValue{Tag: 0, Class: 2, IsCompound: true, Bytes: octetStringFullBytes}
return &contentInfo, nil
}
func makeContentInfos(derBytes []byte, privateKey interface{}, password []byte) ([]contentInfo, error) {
shroudedKeyContentInfo, err := makeShroudedKeyBagContentInfo(privateKey, password)
if err != nil {
return nil, EncodeError("shrouded key content info: " + err.Error())
}
certBagContentInfo, err := makeCertBagContentInfo(derBytes)
if err != nil {
return nil, EncodeError("cert bag content info: " + err.Error())
}
contentInfos := make([]contentInfo, 2)
contentInfos[0] = *shroudedKeyContentInfo
contentInfos[1] = *certBagContentInfo
return contentInfos, nil
}
func makeSalt(saltByteCount int) ([]byte, error) {
salt := make([]byte, saltByteCount)
_, err := io.ReadFull(rand.Reader, salt)
return salt, err
}
// Encode converts a certificate and a private key to the PKCS#12 byte stream format.
//
// derBytes is a DER encoded certificate.
// privateKey is an RSA
func Encode(derBytes []byte, privateKey interface{}, password string) (pfxBytes []byte, err error) {
secret, err := bmpString(password)
if err != nil {
return nil, ErrIncorrectPassword
}
contentInfos, err := makeContentInfos(derBytes, privateKey, secret)
if err != nil {
return nil, err
}
// Marhsal []contentInfo so we can re-constitute the byte stream that will
// be suitable for computing the MAC
bytes, err := asn1.Marshal(contentInfos)
if err != nil {
return nil, err
}
// Unmarshal as an asn1.RawValue so, we can compute the MAC against the .Bytes
var contentInfosRaw asn1.RawValue
err = unmarshal(bytes, &contentInfosRaw)
if err != nil {
return nil, err
}
authSafeContentInfo, err := makeContentInfo(contentInfosRaw)
if err != nil {
return nil, EncodeError("authSafe content info: " + err.Error())
}
salt, err := makeSalt(pbeSaltSizeBytes)
if err != nil {
return nil, EncodeError("salt value: " + err.Error())
}
// Compute the MAC for marshaled bytes of contentInfos, which includes the
// cert bag, and the shrouded key bag.
digest := computeMac(contentInfosRaw.FullBytes, pbeIterationCount, salt, secret)
pfx := pfxPdu{
Version: 3,
AuthSafe: *authSafeContentInfo,
MacData: macData{
Iterations: pbeIterationCount,
MacSalt: salt,
Mac: digestInfo{
Algorithm: pkix.AlgorithmIdentifier{
Algorithm: oidSha1Algorithm,
},
Digest: digest,
},
},
}
bytes, err = asn1.Marshal(pfx)
if err != nil {
return nil, EncodeError("marshal PFX PDU: " + err.Error())
}
return bytes, err
}
+117
View File
@@ -0,0 +1,117 @@
package pkcs12
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"math/big"
"testing"
"time"
gopkcs12 "golang.org/x/crypto/pkcs12"
)
func TestPfxRoundTriRsa(t *testing.T) {
privateKey, err := rsa.GenerateKey(rand.Reader, 512)
if err != nil {
t.Fatal(err.Error())
}
key := testPfxRoundTrip(t, privateKey)
actualPrivateKey, ok := key.(*rsa.PrivateKey)
if !ok {
t.Fatalf("failed to decode private key")
}
if privateKey.D.Cmp(actualPrivateKey.D) != 0 {
t.Errorf("priv.D")
}
}
func TestPfxRoundTriEcdsa(t *testing.T) {
privateKey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
if err != nil {
t.Fatal(err.Error())
}
key := testPfxRoundTrip(t, privateKey)
actualPrivateKey, ok := key.(*ecdsa.PrivateKey)
if !ok {
t.Fatalf("failed to decode private key")
}
if privateKey.D.Cmp(actualPrivateKey.D) != 0 {
t.Errorf("priv.D")
}
}
func testPfxRoundTrip(t *testing.T, privateKey interface{}) interface{} {
certificateBytes, err := newCertificate("hostname", privateKey)
if err != nil {
t.Fatal(err.Error())
}
bytes, err := Encode(certificateBytes, privateKey, "sesame")
if err != nil {
t.Fatal(err.Error())
}
key, _, err := gopkcs12.Decode(bytes, "sesame")
if err != nil {
t.Fatalf(err.Error())
}
return key
}
func newCertificate(hostname string, privateKey interface{}) ([]byte, error) {
t, _ := time.Parse("2006-01-02", "2016-01-01")
notBefore := t
notAfter := notBefore.Add(365 * 24 * time.Hour)
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
err := fmt.Errorf("Failed to Generate Serial Number: %v", err)
return nil, err
}
template := x509.Certificate{
SerialNumber: serialNumber,
Issuer: pkix.Name{
CommonName: hostname,
},
Subject: pkix.Name{
CommonName: hostname,
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
var publicKey interface{}
switch key := privateKey.(type) {
case *rsa.PrivateKey:
publicKey = key.Public()
case *ecdsa.PrivateKey:
publicKey = key.Public()
default:
panic(fmt.Sprintf("unsupported private key type: %T", privateKey))
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey, privateKey)
if err != nil {
return nil, fmt.Errorf("Failed to Generate derBytes: " + err.Error())
}
return derBytes, nil
}
+64
View File
@@ -0,0 +1,64 @@
package pkcs12
import (
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
)
// pkcs8 reflects an ASN.1, PKCS#8 PrivateKey. See
// ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-8/pkcs-8v1_2.asn
// and RFC5208.
type pkcs8 struct {
Version int
Algo pkix.AlgorithmIdentifier
PrivateKey []byte
// optional attributes omitted.
}
var (
oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
nullAsn = asn1.RawValue{Tag: 5}
)
// marshalPKCS8PrivateKey converts a private key to PKCS#8 encoded form.
// See http://www.rsa.com/rsalabs/node.asp?id=2130 and RFC5208.
func marshalPKCS8PrivateKey(key interface{}) (der []byte, err error) {
pkcs := pkcs8{
Version: 0,
}
switch key := key.(type) {
case *rsa.PrivateKey:
pkcs.Algo = pkix.AlgorithmIdentifier{
Algorithm: oidPublicKeyRSA,
Parameters: nullAsn,
}
pkcs.PrivateKey = x509.MarshalPKCS1PrivateKey(key)
case *ecdsa.PrivateKey:
bytes, err := x509.MarshalECPrivateKey(key)
if err != nil {
return nil, errors.New("x509: failed to marshal to PKCS#8: " + err.Error())
}
pkcs.Algo = pkix.AlgorithmIdentifier{
Algorithm: oidPublicKeyECDSA,
Parameters: nullAsn,
}
pkcs.PrivateKey = bytes
default:
return nil, errors.New("x509: PKCS#8 only RSA and ECDSA private keys supported")
}
bytes, err := asn1.Marshal(pkcs)
if err != nil {
return nil, errors.New("x509: failed to marshal to PKCS#8: " + err.Error())
}
return bytes, nil
}
+141
View File
@@ -0,0 +1,141 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pkcs12
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/asn1"
"testing"
)
func TestRoundTripPkcs8Rsa(t *testing.T) {
privateKey, err := rsa.GenerateKey(rand.Reader, 512)
if err != nil {
t.Fatalf("failed to generate a private key: %s", err)
}
bytes, err := marshalPKCS8PrivateKey(privateKey)
if err != nil {
t.Fatalf("failed to marshal private key: %s", err)
}
key, err := x509.ParsePKCS8PrivateKey(bytes)
if err != nil {
t.Fatalf("failed to parse private key: %s", err)
}
actualPrivateKey, ok := key.(*rsa.PrivateKey)
if !ok {
t.Fatalf("expected key to be of type *rsa.PrivateKey, but actual was %T", key)
}
if actualPrivateKey.Validate() != nil {
t.Fatalf("private key did not validate")
}
if actualPrivateKey.N.Cmp(privateKey.N) != 0 {
t.Errorf("private key's N did not round trip")
}
if actualPrivateKey.D.Cmp(privateKey.D) != 0 {
t.Errorf("private key's D did not round trip")
}
if actualPrivateKey.E != privateKey.E {
t.Errorf("private key's E did not round trip")
}
if actualPrivateKey.Primes[0].Cmp(privateKey.Primes[0]) != 0 {
t.Errorf("private key's P did not round trip")
}
if actualPrivateKey.Primes[1].Cmp(privateKey.Primes[1]) != 0 {
t.Errorf("private key's Q did not round trip")
}
}
func TestRoundTripPkcs8Ecdsa(t *testing.T) {
privateKey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
if err != nil {
t.Fatalf("failed to generate a private key: %s", err)
}
bytes, err := marshalPKCS8PrivateKey(privateKey)
if err != nil {
t.Fatalf("failed to marshal private key: %s", err)
}
key, err := x509.ParsePKCS8PrivateKey(bytes)
if err != nil {
t.Fatalf("failed to parse private key: %s", err)
}
actualPrivateKey, ok := key.(*ecdsa.PrivateKey)
if !ok {
t.Fatalf("expected key to be of type *ecdsa.PrivateKey, but actual was %T", key)
}
// sanity check, not exhaustive
if actualPrivateKey.D.Cmp(privateKey.D) != 0 {
t.Errorf("private key's D did not round trip")
}
if actualPrivateKey.X.Cmp(privateKey.X) != 0 {
t.Errorf("private key's X did not round trip")
}
if actualPrivateKey.Y.Cmp(privateKey.Y) != 0 {
t.Errorf("private key's Y did not round trip")
}
if actualPrivateKey.Curve.Params().B.Cmp(privateKey.Curve.Params().B) != 0 {
t.Errorf("private key's Curve.B did not round trip")
}
}
func TestNullParametersPkcs8Rsa(t *testing.T) {
privateKey, err := rsa.GenerateKey(rand.Reader, 512)
if err != nil {
t.Fatalf("failed to generate a private key: %s", err)
}
checkNullParameter(t, privateKey)
}
func TestNullParametersPkcs8Ecdsa(t *testing.T) {
privateKey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
if err != nil {
t.Fatalf("failed to generate a private key: %s", err)
}
checkNullParameter(t, privateKey)
}
func checkNullParameter(t *testing.T, privateKey interface{}) {
bytes, err := marshalPKCS8PrivateKey(privateKey)
if err != nil {
t.Fatalf("failed to marshal private key: %s", err)
}
var pkcs pkcs8
rest, err := asn1.Unmarshal(bytes, &pkcs)
if err != nil {
t.Fatalf("failed to unmarshal PKCS#8: %s", err)
}
if len(rest) != 0 {
t.Fatalf("unexpected trailing bytes of len=%d, bytes=%x", len(rest), rest)
}
// Only version == 0 is known and valid
if pkcs.Version != 0 {
t.Errorf("expected version=0, but actual=%d", pkcs.Version)
}
// ensure a NULL parameter is inserted
if pkcs.Algo.Parameters.Tag != 5 {
t.Errorf("expected parameters to be NULL, but actual tag=%d, class=%d, isCompound=%t, bytes=%x",
pkcs.Algo.Parameters.Tag,
pkcs.Algo.Parameters.Class,
pkcs.Algo.Parameters.IsCompound,
pkcs.Algo.Parameters.Bytes)
}
}
+284
View File
@@ -0,0 +1,284 @@
// Package rc2 implements the RC2 cipher
/*
https://www.ietf.org/rfc/rfc2268.txt
http://people.csail.mit.edu/rivest/pubs/KRRR98.pdf
This code is licensed under the MIT license.
*/
package rc2
import (
"crypto/cipher"
"encoding/binary"
"strconv"
)
// The rc2 block size in bytes
const BlockSize = 8
type rc2Cipher struct {
k [64]uint16
}
// KeySizeError indicates the supplied key was invalid
type KeySizeError int
func (k KeySizeError) Error() string { return "rc2: invalid key size " + strconv.Itoa(int(k)) }
// EffectiveKeySizeError indicates the supplied effective key length was invalid
type EffectiveKeySizeError int
func (k EffectiveKeySizeError) Error() string {
return "rc2: invalid effective key size " + strconv.Itoa(int(k))
}
// New returns a new rc2 cipher with the given key and effective key length t1
func New(key []byte, t1 int) (cipher.Block, error) {
if l := len(key); l == 0 || l > 128 {
return nil, KeySizeError(l)
}
if t1 < 8 || t1 > 1024 {
return nil, EffectiveKeySizeError(t1)
}
return &rc2Cipher{
k: expandKey(key, t1),
}, nil
}
func (c *rc2Cipher) BlockSize() int { return BlockSize }
var piTable = [256]byte{
0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d,
0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2,
0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32,
0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82,
0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc,
0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26,
0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03,
0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7,
0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a,
0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec,
0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39,
0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31,
0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9,
0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9,
0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e,
0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad,
}
func expandKey(key []byte, t1 int) [64]uint16 {
l := make([]byte, 128)
copy(l, key)
var t = len(key)
var t8 = (t1 + 7) / 8
var tm = byte(255 % uint(1<<(8+uint(t1)-8*uint(t8))))
for i := len(key); i < 128; i++ {
l[i] = piTable[l[i-1]+l[uint8(i-t)]]
}
l[128-t8] = piTable[l[128-t8]&tm]
for i := 127 - t8; i >= 0; i-- {
l[i] = piTable[l[i+1]^l[i+t8]]
}
var k [64]uint16
for i := range k {
k[i] = uint16(l[2*i]) + uint16(l[2*i+1])*256
}
return k
}
func rotl16(x uint16, b uint) uint16 {
return (x >> (16 - b)) | (x << b)
}
func (c *rc2Cipher) Encrypt(dst, src []byte) {
r0 := binary.LittleEndian.Uint16(src[0:])
r1 := binary.LittleEndian.Uint16(src[2:])
r2 := binary.LittleEndian.Uint16(src[4:])
r3 := binary.LittleEndian.Uint16(src[6:])
var j int
// These three mix blocks have not been extracted to a common function for to performance reasons.
for j <= 16 {
// mix r0
r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1)
r0 = rotl16(r0, 1)
j++
// mix r1
r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2)
r1 = rotl16(r1, 2)
j++
// mix r2
r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3)
r2 = rotl16(r2, 3)
j++
// mix r3
r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0)
r3 = rotl16(r3, 5)
j++
}
r0 = r0 + c.k[r3&63]
r1 = r1 + c.k[r0&63]
r2 = r2 + c.k[r1&63]
r3 = r3 + c.k[r2&63]
for j <= 40 {
// mix r0
r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1)
r0 = rotl16(r0, 1)
j++
// mix r1
r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2)
r1 = rotl16(r1, 2)
j++
// mix r2
r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3)
r2 = rotl16(r2, 3)
j++
// mix r3
r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0)
r3 = rotl16(r3, 5)
j++
}
r0 = r0 + c.k[r3&63]
r1 = r1 + c.k[r0&63]
r2 = r2 + c.k[r1&63]
r3 = r3 + c.k[r2&63]
for j <= 60 {
// mix r0
r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1)
r0 = rotl16(r0, 1)
j++
// mix r1
r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2)
r1 = rotl16(r1, 2)
j++
// mix r2
r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3)
r2 = rotl16(r2, 3)
j++
// mix r3
r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0)
r3 = rotl16(r3, 5)
j++
}
binary.LittleEndian.PutUint16(dst[0:], r0)
binary.LittleEndian.PutUint16(dst[2:], r1)
binary.LittleEndian.PutUint16(dst[4:], r2)
binary.LittleEndian.PutUint16(dst[6:], r3)
}
func (c *rc2Cipher) Decrypt(dst, src []byte) {
r0 := binary.LittleEndian.Uint16(src[0:])
r1 := binary.LittleEndian.Uint16(src[2:])
r2 := binary.LittleEndian.Uint16(src[4:])
r3 := binary.LittleEndian.Uint16(src[6:])
j := 63
for j >= 44 {
// unmix r3
r3 = rotl16(r3, 16-5)
r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0)
j--
// unmix r2
r2 = rotl16(r2, 16-3)
r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3)
j--
// unmix r1
r1 = rotl16(r1, 16-2)
r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2)
j--
// unmix r0
r0 = rotl16(r0, 16-1)
r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1)
j--
}
r3 = r3 - c.k[r2&63]
r2 = r2 - c.k[r1&63]
r1 = r1 - c.k[r0&63]
r0 = r0 - c.k[r3&63]
for j >= 20 {
// unmix r3
r3 = rotl16(r3, 16-5)
r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0)
j--
// unmix r2
r2 = rotl16(r2, 16-3)
r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3)
j--
// unmix r1
r1 = rotl16(r1, 16-2)
r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2)
j--
// unmix r0
r0 = rotl16(r0, 16-1)
r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1)
j--
}
r3 = r3 - c.k[r2&63]
r2 = r2 - c.k[r1&63]
r1 = r1 - c.k[r0&63]
r0 = r0 - c.k[r3&63]
for j >= 0 {
// unmix r3
r3 = rotl16(r3, 16-5)
r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0)
j--
// unmix r2
r2 = rotl16(r2, 16-3)
r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3)
j--
// unmix r1
r1 = rotl16(r1, 16-2)
r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2)
j--
// unmix r0
r0 = rotl16(r0, 16-1)
r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1)
j--
}
binary.LittleEndian.PutUint16(dst[0:], r0)
binary.LittleEndian.PutUint16(dst[2:], r1)
binary.LittleEndian.PutUint16(dst[4:], r2)
binary.LittleEndian.PutUint16(dst[6:], r3)
}
+105
View File
@@ -0,0 +1,105 @@
package rc2
import (
"bytes"
"encoding/hex"
"testing"
)
func TestEncryptDecrypt(t *testing.T) {
var tests = []struct {
key string
plain string
cipher string
t1 int
}{
{
"0000000000000000",
"0000000000000000",
"ebb773f993278eff",
63,
},
{
"ffffffffffffffff",
"ffffffffffffffff",
"278b27e42e2f0d49",
64,
},
{
"3000000000000000",
"1000000000000001",
"30649edf9be7d2c2",
64,
},
{
"88",
"0000000000000000",
"61a8a244adacccf0",
64,
},
{
"88bca90e90875a",
"0000000000000000",
"6ccf4308974c267f",
64,
},
{
"88bca90e90875a7f0f79c384627bafb2",
"0000000000000000",
"1a807d272bbe5db1",
64,
},
{
"88bca90e90875a7f0f79c384627bafb2",
"0000000000000000",
"2269552ab0f85ca6",
128,
},
{
"88bca90e90875a7f0f79c384627bafb216f80a6f85920584c42fceb0be255daf1e",
"0000000000000000",
"5b78d3a43dfff1f1",
129,
},
}
for _, tt := range tests {
k, _ := hex.DecodeString(tt.key)
p, _ := hex.DecodeString(tt.plain)
c, _ := hex.DecodeString(tt.cipher)
b, _ := New(k, tt.t1)
var dst [8]byte
b.Encrypt(dst[:], p)
if !bytes.Equal(dst[:], c) {
t.Errorf("encrypt failed: got % 2x wanted % 2x\n", dst, c)
}
b.Decrypt(dst[:], c)
if !bytes.Equal(dst[:], p) {
t.Errorf("decrypt failed: got % 2x wanted % 2x\n", dst, p)
}
}
}
func BenchmarkEncrypt(b *testing.B) {
r, _ := New([]byte{0, 0, 0, 0, 0, 0, 0, 0}, 64)
b.ResetTimer()
var src [8]byte
for i := 0; i < b.N; i++ {
r.Encrypt(src[:], src[:])
}
}
func BenchmarkDecrypt(b *testing.B) {
r, _ := New([]byte{0, 0, 0, 0, 0, 0, 0, 0}, 64)
b.ResetTimer()
var src [8]byte
for i := 0; i < b.N; i++ {
r.Decrypt(src[:], src[:])
}
}
+67
View File
@@ -0,0 +1,67 @@
package pkcs12
import (
"crypto/x509/pkix"
"encoding/asn1"
"errors"
)
//see https://tools.ietf.org/html/rfc7292#appendix-D
var (
oidPkcs8ShroudedKeyBagType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 12, 10, 1, 2}
oidCertBagType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 12, 10, 1, 3}
oidCertTypeX509Certificate = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 22, 1}
)
type certBag struct {
Id asn1.ObjectIdentifier
Data []byte `asn1:"tag:0,explicit"`
}
func getAlgorithmParams(salt []byte, iterations int) (asn1.RawValue, error) {
params := pbeParams{
Salt: salt,
Iterations: iterations,
}
return convertToRawVal(params)
}
func encodePkcs8ShroudedKeyBag(privateKey interface{}, password []byte) (bytes []byte, err error) {
privateKeyBytes, err := marshalPKCS8PrivateKey(privateKey)
if err != nil {
return nil, errors.New("pkcs12: error encoding PKCS#8 private key: " + err.Error())
}
salt, err := makeSalt(pbeSaltSizeBytes)
if err != nil {
return nil, errors.New("pkcs12: error creating PKCS#8 salt: " + err.Error())
}
pkData, err := pbEncrypt(privateKeyBytes, salt, password, pbeIterationCount)
if err != nil {
return nil, errors.New("pkcs12: error encoding PKCS#8 shrouded key bag when encrypting cert bag: " + err.Error())
}
params, err := getAlgorithmParams(salt, pbeIterationCount)
if err != nil {
return nil, errors.New("pkcs12: error encoding PKCS#8 shrouded key bag algorithm's parameters: " + err.Error())
}
pkinfo := encryptedPrivateKeyInfo{
AlgorithmIdentifier: pkix.AlgorithmIdentifier{
Algorithm: oidPbeWithSHAAnd3KeyTripleDESCBC,
Parameters: params,
},
EncryptedData: pkData,
}
bytes, err = asn1.Marshal(pkinfo)
if err != nil {
return nil, errors.New("pkcs12: error encoding PKCS#8 shrouded key bag: " + err.Error())
}
return bytes, err
}
+102
View File
@@ -0,0 +1,102 @@
package pkcs12
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/asn1"
"fmt"
"testing"
)
func decodePkcs8ShroudedKeyBag(asn1Data, password []byte) (privateKey interface{}, err error) {
pkinfo := new(encryptedPrivateKeyInfo)
if _, err = asn1.Unmarshal(asn1Data, pkinfo); err != nil {
err = fmt.Errorf("error decoding PKCS8 shrouded key bag: %v", err)
return nil, err
}
pkData, err := pbDecrypt(pkinfo, password)
if err != nil {
err = fmt.Errorf("error decrypting PKCS8 shrouded key bag: %v", err)
return
}
rv := new(asn1.RawValue)
if _, err = asn1.Unmarshal(pkData, rv); err != nil {
err = fmt.Errorf("could not decode decrypted private key data")
}
if privateKey, err = x509.ParsePKCS8PrivateKey(pkData); err != nil {
err = fmt.Errorf("error parsing PKCS8 private key: %v", err)
return nil, err
}
return
}
// Assert the default algorithm parameters are in the correct order,
// and default to the correct value. Defaults are based on OpenSSL.
// 1. IterationCount, defaults to 2,048 long.
// 2. Salt, is 8 bytes long.
func TestDefaultAlgorithmParametersPkcs8ShroudedKeyBag(t *testing.T) {
privateKey, err := rsa.GenerateKey(rand.Reader, 512)
if err != nil {
t.Fatalf("failed to generate a private key: %s", err)
}
password := []byte("sesame")
bytes, err := encodePkcs8ShroudedKeyBag(privateKey, password)
if err != nil {
t.Fatalf("failed to encode PKCS#8 shrouded key bag: %s", err)
}
var pkinfo encryptedPrivateKeyInfo
rest, err := asn1.Unmarshal(bytes, &pkinfo)
if err != nil {
t.Fatalf("failed to unmarshal encryptedPrivateKeyInfo %s", err)
}
if len(rest) != 0 {
t.Fatalf("unexpected trailing bytes of len=%d, bytes=%x", len(rest), rest)
}
var params pbeParams
rest, err = asn1.Unmarshal(pkinfo.GetAlgorithm().Parameters.FullBytes, &params)
if err != nil {
t.Fatalf("failed to unmarshal encryptedPrivateKeyInfo %s", err)
}
if len(rest) != 0 {
t.Fatalf("unexpected trailing bytes of len=%d, bytes=%x", len(rest), rest)
}
if params.Iterations != pbeIterationCount {
t.Errorf("expected iteration count to be %d, but actual=%d", pbeIterationCount, params.Iterations)
}
if len(params.Salt) != pbeSaltSizeBytes {
t.Errorf("expected the number of salt bytes to be %d, but actual=%d", pbeSaltSizeBytes, len(params.Salt))
}
}
func TestRoundTripPkcs8ShroudedKeyBag(t *testing.T) {
privateKey, err := rsa.GenerateKey(rand.Reader, 512)
if err != nil {
t.Fatalf("failed to generate a private key: %s", err)
}
password := []byte("sesame")
bytes, err := encodePkcs8ShroudedKeyBag(privateKey, password)
if err != nil {
t.Fatalf("failed to encode PKCS#8 shrouded key bag: %s", err)
}
key, err := decodePkcs8ShroudedKeyBag(bytes, password)
if err != nil {
t.Fatalf("failed to decode PKCS#8 shrouded key bag: %s", err)
}
actualPrivateKey := key.(*rsa.PrivateKey)
if actualPrivateKey.D.Cmp(privateKey.D) != 0 {
t.Fatalf("failed to round-trip rsa.PrivateKey.D")
}
}
+1
View File
@@ -31,6 +31,7 @@ type Config struct {
Pull bool
RunCommand []string `mapstructure:"run_command"`
Volumes map[string]string
Privileged bool `mapstructure:"privileged"`
// This is used to login to dockerhub to pull a private base container. For
// pushing to dockerhub, see the docker post-processors
+1
View File
@@ -64,6 +64,7 @@ type ContainerConfig struct {
Image string
RunCommand []string
Volumes map[string]string
Privileged bool
}
// This is the template that is used for the RunCommand in the ContainerConfig.
+3
View File
@@ -210,6 +210,9 @@ func (d *DockerDriver) StartContainer(config *ContainerConfig) (string, error) {
// Args that we're going to pass to Docker
args := []string{"run"}
if config.Privileged {
args = append(args, "--privileged")
}
for host, guest := range config.Volumes {
args = append(args, "-v", fmt.Sprintf("%s:%s", host, guest))
}

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