Compare commits

...

115 Commits

Author SHA1 Message Date
Rickard von Essen 074b170ed4 Fixed exec bit 2018-08-15 16:13:34 +02:00
Rickard von Essen b8adf689fd Check that only certain files are executable 2018-08-15 16:08:50 +02:00
Rickard von Essen fa7b9da808 Updated CHANGELOG.md 2018-08-15 15:55:30 +02:00
Rickard von Essen 25452945d7 Merge pull request #6546 from mzupan/do-add-tags
Add tags to DigitalOcean Droplets
2018-08-15 15:52:32 +02:00
Rickard von Essen 889c89ec79 Validate tags 2018-08-15 15:27:00 +02:00
Rickard von Essen dc111004df Updated CHANGELOG.md 2018-08-15 13:37:21 +02:00
Rickard von Essen 9e9f0d2ab9 Merge pull request #6570 from cb-oath/openstack-builder-ports
add support for ports to the OpenStack builder
2018-08-15 13:35:48 +02:00
Rickard von Essen c962e7b856 Simplified loop code 2018-08-15 13:12:47 +02:00
Megan Marsh ce282cee21 Merge pull request #6583 from azr/d-contributing-tweaks
[docs] split go and packer dev setups
2018-08-14 12:26:53 -07:00
Adrien Delorme 2e151f3cd0 Spelling & checks 2018-08-14 09:49:24 +02:00
Adrien Delorme bef5c842af re add the go 1.7 min version warning 2018-08-13 12:53:41 +02:00
Adrien Delorme 1d025c005f Link to godocs install steps 2018-08-13 12:50:55 +02:00
Adrien Delorme 51b9065497 split go and packer dev setups 2018-08-13 12:40:42 +02:00
Christopher Boumenot 39fc8593de Merge pull request #6576 from double16/vagrant-azure
Add to vagrant post-processor support for Azure
2018-08-09 12:36:22 -07:00
Patrick Double 2868971a9b Fixes per code review 2018-08-09 07:14:14 -05:00
Patrick Double d796edc783 Add to vagrant post-processor support for Azure 2018-08-08 10:04:28 -05:00
Megan Marsh f79df4c440 Merge pull request #6574 from hashicorp/fix_6573
remove duplicate code from chef provisioner
2018-08-07 10:26:48 -07:00
Megan Marsh eb685b7140 remove duplicate code from chef provisioner 2018-08-07 10:01:06 -07:00
Megan Marsh 5585855265 Merge pull request #5165 from bennu/esxi-remote-cache
vmware-iso:esxi5 using ISO files uploaded by packer to datastore
2018-08-06 14:31:19 -07:00
Megan Marsh 99d1a1a297 Merge pull request #6541 from rsclarke/qemu-ssh-agent
add ssh agent support for qemu builder
2018-08-06 13:43:26 -07:00
Megan Marsh 57dd1c3ca2 Merge pull request #6543 from thedrow/lxc-root-path
Determine lxc root according to the running user
2018-08-06 11:34:01 -07:00
Megan Marsh 2854bcd739 Merge pull request #6564 from nyetwurk/patch-1
Document the `launch_config` option of lxd/builder
2018-08-06 11:33:28 -07:00
Megan Marsh 1d0e0f82dd Merge pull request #6559 from arizvisa/GH-6491
Emit both the host and the communicator to the user during StepConnect.
2018-08-06 11:32:21 -07:00
chbell43 aaa42543e6 fix formatting 2018-08-03 21:32:34 +00:00
chbell43 5f9d4b729f add support for ports to the OpenStack builder
For networks that have multiple subnets, we may want to target a single
subnet.  OpenStack doesn't let you target a single subnet in a network
and so you need to make a port.
2018-08-03 20:46:36 +00:00
Nye Liu 80bd3b99d0 Fix typo, cosmetic whitespace 2018-08-02 20:05:42 +00:00
Megan Marsh 0633189c98 Merge pull request #6563 from nyetwurk/patch-2
Fix issue #6561 - Pass "--config" option correctly to "lxc launch"
2018-08-02 12:58:09 -07:00
Nye Liu 6d6212b75e Fix issue #6561 - Pass "--config" option correctly to "lxc launch" 2018-08-02 19:37:53 +00:00
Nye Liu 8458b60b24 Document the config option to the lxd builder. 2018-08-02 12:29:17 -07:00
Megan Marsh f3ef599f1c Merge pull request #6557 from arizvisa/GH-6431
Updated common/download.go to handle when a connection error happens …
2018-08-02 11:32:04 -07:00
Megan Marsh 7cbaac4acd Merge pull request #6554 from adarobin/googlecompute_update_docs
Update googlecompute docs on images from scratch
2018-08-02 11:15:33 -07:00
Christopher Boumenot 650a0f3c27 Merge pull request #6558 from boumenot/pr-Azure-clean_image_name
azure: implement clean_image_name
2018-08-02 09:28:34 -07:00
Christopher Boumenot a3e6153068 azure: implement clean_image_name 2018-08-01 10:54:45 -07:00
Megan Marsh 43a410b161 Merge pull request #6553 from lmayorga1980/master
#6297 add a documentation note for windows 2016 images
2018-08-01 10:15:50 -07:00
lmayorga 9ff41d4051 update note restriction for amazon windows 2016 amis only 2018-08-01 08:45:38 -04:00
Adam Robinson e74706c761 update googlecompute docs on images from scratch 2018-07-31 16:08:07 -04:00
lmayorga 5fff424ad5 #6297 add a documentation note for windows 2016 images 2018-07-31 15:09:41 -04:00
Rickard von Essen fe9e1bc9af Merge pull request #6550 from felixonmars/patch-1
Fix a typo in config_test.go
2018-07-31 09:57:33 +02:00
Felix Yan f69ab4ed77 Fix a typo in config_test.go 2018-07-31 15:19:45 +08:00
Matthew Hooker 38280ca90a fix markdown formatting 2018-07-30 16:28:35 -07:00
Megan Marsh efb3602447 Merge pull request #6544 from thedrow/lxc-copy-files
Fix file copying for unprivileged LXC containers
2018-07-30 12:29:33 -07:00
Mike Zupan 9247a3c564 adding in th docs 2018-07-30 11:27:57 -06:00
Mike Zupan 11271ead59 Change name to tags 2018-07-30 07:55:06 -06:00
Mike Zupan 7081fe990b Adding in droplet tags on creation 2018-07-30 07:52:40 -06:00
Omer Katz 26dd6441e0 Locate lxc root directory when exporting as well. 2018-07-29 16:14:17 +03:00
Omer Katz 808df82eea Remove privilege escalation with sudo when copying files. Preserve file permission mapping when copying files. 2018-07-29 16:03:01 +03:00
Omer Katz e5b740e223 Determine lxc root according to the running user. 2018-07-29 13:16:41 +03:00
Ali Rizvi-Santiago a3cec4f274 Emit both the host and the communicator to the user during StepConnect. 2018-07-29 02:18:26 -05:00
Ali Rizvi-Santiago 71e43d0b7f Updated common/download.go to handle when a connection error happens (response is nil), and reformatted the error that's returned when an HTTP error occurs. 2018-07-28 19:09:29 -05:00
Megan Marsh 07cafb80ec Merge pull request #6514 from sharmaanshul2102/6276-amazon-ebbsurrogate
amazon-ebssurrogate clean up volumes
2018-07-27 13:33:07 -07:00
rsclarke 887e694534 add ssh agent support for qemu builder 2018-07-27 20:24:06 +08:00
Anshul Sharma c1edcd3774 amazon-ebssurrogate clean up volumes 2018-07-26 09:38:59 +03:00
Megan Marsh 5fb6f69ad7 Merge pull request #6525 from hashicorp/fix_6524
don't fail if you can't find abs or relative path.
2018-07-25 09:23:22 -07:00
bugbuilder 2c4f703ff8 Merge 2018-07-24 20:54:08 -04:00
Megan Marsh c8970b86eb Merge pull request #6529 from hashicorp/fix_6527
fix crash caused by invalid datacenter url
2018-07-24 16:02:37 -07:00
Christopher Boumenot 9c6b4287e5 Merge pull request #6480 from hashicorp/pr-azure-password-requirements
azure: satisfy Azure password requirements
2018-07-24 09:58:54 -07:00
Megan Marsh 5bbb6633cf Merge pull request #6279 from ChrisLundquist/clundquist/lxc-perms
[WIP] Unpriviliged LXC containers
2018-07-24 09:34:21 -07:00
Megan Marsh 8f1eb5a61b fix crash caused by invalid datacenter url 2018-07-23 16:12:21 -07:00
Megan Marsh 07b6bc0c4f Merge pull request #6504 from sharmaansh/6309-amazon-chroot-create-volume-tags
amazon-chroot: Add tags on CreateVolume
2018-07-23 14:58:17 -07:00
Matthew Hooker 4495f93478 update changelog 2018-07-23 13:53:22 -07:00
Matthew Hooker 9bb0681586 Merge pull request #6423 from hashicorp/fix5513
cmd/validate: Warn users if configs need fixing
2018-07-23 13:52:28 -07:00
Megan Marsh d2823622e5 Merge pull request #6249 from iammattcoleman/add-use_backing_file
qemu builder: add the 'use_backing_file' setting for QCOW2 images
2018-07-23 13:40:04 -07:00
Megan Marsh 66c45273fb Merge pull request #6494 from double16/vagrant-docker
Vagrant post-processor for using a Docker image
2018-07-23 12:18:52 -07:00
Megan Marsh 5ef8b55559 need log import 2018-07-23 10:34:05 -07:00
Megan Marsh a2f5fbadf6 don't fail if you can't find abs or relative path. 2018-07-23 09:54:25 -07:00
Patrick Double 4f9a91012f Change docker-push to return docker-import artifact 2018-07-20 15:27:29 -05:00
Anshul Sharma 431cbb2ad5 Added Docs 2018-07-20 09:04:49 +03:00
Patrick Double ce08eb8b7c Cleanup docs 2018-07-19 20:41:26 -05:00
Megan Marsh a5a6b1ab58 Merge pull request #6501 from Wenzel/expose_ansible_packer_http_addr
Expose ansible packer_http_addr extra var
2018-07-18 17:18:46 -07:00
Megan Marsh 9c2f44be83 Merge pull request #6503 from Wenzel/expose_packer_http_addr_shell_local
Expose PACKER_HTTP_ADDR environment variable for shell-local
2018-07-18 17:02:34 -07:00
Matthew Hooker 814c1cf2b2 spellfix 2018-07-18 13:00:45 -07:00
Andrew Pryde 67f039509a Merge pull request #6498 from neumayer/ocimetadata
Allow instance metadata to be specified in config
2018-07-18 18:27:27 +01:00
Megan Marsh fe15bc53a8 Merge pull request #6450 from shadycuz/ansible_winrm
Feature: Add randomly generated win_rm password to the ansible provisioner
2018-07-18 09:49:48 -07:00
Megan Marsh e8a7d948db update docs 2018-07-18 09:42:15 -07:00
Anshul Sharma 885ecb0790 Issue-6309 Amazon Chroot Provider
-  Add tags on CreateVolume
2018-07-18 13:01:15 +03:00
Megan Marsh e146973d08 change implementation to set winrm password in way that matches powershell and shell-local implementations; sanitize logs 2018-07-17 16:39:50 -07:00
Matthew Hooker 9384f322f6 Prevent make fmt from failing in default case
Apologies for the terrible hack. This just makes sure we always have
a default file to format, so if the unformatted list is empty
we don't error.
2018-07-17 13:30:10 -07:00
Patrick Double c7fef8d22a Update vagrant post-process docs 2018-07-17 15:10:53 -05:00
Patrick Double 066b364873 Remove packer.docker from vagrant post processor builtins 2018-07-17 15:07:22 -05:00
Mathieu Tarral 430d9389be website: add documentation for packer_http_addr extra variable 2018-07-17 19:58:08 +03:00
Mathieu Tarral 3450b6fd6f ansible: expose packer_http_addr extra var 2018-07-17 19:58:07 +03:00
Megan Marsh a658b4a94b Merge pull request #6502 from neumayer/fixtypo
Fix typo in oci documentation
2018-07-17 09:37:30 -07:00
Megan Marsh 23cdaaecb4 Merge pull request #6497 from KohlsTechnology/googlecompute-import-docs-fix
Add missing link to googlecompute-import post-processor docs
2018-07-17 09:22:00 -07:00
Mathieu Tarral 221761d97a shell-local: add docs for PACKER_HTTP_ADDR env var 2018-07-17 18:50:40 +03:00
Mathieu Tarral edcc0b3853 shell-local: expose PACKER_HTTP_ADDR env var 2018-07-17 18:49:36 +03:00
Robert Neumayer 990d139d45 Fix typo in oci documentation 2018-07-17 17:46:23 +02:00
xxx 7630268e1d Incorporate review comments 2018-07-17 17:41:19 +02:00
Patrick Double a301145ae1 Allow docker build as input to vagrant, docs 2018-07-17 09:41:18 -05:00
Sean Malloy cf04fd55ea Add missing link to googlecompute-import post-processor docs 2018-07-16 20:40:19 -05:00
Levi Blaney f5e17d1cad changed wording from builder to provisoner 2018-07-16 19:13:05 -04:00
Levi Blaney a5493e4906 added the builders that set_winrm_passwd works with 2018-07-16 17:26:38 -04:00
Patrick Double 1781d352a5 Add Vagrantfile fragment with docker tag specified 2018-07-16 15:35:02 -05:00
Megan Marsh b28098e0f7 update to version 1.2.6-dev 2018-07-16 13:25:32 -07:00
Levi 17074cda2c added set_winrm_passwd usage instructions 2018-07-14 15:09:48 -04:00
Levi 6646d42490 updated function calls to include buildname and changed variable names 2018-07-14 14:39:38 -04:00
Levi 68ec630fde added function to retreive winrm password from commonhelper 2018-07-14 11:25:04 -04:00
Levi 636cec8f2b added commonhelper import 2018-07-14 11:25:01 -04:00
Levi 37fd50995f added parameter for setting packer password as env variable 2018-07-14 11:25:00 -04:00
Christopher Boumenot fa7f54cb55 azure: satisfy Azure password requirements 2018-07-11 15:32:45 -07:00
Patrick Double e7fc651f60 First cut at vagrant post-processor for docker 2018-07-06 17:11:24 -05:00
Matthew Hooker dde6805ee8 ignore empty top-level config keys when vetting fix 2018-07-02 13:57:11 -07:00
Robert Neumayer 22e5523faa Allow instance metadata to be specified in config 2018-07-02 10:48:08 +02:00
Matthew Hooker a5e29e68da cmd/validate: notify user if config is "fixable" 2018-06-25 22:21:16 -07:00
Chris Lundquist c925a02f82 don't chown to close the security issue 2018-05-16 21:40:22 +00:00
Matt Coleman 3192f5e0da qemu builder: add the 'use_backing_file' setting for QCOW2 images 2018-05-16 10:22:50 -04:00
bugbuilder f7b45312f1 Removing skip attribute 2017-11-10 23:58:24 -03:00
bugbuilder be2afccb85 Revamped the process to verify remote cache. 2017-11-10 23:55:26 -03:00
bugbuilder 463d87adcd Merge branch 'master' of https://github.com/hashicorp/packer into esxi-remote-cache 2017-11-10 23:08:09 -03:00
bugbuilder 15eb338596 Cleaning refactoring name errors x2 2017-07-24 00:30:55 -04:00
bugbuilder f31f154237 Cleaning refactoring name errors 2017-07-24 00:17:18 -04:00
bugbuilder b50e279d8a Making visible verify cache step 2017-07-24 00:11:30 -04:00
bugbuilder d4e0847a74 remove unnecessary initialization 2017-07-23 14:16:03 -04:00
bugbuilder 22aa89db27 file scheme has prioriry as remote targetPath 2017-07-23 14:11:48 -04:00
bugbuilder 84ad413e23 Set remote iso path 2017-07-23 03:20:06 -04:00
bugbuilder 4023b618b4 Verify remote cache for ESXi 2017-07-23 01:31:46 -04:00
84 changed files with 3497 additions and 197 deletions
+16 -19
View File
@@ -48,45 +48,42 @@ can quickly merge or address your contributions.
5. The issue is closed.
## Setting up Go to work on Packer
## Setting up Go
If you have never worked with Go before, you will have to complete the following
steps in order to be able to compile and test Packer. These instructions target
If you have never worked with Go before, you will have to install its
runtime in order to build packer.
1. [Install go](https://golang.org/doc/install#install)
## Setting up Packer for dev
If/when you have go installed you can already `go get` packer and `make` in
order to compile and test Packer. These instructions target
POSIX-like environments (Mac OS X, Linux, Cygwin, etc.) so you may need to
adjust them for Windows or other shells.
The instructions below are for go 1.7. or later.
1. [Download](https://golang.org/dl) and install Go. The instructions below are
for go 1.7. Earlier versions of Go are no longer supported.
2. Set and export the `GOPATH` environment variable and update your `PATH`. For
example, you can add the following to your `.bash_profile` (or comparable
shell startup scripts):
```
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
```
3. Download the Packer source (and its dependencies) by running
1. Download the Packer source (and its dependencies) by running
`go get github.com/hashicorp/packer`. This will download the Packer source to
`$GOPATH/src/github.com/hashicorp/packer`.
4. When working on Packer, first `cd $GOPATH/src/github.com/hashicorp/packer`
2. When working on Packer, first `cd $GOPATH/src/github.com/hashicorp/packer`
so you can run `make` and easily access other files. Run `make help` to get
information about make targets.
5. Make your changes to the Packer source. You can run `make` in
3. Make your changes to the Packer source. You can run `make` in
`$GOPATH/src/github.com/hashicorp/packer` to run tests and build the Packer
binary. Any compilation errors will be shown when the binaries are
rebuilding. If you don't have `make` you can simply run
`go build -o bin/packer .` from the project root.
6. After running building Packer successfully, use
4. After running building Packer successfully, use
`$GOPATH/src/github.com/hashicorp/packer/bin/packer` to build a machine and
verify your changes work. For instance:
`$GOPATH/src/github.com/hashicorp/packer/bin/packer build template.json`.
7. If everything works well and the tests pass, run `go fmt` on your code before
5. If everything works well and the tests pass, run `go fmt` on your code before
submitting a pull-request.
### Opening an Pull Request
+4
View File
@@ -2,6 +2,10 @@
### IMPROVEMENTS:
* builder/digitalocean: Add support for tagging to instances [GH-6546]
* builder/openstack: Add support for ports. [GH-6570]
* command/validate: Warn users if config needs fixing. [GH-6423]
### BUG FIXES:
## 1.2.5 (July 16, 2018)
+14 -2
View File
@@ -11,6 +11,8 @@ GOPATH=$(shell go env GOPATH)
# gofmt
UNFORMATTED_FILES=$(shell find . -not -path "./vendor/*" -name "*.go" | xargs gofmt -s -l)
EXECUTABLE_FILES=$(shell find . -type f -perm +111 | egrep -v '^\./(vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats)' | egrep -v './provisioner/ansible/test-fixtures/exit1')
# Get the git commit
GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)
GIT_COMMIT=$(shell git rev-parse --short HEAD)
@@ -43,6 +45,7 @@ package:
@sh -c "$(CURDIR)/scripts/dist.sh $(VERSION)"
deps:
@go get golang.org/x/tools/cmd/goimports
@go get golang.org/x/tools/cmd/stringer
@go get -u github.com/mna/pigeon
@go get github.com/kardianos/govendor
@@ -61,7 +64,7 @@ dev: deps ## Build and install a development build
@cp $(GOPATH)/bin/packer pkg/$(GOOS)_$(GOARCH)
fmt: ## Format Go code
@gofmt -w -s $(UNFORMATTED_FILES)
@gofmt -w -s main.go $(UNFORMATTED_FILES)
fmt-check: ## Check go code formatting
@echo "==> Checking that code complies with gofmt requirements..."
@@ -74,6 +77,15 @@ fmt-check: ## Check go code formatting
echo "Check passed."; \
fi
mode-check: ## Check that only certain files are executable
@echo "==> Checking that only certain files are executable..."
@if [ ! -z "$(EXECUTABLE_FILES)" ]; then \
echo "These files should not be executable or they must be white listed in the Makefile:"; \
echo "$(EXECUTABLE_FILES)" | xargs -n1; \
exit 1; \
else \
echo "Check passed."; \
fi
fmt-docs:
@find ./website/source/docs -name "*.md" -exec pandoc --wrap auto --columns 79 --atx-headers -s -f "markdown_github+yaml_metadata_block" -t "markdown_github+yaml_metadata_block" {} -o {} \;
@@ -89,7 +101,7 @@ generate: deps ## Generate dynamically generated code
goimports -w common/bootcommand/boot_command.go
gofmt -w command/plugin.go
test: deps fmt-check ## Run unit tests
test: deps fmt-check mode-check ## Run unit tests
@go test $(TEST) $(TESTARGS) -timeout=2m
@go tool vet $(VET) ; if [ $$? -eq 1 ]; then \
echo "ERROR: Vet found problems in the code."; \
Vendored
+4
View File
@@ -5,6 +5,10 @@ LINUX_BASE_BOX = "bento/ubuntu-16.04"
FREEBSD_BASE_BOX = "jen20/FreeBSD-12.0-CURRENT"
Vagrant.configure(2) do |config|
if Vagrant.has_plugin?("vagrant-cachier")
config.cache.scope = :box
end
# Compilation and development boxes
config.vm.define "linux", autostart: true, primary: true do |vmCfg|
vmCfg.vm.box = LINUX_BASE_BOX
+4
View File
@@ -44,6 +44,7 @@ type Config struct {
RootVolumeSize int64 `mapstructure:"root_volume_size"`
SourceAmi string `mapstructure:"source_ami"`
SourceAmiFilter awscommon.AmiFilterOptions `mapstructure:"source_ami_filter"`
RootVolumeTags awscommon.TagMap `mapstructure:"root_volume_tags"`
ctx interpolate.Context
}
@@ -67,6 +68,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
"ami_description",
"snapshot_tags",
"tags",
"root_volume_tags",
"command_wrapper",
"post_mount_commands",
"pre_mount_commands",
@@ -230,6 +232,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepPrepareDevice{},
&StepCreateVolume{
RootVolumeSize: b.config.RootVolumeSize,
RootVolumeTags: b.config.RootVolumeTags,
Ctx: b.config.ctx,
},
&StepAttachVolume{},
&StepEarlyUnflock{},
@@ -10,6 +10,7 @@ import (
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
// StepCreateVolume creates a new volume from the snapshot of the root
@@ -20,6 +21,8 @@ import (
type StepCreateVolume struct {
volumeId string
RootVolumeSize int64
RootVolumeTags awscommon.TagMap
Ctx interpolate.Context
}
func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@@ -28,6 +31,26 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
volTags, err := s.RootVolumeTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
if err != nil {
err := fmt.Errorf("Error tagging volumes: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Collect tags for tagging on resource creation
var tagSpecs []*ec2.TagSpecification
if len(volTags) > 0 {
runVolTags := &ec2.TagSpecification{
ResourceType: aws.String("volume"),
Tags: volTags,
}
tagSpecs = append(tagSpecs, runVolTags)
}
var createVolume *ec2.CreateVolumeInput
if config.FromScratch {
createVolume = &ec2.CreateVolumeInput{
@@ -69,6 +92,10 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
}
}
if len(tagSpecs) > 0 {
createVolume.SetTagSpecifications(tagSpecs)
volTags.Report(ui)
}
log.Printf("Create args: %+v", createVolume)
createVolumeResp, err := ec2conn.CreateVolume(createVolume)
+1 -1
View File
@@ -324,7 +324,7 @@ func applyEnvOverrides(envOverrides overridableWaitVars) []request.WaiterOption
}
if len(waitOpts) == 0 {
log.Printf("No AWS timeout and polling overrides have been set. " +
"Packer will defalt to waiter-specific delays and timeouts. If you would " +
"Packer will default to waiter-specific delays and timeouts. If you would " +
"like to customize the length of time between retries and max " +
"number of retries you may do so by setting the environment " +
"variables AWS_POLL_DELAY_SECONDS and AWS_MAX_ATTEMPTS to your " +
@@ -1,4 +1,4 @@
package ebs
package common
import (
"context"
@@ -6,7 +6,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@@ -14,16 +13,16 @@ import (
// stepCleanupVolumes cleans up any orphaned volumes that were not designated to
// remain after termination of the instance. These volumes are typically ones
// that are marked as "delete on terminate:false" in the source_ami of a build.
type stepCleanupVolumes struct {
BlockDevices common.BlockDevices
type StepCleanupVolumes struct {
BlockDevices BlockDevices
}
func (s *stepCleanupVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepCleanupVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
// stepCleanupVolumes is for Cleanup only
return multistep.ActionContinue
}
func (s *stepCleanupVolumes) Cleanup(state multistep.StateBag) {
func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
ec2conn := state.Get("ec2").(*ec2.EC2)
instanceRaw := state.Get("instance")
var instance *ec2.Instance
+1 -1
View File
@@ -191,7 +191,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
VpcId: b.config.VpcId,
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
},
&stepCleanupVolumes{
&awscommon.StepCleanupVolumes{
BlockDevices: b.config.BlockDevices,
},
instanceStep,
+3
View File
@@ -208,6 +208,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
VpcId: b.config.VpcId,
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
},
&awscommon.StepCleanupVolumes{
BlockDevices: b.config.BlockDevices,
},
instanceStep,
&awscommon.StepGetPassword{
Debug: b.config.PackerDebug,
+11 -2
View File
@@ -18,6 +18,9 @@ type AdditionalDiskArtifact struct {
}
type Artifact struct {
// OS type: Linux, Windows
OSType string
// VHD
StorageAccountLocation string
OSDiskUri string
@@ -29,20 +32,23 @@ type Artifact struct {
ManagedImageResourceGroupName string
ManagedImageName string
ManagedImageLocation string
ManagedImageId string
// Additional Disks
AdditionalDisks *[]AdditionalDiskArtifact
}
func NewManagedImageArtifact(resourceGroup, name, location string) (*Artifact, error) {
func NewManagedImageArtifact(osType, resourceGroup, name, location, id string) (*Artifact, error) {
return &Artifact{
ManagedImageResourceGroupName: resourceGroup,
ManagedImageName: name,
ManagedImageLocation: location,
ManagedImageId: id,
OSType: osType,
}, nil
}
func NewArtifact(template *CaptureTemplate, getSasUrl func(name string) string) (*Artifact, error) {
func NewArtifact(template *CaptureTemplate, getSasUrl func(name string) string, osType string) (*Artifact, error) {
if template == nil {
return nil, fmt.Errorf("nil capture template")
}
@@ -76,6 +82,7 @@ func NewArtifact(template *CaptureTemplate, getSasUrl func(name string) string)
}
return &Artifact{
OSType: osType,
OSDiskUri: vhdUri.String(),
OSDiskUriReadOnlySas: getSasUrl(getStorageUrlPath(vhdUri)),
TemplateUri: templateUri.String(),
@@ -142,9 +149,11 @@ func (a *Artifact) String() string {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("%s:\n\n", a.BuilderId()))
buf.WriteString(fmt.Sprintf("OSType: %s\n", a.OSType))
if a.isManagedImage() {
buf.WriteString(fmt.Sprintf("ManagedImageResourceGroupName: %s\n", a.ManagedImageResourceGroupName))
buf.WriteString(fmt.Sprintf("ManagedImageName: %s\n", a.ManagedImageName))
buf.WriteString(fmt.Sprintf("ManagedImageId: %s\n", a.ManagedImageId))
buf.WriteString(fmt.Sprintf("ManagedImageLocation: %s\n", a.ManagedImageLocation))
} else {
buf.WriteString(fmt.Sprintf("StorageAccountLocation: %s\n", a.StorageAccountLocation))
+20 -8
View File
@@ -28,7 +28,7 @@ func TestArtifactId(t *testing.T) {
},
}
artifact, err := NewArtifact(&template, getFakeSasUrl)
artifact, err := NewArtifact(&template, getFakeSasUrl, "Linux")
if err != nil {
t.Fatalf("err=%s", err)
}
@@ -59,7 +59,7 @@ func TestArtifactString(t *testing.T) {
},
}
artifact, err := NewArtifact(&template, getFakeSasUrl)
artifact, err := NewArtifact(&template, getFakeSasUrl, "Linux")
if err != nil {
t.Fatalf("err=%s", err)
}
@@ -80,6 +80,9 @@ func TestArtifactString(t *testing.T) {
if !strings.Contains(testSubject, "StorageAccountLocation: southcentralus") {
t.Errorf("Expected String() output to contain StorageAccountLocation")
}
if !strings.Contains(testSubject, "OSType: Linux") {
t.Errorf("Expected String() output to contain OSType")
}
}
func TestAdditionalDiskArtifactString(t *testing.T) {
@@ -107,7 +110,7 @@ func TestAdditionalDiskArtifactString(t *testing.T) {
},
}
artifact, err := NewArtifact(&template, getFakeSasUrl)
artifact, err := NewArtifact(&template, getFakeSasUrl, "Linux")
if err != nil {
t.Fatalf("err=%s", err)
}
@@ -128,6 +131,9 @@ func TestAdditionalDiskArtifactString(t *testing.T) {
if !strings.Contains(testSubject, "StorageAccountLocation: southcentralus") {
t.Errorf("Expected String() output to contain StorageAccountLocation")
}
if !strings.Contains(testSubject, "OSType: Linux") {
t.Errorf("Expected String() output to contain OSType")
}
if !strings.Contains(testSubject, "AdditionalDiskUri (datadisk-1): https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-datadisk-1.4085bb15-3644-4641-b9cd-f575918640b4.vhd") {
t.Errorf("Expected String() output to contain AdditionalDiskUri")
}
@@ -154,7 +160,7 @@ func TestArtifactProperties(t *testing.T) {
},
}
testSubject, err := NewArtifact(&template, getFakeSasUrl)
testSubject, err := NewArtifact(&template, getFakeSasUrl, "Linux")
if err != nil {
t.Fatalf("err=%s", err)
}
@@ -174,6 +180,9 @@ func TestArtifactProperties(t *testing.T) {
if testSubject.StorageAccountLocation != "southcentralus" {
t.Errorf("Expected StorageAccountLocation to be 'southcentral', but got %s", testSubject.StorageAccountLocation)
}
if testSubject.OSType != "Linux" {
t.Errorf("Expected OSType to be 'Linux', but got %s", testSubject.OSType)
}
}
func TestAdditionalDiskArtifactProperties(t *testing.T) {
@@ -201,7 +210,7 @@ func TestAdditionalDiskArtifactProperties(t *testing.T) {
},
}
testSubject, err := NewArtifact(&template, getFakeSasUrl)
testSubject, err := NewArtifact(&template, getFakeSasUrl, "Linux")
if err != nil {
t.Fatalf("err=%s", err)
}
@@ -221,6 +230,9 @@ func TestAdditionalDiskArtifactProperties(t *testing.T) {
if testSubject.StorageAccountLocation != "southcentralus" {
t.Errorf("Expected StorageAccountLocation to be 'southcentral', but got %s", testSubject.StorageAccountLocation)
}
if testSubject.OSType != "Linux" {
t.Errorf("Expected OSType to be 'Linux', but got %s", testSubject.OSType)
}
if testSubject.AdditionalDisks == nil {
t.Errorf("Expected AdditionalDisks to be not nil")
}
@@ -253,7 +265,7 @@ func TestArtifactOverHyphenatedCaptureUri(t *testing.T) {
},
}
testSubject, err := NewArtifact(&template, getFakeSasUrl)
testSubject, err := NewArtifact(&template, getFakeSasUrl, "Linux")
if err != nil {
t.Fatalf("err=%s", err)
}
@@ -266,7 +278,7 @@ func TestArtifactOverHyphenatedCaptureUri(t *testing.T) {
func TestArtifactRejectMalformedTemplates(t *testing.T) {
template := CaptureTemplate{}
_, err := NewArtifact(&template, getFakeSasUrl)
_, err := NewArtifact(&template, getFakeSasUrl, "Linux")
if err == nil {
t.Fatalf("Expected artifact creation to fail, but it succeeded.")
}
@@ -289,7 +301,7 @@ func TestArtifactRejectMalformedStorageUri(t *testing.T) {
},
}
_, err := NewArtifact(&template, getFakeSasUrl)
_, err := NewArtifact(&template, getFakeSasUrl, "Linux")
if err == nil {
t.Fatalf("Expected artifact creation to fail, but it succeeded.")
}
+4 -2
View File
@@ -255,7 +255,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
if b.config.isManagedImage() {
return NewManagedImageArtifact(b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, b.config.manageImageLocation)
managedImageID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/images/%s", b.config.SubscriptionID, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName)
return NewManagedImageArtifact(b.config.OSType, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, b.config.manageImageLocation, managedImageID)
} else if template, ok := b.stateBag.GetOk(constants.ArmCaptureTemplate); ok {
return NewArtifact(
template.(*CaptureTemplate),
@@ -266,7 +267,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
options.Expiry = time.Now().AddDate(0, 1, 0).UTC() // one month
sasUrl, _ := blob.GetSASURI(options)
return sasUrl
})
},
b.config.OSType)
}
return &Artifact{}, nil
+4 -4
View File
@@ -150,7 +150,7 @@ type Config struct {
winrmCertificate string
Comm communicator.Config `mapstructure:",squash"`
ctx *interpolate.Context
ctx interpolate.Context
//Cleanup
AsyncResourceGroupDelete bool `mapstructure:"async_resourcegroup_delete"`
@@ -258,10 +258,10 @@ func (c *Config) createCertificate() (string, error) {
func newConfig(raws ...interface{}) (*Config, []string, error) {
var c Config
c.ctx.Funcs = TemplateFuncs
err := config.Decode(&c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: c.ctx,
InterpolateContext: &c.ctx,
}, raws...)
if err != nil {
@@ -299,7 +299,7 @@ func newConfig(raws ...interface{}) (*Config, []string, error) {
}
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
assertRequiredParametersSet(&c, errs)
assertTagProperties(&c, errs)
+43
View File
@@ -0,0 +1,43 @@
package arm
import (
"bytes"
"text/template"
)
func isValidByteValue(b byte) bool {
if '0' <= b && b <= '9' {
return true
}
if 'a' <= b && b <= 'z' {
return true
}
if 'A' <= b && b <= 'Z' {
return true
}
return b == '.' || b == '_' || b == '-'
}
// Clean up image name by replacing invalid characters with "-"
// Names are not allowed to end in '.', '-', or '_' and are trimmed.
func templateCleanImageName(s string) string {
if ok, _ := assertManagedImageName(s, ""); ok {
return s
}
b := []byte(s)
newb := make([]byte, len(b))
for i := range newb {
if isValidByteValue(b[i]) {
newb[i] = b[i]
} else {
newb[i] = '-'
}
}
newb = bytes.TrimRight(newb, "-_.")
return string(newb)
}
var TemplateFuncs = template.FuncMap{
"clean_image_name": templateCleanImageName,
}
+49
View File
@@ -0,0 +1,49 @@
package arm
import "testing"
func TestTemplateCleanImageName(t *testing.T) {
vals := []struct {
origName string
expected string
}{
// test that valid name is unchanged
{
origName: "abcde-012345xyz",
expected: "abcde-012345xyz",
},
// test that colons are converted to hyphens
{
origName: "abcde-012345v1.0:0",
expected: "abcde-012345v1.0-0",
},
// Name starting with number is not valid, but not in scope of this
// function to correct
{
origName: "012345v1.0:0",
expected: "012345v1.0-0",
},
// Name over 80 chars is not valid, but not corrected by this function.
{
origName: "l012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
expected: "l012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
},
// Name cannot end in a -Name over 80 chars is not valid, but not corrected by this function.
{
origName: "abcde-:_",
expected: "abcde",
},
// Lost of special characters
{
origName: "My()./-_:&^ $%[]#'@name",
expected: "My--.--_-----------name",
},
}
for _, v := range vals {
name := templateCleanImageName(v.origName)
if name != v.expected {
t.Fatalf("template names do not match: expected %s got %s\n", v.expected, name)
}
}
}
+38 -3
View File
@@ -2,13 +2,19 @@ package arm
import (
"fmt"
"strings"
"github.com/hashicorp/packer/builder/azure/common"
)
const (
TempNameAlphabet = "0123456789bcdfghjklmnpqrstvwxyz"
TempPasswordAlphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
TempNameAlphabet = "0123456789bcdfghjklmnpqrstvwxyz"
numbers = "0123456789"
lowerCase = "abcdefghijklmnopqrstuvwxyz"
upperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
TempPasswordAlphabet = numbers + lowerCase + upperCase
)
type TempName struct {
@@ -39,8 +45,37 @@ func NewTempName() *TempName {
tempName.VirtualNetworkName = fmt.Sprintf("pkrvn%s", suffix)
tempName.ResourceGroupName = fmt.Sprintf("packer-Resource-Group-%s", suffix)
tempName.AdminPassword = common.RandomString(TempPasswordAlphabet, 32)
tempName.AdminPassword = generatePassword()
tempName.CertificatePassword = common.RandomString(TempPasswordAlphabet, 32)
return tempName
}
// generate a password that is acceptable to Azure
// Three of the four items must be met.
// 1. Contains an uppercase character
// 2. Contains a lowercase character
// 3. Contains a numeric digit
// 4. Contains a special character
func generatePassword() string {
var s string
for i := 0; i < 100; i++ {
s := common.RandomString(TempPasswordAlphabet, 32)
if !strings.ContainsAny(s, numbers) {
continue
}
if !strings.ContainsAny(s, lowerCase) {
continue
}
if !strings.ContainsAny(s, upperCase) {
continue
}
return s
}
// if an acceptable password cannot be generated in 100 tries, give up
return s
}
+14
View File
@@ -41,6 +41,20 @@ func TestTempNameShouldCreatePrefixedRandomNames(t *testing.T) {
}
}
func TestTempAdminPassword(t *testing.T) {
tempName := NewTempName()
if !strings.ContainsAny(tempName.AdminPassword, numbers) {
t.Errorf("Expected AdminPassword to contain at least one of '%s'!", numbers)
}
if !strings.ContainsAny(tempName.AdminPassword, lowerCase) {
t.Errorf("Expected AdminPassword to contain at least one of '%s'!", lowerCase)
}
if !strings.ContainsAny(tempName.AdminPassword, upperCase) {
t.Errorf("Expected AdminPassword to contain at least one of '%s'!", upperCase)
}
}
func TestTempNameShouldHaveSameSuffix(t *testing.T) {
tempName := NewTempName()
suffix := tempName.ComputeName[5:]
+13
View File
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"os"
"regexp"
"time"
"github.com/hashicorp/packer/common"
@@ -35,6 +36,7 @@ type Config struct {
DropletName string `mapstructure:"droplet_name"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
Tags []string `mapstructure:"tags"`
ctx interpolate.Context
}
@@ -121,6 +123,17 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
}
}
if c.Tags == nil {
c.Tags = make([]string, 0)
}
tagRe := regexp.MustCompile("^[[:alnum:]:_-]{1,255}$")
for _, t := range c.Tags {
if !tagRe.MatchString(t) {
errs = packer.MultiErrorAppend(errs, errors.New(fmt.Sprintf("invalid tag: %s", t)))
}
}
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}
@@ -49,6 +49,7 @@ func (s *stepCreateDroplet) Run(_ context.Context, state multistep.StateBag) mul
Monitoring: c.Monitoring,
IPv6: c.IPv6,
UserData: userData,
Tags: c.Tags,
})
if err != nil {
err := fmt.Errorf("Error creating droplet: %s", err)
+26
View File
@@ -1,7 +1,11 @@
package lxc
import (
"bytes"
"fmt"
"log"
"os/exec"
"strings"
)
// CommandWrapper is a type that given a command, will possibly modify that
@@ -13,3 +17,25 @@ type CommandWrapper func(string) (string, error)
func ShellCommand(command string) *exec.Cmd {
return exec.Command("/bin/sh", "-c", command)
}
func RunCommand(args ...string) error {
var stdout, stderr bytes.Buffer
log.Printf("Executing args: %#v", args)
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
stdoutString := strings.TrimSpace(stdout.String())
stderrString := strings.TrimSpace(stderr.String())
if _, ok := err.(*exec.ExitError); ok {
err = fmt.Errorf("Command error: %s", stderrString)
}
log.Printf("stdout: %s", stdoutString)
log.Printf("stderr: %s", stderrString)
return err
}
+9 -6
View File
@@ -59,7 +59,6 @@ func (c *LxcAttachCommunicator) Start(cmd *packer.RemoteCmd) error {
}
func (c *LxcAttachCommunicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
dst = filepath.Join(c.RootFs, dst)
log.Printf("Uploading to rootfs: %s", dst)
tf, err := ioutil.TempFile("", "packer-lxc-attach")
if err != nil {
@@ -68,7 +67,11 @@ func (c *LxcAttachCommunicator) Upload(dst string, r io.Reader, fi *os.FileInfo)
defer os.Remove(tf.Name())
io.Copy(tf, r)
cpCmd, err := c.CmdWrapper(fmt.Sprintf("sudo cp %s %s", tf.Name(), dst))
attachCommand := []string{"cat", "%s", " | ", "lxc-attach"}
attachCommand = append(attachCommand, c.AttachOptions...)
attachCommand = append(attachCommand, []string{"--name", "%s", "--", "/bin/sh -c \"/bin/cat > %s\""}...)
cpCmd, err := c.CmdWrapper(fmt.Sprintf(strings.Join(attachCommand, " "), tf.Name(), c.ContainerName, dst))
if err != nil {
return err
}
@@ -78,14 +81,14 @@ func (c *LxcAttachCommunicator) Upload(dst string, r io.Reader, fi *os.FileInfo)
// rename tempfile to match original file name. This makes sure that if file is being
// moved into a directory, the filename is preserved instead of a temp name.
adjustedTempName := filepath.Join(tfDir, (*fi).Name())
mvCmd, err := c.CmdWrapper(fmt.Sprintf("sudo mv %s %s", tf.Name(), adjustedTempName))
mvCmd, err := c.CmdWrapper(fmt.Sprintf("mv %s %s", tf.Name(), adjustedTempName))
if err != nil {
return err
}
defer os.Remove(adjustedTempName)
ShellCommand(mvCmd).Run()
// change cpCmd to use new file name as source
cpCmd, err = c.CmdWrapper(fmt.Sprintf("sudo cp %s %s", adjustedTempName, dst))
cpCmd, err = c.CmdWrapper(fmt.Sprintf(strings.Join(attachCommand, " "), adjustedTempName, c.ContainerName, dst))
if err != nil {
return err
}
@@ -100,7 +103,7 @@ func (c *LxcAttachCommunicator) UploadDir(dst string, src string, exclude []stri
// TODO: remove any file copied if it appears in `exclude`
dest := filepath.Join(c.RootFs, dst)
log.Printf("Uploading directory '%s' to rootfs '%s'", src, dest)
cpCmd, err := c.CmdWrapper(fmt.Sprintf("sudo cp -R %s/. %s", src, dest))
cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp -R %s/. %s", src, dest))
if err != nil {
return err
}
@@ -131,7 +134,7 @@ func (c *LxcAttachCommunicator) DownloadDir(src string, dst string, exclude []st
func (c *LxcAttachCommunicator) Execute(commandString string) (*exec.Cmd, error) {
log.Printf("Executing with lxc-attach in container: %s %s %s", c.ContainerName, c.RootFs, commandString)
attachCommand := []string{"sudo", "lxc-attach"}
attachCommand := []string{"lxc-attach"}
attachCommand = append(attachCommand, c.AttachOptions...)
attachCommand = append(attachCommand, []string{"--name", "%s", "--", "/bin/sh -c \"%s\""}...)
+13 -31
View File
@@ -1,15 +1,13 @@
package lxc
import (
"bytes"
"context"
"fmt"
"io"
"log"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
@@ -23,7 +21,16 @@ func (s *stepExport) Run(_ context.Context, state multistep.StateBag) multistep.
name := config.ContainerName
containerDir := fmt.Sprintf("/var/lib/lxc/%s", name)
lxc_dir := "/var/lib/lxc"
user, err := user.Current()
if err != nil {
log.Print("Cannot find current user. Falling back to /var/lib/lxc...")
}
if user.Uid != "0" && user.HomeDir != "" {
lxc_dir = filepath.Join(user.HomeDir, ".local", "share", "lxc")
}
containerDir := filepath.Join(lxc_dir, name)
outputPath := filepath.Join(config.OutputDir, "rootfs.tar.gz")
configFilePath := filepath.Join(config.OutputDir, "lxc-config")
@@ -47,7 +54,7 @@ func (s *stepExport) Run(_ context.Context, state multistep.StateBag) multistep.
_, err = io.Copy(configFile, originalConfigFile)
commands := make([][]string, 4)
commands := make([][]string, 3)
commands[0] = []string{
"lxc-stop", "--name", name,
}
@@ -57,13 +64,10 @@ func (s *stepExport) Run(_ context.Context, state multistep.StateBag) multistep.
commands[2] = []string{
"chmod", "+x", configFilePath,
}
commands[3] = []string{
"sh", "-c", "chown $USER:`id -gn` " + filepath.Join(config.OutputDir, "*"),
}
ui.Say("Exporting container...")
for _, command := range commands {
err := s.SudoCommand(command...)
err := RunCommand(command...)
if err != nil {
err := fmt.Errorf("Error exporting container: %s", err)
state.Put("error", err)
@@ -76,25 +80,3 @@ func (s *stepExport) Run(_ context.Context, state multistep.StateBag) multistep.
}
func (s *stepExport) Cleanup(state multistep.StateBag) {}
func (s *stepExport) SudoCommand(args ...string) error {
var stdout, stderr bytes.Buffer
log.Printf("Executing sudo command: %#v", args)
cmd := exec.Command("sudo", args...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
stdoutString := strings.TrimSpace(stdout.String())
stderrString := strings.TrimSpace(stderr.String())
if _, ok := err.(*exec.ExitError); ok {
err = fmt.Errorf("Sudo command error: %s", stderrString)
}
log.Printf("stdout: %s", stdoutString)
log.Printf("stderr: %s", stderrString)
return err
}
+13 -29
View File
@@ -1,13 +1,11 @@
package lxc
import (
"bytes"
"context"
"fmt"
"log"
"os/exec"
"os/user"
"path/filepath"
"strings"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
@@ -23,6 +21,13 @@ func (s *stepLxcCreate) Run(_ context.Context, state multistep.StateBag) multist
// TODO: read from env
lxc_dir := "/var/lib/lxc"
user, err := user.Current()
if err != nil {
log.Print("Cannot find current user. Falling back to /var/lib/lxc...")
}
if user.Uid != "0" && user.HomeDir != "" {
lxc_dir = filepath.Join(user.HomeDir, ".local", "share", "lxc")
}
rootfs := filepath.Join(lxc_dir, name, "rootfs")
if config.PackerForce {
@@ -30,7 +35,9 @@ func (s *stepLxcCreate) Run(_ context.Context, state multistep.StateBag) multist
}
commands := make([][]string, 3)
commands[0] = append(config.EnvVars, "lxc-create")
commands[0] = append(commands[0], "env")
commands[0] = append(commands[0], config.EnvVars...)
commands[0] = append(commands[0], "lxc-create")
commands[0] = append(commands[0], config.CreateOptions...)
commands[0] = append(commands[0], []string{"-n", name, "-t", config.Name, "--"}...)
commands[0] = append(commands[0], config.Parameters...)
@@ -42,8 +49,7 @@ func (s *stepLxcCreate) Run(_ context.Context, state multistep.StateBag) multist
ui.Say("Creating container...")
for _, command := range commands {
log.Printf("Executing sudo command: %#v", command)
err := s.SudoCommand(command...)
err := RunCommand(command...)
if err != nil {
err := fmt.Errorf("Error creating container: %s", err)
state.Put("error", err)
@@ -66,29 +72,7 @@ func (s *stepLxcCreate) Cleanup(state multistep.StateBag) {
}
ui.Say("Unregistering and deleting virtual machine...")
if err := s.SudoCommand(command...); err != nil {
if err := RunCommand(command...); err != nil {
ui.Error(fmt.Sprintf("Error deleting virtual machine: %s", err))
}
}
func (s *stepLxcCreate) SudoCommand(args ...string) error {
var stdout, stderr bytes.Buffer
log.Printf("Executing sudo command: %#v", args)
cmd := exec.Command("sudo", args...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
stdoutString := strings.TrimSpace(stdout.String())
stderrString := strings.TrimSpace(stderr.String())
if _, ok := err.(*exec.ExitError); ok {
err = fmt.Errorf("Sudo command error: %s", stderrString)
}
log.Printf("stdout: %s", stdoutString)
log.Printf("stderr: %s", stderrString)
return err
}
+1 -1
View File
@@ -26,7 +26,7 @@ func (s *stepLxdLaunch) Run(_ context.Context, state multistep.StateBag) multist
}
for k, v := range config.LaunchConfig {
launch_args = append(launch_args, fmt.Sprintf("--config %s=%s", k, v))
launch_args = append(launch_args, "--config", fmt.Sprintf("%s=%s", k, v))
}
ui.Say("Creating container...")
Executable → Regular
+1
View File
@@ -92,6 +92,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SourceImageName: b.config.SourceImageName,
SecurityGroups: b.config.SecurityGroups,
Networks: b.config.Networks,
Ports: b.config.Ports,
AvailabilityZone: b.config.AvailabilityZone,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
+1
View File
@@ -28,6 +28,7 @@ type RunConfig struct {
ReuseIps bool `mapstructure:"reuse_ips"`
SecurityGroups []string `mapstructure:"security_groups"`
Networks []string `mapstructure:"networks"`
Ports []string `mapstructure:"ports"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
InstanceName string `mapstructure:"instance_name"`
+8 -3
View File
@@ -18,6 +18,7 @@ type StepRunSourceServer struct {
SourceImageName string
SecurityGroups []string
Networks []string
Ports []string
AvailabilityZone string
UserData string
UserDataFile string
@@ -39,9 +40,13 @@ func (s *StepRunSourceServer) Run(_ context.Context, state multistep.StateBag) m
return multistep.ActionHalt
}
networks := make([]servers.Network, len(s.Networks))
for i, networkUuid := range s.Networks {
networks[i].UUID = networkUuid
networks := make([]servers.Network, len(s.Networks)+len(s.Ports))
i := 0
for ; i < len(s.Ports); i++ {
networks[i].Port = s.Ports[i]
}
for ; i < len(networks); i++ {
networks[i].UUID = s.Networks[i]
}
userData := []byte(s.UserData)
View File
+7
View File
@@ -48,6 +48,13 @@ type Config struct {
// Instance
InstanceName string `mapstructure:"instance_name"`
// Metadata optionally contains custom metadata key/value pairs provided in the
// configuration. While this can be used to set metadata["user_data"] the explicit
// "user_data" and "user_data_file" values will have precedence.
// An instance's metadata can be obtained from at http://169.254.169.254 on the
// launched instance.
Metadata map[string]string `mapstructure:"metadata"`
// UserData and UserDataFile file are both optional and mutually exclusive.
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
+4 -1
View File
@@ -29,11 +29,14 @@ func testConfig(accessConfFile *os.File) map[string]interface{} {
// Comm
"ssh_username": "opc",
"use_private_ip": false,
"metadata": map[string]string{
"key": "value",
},
}
}
func TestConfig(t *testing.T) {
// Shared set-up and defered deletion
// Shared set-up and deferred deletion
cfg, keyFile, err := baseTestConfigWithTmpKeyFile()
if err != nil {
+5
View File
@@ -42,6 +42,11 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
metadata := map[string]string{
"ssh_authorized_keys": publicKey,
}
if d.cfg.Metadata != nil {
for key, value := range d.cfg.Metadata {
metadata[key] = value
}
}
if d.cfg.UserData != "" {
metadata["user_data"] = d.cfg.UserData
}
+6
View File
@@ -99,6 +99,7 @@ type Config struct {
Format string `mapstructure:"format"`
Headless bool `mapstructure:"headless"`
DiskImage bool `mapstructure:"disk_image"`
UseBackingFile bool `mapstructure:"use_backing_file"`
MachineType string `mapstructure:"machine_type"`
NetDevice string `mapstructure:"net_device"`
OutputDir string `mapstructure:"output_directory"`
@@ -255,6 +256,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.DiskCompression = false
}
if b.config.UseBackingFile && !(b.config.DiskImage && b.config.Format == "qcow2") {
errs = packer.MultiErrorAppend(
errs, errors.New("use_backing_file can only be enabled for QCOW2 images and when disk_image is true"))
}
if _, ok := accels[b.config.Accelerator]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("invalid accelerator, only 'kvm', 'tcg', 'xen', 'hax', 'hvf', or 'none' are allowed"))
+43
View File
@@ -224,6 +224,49 @@ func TestBuilderPrepare_Format(t *testing.T) {
}
}
func TestBuilderPrepare_UseBackingFile(t *testing.T) {
var b Builder
config := testConfig()
config["use_backing_file"] = true
// Bad: iso_url is not a disk_image
config["disk_image"] = false
config["format"] = "qcow2"
b = Builder{}
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Bad: format is not 'qcow2'
config["disk_image"] = true
config["format"] = "raw"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Good: iso_url is a disk image and format is 'qcow2'
config["disk_image"] = true
config["format"] = "qcow2"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
var b Builder
config := testConfig()
+29 -5
View File
@@ -1,10 +1,15 @@
package qemu
import (
"fmt"
"net"
"os"
commonssh "github.com/hashicorp/packer/common/ssh"
"github.com/hashicorp/packer/communicator/ssh"
packerssh "github.com/hashicorp/packer/communicator/ssh"
"github.com/hashicorp/packer/helper/multistep"
gossh "golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
func commHost(state multistep.StateBag) (string, error) {
@@ -19,10 +24,29 @@ func commPort(state multistep.StateBag) (int, error) {
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
config := state.Get("config").(*Config)
auth := []gossh.AuthMethod{
gossh.Password(config.Comm.SSHPassword),
gossh.KeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)),
var auth []gossh.AuthMethod
if config.Comm.SSHAgentAuth {
authSock := os.Getenv("SSH_AUTH_SOCK")
if authSock == "" {
return nil, fmt.Errorf("SSH_AUTH_SOCK is not set")
}
sshAgent, err := net.Dial("unix", authSock)
if err != nil {
return nil, fmt.Errorf("Cannot connect to SSH Agent socket %q: %s", authSock, err)
}
auth = []gossh.AuthMethod{
gossh.PublicKeysCallback(agent.NewClient(sshAgent).Signers),
}
}
if config.Comm.SSHPassword != "" {
auth = append(auth,
gossh.Password(config.Comm.SSHPassword),
gossh.KeyboardInteractive(
packerssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)),
)
}
if config.Comm.SSHPrivateKey != "" {
+1 -1
View File
@@ -28,7 +28,7 @@ func (s *stepCopyDisk) Run(_ context.Context, state multistep.StateBag) multiste
path,
}
if config.DiskImage == false {
if !config.DiskImage || config.UseBackingFile {
return multistep.ActionContinue
}
+11 -3
View File
@@ -23,11 +23,19 @@ func (s *stepCreateDisk) Run(_ context.Context, state multistep.StateBag) multis
command := []string{
"create",
"-f", config.Format,
path,
fmt.Sprintf("%vM", config.DiskSize),
}
if config.DiskImage == true {
if config.UseBackingFile {
isoPath := state.Get("iso_path").(string)
command = append(command, "-b", isoPath)
}
command = append(command,
path,
fmt.Sprintf("%vM", config.DiskSize),
)
if config.DiskImage && !config.UseBackingFile {
return multistep.ActionContinue
}
+12 -1
View File
@@ -36,6 +36,17 @@ func (s *stepRemoteUpload) Run(_ context.Context, state multistep.StateBag) mult
checksum := config.ISOChecksum
checksumType := config.ISOChecksumType
if esx5, ok := remote.(*ESX5Driver); ok {
remotePath := esx5.cachePath(path)
if esx5.verifyChecksum(checksumType, checksum, remotePath) {
ui.Say("Remote cache was verified skipping remote upload...")
state.Put(s.Key, remotePath)
return multistep.ActionContinue
}
}
ui.Say(s.Message)
log.Printf("Remote uploading: %s", path)
newPath, err := remote.UploadISO(path, checksum, checksumType)
@@ -45,8 +56,8 @@ func (s *stepRemoteUpload) Run(_ context.Context, state multistep.StateBag) mult
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put(s.Key, newPath)
return multistep.ActionContinue
}
+55 -1
View File
@@ -1,13 +1,16 @@
package command
import (
"encoding/json"
"fmt"
"log"
"strings"
"github.com/hashicorp/packer/fix"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template"
"github.com/google/go-cmp/cmp"
"github.com/posener/complete"
)
@@ -80,6 +83,58 @@ func (c *ValidateCommand) Run(args []string) int {
}
}
// Check if any of the configuration is fixable
var rawTemplateData map[string]interface{}
input := make(map[string]interface{})
templateData := make(map[string]interface{})
json.Unmarshal(tpl.RawContents, &rawTemplateData)
for k, v := range rawTemplateData {
if vals, ok := v.([]interface{}); ok {
if len(vals) == 0 {
continue
}
}
templateData[strings.ToLower(k)] = v
input[strings.ToLower(k)] = v
}
// fix rawTemplateData into input
for _, name := range fix.FixerOrder {
var err error
fixer, ok := fix.Fixers[name]
if !ok {
panic("fixer not found: " + name)
}
input, err = fixer.Fix(input)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error checking against fixers: %s", err))
return 1
}
}
// delete empty top-level keys since the fixers seem to add them
// willy-nilly
for k := range input {
ml, ok := input[k].([]map[string]interface{})
if !ok {
continue
}
if len(ml) == 0 {
delete(input, k)
}
}
// marshal/unmarshal to make comparable to templateData
var fixedData map[string]interface{}
// Guaranteed to be valid json, so we can ignore errors
j, _ := json.Marshal(input)
json.Unmarshal(j, &fixedData)
if diff := cmp.Diff(templateData, fixedData); diff != "" {
c.Ui.Say("[warning] Fixable configuration found.")
c.Ui.Say("You may need to run `packer fix` to get your build to run")
c.Ui.Say("correctly. See debug log for more information.\n")
log.Printf("Fixable config differences:\n%s", diff)
}
if len(errs) > 0 {
c.Ui.Error("Template validation failed. Errors are shown below.\n")
for i, err := range errs {
@@ -89,7 +144,6 @@ func (c *ValidateCommand) Run(args []string) int {
c.Ui.Error("")
}
}
return 1
}
+25 -7
View File
@@ -278,12 +278,24 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error {
}
resp, err := httpClient.Do(req)
if err != nil {
log.Printf("[DEBUG] (download) Error making HTTP HEAD request: %s", err.Error())
if err != nil || resp == nil {
if resp == nil {
log.Printf("[DEBUG] (download) HTTP connection error: %s", err.Error())
} else if resp.StatusCode >= 400 && resp.StatusCode < 600 {
log.Printf("[DEBUG] (download) Non-successful HTTP status code (%s) while making HEAD request: %s", resp.Status, err.Error())
} else {
log.Printf("[DEBUG] (download) Error making HTTP HEAD request (%s): %s", resp.Status, err.Error())
}
} else {
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
// If the HEAD request succeeded, then attempt to set the range
// query if we can.
if resp.Header.Get("Accept-Ranges") == "bytes" {
if fi, err := dst.Stat(); err == nil {
if _, err = dst.Seek(0, os.SEEK_END); err == nil {
@@ -293,6 +305,7 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error {
}
}
}
} else {
log.Printf("[DEBUG] (download) Unexpected HTTP response during HEAD request: %s", resp.Status)
}
@@ -302,12 +315,17 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error {
req.Method = "GET"
resp, err = httpClient.Do(req)
if err != nil {
return err
} else {
if resp.StatusCode >= 400 && resp.StatusCode < 600 {
return fmt.Errorf("Error making HTTP GET request: %s", resp.Status)
if err == nil && (resp.StatusCode >= 400 && resp.StatusCode < 600) {
return fmt.Errorf("Error making HTTP GET request: %s", resp.Status)
} else if err != nil {
if resp == nil {
return fmt.Errorf("HTTP connection error: %s", err.Error())
} else if resp.StatusCode >= 400 && resp.StatusCode < 600 {
return fmt.Errorf("HTTP %s error: %s", resp.Status, err.Error())
}
return fmt.Errorf("HTTP error: %s", err.Error())
}
d.total = d.current + uint64(resp.ContentLength)
+5 -2
View File
@@ -4,6 +4,7 @@ import (
"bufio"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"os"
@@ -147,12 +148,14 @@ func (c *ISOConfig) parseCheckSumFile(rd *bufio.Reader) error {
absPath, err := filepath.Abs(u.Path)
if err != nil {
return fmt.Errorf("Unable to generate absolute path from provided iso_url: %s", err)
log.Printf("Unable to generate absolute path from provided iso_url: %s", err)
absPath = ""
}
relpath, err := filepath.Rel(filepath.Dir(checksumurl.Path), absPath)
if err != nil {
return err
log.Printf("Unable to determine relative pathing; continuing with abspath.")
relpath = ""
}
filename := filepath.Base(u.Path)
+7
View File
@@ -9,6 +9,7 @@ import (
"sort"
"strings"
"github.com/hashicorp/packer/common"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
@@ -165,6 +166,12 @@ func createFlattenedEnvVars(config *Config) (string, error) {
envVars["PACKER_BUILD_NAME"] = fmt.Sprintf("%s", config.PackerBuildName)
envVars["PACKER_BUILDER_TYPE"] = fmt.Sprintf("%s", config.PackerBuilderType)
// expose PACKER_HTTP_ADDR
httpAddr := common.GetHTTPAddr()
if httpAddr != "" {
envVars["PACKER_HTTP_ADDR"] = fmt.Sprintf("%s", httpAddr)
}
// interpolate environment variables
config.Ctx.Data = &EnvVarsTemplate{
WinRMPassword: getWinRMPassword(config.PackerBuildName),
+14 -5
View File
@@ -45,6 +45,8 @@ type StepConnect struct {
}
func (s *StepConnect) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
typeMap := map[string]multistep.Step{
"none": nil,
"ssh": &StepConnectSSH{
@@ -71,19 +73,26 @@ func (s *StepConnect) Run(ctx context.Context, state multistep.StateBag) multist
}
if step == nil {
comm, err := none.New("none")
if err != nil {
if comm, err := none.New("none"); err != nil {
err := fmt.Errorf("Failed to set communicator 'none': %s", err)
state.Put("error", err)
ui := state.Get("ui").(packer.Ui)
ui.Error(err.Error())
return multistep.ActionHalt
} else {
state.Put("communicator", comm)
log.Printf("[INFO] communicator disabled, will not connect")
}
state.Put("communicator", comm)
log.Printf("[INFO] communicator disabled, will not connect")
return multistep.ActionContinue
}
if host, err := s.Host(state); err == nil {
ui.Say(fmt.Sprintf("Using %s communicator to connect: %s", s.Config.Type, host))
} else {
log.Printf("[DEBUG] Unable to get address during connection step: %s", err)
}
s.substep = step
return s.substep.Run(ctx, state)
}
+7 -2
View File
@@ -7,6 +7,7 @@ type MockArtifact struct {
IdValue string
StateValues map[string]interface{}
DestroyCalled bool
StringValue string
}
func (a *MockArtifact) BuilderId() string {
@@ -34,8 +35,12 @@ func (a *MockArtifact) Id() string {
return id
}
func (*MockArtifact) String() string {
return "string"
func (a *MockArtifact) String() string {
str := a.StringValue
if str == "" {
str = "string"
}
return str
}
func (a *MockArtifact) State(name string) interface{} {
+9 -1
View File
@@ -12,6 +12,8 @@ import (
"github.com/hashicorp/packer/template/interpolate"
)
const BuilderIdImport = "packer.post-processor.docker-import"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
@@ -103,5 +105,11 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
return nil, false, err
}
return nil, false, nil
artifact = &docker.ImportArtifact{
BuilderIdValue: BuilderIdImport,
Driver: driver,
IdValue: name,
}
return artifact, true, nil
}
@@ -42,11 +42,11 @@ func TestPostProcessor_PostProcess(t *testing.T) {
}
result, keep, err := p.PostProcess(testUi(), artifact)
if result != nil {
t.Fatal("should be nil")
if _, ok := result.(packer.Artifact); !ok {
t.Fatal("should be instance of Artifact")
}
if keep {
t.Fatal("should not keep")
if !keep {
t.Fatal("should keep")
}
if err != nil {
t.Fatalf("err: %s", err)
@@ -58,6 +58,9 @@ func TestPostProcessor_PostProcess(t *testing.T) {
if driver.PushName != "foo/bar" {
t.Fatal("bad name")
}
if result.Id() != "foo/bar" {
t.Fatal("bad image id")
}
}
func TestPostProcessor_PostProcess_portInName(t *testing.T) {
@@ -69,11 +72,11 @@ func TestPostProcessor_PostProcess_portInName(t *testing.T) {
}
result, keep, err := p.PostProcess(testUi(), artifact)
if result != nil {
t.Fatal("should be nil")
if _, ok := result.(packer.Artifact); !ok {
t.Fatal("should be instance of Artifact")
}
if keep {
t.Fatal("should not keep")
if !keep {
t.Fatal("should keep")
}
if err != nil {
t.Fatalf("err: %s", err)
@@ -85,6 +88,9 @@ func TestPostProcessor_PostProcess_portInName(t *testing.T) {
if driver.PushName != "localhost:5000/foo/bar" {
t.Fatal("bad name")
}
if result.Id() != "localhost:5000/foo/bar" {
t.Fatal("bad image id")
}
}
func TestPostProcessor_PostProcess_tags(t *testing.T) {
@@ -96,11 +102,11 @@ func TestPostProcessor_PostProcess_tags(t *testing.T) {
}
result, keep, err := p.PostProcess(testUi(), artifact)
if result != nil {
t.Fatal("should be nil")
if _, ok := result.(packer.Artifact); !ok {
t.Fatal("should be instance of Artifact")
}
if keep {
t.Fatal("should not keep")
if !keep {
t.Fatal("should keep")
}
if err != nil {
t.Fatalf("err: %s", err)
@@ -112,4 +118,7 @@ func TestPostProcessor_PostProcess_tags(t *testing.T) {
if driver.PushName != "hashicorp/ubuntu:precise" {
t.Fatalf("bad name: %s", driver.PushName)
}
if result.Id() != "hashicorp/ubuntu:precise" {
t.Fatal("bad image id")
}
}
+67
View File
@@ -0,0 +1,67 @@
package vagrant
import (
"fmt"
"strings"
"github.com/hashicorp/packer/packer"
)
type AzureProvider struct{}
func (p *AzureProvider) KeepInputArtifact() bool {
return true
}
func (p *AzureProvider) Process(ui packer.Ui, artifact packer.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
// Create the metadata
metadata = map[string]interface{}{"provider": "azure"}
var AzureImageProps map[string]string
AzureImageProps = make(map[string]string)
// HACK(double16): It appears we can not access the Azure Artifact directly, so parse String()
artifactString := artifact.String()
ui.Message(fmt.Sprintf("artifact string: '%s'", artifactString))
lines := strings.Split(artifactString, "\n")
for l := 0; l < len(lines); l++ {
split := strings.Split(lines[l], ": ")
if len(split) > 1 {
AzureImageProps[strings.TrimSpace(split[0])] = strings.TrimSpace(split[1])
}
}
ui.Message(fmt.Sprintf("artifact string parsed: %+v", AzureImageProps))
if AzureImageProps["ManagedImageId"] != "" {
vagrantfile = fmt.Sprintf(managedImageVagrantfile, AzureImageProps["ManagedImageLocation"], AzureImageProps["ManagedImageId"])
} else if AzureImageProps["OSDiskUri"] != "" {
vagrantfile = fmt.Sprintf(vhdVagrantfile, AzureImageProps["StorageAccountLocation"], AzureImageProps["OSDiskUri"], AzureImageProps["OSType"])
} else {
err = fmt.Errorf("No managed image nor VHD URI found in artifact: %s", artifactString)
return
}
return
}
var managedImageVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.provider :azure do |azure, override|
azure.location = "%s"
azure.vm_managed_image_id = "%s"
override.winrm.transport = :ssl
override.winrm.port = 5986
end
end
`
var vhdVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.provider :azure do |azure, override|
azure.location = "%s"
azure.vm_vhd_uri = "%s"
azure.vm_operating_system = "%s"
override.winrm.transport = :ssl
override.winrm.port = 5986
end
end
`
+94
View File
@@ -0,0 +1,94 @@
package vagrant
import (
"strings"
"testing"
"github.com/hashicorp/packer/packer"
)
func TestAzureProvider_impl(t *testing.T) {
var _ Provider = new(AzureProvider)
}
func TestAzureProvider_KeepInputArtifact(t *testing.T) {
p := new(AzureProvider)
if !p.KeepInputArtifact() {
t.Fatal("should keep input artifact")
}
}
func TestAzureProvider_ManagedImage(t *testing.T) {
p := new(AzureProvider)
ui := testUi()
artifact := &packer.MockArtifact{
StringValue: `Azure.ResourceManagement.VMImage:
OSType: Linux
ManagedImageResourceGroupName: packerruns
ManagedImageName: packer-1533651633
ManagedImageId: /subscriptions/e6229913-d9c3-4ddd-99a4-9e1ef3beaa1b/resourceGroups/packerruns/providers/Microsoft.Compute/images/packer-1533675589
ManagedImageLocation: westus`,
}
vagrantfile, _, err := p.Process(ui, artifact, "foo")
if err != nil {
t.Fatalf("should not have error: %s", err)
}
result := `azure.location = "westus"`
if !strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
result = `azure.vm_managed_image_id = "/subscriptions/e6229913-d9c3-4ddd-99a4-9e1ef3beaa1b/resourceGroups/packerruns/providers/Microsoft.Compute/images/packer-1533675589"`
if !strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
// DO NOT set resource group in Vagrantfile, it should be separate from the image
result = `azure.resource_group_name`
if strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
result = `azure.vm_operating_system`
if strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
}
func TestAzureProvider_VHD(t *testing.T) {
p := new(AzureProvider)
ui := testUi()
artifact := &packer.MockArtifact{
IdValue: "https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd",
StringValue: `Azure.ResourceManagement.VMImage:
OSType: Linux
StorageAccountLocation: westus
OSDiskUri: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd
OSDiskUriReadOnlySas: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd?se=2018-09-07T18%3A36%3A34Z&sig=xUiFvwAviPYoP%2Bc91vErqvwYR1eK4x%2BAx7YLMe84zzU%3D&sp=r&sr=b&sv=2016-05-31
TemplateUri: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.96ed2120-591d-4900-95b0-ee8e985f2213.json
TemplateUriReadOnlySas: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.96ed2120-591d-4900-95b0-ee8e985f2213.json?se=2018-09-07T18%3A36%3A34Z&sig=lDxePyAUCZbfkB5ddiofimXfwk5INn%2F9E2BsnqIKC9Q%3D&sp=r&sr=b&sv=2016-05-31`,
}
vagrantfile, _, err := p.Process(ui, artifact, "foo")
if err != nil {
t.Fatalf("should not have error: %s", err)
}
result := `azure.location = "westus"`
if !strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
result = `azure.vm_vhd_uri = "https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd"`
if !strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
result = `azure.vm_operating_system = "Linux"`
if !strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
// DO NOT set resource group in Vagrantfile, it should be separate from the image
result = `azure.resource_group_name`
if strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
}
+29
View File
@@ -0,0 +1,29 @@
package vagrant
import (
"fmt"
"github.com/hashicorp/packer/packer"
)
type DockerProvider struct{}
func (p *DockerProvider) KeepInputArtifact() bool {
return false
}
func (p *DockerProvider) Process(ui packer.Ui, artifact packer.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
// Create the metadata
metadata = map[string]interface{}{"provider": "docker"}
vagrantfile = fmt.Sprintf(dockerVagrantfile, artifact.Id())
return
}
var dockerVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.provider :docker do |docker, override|
docker.image = "%s"
end
end
`
+9
View File
@@ -0,0 +1,9 @@
package vagrant
import (
"testing"
)
func TestDockerProvider_impl(t *testing.T) {
var _ Provider = new(DockerProvider)
}
+20 -12
View File
@@ -19,18 +19,22 @@ import (
)
var builtins = map[string]string{
"mitchellh.amazonebs": "aws",
"mitchellh.amazon.instance": "aws",
"mitchellh.virtualbox": "virtualbox",
"mitchellh.vmware": "vmware",
"mitchellh.vmware-esx": "vmware",
"pearkes.digitalocean": "digitalocean",
"packer.googlecompute": "google",
"hashicorp.scaleway": "scaleway",
"packer.parallels": "parallels",
"MSOpenTech.hyperv": "hyperv",
"transcend.qemu": "libvirt",
"ustream.lxc": "lxc",
"mitchellh.amazonebs": "aws",
"mitchellh.amazon.instance": "aws",
"mitchellh.virtualbox": "virtualbox",
"mitchellh.vmware": "vmware",
"mitchellh.vmware-esx": "vmware",
"pearkes.digitalocean": "digitalocean",
"packer.googlecompute": "google",
"hashicorp.scaleway": "scaleway",
"packer.parallels": "parallels",
"MSOpenTech.hyperv": "hyperv",
"transcend.qemu": "libvirt",
"ustream.lxc": "lxc",
"Azure.ResourceManagement.VMImage": "azure",
"packer.post-processor.docker-import": "docker",
"packer.post-processor.docker-tag": "docker",
"packer.post-processor.docker-push": "docker",
}
type Config struct {
@@ -241,6 +245,10 @@ func providerForName(name string) Provider {
return new(GoogleProvider)
case "lxc":
return new(LXCProvider)
case "azure":
return new(AzureProvider)
case "docker":
return new(DockerProvider)
default:
return nil
}
@@ -76,6 +76,7 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error invalid vSphere sdk endpoint: %s", err))
return errs
}
sdk.User = url.UserPassword(p.config.Username, p.config.Password)
+51 -1
View File
@@ -26,6 +26,7 @@ import (
"golang.org/x/crypto/ssh"
"github.com/hashicorp/packer/common"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
@@ -67,9 +68,19 @@ type Provisioner struct {
ansibleMajVersion uint
}
type PassthroughTemplate struct {
WinRMPassword string
}
func (p *Provisioner) Prepare(raws ...interface{}) error {
p.done = make(chan struct{})
// Create passthrough for winrm password so we can fill it in once we know
// it
p.config.ctx.Data = &PassthroughTemplate{
WinRMPassword: `{{.WinRMPassword}}`,
}
err := config.Decode(&p.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &p.config.ctx,
@@ -188,6 +199,25 @@ func (p *Provisioner) getVersion() error {
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Say("Provisioning with Ansible...")
// Interpolate env vars to check for .WinRMPassword
p.config.ctx.Data = &PassthroughTemplate{
WinRMPassword: getWinRMPassword(p.config.PackerBuildName),
}
for i, envVar := range p.config.AnsibleEnvVars {
envVar, err := interpolate.Render(envVar, &p.config.ctx)
if err != nil {
return fmt.Errorf("Could not interpolate ansible env vars: %s", err)
}
p.config.AnsibleEnvVars[i] = envVar
}
// Interpolate extra vars to check for .WinRMPassword
for i, arg := range p.config.ExtraArguments {
arg, err := interpolate.Render(arg, &p.config.ctx)
if err != nil {
return fmt.Errorf("Could not interpolate ansible env vars: %s", err)
}
p.config.ExtraArguments[i] = arg
}
k, err := newUserKey(p.config.SSHAuthorizedKeyFile)
if err != nil {
@@ -336,6 +366,13 @@ func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator, pri
// args = append(args, "--private-key", privKeyFile)
args = append(args, "-e", fmt.Sprintf("ansible_ssh_private_key_file=%s", privKeyFile))
}
// expose packer_http_addr extra variable
httpAddr := common.GetHTTPAddr()
if httpAddr != "" {
args = append(args, "--extra-vars", fmt.Sprintf("packer_http_addr=%s", httpAddr))
}
args = append(args, p.config.ExtraArguments...)
if len(p.config.AnsibleEnvVars) > 0 {
envvars = append(envvars, p.config.AnsibleEnvVars...)
@@ -381,7 +418,15 @@ func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator, pri
go repeat(stdout)
go repeat(stderr)
ui.Say(fmt.Sprintf("Executing Ansible: %s", strings.Join(cmd.Args, " ")))
// remove winrm password from command, if it's been added
flattenedCmd := strings.Join(cmd.Args, " ")
sanitized := flattenedCmd
if len(getWinRMPassword(p.config.PackerBuildName)) > 0 {
sanitized = strings.Replace(sanitized,
getWinRMPassword(p.config.PackerBuildName), "*****", -1)
}
ui.Say(fmt.Sprintf("Executing Ansible: %s", sanitized))
if err := cmd.Start(); err != nil {
return err
}
@@ -508,6 +553,11 @@ func newSigner(privKeyFile string) (*signer, error) {
return signer, nil
}
func getWinRMPassword(buildName string) string {
winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName)
return winRMPass
}
// Ui provides concurrency-safe access to packer.Ui.
type Ui struct {
sem chan int
-9
View File
@@ -174,15 +174,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
}
if p.config.EncryptedDataBagSecretPath != "" {
pFileInfo, err := os.Stat(p.config.EncryptedDataBagSecretPath)
if err != nil || pFileInfo.IsDir() {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Bad encrypted data bag secret '%s': %s", p.config.EncryptedDataBagSecretPath, err))
}
}
if p.config.ServerUrl == "" {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("server_url must be set"))
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) 2017 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+599
View File
@@ -0,0 +1,599 @@
// Copyright 2017, 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.md file.
// Package cmp determines equality of values.
//
// This package is intended to be a more powerful and safer alternative to
// reflect.DeepEqual for comparing whether two values are semantically equal.
//
// The primary features of cmp are:
//
// • When the default behavior of equality does not suit the needs of the test,
// custom equality functions can override the equality operation.
// For example, an equality function may report floats as equal so long as they
// are within some tolerance of each other.
//
// • Types that have an Equal method may use that method to determine equality.
// This allows package authors to determine the equality operation for the types
// that they define.
//
// • If no custom equality functions are used and no Equal method is defined,
// equality is determined by recursively comparing the primitive kinds on both
// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
// fields are not compared by default; they result in panics unless suppressed
// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
// using the AllowUnexported option.
package cmp
import (
"fmt"
"reflect"
"strings"
"github.com/google/go-cmp/cmp/internal/diff"
"github.com/google/go-cmp/cmp/internal/function"
"github.com/google/go-cmp/cmp/internal/value"
)
// BUG(dsnet): Maps with keys containing NaN values cannot be properly compared due to
// the reflection package's inability to retrieve such entries. Equal will panic
// anytime it comes across a NaN key, but this behavior may change.
//
// See https://golang.org/issue/11104 for more details.
var nothing = reflect.Value{}
// Equal reports whether x and y are equal by recursively applying the
// following rules in the given order to x and y and all of their sub-values:
//
// • If two values are not of the same type, then they are never equal
// and the overall result is false.
//
// • Let S be the set of all Ignore, Transformer, and Comparer options that
// remain after applying all path filters, value filters, and type filters.
// If at least one Ignore exists in S, then the comparison is ignored.
// If the number of Transformer and Comparer options in S is greater than one,
// then Equal panics because it is ambiguous which option to use.
// If S contains a single Transformer, then use that to transform the current
// values and recursively call Equal on the output values.
// If S contains a single Comparer, then use that to compare the current values.
// Otherwise, evaluation proceeds to the next rule.
//
// • If the values have an Equal method of the form "(T) Equal(T) bool" or
// "(T) Equal(I) bool" where T is assignable to I, then use the result of
// x.Equal(y) even if x or y is nil.
// Otherwise, no such method exists and evaluation proceeds to the next rule.
//
// • Lastly, try to compare x and y based on their basic kinds.
// Simple kinds like booleans, integers, floats, complex numbers, strings, and
// channels are compared using the equivalent of the == operator in Go.
// Functions are only equal if they are both nil, otherwise they are unequal.
// Pointers are equal if the underlying values they point to are also equal.
// Interfaces are equal if their underlying concrete values are also equal.
//
// Structs are equal if all of their fields are equal. If a struct contains
// unexported fields, Equal panics unless the AllowUnexported option is used or
// an Ignore option (e.g., cmpopts.IgnoreUnexported) ignores that field.
//
// Arrays, slices, and maps are equal if they are both nil or both non-nil
// with the same length and the elements at each index or key are equal.
// Note that a non-nil empty slice and a nil slice are not equal.
// To equate empty slices and maps, consider using cmpopts.EquateEmpty.
// Map keys are equal according to the == operator.
// To use custom comparisons for map keys, consider using cmpopts.SortMaps.
func Equal(x, y interface{}, opts ...Option) bool {
s := newState(opts)
s.compareAny(reflect.ValueOf(x), reflect.ValueOf(y))
return s.result.Equal()
}
// Diff returns a human-readable report of the differences between two values.
// It returns an empty string if and only if Equal returns true for the same
// input values and options. The output string will use the "-" symbol to
// indicate elements removed from x, and the "+" symbol to indicate elements
// added to y.
//
// Do not depend on this output being stable.
func Diff(x, y interface{}, opts ...Option) string {
r := new(defaultReporter)
opts = Options{Options(opts), r}
eq := Equal(x, y, opts...)
d := r.String()
if (d == "") != eq {
panic("inconsistent difference and equality results")
}
return d
}
type state struct {
// These fields represent the "comparison state".
// Calling statelessCompare must not result in observable changes to these.
result diff.Result // The current result of comparison
curPath Path // The current path in the value tree
reporter reporter // Optional reporter used for difference formatting
// recChecker checks for infinite cycles applying the same set of
// transformers upon the output of itself.
recChecker recChecker
// dynChecker triggers pseudo-random checks for option correctness.
// It is safe for statelessCompare to mutate this value.
dynChecker dynChecker
// These fields, once set by processOption, will not change.
exporters map[reflect.Type]bool // Set of structs with unexported field visibility
opts Options // List of all fundamental and filter options
}
func newState(opts []Option) *state {
s := new(state)
for _, opt := range opts {
s.processOption(opt)
}
return s
}
func (s *state) processOption(opt Option) {
switch opt := opt.(type) {
case nil:
case Options:
for _, o := range opt {
s.processOption(o)
}
case coreOption:
type filtered interface {
isFiltered() bool
}
if fopt, ok := opt.(filtered); ok && !fopt.isFiltered() {
panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
}
s.opts = append(s.opts, opt)
case visibleStructs:
if s.exporters == nil {
s.exporters = make(map[reflect.Type]bool)
}
for t := range opt {
s.exporters[t] = true
}
case reporter:
if s.reporter != nil {
panic("difference reporter already registered")
}
s.reporter = opt
default:
panic(fmt.Sprintf("unknown option %T", opt))
}
}
// statelessCompare compares two values and returns the result.
// This function is stateless in that it does not alter the current result,
// or output to any registered reporters.
func (s *state) statelessCompare(vx, vy reflect.Value) diff.Result {
// We do not save and restore the curPath because all of the compareX
// methods should properly push and pop from the path.
// It is an implementation bug if the contents of curPath differs from
// when calling this function to when returning from it.
oldResult, oldReporter := s.result, s.reporter
s.result = diff.Result{} // Reset result
s.reporter = nil // Remove reporter to avoid spurious printouts
s.compareAny(vx, vy)
res := s.result
s.result, s.reporter = oldResult, oldReporter
return res
}
func (s *state) compareAny(vx, vy reflect.Value) {
// TODO: Support cyclic data structures.
s.recChecker.Check(s.curPath)
// Rule 0: Differing types are never equal.
if !vx.IsValid() || !vy.IsValid() {
s.report(vx.IsValid() == vy.IsValid(), vx, vy)
return
}
if vx.Type() != vy.Type() {
s.report(false, vx, vy) // Possible for path to be empty
return
}
t := vx.Type()
if len(s.curPath) == 0 {
s.curPath.push(&pathStep{typ: t})
defer s.curPath.pop()
}
vx, vy = s.tryExporting(vx, vy)
// Rule 1: Check whether an option applies on this node in the value tree.
if s.tryOptions(vx, vy, t) {
return
}
// Rule 2: Check whether the type has a valid Equal method.
if s.tryMethod(vx, vy, t) {
return
}
// Rule 3: Recursively descend into each value's underlying kind.
switch t.Kind() {
case reflect.Bool:
s.report(vx.Bool() == vy.Bool(), vx, vy)
return
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
s.report(vx.Int() == vy.Int(), vx, vy)
return
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
s.report(vx.Uint() == vy.Uint(), vx, vy)
return
case reflect.Float32, reflect.Float64:
s.report(vx.Float() == vy.Float(), vx, vy)
return
case reflect.Complex64, reflect.Complex128:
s.report(vx.Complex() == vy.Complex(), vx, vy)
return
case reflect.String:
s.report(vx.String() == vy.String(), vx, vy)
return
case reflect.Chan, reflect.UnsafePointer:
s.report(vx.Pointer() == vy.Pointer(), vx, vy)
return
case reflect.Func:
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
return
case reflect.Ptr:
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
return
}
s.curPath.push(&indirect{pathStep{t.Elem()}})
defer s.curPath.pop()
s.compareAny(vx.Elem(), vy.Elem())
return
case reflect.Interface:
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
return
}
if vx.Elem().Type() != vy.Elem().Type() {
s.report(false, vx.Elem(), vy.Elem())
return
}
s.curPath.push(&typeAssertion{pathStep{vx.Elem().Type()}})
defer s.curPath.pop()
s.compareAny(vx.Elem(), vy.Elem())
return
case reflect.Slice:
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
return
}
fallthrough
case reflect.Array:
s.compareArray(vx, vy, t)
return
case reflect.Map:
s.compareMap(vx, vy, t)
return
case reflect.Struct:
s.compareStruct(vx, vy, t)
return
default:
panic(fmt.Sprintf("%v kind not handled", t.Kind()))
}
}
func (s *state) tryExporting(vx, vy reflect.Value) (reflect.Value, reflect.Value) {
if sf, ok := s.curPath[len(s.curPath)-1].(*structField); ok && sf.unexported {
if sf.force {
// Use unsafe pointer arithmetic to get read-write access to an
// unexported field in the struct.
vx = unsafeRetrieveField(sf.pvx, sf.field)
vy = unsafeRetrieveField(sf.pvy, sf.field)
} else {
// We are not allowed to export the value, so invalidate them
// so that tryOptions can panic later if not explicitly ignored.
vx = nothing
vy = nothing
}
}
return vx, vy
}
func (s *state) tryOptions(vx, vy reflect.Value, t reflect.Type) bool {
// If there were no FilterValues, we will not detect invalid inputs,
// so manually check for them and append invalid if necessary.
// We still evaluate the options since an ignore can override invalid.
opts := s.opts
if !vx.IsValid() || !vy.IsValid() {
opts = Options{opts, invalid{}}
}
// Evaluate all filters and apply the remaining options.
if opt := opts.filter(s, vx, vy, t); opt != nil {
opt.apply(s, vx, vy)
return true
}
return false
}
func (s *state) tryMethod(vx, vy reflect.Value, t reflect.Type) bool {
// Check if this type even has an Equal method.
m, ok := t.MethodByName("Equal")
if !ok || !function.IsType(m.Type, function.EqualAssignable) {
return false
}
eq := s.callTTBFunc(m.Func, vx, vy)
s.report(eq, vx, vy)
return true
}
func (s *state) callTRFunc(f, v reflect.Value) reflect.Value {
v = sanitizeValue(v, f.Type().In(0))
if !s.dynChecker.Next() {
return f.Call([]reflect.Value{v})[0]
}
// Run the function twice and ensure that we get the same results back.
// We run in goroutines so that the race detector (if enabled) can detect
// unsafe mutations to the input.
c := make(chan reflect.Value)
go detectRaces(c, f, v)
want := f.Call([]reflect.Value{v})[0]
if got := <-c; !s.statelessCompare(got, want).Equal() {
// To avoid false-positives with non-reflexive equality operations,
// we sanity check whether a value is equal to itself.
if !s.statelessCompare(want, want).Equal() {
return want
}
fn := getFuncName(f.Pointer())
panic(fmt.Sprintf("non-deterministic function detected: %s", fn))
}
return want
}
func (s *state) callTTBFunc(f, x, y reflect.Value) bool {
x = sanitizeValue(x, f.Type().In(0))
y = sanitizeValue(y, f.Type().In(1))
if !s.dynChecker.Next() {
return f.Call([]reflect.Value{x, y})[0].Bool()
}
// Swapping the input arguments is sufficient to check that
// f is symmetric and deterministic.
// We run in goroutines so that the race detector (if enabled) can detect
// unsafe mutations to the input.
c := make(chan reflect.Value)
go detectRaces(c, f, y, x)
want := f.Call([]reflect.Value{x, y})[0].Bool()
if got := <-c; !got.IsValid() || got.Bool() != want {
fn := getFuncName(f.Pointer())
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", fn))
}
return want
}
func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
var ret reflect.Value
defer func() {
recover() // Ignore panics, let the other call to f panic instead
c <- ret
}()
ret = f.Call(vs)[0]
}
// sanitizeValue converts nil interfaces of type T to those of type R,
// assuming that T is assignable to R.
// Otherwise, it returns the input value as is.
func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value {
// TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/22143).
// The upstream fix landed in Go1.10, so we can remove this when drop support
// for Go1.9 and below.
if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t {
return reflect.New(t).Elem()
}
return v
}
func (s *state) compareArray(vx, vy reflect.Value, t reflect.Type) {
step := &sliceIndex{pathStep{t.Elem()}, 0, 0}
s.curPath.push(step)
// Compute an edit-script for slices vx and vy.
es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result {
step.xkey, step.ykey = ix, iy
return s.statelessCompare(vx.Index(ix), vy.Index(iy))
})
// Report the entire slice as is if the arrays are of primitive kind,
// and the arrays are different enough.
isPrimitive := false
switch t.Elem().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
isPrimitive = true
}
if isPrimitive && es.Dist() > (vx.Len()+vy.Len())/4 {
s.curPath.pop() // Pop first since we are reporting the whole slice
s.report(false, vx, vy)
return
}
// Replay the edit-script.
var ix, iy int
for _, e := range es {
switch e {
case diff.UniqueX:
step.xkey, step.ykey = ix, -1
s.report(false, vx.Index(ix), nothing)
ix++
case diff.UniqueY:
step.xkey, step.ykey = -1, iy
s.report(false, nothing, vy.Index(iy))
iy++
default:
step.xkey, step.ykey = ix, iy
if e == diff.Identity {
s.report(true, vx.Index(ix), vy.Index(iy))
} else {
s.compareAny(vx.Index(ix), vy.Index(iy))
}
ix++
iy++
}
}
s.curPath.pop()
return
}
func (s *state) compareMap(vx, vy reflect.Value, t reflect.Type) {
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
return
}
// We combine and sort the two map keys so that we can perform the
// comparisons in a deterministic order.
step := &mapIndex{pathStep: pathStep{t.Elem()}}
s.curPath.push(step)
defer s.curPath.pop()
for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
step.key = k
vvx := vx.MapIndex(k)
vvy := vy.MapIndex(k)
switch {
case vvx.IsValid() && vvy.IsValid():
s.compareAny(vvx, vvy)
case vvx.IsValid() && !vvy.IsValid():
s.report(false, vvx, nothing)
case !vvx.IsValid() && vvy.IsValid():
s.report(false, nothing, vvy)
default:
// It is possible for both vvx and vvy to be invalid if the
// key contained a NaN value in it. There is no way in
// reflection to be able to retrieve these values.
// See https://golang.org/issue/11104
panic(fmt.Sprintf("%#v has map key with NaNs", s.curPath))
}
}
}
func (s *state) compareStruct(vx, vy reflect.Value, t reflect.Type) {
var vax, vay reflect.Value // Addressable versions of vx and vy
step := &structField{}
s.curPath.push(step)
defer s.curPath.pop()
for i := 0; i < t.NumField(); i++ {
vvx := vx.Field(i)
vvy := vy.Field(i)
step.typ = t.Field(i).Type
step.name = t.Field(i).Name
step.idx = i
step.unexported = !isExported(step.name)
if step.unexported {
// Defer checking of unexported fields until later to give an
// Ignore a chance to ignore the field.
if !vax.IsValid() || !vay.IsValid() {
// For unsafeRetrieveField to work, the parent struct must
// be addressable. Create a new copy of the values if
// necessary to make them addressable.
vax = makeAddressable(vx)
vay = makeAddressable(vy)
}
step.force = s.exporters[t]
step.pvx = vax
step.pvy = vay
step.field = t.Field(i)
}
s.compareAny(vvx, vvy)
}
}
// report records the result of a single comparison.
// It also calls Report if any reporter is registered.
func (s *state) report(eq bool, vx, vy reflect.Value) {
if eq {
s.result.NSame++
} else {
s.result.NDiff++
}
if s.reporter != nil {
s.reporter.Report(vx, vy, eq, s.curPath)
}
}
// recChecker tracks the state needed to periodically perform checks that
// user provided transformers are not stuck in an infinitely recursive cycle.
type recChecker struct{ next int }
// Check scans the Path for any recursive transformers and panics when any
// recursive transformers are detected. Note that the presence of a
// recursive Transformer does not necessarily imply an infinite cycle.
// As such, this check only activates after some minimal number of path steps.
func (rc *recChecker) Check(p Path) {
const minLen = 1 << 16
if rc.next == 0 {
rc.next = minLen
}
if len(p) < rc.next {
return
}
rc.next <<= 1
// Check whether the same transformer has appeared at least twice.
var ss []string
m := map[Option]int{}
for _, ps := range p {
if t, ok := ps.(Transform); ok {
t := t.Option()
if m[t] == 1 { // Transformer was used exactly once before
tf := t.(*transformer).fnc.Type()
ss = append(ss, fmt.Sprintf("%v: %v => %v", t, tf.In(0), tf.Out(0)))
}
m[t]++
}
}
if len(ss) > 0 {
const warning = "recursive set of Transformers detected"
const help = "consider using cmpopts.AcyclicTransformer"
set := strings.Join(ss, "\n\t")
panic(fmt.Sprintf("%s:\n\t%s\n%s", warning, set, help))
}
}
// dynChecker tracks the state needed to periodically perform checks that
// user provided functions are symmetric and deterministic.
// The zero value is safe for immediate use.
type dynChecker struct{ curr, next int }
// Next increments the state and reports whether a check should be performed.
//
// Checks occur every Nth function call, where N is a triangular number:
// 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
// See https://en.wikipedia.org/wiki/Triangular_number
//
// This sequence ensures that the cost of checks drops significantly as
// the number of functions calls grows larger.
func (dc *dynChecker) Next() bool {
ok := dc.curr == dc.next
if ok {
dc.curr = 0
dc.next++
}
dc.curr++
return ok
}
// makeAddressable returns a value that is always addressable.
// It returns the input verbatim if it is already addressable,
// otherwise it creates a new value and returns an addressable copy.
func makeAddressable(v reflect.Value) reflect.Value {
if v.CanAddr() {
return v
}
vc := reflect.New(v.Type()).Elem()
vc.Set(v)
return vc
}
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2017, 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.md file.
// +build !debug
package diff
var debug debugger
type debugger struct{}
func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc {
return f
}
func (debugger) Update() {}
func (debugger) Finish() {}
+122
View File
@@ -0,0 +1,122 @@
// Copyright 2017, 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.md file.
// +build debug
package diff
import (
"fmt"
"strings"
"sync"
"time"
)
// The algorithm can be seen running in real-time by enabling debugging:
// go test -tags=debug -v
//
// Example output:
// === RUN TestDifference/#34
// ┌───────────────────────────────┐
// │ \ · · · · · · · · · · · · · · │
// │ · # · · · · · · · · · · · · · │
// │ · \ · · · · · · · · · · · · · │
// │ · · \ · · · · · · · · · · · · │
// │ · · · X # · · · · · · · · · · │
// │ · · · # \ · · · · · · · · · · │
// │ · · · · · # # · · · · · · · · │
// │ · · · · · # \ · · · · · · · · │
// │ · · · · · · · \ · · · · · · · │
// │ · · · · · · · · \ · · · · · · │
// │ · · · · · · · · · \ · · · · · │
// │ · · · · · · · · · · \ · · # · │
// │ · · · · · · · · · · · \ # # · │
// │ · · · · · · · · · · · # # # · │
// │ · · · · · · · · · · # # # # · │
// │ · · · · · · · · · # # # # # · │
// │ · · · · · · · · · · · · · · \ │
// └───────────────────────────────┘
// [.Y..M.XY......YXYXY.|]
//
// The grid represents the edit-graph where the horizontal axis represents
// list X and the vertical axis represents list Y. The start of the two lists
// is the top-left, while the ends are the bottom-right. The '·' represents
// an unexplored node in the graph. The '\' indicates that the two symbols
// from list X and Y are equal. The 'X' indicates that two symbols are similar
// (but not exactly equal) to each other. The '#' indicates that the two symbols
// are different (and not similar). The algorithm traverses this graph trying to
// make the paths starting in the top-left and the bottom-right connect.
//
// The series of '.', 'X', 'Y', and 'M' characters at the bottom represents
// the currently established path from the forward and reverse searches,
// separated by a '|' character.
const (
updateDelay = 100 * time.Millisecond
finishDelay = 500 * time.Millisecond
ansiTerminal = true // ANSI escape codes used to move terminal cursor
)
var debug debugger
type debugger struct {
sync.Mutex
p1, p2 EditScript
fwdPath, revPath *EditScript
grid []byte
lines int
}
func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc {
dbg.Lock()
dbg.fwdPath, dbg.revPath = p1, p2
top := "┌─" + strings.Repeat("──", nx) + "┐\n"
row := "│ " + strings.Repeat("· ", nx) + "│\n"
btm := "└─" + strings.Repeat("──", nx) + "┘\n"
dbg.grid = []byte(top + strings.Repeat(row, ny) + btm)
dbg.lines = strings.Count(dbg.String(), "\n")
fmt.Print(dbg)
// Wrap the EqualFunc so that we can intercept each result.
return func(ix, iy int) (r Result) {
cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")]
for i := range cell {
cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot
}
switch r = f(ix, iy); {
case r.Equal():
cell[0] = '\\'
case r.Similar():
cell[0] = 'X'
default:
cell[0] = '#'
}
return
}
}
func (dbg *debugger) Update() {
dbg.print(updateDelay)
}
func (dbg *debugger) Finish() {
dbg.print(finishDelay)
dbg.Unlock()
}
func (dbg *debugger) String() string {
dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0]
for i := len(*dbg.revPath) - 1; i >= 0; i-- {
dbg.p2 = append(dbg.p2, (*dbg.revPath)[i])
}
return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2)
}
func (dbg *debugger) print(d time.Duration) {
if ansiTerminal {
fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor
}
fmt.Print(dbg)
time.Sleep(d)
}
+363
View File
@@ -0,0 +1,363 @@
// Copyright 2017, 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.md file.
// Package diff implements an algorithm for producing edit-scripts.
// The edit-script is a sequence of operations needed to transform one list
// of symbols into another (or vice-versa). The edits allowed are insertions,
// deletions, and modifications. The summation of all edits is called the
// Levenshtein distance as this problem is well-known in computer science.
//
// This package prioritizes performance over accuracy. That is, the run time
// is more important than obtaining a minimal Levenshtein distance.
package diff
// EditType represents a single operation within an edit-script.
type EditType uint8
const (
// Identity indicates that a symbol pair is identical in both list X and Y.
Identity EditType = iota
// UniqueX indicates that a symbol only exists in X and not Y.
UniqueX
// UniqueY indicates that a symbol only exists in Y and not X.
UniqueY
// Modified indicates that a symbol pair is a modification of each other.
Modified
)
// EditScript represents the series of differences between two lists.
type EditScript []EditType
// String returns a human-readable string representing the edit-script where
// Identity, UniqueX, UniqueY, and Modified are represented by the
// '.', 'X', 'Y', and 'M' characters, respectively.
func (es EditScript) String() string {
b := make([]byte, len(es))
for i, e := range es {
switch e {
case Identity:
b[i] = '.'
case UniqueX:
b[i] = 'X'
case UniqueY:
b[i] = 'Y'
case Modified:
b[i] = 'M'
default:
panic("invalid edit-type")
}
}
return string(b)
}
// stats returns a histogram of the number of each type of edit operation.
func (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) {
for _, e := range es {
switch e {
case Identity:
s.NI++
case UniqueX:
s.NX++
case UniqueY:
s.NY++
case Modified:
s.NM++
default:
panic("invalid edit-type")
}
}
return
}
// Dist is the Levenshtein distance and is guaranteed to be 0 if and only if
// lists X and Y are equal.
func (es EditScript) Dist() int { return len(es) - es.stats().NI }
// LenX is the length of the X list.
func (es EditScript) LenX() int { return len(es) - es.stats().NY }
// LenY is the length of the Y list.
func (es EditScript) LenY() int { return len(es) - es.stats().NX }
// EqualFunc reports whether the symbols at indexes ix and iy are equal.
// When called by Difference, the index is guaranteed to be within nx and ny.
type EqualFunc func(ix int, iy int) Result
// Result is the result of comparison.
// NSame is the number of sub-elements that are equal.
// NDiff is the number of sub-elements that are not equal.
type Result struct{ NSame, NDiff int }
// Equal indicates whether the symbols are equal. Two symbols are equal
// if and only if NDiff == 0. If Equal, then they are also Similar.
func (r Result) Equal() bool { return r.NDiff == 0 }
// Similar indicates whether two symbols are similar and may be represented
// by using the Modified type. As a special case, we consider binary comparisons
// (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar.
//
// The exact ratio of NSame to NDiff to determine similarity may change.
func (r Result) Similar() bool {
// Use NSame+1 to offset NSame so that binary comparisons are similar.
return r.NSame+1 >= r.NDiff
}
// Difference reports whether two lists of lengths nx and ny are equal
// given the definition of equality provided as f.
//
// This function returns an edit-script, which is a sequence of operations
// needed to convert one list into the other. The following invariants for
// the edit-script are maintained:
// • eq == (es.Dist()==0)
// • nx == es.LenX()
// • ny == es.LenY()
//
// This algorithm is not guaranteed to be an optimal solution (i.e., one that
// produces an edit-script with a minimal Levenshtein distance). This algorithm
// favors performance over optimality. The exact output is not guaranteed to
// be stable and may change over time.
func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// This algorithm is based on traversing what is known as an "edit-graph".
// See Figure 1 from "An O(ND) Difference Algorithm and Its Variations"
// by Eugene W. Myers. Since D can be as large as N itself, this is
// effectively O(N^2). Unlike the algorithm from that paper, we are not
// interested in the optimal path, but at least some "decent" path.
//
// For example, let X and Y be lists of symbols:
// X = [A B C A B B A]
// Y = [C B A B A C]
//
// The edit-graph can be drawn as the following:
// A B C A B B A
// ┌─────────────┐
// C │_|_|\|_|_|_|_│ 0
// B │_|\|_|_|\|\|_│ 1
// A │\|_|_|\|_|_|\│ 2
// B │_|\|_|_|\|\|_│ 3
// A │\|_|_|\|_|_|\│ 4
// C │ | |\| | | | │ 5
// └─────────────┘ 6
// 0 1 2 3 4 5 6 7
//
// List X is written along the horizontal axis, while list Y is written
// along the vertical axis. At any point on this grid, if the symbol in
// list X matches the corresponding symbol in list Y, then a '\' is drawn.
// The goal of any minimal edit-script algorithm is to find a path from the
// top-left corner to the bottom-right corner, while traveling through the
// fewest horizontal or vertical edges.
// A horizontal edge is equivalent to inserting a symbol from list X.
// A vertical edge is equivalent to inserting a symbol from list Y.
// A diagonal edge is equivalent to a matching symbol between both X and Y.
// Invariants:
// • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
// • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
//
// In general:
// • fwdFrontier.X < revFrontier.X
// • fwdFrontier.Y < revFrontier.Y
// Unless, it is time for the algorithm to terminate.
fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
fwdFrontier := fwdPath.point // Forward search frontier
revFrontier := revPath.point // Reverse search frontier
// Search budget bounds the cost of searching for better paths.
// The longest sequence of non-matching symbols that can be tolerated is
// approximately the square-root of the search budget.
searchBudget := 4 * (nx + ny) // O(n)
// The algorithm below is a greedy, meet-in-the-middle algorithm for
// computing sub-optimal edit-scripts between two lists.
//
// The algorithm is approximately as follows:
// • Searching for differences switches back-and-forth between
// a search that starts at the beginning (the top-left corner), and
// a search that starts at the end (the bottom-right corner). The goal of
// the search is connect with the search from the opposite corner.
// • As we search, we build a path in a greedy manner, where the first
// match seen is added to the path (this is sub-optimal, but provides a
// decent result in practice). When matches are found, we try the next pair
// of symbols in the lists and follow all matches as far as possible.
// • When searching for matches, we search along a diagonal going through
// through the "frontier" point. If no matches are found, we advance the
// frontier towards the opposite corner.
// • This algorithm terminates when either the X coordinates or the
// Y coordinates of the forward and reverse frontier points ever intersect.
//
// This algorithm is correct even if searching only in the forward direction
// or in the reverse direction. We do both because it is commonly observed
// that two lists commonly differ because elements were added to the front
// or end of the other list.
//
// Running the tests with the "debug" build tag prints a visualization of
// the algorithm running in real-time. This is educational for understanding
// how the algorithm works. See debug_enable.go.
f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
for {
// Forward search from the beginning.
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
break
}
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
// Search in a diagonal pattern for a match.
z := zigzag(i)
p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
switch {
case p.X >= revPath.X || p.Y < fwdPath.Y:
stop1 = true // Hit top-right corner
case p.Y >= revPath.Y || p.X < fwdPath.X:
stop2 = true // Hit bottom-left corner
case f(p.X, p.Y).Equal():
// Match found, so connect the path to this point.
fwdPath.connect(p, f)
fwdPath.append(Identity)
// Follow sequence of matches as far as possible.
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
if !f(fwdPath.X, fwdPath.Y).Equal() {
break
}
fwdPath.append(Identity)
}
fwdFrontier = fwdPath.point
stop1, stop2 = true, true
default:
searchBudget-- // Match not found
}
debug.Update()
}
// Advance the frontier towards reverse point.
if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y {
fwdFrontier.X++
} else {
fwdFrontier.Y++
}
// Reverse search from the end.
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
break
}
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
// Search in a diagonal pattern for a match.
z := zigzag(i)
p := point{revFrontier.X - z, revFrontier.Y + z}
switch {
case fwdPath.X >= p.X || revPath.Y < p.Y:
stop1 = true // Hit bottom-left corner
case fwdPath.Y >= p.Y || revPath.X < p.X:
stop2 = true // Hit top-right corner
case f(p.X-1, p.Y-1).Equal():
// Match found, so connect the path to this point.
revPath.connect(p, f)
revPath.append(Identity)
// Follow sequence of matches as far as possible.
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
if !f(revPath.X-1, revPath.Y-1).Equal() {
break
}
revPath.append(Identity)
}
revFrontier = revPath.point
stop1, stop2 = true, true
default:
searchBudget-- // Match not found
}
debug.Update()
}
// Advance the frontier towards forward point.
if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y {
revFrontier.X--
} else {
revFrontier.Y--
}
}
// Join the forward and reverse paths and then append the reverse path.
fwdPath.connect(revPath.point, f)
for i := len(revPath.es) - 1; i >= 0; i-- {
t := revPath.es[i]
revPath.es = revPath.es[:i]
fwdPath.append(t)
}
debug.Finish()
return fwdPath.es
}
type path struct {
dir int // +1 if forward, -1 if reverse
point // Leading point of the EditScript path
es EditScript
}
// connect appends any necessary Identity, Modified, UniqueX, or UniqueY types
// to the edit-script to connect p.point to dst.
func (p *path) connect(dst point, f EqualFunc) {
if p.dir > 0 {
// Connect in forward direction.
for dst.X > p.X && dst.Y > p.Y {
switch r := f(p.X, p.Y); {
case r.Equal():
p.append(Identity)
case r.Similar():
p.append(Modified)
case dst.X-p.X >= dst.Y-p.Y:
p.append(UniqueX)
default:
p.append(UniqueY)
}
}
for dst.X > p.X {
p.append(UniqueX)
}
for dst.Y > p.Y {
p.append(UniqueY)
}
} else {
// Connect in reverse direction.
for p.X > dst.X && p.Y > dst.Y {
switch r := f(p.X-1, p.Y-1); {
case r.Equal():
p.append(Identity)
case r.Similar():
p.append(Modified)
case p.Y-dst.Y >= p.X-dst.X:
p.append(UniqueY)
default:
p.append(UniqueX)
}
}
for p.X > dst.X {
p.append(UniqueX)
}
for p.Y > dst.Y {
p.append(UniqueY)
}
}
}
func (p *path) append(t EditType) {
p.es = append(p.es, t)
switch t {
case Identity, Modified:
p.add(p.dir, p.dir)
case UniqueX:
p.add(p.dir, 0)
case UniqueY:
p.add(0, p.dir)
}
debug.Update()
}
type point struct{ X, Y int }
func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
// zigzag maps a consecutive sequence of integers to a zig-zag sequence.
// [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
func zigzag(x int) int {
if x&1 != 0 {
x = ^x
}
return x >> 1
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2017, 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.md file.
// Package function identifies function types.
package function
import "reflect"
type funcType int
const (
_ funcType = iota
ttbFunc // func(T, T) bool
tibFunc // func(T, I) bool
trFunc // func(T) R
Equal = ttbFunc // func(T, T) bool
EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool
Transformer = trFunc // func(T) R
ValueFilter = ttbFunc // func(T, T) bool
Less = ttbFunc // func(T, T) bool
)
var boolType = reflect.TypeOf(true)
// IsType reports whether the reflect.Type is of the specified function type.
func IsType(t reflect.Type, ft funcType) bool {
if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
return false
}
ni, no := t.NumIn(), t.NumOut()
switch ft {
case ttbFunc: // func(T, T) bool
if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType {
return true
}
case tibFunc: // func(T, I) bool
if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType {
return true
}
case trFunc: // func(T) R
if ni == 1 && no == 1 {
return true
}
}
return false
}
+280
View File
@@ -0,0 +1,280 @@
// Copyright 2017, 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.md file.
// Package value provides functionality for reflect.Value types.
package value
import (
"fmt"
"reflect"
"strconv"
"strings"
"unicode"
)
var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
// Format formats the value v as a string.
//
// This is similar to fmt.Sprintf("%+v", v) except this:
// * Prints the type unless it can be elided
// * Avoids printing struct fields that are zero
// * Prints a nil-slice as being nil, not empty
// * Prints map entries in deterministic order
func Format(v reflect.Value, conf FormatConfig) string {
conf.printType = true
conf.followPointers = true
conf.realPointers = true
return formatAny(v, conf, visited{})
}
type FormatConfig struct {
UseStringer bool // Should the String method be used if available?
printType bool // Should we print the type before the value?
PrintPrimitiveType bool // Should we print the type of primitives?
followPointers bool // Should we recursively follow pointers?
realPointers bool // Should we print the real address of pointers?
}
func formatAny(v reflect.Value, conf FormatConfig, m visited) string {
// TODO: Should this be a multi-line printout in certain situations?
if !v.IsValid() {
return "<non-existent>"
}
if conf.UseStringer && v.Type().Implements(stringerIface) && v.CanInterface() {
if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && v.IsNil() {
return "<nil>"
}
const stringerPrefix = "s" // Indicates that the String method was used
s := v.Interface().(fmt.Stringer).String()
return stringerPrefix + formatString(s)
}
switch v.Kind() {
case reflect.Bool:
return formatPrimitive(v.Type(), v.Bool(), conf)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return formatPrimitive(v.Type(), v.Int(), conf)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr {
// Unnamed uints are usually bytes or words, so use hexadecimal.
return formatPrimitive(v.Type(), formatHex(v.Uint()), conf)
}
return formatPrimitive(v.Type(), v.Uint(), conf)
case reflect.Float32, reflect.Float64:
return formatPrimitive(v.Type(), v.Float(), conf)
case reflect.Complex64, reflect.Complex128:
return formatPrimitive(v.Type(), v.Complex(), conf)
case reflect.String:
return formatPrimitive(v.Type(), formatString(v.String()), conf)
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
return formatPointer(v, conf)
case reflect.Ptr:
if v.IsNil() {
if conf.printType {
return fmt.Sprintf("(%v)(nil)", v.Type())
}
return "<nil>"
}
if m.Visit(v) || !conf.followPointers {
return formatPointer(v, conf)
}
return "&" + formatAny(v.Elem(), conf, m)
case reflect.Interface:
if v.IsNil() {
if conf.printType {
return fmt.Sprintf("%v(nil)", v.Type())
}
return "<nil>"
}
return formatAny(v.Elem(), conf, m)
case reflect.Slice:
if v.IsNil() {
if conf.printType {
return fmt.Sprintf("%v(nil)", v.Type())
}
return "<nil>"
}
fallthrough
case reflect.Array:
var ss []string
subConf := conf
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
for i := 0; i < v.Len(); i++ {
vi := v.Index(i)
if vi.CanAddr() { // Check for recursive elements
p := vi.Addr()
if m.Visit(p) {
subConf := conf
subConf.printType = true
ss = append(ss, "*"+formatPointer(p, subConf))
continue
}
}
ss = append(ss, formatAny(vi, subConf, m))
}
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
if conf.printType {
return v.Type().String() + s
}
return s
case reflect.Map:
if v.IsNil() {
if conf.printType {
return fmt.Sprintf("%v(nil)", v.Type())
}
return "<nil>"
}
if m.Visit(v) {
return formatPointer(v, conf)
}
var ss []string
keyConf, valConf := conf, conf
keyConf.printType = v.Type().Key().Kind() == reflect.Interface
keyConf.followPointers = false
valConf.printType = v.Type().Elem().Kind() == reflect.Interface
for _, k := range SortKeys(v.MapKeys()) {
sk := formatAny(k, keyConf, m)
sv := formatAny(v.MapIndex(k), valConf, m)
ss = append(ss, fmt.Sprintf("%s: %s", sk, sv))
}
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
if conf.printType {
return v.Type().String() + s
}
return s
case reflect.Struct:
var ss []string
subConf := conf
subConf.printType = true
for i := 0; i < v.NumField(); i++ {
vv := v.Field(i)
if isZero(vv) {
continue // Elide zero value fields
}
name := v.Type().Field(i).Name
subConf.UseStringer = conf.UseStringer
s := formatAny(vv, subConf, m)
ss = append(ss, fmt.Sprintf("%s: %s", name, s))
}
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
if conf.printType {
return v.Type().String() + s
}
return s
default:
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
}
}
func formatString(s string) string {
// Use quoted string if it the same length as a raw string literal.
// Otherwise, attempt to use the raw string form.
qs := strconv.Quote(s)
if len(qs) == 1+len(s)+1 {
return qs
}
// Disallow newlines to ensure output is a single line.
// Only allow printable runes for readability purposes.
rawInvalid := func(r rune) bool {
return r == '`' || r == '\n' || !unicode.IsPrint(r)
}
if strings.IndexFunc(s, rawInvalid) < 0 {
return "`" + s + "`"
}
return qs
}
func formatPrimitive(t reflect.Type, v interface{}, conf FormatConfig) string {
if conf.printType && (conf.PrintPrimitiveType || t.PkgPath() != "") {
return fmt.Sprintf("%v(%v)", t, v)
}
return fmt.Sprintf("%v", v)
}
func formatPointer(v reflect.Value, conf FormatConfig) string {
p := v.Pointer()
if !conf.realPointers {
p = 0 // For deterministic printing purposes
}
s := formatHex(uint64(p))
if conf.printType {
return fmt.Sprintf("(%v)(%s)", v.Type(), s)
}
return s
}
func formatHex(u uint64) string {
var f string
switch {
case u <= 0xff:
f = "0x%02x"
case u <= 0xffff:
f = "0x%04x"
case u <= 0xffffff:
f = "0x%06x"
case u <= 0xffffffff:
f = "0x%08x"
case u <= 0xffffffffff:
f = "0x%010x"
case u <= 0xffffffffffff:
f = "0x%012x"
case u <= 0xffffffffffffff:
f = "0x%014x"
case u <= 0xffffffffffffffff:
f = "0x%016x"
}
return fmt.Sprintf(f, u)
}
// isZero reports whether v is the zero value.
// This does not rely on Interface and so can be used on unexported fields.
func isZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Bool:
return v.Bool() == false
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Complex64, reflect.Complex128:
return v.Complex() == 0
case reflect.String:
return v.String() == ""
case reflect.UnsafePointer:
return v.Pointer() == 0
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
return v.IsNil()
case reflect.Array:
for i := 0; i < v.Len(); i++ {
if !isZero(v.Index(i)) {
return false
}
}
return true
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
if !isZero(v.Field(i)) {
return false
}
}
return true
}
return false
}
type visited map[Pointer]bool
func (m visited) Visit(v reflect.Value) bool {
p := PointerOf(v)
visited := m[p]
m[p] = true
return visited
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2018, 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.md file.
// +build purego
package value
import "reflect"
// Pointer is an opaque typed pointer and is guaranteed to be comparable.
type Pointer struct {
p uintptr
t reflect.Type
}
// PointerOf returns a Pointer from v, which must be a
// reflect.Ptr, reflect.Slice, or reflect.Map.
func PointerOf(v reflect.Value) Pointer {
// NOTE: Storing a pointer as an uintptr is technically incorrect as it
// assumes that the GC implementation does not use a moving collector.
return Pointer{v.Pointer(), v.Type()}
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2018, 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.md file.
// +build !purego
package value
import (
"reflect"
"unsafe"
)
// Pointer is an opaque typed pointer and is guaranteed to be comparable.
type Pointer struct {
p unsafe.Pointer
t reflect.Type
}
// PointerOf returns a Pointer from v, which must be a
// reflect.Ptr, reflect.Slice, or reflect.Map.
func PointerOf(v reflect.Value) Pointer {
// The proper representation of a pointer is unsafe.Pointer,
// which is necessary if the GC ever uses a moving collector.
return Pointer{unsafe.Pointer(v.Pointer()), v.Type()}
}
+111
View File
@@ -0,0 +1,111 @@
// Copyright 2017, 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.md file.
package value
import (
"fmt"
"math"
"reflect"
"sort"
)
// SortKeys sorts a list of map keys, deduplicating keys if necessary.
// The type of each value must be comparable.
func SortKeys(vs []reflect.Value) []reflect.Value {
if len(vs) == 0 {
return vs
}
// Sort the map keys.
sort.Sort(valueSorter(vs))
// Deduplicate keys (fails for NaNs).
vs2 := vs[:1]
for _, v := range vs[1:] {
if isLess(vs2[len(vs2)-1], v) {
vs2 = append(vs2, v)
}
}
return vs2
}
// TODO: Use sort.Slice once Google AppEngine is on Go1.8 or above.
type valueSorter []reflect.Value
func (vs valueSorter) Len() int { return len(vs) }
func (vs valueSorter) Less(i, j int) bool { return isLess(vs[i], vs[j]) }
func (vs valueSorter) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
// isLess is a generic function for sorting arbitrary map keys.
// The inputs must be of the same type and must be comparable.
func isLess(x, y reflect.Value) bool {
switch x.Type().Kind() {
case reflect.Bool:
return !x.Bool() && y.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return x.Int() < y.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return x.Uint() < y.Uint()
case reflect.Float32, reflect.Float64:
fx, fy := x.Float(), y.Float()
return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy)
case reflect.Complex64, reflect.Complex128:
cx, cy := x.Complex(), y.Complex()
rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy)
if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) {
return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy)
}
return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry)
case reflect.Ptr, reflect.UnsafePointer, reflect.Chan:
return x.Pointer() < y.Pointer()
case reflect.String:
return x.String() < y.String()
case reflect.Array:
for i := 0; i < x.Len(); i++ {
if isLess(x.Index(i), y.Index(i)) {
return true
}
if isLess(y.Index(i), x.Index(i)) {
return false
}
}
return false
case reflect.Struct:
for i := 0; i < x.NumField(); i++ {
if isLess(x.Field(i), y.Field(i)) {
return true
}
if isLess(y.Field(i), x.Field(i)) {
return false
}
}
return false
case reflect.Interface:
vx, vy := x.Elem(), y.Elem()
if !vx.IsValid() || !vy.IsValid() {
return !vx.IsValid() && vy.IsValid()
}
tx, ty := vx.Type(), vy.Type()
if tx == ty {
return isLess(x.Elem(), y.Elem())
}
if tx.Kind() != ty.Kind() {
return vx.Kind() < vy.Kind()
}
if tx.String() != ty.String() {
return tx.String() < ty.String()
}
if tx.PkgPath() != ty.PkgPath() {
return tx.PkgPath() < ty.PkgPath()
}
// This can happen in rare situations, so we fallback to just comparing
// the unique pointer for a reflect.Type. This guarantees deterministic
// ordering within a program, but it is obviously not stable.
return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer()
default:
// Must be Func, Map, or Slice; which are not comparable.
panic(fmt.Sprintf("%T is not comparable", x.Type()))
}
}
+456
View File
@@ -0,0 +1,456 @@
// Copyright 2017, 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.md file.
package cmp
import (
"fmt"
"reflect"
"runtime"
"strings"
"github.com/google/go-cmp/cmp/internal/function"
)
// Option configures for specific behavior of Equal and Diff. In particular,
// the fundamental Option functions (Ignore, Transformer, and Comparer),
// configure how equality is determined.
//
// The fundamental options may be composed with filters (FilterPath and
// FilterValues) to control the scope over which they are applied.
//
// The cmp/cmpopts package provides helper functions for creating options that
// may be used with Equal and Diff.
type Option interface {
// filter applies all filters and returns the option that remains.
// Each option may only read s.curPath and call s.callTTBFunc.
//
// An Options is returned only if multiple comparers or transformers
// can apply simultaneously and will only contain values of those types
// or sub-Options containing values of those types.
filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption
}
// applicableOption represents the following types:
// Fundamental: ignore | invalid | *comparer | *transformer
// Grouping: Options
type applicableOption interface {
Option
// apply executes the option, which may mutate s or panic.
apply(s *state, vx, vy reflect.Value)
}
// coreOption represents the following types:
// Fundamental: ignore | invalid | *comparer | *transformer
// Filters: *pathFilter | *valuesFilter
type coreOption interface {
Option
isCore()
}
type core struct{}
func (core) isCore() {}
// Options is a list of Option values that also satisfies the Option interface.
// Helper comparison packages may return an Options value when packing multiple
// Option values into a single Option. When this package processes an Options,
// it will be implicitly expanded into a flat list.
//
// Applying a filter on an Options is equivalent to applying that same filter
// on all individual options held within.
type Options []Option
func (opts Options) filter(s *state, vx, vy reflect.Value, t reflect.Type) (out applicableOption) {
for _, opt := range opts {
switch opt := opt.filter(s, vx, vy, t); opt.(type) {
case ignore:
return ignore{} // Only ignore can short-circuit evaluation
case invalid:
out = invalid{} // Takes precedence over comparer or transformer
case *comparer, *transformer, Options:
switch out.(type) {
case nil:
out = opt
case invalid:
// Keep invalid
case *comparer, *transformer, Options:
out = Options{out, opt} // Conflicting comparers or transformers
}
}
}
return out
}
func (opts Options) apply(s *state, _, _ reflect.Value) {
const warning = "ambiguous set of applicable options"
const help = "consider using filters to ensure at most one Comparer or Transformer may apply"
var ss []string
for _, opt := range flattenOptions(nil, opts) {
ss = append(ss, fmt.Sprint(opt))
}
set := strings.Join(ss, "\n\t")
panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", warning, s.curPath, set, help))
}
func (opts Options) String() string {
var ss []string
for _, opt := range opts {
ss = append(ss, fmt.Sprint(opt))
}
return fmt.Sprintf("Options{%s}", strings.Join(ss, ", "))
}
// FilterPath returns a new Option where opt is only evaluated if filter f
// returns true for the current Path in the value tree.
//
// The option passed in may be an Ignore, Transformer, Comparer, Options, or
// a previously filtered Option.
func FilterPath(f func(Path) bool, opt Option) Option {
if f == nil {
panic("invalid path filter function")
}
if opt := normalizeOption(opt); opt != nil {
return &pathFilter{fnc: f, opt: opt}
}
return nil
}
type pathFilter struct {
core
fnc func(Path) bool
opt Option
}
func (f pathFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption {
if f.fnc(s.curPath) {
return f.opt.filter(s, vx, vy, t)
}
return nil
}
func (f pathFilter) String() string {
fn := getFuncName(reflect.ValueOf(f.fnc).Pointer())
return fmt.Sprintf("FilterPath(%s, %v)", fn, f.opt)
}
// FilterValues returns a new Option where opt is only evaluated if filter f,
// which is a function of the form "func(T, T) bool", returns true for the
// current pair of values being compared. If the type of the values is not
// assignable to T, then this filter implicitly returns false.
//
// The filter function must be
// symmetric (i.e., agnostic to the order of the inputs) and
// deterministic (i.e., produces the same result when given the same inputs).
// If T is an interface, it is possible that f is called with two values with
// different concrete types that both implement T.
//
// The option passed in may be an Ignore, Transformer, Comparer, Options, or
// a previously filtered Option.
func FilterValues(f interface{}, opt Option) Option {
v := reflect.ValueOf(f)
if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() {
panic(fmt.Sprintf("invalid values filter function: %T", f))
}
if opt := normalizeOption(opt); opt != nil {
vf := &valuesFilter{fnc: v, opt: opt}
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
vf.typ = ti
}
return vf
}
return nil
}
type valuesFilter struct {
core
typ reflect.Type // T
fnc reflect.Value // func(T, T) bool
opt Option
}
func (f valuesFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption {
if !vx.IsValid() || !vy.IsValid() {
return invalid{}
}
if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) {
return f.opt.filter(s, vx, vy, t)
}
return nil
}
func (f valuesFilter) String() string {
fn := getFuncName(f.fnc.Pointer())
return fmt.Sprintf("FilterValues(%s, %v)", fn, f.opt)
}
// Ignore is an Option that causes all comparisons to be ignored.
// This value is intended to be combined with FilterPath or FilterValues.
// It is an error to pass an unfiltered Ignore option to Equal.
func Ignore() Option { return ignore{} }
type ignore struct{ core }
func (ignore) isFiltered() bool { return false }
func (ignore) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return ignore{} }
func (ignore) apply(_ *state, _, _ reflect.Value) { return }
func (ignore) String() string { return "Ignore()" }
// invalid is a sentinel Option type to indicate that some options could not
// be evaluated due to unexported fields.
type invalid struct{ core }
func (invalid) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return invalid{} }
func (invalid) apply(s *state, _, _ reflect.Value) {
const help = "consider using AllowUnexported or cmpopts.IgnoreUnexported"
panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help))
}
// Transformer returns an Option that applies a transformation function that
// converts values of a certain type into that of another.
//
// The transformer f must be a function "func(T) R" that converts values of
// type T to those of type R and is implicitly filtered to input values
// assignable to T. The transformer must not mutate T in any way.
//
// To help prevent some cases of infinite recursive cycles applying the
// same transform to the output of itself (e.g., in the case where the
// input and output types are the same), an implicit filter is added such that
// a transformer is applicable only if that exact transformer is not already
// in the tail of the Path since the last non-Transform step.
// For situations where the implicit filter is still insufficient,
// consider using cmpopts.AcyclicTransformer, which adds a filter
// to prevent the transformer from being recursively applied upon itself.
//
// The name is a user provided label that is used as the Transform.Name in the
// transformation PathStep. If empty, an arbitrary name is used.
func Transformer(name string, f interface{}) Option {
v := reflect.ValueOf(f)
if !function.IsType(v.Type(), function.Transformer) || v.IsNil() {
panic(fmt.Sprintf("invalid transformer function: %T", f))
}
if name == "" {
name = "λ" // Lambda-symbol as place-holder for anonymous transformer
}
if !isValid(name) {
panic(fmt.Sprintf("invalid name: %q", name))
}
tr := &transformer{name: name, fnc: reflect.ValueOf(f)}
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
tr.typ = ti
}
return tr
}
type transformer struct {
core
name string
typ reflect.Type // T
fnc reflect.Value // func(T) R
}
func (tr *transformer) isFiltered() bool { return tr.typ != nil }
func (tr *transformer) filter(s *state, _, _ reflect.Value, t reflect.Type) applicableOption {
for i := len(s.curPath) - 1; i >= 0; i-- {
if t, ok := s.curPath[i].(*transform); !ok {
break // Hit most recent non-Transform step
} else if tr == t.trans {
return nil // Cannot directly use same Transform
}
}
if tr.typ == nil || t.AssignableTo(tr.typ) {
return tr
}
return nil
}
func (tr *transformer) apply(s *state, vx, vy reflect.Value) {
// Update path before calling the Transformer so that dynamic checks
// will use the updated path.
s.curPath.push(&transform{pathStep{tr.fnc.Type().Out(0)}, tr})
defer s.curPath.pop()
vx = s.callTRFunc(tr.fnc, vx)
vy = s.callTRFunc(tr.fnc, vy)
s.compareAny(vx, vy)
}
func (tr transformer) String() string {
return fmt.Sprintf("Transformer(%s, %s)", tr.name, getFuncName(tr.fnc.Pointer()))
}
// Comparer returns an Option that determines whether two values are equal
// to each other.
//
// The comparer f must be a function "func(T, T) bool" and is implicitly
// filtered to input values assignable to T. If T is an interface, it is
// possible that f is called with two values of different concrete types that
// both implement T.
//
// The equality function must be:
// • Symmetric: equal(x, y) == equal(y, x)
// • Deterministic: equal(x, y) == equal(x, y)
// • Pure: equal(x, y) does not modify x or y
func Comparer(f interface{}) Option {
v := reflect.ValueOf(f)
if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
panic(fmt.Sprintf("invalid comparer function: %T", f))
}
cm := &comparer{fnc: v}
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
cm.typ = ti
}
return cm
}
type comparer struct {
core
typ reflect.Type // T
fnc reflect.Value // func(T, T) bool
}
func (cm *comparer) isFiltered() bool { return cm.typ != nil }
func (cm *comparer) filter(_ *state, _, _ reflect.Value, t reflect.Type) applicableOption {
if cm.typ == nil || t.AssignableTo(cm.typ) {
return cm
}
return nil
}
func (cm *comparer) apply(s *state, vx, vy reflect.Value) {
eq := s.callTTBFunc(cm.fnc, vx, vy)
s.report(eq, vx, vy)
}
func (cm comparer) String() string {
return fmt.Sprintf("Comparer(%s)", getFuncName(cm.fnc.Pointer()))
}
// AllowUnexported returns an Option that forcibly allows operations on
// unexported fields in certain structs, which are specified by passing in a
// value of each struct type.
//
// Users of this option must understand that comparing on unexported fields
// from external packages is not safe since changes in the internal
// implementation of some external package may cause the result of Equal
// to unexpectedly change. However, it may be valid to use this option on types
// defined in an internal package where the semantic meaning of an unexported
// field is in the control of the user.
//
// For some cases, a custom Comparer should be used instead that defines
// equality as a function of the public API of a type rather than the underlying
// unexported implementation.
//
// For example, the reflect.Type documentation defines equality to be determined
// by the == operator on the interface (essentially performing a shallow pointer
// comparison) and most attempts to compare *regexp.Regexp types are interested
// in only checking that the regular expression strings are equal.
// Both of these are accomplished using Comparers:
//
// Comparer(func(x, y reflect.Type) bool { return x == y })
// Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })
//
// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore
// all unexported fields on specified struct types.
func AllowUnexported(types ...interface{}) Option {
if !supportAllowUnexported {
panic("AllowUnexported is not supported on purego builds, Google App Engine Standard, or GopherJS")
}
m := make(map[reflect.Type]bool)
for _, typ := range types {
t := reflect.TypeOf(typ)
if t.Kind() != reflect.Struct {
panic(fmt.Sprintf("invalid struct type: %T", typ))
}
m[t] = true
}
return visibleStructs(m)
}
type visibleStructs map[reflect.Type]bool
func (visibleStructs) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption {
panic("not implemented")
}
// reporter is an Option that configures how differences are reported.
type reporter interface {
// TODO: Not exported yet.
//
// Perhaps add PushStep and PopStep and change Report to only accept
// a PathStep instead of the full-path? Adding a PushStep and PopStep makes
// it clear that we are traversing the value tree in a depth-first-search
// manner, which has an effect on how values are printed.
Option
// Report is called for every comparison made and will be provided with
// the two values being compared, the equality result, and the
// current path in the value tree. It is possible for x or y to be an
// invalid reflect.Value if one of the values is non-existent;
// which is possible with maps and slices.
Report(x, y reflect.Value, eq bool, p Path)
}
// normalizeOption normalizes the input options such that all Options groups
// are flattened and groups with a single element are reduced to that element.
// Only coreOptions and Options containing coreOptions are allowed.
func normalizeOption(src Option) Option {
switch opts := flattenOptions(nil, Options{src}); len(opts) {
case 0:
return nil
case 1:
return opts[0]
default:
return opts
}
}
// flattenOptions copies all options in src to dst as a flat list.
// Only coreOptions and Options containing coreOptions are allowed.
func flattenOptions(dst, src Options) Options {
for _, opt := range src {
switch opt := opt.(type) {
case nil:
continue
case Options:
dst = flattenOptions(dst, opt)
case coreOption:
dst = append(dst, opt)
default:
panic(fmt.Sprintf("invalid option type: %T", opt))
}
}
return dst
}
// getFuncName returns a short function name from the pointer.
// The string parsing logic works up until Go1.9.
func getFuncName(p uintptr) string {
fnc := runtime.FuncForPC(p)
if fnc == nil {
return "<unknown>"
}
name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm"
if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") {
// Strip the package name from method name.
name = strings.TrimSuffix(name, ")-fm")
name = strings.TrimSuffix(name, ")·fm")
if i := strings.LastIndexByte(name, '('); i >= 0 {
methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc"
if j := strings.LastIndexByte(methodName, '.'); j >= 0 {
methodName = methodName[j+1:] // E.g., "myfunc"
}
name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc"
}
}
if i := strings.LastIndexByte(name, '/'); i >= 0 {
// Strip the package name.
name = name[i+1:] // E.g., "mypkg.(mytype).myfunc"
}
return name
}
+309
View File
@@ -0,0 +1,309 @@
// Copyright 2017, 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.md file.
package cmp
import (
"fmt"
"reflect"
"strings"
"unicode"
"unicode/utf8"
)
type (
// Path is a list of PathSteps describing the sequence of operations to get
// from some root type to the current position in the value tree.
// The first Path element is always an operation-less PathStep that exists
// simply to identify the initial type.
//
// When traversing structs with embedded structs, the embedded struct will
// always be accessed as a field before traversing the fields of the
// embedded struct themselves. That is, an exported field from the
// embedded struct will never be accessed directly from the parent struct.
Path []PathStep
// PathStep is a union-type for specific operations to traverse
// a value's tree structure. Users of this package never need to implement
// these types as values of this type will be returned by this package.
PathStep interface {
String() string
Type() reflect.Type // Resulting type after performing the path step
isPathStep()
}
// SliceIndex is an index operation on a slice or array at some index Key.
SliceIndex interface {
PathStep
Key() int // May return -1 if in a split state
// SplitKeys returns the indexes for indexing into slices in the
// x and y values, respectively. These indexes may differ due to the
// insertion or removal of an element in one of the slices, causing
// all of the indexes to be shifted. If an index is -1, then that
// indicates that the element does not exist in the associated slice.
//
// Key is guaranteed to return -1 if and only if the indexes returned
// by SplitKeys are not the same. SplitKeys will never return -1 for
// both indexes.
SplitKeys() (x int, y int)
isSliceIndex()
}
// MapIndex is an index operation on a map at some index Key.
MapIndex interface {
PathStep
Key() reflect.Value
isMapIndex()
}
// TypeAssertion represents a type assertion on an interface.
TypeAssertion interface {
PathStep
isTypeAssertion()
}
// StructField represents a struct field access on a field called Name.
StructField interface {
PathStep
Name() string
Index() int
isStructField()
}
// Indirect represents pointer indirection on the parent type.
Indirect interface {
PathStep
isIndirect()
}
// Transform is a transformation from the parent type to the current type.
Transform interface {
PathStep
Name() string
Func() reflect.Value
// Option returns the originally constructed Transformer option.
// The == operator can be used to detect the exact option used.
Option() Option
isTransform()
}
)
func (pa *Path) push(s PathStep) {
*pa = append(*pa, s)
}
func (pa *Path) pop() {
*pa = (*pa)[:len(*pa)-1]
}
// Last returns the last PathStep in the Path.
// If the path is empty, this returns a non-nil PathStep that reports a nil Type.
func (pa Path) Last() PathStep {
return pa.Index(-1)
}
// Index returns the ith step in the Path and supports negative indexing.
// A negative index starts counting from the tail of the Path such that -1
// refers to the last step, -2 refers to the second-to-last step, and so on.
// If index is invalid, this returns a non-nil PathStep that reports a nil Type.
func (pa Path) Index(i int) PathStep {
if i < 0 {
i = len(pa) + i
}
if i < 0 || i >= len(pa) {
return pathStep{}
}
return pa[i]
}
// String returns the simplified path to a node.
// The simplified path only contains struct field accesses.
//
// For example:
// MyMap.MySlices.MyField
func (pa Path) String() string {
var ss []string
for _, s := range pa {
if _, ok := s.(*structField); ok {
ss = append(ss, s.String())
}
}
return strings.TrimPrefix(strings.Join(ss, ""), ".")
}
// GoString returns the path to a specific node using Go syntax.
//
// For example:
// (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
func (pa Path) GoString() string {
var ssPre, ssPost []string
var numIndirect int
for i, s := range pa {
var nextStep PathStep
if i+1 < len(pa) {
nextStep = pa[i+1]
}
switch s := s.(type) {
case *indirect:
numIndirect++
pPre, pPost := "(", ")"
switch nextStep.(type) {
case *indirect:
continue // Next step is indirection, so let them batch up
case *structField:
numIndirect-- // Automatic indirection on struct fields
case nil:
pPre, pPost = "", "" // Last step; no need for parenthesis
}
if numIndirect > 0 {
ssPre = append(ssPre, pPre+strings.Repeat("*", numIndirect))
ssPost = append(ssPost, pPost)
}
numIndirect = 0
continue
case *transform:
ssPre = append(ssPre, s.trans.name+"(")
ssPost = append(ssPost, ")")
continue
case *typeAssertion:
// As a special-case, elide type assertions on anonymous types
// since they are typically generated dynamically and can be very
// verbose. For example, some transforms return interface{} because
// of Go's lack of generics, but typically take in and return the
// exact same concrete type.
if s.Type().PkgPath() == "" {
continue
}
}
ssPost = append(ssPost, s.String())
}
for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 {
ssPre[i], ssPre[j] = ssPre[j], ssPre[i]
}
return strings.Join(ssPre, "") + strings.Join(ssPost, "")
}
type (
pathStep struct {
typ reflect.Type
}
sliceIndex struct {
pathStep
xkey, ykey int
}
mapIndex struct {
pathStep
key reflect.Value
}
typeAssertion struct {
pathStep
}
structField struct {
pathStep
name string
idx int
// These fields are used for forcibly accessing an unexported field.
// pvx, pvy, and field are only valid if unexported is true.
unexported bool
force bool // Forcibly allow visibility
pvx, pvy reflect.Value // Parent values
field reflect.StructField // Field information
}
indirect struct {
pathStep
}
transform struct {
pathStep
trans *transformer
}
)
func (ps pathStep) Type() reflect.Type { return ps.typ }
func (ps pathStep) String() string {
if ps.typ == nil {
return "<nil>"
}
s := ps.typ.String()
if s == "" || strings.ContainsAny(s, "{}\n") {
return "root" // Type too simple or complex to print
}
return fmt.Sprintf("{%s}", s)
}
func (si sliceIndex) String() string {
switch {
case si.xkey == si.ykey:
return fmt.Sprintf("[%d]", si.xkey)
case si.ykey == -1:
// [5->?] means "I don't know where X[5] went"
return fmt.Sprintf("[%d->?]", si.xkey)
case si.xkey == -1:
// [?->3] means "I don't know where Y[3] came from"
return fmt.Sprintf("[?->%d]", si.ykey)
default:
// [5->3] means "X[5] moved to Y[3]"
return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey)
}
}
func (mi mapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) }
func (ta typeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) }
func (sf structField) String() string { return fmt.Sprintf(".%s", sf.name) }
func (in indirect) String() string { return "*" }
func (tf transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) }
func (si sliceIndex) Key() int {
if si.xkey != si.ykey {
return -1
}
return si.xkey
}
func (si sliceIndex) SplitKeys() (x, y int) { return si.xkey, si.ykey }
func (mi mapIndex) Key() reflect.Value { return mi.key }
func (sf structField) Name() string { return sf.name }
func (sf structField) Index() int { return sf.idx }
func (tf transform) Name() string { return tf.trans.name }
func (tf transform) Func() reflect.Value { return tf.trans.fnc }
func (tf transform) Option() Option { return tf.trans }
func (pathStep) isPathStep() {}
func (sliceIndex) isSliceIndex() {}
func (mapIndex) isMapIndex() {}
func (typeAssertion) isTypeAssertion() {}
func (structField) isStructField() {}
func (indirect) isIndirect() {}
func (transform) isTransform() {}
var (
_ SliceIndex = sliceIndex{}
_ MapIndex = mapIndex{}
_ TypeAssertion = typeAssertion{}
_ StructField = structField{}
_ Indirect = indirect{}
_ Transform = transform{}
_ PathStep = sliceIndex{}
_ PathStep = mapIndex{}
_ PathStep = typeAssertion{}
_ PathStep = structField{}
_ PathStep = indirect{}
_ PathStep = transform{}
)
// isExported reports whether the identifier is exported.
func isExported(id string) bool {
r, _ := utf8.DecodeRuneInString(id)
return unicode.IsUpper(r)
}
// isValid reports whether the identifier is valid.
// Empty and underscore-only strings are not valid.
func isValid(id string) bool {
ok := id != "" && id != "_"
for j, c := range id {
ok = ok && (j > 0 || !unicode.IsDigit(c))
ok = ok && (c == '_' || unicode.IsLetter(c) || unicode.IsDigit(c))
}
return ok
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2017, 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.md file.
package cmp
import (
"fmt"
"reflect"
"strings"
"github.com/google/go-cmp/cmp/internal/value"
)
type defaultReporter struct {
Option
diffs []string // List of differences, possibly truncated
ndiffs int // Total number of differences
nbytes int // Number of bytes in diffs
nlines int // Number of lines in diffs
}
var _ reporter = (*defaultReporter)(nil)
func (r *defaultReporter) Report(x, y reflect.Value, eq bool, p Path) {
if eq {
return // Ignore equal results
}
const maxBytes = 4096
const maxLines = 256
r.ndiffs++
if r.nbytes < maxBytes && r.nlines < maxLines {
sx := value.Format(x, value.FormatConfig{UseStringer: true})
sy := value.Format(y, value.FormatConfig{UseStringer: true})
if sx == sy {
// Unhelpful output, so use more exact formatting.
sx = value.Format(x, value.FormatConfig{PrintPrimitiveType: true})
sy = value.Format(y, value.FormatConfig{PrintPrimitiveType: true})
}
s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy)
r.diffs = append(r.diffs, s)
r.nbytes += len(s)
r.nlines += strings.Count(s, "\n")
}
}
func (r *defaultReporter) String() string {
s := strings.Join(r.diffs, "")
if r.ndiffs == len(r.diffs) {
return s
}
return fmt.Sprintf("%s... %d more differences ...", s, r.ndiffs-len(r.diffs))
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2017, 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.md file.
// +build !purego,!appengine,!js
package cmp
import (
"reflect"
"unsafe"
)
const supportAllowUnexported = true
// unsafeRetrieveField uses unsafe to forcibly retrieve any field from a struct
// such that the value has read-write permissions.
//
// The parent struct, v, must be addressable, while f must be a StructField
// describing the field to retrieve.
func unsafeRetrieveField(v reflect.Value, f reflect.StructField) reflect.Value {
return reflect.NewAt(f.Type, unsafe.Pointer(v.UnsafeAddr()+f.Offset)).Elem()
}
+24
View File
@@ -684,6 +684,30 @@
"path": "github.com/golang/protobuf/proto",
"revision": "b982704f8bb716bb608144408cff30e15fbde841"
},
{
"checksumSHA1": "sOd6u/eTkieb8AgKxnXZ/C9/FhI=",
"path": "github.com/google/go-cmp/cmp",
"revision": "5411ab924f9ffa6566244a9e504bc347edacffd3",
"revisionTime": "2018-03-28T20:15:12Z"
},
{
"checksumSHA1": "oLBV7th4sxzxzmf1b91NVVbdOfI=",
"path": "github.com/google/go-cmp/cmp/internal/diff",
"revision": "5411ab924f9ffa6566244a9e504bc347edacffd3",
"revisionTime": "2018-03-28T20:15:12Z"
},
{
"checksumSHA1": "kYtvRhMjM0X4bvEjR3pqEHLw1qo=",
"path": "github.com/google/go-cmp/cmp/internal/function",
"revision": "5411ab924f9ffa6566244a9e504bc347edacffd3",
"revisionTime": "2018-03-28T20:15:12Z"
},
{
"checksumSHA1": "OQQ56f38ikfmG2Zd3lUlk4LUBIw=",
"path": "github.com/google/go-cmp/cmp/internal/value",
"revision": "5411ab924f9ffa6566244a9e504bc347edacffd3",
"revisionTime": "2018-03-28T20:15:12Z"
},
{
"checksumSHA1": "Evpv9y6iPdy+8FeAVDmKrqV1sqo=",
"path": "github.com/google/go-querystring/query",
+2 -2
View File
@@ -9,12 +9,12 @@ import (
var GitCommit string
// The main version number that is being run at the moment.
const Version = "1.2.5"
const Version = "1.2.6"
// A pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
// such as "dev" (in development), "beta", "rc1", etc.
const VersionPrerelease = ""
const VersionPrerelease = "dev"
func FormattedVersion() string {
var versionString bytes.Buffer
+1 -1
View File
@@ -39,7 +39,7 @@ power of Packer templates.
\- Ubuntu 16.04 minimal Vagrant Box using Ansible provisioner
* [jakobadam/packer-qemu-templates](https://github.com/jakobadam/packer-qemu-templates)
- QEMU templates for various operating systems
\- QEMU templates for various operating systems
## Wrappers
@@ -254,6 +254,11 @@ each category, the available configuration keys are alphabetized.
of the `source_ami` unless `from_scratch` is `true`, in which case
this field must be defined.
- `root_volume_tags` (object of key/value strings) - Tags to apply to the volumes
that are *launched*. This is a
[template engine](/docs/templates/engine.html),
see [Build template data](#build-template-data) for more information.
- `skip_region_validation` (boolean) - Set to true if you want to skip
validation of the `ami_regions` configuration option. Default `false`.
@@ -531,3 +531,19 @@ up all residual volumes that are not designated by the user to remain after
termination. If you need to preserve those source volumes, you can overwrite the
termination setting by specifying `delete_on_termination=false` in the
`launch_block_device_mappings` block for the device.
## Windows 2016 Sysprep Commands - For Amazon Windows AMIs Only
For Amazon Windows 2016 AMIs it is necessary to run Sysprep commands which can be easily added
to the provisioner section.
```json
{
"type": "powershell",
"inline": [
"C:/ProgramData/Amazon/EC2-Windows/Launch/Scripts/InitializeInstance.ps1 -Schedule",
"C:/ProgramData/Amazon/EC2-Windows/Launch/Scripts/SysprepInstance.ps1 -NoShutdown"
]
}
```
@@ -88,6 +88,8 @@ builder.
- `user_data_file` (string) - Path to a file that will be used for the user
data when launching the Droplet.
- `tags` (list) - Tags to apply to the droplet when it is created
## Basic Example
Here is a basic example. It is completely valid as soon as you enter your own
@@ -13,10 +13,14 @@ Type: `googlecompute`
The `googlecompute` Packer builder is able to create
[images](https://developers.google.com/compute/docs/images) for use with [Google
Compute Engine](https://cloud.google.com/products/compute-engine)(GCE) based on
existing images. Building GCE images from scratch is not possible from Packer at
this time. For building images from scratch, please see
[Building GCE Images from Scratch](https://cloud.google.com/compute/docs/tutorials/building-images).
Compute Engine](https://cloud.google.com/products/compute-engine) (GCE) based on
existing images.
Building images from scratch is possible, but not with the `googlecompute` Packer builder.
The process is recommended only for advanced users, please see [Building GCE Images from Scratch]
(https://cloud.google.com/compute/docs/tutorials/building-images)
and the [Google Compute Import Post-Processor](/docs/post-processors/googlecompute-import.html)
for more information.
## Authentication
+4 -1
View File
@@ -66,8 +66,11 @@ Below is a fully functioning example.
- `command_wrapper` (string) - Lets you prefix all builder commands, such as
with `ssh` for a remote build host. Defaults to `""`.
- `publish_properties` (map[string]string) - Pass key values to the publish
- `publish_properties` (map[string]string) - Pass key values to the publish
step to be set as properties on the output image. This is most helpful to
set the description, but can be used to set anything needed.
See https://stgraber.org/2016/03/30/lxd-2-0-image-management-512/
for more properties.
- `launch_config` (map[string]string) - List of key/value pairs you wish to
pass to `lxc launch` via `--config`. Defaults to empty.
@@ -139,6 +139,9 @@ builder.
- `networks` (array of strings) - A list of networks by UUID to attach to
this instance.
- `ports` (array of strings) - A list of ports by UUID to attach to
this instance.
- `rackconnect_wait` (boolean) - For rackspace, whether or not to wait for
Rackconnect to assign the machine an IP address before connecting via SSH.
Defaults to false.
@@ -125,9 +125,13 @@ builder.
- `use_private_ip` (boolean) - Use private ip addresses to connect to the instance via ssh.
- `metadata` (map of strings) - Metadata optionally contains custom metadata key/value pairs provided in the
configuration. While this can be used to set metadata["user_data"] the explicit "user_data" and "user_data_file" values will have precedence. An instance's metadata can be obtained from at http://169.254.169.254 on the
launched instance.
- `user_data` (string) - user_data to be used by cloud
init. See [the Oracle docs](https://docs.us-phoenix-1.oraclecloud.com/api/#/en/iaas/20160918/LaunchInstanceDetails) for more details. Generally speaking, it is easier to use the `user_data_file`,
but you can use this option to put either the platintext data or the base64
but you can use this option to put either the plaintext data or the base64
encoded data directly into your Packer config.
- `user_data_file` (string) - Path to a file to be used as user_data by cloud
@@ -152,8 +152,9 @@ Linux server and have not enabled X11 forwarding (`ssh -X`).
- `disk_image` (boolean) - Packer defaults to building from an ISO file, this
parameter controls whether the ISO URL supplied is actually a bootable
QEMU image. When this value is set to true, the machine will clone the
source, resize it according to `disk_size` and boot the image.
QEMU image. When this value is set to `true`, the machine will either clone
the source or use it as a backing file (if `use_backing_file` is `true`);
then, it will resize the image according to `disk_size` and boot it.
- `disk_interface` (string) - The interface to use for the disk. Allowed
values include any of `ide`, `scsi`, `virtio` or `virtio-scsi`^\*. Note
@@ -319,6 +320,12 @@ will bind to their own SSH port as determined by each process. This will also
work with WinRM, just change the port forward in `qemuargs` to map to WinRM's
default port of `5985` or whatever value you have the service set to listen on.
- `use_backing_file` (boolean) - Only applicable when `disk_image` is `true`
and `format` is `qcow2`, set this option to `true` to create a new QCOW2
file that uses the file located at `iso_url` as a backing file. The new file
will only contain blocks that have changed compared to the backing file, so
enabling this option can significantly reduce disk usage.
- `use_default_display` (boolean) - If true, do not pass a `-display` option
to qemu, allowing it to choose the default. This may be needed when running
under macOS, and getting errors about `sdl` not being available.
@@ -31,14 +31,16 @@ Currently, the Vagrant post-processor can create boxes for the following
providers.
- AWS
- Azure
- DigitalOcean
- Google
- Azure
- Hyper-V
- LXC
- Parallels
- QEMU
- VirtualBox
- VMware
- Docker
-&gt; **Support for additional providers** is planned. If the Vagrant
post-processor doesn't support creating boxes for a provider you care about,
@@ -105,6 +107,7 @@ where it will be set to 0.
The available provider names are:
- `aws`
- `azure`
- `digitalocean`
- `google`
- `hyperv`
@@ -114,6 +117,7 @@ The available provider names are:
- `scaleway`
- `virtualbox`
- `vmware`
- `docker`
## Input Artifacts
@@ -124,3 +128,16 @@ it.
Please see the [documentation on input
artifacts](/docs/templates/post-processors.html#toc_2) for more information.
### Docker
Using a Docker input artifact will include a reference to the image in the
`Vagrantfile`. If the image tag is not specified in the post-processor, the
sha256 hash will be used.
The following Docker input artifacts are supported:
- `docker` builder with `commit: true`, always uses the sha256 hash
- `docker-import`
- `docker-tag`
- `docker-push`
@@ -14,7 +14,7 @@ Type: `ansible`
The `ansible` Packer provisioner runs Ansible playbooks. It dynamically creates
an Ansible inventory file configured to use SSH, runs an SSH server, executes
`ansible-playbook`, and marshals Ansible plays through the SSH server to the
machine being provisioned by Packer.
machine being provisioned by Packer.
-&gt; **Note:**: Any `remote_user` defined in tasks will be ignored. Packer will
always connect with the user given in the json config for this provisioner.
@@ -61,6 +61,15 @@ Optional Parameters:
"ansible_env_vars": [ "ANSIBLE_HOST_KEY_CHECKING=False", "ANSIBLE_SSH_ARGS='-o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s'", "ANSIBLE_NOCOLOR=True" ]
}
```
If you are running a Windows build on AWS, Azure or Google Compute and would
like to access the auto-generated password that Packer uses to connect to a
Windows instance via WinRM, you can use the template variable
{{.WinRMPassword}} in this option.
For example:
```json
"ansible_env_vars": [ "WINRM_PASSWORD={{.WinRMPassword}}" ],
```
- `command` (string) - The command to invoke ansible.
Defaults to `ansible-playbook`.
@@ -72,12 +81,24 @@ Optional Parameters:
These arguments *will not* be passed through a shell and arguments should
not be quoted. Usage example:
``` json
```json
{
"extra_arguments": [ "--extra-vars", "Region={{user `Region`}} Stage={{user `Stage`}}" ]
}
```
If you are running a Windows build on AWS, Azure or Google Compute and would
like to access the auto-generated password that Packer uses to connect to a
Windows instance via WinRM, you can use the template variable
{{.WinRMPassword}} in this option.
For example:
```json
"extra_arguments": [
"--extra-vars", "winrm_password={{ .WinRMPassword }}"
]
```
- `groups` (array of strings) - The groups into which the Ansible host
should be placed. When unspecified, the host is not associated with any
groups.
@@ -142,11 +163,18 @@ commonly useful Ansible variables:
machine that the script is running on. This is useful if you want to run
only certain parts of the playbook on systems built with certain builders.
- `packer_http_addr` If using a builder that provides an http server for file
transfer (such as hyperv, parallels, qemu, virtualbox, and vmware), this
will be set to the address. You can use this address in your provisioner to
download large files over http. This may be useful if you're experiencing
slower speeds using the default file provisioner. A file provisioner using
the `winrm` communicator may experience these types of difficulties.
## Debugging
To debug underlying issues with Ansible, add `"-vvvv"` to `"extra_arguments"` to enable verbose logging.
``` json
```json
{
"extra_arguments": [ "-vvvv" ]
}
@@ -167,7 +195,7 @@ Redhat / CentOS builds have been known to fail with the following error due to `
Building within a chroot (e.g. `amazon-chroot`) requires changing the Ansible connection to chroot.
``` json
```json
{
"builders": [
{
@@ -184,6 +184,13 @@ commonly useful environmental variables:
machine that the script is running on. This is useful if you want to run
only certain parts of the script on systems built with certain builders.
- `PACKER_HTTP_ADDR` If using a builder that provides an http server for file
transfer (such as hyperv, parallels, qemu, virtualbox, and vmware), this
will be set to the address. You can use this address in your provisioner to
download large files over http. This may be useful if you're experiencing
slower speeds using the default file provisioner. A file provisioner using
the `winrm` communicator may experience these types of difficulties.
## Safely Writing A Script
Whether you use the `inline` option, or pass it a direct `script` or `scripts`,
+20
View File
@@ -73,6 +73,26 @@ Here is a full list of the available functions for reference.
fail because the image name will start with a number, which is why in the
above example we prepend the isotime with "mybuild".
#### Specific to Azure builders:
- `clean_image_name` - Azure managed image names can only contain
certain characters and the maximum length is 80. This function
will replace illegal characters with a "-" character. Example:
`"mybuild-{{isotime | clean_image_name}}"` will become
`mybuild-2017-10-18t02-06-30z`.
Note: Valid Azure image names must match the regex
`^[^_\\W][\\w-._)]{0,79}$`
This engine does not guarantee that the final image name will
match the regex; it will not truncate your name if it exceeds 80
characters, and it will not validate that the beginning and end of
the engine's output are valid. It will truncate invalid
characters from the end of the name when converting illegal
characters. For example, `"managed_image_name: "My-Name::"` will
be converted to `"managed_image_name: "My-Name"`
## Template variables
Template variables are special variables automatically set by Packer at build time. Some builders, provisioners and other components have template variables that are available only for that component. Template variables are recognizable because they're prefixed by a period, such as `{{ .Name }}`. For example, when using the [`shell`](/docs/builders/vmware-iso.html) builder template variables are available to customize the [`execute_command`](/docs/provisioners/shell.html#execute_command) parameter used to determine how Packer will run the shell command.
+3
View File
@@ -281,6 +281,9 @@
<li<%= sidebar_current("docs-post-processors-googlecompute-export") %>>
<a href="/docs/post-processors/googlecompute-export.html">Google Compute Export</a>
</li>
<li<%= sidebar_current("docs-post-processors-googlecompute-import") %>>
<a href="/docs/post-processors/googlecompute-import.html">Google Compute Import</a>
</li>
<li<%= sidebar_current("docs-post-processors-manifest") %>>
<a href="/docs/post-processors/manifest.html">Manifest</a>
</li>