From fc4b2e1569b4ae8f2bfffff108777b7c82cd5ce8 Mon Sep 17 00:00:00 2001 From: Ben Broderick Phillips Date: Thu, 27 Feb 2014 00:34:24 -0800 Subject: [PATCH 001/593] Add flexible IP pool/address handling. --- builder/openstack/builder.go | 4 ++ builder/openstack/run_config.go | 7 +++ builder/openstack/ssh.go | 37 +++++++++------ builder/openstack/step_allocate_ip.go | 66 +++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 builder/openstack/step_allocate_ip.go diff --git a/builder/openstack/builder.go b/builder/openstack/builder.go index d63e847f2..e745b8db0 100644 --- a/builder/openstack/builder.go +++ b/builder/openstack/builder.go @@ -92,6 +92,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Flavor: b.config.Flavor, SourceImage: b.config.SourceImage, }, + &StepAllocateIp{ + FloatingIpPool: b.config.FloatingIpPool, + FloatingIp: b.config.FloatingIp, + }, &common.StepConnectSSH{ SSHAddress: SSHAddress(csp, b.config.SSHPort), SSHConfig: SSHConfig(b.config.SSHUsername), diff --git a/builder/openstack/run_config.go b/builder/openstack/run_config.go index 688d681a5..96388e254 100644 --- a/builder/openstack/run_config.go +++ b/builder/openstack/run_config.go @@ -16,6 +16,9 @@ type RunConfig struct { SSHUsername string `mapstructure:"ssh_username"` SSHPort int `mapstructure:"ssh_port"` OpenstackProvider string `mapstructure:"openstack_provider"` + UseFloatingIp bool `mapstructure:"use_floating_ip"` + FloatingIpPool string `mapstructure:"floating_ip_pool"` + FloatingIp string `mapstructure:"floating_ip"` // Unexported fields that are calculated from others sshTimeout time.Duration @@ -43,6 +46,10 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { c.RawSSHTimeout = "5m" } + if c.UseFloatingIp && c.FloatingIpPool == "" { + c.FloatingIpPool = "public" + } + // Validation var err error errs := make([]error, 0) diff --git a/builder/openstack/ssh.go b/builder/openstack/ssh.go index a27a7f3c4..bdb3d75d6 100644 --- a/builder/openstack/ssh.go +++ b/builder/openstack/ssh.go @@ -13,24 +13,35 @@ import ( // for determining the SSH address based on the server AccessIPv4 setting.. func SSHAddress(csp gophercloud.CloudServersProvider, port int) func(multistep.StateBag) (string, error) { return func(state multistep.StateBag) (string, error) { - for j := 0; j < 2; j++ { - s := state.Get("server").(*gophercloud.Server) - if s.AccessIPv4 != "" { - return fmt.Sprintf("%s:%d", s.AccessIPv4, port), nil - } - if s.AccessIPv6 != "" { - return fmt.Sprintf("[%s]:%d", s.AccessIPv6, port), nil - } - serverState, err := csp.ServerById(s.Id) + s := state.Get("server").(*gophercloud.Server) - if err != nil { - return "", err + if ip := state.Get("access_ip").(gophercloud.FloatingIp); ip.Ip != "" { + return fmt.Sprintf("%s:%d", ip.Ip, port), nil + } + + ip_pools, err := s.AllAddressPools() + if err != nil { + return "", errors.New("Error parsing SSH addresses") + } + for pool, addresses := range ip_pools { + if pool != "" { + for _, address := range addresses { + if address.Addr != "" { + return fmt.Sprintf("%s:%d", address.Addr, port), nil + } + } } + } + + serverState, err := csp.ServerById(s.Id) - state.Put("server", serverState) - time.Sleep(1 * time.Second) + if err != nil { + return "", err } + state.Put("server", serverState) + time.Sleep(1 * time.Second) + return "", errors.New("couldn't determine IP address for server") } } diff --git a/builder/openstack/step_allocate_ip.go b/builder/openstack/step_allocate_ip.go new file mode 100644 index 000000000..28e97b4d5 --- /dev/null +++ b/builder/openstack/step_allocate_ip.go @@ -0,0 +1,66 @@ +package openstack + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/rackspace/gophercloud" +) + +type StepAllocateIp struct { + FloatingIpPool string + FloatingIp string +} + +func (s *StepAllocateIp) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + csp := state.Get("csp").(gophercloud.CloudServersProvider) + server := state.Get("server").(*gophercloud.Server) + + var instanceIp gophercloud.FloatingIp + // This is here in case we error out before putting instanceIp into the + // statebag below, because it is requested by Cleanup() + state.Put("access_ip", instanceIp) + + if s.FloatingIp != "" { + instanceIp.Ip = s.FloatingIp + } else if s.FloatingIpPool != "" { + newIp, err := csp.CreateFloatingIp(s.FloatingIpPool) + if err != nil { + err := fmt.Errorf("Error creating floating ip from pool '%s'", s.FloatingIpPool) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + instanceIp = newIp + ui.Say(fmt.Sprintf("Created temporary floating IP %s...", instanceIp.Ip)) + } + + if instanceIp.Ip != "" { + if err := csp.AssociateFloatingIp(server.Id, instanceIp); err != nil { + err := fmt.Errorf("Error associating floating IP %s with instance.", instanceIp.Ip) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } else { + ui.Say(fmt.Sprintf("Added floating IP %s to instance...", instanceIp.Ip)) + } + } + + state.Put("access_ip", instanceIp) + + return multistep.ActionContinue +} + +func (s *StepAllocateIp) Cleanup(state multistep.StateBag) { + ui := state.Get("ui").(packer.Ui) + csp := state.Get("csp").(gophercloud.CloudServersProvider) + instanceIp := state.Get("access_ip").(gophercloud.FloatingIp) + if s.FloatingIpPool != "" && instanceIp.Id != 0 { + if err := csp.DeleteFloatingIp(instanceIp); err != nil { + ui.Error(fmt.Sprintf("Error deleting temporary floating IP %s", instanceIp.Ip)) + return + } + ui.Say(fmt.Sprintf("Deleted temporary floating IP %s", instanceIp.Ip)) + } +} From 6017f895f1dd5239efe6073d36b5a86053684e1c Mon Sep 17 00:00:00 2001 From: James Cline Date: Mon, 28 Apr 2014 15:29:15 -0700 Subject: [PATCH 002/593] provisioners/chef-client: delete correct client The server URL was being passed through rather than the client name. --- provisioner/chef-client/provisioner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioner/chef-client/provisioner.go b/provisioner/chef-client/provisioner.go index 5c30bbe37..8d637fbaa 100644 --- a/provisioner/chef-client/provisioner.go +++ b/provisioner/chef-client/provisioner.go @@ -208,7 +208,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { } if !p.config.SkipCleanClient { - if err2 := p.cleanClient(ui, comm, serverUrl); err2 != nil { + if err2 := p.cleanClient(ui, comm, nodeName); err2 != nil { return fmt.Errorf("Error cleaning up chef client: %s", err2) } } From c8b91f3dad551410013eb43e73cfeae4369d2811 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 28 Apr 2014 15:30:43 -0700 Subject: [PATCH 003/593] website: clarify docs --- .../source/docs/provisioners/puppet-masterless.html.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/source/docs/provisioners/puppet-masterless.html.markdown b/website/source/docs/provisioners/puppet-masterless.html.markdown index 23c07f433..8571b9355 100644 --- a/website/source/docs/provisioners/puppet-masterless.html.markdown +++ b/website/source/docs/provisioners/puppet-masterless.html.markdown @@ -60,7 +60,9 @@ Optional parameters: * `manifest_dir` (string) - The path to a local directory with manifests to be uploaded to the remote machine. This is useful if your main - manifest file uses imports. + manifest file uses imports. This directory doesn't necessarily contain + the `manifest_file`. It is a separate directory that will be set as + the "manifestdir" setting on Puppet. * `module_paths` (array of strings) - This is an array of paths to module directories on your local filesystem. These will be uploaded to the remote From ba05119a751283d9c5fead1258886f540314aced Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 28 Apr 2014 16:18:11 -0700 Subject: [PATCH 004/593] common: user variable conversion to non-string types works [GH-1079] --- common/config.go | 9 +++++++++ common/config_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/common/config.go b/common/config.go index 4c6fbbbef..f55cbbb2f 100644 --- a/common/config.go +++ b/common/config.go @@ -217,6 +217,15 @@ func decodeConfigHook(raws []interface{}) (mapstructure.DecodeHookFunc, error) { return func(f reflect.Kind, t reflect.Kind, v interface{}) (interface{}, error) { if t != reflect.String { + if f == reflect.Slice { + dataVal := reflect.ValueOf(v) + dataType := dataVal.Type() + elemKind := dataType.Elem().Kind() + if elemKind == reflect.Uint8 { + v = string(dataVal.Interface().([]uint8)) + } + } + if sv, ok := v.(string); ok { var err error v, err = tpl.Process(sv, nil) diff --git a/common/config_test.go b/common/config_test.go index 3217a2ebd..1108ccb18 100644 --- a/common/config_test.go +++ b/common/config_test.go @@ -148,6 +148,32 @@ func TestDecodeConfig_userVarConversion(t *testing.T) { } } +// This tests the way MessagePack decodes strings (into []uint8) and +// that we can still decode into the proper types. +func TestDecodeConfig_userVarConversionUInt8(t *testing.T) { + type Local struct { + Val int + } + + raw := map[string]interface{}{ + "packer_user_variables": map[string]string{ + "foo": "42", + }, + + "val": []uint8("{{user `foo`}}"), + } + + var result Local + _, err := DecodeConfig(&result, raw) + if err != nil { + t.Fatalf("err: %s", err) + } + + if result.Val != 42 { + t.Fatalf("invalid: %#v", result.Val) + } +} + func TestDownloadableURL(t *testing.T) { // Invalid URL: has hex code in host _, err := DownloadableURL("http://what%20.com") From 5cd2cfa5638fd90a01de1fe94f0a2b9eb16f8ba2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 28 Apr 2014 16:19:35 -0700 Subject: [PATCH 005/593] comment so future-mitchell knows what I've done --- common/config.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/config.go b/common/config.go index f55cbbb2f..0fe5ffc99 100644 --- a/common/config.go +++ b/common/config.go @@ -217,6 +217,9 @@ func decodeConfigHook(raws []interface{}) (mapstructure.DecodeHookFunc, error) { return func(f reflect.Kind, t reflect.Kind, v interface{}) (interface{}, error) { if t != reflect.String { + // We need to convert []uint8 to string. We have to do this + // because internally Packer uses MsgPack for RPC and the MsgPack + // codec turns strings into []uint8 if f == reflect.Slice { dataVal := reflect.ValueOf(v) dataType := dataVal.Type() From 8cdd784e46abfca9937909a372807486a7f51d58 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Mon, 28 Apr 2014 19:06:08 -0700 Subject: [PATCH 006/593] scripts: fix executable name when packer dir is a symlink --- scripts/compile.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/compile.sh b/scripts/compile.sh index d2af8cdd1..24b6d8736 100755 --- a/scripts/compile.sh +++ b/scripts/compile.sh @@ -43,8 +43,8 @@ gox \ # Make sure "packer-packer" is renamed properly for PLATFORM in $(find ./pkg -mindepth 1 -maxdepth 1 -type d); do set +e - mv ${PLATFORM}/packer-packer ${PLATFORM}/packer 2>/dev/null - mv ${PLATFORM}/packer-packer.exe ${PLATFORM}/packer.exe 2>/dev/null + mv ${PLATFORM}/packer-packer*.exe ${PLATFORM}/packer.exe 2>/dev/null + mv ${PLATFORM}/packer-packer* ${PLATFORM}/packer 2>/dev/null set -e done From 3ac246d611957f997f32a12351d4745e5d92af92 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 28 Apr 2014 21:36:49 -0700 Subject: [PATCH 007/593] packer/rpc: don't reply with arbitrary error --- packer/rpc/provisioner.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packer/rpc/provisioner.go b/packer/rpc/provisioner.go index 228203a2d..e2346cd21 100644 --- a/packer/rpc/provisioner.go +++ b/packer/rpc/provisioner.go @@ -26,7 +26,7 @@ type ProvisionerPrepareArgs struct { func (p *provisioner) Prepare(configs ...interface{}) (err error) { args := &ProvisionerPrepareArgs{configs} - if cerr := p.client.Call("Provisioner.Prepare", args, &err); cerr != nil { + if cerr := p.client.Call("Provisioner.Prepare", args, new(interface{})); cerr != nil { err = cerr } @@ -50,13 +50,8 @@ func (p *provisioner) Cancel() { } } -func (p *ProvisionerServer) Prepare(args *ProvisionerPrepareArgs, reply *error) error { - *reply = p.p.Prepare(args.Configs...) - if *reply != nil { - *reply = NewBasicError(*reply) - } - - return nil +func (p *ProvisionerServer) Prepare(args *ProvisionerPrepareArgs, reply *interface{}) error { + return p.p.Prepare(args.Configs...) } func (p *ProvisionerServer) Provision(streamId uint32, reply *interface{}) error { From 8fc46aaa826894a0d5c7438a2e502f898e430790 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 28 Apr 2014 21:56:32 -0700 Subject: [PATCH 008/593] provisioner/chef-solo: deeploy nested JSON works properly --- CHANGELOG.md | 1 + provisioner/chef-solo/provisioner.go | 75 +++++++++++++++++++++-- provisioner/chef-solo/provisioner_test.go | 30 +++++++++ 3 files changed, 100 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2927455c8..518820087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ BUG FIXES: Windows [GH-963] * provisioner/ansible: set cwd to staging directory [GH-1016] * provisioners/chef-client: Don't chown directory with Ubuntu. [GH-939] + * provisioners/chef-solo: Deeply nested JSON works properly. [GH-1076] * provisioners/shell: Env var values can have equal signs. [GH-1045] * provisioners/shell: chmod the uploaded script file to 0777. [GH-994] * post-processor/docker-push: Allow repositories with ports. [GH-923] diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 0d95a0ec5..0295546cd 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -203,12 +203,24 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } } - // Process the user variables within the JSON and set the JSON. - // Do this early so that we can validate and show errors. - p.config.Json, err = p.processJsonUserVars() - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Error processing user variables in JSON: %s", err)) + jsonValid := true + for k, v := range p.config.Json { + p.config.Json[k], err = p.deepJsonFix(k, v) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing JSON: %s", err)) + jsonValid = false + } + } + + if jsonValid { + // Process the user variables within the JSON and set the JSON. + // Do this early so that we can validate and show errors. + p.config.Json, err = p.processJsonUserVars() + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing user variables in JSON: %s", err)) + } } if errs != nil && len(errs.Errors) > 0 { @@ -470,6 +482,57 @@ func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator) error return nil } +func (p *Provisioner) deepJsonFix(key string, current interface{}) (interface{}, error) { + if current == nil { + return nil, nil + } + + switch c := current.(type) { + case []interface{}: + val := make([]interface{}, len(c)) + for i, v := range c { + var err error + val[i], err = p.deepJsonFix(fmt.Sprintf("%s[%d]", key, i), v) + if err != nil { + return nil, err + } + } + + return val, nil + case bool: + return c, nil + case int: + return c, nil + case uint: + return c, nil + case float32: + return c, nil + case float64: + return c, nil + case map[interface{}]interface{}: + val := make(map[string]interface{}) + for k, v := range c { + ks, ok := k.(string) + if !ok { + return nil, fmt.Errorf("%s: key is not string", key) + } + + var err error + val[ks], err = p.deepJsonFix( + fmt.Sprintf("%s.%s", key, ks), v) + if err != nil { + return nil, err + } + } + + return val, nil + case string: + return c, nil + default: + return nil, fmt.Errorf("Unknown type for key: '%s'", key) + } +} + func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) { jsonBytes, err := json.Marshal(p.config.Json) if err != nil { diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go index f7094103e..35fdb915a 100644 --- a/provisioner/chef-solo/provisioner_test.go +++ b/provisioner/chef-solo/provisioner_test.go @@ -262,3 +262,33 @@ func TestProvisionerPrepare_json(t *testing.T) { t.Fatalf("bad: %#v", p.config.Json) } } + +func TestProvisionerPrepare_jsonNested(t *testing.T) { + config := testConfig() + config["json"] = map[string]interface{}{ + "foo": map[interface{}]interface{}{ + "bar": "baz", + }, + + "bar": []interface{}{ + "foo", + + map[interface{}]interface{}{ + "bar": "baz", + }, + }, + + "bFalse": false, + "bTrue": true, + "bNil": nil, + + "bInt": 1, + "bFloat": 4.5, + } + + var p Provisioner + err := p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } +} From 4379997c2c55a1ac5b1227fcb4b808f53650b9d5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 28 Apr 2014 22:18:21 -0700 Subject: [PATCH 009/593] provisioner/chef-solo: better error for bad type in JSON --- provisioner/chef-solo/provisioner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 0295546cd..9d37b8f90 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -529,7 +529,7 @@ func (p *Provisioner) deepJsonFix(key string, current interface{}) (interface{}, case string: return c, nil default: - return nil, fmt.Errorf("Unknown type for key: '%s'", key) + return nil, fmt.Errorf("Unknown type for key '%s': %T", key, current) } } From 379f3ae933535db0e26ce59ebb660d3af75897bd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 28 Apr 2014 22:20:57 -0700 Subject: [PATCH 010/593] provisioner/chef-solo: be more lenient on json fix --- provisioner/chef-solo/provisioner.go | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 9d37b8f90..8ecf8490d 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -499,16 +499,6 @@ func (p *Provisioner) deepJsonFix(key string, current interface{}) (interface{}, } return val, nil - case bool: - return c, nil - case int: - return c, nil - case uint: - return c, nil - case float32: - return c, nil - case float64: - return c, nil case map[interface{}]interface{}: val := make(map[string]interface{}) for k, v := range c { @@ -526,10 +516,8 @@ func (p *Provisioner) deepJsonFix(key string, current interface{}) (interface{}, } return val, nil - case string: - return c, nil default: - return nil, fmt.Errorf("Unknown type for key '%s': %T", key, current) + return current, nil } } From 93c7e01c9f4de07fa6a4fb05242e56b119479426 Mon Sep 17 00:00:00 2001 From: Jim Browne Date: Tue, 29 Apr 2014 00:05:49 -0700 Subject: [PATCH 011/593] Fix EC2 IAM documentation. Possibly fixes #577 --- .../source/docs/builders/amazon.html.markdown | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/website/source/docs/builders/amazon.html.markdown b/website/source/docs/builders/amazon.html.markdown index e4e15f886..41d1ef0f7 100644 --- a/website/source/docs/builders/amazon.html.markdown +++ b/website/source/docs/builders/amazon.html.markdown @@ -46,19 +46,25 @@ The following policy document provides the minimal set permissions necessary for "ec2:AttachVolume", "ec2:CreateVolume", "ec2:DeleteVolume", + "ec2:CreateKeypair", + "ec2:DeleteKeypair", + "ec2:CreateSecurityGroup", + "ec2:DeleteSecurityGroup", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateImage", + "ec2:RunInstances", + "ec2:TerminateInstances", + "ec2:StopInstances", "ec2:DescribeVolumes", "ec2:DetachVolume", - "ec2:DescribeInstances", - "ec2:CreateSnapshot", "ec2:DeleteSnapshot", "ec2:DescribeSnapshots", - "ec2:DescribeImages", "ec2:RegisterImage", - - "ec2:CreateTags" + "ec2:CreateTags", + "ec2:ModifyImageAttribute" ], "Resource" : "*" }] From bb8d0a5e7a024620a238f26d643dba5b25be8096 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 29 Apr 2014 00:33:35 -0700 Subject: [PATCH 012/593] provisioner/chef-solo: convert []uint8 to string --- provisioner/chef-solo/provisioner.go | 2 ++ provisioner/chef-solo/provisioner_test.go | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 8ecf8490d..1e5a47241 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -499,6 +499,8 @@ func (p *Provisioner) deepJsonFix(key string, current interface{}) (interface{}, } return val, nil + case []uint8: + return string(c), nil case map[interface{}]interface{}: val := make(map[string]interface{}) for k, v := range c { diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go index 35fdb915a..73e54de57 100644 --- a/provisioner/chef-solo/provisioner_test.go +++ b/provisioner/chef-solo/provisioner_test.go @@ -267,7 +267,7 @@ func TestProvisionerPrepare_jsonNested(t *testing.T) { config := testConfig() config["json"] = map[string]interface{}{ "foo": map[interface{}]interface{}{ - "bar": "baz", + "bar": []uint8("baz"), }, "bar": []interface{}{ @@ -281,6 +281,7 @@ func TestProvisionerPrepare_jsonNested(t *testing.T) { "bFalse": false, "bTrue": true, "bNil": nil, + "bStr": []uint8("bar"), "bInt": 1, "bFloat": 4.5, @@ -291,4 +292,12 @@ func TestProvisionerPrepare_jsonNested(t *testing.T) { if err != nil { t.Fatalf("err: %s", err) } + + fooMap := p.config.Json["foo"].(map[string]interface{}) + if fooMap["bar"] != "baz" { + t.Fatalf("nope: %#v", fooMap["bar"]) + } + if p.config.Json["bStr"] != "bar" { + t.Fatalf("nope: %#v", fooMap["bar"]) + } } From 38e880a187249b9ef76de434f527f3de3cdbb962 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Wed, 12 Mar 2014 16:14:44 -0700 Subject: [PATCH 013/593] Add ability to run vboxmanage commands just before exporting [GH-664] --- .../common/vboxmanage_post_config.go | 28 ++++++++++++++ .../common/vboxmanage_post_config_test.go | 37 +++++++++++++++++++ builder/virtualbox/iso/builder.go | 26 ++++++++----- builder/virtualbox/ovf/builder.go | 4 ++ builder/virtualbox/ovf/config.go | 22 ++++++----- .../builders/virtualbox-iso.html.markdown | 4 ++ .../builders/virtualbox-ovf.html.markdown | 4 ++ 7 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 builder/virtualbox/common/vboxmanage_post_config.go create mode 100644 builder/virtualbox/common/vboxmanage_post_config_test.go diff --git a/builder/virtualbox/common/vboxmanage_post_config.go b/builder/virtualbox/common/vboxmanage_post_config.go new file mode 100644 index 000000000..df683f100 --- /dev/null +++ b/builder/virtualbox/common/vboxmanage_post_config.go @@ -0,0 +1,28 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/packer/packer" +) + +type VBoxManagePostConfig struct { + VBoxManagePost [][]string `mapstructure:"vboxmanage_post"` +} + +func (c *VBoxManagePostConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.VBoxManagePost == nil { + c.VBoxManagePost = make([][]string, 0) + } + + errs := make([]error, 0) + for i, args := range c.VBoxManagePost { + for j, arg := range args { + if err := t.Validate(arg); err != nil { + errs = append(errs, + fmt.Errorf("Error processing vboxmanage_post[%d][%d]: %s", i, j, err)) + } + } + } + + return errs +} diff --git a/builder/virtualbox/common/vboxmanage_post_config_test.go b/builder/virtualbox/common/vboxmanage_post_config_test.go new file mode 100644 index 000000000..60a2c7abb --- /dev/null +++ b/builder/virtualbox/common/vboxmanage_post_config_test.go @@ -0,0 +1,37 @@ +package common + +import ( + "reflect" + "testing" +) + +func TestVBoxManagePostConfigPrepare_VBoxManage(t *testing.T) { + // Test with empty + c := new(VBoxManagePostConfig) + errs := c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if !reflect.DeepEqual(c.VBoxManagePost, [][]string{}) { + t.Fatalf("bad: %#v", c.VBoxManagePost) + } + + // Test with a good one + c = new(VBoxManagePostConfig) + c.VBoxManagePost = [][]string{ + {"foo", "bar", "baz"}, + } + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + expected := [][]string{ + []string{"foo", "bar", "baz"}, + } + + if !reflect.DeepEqual(c.VBoxManagePost, expected) { + t.Fatalf("bad: %#v", c.VBoxManagePost) + } +} diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index 31660a961..7861866b2 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -27,16 +27,17 @@ type Builder struct { } type config struct { - common.PackerConfig `mapstructure:",squash"` - vboxcommon.ExportConfig `mapstructure:",squash"` - vboxcommon.ExportOpts `mapstructure:",squash"` - vboxcommon.FloppyConfig `mapstructure:",squash"` - vboxcommon.OutputConfig `mapstructure:",squash"` - vboxcommon.RunConfig `mapstructure:",squash"` - vboxcommon.ShutdownConfig `mapstructure:",squash"` - vboxcommon.SSHConfig `mapstructure:",squash"` - vboxcommon.VBoxManageConfig `mapstructure:",squash"` - vboxcommon.VBoxVersionConfig `mapstructure:",squash"` + common.PackerConfig `mapstructure:",squash"` + vboxcommon.ExportConfig `mapstructure:",squash"` + vboxcommon.ExportOpts `mapstructure:",squash"` + vboxcommon.FloppyConfig `mapstructure:",squash"` + vboxcommon.OutputConfig `mapstructure:",squash"` + vboxcommon.RunConfig `mapstructure:",squash"` + vboxcommon.ShutdownConfig `mapstructure:",squash"` + vboxcommon.SSHConfig `mapstructure:",squash"` + vboxcommon.VBoxManageConfig `mapstructure:",squash"` + vboxcommon.VBoxManagePostConfig `mapstructure:",squash"` + vboxcommon.VBoxVersionConfig `mapstructure:",squash"` BootCommand []string `mapstructure:"boot_command"` DiskSize uint `mapstructure:"disk_size"` @@ -82,6 +83,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...) errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) errs = packer.MultiErrorAppend(errs, b.config.VBoxManageConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.VBoxManagePostConfig.Prepare(b.config.tpl)...) errs = packer.MultiErrorAppend(errs, b.config.VBoxVersionConfig.Prepare(b.config.tpl)...) warnings := make([]string, 0) @@ -318,6 +320,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Timeout: b.config.ShutdownTimeout, }, new(vboxcommon.StepRemoveDevices), + &vboxcommon.StepVBoxManage{ + Commands: b.config.VBoxManagePost, + Tpl: b.config.tpl, + }, &vboxcommon.StepExport{ Format: b.config.Format, OutputDir: b.config.OutputDir, diff --git a/builder/virtualbox/ovf/builder.go b/builder/virtualbox/ovf/builder.go index f2e6a92f5..ffc1a0d1b 100644 --- a/builder/virtualbox/ovf/builder.go +++ b/builder/virtualbox/ovf/builder.go @@ -94,6 +94,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Timeout: b.config.ShutdownTimeout, }, new(vboxcommon.StepRemoveDevices), + &vboxcommon.StepVBoxManage{ + Commands: b.config.VBoxManagePost, + Tpl: b.config.tpl, + }, &vboxcommon.StepExport{ Format: b.config.Format, OutputDir: b.config.OutputDir, diff --git a/builder/virtualbox/ovf/config.go b/builder/virtualbox/ovf/config.go index 054d09e3a..4b0c9111a 100644 --- a/builder/virtualbox/ovf/config.go +++ b/builder/virtualbox/ovf/config.go @@ -11,16 +11,17 @@ import ( // Config is the configuration structure for the builder. type Config struct { - common.PackerConfig `mapstructure:",squash"` - vboxcommon.ExportConfig `mapstructure:",squash"` - vboxcommon.ExportOpts `mapstructure:",squash"` - vboxcommon.FloppyConfig `mapstructure:",squash"` - vboxcommon.OutputConfig `mapstructure:",squash"` - vboxcommon.RunConfig `mapstructure:",squash"` - vboxcommon.SSHConfig `mapstructure:",squash"` - vboxcommon.ShutdownConfig `mapstructure:",squash"` - vboxcommon.VBoxManageConfig `mapstructure:",squash"` - vboxcommon.VBoxVersionConfig `mapstructure:",squash"` + common.PackerConfig `mapstructure:",squash"` + vboxcommon.ExportConfig `mapstructure:",squash"` + vboxcommon.ExportOpts `mapstructure:",squash"` + vboxcommon.FloppyConfig `mapstructure:",squash"` + vboxcommon.OutputConfig `mapstructure:",squash"` + vboxcommon.RunConfig `mapstructure:",squash"` + vboxcommon.SSHConfig `mapstructure:",squash"` + vboxcommon.ShutdownConfig `mapstructure:",squash"` + vboxcommon.VBoxManageConfig `mapstructure:",squash"` + vboxcommon.VBoxManagePostConfig `mapstructure:",squash"` + vboxcommon.VBoxVersionConfig `mapstructure:",squash"` SourcePath string `mapstructure:"source_path"` VMName string `mapstructure:"vm_name"` @@ -57,6 +58,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.VBoxManageConfig.Prepare(c.tpl)...) + errs = packer.MultiErrorAppend(errs, c.VBoxManagePostConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.VBoxVersionConfig.Prepare(c.tpl)...) templates := map[string]*string{ diff --git a/website/source/docs/builders/virtualbox-iso.html.markdown b/website/source/docs/builders/virtualbox-iso.html.markdown index e6e3b2454..c59b4ae5e 100644 --- a/website/source/docs/builders/virtualbox-iso.html.markdown +++ b/website/source/docs/builders/virtualbox-iso.html.markdown @@ -201,6 +201,10 @@ Optional: where the `Name` variable is replaced with the VM name. More details on how to use `VBoxManage` are below. +* `vboxmanage_post` (array of array of strings) - Identical to `vboxmanage`, + except that it is run after the virtual machine is shutdown, and before the + virtual machine is exported. + * `virtualbox_version_file` (string) - The path within the virtual machine to upload a file that contains the VirtualBox version that was used to create the machine. This information can be useful for provisioning. diff --git a/website/source/docs/builders/virtualbox-ovf.html.markdown b/website/source/docs/builders/virtualbox-ovf.html.markdown index 6bf6e49d9..602ced197 100644 --- a/website/source/docs/builders/virtualbox-ovf.html.markdown +++ b/website/source/docs/builders/virtualbox-ovf.html.markdown @@ -136,6 +136,10 @@ Optional: where the `Name` variable is replaced with the VM name. More details on how to use `VBoxManage` are below. +* `vboxmanage_post` (array of array of strings) - Identical to `vboxmanage`, + except that it is run after the virtual machine is shutdown, and before the + virtual machine is exported. + * `virtualbox_version_file` (string) - The path within the virtual machine to upload a file that contains the VirtualBox version that was used to create the machine. This information can be useful for provisioning. From e422d45f92c7357cffd540ce80028cebf8743fbf Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Tue, 29 Apr 2014 12:27:34 -0700 Subject: [PATCH 014/593] Allow wildcards and directories for floppy_files parameter --- common/step_create_floppy.go | 68 +++++- common/step_create_floppy_test.go | 193 ++++++++++++++++++ .../source/docs/builders/qemu.html.markdown | 10 +- .../builders/virtualbox-iso.html.markdown | 15 +- .../builders/virtualbox-ovf.html.markdown | 15 +- .../docs/builders/vmware-iso.html.markdown | 17 +- .../docs/builders/vmware-vmx.html.markdown | 15 +- 7 files changed, 301 insertions(+), 32 deletions(-) create mode 100644 common/step_create_floppy_test.go diff --git a/common/step_create_floppy.go b/common/step_create_floppy.go index 0f4e2939c..7b9b8c537 100644 --- a/common/step_create_floppy.go +++ b/common/step_create_floppy.go @@ -11,6 +11,7 @@ import ( "log" "os" "path/filepath" + "strings" ) // StepCreateFloppy will create a floppy disk with the given files. @@ -20,6 +21,8 @@ type StepCreateFloppy struct { Files []string floppyPath string + + FilesAdded map[string]bool } func (s *StepCreateFloppy) Run(state multistep.StateBag) multistep.StepAction { @@ -28,6 +31,8 @@ func (s *StepCreateFloppy) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } + s.FilesAdded = make(map[string]bool) + ui := state.Get("ui").(packer.Ui) ui.Say("Creating floppy disk...") @@ -43,7 +48,7 @@ func (s *StepCreateFloppy) Run(state multistep.StateBag) multistep.StepAction { // Set the path so we can remove it later s.floppyPath = floppyF.Name() - log.Printf("Floppy path: %s", floppyF.Name()) + log.Printf("Floppy path: %s", s.floppyPath) // Set the size of the file to be a floppy sized if err := floppyF.Truncate(1440 * 1024); err != nil { @@ -89,8 +94,8 @@ func (s *StepCreateFloppy) Run(state multistep.StateBag) multistep.StepAction { // Go over each file and copy it. for _, filename := range s.Files { - ui.Message(fmt.Sprintf("Copying: %s", filepath.Base(filename))) - if err := s.addSingleFile(rootDir, filename); err != nil { + ui.Message(fmt.Sprintf("Copying: %s", filename)) + if err := s.addFilespec(rootDir, filename); err != nil { state.Put("error", fmt.Errorf("Error adding file to floppy: %s", err)) return multistep.ActionHalt } @@ -109,6 +114,61 @@ func (s *StepCreateFloppy) Cleanup(multistep.StateBag) { } } +func (s *StepCreateFloppy) addFilespec(dir fs.Directory, src string) error { + // same as http://golang.org/src/pkg/path/filepath/match.go#L308 + if strings.IndexAny(src, "*?[") >= 0 { + matches, err := filepath.Glob(src) + if err != nil { + return err + } + return s.addFiles(dir, matches) + } + + finfo, err := os.Stat(src) + if err != nil { + return err + } + + if finfo.IsDir() { + return s.addDirectory(dir, src) + } + + return s.addSingleFile(dir, src) +} + +func (s *StepCreateFloppy) addFiles(dir fs.Directory, files []string) error { + for _, file := range files { + err := s.addFilespec(dir, file) + if err != nil { + return err + } + } + + return nil +} + +func (s *StepCreateFloppy) addDirectory(dir fs.Directory, src string) error { + log.Printf("Adding directory to floppy: %s", src) + + walkFn := func(path string, finfo os.FileInfo, err error) error { + if err != nil { + return err + } + + if path == src { + return nil + } + + if finfo.IsDir() { + return s.addDirectory(dir, path) + } + + return s.addSingleFile(dir, path) + } + + return filepath.Walk(src, walkFn) +} + func (s *StepCreateFloppy) addSingleFile(dir fs.Directory, src string) error { log.Printf("Adding file to floppy: %s", src) @@ -132,5 +192,7 @@ func (s *StepCreateFloppy) addSingleFile(dir fs.Directory, src string) error { return err } + s.FilesAdded[src] = true + return nil } diff --git a/common/step_create_floppy_test.go b/common/step_create_floppy_test.go new file mode 100644 index 000000000..f68c0df69 --- /dev/null +++ b/common/step_create_floppy_test.go @@ -0,0 +1,193 @@ +package common + +import ( + "bytes" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "io/ioutil" + "os" + "path" + "strconv" + "testing" +) + +func TestStepCreateFloppy_Impl(t *testing.T) { + var raw interface{} + raw = new(StepCreateFloppy) + if _, ok := raw.(multistep.Step); !ok { + t.Fatalf("StepCreateFloppy should be a step") + } +} + +func testStepCreateFloppyState(t *testing.T) multistep.StateBag { + state := new(multistep.BasicStateBag) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + return state +} + +func TestStepCreateFloppy(t *testing.T) { + state := testStepCreateFloppyState(t) + step := new(StepCreateFloppy) + + dir, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(dir) + + count := 10 + expected := count + files := make([]string, count) + + prefix := "exists" + ext := ".tmp" + + for i := 0; i < expected; i++ { + files[i] = path.Join(dir, prefix + strconv.Itoa(i) + ext) + + _, err := os.Create(files[i]) + if err != nil { + t.Fatalf("err: %s", err) + } + } + + lists := [][]string{ + files, + []string{dir + string(os.PathSeparator) + prefix + "*" + ext}, + []string{dir + string(os.PathSeparator) + prefix + "?" + ext}, + []string{dir + string(os.PathSeparator) + prefix + "[0123456789]" + ext}, + []string{dir + string(os.PathSeparator) + prefix + "[0-9]" + ext}, + []string{dir + string(os.PathSeparator)}, + []string{dir}, + } + + for _, step.Files = range lists { + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v for %v", action, step.Files) + } + + if _, ok := state.GetOk("error"); ok { + t.Fatalf("state should be ok for %v", step.Files) + } + + floppy_path := state.Get("floppy_path").(string) + + if _, err := os.Stat(floppy_path); err != nil { + t.Fatal("file not found: %s for %v", floppy_path, step.Files) + } + + if len(step.FilesAdded) != expected { + t.Fatalf("expected %d, found %d for %v", expected, len(step.FilesAdded), step.Files) + } + + step.Cleanup(state) + + if _, err := os.Stat(floppy_path); err == nil { + t.Fatal("file found: %s for %v", floppy_path, step.Files) + } + } +} + +func xxxTestStepCreateFloppy_missing(t *testing.T) { + state := testStepCreateFloppyState(t) + step := new(StepCreateFloppy) + + dir, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(dir) + + count := 2 + expected := 0 + files := make([]string, count) + + prefix := "missing" + + for i := 0; i < count; i++ { + files[i] = path.Join(dir, prefix + strconv.Itoa(i)) + } + + lists := [][]string{ + files, + } + + for _, step.Files = range lists { + if action := step.Run(state); action != multistep.ActionHalt { + t.Fatalf("bad action: %#v for %v", action, step.Files) + } + + if _, ok := state.GetOk("error"); !ok { + t.Fatalf("state should not be ok for %v", step.Files) + } + + floppy_path := state.Get("floppy_path") + + if floppy_path != nil { + t.Fatalf("floppy_path is not nil for %v", step.Files) + } + + if len(step.FilesAdded) != expected { + t.Fatalf("expected %d, found %d for %v", expected, len(step.FilesAdded), step.Files) + } + } +} + +func xxxTestStepCreateFloppy_notfound(t *testing.T) { + state := testStepCreateFloppyState(t) + step := new(StepCreateFloppy) + + dir, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(dir) + + count := 2 + expected := 0 + files := make([]string, count) + + prefix := "notfound" + + for i := 0; i < count; i++ { + files[i] = path.Join(dir, prefix + strconv.Itoa(i)) + } + + lists := [][]string{ + []string{dir + string(os.PathSeparator) + prefix + "*"}, + []string{dir + string(os.PathSeparator) + prefix + "?"}, + []string{dir + string(os.PathSeparator) + prefix + "[0123456789]"}, + []string{dir + string(os.PathSeparator) + prefix + "[0-9]"}, + []string{dir + string(os.PathSeparator)}, + []string{dir}, + } + + for _, step.Files = range lists { + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v for %v", action, step.Files) + } + + if _, ok := state.GetOk("error"); ok { + t.Fatalf("state should be ok for %v", step.Files) + } + + floppy_path := state.Get("floppy_path").(string) + + if _, err := os.Stat(floppy_path); err != nil { + t.Fatal("file not found: %s for %v", floppy_path, step.Files) + } + + if len(step.FilesAdded) != expected { + t.Fatalf("expected %d, found %d for %v", expected, len(step.FilesAdded), step.Files) + } + + step.Cleanup(state) + + if _, err := os.Stat(floppy_path); err == nil { + t.Fatal("file found: %s for %v", floppy_path, step.Files) + } + } +} diff --git a/website/source/docs/builders/qemu.html.markdown b/website/source/docs/builders/qemu.html.markdown index d72437219..9cb78d2e6 100644 --- a/website/source/docs/builders/qemu.html.markdown +++ b/website/source/docs/builders/qemu.html.markdown @@ -119,12 +119,14 @@ Optional: format of the virtual machine image. This defaults to "qcow2". * `floppy_files` (array of strings) - A list of files to place onto a floppy - disk that gets attached when Packer powers up the VM. This is most useful + disk that is attached when the VM is booted. This is most useful for unattended Windows installs, which look for an `Autounattend.xml` file - on removable media. By default no floppy will be attached. All files + on removable media. By default, no floppy will be attached. All files listed in this setting get placed into the root directory of the floppy - and teh floppy is attached as the first floppy device. Currently, no - support exists for sub-directories. + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. * `headless` (bool) - Packer defaults to building virtual machines by launching a GUI that shows the console of the machine being built. diff --git a/website/source/docs/builders/virtualbox-iso.html.markdown b/website/source/docs/builders/virtualbox-iso.html.markdown index e6e3b2454..b0849928d 100644 --- a/website/source/docs/builders/virtualbox-iso.html.markdown +++ b/website/source/docs/builders/virtualbox-iso.html.markdown @@ -85,12 +85,15 @@ Optional: * `disk_size` (int) - The size, in megabytes, of the hard disk to create for the VM. By default, this is 40000 (about 40 GB). -* `floppy_files` (array of strings) - A list of files to put onto a floppy - disk that is attached when the VM is booted for the first time. This is - most useful for unattended Windows installs, which look for an - `Autounattend.xml` file on removable media. By default no floppy will - be attached. The files listed in this configuration will all be put - into the root directory of the floppy disk; sub-directories are not supported. +* `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. * `format` (string) - Either "ovf" or "ova", this specifies the output format of the exported virtual machine. This defaults to "ovf". diff --git a/website/source/docs/builders/virtualbox-ovf.html.markdown b/website/source/docs/builders/virtualbox-ovf.html.markdown index 6bf6e49d9..7da486864 100644 --- a/website/source/docs/builders/virtualbox-ovf.html.markdown +++ b/website/source/docs/builders/virtualbox-ovf.html.markdown @@ -52,12 +52,15 @@ Required: Optional: -* `floppy_files` (array of strings) - A list of files to put onto a floppy - disk that is attached when the VM is booted for the first time. This is - most useful for unattended Windows installs, which look for an - `Autounattend.xml` file on removable media. By default no floppy will - be attached. The files listed in this configuration will all be put - into the root directory of the floppy disk; sub-directories are not supported. +* `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. * `format` (string) - Either "ovf" or "ova", this specifies the output format of the exported virtual machine. This defaults to "ovf". diff --git a/website/source/docs/builders/vmware-iso.html.markdown b/website/source/docs/builders/vmware-iso.html.markdown index dd545f56a..1276d2ead 100644 --- a/website/source/docs/builders/vmware-iso.html.markdown +++ b/website/source/docs/builders/vmware-iso.html.markdown @@ -95,12 +95,15 @@ Optional: [Virtual Disk Manager User's Guide](http://www.vmware.com/pdf/VirtualDiskManager.pdf) for desktop VMware clients. For ESXi, refer to the proper ESXi documentation. -* `floppy_files` (array of strings) - A list of files to put onto a floppy - disk that is attached when the VM is booted for the first time. This is - most useful for unattended Windows installs, which look for an - `Autounattend.xml` file on removable media. By default no floppy will - be attached. The files listed in this configuration will all be put - into the root directory of the floppy disk; sub-directories are not supported. +* `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. * `fusion_app_path` (string) - Path to "VMware Fusion.app". By default this is "/Applications/VMware Fusion.app" but this setting allows you to @@ -186,7 +189,7 @@ Optional: VM being prepared by some other process (kickstart, etc.). * `ssh_host` (string) - Hostname or IP address of the host. By default, DHCP - is used to connect to the host and this field is not used. + is used to connect to the host and this field is not used. * `ssh_password` (string) - The password for `ssh_username` to use to authenticate with SSH. By default this is the empty string. diff --git a/website/source/docs/builders/vmware-vmx.html.markdown b/website/source/docs/builders/vmware-vmx.html.markdown index 1fbbb3103..eacaa82c0 100644 --- a/website/source/docs/builders/vmware-vmx.html.markdown +++ b/website/source/docs/builders/vmware-vmx.html.markdown @@ -57,12 +57,15 @@ Optional: five seconds and one minute 30 seconds, respectively. If this isn't specified, the default is 10 seconds. -* `floppy_files` (array of strings) - A list of files to put onto a floppy - disk that is attached when the VM is booted for the first time. This is - most useful for unattended Windows installs, which look for an - `Autounattend.xml` file on removable media. By default no floppy will - be attached. The files listed in this configuration will all be put - into the root directory of the floppy disk; sub-directories are not supported. +* `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. * `fusion_app_path` (string) - Path to "VMware Fusion.app". By default this is "/Applications/VMware Fusion.app" but this setting allows you to From 3d960ccc6955affc851f2df0be875d13380615b2 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Tue, 29 Apr 2014 12:29:15 -0700 Subject: [PATCH 015/593] go fmt --- common/step_create_floppy_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/step_create_floppy_test.go b/common/step_create_floppy_test.go index f68c0df69..b6e591a41 100644 --- a/common/step_create_floppy_test.go +++ b/common/step_create_floppy_test.go @@ -46,7 +46,7 @@ func TestStepCreateFloppy(t *testing.T) { ext := ".tmp" for i := 0; i < expected; i++ { - files[i] = path.Join(dir, prefix + strconv.Itoa(i) + ext) + files[i] = path.Join(dir, prefix+strconv.Itoa(i)+ext) _, err := os.Create(files[i]) if err != nil { @@ -108,7 +108,7 @@ func xxxTestStepCreateFloppy_missing(t *testing.T) { prefix := "missing" for i := 0; i < count; i++ { - files[i] = path.Join(dir, prefix + strconv.Itoa(i)) + files[i] = path.Join(dir, prefix+strconv.Itoa(i)) } lists := [][]string{ @@ -153,7 +153,7 @@ func xxxTestStepCreateFloppy_notfound(t *testing.T) { prefix := "notfound" for i := 0; i < count; i++ { - files[i] = path.Join(dir, prefix + strconv.Itoa(i)) + files[i] = path.Join(dir, prefix+strconv.Itoa(i)) } lists := [][]string{ From c680b5ef83a4bb7ed4d2234437837371a46aed36 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Tue, 29 Apr 2014 12:54:19 -0700 Subject: [PATCH 016/593] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 518820087..c08629190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ IMPROVEMENTS: * builder/qemu: User variable expansion in `ssh_key_path` [GH-918] * builder/virtualbox: Support an `export_opts` option which allows specifying arbitrary arguments when exporting the VM. [GH-945] + * builder/virtualbox: Run vboxmanage commands just before exporting [GH-664] * builder/vmware: Workstation 10 support for Linux. [GH-900] * builder/vmware: add cloning support on Windows [GH-824] * command/build: Added '-parallel' flag so you can disable parallelization From 96daafbfa6e2c6ccb2c080b3e306d7bbd7772863 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Tue, 29 Apr 2014 12:56:34 -0700 Subject: [PATCH 017/593] Update CHANGELOG.md --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c08629190..af07c8a00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,10 +27,11 @@ IMPROVEMENTS: * builder/qemu: User variable expansion in `ssh_key_path` [GH-918] * builder/virtualbox: Support an `export_opts` option which allows specifying arbitrary arguments when exporting the VM. [GH-945] - * builder/virtualbox: Run vboxmanage commands just before exporting [GH-664] + * builder/virtualbox: Added `vboxmanage_post` option to run vboxmanage + commands just before exporting [GH-664] * builder/vmware: Workstation 10 support for Linux. [GH-900] * builder/vmware: add cloning support on Windows [GH-824] - * command/build: Added '-parallel' flag so you can disable parallelization + * command/build: Added `-parallel` flag so you can disable parallelization with `-no-parallel`. [GH-924] * post-processors/vsphere: `disk_mode` option. [GH-778] * provisioner/ansible: Add `inventory_file` option [GH-1006] From 0e4fa69662d8e3ddc6400eff9dc811ab618fbc7d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 29 Apr 2014 16:56:42 -0700 Subject: [PATCH 018/593] Update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af07c8a00..275e9a6db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,12 +25,18 @@ IMPROVEMENTS: * builder/openstack: Support `openstack_provider` option to automatically fill defaults for different OpenStack variants. [GH-912] * builder/qemu: User variable expansion in `ssh_key_path` [GH-918] + * builder/qemu: Floppy disk files list can also include globs + and directories. [GH-1086] * builder/virtualbox: Support an `export_opts` option which allows specifying arbitrary arguments when exporting the VM. [GH-945] * builder/virtualbox: Added `vboxmanage_post` option to run vboxmanage commands just before exporting [GH-664] + * builder/virtualbox: Floppy disk files list can also include globs + and directories. [GH-1086] * builder/vmware: Workstation 10 support for Linux. [GH-900] * builder/vmware: add cloning support on Windows [GH-824] + * builder/vmware: Floppy disk files list can also include globs + and directories. [GH-1086] * command/build: Added `-parallel` flag so you can disable parallelization with `-no-parallel`. [GH-924] * post-processors/vsphere: `disk_mode` option. [GH-778] From 2bcd9a304e1ad53808cd2cbd422ea8605143e001 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Tue, 29 Apr 2014 20:33:31 -0700 Subject: [PATCH 019/593] builder/digitalOcean: use names/slugs as well as IDs for image/region/size --- builder/digitalocean/api.go | 143 ++++++++++++++++-- builder/digitalocean/artifact.go | 3 - builder/digitalocean/artifact_test.go | 2 +- builder/digitalocean/builder.go | 59 ++++++-- builder/digitalocean/builder_test.go | 45 +++--- builder/digitalocean/step_create_droplet.go | 2 +- builder/digitalocean/step_snapshot.go | 2 +- .../docs/builders/digitalocean.html.markdown | 26 +++- 8 files changed, 229 insertions(+), 53 deletions(-) diff --git a/builder/digitalocean/api.go b/builder/digitalocean/api.go index 7aca1c14f..e9525c0bf 100644 --- a/builder/digitalocean/api.go +++ b/builder/digitalocean/api.go @@ -13,6 +13,7 @@ import ( "log" "net/http" "net/url" + "strconv" "strings" "time" ) @@ -22,6 +23,7 @@ const DIGITALOCEAN_API_URL = "https://api.digitalocean.com" type Image struct { Id uint Name string + Slug string Distribution string } @@ -32,12 +34,23 @@ type ImagesResp struct { type Region struct { Id uint Name string + Slug string } type RegionsResp struct { Regions []Region } +type Size struct { + Id uint + Name string + Slug string +} + +type SizesResp struct { + Sizes []Size +} + type DigitalOceanClient struct { // The http client for communicating client *http.Client @@ -90,12 +103,28 @@ func (d DigitalOceanClient) DestroyKey(id uint) error { } // Creates a droplet and returns it's id -func (d DigitalOceanClient) CreateDroplet(name string, size uint, image uint, region uint, keyId uint, privateNetworking bool) (uint, error) { +func (d DigitalOceanClient) CreateDroplet(name string, size string, image string, region string, keyId uint, privateNetworking bool) (uint, error) { params := url.Values{} params.Set("name", name) - params.Set("size_id", fmt.Sprintf("%v", size)) - params.Set("image_id", fmt.Sprintf("%v", image)) - params.Set("region_id", fmt.Sprintf("%v", region)) + + found_size, err := d.Size(size) + if err != nil { + return 0, fmt.Errorf("Invalid size or lookup failure: '%s': %s", size, err) + } + + found_image, err := d.Image(image) + if err != nil { + return 0, fmt.Errorf("Invalid image or lookup failure: '%s': %s", image, err) + } + + found_region, err := d.Region(region) + if err != nil { + return 0, fmt.Errorf("Invalid region or lookup failure: '%s': %s", region, err) + } + + params.Set("size_slug", found_size.Slug) + params.Set("image_slug", found_image.Slug) + params.Set("region_slug", found_region.Slug) params.Set("ssh_key_ids", fmt.Sprintf("%v", keyId)) params.Set("private_networking", fmt.Sprintf("%v", privateNetworking)) @@ -263,6 +292,38 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin return nil, lastErr } +func (d DigitalOceanClient) Image(slug_or_name_or_id string) (Image, error) { + images, err := d.Images() + if err != nil { + return Image{}, err + } + + for _, image := range images { + if strings.EqualFold(image.Slug, slug_or_name_or_id) { + return image, nil + } + } + + for _, image := range images { + if strings.EqualFold(image.Name, slug_or_name_or_id) { + return image, nil + } + } + + for _, image := range images { + id, err := strconv.Atoi(slug_or_name_or_id) + if err == nil { + if image.Id == uint(id) { + return image, nil + } + } + } + + err = errors.New(fmt.Sprintf("Unknown image '%v'", slug_or_name_or_id)) + + return Image{}, err +} + // Returns all available regions. func (d DigitalOceanClient) Regions() ([]Region, error) { resp, err := NewRequest(d, "regions", url.Values{}) @@ -278,19 +339,81 @@ func (d DigitalOceanClient) Regions() ([]Region, error) { return result.Regions, nil } -func (d DigitalOceanClient) RegionName(region_id uint) (string, error) { +func (d DigitalOceanClient) Region(slug_or_name_or_id string) (Region, error) { regions, err := d.Regions() if err != nil { - return "", err + return Region{}, err } for _, region := range regions { - if region.Id == region_id { - return region.Name, nil + if strings.EqualFold(region.Slug, slug_or_name_or_id) { + return region, nil + } + } + + for _, region := range regions { + if strings.EqualFold(region.Name, slug_or_name_or_id) { + return region, nil + } + } + + for _, region := range regions { + id, err := strconv.Atoi(slug_or_name_or_id) + if err == nil { + if region.Id == uint(id) { + return region, nil + } + } + } + + err = errors.New(fmt.Sprintf("Unknown region '%v'", slug_or_name_or_id)) + + return Region{}, err +} + +// Returns all available sizes. +func (d DigitalOceanClient) Sizes() ([]Size, error) { + resp, err := NewRequest(d, "sizes", url.Values{}) + if err != nil { + return nil, err + } + + var result SizesResp + if err := mapstructure.Decode(resp, &result); err != nil { + return nil, err + } + + return result.Sizes, nil +} + +func (d DigitalOceanClient) Size(slug_or_name_or_id string) (Size, error) { + sizes, err := d.Sizes() + if err != nil { + return Size{}, err + } + + for _, size := range sizes { + if strings.EqualFold(size.Slug, slug_or_name_or_id) { + return size, nil + } + } + + for _, size := range sizes { + if strings.EqualFold(size.Name, slug_or_name_or_id) { + return size, nil + } + } + + for _, size := range sizes { + id, err := strconv.Atoi(slug_or_name_or_id) + if err == nil { + if size.Id == uint(id) { + return size, nil + } } } - err = errors.New(fmt.Sprintf("Unknown region id %v", region_id)) + err = errors.New(fmt.Sprintf("Unknown size '%v'", slug_or_name_or_id)) - return "", err + return Size{}, err } diff --git a/builder/digitalocean/artifact.go b/builder/digitalocean/artifact.go index 06f09fe40..ebabdd41c 100644 --- a/builder/digitalocean/artifact.go +++ b/builder/digitalocean/artifact.go @@ -15,9 +15,6 @@ type Artifact struct { // The name of the region regionName string - // The ID of the region - regionId uint - // The client for making API calls client *DigitalOceanClient } diff --git a/builder/digitalocean/artifact_test.go b/builder/digitalocean/artifact_test.go index 0b4427eae..83681b3fa 100644 --- a/builder/digitalocean/artifact_test.go +++ b/builder/digitalocean/artifact_test.go @@ -14,7 +14,7 @@ func TestArtifact_Impl(t *testing.T) { } func TestArtifactString(t *testing.T) { - a := &Artifact{"packer-foobar", 42, "San Francisco", 3, nil} + a := &Artifact{"packer-foobar", 42, "San Francisco", nil} expected := "A snapshot was created: 'packer-foobar' in region 'San Francisco'" if a.String() != expected { diff --git a/builder/digitalocean/builder.go b/builder/digitalocean/builder.go index ef90aab85..d07bb0e52 100644 --- a/builder/digitalocean/builder.go +++ b/builder/digitalocean/builder.go @@ -15,6 +15,18 @@ import ( "time" ) +// see https://api.digitalocean.com/images/?client_id=[client_id]&api_key=[api_key] +// name="Ubuntu 12.04.4 x64", id=3101045, +const DefaultImage = "ubuntu-12-04-x64" + +// see https://api.digitalocean.com/regions/?client_id=[client_id]&api_key=[api_key] +// name="New York", id=1 +const DefaultRegion = "nyc1" + +// see https://api.digitalocean.com/sizes/?client_id=[client_id]&api_key=[api_key] +// name="512MB", id=66 (the smallest droplet size) +const DefaultSize = "512mb" + // The unique id for the builder const BuilderId = "pearkes.digitalocean" @@ -30,6 +42,10 @@ type config struct { SizeID uint `mapstructure:"size_id"` ImageID uint `mapstructure:"image_id"` + Region string `mapstructure:"region"` + Size string `mapstructure:"size"` + Image string `mapstructure:"image"` + PrivateNetworking bool `mapstructure:"private_networking"` SnapshotName string `mapstructure:"snapshot_name"` DropletName string `mapstructure:"droplet_name"` @@ -78,19 +94,28 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.ClientID = os.Getenv("DIGITALOCEAN_CLIENT_ID") } - if b.config.RegionID == 0 { - // Default to Region "New York" - b.config.RegionID = 1 + if b.config.Region == "" { + if b.config.RegionID != 0 { + b.config.Region = fmt.Sprintf("%v", b.config.RegionID) + } else { + b.config.Region = DefaultRegion + } } - if b.config.SizeID == 0 { - // Default to 512mb, the smallest droplet size - b.config.SizeID = 66 + if b.config.Size == "" { + if b.config.SizeID != 0 { + b.config.Size = fmt.Sprintf("%v", b.config.SizeID) + } else { + b.config.Size = DefaultSize + } } - if b.config.ImageID == 0 { - // Default to base image "Ubuntu 12.04.4 x64 (id: 3101045)" - b.config.ImageID = 3101045 + if b.config.Image == "" { + if b.config.ImageID != 0 { + b.config.Image = fmt.Sprintf("%v", b.config.ImageID) + } else { + b.config.Image = DefaultImage + } } if b.config.SnapshotName == "" { @@ -226,9 +251,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, nil } - region_id := state.Get("region_id").(uint) + sregion := state.Get("region") + + var region string + + if sregion != nil { + region = sregion.(string) + } else { + region = fmt.Sprintf("%v", state.Get("region_id").(uint)) + } + + found_region, err := client.Region(region) - regionName, err := client.RegionName(region_id) if err != nil { return nil, err } @@ -236,8 +270,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe artifact := &Artifact{ snapshotName: state.Get("snapshot_name").(string), snapshotId: state.Get("snapshot_image_id").(uint), - regionId: region_id, - regionName: regionName, + regionName: found_region.Name, client: client, } diff --git a/builder/digitalocean/builder_test.go b/builder/digitalocean/builder_test.go index 751cd1301..d6857bbe6 100644 --- a/builder/digitalocean/builder_test.go +++ b/builder/digitalocean/builder_test.go @@ -142,7 +142,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { } } -func TestBuilderPrepare_RegionID(t *testing.T) { +func TestBuilderPrepare_Region(t *testing.T) { var b Builder config := testConfig() @@ -155,12 +155,15 @@ func TestBuilderPrepare_RegionID(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.RegionID != 1 { - t.Errorf("invalid: %d", b.config.RegionID) + if b.config.Region != DefaultRegion { + t.Errorf("found %s, expected %s", b.config.Region, DefaultRegion) } + expected := "sfo1" + // Test set - config["region_id"] = 2 + config["region_id"] = 0 + config["region"] = expected b = Builder{} warnings, err = b.Prepare(config) if len(warnings) > 0 { @@ -170,12 +173,12 @@ func TestBuilderPrepare_RegionID(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.RegionID != 2 { - t.Errorf("invalid: %d", b.config.RegionID) + if b.config.Region != expected { + t.Errorf("found %s, expected %s", b.config.Region, expected) } } -func TestBuilderPrepare_SizeID(t *testing.T) { +func TestBuilderPrepare_Size(t *testing.T) { var b Builder config := testConfig() @@ -188,12 +191,15 @@ func TestBuilderPrepare_SizeID(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.SizeID != 66 { - t.Errorf("invalid: %d", b.config.SizeID) + if b.config.Size != DefaultSize { + t.Errorf("found %s, expected %s", b.config.Size, DefaultSize) } + expected := "1024mb" + // Test set - config["size_id"] = 67 + config["size_id"] = 0 + config["size"] = expected b = Builder{} warnings, err = b.Prepare(config) if len(warnings) > 0 { @@ -203,12 +209,12 @@ func TestBuilderPrepare_SizeID(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.SizeID != 67 { - t.Errorf("invalid: %d", b.config.SizeID) + if b.config.Size != expected { + t.Errorf("found %s, expected %s", b.config.Size, expected) } } -func TestBuilderPrepare_ImageID(t *testing.T) { +func TestBuilderPrepare_Image(t *testing.T) { var b Builder config := testConfig() @@ -221,12 +227,15 @@ func TestBuilderPrepare_ImageID(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.SizeID != 66 { - t.Errorf("invalid: %d", b.config.SizeID) + if b.config.Image != DefaultImage { + t.Errorf("found %s, expected %s", b.config.Image, DefaultImage) } + expected := "ubuntu-14-04-x64" + // Test set - config["size_id"] = 2 + config["image_id"] = 0 + config["image"] = expected b = Builder{} warnings, err = b.Prepare(config) if len(warnings) > 0 { @@ -236,8 +245,8 @@ func TestBuilderPrepare_ImageID(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.SizeID != 2 { - t.Errorf("invalid: %d", b.config.SizeID) + if b.config.Image != expected { + t.Errorf("found %s, expected %s", b.config.Image, expected) } } diff --git a/builder/digitalocean/step_create_droplet.go b/builder/digitalocean/step_create_droplet.go index f46e7fd3c..79a235413 100644 --- a/builder/digitalocean/step_create_droplet.go +++ b/builder/digitalocean/step_create_droplet.go @@ -19,7 +19,7 @@ func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Creating droplet...") // Create the droplet based on configuration - dropletId, err := client.CreateDroplet(c.DropletName, c.SizeID, c.ImageID, c.RegionID, sshKeyId, c.PrivateNetworking) + dropletId, err := client.CreateDroplet(c.DropletName, c.Size, c.Image, c.Region, sshKeyId, c.PrivateNetworking) if err != nil { err := fmt.Errorf("Error creating droplet: %s", err) diff --git a/builder/digitalocean/step_snapshot.go b/builder/digitalocean/step_snapshot.go index b8ae66062..1b29dc1ef 100644 --- a/builder/digitalocean/step_snapshot.go +++ b/builder/digitalocean/step_snapshot.go @@ -62,7 +62,7 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction { state.Put("snapshot_image_id", imageId) state.Put("snapshot_name", c.SnapshotName) - state.Put("region_id", c.RegionID) + state.Put("region", c.Region) return multistep.ActionContinue } diff --git a/website/source/docs/builders/digitalocean.html.markdown b/website/source/docs/builders/digitalocean.html.markdown index e5139db27..c317842a0 100644 --- a/website/source/docs/builders/digitalocean.html.markdown +++ b/website/source/docs/builders/digitalocean.html.markdown @@ -35,16 +35,30 @@ Required: Optional: +* `image` (string) - The name (or slug) of the base image to use. This is the + image that will be used to launch a new droplet and provision it. This + defaults to 'ubuntu-12-04-x64' which is the slug for "Ubuntu 12.04.4 x64". + See https://developers.digitalocean.com/images/ for the accepted image names/slugs. + * `image_id` (int) - The ID of the base image to use. This is the image that - will be used to launch a new droplet and provision it. Defaults to "3101045", - which happens to be "Ubuntu 12.04.4 x64". + will be used to launch a new droplet and provision it. + This setting is deprecated. Use `image` instead. + +* `region` (string) - The name (or slug) of the region to launch the droplet in. + Consequently, this is the region where the snapshot will be available. + This defaults to "nyc1", which the slug for "New York 1". + See https://developers.digitalocean.com/regions/ for the accepted region names/slugs. * `region_id` (int) - The ID of the region to launch the droplet in. Consequently, - this is the region where the snapshot will be available. This defaults to - "1", which is "New York 1". + this is the region where the snapshot will be available. + This setting is deprecated. Use `region` instead. + +* `size` (string) - The name (or slug) of the droplet size to use. + This defaults to "512mb", which is the slug for "512MB". + See https://developers.digitalocean.com/sizes/ for the accepted size names/slugs. -* `size_id` (int) - The ID of the droplet size to use. This defaults to "66", - which is the 512MB droplet. +* `size_id` (int) - The ID of the droplet size to use. + This setting is deprecated. Use `size` instead. * `private_networking` (bool) - Set to `true` to enable private networking for the droplet being created. This defaults to `false`, or not enabled. From fce2fc526f2015f487fe5e17a11574883e484bf6 Mon Sep 17 00:00:00 2001 From: Michael Kuzmin Date: Wed, 30 Apr 2014 16:02:09 +0400 Subject: [PATCH 020/593] Optional parameters in vSphere post-processor. datastore, vm_folder, and vm_network options can be omitted. --- post-processor/vsphere/post-processor.go | 9 ++++++--- .../docs/post-processors/vsphere.html.markdown | 14 +++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/post-processor/vsphere/post-processor.go b/post-processor/vsphere/post-processor.go index 29d0f32bc..f9f9ee102 100644 --- a/post-processor/vsphere/post-processor.go +++ b/post-processor/vsphere/post-processor.go @@ -64,14 +64,11 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { templates := map[string]*string{ "cluster": &p.config.Cluster, "datacenter": &p.config.Datacenter, - "datastore": &p.config.Datastore, "diskmode": &p.config.DiskMode, "host": &p.config.Host, - "vm_network": &p.config.VMNetwork, "password": &p.config.Password, "resource_pool": &p.config.ResourcePool, "username": &p.config.Username, - "vm_folder": &p.config.VMFolder, "vm_name": &p.config.VMName, } @@ -80,7 +77,13 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { errs = packer.MultiErrorAppend( errs, fmt.Errorf("%s must be set", key)) } + } + + templates ["datastore"] = &p.config.Datastore + templates ["vm_network"] = &p.config.VMNetwork + templates ["vm_folder"] = &p.config.VMFolder + for key, ptr := range templates { *ptr, err = p.config.tpl.Process(*ptr, nil) if err != nil { errs = packer.MultiErrorAppend( diff --git a/website/source/docs/post-processors/vsphere.html.markdown b/website/source/docs/post-processors/vsphere.html.markdown index 742568dbe..8dde0e8fb 100644 --- a/website/source/docs/post-processors/vsphere.html.markdown +++ b/website/source/docs/post-processors/vsphere.html.markdown @@ -23,8 +23,6 @@ Required: * `datacenter` (string) - The name of the datacenter within vSphere to add the VM to. -* `datastore` (string) - The name of the datastore to store this VM. - * `host` (string) - The vSphere host that will be contacted to perform the VM upload. @@ -36,17 +34,19 @@ Required: * `username` (string) - The username to use to authenticate to the vSphere endpoint. -* `vm_folder` (string) - The folder within the datastore to store the VM. - * `vm_name` (string) - The name of the VM once it is uploaded. -* `vm_network` (string) - The name of the VM network this VM will be - added to. - Optional: +* `datastore` (string) - The name of the datastore to store this VM. + * `disk_mode` (string) - Target disk format. See `ovftool` manual for available options. By default, "thick" will be used. * `insecure` (bool) - Whether or not the connection to vSphere can be done over an insecure connection. By default this is false. + +* `vm_folder` (string) - The folder within the datastore to store the VM. + +* `vm_network` (string) - The name of the VM network this VM will be + added to. From 0417ae3059ca3d106a0c0d319d2f11b539dd2c3a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 30 Apr 2014 15:43:11 -0700 Subject: [PATCH 021/593] builder/amazon: alphabetize some things --- builder/amazon/common/block_device.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/amazon/common/block_device.go b/builder/amazon/common/block_device.go index 08f07e269..6cfe18f10 100644 --- a/builder/amazon/common/block_device.go +++ b/builder/amazon/common/block_device.go @@ -6,14 +6,14 @@ import ( // BlockDevice type BlockDevice struct { + DeleteOnTermination bool `mapstructure:"delete_on_termination"` DeviceName string `mapstructure:"device_name"` - VirtualName string `mapstructure:"virtual_name"` + IOPS int64 `mapstructure:"iops"` + NoDevice bool `mapstructure:"no_device"` SnapshotId string `mapstructure:"snapshot_id"` + VirtualName string `mapstructure:"virtual_name"` VolumeType string `mapstructure:"volume_type"` VolumeSize int64 `mapstructure:"volume_size"` - DeleteOnTermination bool `mapstructure:"delete_on_termination"` - IOPS int64 `mapstructure:"iops"` - NoDevice bool `mapstructure:"no_device"` } type BlockDevices struct { From 3c2df9a1324eb87e0ac2e85ad33fdbd300f22ad6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 30 Apr 2014 15:52:01 -0700 Subject: [PATCH 022/593] post-processor/vsphere: comments and style --- post-processor/vsphere/post-processor.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/post-processor/vsphere/post-processor.go b/post-processor/vsphere/post-processor.go index f9f9ee102..c120cad70 100644 --- a/post-processor/vsphere/post-processor.go +++ b/post-processor/vsphere/post-processor.go @@ -61,6 +61,7 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { errs, fmt.Errorf("ovftool not found: %s", err)) } + // First define all our templatable parameters that are _required_ templates := map[string]*string{ "cluster": &p.config.Cluster, "datacenter": &p.config.Datacenter, @@ -71,7 +72,6 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { "username": &p.config.Username, "vm_name": &p.config.VMName, } - for key, ptr := range templates { if *ptr == "" { errs = packer.MultiErrorAppend( @@ -79,10 +79,12 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { } } - templates ["datastore"] = &p.config.Datastore - templates ["vm_network"] = &p.config.VMNetwork - templates ["vm_folder"] = &p.config.VMFolder + // Then define the ones that are optional + templates["datastore"] = &p.config.Datastore + templates["vm_network"] = &p.config.VMNetwork + templates["vm_folder"] = &p.config.VMFolder + // Template process for key, ptr := range templates { *ptr, err = p.config.tpl.Process(*ptr, nil) if err != nil { From 492a6d5abae91d91608830f5744ab24b4d58d3ae Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 30 Apr 2014 15:52:25 -0700 Subject: [PATCH 023/593] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 275e9a6db..49b43dac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,8 @@ BUG FIXES: * provisioners/shell: chmod the uploaded script file to 0777. [GH-994] * post-processor/docker-push: Allow repositories with ports. [GH-923] * post-processor/vagrant: Create parent directories for `output` path [GH-1059] + * post-processor/vsphere: datastore, network, and folder are no longer + required. [GH-1091] ## 0.5.2 (02/21/2014) From 2a3673c1c9d3f9d61ec2b33f1a914782ebcf487a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 30 Apr 2014 15:55:58 -0700 Subject: [PATCH 024/593] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49b43dac3..0942bcadc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ IMPROVEMENTS: array configurations. [GH-950] * builder/amazon: Added `ssh_private_key_file` option [GH-971] * builder/amazon: Added `ami_virtualization_type` option [GH-1021] + * builder/digitalocean: Regions, image names, and sizes can be + names that are looked up for their valid ID. [GH-960] * builder/googlecompute: Configurable instance name. [GH-1065] * builder/openstack: Support for conventional OpenStack environmental variables such as `OS_USERNAME`, `OS_PASSWORD`, etc. [GH-768] From e8ab99676e2ae0372be13001d5a66816d128fc65 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 30 Apr 2014 16:55:50 -0700 Subject: [PATCH 025/593] website: openstack docs --- website/source/docs/builders/openstack.html.markdown | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/website/source/docs/builders/openstack.html.markdown b/website/source/docs/builders/openstack.html.markdown index db432ed41..ba6012c4f 100644 --- a/website/source/docs/builders/openstack.html.markdown +++ b/website/source/docs/builders/openstack.html.markdown @@ -58,6 +58,13 @@ Optional: installations require this. If not specified, Packer will attempt to read this from the `SDK_API_KEY` environmental variable. +* `floating_ip` (string) - A specific floating IP to assign to this instance. + `use_floating_ip` must also be set to true for this to have an affect. + +* `floating_ip_pool` (string) - The name of the floating IP pool to use + to allocate a floating IP. `use_floating_ip` must also be set to true + for this to have an affect. + * `project` (string) - The project name to boot the instance into. Some OpenStack installations require this. If not specified, Packer will attempt to read this from the `SDK_PROJECT` or `OS_TENANT_NAME` environmental @@ -78,6 +85,9 @@ Optional: * `ssh_username` (string) - The username to use in order to communicate over SSH to the running server. The default is "root". +* `use_floating_ip` (bool) - Whether or not to use a floating IP for + the instance. + ## Basic Example Here is a basic example. This is a working example to build a From 4194a9bae381f079265269a94f05c7a0c7b06232 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Wed, 30 Apr 2014 17:10:12 -0700 Subject: [PATCH 026/593] Update digitalocean.html.markdown tiniest of typos --- website/source/docs/builders/digitalocean.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/builders/digitalocean.html.markdown b/website/source/docs/builders/digitalocean.html.markdown index c317842a0..a493de93b 100644 --- a/website/source/docs/builders/digitalocean.html.markdown +++ b/website/source/docs/builders/digitalocean.html.markdown @@ -46,7 +46,7 @@ Optional: * `region` (string) - The name (or slug) of the region to launch the droplet in. Consequently, this is the region where the snapshot will be available. - This defaults to "nyc1", which the slug for "New York 1". + This defaults to "nyc1", which is the slug for "New York 1". See https://developers.digitalocean.com/regions/ for the accepted region names/slugs. * `region_id` (int) - The ID of the region to launch the droplet in. Consequently, From 660be5b783d8bf55169965ecaf6bec157cff4bc3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 30 Apr 2014 17:20:44 -0700 Subject: [PATCH 027/593] builder/openstack: support a tenant ID --- builder/openstack/access_config.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/builder/openstack/access_config.go b/builder/openstack/access_config.go index 98bc05779..7f91f70ca 100644 --- a/builder/openstack/access_config.go +++ b/builder/openstack/access_config.go @@ -20,6 +20,7 @@ type AccessConfig struct { Provider string `mapstructure:"provider"` RawRegion string `mapstructure:"region"` ProxyUrl string `mapstructure:"proxy_url"` + TenantId string `mapstructure:"tenant_id"` } // Auth returns a valid Auth object for access to openstack services, or @@ -31,6 +32,7 @@ func (c *AccessConfig) Auth() (gophercloud.AccessProvider, error) { c.Project = common.ChooseString(c.Project, os.Getenv("SDK_PROJECT"), os.Getenv("OS_TENANT_NAME")) c.Provider = common.ChooseString(c.Provider, os.Getenv("SDK_PROVIDER"), os.Getenv("OS_AUTH_URL")) c.RawRegion = common.ChooseString(c.RawRegion, os.Getenv("SDK_REGION"), os.Getenv("OS_REGION_NAME")) + c.TenantId = common.ChooseString(c.TenantId, os.Getenv("OS_TENANT_ID")) // OpenStack's auto-generated openrc.sh files do not append the suffix // /tokens to the authentication URL. This ensures it is present when @@ -40,14 +42,13 @@ func (c *AccessConfig) Auth() (gophercloud.AccessProvider, error) { } authoptions := gophercloud.AuthOptions{ - Username: c.Username, - Password: c.Password, - ApiKey: c.ApiKey, AllowReauth: true, - } - if c.Project != "" { - authoptions.TenantName = c.Project + ApiKey: c.ApiKey, + TenantId: c.TenantId, + TenantName: c.Project, + Username: c.Username, + Password: c.Password, } // For corporate networks it may be the case where we want our API calls From 92d58a5edebed3ee36047c36e29ef6e3fadbfe6e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 30 Apr 2014 17:21:15 -0700 Subject: [PATCH 028/593] website: update openstack docs --- website/source/docs/builders/openstack.html.markdown | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/source/docs/builders/openstack.html.markdown b/website/source/docs/builders/openstack.html.markdown index ba6012c4f..35dd6c008 100644 --- a/website/source/docs/builders/openstack.html.markdown +++ b/website/source/docs/builders/openstack.html.markdown @@ -85,6 +85,9 @@ Optional: * `ssh_username` (string) - The username to use in order to communicate over SSH to the running server. The default is "root". +* `tenant_id` (string) - Tenant ID for accessing OpenStack if your + installation requires this. + * `use_floating_ip` (bool) - Whether or not to use a floating IP for the instance. From f48fc1e2cd41878f4eb9a772f25c7ce691c55f61 Mon Sep 17 00:00:00 2001 From: Kgespada Date: Fri, 17 Jan 2014 15:46:35 -0800 Subject: [PATCH 029/593] Adds security group support Allows security groups to be specified in the template. --- builder/openstack/builder.go | 7 ++++--- builder/openstack/run_config.go | 22 ++++++++++----------- builder/openstack/step_run_source_server.go | 22 ++++++++++++++------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/builder/openstack/builder.go b/builder/openstack/builder.go index e745b8db0..93dd18520 100644 --- a/builder/openstack/builder.go +++ b/builder/openstack/builder.go @@ -88,9 +88,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe DebugKeyPath: fmt.Sprintf("os_%s.pem", b.config.PackerBuildName), }, &StepRunSourceServer{ - Name: b.config.ImageName, - Flavor: b.config.Flavor, - SourceImage: b.config.SourceImage, + Name: b.config.ImageName, + Flavor: b.config.Flavor, + SourceImage: b.config.SourceImage, + SecurityGroups: b.config.SecurityGroups, }, &StepAllocateIp{ FloatingIpPool: b.config.FloatingIpPool, diff --git a/builder/openstack/run_config.go b/builder/openstack/run_config.go index 96388e254..40c75e452 100644 --- a/builder/openstack/run_config.go +++ b/builder/openstack/run_config.go @@ -10,15 +10,16 @@ import ( // RunConfig contains configuration for running an instance from a source // image and details on how to access that launched image. type RunConfig struct { - SourceImage string `mapstructure:"source_image"` - Flavor string `mapstructure:"flavor"` - RawSSHTimeout string `mapstructure:"ssh_timeout"` - SSHUsername string `mapstructure:"ssh_username"` - SSHPort int `mapstructure:"ssh_port"` - OpenstackProvider string `mapstructure:"openstack_provider"` - UseFloatingIp bool `mapstructure:"use_floating_ip"` - FloatingIpPool string `mapstructure:"floating_ip_pool"` - FloatingIp string `mapstructure:"floating_ip"` + SourceImage string `mapstructure:"source_image"` + Flavor string `mapstructure:"flavor"` + RawSSHTimeout string `mapstructure:"ssh_timeout"` + SSHUsername string `mapstructure:"ssh_username"` + SSHPort int `mapstructure:"ssh_port"` + OpenstackProvider string `mapstructure:"openstack_provider"` + UseFloatingIp bool `mapstructure:"use_floating_ip"` + FloatingIpPool string `mapstructure:"floating_ip_pool"` + FloatingIp string `mapstructure:"floating_ip"` + SecurityGroups []string `mapstructure:"security_groups"` // Unexported fields that are calculated from others sshTimeout time.Duration @@ -76,8 +77,7 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { var err error *ptr, err = t.Process(*ptr, nil) if err != nil { - errs = append( - errs, fmt.Errorf("Error processing %s: %s", n, err)) + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) } } diff --git a/builder/openstack/step_run_source_server.go b/builder/openstack/step_run_source_server.go index c8e121e09..09062b4e6 100644 --- a/builder/openstack/step_run_source_server.go +++ b/builder/openstack/step_run_source_server.go @@ -9,9 +9,10 @@ import ( ) type StepRunSourceServer struct { - Flavor string - Name string - SourceImage string + Flavor string + Name string + SourceImage string + SecurityGroups []string server *gophercloud.Server } @@ -23,11 +24,18 @@ func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction // XXX - validate image and flavor is available + securityGroups := make([]map[string]interface{}, len(s.SecurityGroups)) + for i, groupName := range s.SecurityGroups { + securityGroups[i] = make(map[string]interface{}) + securityGroups[i]["name"] = groupName + } + server := gophercloud.NewServer{ - Name: s.Name, - ImageRef: s.SourceImage, - FlavorRef: s.Flavor, - KeyPairName: keyName, + Name: s.Name, + ImageRef: s.SourceImage, + FlavorRef: s.Flavor, + KeyPairName: keyName, + SecurityGroup: securityGroups, } serverResp, err := csp.CreateServer(server) From 04d1bc67ffaf859b1d3cd23062c91a1f9f8e8fb3 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Thu, 1 May 2014 10:23:43 +0100 Subject: [PATCH 030/593] Support more user variables in the OpenStack builder --- builder/openstack/access_config.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/builder/openstack/access_config.go b/builder/openstack/access_config.go index 7f91f70ca..151288679 100644 --- a/builder/openstack/access_config.go +++ b/builder/openstack/access_config.go @@ -81,10 +81,14 @@ func (c *AccessConfig) Prepare(t *packer.ConfigTemplate) []error { } templates := map[string]*string{ - "username": &c.Username, - "password": &c.Password, - "apiKey": &c.ApiKey, - "provider": &c.Provider, + "username": &c.Username, + "password": &c.Password, + "api_key": &c.ApiKey, + "provider": &c.Provider, + "project": &c.Project, + "tenant_id": &c.TenantId, + "region": &c.RawRegion, + "proxy_url": &c.ProxyUrl, } errs := make([]error, 0) From b801713e837617608944fd9f03dd25fd982a978f Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Thu, 1 May 2014 12:48:04 -0300 Subject: [PATCH 031/593] Node DNA needs to handle multiple types Makes chef-client provisioner consistent with chef-solo in its handling of nested JSON as well as strings. Fixes #1096 --- provisioner/chef-client/provisioner.go | 65 +++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/provisioner/chef-client/provisioner.go b/provisioner/chef-client/provisioner.go index 8d637fbaa..451019498 100644 --- a/provisioner/chef-client/provisioner.go +++ b/provisioner/chef-client/provisioner.go @@ -152,12 +152,24 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { errs, fmt.Errorf("server_url must be set")) } - // Process the user variables within the JSON and set the JSON. - // Do this early so that we can validate and show errors. - p.config.Json, err = p.processJsonUserVars() - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Error processing user variables in JSON: %s", err)) + jsonValid := true + for k, v := range p.config.Json { + p.config.Json[k], err = p.deepJsonFix(k, v) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing JSON: %s", err)) + jsonValid = false + } + } + + if jsonValid { + // Process the user variables within the JSON and set the JSON. + // Do this early so that we can validate and show errors. + p.config.Json, err = p.processJsonUserVars() + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing user variables in JSON: %s", err)) + } } if errs != nil && len(errs.Errors) > 0 { @@ -440,6 +452,47 @@ func (p *Provisioner) copyValidationKey(ui packer.Ui, comm packer.Communicator, return nil } +func (p *Provisioner) deepJsonFix(key string, current interface{}) (interface{}, error) { + if current == nil { + return nil, nil + } + + switch c := current.(type) { + case []interface{}: + val := make([]interface{}, len(c)) + for i, v := range c { + var err error + val[i], err = p.deepJsonFix(fmt.Sprintf("%s[%d]", key, i), v) + if err != nil { + return nil, err + } + } + + return val, nil + case []uint8: + return string(c), nil + case map[interface{}]interface{}: + val := make(map[string]interface{}) + for k, v := range c { + ks, ok := k.(string) + if !ok { + return nil, fmt.Errorf("%s: key is not string", key) + } + + var err error + val[ks], err = p.deepJsonFix( + fmt.Sprintf("%s.%s", key, ks), v) + if err != nil { + return nil, err + } + } + + return val, nil + default: + return current, nil + } +} + func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) { jsonBytes, err := json.Marshal(p.config.Json) if err != nil { From cdaa9d5a8e166be0dda6b7dd04f4fabf4c41d76d Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Sun, 6 Apr 2014 19:21:22 +0200 Subject: [PATCH 032/593] Added support for Parallels Desktop for Mac [GH-223] Added builder plugins: builder-parallels-iso and builder-parallels-pvm. --- builder/parallels/common/artifact.go | 71 +++ builder/parallels/common/artifact_test.go | 43 ++ builder/parallels/common/config_test.go | 15 + builder/parallels/common/driver.go | 63 +++ builder/parallels/common/driver_9.go | 241 ++++++++++ builder/parallels/common/driver_9_test.go | 9 + builder/parallels/common/driver_mock.go | 100 ++++ builder/parallels/common/floppy_config.go | 31 ++ .../parallels/common/floppy_config_test.go | 18 + builder/parallels/common/output_config.go | 40 ++ .../parallels/common/output_config_test.go | 65 +++ builder/parallels/common/prlctl_config.go | 28 ++ .../parallels/common/prlctl_config_test.go | 37 ++ .../parallels/common/prlctl_version_config.go | 31 ++ .../common/prlctl_version_config_test.go | 33 ++ builder/parallels/common/run_config.go | 41 ++ builder/parallels/common/run_config_test.go | 37 ++ builder/parallels/common/shutdown_config.go | 42 ++ .../parallels/common/shutdown_config_test.go | 45 ++ builder/parallels/common/ssh.go | 71 +++ builder/parallels/common/ssh_config.go | 65 +++ builder/parallels/common/ssh_config_test.go | 155 +++++++ .../parallels/common/step_attach_floppy.go | 55 +++ .../common/step_attach_floppy_test.go | 71 +++ .../common/step_attach_parallels_tools.go | 56 +++ builder/parallels/common/step_output_dir.go | 73 +++ .../parallels/common/step_output_dir_test.go | 96 ++++ builder/parallels/common/step_prlctl.go | 68 +++ .../parallels/common/step_remove_devices.go | 63 +++ .../common/step_remove_devices_test.go | 90 ++++ builder/parallels/common/step_run.go | 80 ++++ builder/parallels/common/step_shutdown.go | 78 ++++ .../parallels/common/step_shutdown_test.go | 105 +++++ builder/parallels/common/step_test.go | 18 + .../common/step_upload_parallels_tools.go | 67 +++ .../parallels/common/step_upload_version.go | 44 ++ .../common/step_upload_version_test.go | 61 +++ builder/parallels/common/tools_modes.go | 9 + builder/parallels/iso/builder.go | 360 +++++++++++++++ builder/parallels/iso/builder_test.go | 436 ++++++++++++++++++ builder/parallels/iso/host_ip.go | 7 + builder/parallels/iso/host_ip_ifconfig.go | 55 +++ .../parallels/iso/host_ip_ifconfig_test.go | 11 + builder/parallels/iso/step_attach_iso.go | 45 ++ builder/parallels/iso/step_create_disk.go | 40 ++ builder/parallels/iso/step_create_vm.go | 79 ++++ builder/parallels/iso/step_http_server.go | 75 +++ .../parallels/iso/step_type_boot_command.go | 239 ++++++++++ builder/parallels/pvm/builder.go | 129 ++++++ builder/parallels/pvm/config.go | 131 ++++++ builder/parallels/pvm/config_test.go | 85 ++++ builder/parallels/pvm/step_import.go | 48 ++ builder/parallels/pvm/step_test.go | 19 + config.go | 2 + plugin/builder-parallels-iso/main.go | 15 + plugin/builder-parallels-iso/main_test.go | 1 + plugin/builder-parallels-pvm/main.go | 15 + plugin/builder-parallels-pvm/main_test.go | 1 + 58 files changed, 4108 insertions(+) create mode 100644 builder/parallels/common/artifact.go create mode 100644 builder/parallels/common/artifact_test.go create mode 100644 builder/parallels/common/config_test.go create mode 100644 builder/parallels/common/driver.go create mode 100644 builder/parallels/common/driver_9.go create mode 100644 builder/parallels/common/driver_9_test.go create mode 100644 builder/parallels/common/driver_mock.go create mode 100644 builder/parallels/common/floppy_config.go create mode 100644 builder/parallels/common/floppy_config_test.go create mode 100644 builder/parallels/common/output_config.go create mode 100644 builder/parallels/common/output_config_test.go create mode 100644 builder/parallels/common/prlctl_config.go create mode 100644 builder/parallels/common/prlctl_config_test.go create mode 100644 builder/parallels/common/prlctl_version_config.go create mode 100644 builder/parallels/common/prlctl_version_config_test.go create mode 100644 builder/parallels/common/run_config.go create mode 100644 builder/parallels/common/run_config_test.go create mode 100644 builder/parallels/common/shutdown_config.go create mode 100644 builder/parallels/common/shutdown_config_test.go create mode 100644 builder/parallels/common/ssh.go create mode 100644 builder/parallels/common/ssh_config.go create mode 100644 builder/parallels/common/ssh_config_test.go create mode 100644 builder/parallels/common/step_attach_floppy.go create mode 100644 builder/parallels/common/step_attach_floppy_test.go create mode 100644 builder/parallels/common/step_attach_parallels_tools.go create mode 100644 builder/parallels/common/step_output_dir.go create mode 100644 builder/parallels/common/step_output_dir_test.go create mode 100644 builder/parallels/common/step_prlctl.go create mode 100644 builder/parallels/common/step_remove_devices.go create mode 100644 builder/parallels/common/step_remove_devices_test.go create mode 100644 builder/parallels/common/step_run.go create mode 100644 builder/parallels/common/step_shutdown.go create mode 100644 builder/parallels/common/step_shutdown_test.go create mode 100644 builder/parallels/common/step_test.go create mode 100644 builder/parallels/common/step_upload_parallels_tools.go create mode 100644 builder/parallels/common/step_upload_version.go create mode 100644 builder/parallels/common/step_upload_version_test.go create mode 100644 builder/parallels/common/tools_modes.go create mode 100644 builder/parallels/iso/builder.go create mode 100644 builder/parallels/iso/builder_test.go create mode 100644 builder/parallels/iso/host_ip.go create mode 100644 builder/parallels/iso/host_ip_ifconfig.go create mode 100644 builder/parallels/iso/host_ip_ifconfig_test.go create mode 100644 builder/parallels/iso/step_attach_iso.go create mode 100644 builder/parallels/iso/step_create_disk.go create mode 100644 builder/parallels/iso/step_create_vm.go create mode 100644 builder/parallels/iso/step_http_server.go create mode 100644 builder/parallels/iso/step_type_boot_command.go create mode 100644 builder/parallels/pvm/builder.go create mode 100644 builder/parallels/pvm/config.go create mode 100644 builder/parallels/pvm/config_test.go create mode 100644 builder/parallels/pvm/step_import.go create mode 100644 builder/parallels/pvm/step_test.go create mode 100644 plugin/builder-parallels-iso/main.go create mode 100644 plugin/builder-parallels-iso/main_test.go create mode 100644 plugin/builder-parallels-pvm/main.go create mode 100644 plugin/builder-parallels-pvm/main_test.go diff --git a/builder/parallels/common/artifact.go b/builder/parallels/common/artifact.go new file mode 100644 index 000000000..308b99f72 --- /dev/null +++ b/builder/parallels/common/artifact.go @@ -0,0 +1,71 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/packer/packer" + "os" + "path/filepath" + "regexp" +) + +// This is the common builder ID to all of these artifacts. +const BuilderId = "rickard-von-essen.parallels" + +// These are the extensions of files and directories that are unnecessary for the function +// of a Parallels virtual machine. +var unnecessaryFiles = []string{"\\.log$", "\\.backup$", "\\.Backup$", "\\.app"} + +// Artifact is the result of running the parallels builder, namely a set +// of files associated with the resulting machine. +type artifact struct { + dir string + f []string +} + +// NewArtifact returns a Parallels artifact containing the files +// in the given directory. +func NewArtifact(dir string) (packer.Artifact, error) { + files := make([]string, 0, 5) + visit := func(path string, info os.FileInfo, err error) error { + for _, unnecessaryFile := range unnecessaryFiles { + if unnecessary, _ := regexp.MatchString(unnecessaryFile, path); unnecessary { + return os.RemoveAll(path) + } + } + + if !info.IsDir() { + files = append(files, path) + } + + return err + } + + if err := filepath.Walk(dir, visit); err != nil { + return nil, err + } + + return &artifact{ + dir: dir, + f: files, + }, nil +} + +func (*artifact) BuilderId() string { + return BuilderId +} + +func (a *artifact) Files() []string { + return a.f +} + +func (*artifact) Id() string { + return "VM" +} + +func (a *artifact) String() string { + return fmt.Sprintf("VM files in directory: %s", a.dir) +} + +func (a *artifact) Destroy() error { + return os.RemoveAll(a.dir) +} diff --git a/builder/parallels/common/artifact_test.go b/builder/parallels/common/artifact_test.go new file mode 100644 index 000000000..f9ddc5dbf --- /dev/null +++ b/builder/parallels/common/artifact_test.go @@ -0,0 +1,43 @@ +package common + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/mitchellh/packer/packer" +) + +func TestArtifact_impl(t *testing.T) { + var _ packer.Artifact = new(artifact) +} + +func TestNewArtifact(t *testing.T) { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + err = ioutil.WriteFile(filepath.Join(td, "a"), []byte("foo"), 0644) + if err != nil { + t.Fatalf("err: %s", err) + } + + if err := os.Mkdir(filepath.Join(td, "b"), 0755); err != nil { + t.Fatalf("err: %s", err) + } + + a, err := NewArtifact(td) + if err != nil { + t.Fatalf("err: %s", err) + } + + if a.BuilderId() != BuilderId { + t.Fatalf("bad: %#v", a.BuilderId()) + } + if len(a.Files()) != 1 { + t.Fatalf("should length 1: %d", len(a.Files())) + } +} diff --git a/builder/parallels/common/config_test.go b/builder/parallels/common/config_test.go new file mode 100644 index 000000000..a84c51bc1 --- /dev/null +++ b/builder/parallels/common/config_test.go @@ -0,0 +1,15 @@ +package common + +import ( + "github.com/mitchellh/packer/packer" + "testing" +) + +func testConfigTemplate(t *testing.T) *packer.ConfigTemplate { + result, err := packer.NewConfigTemplate() + if err != nil { + t.Fatalf("err: %s", err) + } + + return result +} diff --git a/builder/parallels/common/driver.go b/builder/parallels/common/driver.go new file mode 100644 index 000000000..c28f0f9f4 --- /dev/null +++ b/builder/parallels/common/driver.go @@ -0,0 +1,63 @@ +package common + +import ( + "log" + "os/exec" +) + +// A driver is able to talk to Parallels and perform certain +// operations with it. Some of the operations on here may seem overly +// specific, but they were built specifically in mind to handle features +// of the Parallels builder for Packer, and to abstract differences in +// versions out of the builder steps, so sometimes the methods are +// extremely specific. +type Driver interface { + // Import a VM + Import(string, string, string) error + + // Checks if the VM with the given name is running. + IsRunning(string) (bool, error) + + // Stop stops a running machine, forcefully. + Stop(string) error + + // Prlctl executes the given Prlctl command + Prlctl(...string) error + + // Verify checks to make sure that this driver should function + // properly. If there is any indication the driver can't function, + // this will return an error. + Verify() error + + // Version reads the version of Parallels that is installed. + Version() (string, error) + + // Send scancodes to the vm using the prltype tool. + SendKeyScanCodes(string, ...string) error + + // Finds the MAC address of the NIC nic0 + Mac(string) (string, error) + + // Finds the IP address of a VM connected that uses DHCP by its MAC address + IpAddress(string) (string, error) +} + +func NewDriver() (Driver, error) { + var prlctlPath string + + if prlctlPath == "" { + var err error + prlctlPath, err = exec.LookPath("prlctl") + if err != nil { + return nil, err + } + } + + log.Printf("prlctl path: %s", prlctlPath) + driver := &Parallels9Driver{prlctlPath} + if err := driver.Verify(); err != nil { + return nil, err + } + + return driver, nil +} diff --git a/builder/parallels/common/driver_9.go b/builder/parallels/common/driver_9.go new file mode 100644 index 000000000..e29c81bc6 --- /dev/null +++ b/builder/parallels/common/driver_9.go @@ -0,0 +1,241 @@ +package common + +import ( + "bytes" + "fmt" + "github.com/going/toolkit/xmlpath" + "log" + "os" + "os/exec" + "regexp" + "strings" + "time" +) + +type Parallels9Driver struct { + // This is the path to the "prlctl" application. + PrlctlPath string +} + +func (d *Parallels9Driver) Import(name, srcPath, dstDir string) error { + + err := d.Prlctl("register", srcPath, "--preserve-uuid") + if err != nil { + return err + } + + srcId, err := getVmId(srcPath) + if err != nil { + return err + } + + srcMac, err := getFirtsMacAddress(srcPath) + if err != nil { + return err + } + + err = d.Prlctl("clone", srcId, "--name", name, "--dst", dstDir) + if err != nil { + return err + } + + err = d.Prlctl("unregister", srcId) + if err != nil { + return err + } + + err = d.Prlctl("set", name, "--device-set", "net0", "--mac", srcMac) + return nil +} + +func getVmId(path string) (string, error) { + return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Identification/VmUuid") +} + +func getFirtsMacAddress(path string) (string, error) { + return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Hardware/NetworkAdapter[@id='0']/MAC") +} + +func getConfigValueFromXpath(path, xpath string) (string, error) { + file, err := os.Open(path + "/config.pvs") + if err != nil { + return "", err + } + xpathComp := xmlpath.MustCompile(xpath) + root, err := xmlpath.Parse(file) + if err != nil { + return "", err + } + value, _ := xpathComp.String(root) + return value, nil +} + +func (d *Parallels9Driver) IsRunning(name string) (bool, error) { + var stdout bytes.Buffer + + cmd := exec.Command(d.PrlctlPath, "list", name, "--no-header", "--output", "status") + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + return false, err + } + + for _, line := range strings.Split(stdout.String(), "\n") { + if line == "running" { + return true, nil + } + + if line == "suspended" { + return true, nil + } + if line == "paused" { + return true, nil + } + } + + return false, nil +} + +func (d *Parallels9Driver) Stop(name string) error { + if err := d.Prlctl("stop", name); err != nil { + return err + } + + // We sleep here for a little bit to let the session "unlock" + time.Sleep(2 * time.Second) + + return nil +} + +func (d *Parallels9Driver) Prlctl(args ...string) error { + var stdout, stderr bytes.Buffer + + log.Printf("Executing prlctl: %#v", args) + cmd := exec.Command(d.PrlctlPath, 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("prlctl error: %s", stderrString) + } + + log.Printf("stdout: %s", stdoutString) + log.Printf("stderr: %s", stderrString) + + return err +} + +func (d *Parallels9Driver) Verify() error { + version, _ := d.Version() + if !strings.HasPrefix(version, "9.") { + return fmt.Errorf("The packer-parallels builder plugin only supports Parallels Desktop v. 9. You have: %s!\n", version) + } + return nil +} + +func (d *Parallels9Driver) Version() (string, error) { + var stdout bytes.Buffer + + cmd := exec.Command(d.PrlctlPath, "--version") + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + return "", err + } + + versionOutput := strings.TrimSpace(stdout.String()) + re := regexp.MustCompile("prlctl version ([0-9\\.]+)") + verMatch := re.FindAllStringSubmatch(versionOutput, 1) + + if len(verMatch) != 1 { + return "", fmt.Errorf("prlctl version not found!\n") + } + + version := verMatch[0][1] + log.Printf("prlctl version: %s\n", version) + return version, nil +} + +func (d *Parallels9Driver) SendKeyScanCodes(vmName string, codes ...string) error { + var stdout, stderr bytes.Buffer + + args := prepend(vmName, codes) + cmd := exec.Command("prltype", 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("prltype error: %s", stderrString) + } + + log.Printf("stdout: %s", stdoutString) + log.Printf("stderr: %s", stderrString) + + return err +} + +func prepend(head string, tail []string) []string { + tmp := make([]string, len(tail)+1) + for i := 0; i < len(tail); i++ { + tmp[i+1] = tail[i] + } + tmp[0] = head + return tmp +} + +func (d *Parallels9Driver) Mac(vmName string) (string, error) { + var stdout bytes.Buffer + + cmd := exec.Command(d.PrlctlPath, "list", "-i", vmName) + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + log.Printf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName) + return "", err + } + + stdoutString := strings.TrimSpace(stdout.String()) + re := regexp.MustCompile("net0.* mac=([0-9A-F]{12}) card=.*") + macMatch := re.FindAllStringSubmatch(stdoutString, 1) + + if len(macMatch) != 1 { + return "", fmt.Errorf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName) + } + + mac := macMatch[0][1] + log.Printf("Found MAC address for NIC: net0 - %s\n", mac) + return mac, nil +} + +// Finds the IP address of a VM connected that uses DHCP by its MAC address +func (d *Parallels9Driver) IpAddress(mac string) (string, error) { + var stdout bytes.Buffer + dhcp_lease_file := "/Library/Preferences/Parallels/parallels_dhcp_leases" + + if len(mac) != 12 { + return "", fmt.Errorf("Not a valid MAC address: %s. It should be exactly 12 digits.", mac) + } + + cmd := exec.Command("grep", "-i", mac, dhcp_lease_file) + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + return "", err + } + + stdoutString := strings.TrimSpace(stdout.String()) + re := regexp.MustCompile("(.*)=.*") + ipMatch := re.FindAllStringSubmatch(stdoutString, 1) + + if len(ipMatch) != 1 { + return "", fmt.Errorf("IP lease not found for MAC address %s in: %s\n", mac, dhcp_lease_file) + } + + ip := ipMatch[0][1] + log.Printf("Found IP lease: %s for MAC address %s\n", ip, mac) + return ip, nil +} diff --git a/builder/parallels/common/driver_9_test.go b/builder/parallels/common/driver_9_test.go new file mode 100644 index 000000000..a7c61b9d2 --- /dev/null +++ b/builder/parallels/common/driver_9_test.go @@ -0,0 +1,9 @@ +package common + +import ( + "testing" +) + +func TestParallels9Driver_impl(t *testing.T) { + var _ Driver = new(Parallels9Driver) +} diff --git a/builder/parallels/common/driver_mock.go b/builder/parallels/common/driver_mock.go new file mode 100644 index 000000000..e54c5cd5a --- /dev/null +++ b/builder/parallels/common/driver_mock.go @@ -0,0 +1,100 @@ +package common + +import "sync" + +type DriverMock struct { + sync.Mutex + + ImportCalled bool + ImportName string + ImportSrcPath string + ImportDstPath string + ImportErr error + + IsRunningName string + IsRunningReturn bool + IsRunningErr error + + StopName string + StopErr error + + PrlctlCalls [][]string + PrlctlErrs []error + + VerifyCalled bool + VerifyErr error + + VersionCalled bool + VersionResult string + VersionErr error + + SendKeyScanCodesCalls [][]string + SendKeyScanCodesErrs []error + + MacName string + MacReturn string + MacError error + + IpAddressMac string + IpAddressReturn string + IpAddressError error +} + +func (d *DriverMock) Import(name, srcPath, dstPath string) error { + d.ImportCalled = true + d.ImportName = name + d.ImportSrcPath = srcPath + d.ImportDstPath = dstPath + return d.ImportErr +} + +func (d *DriverMock) IsRunning(name string) (bool, error) { + d.Lock() + defer d.Unlock() + + d.IsRunningName = name + return d.IsRunningReturn, d.IsRunningErr +} + +func (d *DriverMock) Stop(name string) error { + d.StopName = name + return d.StopErr +} + +func (d *DriverMock) Prlctl(args ...string) error { + d.PrlctlCalls = append(d.PrlctlCalls, args) + + if len(d.PrlctlErrs) >= len(d.PrlctlCalls) { + return d.PrlctlErrs[len(d.PrlctlCalls)-1] + } + return nil +} + +func (d *DriverMock) Verify() error { + d.VerifyCalled = true + return d.VerifyErr +} + +func (d *DriverMock) Version() (string, error) { + d.VersionCalled = true + return d.VersionResult, d.VersionErr +} + +func (d *DriverMock) SendKeyScanCodes(name string, scancodes ...string) error { + d.SendKeyScanCodesCalls = append(d.SendKeyScanCodesCalls, scancodes) + + if len(d.SendKeyScanCodesErrs) >= len(d.SendKeyScanCodesCalls) { + return d.SendKeyScanCodesErrs[len(d.SendKeyScanCodesCalls)-1] + } + return nil +} + +func (d *DriverMock) Mac(name string) (string, error) { + d.MacName = name + return d.MacReturn, d.MacError +} + +func (d *DriverMock) IpAddress(mac string) (string, error) { + d.IpAddressMac = mac + return d.IpAddressReturn, d.IpAddressError +} diff --git a/builder/parallels/common/floppy_config.go b/builder/parallels/common/floppy_config.go new file mode 100644 index 000000000..35cd7aca4 --- /dev/null +++ b/builder/parallels/common/floppy_config.go @@ -0,0 +1,31 @@ +package common + +import ( + "fmt" + + "github.com/mitchellh/packer/packer" +) + +// FloppyConfig is configuration related to created floppy disks and attaching +// them to a VirtualBox machine. +type FloppyConfig struct { + FloppyFiles []string `mapstructure:"floppy_files"` +} + +func (c *FloppyConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.FloppyFiles == nil { + c.FloppyFiles = make([]string, 0) + } + + errs := make([]error, 0) + for i, file := range c.FloppyFiles { + var err error + c.FloppyFiles[i], err = t.Process(file, nil) + if err != nil { + errs = append(errs, fmt.Errorf( + "Error processing floppy_files[%d]: %s", i, err)) + } + } + + return errs +} diff --git a/builder/parallels/common/floppy_config_test.go b/builder/parallels/common/floppy_config_test.go new file mode 100644 index 000000000..3e4fc55db --- /dev/null +++ b/builder/parallels/common/floppy_config_test.go @@ -0,0 +1,18 @@ +package common + +import ( + "testing" +) + +func TestFloppyConfigPrepare(t *testing.T) { + c := new(FloppyConfig) + + errs := c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if len(c.FloppyFiles) > 0 { + t.Fatal("should not have floppy files") + } +} diff --git a/builder/parallels/common/output_config.go b/builder/parallels/common/output_config.go new file mode 100644 index 000000000..19be1ba00 --- /dev/null +++ b/builder/parallels/common/output_config.go @@ -0,0 +1,40 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "os" +) + +type OutputConfig struct { + OutputDir string `mapstructure:"output_directory"` +} + +func (c *OutputConfig) Prepare(t *packer.ConfigTemplate, pc *common.PackerConfig) []error { + if c.OutputDir == "" { + c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName) + } + + templates := map[string]*string{ + "output_directory": &c.OutputDir, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + if !pc.PackerForce { + if _, err := os.Stat(c.OutputDir); err == nil { + errs = append(errs, fmt.Errorf( + "Output directory '%s' already exists. It must not exist.", c.OutputDir)) + } + } + + return errs +} diff --git a/builder/parallels/common/output_config_test.go b/builder/parallels/common/output_config_test.go new file mode 100644 index 000000000..7fa039a16 --- /dev/null +++ b/builder/parallels/common/output_config_test.go @@ -0,0 +1,65 @@ +package common + +import ( + "github.com/mitchellh/packer/common" + "io/ioutil" + "os" + "testing" +) + +func TestOutputConfigPrepare(t *testing.T) { + c := new(OutputConfig) + if c.OutputDir != "" { + t.Fatalf("what: %s", c.OutputDir) + } + + pc := &common.PackerConfig{PackerBuildName: "foo"} + errs := c.Prepare(testConfigTemplate(t), pc) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if c.OutputDir == "" { + t.Fatal("should have output dir") + } +} + +func TestOutputConfigPrepare_exists(t *testing.T) { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + c := new(OutputConfig) + c.OutputDir = td + + pc := &common.PackerConfig{ + PackerBuildName: "foo", + PackerForce: false, + } + errs := c.Prepare(testConfigTemplate(t), pc) + if len(errs) == 0 { + t.Fatal("should have errors") + } +} + +func TestOutputConfigPrepare_forceExists(t *testing.T) { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + c := new(OutputConfig) + c.OutputDir = td + + pc := &common.PackerConfig{ + PackerBuildName: "foo", + PackerForce: true, + } + errs := c.Prepare(testConfigTemplate(t), pc) + if len(errs) > 0 { + t.Fatal("should not have errors") + } +} diff --git a/builder/parallels/common/prlctl_config.go b/builder/parallels/common/prlctl_config.go new file mode 100644 index 000000000..eff0618b3 --- /dev/null +++ b/builder/parallels/common/prlctl_config.go @@ -0,0 +1,28 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/packer/packer" +) + +type PrlctlConfig struct { + Prlctl [][]string `mapstructure:"prlctl"` +} + +func (c *PrlctlConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.Prlctl == nil { + c.Prlctl = make([][]string, 0) + } + + errs := make([]error, 0) + for i, args := range c.Prlctl { + for j, arg := range args { + if err := t.Validate(arg); err != nil { + errs = append(errs, + fmt.Errorf("Error processing prlctl[%d][%d]: %s", i, j, err)) + } + } + } + + return errs +} diff --git a/builder/parallels/common/prlctl_config_test.go b/builder/parallels/common/prlctl_config_test.go new file mode 100644 index 000000000..cafb3d23c --- /dev/null +++ b/builder/parallels/common/prlctl_config_test.go @@ -0,0 +1,37 @@ +package common + +import ( + "reflect" + "testing" +) + +func TestPrlctlConfigPrepare_Prlctl(t *testing.T) { + // Test with empty + c := new(PrlctlConfig) + errs := c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if !reflect.DeepEqual(c.Prlctl, [][]string{}) { + t.Fatalf("bad: %#v", c.Prlctl) + } + + // Test with a good one + c = new(PrlctlConfig) + c.Prlctl = [][]string{ + {"foo", "bar", "baz"}, + } + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + expected := [][]string{ + []string{"foo", "bar", "baz"}, + } + + if !reflect.DeepEqual(c.Prlctl, expected) { + t.Fatalf("bad: %#v", c.Prlctl) + } +} diff --git a/builder/parallels/common/prlctl_version_config.go b/builder/parallels/common/prlctl_version_config.go new file mode 100644 index 000000000..2103d9a59 --- /dev/null +++ b/builder/parallels/common/prlctl_version_config.go @@ -0,0 +1,31 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/packer/packer" +) + +type PrlctlVersionConfig struct { + PrlctlVersionFile string `mapstructure:"prlctl_version_file"` +} + +func (c *PrlctlVersionConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.PrlctlVersionFile == "" { + c.PrlctlVersionFile = ".prlctl_version" + } + + templates := map[string]*string{ + "prlctl_version_file": &c.PrlctlVersionFile, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + return errs +} diff --git a/builder/parallels/common/prlctl_version_config_test.go b/builder/parallels/common/prlctl_version_config_test.go new file mode 100644 index 000000000..daf934a16 --- /dev/null +++ b/builder/parallels/common/prlctl_version_config_test.go @@ -0,0 +1,33 @@ +package common + +import ( + "testing" +) + +func TestPrlctlVersionConfigPrepare_BootWait(t *testing.T) { + var c *PrlctlVersionConfig + var errs []error + + // Test empty + c = new(PrlctlVersionConfig) + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %s", errs) + } + + if c.PrlctlVersionFile != ".prlctl_version" { + t.Fatalf("bad value: %s", c.PrlctlVersionFile) + } + + // Test with a good one + c = new(PrlctlVersionConfig) + c.PrlctlVersionFile = "foo" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %s", errs) + } + + if c.PrlctlVersionFile != "foo" { + t.Fatalf("bad value: %s", c.PrlctlVersionFile) + } +} diff --git a/builder/parallels/common/run_config.go b/builder/parallels/common/run_config.go new file mode 100644 index 000000000..755d0f1c1 --- /dev/null +++ b/builder/parallels/common/run_config.go @@ -0,0 +1,41 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/packer/packer" + "time" +) + +type RunConfig struct { + Headless bool `mapstructure:"headless"` + RawBootWait string `mapstructure:"boot_wait"` + + BootWait time.Duration `` +} + +func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.RawBootWait == "" { + c.RawBootWait = "10s" + } + + templates := map[string]*string{ + "boot_wait": &c.RawBootWait, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + var err error + c.BootWait, err = time.ParseDuration(c.RawBootWait) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing boot_wait: %s", err)) + } + + return errs +} diff --git a/builder/parallels/common/run_config_test.go b/builder/parallels/common/run_config_test.go new file mode 100644 index 000000000..8068fe625 --- /dev/null +++ b/builder/parallels/common/run_config_test.go @@ -0,0 +1,37 @@ +package common + +import ( + "testing" +) + +func TestRunConfigPrepare_BootWait(t *testing.T) { + var c *RunConfig + var errs []error + + // Test a default boot_wait + c = new(RunConfig) + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %s", errs) + } + + if c.RawBootWait != "10s" { + t.Fatalf("bad value: %s", c.RawBootWait) + } + + // Test with a bad boot_wait + c = new(RunConfig) + c.RawBootWait = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatalf("bad: %#v", errs) + } + + // Test with a good one + c = new(RunConfig) + c.RawBootWait = "5s" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %s", errs) + } +} diff --git a/builder/parallels/common/shutdown_config.go b/builder/parallels/common/shutdown_config.go new file mode 100644 index 000000000..05e5fdfeb --- /dev/null +++ b/builder/parallels/common/shutdown_config.go @@ -0,0 +1,42 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/packer/packer" + "time" +) + +type ShutdownConfig struct { + ShutdownCommand string `mapstructure:"shutdown_command"` + RawShutdownTimeout string `mapstructure:"shutdown_timeout"` + + ShutdownTimeout time.Duration `` +} + +func (c *ShutdownConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.RawShutdownTimeout == "" { + c.RawShutdownTimeout = "5m" + } + + templates := map[string]*string{ + "shutdown_command": &c.ShutdownCommand, + "shutdown_timeout": &c.RawShutdownTimeout, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + var err error + c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) + } + + return errs +} diff --git a/builder/parallels/common/shutdown_config_test.go b/builder/parallels/common/shutdown_config_test.go new file mode 100644 index 000000000..5da613a19 --- /dev/null +++ b/builder/parallels/common/shutdown_config_test.go @@ -0,0 +1,45 @@ +package common + +import ( + "testing" + "time" +) + +func testShutdownConfig() *ShutdownConfig { + return &ShutdownConfig{} +} + +func TestShutdownConfigPrepare_ShutdownCommand(t *testing.T) { + var c *ShutdownConfig + var errs []error + + c = testShutdownConfig() + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } +} + +func TestShutdownConfigPrepare_ShutdownTimeout(t *testing.T) { + var c *ShutdownConfig + var errs []error + + // Test with a bad value + c = testShutdownConfig() + c.RawShutdownTimeout = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatalf("should have error") + } + + // Test with a good one + c = testShutdownConfig() + c.RawShutdownTimeout = "5s" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + if c.ShutdownTimeout != 5*time.Second { + t.Fatalf("bad: %s", c.ShutdownTimeout) + } +} diff --git a/builder/parallels/common/ssh.go b/builder/parallels/common/ssh.go new file mode 100644 index 000000000..04aed86a2 --- /dev/null +++ b/builder/parallels/common/ssh.go @@ -0,0 +1,71 @@ +package common + +import ( + "code.google.com/p/go.crypto/ssh" + "fmt" + "github.com/mitchellh/multistep" + packerssh "github.com/mitchellh/packer/communicator/ssh" + "io/ioutil" + "os" +) + +func SSHAddress(state multistep.StateBag) (string, error) { + vmName := state.Get("vmName").(string) + driver := state.Get("driver").(Driver) + + mac, err := driver.Mac(vmName) + if err != nil { + return "", err + } + + ip, err := driver.IpAddress(mac) + if err != nil { + return "", err + } + + return fmt.Sprintf("%s:22", ip), nil +} + +func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig, error) { + return func(state multistep.StateBag) (*ssh.ClientConfig, error) { + auth := []ssh.AuthMethod{ + ssh.Password(config.SSHPassword), + ssh.KeyboardInteractive( + packerssh.PasswordKeyboardInteractive(config.SSHPassword)), + } + + if config.SSHKeyPath != "" { + signer, err := sshKeyToSigner(config.SSHKeyPath) + if err != nil { + return nil, err + } + + auth = append(auth, ssh.PublicKeys(signer)) + } + + return &ssh.ClientConfig{ + User: config.SSHUser, + Auth: auth, + }, nil + } +} + +func sshKeyToSigner(path string) (ssh.Signer, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + keyBytes, err := ioutil.ReadAll(f) + if err != nil { + return nil, err + } + + signer, err := ssh.ParsePrivateKey(keyBytes) + if err != nil { + return nil, fmt.Errorf("Error setting up SSH config: %s", err) + } + + return signer, nil +} diff --git a/builder/parallels/common/ssh_config.go b/builder/parallels/common/ssh_config.go new file mode 100644 index 000000000..f28fb3029 --- /dev/null +++ b/builder/parallels/common/ssh_config.go @@ -0,0 +1,65 @@ +package common + +import ( + "errors" + "fmt" + "github.com/mitchellh/packer/packer" + "os" + "time" +) + +type SSHConfig struct { + SSHKeyPath string `mapstructure:"ssh_key_path"` + SSHPassword string `mapstructure:"ssh_password"` + SSHPort uint `mapstructure:"ssh_port"` + SSHUser string `mapstructure:"ssh_username"` + RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"` + + SSHWaitTimeout time.Duration +} + +func (c *SSHConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.SSHPort == 0 { + c.SSHPort = 22 + } + + if c.RawSSHWaitTimeout == "" { + c.RawSSHWaitTimeout = "20m" + } + + templates := map[string]*string{ + "ssh_key_path": &c.SSHKeyPath, + "ssh_password": &c.SSHPassword, + "ssh_username": &c.SSHUser, + "ssh_wait_timeout": &c.RawSSHWaitTimeout, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + if c.SSHKeyPath != "" { + if _, err := os.Stat(c.SSHKeyPath); err != nil { + errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) + } else if _, err := sshKeyToSigner(c.SSHKeyPath); err != nil { + errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) + } + } + + if c.SSHUser == "" { + errs = append(errs, errors.New("An ssh_username must be specified.")) + } + + var err error + c.SSHWaitTimeout, err = time.ParseDuration(c.RawSSHWaitTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err)) + } + + return errs +} diff --git a/builder/parallels/common/ssh_config_test.go b/builder/parallels/common/ssh_config_test.go new file mode 100644 index 000000000..a6c9e8ef5 --- /dev/null +++ b/builder/parallels/common/ssh_config_test.go @@ -0,0 +1,155 @@ +package common + +import ( + "io/ioutil" + "os" + "testing" +) + +func testSSHConfig() *SSHConfig { + return &SSHConfig{ + SSHUser: "foo", + } +} + +func TestSSHConfigPrepare(t *testing.T) { + c := testSSHConfig() + errs := c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if c.SSHPort != 22 { + t.Errorf("bad ssh port: %d", c.SSHPort) + } +} + +func TestSSHConfigPrepare_SSHKeyPath(t *testing.T) { + var c *SSHConfig + var errs []error + + c = testSSHConfig() + c.SSHKeyPath = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } + + c = testSSHConfig() + c.SSHKeyPath = "/i/dont/exist" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should have error") + } + + // Test bad contents + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(tf.Name()) + defer tf.Close() + + if _, err := tf.Write([]byte("HELLO!")); err != nil { + t.Fatalf("err: %s", err) + } + + c = testSSHConfig() + c.SSHKeyPath = tf.Name() + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should have error") + } + + // Test good contents + tf.Seek(0, 0) + tf.Truncate(0) + tf.Write([]byte(testPem)) + c = testSSHConfig() + c.SSHKeyPath = tf.Name() + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } +} + +func TestSSHConfigPrepare_SSHUser(t *testing.T) { + var c *SSHConfig + var errs []error + + c = testSSHConfig() + c.SSHUser = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatalf("should have error") + } + + c = testSSHConfig() + c.SSHUser = "exists" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } +} + +func TestSSHConfigPrepare_SSHWaitTimeout(t *testing.T) { + var c *SSHConfig + var errs []error + + // Defaults + c = testSSHConfig() + c.RawSSHWaitTimeout = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } + if c.RawSSHWaitTimeout != "20m" { + t.Fatalf("bad value: %s", c.RawSSHWaitTimeout) + } + + // Test with a bad value + c = testSSHConfig() + c.RawSSHWaitTimeout = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should have error") + } + + // Test with a good one + c = testSSHConfig() + c.RawSSHWaitTimeout = "5s" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } +} + +const testPem = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu +hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW +LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN +AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD +2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH +uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3 +5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV +BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG +E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko +9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF +K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3 +/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+ +2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa +nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn +kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6 +hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC +v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl +b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR +v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3 +uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1 +9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR +lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc +eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa +1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG +3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4= +-----END RSA PRIVATE KEY----- +` diff --git a/builder/parallels/common/step_attach_floppy.go b/builder/parallels/common/step_attach_floppy.go new file mode 100644 index 000000000..83814f17c --- /dev/null +++ b/builder/parallels/common/step_attach_floppy.go @@ -0,0 +1,55 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +// This step attaches a floppy to the virtual machine. +// +// Uses: +// driver Driver +// ui packer.Ui +// vmName string +// +// Produces: +type StepAttachFloppy struct { + floppyPath string +} + +func (s *StepAttachFloppy) Run(state multistep.StateBag) multistep.StepAction { + // Determine if we even have a floppy disk to attach + var floppyPath string + if floppyPathRaw, ok := state.GetOk("floppy_path"); ok { + floppyPath = floppyPathRaw.(string) + } else { + log.Println("No floppy disk, not attaching.") + return multistep.ActionContinue + } + + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + ui.Say("Attaching floppy disk...") + + // Create the floppy disk controller + command := []string{ + "set", vmName, + "--device-add", "fdd", + "--image", floppyPath, + } + if err := driver.Prlctl(command...); err != nil { + state.Put("error", fmt.Errorf("Error adding floppy: %s", err)) + return multistep.ActionHalt + } + + // Track the path so that we can unregister it from Parallels later + s.floppyPath = floppyPath + + return multistep.ActionContinue +} + +func (s *StepAttachFloppy) Cleanup(state multistep.StateBag) {} diff --git a/builder/parallels/common/step_attach_floppy_test.go b/builder/parallels/common/step_attach_floppy_test.go new file mode 100644 index 000000000..2a4f8bf11 --- /dev/null +++ b/builder/parallels/common/step_attach_floppy_test.go @@ -0,0 +1,71 @@ +package common + +import ( + "github.com/mitchellh/multistep" + "io/ioutil" + "os" + "testing" +) + +func TestStepAttachFloppy_impl(t *testing.T) { + var _ multistep.Step = new(StepAttachFloppy) +} + +func TestStepAttachFloppy(t *testing.T) { + state := testState(t) + step := new(StepAttachFloppy) + + // Create a temporary file for our floppy file + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + tf.Close() + defer os.Remove(tf.Name()) + + state.Put("floppy_path", tf.Name()) + state.Put("vmName", "foo") + + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + if len(driver.PrlctlCalls) != 1 { + t.Fatal("not enough calls to prlctl") + } + + if driver.PrlctlCalls[0][0] != "set" { + t.Fatal("bad call") + } + if driver.PrlctlCalls[0][2] != "--device-add" { + t.Fatal("bad call") + } + if driver.PrlctlCalls[0][3] != "fdd" { + t.Fatal("bad call") + } +} + +func TestStepAttachFloppy_noFloppy(t *testing.T) { + state := testState(t) + step := new(StepAttachFloppy) + + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + if len(driver.PrlctlCalls) > 0 { + t.Fatal("should not call vboxmanage") + } +} diff --git a/builder/parallels/common/step_attach_parallels_tools.go b/builder/parallels/common/step_attach_parallels_tools.go new file mode 100644 index 000000000..250f2e50c --- /dev/null +++ b/builder/parallels/common/step_attach_parallels_tools.go @@ -0,0 +1,56 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +// This step attaches the Parallels Tools as a inserted CD onto +// the virtual machine. +// +// Uses: +// driver Driver +// toolsPath string +// ui packer.Ui +// vmName string +// +// Produces: +type StepAttachParallelsTools struct { + ParallelsToolsHostPath string + ParallelsToolsMode string +} + +func (s *StepAttachParallelsTools) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + // If we're not attaching the guest additions then just return + if s.ParallelsToolsMode != ParallelsToolsModeAttach { + log.Println("Not attaching parallels tools since we're uploading.") + return multistep.ActionContinue + } + + // Attach the guest additions to the computer + log.Println("Attaching Parallels Tools ISO onto IDE controller...") + command := []string{ + "set", vmName, + "--device-add", "cdrom", + "--image", s.ParallelsToolsHostPath, + } + if err := driver.Prlctl(command...); err != nil { + err := fmt.Errorf("Error attaching Parallels Tools: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Set some state so we know to remove + state.Put("attachedToolsIso", true) + + return multistep.ActionContinue +} + +func (s *StepAttachParallelsTools) Cleanup(state multistep.StateBag) {} diff --git a/builder/parallels/common/step_output_dir.go b/builder/parallels/common/step_output_dir.go new file mode 100644 index 000000000..0c7c04d15 --- /dev/null +++ b/builder/parallels/common/step_output_dir.go @@ -0,0 +1,73 @@ +package common + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// StepOutputDir sets up the output directory by creating it if it does +// not exist, deleting it if it does exist and we're forcing, and cleaning +// it up when we're done with it. +type StepOutputDir struct { + Force bool + Path string + success bool +} + +func (s *StepOutputDir) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if _, err := os.Stat(s.Path); err == nil && s.Force { + ui.Say("Deleting previous output directory...") + os.RemoveAll(s.Path) + } + + // Create the directory + if err := os.MkdirAll(s.Path, 0755); err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + // Make sure we can write in the directory + f, err := os.Create(filepath.Join(s.Path, "_packer_perm_check")) + if err != nil { + err = fmt.Errorf("Couldn't write to output directory: %s", err) + state.Put("error", err) + return multistep.ActionHalt + } + f.Close() + os.Remove(f.Name()) + + s.success = true + return multistep.ActionContinue +} + +func (s *StepOutputDir) Cleanup(state multistep.StateBag) { + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if !s.success { + return + } + + if cancelled || halted { + ui := state.Get("ui").(packer.Ui) + + ui.Say("Deleting output directory...") + for i := 0; i < 5; i++ { + err := os.RemoveAll(s.Path) + if err == nil { + break + } + + log.Printf("Error removing output dir: %s", err) + time.Sleep(2 * time.Second) + } + } +} diff --git a/builder/parallels/common/step_output_dir_test.go b/builder/parallels/common/step_output_dir_test.go new file mode 100644 index 000000000..be485c278 --- /dev/null +++ b/builder/parallels/common/step_output_dir_test.go @@ -0,0 +1,96 @@ +package common + +import ( + "github.com/mitchellh/multistep" + "io/ioutil" + "os" + "testing" +) + +func testStepOutputDir(t *testing.T) *StepOutputDir { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + if err := os.RemoveAll(td); err != nil { + t.Fatalf("err: %s", err) + } + + return &StepOutputDir{Force: false, Path: td} +} + +func TestStepOutputDir_impl(t *testing.T) { + var _ multistep.Step = new(StepOutputDir) +} + +func TestStepOutputDir(t *testing.T) { + state := testState(t) + step := testStepOutputDir(t) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + if _, err := os.Stat(step.Path); err != nil { + t.Fatalf("err: %s", err) + } + + // Test the cleanup + step.Cleanup(state) + if _, err := os.Stat(step.Path); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestStepOutputDir_cancelled(t *testing.T) { + state := testState(t) + step := testStepOutputDir(t) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + if _, err := os.Stat(step.Path); err != nil { + t.Fatalf("err: %s", err) + } + + // Mark + state.Put(multistep.StateCancelled, true) + + // Test the cleanup + step.Cleanup(state) + if _, err := os.Stat(step.Path); err == nil { + t.Fatal("should not exist") + } +} + +func TestStepOutputDir_halted(t *testing.T) { + state := testState(t) + step := testStepOutputDir(t) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + if _, err := os.Stat(step.Path); err != nil { + t.Fatalf("err: %s", err) + } + + // Mark + state.Put(multistep.StateHalted, true) + + // Test the cleanup + step.Cleanup(state) + if _, err := os.Stat(step.Path); err == nil { + t.Fatal("should not exist") + } +} diff --git a/builder/parallels/common/step_prlctl.go b/builder/parallels/common/step_prlctl.go new file mode 100644 index 000000000..9bd7c4b73 --- /dev/null +++ b/builder/parallels/common/step_prlctl.go @@ -0,0 +1,68 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "strings" +) + +type commandTemplate struct { + Name string +} + +// This step executes additional prlctl commands as specified by the +// template. +// +// Uses: +// driver Driver +// ui packer.Ui +// vmName string +// +// Produces: +type StepPrlctl struct { + Commands [][]string + Tpl *packer.ConfigTemplate +} + +func (s *StepPrlctl) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + if len(s.Commands) > 0 { + ui.Say("Executing custom prlctl commands...") + } + + tplData := &commandTemplate{ + Name: vmName, + } + + for _, originalCommand := range s.Commands { + command := make([]string, len(originalCommand)) + copy(command, originalCommand) + + for i, arg := range command { + var err error + command[i], err = s.Tpl.Process(arg, tplData) + if err != nil { + err := fmt.Errorf("Error preparing prlctl command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + ui.Message(fmt.Sprintf("Executing: prlctl %s", strings.Join(command, " "))) + if err := driver.Prlctl(command...); err != nil { + err := fmt.Errorf("Error executing command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + return multistep.ActionContinue +} + +func (s *StepPrlctl) Cleanup(state multistep.StateBag) {} diff --git a/builder/parallels/common/step_remove_devices.go b/builder/parallels/common/step_remove_devices.go new file mode 100644 index 000000000..4d1b29611 --- /dev/null +++ b/builder/parallels/common/step_remove_devices.go @@ -0,0 +1,63 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// This step removes any devices (floppy disks, ISOs, etc.) from the +// machine that we may have added. +// +// Uses: +// driver Driver +// ui packer.Ui +// vmName string +// +// Produces: +type StepRemoveDevices struct{} + +func (s *StepRemoveDevices) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + // Remove the attached floppy disk, if it exists + if _, ok := state.GetOk("floppy_path"); ok { + ui.Message("Removing floppy drive...") + command := []string{"set", vmName, "--device-del", "fdd0"} + if err := driver.Prlctl(command...); err != nil { + err := fmt.Errorf("Error removing floppy: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + if _, ok := state.GetOk("attachedIso"); ok { + command := []string{"set", vmName, "--device-del", "cdrom0"} + + if err := driver.Prlctl(command...); err != nil { + err := fmt.Errorf("Error detaching ISO: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + if _, ok := state.GetOk("attachedToolsIso"); ok { + command := []string{"set", vmName, "--device-del", "cdrom1"} + + if err := driver.Prlctl(command...); err != nil { + err := fmt.Errorf("Error detaching ISO: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + return multistep.ActionContinue +} + +func (s *StepRemoveDevices) Cleanup(state multistep.StateBag) { +} diff --git a/builder/parallels/common/step_remove_devices_test.go b/builder/parallels/common/step_remove_devices_test.go new file mode 100644 index 000000000..eb8638097 --- /dev/null +++ b/builder/parallels/common/step_remove_devices_test.go @@ -0,0 +1,90 @@ +package common + +import ( + "github.com/mitchellh/multistep" + "testing" +) + +func TestStepRemoveDevices_impl(t *testing.T) { + var _ multistep.Step = new(StepRemoveDevices) +} + +func TestStepRemoveDevices(t *testing.T) { + state := testState(t) + step := new(StepRemoveDevices) + + state.Put("vmName", "foo") + + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test that ISO was removed + if len(driver.PrlctlCalls) != 0 { + t.Fatalf("bad: %#v", driver.PrlctlCalls) + } +} + +func TestStepRemoveDevices_attachedIso(t *testing.T) { + state := testState(t) + step := new(StepRemoveDevices) + + state.Put("attachedIso", true) + state.Put("vmName", "foo") + + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test that ISO was removed + if len(driver.PrlctlCalls) != 1 { + t.Fatalf("bad: %#v", driver.PrlctlCalls) + } + if driver.PrlctlCalls[0][2] != "--device-del" { + t.Fatalf("bad: %#v", driver.PrlctlCalls) + } + if driver.PrlctlCalls[0][3] != "cdrom0" { + t.Fatalf("bad: %#v", driver.PrlctlCalls) + } +} + +func TestStepRemoveDevices_floppyPath(t *testing.T) { + state := testState(t) + step := new(StepRemoveDevices) + + state.Put("floppy_path", "foo") + state.Put("vmName", "foo") + + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test that both were removed + if len(driver.PrlctlCalls) != 1 { + t.Fatalf("bad: %#v", driver.PrlctlCalls) + } + if driver.PrlctlCalls[0][2] != "--device-del" { + t.Fatalf("bad: %#v", driver.PrlctlCalls) + } + if driver.PrlctlCalls[0][3] != "fdd0" { + t.Fatalf("bad: %#v", driver.PrlctlCalls) + } +} diff --git a/builder/parallels/common/step_run.go b/builder/parallels/common/step_run.go new file mode 100644 index 000000000..e9c3ab27d --- /dev/null +++ b/builder/parallels/common/step_run.go @@ -0,0 +1,80 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" +) + +// This step starts the virtual machine. +// +// Uses: +// driver Driver +// ui packer.Ui +// vmName string +// +// Produces: +type StepRun struct { + BootWait time.Duration + Headless bool + + vmName string +} + +func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + ui.Say("Starting the virtual machine...") + //guiArgument := "gui" + if s.Headless == true { + ui.Message("WARNING: The VM will be started in headless mode, as configured.\n" + + "In headless mode, errors during the boot sequence or OS setup\n" + + "won't be easily visible. Use at your own discretion.") + //guiArgument = "headless" + } + command := []string{"start", vmName} + if err := driver.Prlctl(command...); err != nil { + err := fmt.Errorf("Error starting VM: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + s.vmName = vmName + + if int64(s.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait)) + wait := time.After(s.BootWait) + WAITLOOP: + for { + select { + case <-wait: + break WAITLOOP + case <-time.After(1 * time.Second): + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return multistep.ActionHalt + } + } + } + } + + return multistep.ActionContinue +} + +func (s *StepRun) Cleanup(state multistep.StateBag) { + if s.vmName == "" { + return + } + + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + if running, _ := driver.IsRunning(s.vmName); running { + if err := driver.Prlctl("stop", s.vmName); err != nil { + ui.Error(fmt.Sprintf("Error stopping VM: %s", err)) + } + } +} diff --git a/builder/parallels/common/step_shutdown.go b/builder/parallels/common/step_shutdown.go new file mode 100644 index 000000000..0f33692ce --- /dev/null +++ b/builder/parallels/common/step_shutdown.go @@ -0,0 +1,78 @@ +package common + +import ( + "errors" + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "time" +) + +// This step shuts down the machine. It first attempts to do so gracefully, +// but ultimately forcefully shuts it down if that fails. +// +// Uses: +// communicator packer.Communicator +// driver Driver +// ui packer.Ui +// vmName string +// +// Produces: +// +type StepShutdown struct { + Command string + Timeout time.Duration +} + +func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { + comm := state.Get("communicator").(packer.Communicator) + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + if s.Command != "" { + ui.Say("Gracefully halting virtual machine...") + log.Printf("Executing shutdown command: %s", s.Command) + cmd := &packer.RemoteCmd{Command: s.Command} + if err := cmd.StartWithUi(comm, ui); err != nil { + err := fmt.Errorf("Failed to send shutdown command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Wait for the machine to actually shut down + log.Printf("Waiting max %s for shutdown to complete", s.Timeout) + shutdownTimer := time.After(s.Timeout) + for { + running, _ := driver.IsRunning(vmName) + if !running { + break + } + + select { + case <-shutdownTimer: + err := errors.New("Timeout while waiting for machine to shut down.") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + default: + time.Sleep(500 * time.Millisecond) + } + } + } else { + ui.Say("Halting the virtual machine...") + if err := driver.Stop(vmName); err != nil { + err := fmt.Errorf("Error stopping VM: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + log.Println("VM shut down.") + return multistep.ActionContinue +} + +func (s *StepShutdown) Cleanup(state multistep.StateBag) {} diff --git a/builder/parallels/common/step_shutdown_test.go b/builder/parallels/common/step_shutdown_test.go new file mode 100644 index 000000000..215eefd30 --- /dev/null +++ b/builder/parallels/common/step_shutdown_test.go @@ -0,0 +1,105 @@ +package common + +import ( + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "testing" + "time" +) + +func TestStepShutdown_impl(t *testing.T) { + var _ multistep.Step = new(StepShutdown) +} + +func TestStepShutdown_noShutdownCommand(t *testing.T) { + state := testState(t) + step := new(StepShutdown) + + comm := new(packer.MockCommunicator) + state.Put("communicator", comm) + state.Put("vmName", "foo") + + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test that Stop was just called + if driver.StopName != "foo" { + t.Fatal("should call stop") + } + if comm.StartCalled { + t.Fatal("comm start should not be called") + } +} + +func TestStepShutdown_shutdownCommand(t *testing.T) { + state := testState(t) + step := new(StepShutdown) + step.Command = "poweroff" + step.Timeout = 1 * time.Second + + comm := new(packer.MockCommunicator) + state.Put("communicator", comm) + state.Put("vmName", "foo") + + driver := state.Get("driver").(*DriverMock) + driver.IsRunningReturn = true + + go func() { + time.Sleep(10 * time.Millisecond) + driver.Lock() + defer driver.Unlock() + driver.IsRunningReturn = false + }() + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test that Stop was just called + if driver.StopName != "" { + t.Fatal("should not call stop") + } + if comm.StartCmd.Command != step.Command { + t.Fatal("comm start should be called") + } +} + +func TestStepShutdown_shutdownTimeout(t *testing.T) { + state := testState(t) + step := new(StepShutdown) + step.Command = "poweroff" + step.Timeout = 1 * time.Second + + comm := new(packer.MockCommunicator) + state.Put("communicator", comm) + state.Put("vmName", "foo") + + driver := state.Get("driver").(*DriverMock) + driver.IsRunningReturn = true + + go func() { + time.Sleep(2 * time.Second) + driver.Lock() + defer driver.Unlock() + driver.IsRunningReturn = false + }() + + // Test the run + if action := step.Run(state); action != multistep.ActionHalt { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); !ok { + t.Fatal("should have error") + } +} diff --git a/builder/parallels/common/step_test.go b/builder/parallels/common/step_test.go new file mode 100644 index 000000000..9bf6d5d67 --- /dev/null +++ b/builder/parallels/common/step_test.go @@ -0,0 +1,18 @@ +package common + +import ( + "bytes" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "testing" +) + +func testState(t *testing.T) multistep.StateBag { + state := new(multistep.BasicStateBag) + state.Put("driver", new(DriverMock)) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + return state +} diff --git a/builder/parallels/common/step_upload_parallels_tools.go b/builder/parallels/common/step_upload_parallels_tools.go new file mode 100644 index 000000000..374a8163e --- /dev/null +++ b/builder/parallels/common/step_upload_parallels_tools.go @@ -0,0 +1,67 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "os" +) + +type toolsPathTemplate struct { + Version string +} + +// This step uploads the guest additions ISO to the VM. +type StepUploadParallelsTools struct { + ParallelsToolsHostPath string + ParallelsToolsGuestPath string + ParallelsToolsMode string + Tpl *packer.ConfigTemplate +} + +func (s *StepUploadParallelsTools) Run(state multistep.StateBag) multistep.StepAction { + comm := state.Get("communicator").(packer.Communicator) + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + // If we're attaching then don't do this, since we attached. + if s.ParallelsToolsMode != ParallelsToolsModeUpload { + log.Println("Not uploading Parallels Tools since mode is not upload") + return multistep.ActionContinue + } + + version, err := driver.Version() + if err != nil { + state.Put("error", fmt.Errorf("Error reading version for Parallels Tools upload: %s", err)) + return multistep.ActionHalt + } + + f, err := os.Open(s.ParallelsToolsHostPath) + if err != nil { + state.Put("error", fmt.Errorf("Error opening Parallels Tools ISO: %s", err)) + return multistep.ActionHalt + } + + tplData := &toolsPathTemplate{ + Version: version, + } + + s.ParallelsToolsGuestPath, err = s.Tpl.Process(s.ParallelsToolsGuestPath, tplData) + if err != nil { + err := fmt.Errorf("Error preparing Parallels Tools path: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("Uploading Parallels Tools ISO...") + if err := comm.Upload(s.ParallelsToolsGuestPath, f); err != nil { + state.Put("error", fmt.Errorf("Error uploading Parallels Tools: %s", err)) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepUploadParallelsTools) Cleanup(state multistep.StateBag) {} diff --git a/builder/parallels/common/step_upload_version.go b/builder/parallels/common/step_upload_version.go new file mode 100644 index 000000000..f5e03c4f5 --- /dev/null +++ b/builder/parallels/common/step_upload_version.go @@ -0,0 +1,44 @@ +package common + +import ( + "bytes" + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +// This step uploads a file containing the Parallels version, which +// can be useful for various provisioning reasons. +type StepUploadVersion struct { + Path string +} + +func (s *StepUploadVersion) Run(state multistep.StateBag) multistep.StepAction { + comm := state.Get("communicator").(packer.Communicator) + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + if s.Path == "" { + log.Println("ParallelsVersionFile is empty. Not uploading.") + return multistep.ActionContinue + } + + version, err := driver.Version() + if err != nil { + state.Put("error", fmt.Errorf("Error reading version for metadata upload: %s", err)) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Uploading Parallels version info (%s)", version)) + var data bytes.Buffer + data.WriteString(version) + if err := comm.Upload(s.Path, &data); err != nil { + state.Put("error", fmt.Errorf("Error uploading Parallels version: %s", err)) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepUploadVersion) Cleanup(state multistep.StateBag) {} diff --git a/builder/parallels/common/step_upload_version_test.go b/builder/parallels/common/step_upload_version_test.go new file mode 100644 index 000000000..234c4b5df --- /dev/null +++ b/builder/parallels/common/step_upload_version_test.go @@ -0,0 +1,61 @@ +package common + +import ( + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "testing" +) + +func TestStepUploadVersion_impl(t *testing.T) { + var _ multistep.Step = new(StepUploadVersion) +} + +func TestStepUploadVersion(t *testing.T) { + state := testState(t) + step := new(StepUploadVersion) + step.Path = "foopath" + + comm := new(packer.MockCommunicator) + state.Put("communicator", comm) + + driver := state.Get("driver").(*DriverMock) + driver.VersionResult = "foo" + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Verify + if comm.UploadPath != "foopath" { + t.Fatalf("bad: %#v", comm.UploadPath) + } + if comm.UploadData != "foo" { + t.Fatalf("upload data bad: %#v", comm.UploadData) + } +} + +func TestStepUploadVersion_noPath(t *testing.T) { + state := testState(t) + step := new(StepUploadVersion) + step.Path = "" + + comm := new(packer.MockCommunicator) + state.Put("communicator", comm) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Verify + if comm.UploadCalled { + t.Fatal("bad") + } +} diff --git a/builder/parallels/common/tools_modes.go b/builder/parallels/common/tools_modes.go new file mode 100644 index 000000000..3523fbca7 --- /dev/null +++ b/builder/parallels/common/tools_modes.go @@ -0,0 +1,9 @@ +package common + +// These are the different valid mode values for "parallels_tools_mode" which +// determine how guest additions are delivered to the guest. +const ( + ParallelsToolsModeDisable string = "disable" + ParallelsToolsModeAttach = "attach" + ParallelsToolsModeUpload = "upload" +) diff --git a/builder/parallels/iso/builder.go b/builder/parallels/iso/builder.go new file mode 100644 index 000000000..12cb33d0a --- /dev/null +++ b/builder/parallels/iso/builder.go @@ -0,0 +1,360 @@ +package iso + +import ( + "errors" + "fmt" + "github.com/mitchellh/multistep" + parallelscommon "github.com/mitchellh/packer/builder/parallels/common" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "log" + "strings" +) + +const BuilderId = "rickard-von-essen.parallels" + +type Builder struct { + config config + runner multistep.Runner +} + +type config struct { + common.PackerConfig `mapstructure:",squash"` + parallelscommon.FloppyConfig `mapstructure:",squash"` + parallelscommon.OutputConfig `mapstructure:",squash"` + parallelscommon.RunConfig `mapstructure:",squash"` + parallelscommon.ShutdownConfig `mapstructure:",squash"` + parallelscommon.SSHConfig `mapstructure:",squash"` + parallelscommon.PrlctlConfig `mapstructure:",squash"` + parallelscommon.PrlctlVersionConfig `mapstructure:",squash"` + + BootCommand []string `mapstructure:"boot_command"` + DiskSize uint `mapstructure:"disk_size"` + ParallelsToolsMode string `mapstructure:"parallels_tools_mode"` + ParallelsToolsGuestPath string `mapstructure:"parallels_tools_guest_path"` + ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"` + GuestOSType string `mapstructure:"guest_os_type"` + GuestOSDistribution string `mapstructure:"guest_os_distribution"` + HardDriveInterface string `mapstructure:"hard_drive_interface"` + HTTPDir string `mapstructure:"http_directory"` + HTTPPortMin uint `mapstructure:"http_port_min"` + HTTPPortMax uint `mapstructure:"http_port_max"` + ISOChecksum string `mapstructure:"iso_checksum"` + ISOChecksumType string `mapstructure:"iso_checksum_type"` + ISOUrls []string `mapstructure:"iso_urls"` + VMName string `mapstructure:"vm_name"` + + RawSingleISOUrl string `mapstructure:"iso_url"` + + tpl *packer.ConfigTemplate +} + +func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { + + md, err := common.DecodeConfig(&b.config, raws...) + if err != nil { + return nil, err + } + + b.config.tpl, err = packer.NewConfigTemplate() + if err != nil { + return nil, err + } + b.config.tpl.UserVars = b.config.PackerUserVars + + // Accumulate any errors and warnings + errs := common.CheckUnusedConfig(md) + errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend( + errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.PrlctlConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.PrlctlVersionConfig.Prepare(b.config.tpl)...) + warnings := make([]string, 0) + + if b.config.DiskSize == 0 { + b.config.DiskSize = 40000 + } + + if b.config.ParallelsToolsMode == "" { + b.config.ParallelsToolsMode = "upload" + } + + if b.config.ParallelsToolsGuestPath == "" { + b.config.ParallelsToolsGuestPath = "prl-tools.iso" + } + + if b.config.ParallelsToolsHostPath == "" { + b.config.ParallelsToolsHostPath = "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso" + } + + if b.config.HardDriveInterface == "" { + b.config.HardDriveInterface = "sata" + } + + if b.config.GuestOSType == "" { + b.config.GuestOSType = "other" + } + + if b.config.GuestOSDistribution == "" { + b.config.GuestOSDistribution = "other" + } + + if b.config.HTTPPortMin == 0 { + b.config.HTTPPortMin = 8000 + } + + if b.config.HTTPPortMax == 0 { + b.config.HTTPPortMax = 9000 + } + + if b.config.VMName == "" { + b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) + } + + // Errors + templates := map[string]*string{ + "parallels_tools_mode": &b.config.ParallelsToolsMode, + "parallels_tools_host_path": &b.config.ParallelsToolsHostPath, + "parallels_tools_guest_path": &b.config.ParallelsToolsGuestPath, + "guest_os_type": &b.config.GuestOSType, + "guest_os_distribution": &b.config.GuestOSDistribution, + "hard_drive_interface": &b.config.HardDriveInterface, + "http_directory": &b.config.HTTPDir, + "iso_checksum": &b.config.ISOChecksum, + "iso_checksum_type": &b.config.ISOChecksumType, + "iso_url": &b.config.RawSingleISOUrl, + "vm_name": &b.config.VMName, + } + + for n, ptr := range templates { + var err error + *ptr, err = b.config.tpl.Process(*ptr, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + for i, url := range b.config.ISOUrls { + var err error + b.config.ISOUrls[i], err = b.config.tpl.Process(url, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing iso_urls[%d]: %s", i, err)) + } + } + + validates := map[string]*string{ + "parallels_tools_guest_path": &b.config.ParallelsToolsGuestPath, + } + + for n, ptr := range validates { + if err := b.config.tpl.Validate(*ptr); err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error parsing %s: %s", n, err)) + } + } + + for i, command := range b.config.BootCommand { + if err := b.config.tpl.Validate(command); err != nil { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("Error processing boot_command[%d]: %s", i, err)) + } + } + + if b.config.HardDriveInterface != "ide" && b.config.HardDriveInterface != "sata" && b.config.HardDriveInterface != "scsi" { + errs = packer.MultiErrorAppend( + errs, errors.New("hard_drive_interface can only be ide, sata, or scsi")) + } + + if b.config.HTTPPortMin > b.config.HTTPPortMax { + errs = packer.MultiErrorAppend( + errs, errors.New("http_port_min must be less than http_port_max")) + } + + if b.config.ISOChecksumType == "" { + errs = packer.MultiErrorAppend( + errs, errors.New("The iso_checksum_type must be specified.")) + } else { + b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType) + if b.config.ISOChecksumType != "none" { + if b.config.ISOChecksum == "" { + errs = packer.MultiErrorAppend( + errs, errors.New("Due to large file sizes, an iso_checksum is required")) + } else { + b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum) + } + + if h := common.HashForType(b.config.ISOChecksumType); h == nil { + errs = packer.MultiErrorAppend( + errs, + fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType)) + } + } + } + + if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 { + errs = packer.MultiErrorAppend( + errs, errors.New("One of iso_url or iso_urls must be specified.")) + } else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 { + errs = packer.MultiErrorAppend( + errs, errors.New("Only one of iso_url or iso_urls may be specified.")) + } else if b.config.RawSingleISOUrl != "" { + b.config.ISOUrls = []string{b.config.RawSingleISOUrl} + } + + for i, url := range b.config.ISOUrls { + b.config.ISOUrls[i], err = common.DownloadableURL(url) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err)) + } + } + + validMode := false + validModes := []string{ + parallelscommon.ParallelsToolsModeDisable, + parallelscommon.ParallelsToolsModeAttach, + parallelscommon.ParallelsToolsModeUpload, + } + + for _, mode := range validModes { + if b.config.ParallelsToolsMode == mode { + validMode = true + break + } + } + + if !validMode { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("parallels_tools_mode is invalid. Must be one of: %v", validModes)) + } + + // Warnings + if b.config.ISOChecksumType == "none" { + warnings = append(warnings, + "A checksum type of 'none' was specified. Since ISO files are so big,\n"+ + "a checksum is highly recommended.") + } + + if b.config.ShutdownCommand == "" { + warnings = append(warnings, + "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ + "will forcibly halt the virtual machine, which may result in data loss.") + } + + if errs != nil && len(errs.Errors) > 0 { + return warnings, errs + } + + return warnings, nil +} + +func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { + // Create the driver that we'll use to communicate with Parallels + driver, err := parallelscommon.NewDriver() + if err != nil { + return nil, fmt.Errorf("Failed creating Parallels driver: %s", err) + } + + steps := []multistep.Step{ + &common.StepDownload{ + Checksum: b.config.ISOChecksum, + ChecksumType: b.config.ISOChecksumType, + Description: "ISO", + ResultKey: "iso_path", + Url: b.config.ISOUrls, + }, + ¶llelscommon.StepOutputDir{ + Force: b.config.PackerForce, + Path: b.config.OutputDir, + }, + &common.StepCreateFloppy{ + Files: b.config.FloppyFiles, + }, + new(stepHTTPServer), + new(stepCreateVM), + new(stepCreateDisk), + new(stepAttachISO), + ¶llelscommon.StepAttachParallelsTools{ + ParallelsToolsHostPath: b.config.ParallelsToolsHostPath, + ParallelsToolsMode: b.config.ParallelsToolsMode, + }, + new(parallelscommon.StepAttachFloppy), + ¶llelscommon.StepPrlctl{ + Commands: b.config.Prlctl, + Tpl: b.config.tpl, + }, + ¶llelscommon.StepRun{ + BootWait: b.config.BootWait, + Headless: b.config.Headless, // TODO: migth work on Enterprise Ed. + }, + new(stepTypeBootCommand), + &common.StepConnectSSH{ + SSHAddress: parallelscommon.SSHAddress, + SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig), + SSHWaitTimeout: b.config.SSHWaitTimeout, + }, + ¶llelscommon.StepUploadVersion{ + Path: b.config.PrlctlVersionFile, + }, + ¶llelscommon.StepUploadParallelsTools{ + ParallelsToolsGuestPath: b.config.ParallelsToolsGuestPath, + ParallelsToolsHostPath: b.config.ParallelsToolsHostPath, + ParallelsToolsMode: b.config.ParallelsToolsMode, + Tpl: b.config.tpl, + }, + new(common.StepProvision), + ¶llelscommon.StepShutdown{ + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + }, + new(parallelscommon.StepRemoveDevices), + } + + // Setup the state bag + state := new(multistep.BasicStateBag) + state.Put("cache", cache) + state.Put("config", &b.config) + state.Put("driver", driver) + state.Put("hook", hook) + state.Put("ui", ui) + + // Run + if b.config.PackerDebug { + b.runner = &multistep.DebugRunner{ + Steps: steps, + PauseFn: common.MultistepDebugFn(ui), + } + } else { + b.runner = &multistep.BasicRunner{Steps: steps} + } + + b.runner.Run(state) + + // If there was an error, return that + if rawErr, ok := state.GetOk("error"); ok { + return nil, rawErr.(error) + } + + // If we were interrupted or cancelled, then just exit. + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return nil, errors.New("Build was cancelled.") + } + + if _, ok := state.GetOk(multistep.StateHalted); ok { + return nil, errors.New("Build was halted.") + } + + return parallelscommon.NewArtifact(b.config.OutputDir) +} + +func (b *Builder) Cancel() { + if b.runner != nil { + log.Println("Cancelling the step runner...") + b.runner.Cancel() + } +} diff --git a/builder/parallels/iso/builder_test.go b/builder/parallels/iso/builder_test.go new file mode 100644 index 000000000..ba9cecdd8 --- /dev/null +++ b/builder/parallels/iso/builder_test.go @@ -0,0 +1,436 @@ +package iso + +import ( + "github.com/mitchellh/packer/builder/parallels/common" + "github.com/mitchellh/packer/packer" + "reflect" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + "iso_checksum": "foo", + "iso_checksum_type": "md5", + "iso_url": "http://www.google.com/", + "shutdown_command": "yes", + "ssh_username": "foo", + + packer.BuildNameConfigKey: "foo", + } +} + +func TestBuilder_ImplementsBuilder(t *testing.T) { + var raw interface{} + raw = &Builder{} + if _, ok := raw.(packer.Builder); !ok { + t.Error("Builder must implement builder.") + } +} + +func TestBuilderPrepare_Defaults(t *testing.T) { + var b Builder + config := testConfig() + 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) + } + + if b.config.ParallelsToolsMode != common.ParallelsToolsModeUpload { + t.Errorf("bad parallels tools mode: %s", b.config.ParallelsToolsMode) + } + + if b.config.GuestOSType != "other" { + t.Errorf("bad guest OS type: %s", b.config.GuestOSType) + } + + if b.config.VMName != "packer-foo" { + t.Errorf("bad vm name: %s", b.config.VMName) + } +} + +func TestBuilderPrepare_DiskSize(t *testing.T) { + var b Builder + config := testConfig() + + delete(config, "disk_size") + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("bad err: %s", err) + } + + if b.config.DiskSize != 40000 { + t.Fatalf("bad size: %d", b.config.DiskSize) + } + + config["disk_size"] = 60000 + 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) + } + + if b.config.DiskSize != 60000 { + t.Fatalf("bad size: %s", b.config.DiskSize) + } +} + +func TestBuilderPrepare_ParallelsToolsMode(t *testing.T) { + var b Builder + config := testConfig() + + // test default mode + delete(config, "parallels_tools_mode") + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("bad err: %s", err) + } + + // Test another mode + config["parallels_tools_mode"] = "attach" + 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) + } + + if b.config.ParallelsToolsMode != common.ParallelsToolsModeAttach { + t.Fatalf("bad: %s", b.config.ParallelsToolsMode) + } + + // Test bad mode + config["parllels_tools_mode"] = "teleport" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should error") + } +} + +func TestBuilderPrepare_ParallelsToolsGuestPath(t *testing.T) { + var b Builder + config := testConfig() + + delete(config, "parallesl_tools_guest_path") + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("bad err: %s", err) + } + + if b.config.ParallelsToolsGuestPath != "prl-tools.iso" { + t.Fatalf("bad: %s", b.config.ParallelsToolsGuestPath) + } + + config["parallels_tools_guest_path"] = "foo" + 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) + } + + if b.config.ParallelsToolsGuestPath != "foo" { + t.Fatalf("bad size: %s", b.config.ParallelsToolsGuestPath) + } +} + +func TestBuilderPrepare_ParallelsToolsHostPath(t *testing.T) { + var b Builder + config := testConfig() + + config["parallels_tools_host_path"] = "" + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("err: %s", err) + } + + if b.config.ParallelsToolsHostPath != "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso" { + t.Fatalf("bad: %s", b.config.ParallelsToolsHostPath) + } + + config["parallels_tools_host_path"] = "./prl-tools-lin.iso" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Errorf("should not have error: %s", err) + } +} + +func TestBuilderPrepare_HardDriveInterface(t *testing.T) { + var b Builder + config := testConfig() + + // Test a default boot_wait + delete(config, "hard_drive_interface") + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("err: %s", err) + } + + if b.config.HardDriveInterface != "sata" { + t.Fatalf("bad: %s", b.config.HardDriveInterface) + } + + // Test with a bad + config["hard_drive_interface"] = "fake" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test with a good + config["hard_drive_interface"] = "scsi" + 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_HTTPPort(t *testing.T) { + var b Builder + config := testConfig() + + // Bad + config["http_port_min"] = 1000 + config["http_port_max"] = 500 + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Bad + config["http_port_min"] = -500 + 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 + config["http_port_min"] = 500 + config["http_port_max"] = 1000 + 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_InvalidKey(t *testing.T) { + var b Builder + config := testConfig() + + // Add a random key + config["i_should_not_be_valid"] = true + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } +} + +func TestBuilderPrepare_ISOChecksum(t *testing.T) { + var b Builder + config := testConfig() + + // Test bad + config["iso_checksum"] = "" + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test good + config["iso_checksum"] = "FOo" + 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) + } + + if b.config.ISOChecksum != "foo" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksum) + } +} + +func TestBuilderPrepare_ISOChecksumType(t *testing.T) { + var b Builder + config := testConfig() + + // Test bad + config["iso_checksum_type"] = "" + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test good + config["iso_checksum_type"] = "mD5" + 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) + } + + if b.config.ISOChecksumType != "md5" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType) + } + + // Test unknown + config["iso_checksum_type"] = "fake" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test none + config["iso_checksum_type"] = "none" + 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) + } + + if b.config.ISOChecksumType != "none" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType) + } +} + +func TestBuilderPrepare_ISOUrl(t *testing.T) { + var b Builder + config := testConfig() + delete(config, "iso_url") + delete(config, "iso_urls") + + // Test both epty + config["iso_url"] = "" + b = Builder{} + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test iso_url set + config["iso_url"] = "http://www.packer.io" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Errorf("should not have error: %s", err) + } + + expected := []string{"http://www.packer.io"} + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } + + // Test both set + config["iso_url"] = "http://www.packer.io" + config["iso_urls"] = []string{"http://www.packer.io"} + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test just iso_urls set + delete(config, "iso_url") + config["iso_urls"] = []string{ + "http://www.packer.io", + "http://www.hashicorp.com", + } + + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Errorf("should not have error: %s", err) + } + + expected = []string{ + "http://www.packer.io", + "http://www.hashicorp.com", + } + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } +} diff --git a/builder/parallels/iso/host_ip.go b/builder/parallels/iso/host_ip.go new file mode 100644 index 000000000..98a6d26df --- /dev/null +++ b/builder/parallels/iso/host_ip.go @@ -0,0 +1,7 @@ +package iso + +// Interface to help find the host IP that is available from within +// the VMware virtual machines. +type HostIPFinder interface { + HostIP() (string, error) +} diff --git a/builder/parallels/iso/host_ip_ifconfig.go b/builder/parallels/iso/host_ip_ifconfig.go new file mode 100644 index 000000000..bb756cec3 --- /dev/null +++ b/builder/parallels/iso/host_ip_ifconfig.go @@ -0,0 +1,55 @@ +package iso + +import ( + "bytes" + "errors" + "os" + "os/exec" + "regexp" +) + +// IfconfigIPFinder finds the host IP based on the output of `ifconfig`. +type IfconfigIPFinder struct { + Devices []string +} + +func (f *IfconfigIPFinder) HostIP() (string, error) { + var ifconfigPath string + + // On some systems, ifconfig is in /sbin which is generally not + // on the PATH for a standard user, so we just check that first. + if _, err := os.Stat("/sbin/ifconfig"); err == nil { + ifconfigPath = "/sbin/ifconfig" + } + + if ifconfigPath == "" { + var err error + ifconfigPath, err = exec.LookPath("ifconfig") + if err != nil { + return "", err + } + } + + for _, device := range f.Devices { + stdout := new(bytes.Buffer) + + cmd := exec.Command(ifconfigPath, device) + cmd.Env = append(cmd.Env, os.Environ()...) + + // Force LANG=C so that the output is what we expect it to be + // despite the locale. + cmd.Env = append(cmd.Env, "LANG=C") + + cmd.Stdout = stdout + cmd.Stderr = new(bytes.Buffer) + + if err := cmd.Run(); err == nil { + re := regexp.MustCompile(`inet\s+(?:addr:)?(.+?)\s`) + matches := re.FindStringSubmatch(stdout.String()) + if matches != nil { + return matches[1], nil + } + } + } + return "", errors.New("IP not found in ifconfig output...") +} diff --git a/builder/parallels/iso/host_ip_ifconfig_test.go b/builder/parallels/iso/host_ip_ifconfig_test.go new file mode 100644 index 000000000..51ccc272c --- /dev/null +++ b/builder/parallels/iso/host_ip_ifconfig_test.go @@ -0,0 +1,11 @@ +package iso + +import "testing" + +func TestIfconfigIPFinder_Impl(t *testing.T) { + var raw interface{} + raw = &IfconfigIPFinder{} + if _, ok := raw.(HostIPFinder); !ok { + t.Fatalf("IfconfigIPFinder is not a host IP finder") + } +} diff --git a/builder/parallels/iso/step_attach_iso.go b/builder/parallels/iso/step_attach_iso.go new file mode 100644 index 000000000..c18143409 --- /dev/null +++ b/builder/parallels/iso/step_attach_iso.go @@ -0,0 +1,45 @@ +package iso + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + parallelscommon "github.com/mitchellh/packer/builder/parallels/common" +) + +// This step attaches the ISO to the virtual machine. +// +// Uses: +// +// Produces: +type stepAttachISO struct { + diskPath string +} + +func (s *stepAttachISO) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(parallelscommon.Driver) + isoPath := state.Get("iso_path").(string) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + // Attach the disk to the controller + command := []string{ + "set", vmName, + "--device-set", "cdrom0", + "--image", isoPath, + "--enable", "--connect", + } + if err := driver.Prlctl(command...); err != nil { + err := fmt.Errorf("Error attaching ISO: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Set some state so we know to remove + state.Put("attachedIso", true) + + return multistep.ActionContinue +} + +func (s *stepAttachISO) Cleanup(state multistep.StateBag) {} diff --git a/builder/parallels/iso/step_create_disk.go b/builder/parallels/iso/step_create_disk.go new file mode 100644 index 000000000..7571abce3 --- /dev/null +++ b/builder/parallels/iso/step_create_disk.go @@ -0,0 +1,40 @@ +package iso + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + parallelscommon "github.com/mitchellh/packer/builder/parallels/common" + "strconv" +) + +// This step creates the virtual disk that will be used as the +// hard drive for the virtual machine. +type stepCreateDisk struct{} + +func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*config) + driver := state.Get("driver").(parallelscommon.Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + command := []string{ + "set", vmName, + "--device-set", "hdd0", + "--size", strconv.FormatUint(uint64(config.DiskSize), 10), + "--iface", config.HardDriveInterface, + } + + ui.Say("Creating hard drive...") + err := driver.Prlctl(command...) + if err != nil { + err := fmt.Errorf("Error creating hard drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepCreateDisk) Cleanup(state multistep.StateBag) {} diff --git a/builder/parallels/iso/step_create_vm.go b/builder/parallels/iso/step_create_vm.go new file mode 100644 index 000000000..3b8824302 --- /dev/null +++ b/builder/parallels/iso/step_create_vm.go @@ -0,0 +1,79 @@ +package iso + +import ( + "fmt" + "github.com/mitchellh/multistep" + parallelscommon "github.com/mitchellh/packer/builder/parallels/common" + "github.com/mitchellh/packer/packer" + "path/filepath" +) + +// This step creates the actual virtual machine. +// +// Produces: +// vmName string - The name of the VM +type stepCreateVM struct { + vmName string +} + +func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction { + + config := state.Get("config").(*config) + driver := state.Get("driver").(parallelscommon.Driver) + ui := state.Get("ui").(packer.Ui) + + name := config.VMName + path := filepath.Join(".", config.OutputDir) + + commands := make([][]string, 9) + commands[0] = []string{ + "create", name, + "--ostype", config.GuestOSType, + "--distribution", config.GuestOSDistribution, + "--dst", path, + "--vmtype", "vm", + } + commands[1] = []string{"set", name, "--cpus", "1"} + commands[2] = []string{"set", name, "--memsize", "512"} + commands[3] = []string{"set", name, "--startup-view", "same"} + commands[4] = []string{"set", name, "--on-shutdown", "close"} + commands[5] = []string{"set", name, "--on-window-close", "keep-running"} + commands[6] = []string{"set", name, "--auto-share-camera", "off"} + commands[7] = []string{"set", name, "--device-del", "sound0"} + commands[8] = []string{"set", name, "--smart-guard", "off"} + + ui.Say("Creating virtual machine...") + for _, command := range commands { + err := driver.Prlctl(command...) + ui.Say(fmt.Sprintf("Executing: prlctl %s", command)) + if err != nil { + err := fmt.Errorf("Error creating VM: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Set the VM name property on the first command + if s.vmName == "" { + s.vmName = name + } + } + + // Set the final name in the state bag so others can use it + state.Put("vmName", s.vmName) + return multistep.ActionContinue +} + +func (s *stepCreateVM) Cleanup(state multistep.StateBag) { + if s.vmName == "" { + return + } + + driver := state.Get("driver").(parallelscommon.Driver) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Unregistering virtual machine...") + if err := driver.Prlctl("unregister", s.vmName); err != nil { + ui.Error(fmt.Sprintf("Error unregistering virtual machine: %s", err)) + } +} diff --git a/builder/parallels/iso/step_http_server.go b/builder/parallels/iso/step_http_server.go new file mode 100644 index 000000000..24da8dd9d --- /dev/null +++ b/builder/parallels/iso/step_http_server.go @@ -0,0 +1,75 @@ +package iso + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "math/rand" + "net" + "net/http" +) + +// This step creates and runs the HTTP server that is serving the files +// specified by the 'http_files` configuration parameter in the template. +// +// Uses: +// config *config +// ui packer.Ui +// +// Produces: +// http_port int - The port the HTTP server started on. +type stepHTTPServer struct { + l net.Listener +} + +func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*config) + ui := state.Get("ui").(packer.Ui) + + var httpPort uint = 0 + if config.HTTPDir == "" { + state.Put("http_port", httpPort) + return multistep.ActionContinue + } + + // Find an available TCP port for our HTTP server + var httpAddr string + portRange := int(config.HTTPPortMax - config.HTTPPortMin) + for { + var err error + var offset uint = 0 + + if portRange > 0 { + // Intn will panic if portRange == 0, so we do a check. + offset = uint(rand.Intn(portRange)) + } + + httpPort = offset + config.HTTPPortMin + httpAddr = fmt.Sprintf(":%d", httpPort) + log.Printf("Trying port: %d", httpPort) + s.l, err = net.Listen("tcp", httpAddr) + if err == nil { + break + } + } + + ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort)) + + // Start the HTTP server and run it in the background + fileServer := http.FileServer(http.Dir(config.HTTPDir)) + server := &http.Server{Addr: httpAddr, Handler: fileServer} + go server.Serve(s.l) + + // Save the address into the state so it can be accessed in the future + state.Put("http_port", httpPort) + + return multistep.ActionContinue +} + +func (s *stepHTTPServer) Cleanup(multistep.StateBag) { + if s.l != nil { + // Close the listener so that the HTTP server stops + s.l.Close() + } +} diff --git a/builder/parallels/iso/step_type_boot_command.go b/builder/parallels/iso/step_type_boot_command.go new file mode 100644 index 000000000..342fa91fb --- /dev/null +++ b/builder/parallels/iso/step_type_boot_command.go @@ -0,0 +1,239 @@ +package iso + +import ( + "fmt" + "github.com/mitchellh/multistep" + parallelscommon "github.com/mitchellh/packer/builder/parallels/common" + "github.com/mitchellh/packer/packer" + "log" + "strings" + "time" + "unicode" + "unicode/utf8" +) + +const KeyLeftShift uint32 = 0xFFE1 + +type bootCommandTemplateData struct { + HTTPIP string + HTTPPort uint + Name string +} + +// This step "types" the boot command into the VM via prltype, built on the +// Parallels Virtualization SDK - C API. +// +// Uses: +// config *config +// driver Driver +// http_port int +// ui packer.Ui +// vmName string +// +// Produces: +// +type stepTypeBootCommand struct{} + +func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*config) + httpPort := state.Get("http_port").(uint) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + driver := state.Get("driver").(parallelscommon.Driver) + + // Determine the host IP + ipFinder := &IfconfigIPFinder{Devices: []string{"en0", "en1", "en2", "en3", "en4", "en5", "en6", "en7", "en8", "en9"}} + + hostIp, err := ipFinder.HostIP() + if err != nil { + err := fmt.Errorf("Error detecting host IP: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Host IP for the Parallels machine: %s", hostIp)) + + tplData := &bootCommandTemplateData{ + hostIp, + httpPort, + config.VMName, + } + + ui.Say("Typing the boot command...") + for _, command := range config.BootCommand { + command, err := config.tpl.Process(command, tplData) + if err != nil { + err := fmt.Errorf("Error preparing boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + codes := []string{} + for _, code := range scancodes(command) { + if code == "wait" { + if err := driver.SendKeyScanCodes(vmName, codes...); err != nil { + err := fmt.Errorf("Error sending boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + codes = []string{} + time.Sleep(1 * time.Second) + continue + } + + if code == "wait5" { + if err := driver.SendKeyScanCodes(vmName, codes...); err != nil { + err := fmt.Errorf("Error sending boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + codes = []string{} + time.Sleep(5 * time.Second) + continue + } + + if code == "wait10" { + if err := driver.SendKeyScanCodes(vmName, codes...); err != nil { + err := fmt.Errorf("Error sending boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + codes = []string{} + time.Sleep(10 * time.Second) + continue + } + + // Since typing is sometimes so slow, we check for an interrupt + // in between each character. + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return multistep.ActionHalt + } + codes = append(codes, code) + } + log.Printf("Sending scancodes: %#v", codes) + if err := driver.SendKeyScanCodes(vmName, codes...); err != nil { + err := fmt.Errorf("Error sending boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + return multistep.ActionContinue +} + +func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {} + +func scancodes(message string) []string { + // Scancodes reference: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html + // + // Scancodes represent raw keyboard output and are fed to the VM by the + // Parallels Virtualization SDK - C API, PrlDevKeyboard_SendKeyEvent + // + // Scancodes are recorded here in pairs. The first entry represents + // the key press and the second entry represents the key release and is + // derived from the first by the addition of 0x80. + special := make(map[string][]string) + special[""] = []string{"0e", "8e"} + special[""] = []string{"53", "d3"} + special[""] = []string{"1c", "9c"} + special[""] = []string{"01", "81"} + special[""] = []string{"3b", "bb"} + special[""] = []string{"3c", "bc"} + special[""] = []string{"3d", "bd"} + special[""] = []string{"3e", "be"} + special[""] = []string{"3f", "bf"} + special[""] = []string{"40", "c0"} + special[""] = []string{"41", "c1"} + special[""] = []string{"42", "c2"} + special[""] = []string{"43", "c3"} + special[""] = []string{"44", "c4"} + special[""] = []string{"1c", "9c"} + special[""] = []string{"0f", "8f"} + + shiftedChars := "!@#$%^&*()_+{}:\"~|<>?" + + scancodeIndex := make(map[string]uint) + scancodeIndex["1234567890-="] = 0x02 + scancodeIndex["!@#$%^&*()_+"] = 0x02 + scancodeIndex["qwertyuiop[]"] = 0x10 + scancodeIndex["QWERTYUIOP{}"] = 0x10 + scancodeIndex["asdfghjkl;´`"] = 0x1e + scancodeIndex[`ASDFGHJKL:"~`] = 0x1e + scancodeIndex["\\zxcvbnm,./"] = 0x2b + scancodeIndex["|ZXCVBNM<>?"] = 0x2b + scancodeIndex[" "] = 0x39 + + scancodeMap := make(map[rune]uint) + for chars, start := range scancodeIndex { + var i uint = 0 + for len(chars) > 0 { + r, size := utf8.DecodeRuneInString(chars) + chars = chars[size:] + scancodeMap[r] = start + i + i += 1 + } + } + + result := make([]string, 0, len(message)*2) + for len(message) > 0 { + var scancode []string + + if strings.HasPrefix(message, "") { + log.Printf("Special code found, will sleep 1 second at this point.") + scancode = []string{"wait"} + message = message[len(""):] + } + + if strings.HasPrefix(message, "") { + log.Printf("Special code found, will sleep 5 seconds at this point.") + scancode = []string{"wait5"} + message = message[len(""):] + } + + if strings.HasPrefix(message, "") { + log.Printf("Special code found, will sleep 10 seconds at this point.") + scancode = []string{"wait10"} + message = message[len(""):] + } + + if scancode == nil { + for specialCode, specialValue := range special { + if strings.HasPrefix(message, specialCode) { + log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue) + scancode = specialValue + message = message[len(specialCode):] + break + } + } + } + + if scancode == nil { + r, size := utf8.DecodeRuneInString(message) + message = message[size:] + scancodeInt := scancodeMap[r] + keyShift := unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) + + scancode = make([]string, 0, 4) + if keyShift { + scancode = append(scancode, "2a") + } + + scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt)) + scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt+0x80)) + + if keyShift { + scancode = append(scancode, "aa") + } + } + + result = append(result, scancode...) + } + + return result +} diff --git a/builder/parallels/pvm/builder.go b/builder/parallels/pvm/builder.go new file mode 100644 index 000000000..50eedaa3b --- /dev/null +++ b/builder/parallels/pvm/builder.go @@ -0,0 +1,129 @@ +package pvm + +import ( + "errors" + "fmt" + "github.com/mitchellh/multistep" + parallelscommon "github.com/mitchellh/packer/builder/parallels/common" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "log" +) + +// Builder implements packer.Builder and builds the actual Parallels +// images. +type Builder struct { + config *Config + runner multistep.Runner +} + +// Prepare processes the build configuration parameters. +func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { + c, warnings, errs := NewConfig(raws...) + if errs != nil { + return warnings, errs + } + b.config = c + + return warnings, nil +} + +// Run executes a Packer build and returns a packer.Artifact representing +// a Parallels appliance. +func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { + // Create the driver that we'll use to communicate with Parallels + driver, err := parallelscommon.NewDriver() + if err != nil { + return nil, fmt.Errorf("Failed creating Paralles driver: %s", err) + } + + // Set up the state. + state := new(multistep.BasicStateBag) + state.Put("config", b.config) + state.Put("driver", driver) + state.Put("hook", hook) + state.Put("ui", ui) + + // Build the steps. + steps := []multistep.Step{ + ¶llelscommon.StepOutputDir{ + Force: b.config.PackerForce, + Path: b.config.OutputDir, + }, + &common.StepCreateFloppy{ + Files: b.config.FloppyFiles, + }, + &StepImport{ + Name: b.config.VMName, + SourcePath: b.config.SourcePath, + }, + ¶llelscommon.StepAttachParallelsTools{ + ParallelsToolsHostPath: b.config.ParallelsToolsHostPath, + ParallelsToolsMode: b.config.ParallelsToolsMode, + }, + new(parallelscommon.StepAttachFloppy), + ¶llelscommon.StepPrlctl{ + Commands: b.config.Prlctl, + Tpl: b.config.tpl, + }, + ¶llelscommon.StepRun{ + BootWait: b.config.BootWait, + Headless: b.config.Headless, + }, + &common.StepConnectSSH{ + SSHAddress: parallelscommon.SSHAddress, + SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig), + SSHWaitTimeout: b.config.SSHWaitTimeout, + }, + ¶llelscommon.StepUploadVersion{ + Path: b.config.PrlctlVersionFile, + }, + ¶llelscommon.StepUploadParallelsTools{ + ParallelsToolsGuestPath: b.config.ParallelsToolsGuestPath, + ParallelsToolsHostPath: b.config.ParallelsToolsHostPath, + ParallelsToolsMode: b.config.ParallelsToolsMode, + Tpl: b.config.tpl, + }, + new(common.StepProvision), + ¶llelscommon.StepShutdown{ + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + }, + new(parallelscommon.StepRemoveDevices), + } + + // Run the steps. + if b.config.PackerDebug { + b.runner = &multistep.DebugRunner{ + Steps: steps, + PauseFn: common.MultistepDebugFn(ui), + } + } else { + b.runner = &multistep.BasicRunner{Steps: steps} + } + b.runner.Run(state) + + // Report any errors. + if rawErr, ok := state.GetOk("error"); ok { + return nil, rawErr.(error) + } + + // If we were interrupted or cancelled, then just exit. + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return nil, errors.New("Build was cancelled.") + } + + if _, ok := state.GetOk(multistep.StateHalted); ok { + return nil, errors.New("Build was halted.") + } + + return parallelscommon.NewArtifact(b.config.OutputDir) +} + +// Cancel. +func (b *Builder) Cancel() { + if b.runner != nil { + log.Println("Cancelling the step runner...") + b.runner.Cancel() + } +} diff --git a/builder/parallels/pvm/config.go b/builder/parallels/pvm/config.go new file mode 100644 index 000000000..4eed5db10 --- /dev/null +++ b/builder/parallels/pvm/config.go @@ -0,0 +1,131 @@ +package pvm + +import ( + "fmt" + parallelscommon "github.com/mitchellh/packer/builder/parallels/common" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "os" +) + +// Config is the configuration structure for the builder. +type Config struct { + common.PackerConfig `mapstructure:",squash"` + parallelscommon.FloppyConfig `mapstructure:",squash"` + parallelscommon.OutputConfig `mapstructure:",squash"` + parallelscommon.RunConfig `mapstructure:",squash"` + parallelscommon.SSHConfig `mapstructure:",squash"` + parallelscommon.ShutdownConfig `mapstructure:",squash"` + parallelscommon.PrlctlConfig `mapstructure:",squash"` + parallelscommon.PrlctlVersionConfig `mapstructure:",squash"` + + ParallelsToolsMode string `mapstructure:"parallels_tools_mode"` + ParallelsToolsGuestPath string `mapstructure:"parallels_tools_guest_path"` + ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"` + + SourcePath string `mapstructure:"source_path"` + VMName string `mapstructure:"vm_name"` + + tpl *packer.ConfigTemplate +} + +func NewConfig(raws ...interface{}) (*Config, []string, error) { + c := new(Config) + md, err := common.DecodeConfig(c, raws...) + if err != nil { + return nil, nil, err + } + + c.tpl, err = packer.NewConfigTemplate() + if err != nil { + return nil, nil, err + } + c.tpl.UserVars = c.PackerUserVars + + // Defaults + if c.ParallelsToolsMode == "" { + c.ParallelsToolsMode = "disable" + } + + if c.ParallelsToolsGuestPath == "" { + c.ParallelsToolsGuestPath = "prl-tools.iso" + } + + if c.ParallelsToolsHostPath == "" { + c.ParallelsToolsHostPath = "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso" + } + + if c.VMName == "" { + c.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", c.PackerBuildName) + } + + // Prepare the errors + errs := common.CheckUnusedConfig(md) + errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(c.tpl)...) + errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(c.tpl, &c.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(c.tpl)...) + errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(c.tpl)...) + errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(c.tpl)...) + errs = packer.MultiErrorAppend(errs, c.PrlctlConfig.Prepare(c.tpl)...) + errs = packer.MultiErrorAppend(errs, c.PrlctlVersionConfig.Prepare(c.tpl)...) + + templates := map[string]*string{ + "parallels_tools_mode": &c.ParallelsToolsMode, + "parallels_tools_host_paht": &c.ParallelsToolsHostPath, + "parallels_tools_guest_path": &c.ParallelsToolsGuestPath, + "source_path": &c.SourcePath, + "vm_name": &c.VMName, + } + + for n, ptr := range templates { + var err error + *ptr, err = c.tpl.Process(*ptr, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + validMode := false + validModes := []string{ + parallelscommon.ParallelsToolsModeDisable, + parallelscommon.ParallelsToolsModeAttach, + parallelscommon.ParallelsToolsModeUpload, + } + + for _, mode := range validModes { + if c.ParallelsToolsMode == mode { + validMode = true + break + } + } + + if !validMode { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("parallels_tools_mode is invalid. Must be one of: %v", validModes)) + } + + if c.SourcePath == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required")) + } else { + if _, err := os.Stat(c.SourcePath); err != nil { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("source_path is invalid: %s", err)) + } + } + + // Warnings + var warnings []string + if c.ShutdownCommand == "" { + warnings = append(warnings, + "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ + "will forcibly halt the virtual machine, which may result in data loss.") + } + + // Check for any errors. + if errs != nil && len(errs.Errors) > 0 { + return nil, warnings, errs + } + + return c, warnings, nil +} diff --git a/builder/parallels/pvm/config_test.go b/builder/parallels/pvm/config_test.go new file mode 100644 index 000000000..1fb15849b --- /dev/null +++ b/builder/parallels/pvm/config_test.go @@ -0,0 +1,85 @@ +package pvm + +import ( + "io/ioutil" + "os" + "testing" +) + +func testConfig(t *testing.T) map[string]interface{} { + return map[string]interface{}{ + "ssh_username": "foo", + "shutdown_command": "foo", + } +} + +func getTempFile(t *testing.T) *os.File { + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + tf.Close() + + // don't forget to cleanup the file downstream: + // defer os.Remove(tf.Name()) + + return tf +} + +func testConfigErr(t *testing.T, warns []string, err error) { + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should error") + } +} + +func testConfigOk(t *testing.T, warns []string, err error) { + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("bad: %s", err) + } +} + +func TestNewConfig_sourcePath(t *testing.T) { + // Bad + c := testConfig(t) + delete(c, "source_path") + _, warns, errs := NewConfig(c) + testConfigErr(t, warns, errs) + + // Bad + c = testConfig(t) + c["source_path"] = "/i/dont/exist" + _, warns, errs = NewConfig(c) + testConfigErr(t, warns, errs) + + // Good + tf := getTempFile(t) + defer os.Remove(tf.Name()) + + c = testConfig(t) + c["source_path"] = tf.Name() + _, warns, errs = NewConfig(c) + testConfigOk(t, warns, errs) +} + +func TestNewConfig_shutdown_timeout(t *testing.T) { + c := testConfig(t) + tf := getTempFile(t) + defer os.Remove(tf.Name()) + + // Expect this to fail + c["source_path"] = tf.Name() + c["shutdown_timeout"] = "NaN" + _, warns, errs := NewConfig(c) + testConfigErr(t, warns, errs) + + // Passes when given a valid time duration + c["shutdown_timeout"] = "10s" + _, warns, errs = NewConfig(c) + testConfigOk(t, warns, errs) +} diff --git a/builder/parallels/pvm/step_import.go b/builder/parallels/pvm/step_import.go new file mode 100644 index 000000000..71ceac6a7 --- /dev/null +++ b/builder/parallels/pvm/step_import.go @@ -0,0 +1,48 @@ +package pvm + +import ( + "fmt" + "github.com/mitchellh/multistep" + parallelscommon "github.com/mitchellh/packer/builder/parallels/common" + "github.com/mitchellh/packer/packer" +) + +// This step imports an PVM VM into Parallels. +type StepImport struct { + Name string + SourcePath string + vmName string +} + +func (s *StepImport) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(parallelscommon.Driver) + ui := state.Get("ui").(packer.Ui) + config := state.Get("config").(*Config) + + ui.Say(fmt.Sprintf("Importing VM: %s", s.SourcePath)) + if err := driver.Import(s.Name, s.SourcePath, config.OutputDir); err != nil { + err := fmt.Errorf("Error importing VM: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + s.vmName = s.Name + state.Put("vmName", s.Name) + return multistep.ActionContinue +} + +func (s *StepImport) Cleanup(state multistep.StateBag) { + + if s.vmName == "" { + return + } + + driver := state.Get("driver").(parallelscommon.Driver) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Unregistering virtual machine...") + if err := driver.Prlctl("unregister", s.vmName); err != nil { + ui.Error(fmt.Sprintf("Error unregistering virtual machine: %s", err)) + } +} diff --git a/builder/parallels/pvm/step_test.go b/builder/parallels/pvm/step_test.go new file mode 100644 index 000000000..9a5fd6e38 --- /dev/null +++ b/builder/parallels/pvm/step_test.go @@ -0,0 +1,19 @@ +package pvm + +import ( + "bytes" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + parallelscommon "github.com/mitchellh/packer/builder/parallels/common" + "testing" +) + +func testState(t *testing.T) multistep.StateBag { + state := new(multistep.BasicStateBag) + state.Put("driver", new(parallelscommon.DriverMock)) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + return state +} diff --git a/config.go b/config.go index 1a58a0862..9f1485aa8 100644 --- a/config.go +++ b/config.go @@ -31,6 +31,8 @@ const defaultConfig = ` "virtualbox-ovf": "packer-builder-virtualbox-ovf", "vmware-iso": "packer-builder-vmware-iso", "vmware-vmx": "packer-builder-vmware-vmx", + "parallels-iso": "packer-builder-parallels-iso", + "parallels-pvm": "packer-builder-parallels-pvm", "null": "packer-builder-null" }, diff --git a/plugin/builder-parallels-iso/main.go b/plugin/builder-parallels-iso/main.go new file mode 100644 index 000000000..9d0f85e43 --- /dev/null +++ b/plugin/builder-parallels-iso/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/builder/parallels/iso" + "github.com/mitchellh/packer/packer/plugin" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterBuilder(new(iso.Builder)) + server.Serve() +} diff --git a/plugin/builder-parallels-iso/main_test.go b/plugin/builder-parallels-iso/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/plugin/builder-parallels-iso/main_test.go @@ -0,0 +1 @@ +package main diff --git a/plugin/builder-parallels-pvm/main.go b/plugin/builder-parallels-pvm/main.go new file mode 100644 index 000000000..36aafbd62 --- /dev/null +++ b/plugin/builder-parallels-pvm/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/builder/parallels/pvm" + "github.com/mitchellh/packer/packer/plugin" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterBuilder(new(pvm.Builder)) + server.Serve() +} diff --git a/plugin/builder-parallels-pvm/main_test.go b/plugin/builder-parallels-pvm/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/plugin/builder-parallels-pvm/main_test.go @@ -0,0 +1 @@ +package main From c5e9fbcb504564d2391f30be52a3b4325c3513aa Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Thu, 10 Apr 2014 21:20:17 +0200 Subject: [PATCH 033/593] Added support for Parallels Desktop for Mac [GH-223] Added documentation for Parallels builder. --- .../docs/builders/parallels-iso.html.markdown | 290 ++++++++++++++++++ .../docs/builders/parallels-pvm.html.markdown | 159 ++++++++++ .../docs/builders/parallels.html.markdown | 39 +++ website/source/layouts/docs.erb | 1 + 4 files changed, 489 insertions(+) create mode 100644 website/source/docs/builders/parallels-iso.html.markdown create mode 100644 website/source/docs/builders/parallels-pvm.html.markdown create mode 100644 website/source/docs/builders/parallels.html.markdown diff --git a/website/source/docs/builders/parallels-iso.html.markdown b/website/source/docs/builders/parallels-iso.html.markdown new file mode 100644 index 000000000..e27152731 --- /dev/null +++ b/website/source/docs/builders/parallels-iso.html.markdown @@ -0,0 +1,290 @@ +--- +layout: "docs" +page_title: "Parallels Builder (from an ISO)" +--- + +# Parallels Builder (from an ISO) + +Type: `parallels-iso` + +The Parallels builder is able to create +[Parallels Desktop for Mac](http://www.parallels.com/products/desktop/) virtual +machines and export them in the PVM format, starting from an +ISO image. + +The builder builds a virtual machine by creating a new virtual machine +from scratch, booting it, installing an OS, provisioning software within +the OS, then shutting it down. The result of the Parallels builder is a directory +containing all the files necessary to run the virtual machine portably. + +## Basic Example + +Here is a basic example. This example is not functional. It will start the +OS installer but then fail because we don't provide the preseed file for +Ubuntu to self-install. Still, the example serves to show the basic configuration: + +
+{
+  "type": "parallels-iso",
+  "guest_os_type": "Ubuntu_64",
+  "iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.3-server-amd64.iso",
+  "iso_checksum": "2cbe868812a871242cdcdd8f2fd6feb9",
+  "iso_checksum_type": "md5",
+  "ssh_username": "packer",
+  "ssh_password": "packer",
+  "ssh_wait_timeout": "30s",
+  "shutdown_command": "echo 'packer' | sudo -S shutdown -P now"
+}
+
+ +It is important to add a `shutdown_command`. By default Packer halts the +virtual machine and the file system may not be sync'd. Thus, changes made in a +provisioner might not be saved. + +## Configuration Reference + +There are many configuration options available for the Parallels builder. +They are organized below into two categories: required and optional. Within +each category, the available options are alphabetized and described. + +Required: + +* `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO + files are so large, this is required and Packer will verify it prior + to booting a virtual machine with the ISO attached. The type of the + checksum is specified with `iso_checksum_type`, documented below. + +* `iso_checksum_type` (string) - The type of the checksum specified in + `iso_checksum`. Valid values are "none", "md5", "sha1", "sha256", or + "sha512" currently. While "none" will skip checksumming, this is not + recommended since ISO files are generally large and corruption does happen + from time to time. + +* `iso_url` (string) - A URL to the ISO containing the installation image. + This URL can be either an HTTP URL or a file URL (or path to a file). + If this is an HTTP URL, Packer will download it and cache it between + runs. + +* `ssh_username` (string) - The username to use to SSH into the machine + once the OS is installed. + +Optional: + +* `boot_command` (array of strings) - This is an array of commands to type + when the virtual machine is first booted. The goal of these commands should + be to type just enough to initialize the operating system installer. Special + keys can be typed as well, and are covered in the section below on the boot + command. If this is not specified, it is assumed the installer will start + itself. + +* `boot_wait` (string) - The time to wait after booting the initial virtual + machine before typing the `boot_command`. The value of this should be + a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + five seconds and one minute 30 seconds, respectively. If this isn't specified, + the default is 10 seconds. + +* `disk_size` (int) - The size, in megabytes, of the hard disk to create + for the VM. By default, this is 40000 (about 40 GB). + +* `floppy_files` (array of strings) - A list of files to put onto a floppy + disk that is attached when the VM is booted for the first time. This is + most useful for unattended Windows installs, which look for an + `Autounattend.xml` file on removable media. By default no floppy will + be attached. The files listed in this configuration will all be put + into the root directory of the floppy disk; sub-directories are not supported. + +* `guest_os_type` (string) - The guest OS type being installed. By default + this is "other", but you can get _dramatic_ performance improvements by + setting this to the proper value. To view all available values for this + run `prlctl create x --ostype list`. Setting the correct value hints to + Parallels Desktop how to optimize the virtual hardware to work best with + that operating system. + +* `guest_os_distribution` (string) - The guest OS distribution being + installed. By default this is "other", but you can get dramatic + performance improvements by setting this to the proper value. To + view all available values for this run `prlctl create x --distribution list`. + Setting the correct value hints to Parallels how to optimize the virtual + hardware to work best with that operating system. + +* `hard_drive_interface` (string) - The type of controller that the + hard drives are attached to, defaults to "sata". Valid options are + "sata", "ide", and "scsi". + +* `http_directory` (string) - Path to a directory to serve using an HTTP + server. The files in this directory will be available over HTTP that will + be requestable from the virtual machine. This is useful for hosting + kickstart files and so on. By default this is "", which means no HTTP + server will be started. The address and port of the HTTP server will be + available as variables in `boot_command`. This is covered in more detail + below. + +* `http_port_min` and `http_port_max` (int) - These are the minimum and + maximum port to use for the HTTP server started to serve the `http_directory`. + Because Packer often runs in parallel, Packer will choose a randomly available + port in this range to run the HTTP server. If you want to force the HTTP + server to be on one port, make this minimum and maximum port the same. + By default the values are 8000 and 9000, respectively. + +* `iso_urls` (array of strings) - Multiple URLs for the ISO to download. + Packer will try these in order. If anything goes wrong attempting to download + or while downloading a single URL, it will move on to the next. All URLs + must point to the same file (same checksum). By default this is empty + and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. + +* `output_directory` (string) - This is the path to the directory where the + resulting virtual machine will be created. This may be relative or absolute. + If relative, the path is relative to the working directory when `packer` + is executed. This directory must not exist or be empty prior to running the builder. + By default this is "output-BUILDNAME" where "BUILDNAME" is the name + of the build. + +* `prlctl` (array of array of strings) - Custom `prlctl` commands to execute in + order to further customize the virtual machine being created. The value of + this is an array of commands to execute. The commands are executed in the order + defined in the template. For each command, the command is defined itself as an + array of strings, where each string represents a single argument on the + command-line to `prlctl` (but excluding `prlctl` itself). Each arg is treated + as a [configuration template](/docs/templates/configuration-templates.html), + where the `Name` variable is replaced with the VM name. More details on how + to use `prlctl` are below. + +* `parallels_tools_mode` (string) - The method by which Parallels tools are + made available to the guest for installation. Valid options are "upload", + "attach", or "disable". The functions of each of these should be + self-explanatory. The default value is "upload". + +* `parallels_tools_guest_path` (string) - The path on the guest virtual machine + where the Parallels tools ISO will be uploaded. By default this is + "prl-tools.iso" which should upload into the login directory of the user. + This is a configuration template where the `Version` variable is replaced + with the prlctl version. + +* `parallels_tools_host_path` (string) - The path to the Parallels Tools ISO to + upload. By default the Parallels builder will use the "other" OS tools ISO from + the Parallels installation: + "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso" + +* `prlctl_version_file` (string) - The path within the virtual machine to upload + a file that contains the `prlctl` version that was used to create the machine. + This information can be useful for provisioning. By default this is + ".prlctl_version", which will generally upload it into the home directory. + +* `shutdown_command` (string) - The command to use to gracefully shut down + the machine once all the provisioning is done. By default this is an empty + string, which tells Packer to just forcefully shut down the machine. + +* `shutdown_timeout` (string) - The amount of time to wait after executing + the `shutdown_command` for the virtual machine to actually shut down. + If it doesn't shut down in this time, it is an error. By default, the timeout + is "5m", or five minutes. + +* `ssh_key_path` (string) - Path to a private key to use for authenticating + with SSH. By default this is not set (key-based auth won't be used). + The associated public key is expected to already be configured on the + VM being prepared by some other process (kickstart, etc.). + +* `ssh_password` (string) - The password for `ssh_username` to use to + authenticate with SSH. By default this is the empty string. + +* `ssh_port` (int) - The port that SSH will be listening on in the guest + virtual machine. By default this is 22. + +* `ssh_wait_timeout` (string) - The duration to wait for SSH to become + available. By default this is "20m", or 20 minutes. Note that this should + be quite long since the timer begins as soon as the virtual machine is booted. + +* `vm_name` (string) - This is the name of the PVM directory for the new + virtual machine, without the file extension. By default this is + "packer-BUILDNAME", where "BUILDNAME" is the name of the build. + +## Boot Command + +The `boot_command` configuration is very important: it specifies the keys +to type when the virtual machine is first booted in order to start the +OS installer. This command is typed after `boot_wait`, which gives the +virtual machine some time to actually load the ISO. + +As documented above, the `boot_command` is an array of strings. The +strings are all typed in sequence. It is an array only to improve readability +within the template. + +The boot command is "typed" character for character using the `prltype` (part +of prl-utils, see [Parallels Builder](/docs/builders/parallels.html)) +command connected to the machine, simulating a human actually typing the +keyboard. There are a set of special keys available. If these are in your +boot command, they will be replaced by the proper key: + +* `` and `` - Simulates an actual "enter" or "return" keypress. + +* `` - Simulates pressing the escape key. + +* `` - Simulates pressing the tab key. + +* `` `` `` - Adds a 1, 5 or 10 second pause before sending + any additional keys. This is useful if you have to generally wait for the UI + to update before typing more. + +In addition to the special keys, each command to type is treated as a +[configuration template](/docs/templates/configuration-templates.html). +The available variables are: + +* `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server + that is started serving the directory specified by the `http_directory` + configuration parameter. If `http_directory` isn't specified, these will + be blank! + +Example boot command. This is actually a working boot command used to start +an Ubuntu 12.04 installer: + +
+[
+  "<esc><esc><enter><wait>",
+  "/install/vmlinuz noapic ",
+  "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ",
+  "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ",
+  "hostname={{ .Name }} ",
+  "fb=false debconf/frontend=noninteractive ",
+  "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ",
+  "keyboard-configuration/variant=USA console-setup/ask_detect=false ",
+  "initrd=/install/initrd.gz -- <enter>"
+]
+
+ +## Parallels Tools +After the virtual machine is up and the operating system is installed, Packer +uploads the Parallels Tools into the virtual machine. The path where they are +uploaded is controllable by `parallels_tools_path`, and defaults to +"prl-tools.iso". Without an absolute path, it is uploaded to the home directory +of the SSH user. Parallels Tools ISO's can be found in: +"/Applications/Parallels Desktop.app/Contents/Resources/Tools/" + +## prlctl Commands +In order to perform extra customization of the virtual machine, a template can +define extra calls to `prlctl` to perform. +[prlctl](http://download.parallels.com/desktop/v4/wl/docs/en/Parallels_Command_Line_Reference_Guide/) +is the command-line interface to Parallels. It can be used to do things such as +set RAM, CPUs, etc. + +Extra `prlctl` commands are defined in the template in the `prlctl` section. +An example is shown below that sets the memory and number of CPUs within the +virtual machine: + +
+{
+  "prlctl": [
+    ["set", "{{.Name}}", "--memsize", "1024"],
+    ["set", "{{.Name}}", "--cpus", "2"]
+  ]
+}
+
+ +The value of `prlctl` is an array of commands to execute. These commands are +executed in the order defined. So in the above example, the memory will be set +followed by the CPUs. + +Each command itself is an array of strings, where each string is an argument to +`prlctl`. Each argument is treated as a +[configuration template](/docs/templates/configuration-templates.html). The only +available variable is `Name` which is replaced with the unique name of the VM, +which is required for many `prlctl` calls. diff --git a/website/source/docs/builders/parallels-pvm.html.markdown b/website/source/docs/builders/parallels-pvm.html.markdown new file mode 100644 index 000000000..0e32ab975 --- /dev/null +++ b/website/source/docs/builders/parallels-pvm.html.markdown @@ -0,0 +1,159 @@ +--- +layout: "docs" +page_title: "Parallels Builder (from a PVM)" +--- + +# Parallels Builder (from a PVM) + +Type: `parallels-pvm` + +This Parallels builder is able to create +[Parallels Desktop for Mac](http://www.parallels.com/products/desktop/) +virtual machines and export them in the PVM format, starting from an +existing PVM (exported virtual machine image). + +The builder builds a virtual machine by importing an existing PVM +file. It then boots this image, runs provisioners on this new VM, and +exports that VM to create the image. The imported machine is deleted prior +to finishing the build. + +## Basic Example + +Here is a basic example. This example is functional if you have an PVM matching +the settings here. + +
+{
+  "type": "parallels-pvm",
+  "source_path": "source.pvm",
+  "ssh_username": "packer",
+  "ssh_password": "packer",
+  "ssh_wait_timeout": "30s",
+  "shutdown_command": "echo 'packer' | sudo -S shutdown -P now"
+}
+
+ +It is important to add a `shutdown_command`. By default Packer halts the +virtual machine and the file system may not be sync'd. Thus, changes made in a +provisioner might not be saved. + +## Configuration Reference + +There are many configuration options available for the Parallels builder. +They are organized below into two categories: required and optional. Within +each category, the available options are alphabetized and described. + +Required: + +* `source_path` (string) - The path to a PVM directory that acts as + the source of this build. + +* `ssh_username` (string) - The username to use to SSH into the machine + once the OS is installed. + +Optional: + +* `floppy_files` (array of strings) - A list of files to put onto a floppy + disk that is attached when the VM is booted for the first time. This is + most useful for unattended Windows installs, which look for an + `Autounattend.xml` file on removable media. By default no floppy will + be attached. The files listed in this configuration will all be put + into the root directory of the floppy disk; sub-directories are not supported. + +* `output_directory` (string) - This is the path to the directory where the + resulting virtual machine will be created. This may be relative or absolute. + If relative, the path is relative to the working directory when `packer` + is executed. This directory must not exist or be empty prior to running the builder. + By default this is "output-BUILDNAME" where "BUILDNAME" is the name + of the build. + +* `prlctl` (array of array of strings) - Custom `prlctl` commands to execute in + order to further customize the virtual machine being created. The value of + this is an array of commands to execute. The commands are executed in the order + defined in the template. For each command, the command is defined itself as an + array of strings, where each string represents a single argument on the + command-line to `prlctl` (but excluding `prlctl` itself). Each arg is treated + as a [configuration template](/docs/templates/configuration-templates.html), + where the `Name` variable is replaced with the VM name. More details on how + to use `prlctl` are below. + +* `parallels_tools_mode` (string) - The method by which Parallels tools are + made available to the guest for installation. Valid options are "upload", + "attach", or "disable". The functions of each of these should be + self-explanatory. The default value is "upload". + +* `parallels_tools_guest_path` (string) - The path on the guest virtual machine + where the Parallels tools ISO will be uploaded. By default this is + "prl-tools.iso" which should upload into the login directory of the user. + This is a configuration template where the `Version` variable is replaced + with the prlctl version. + +* `parallels_tools_host_path` (string) - The path to the Parallels Tools ISO to + upload. By default the Parallels builder will use the "other" OS tools ISO from + the Parallels installation: + "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso" + +* `prlctl_version_file` (string) - The path within the virtual machine to upload + a file that contains the `prlctl` version that was used to create the machine. + This information can be useful for provisioning. By default this is + ".prlctl_version", which will generally upload it into the home directory. + +* `shutdown_command` (string) - The command to use to gracefully shut down + the machine once all the provisioning is done. By default this is an empty + string, which tells Packer to just forcefully shut down the machine. + +* `shutdown_timeout` (string) - The amount of time to wait after executing + the `shutdown_command` for the virtual machine to actually shut down. + If it doesn't shut down in this time, it is an error. By default, the timeout + is "5m", or five minutes. + +* `ssh_key_path` (string) - Path to a private key to use for authenticating + with SSH. By default this is not set (key-based auth won't be used). + The associated public key is expected to already be configured on the + VM being prepared by some other process (kickstart, etc.). + +* `ssh_password` (string) - The password for `ssh_username` to use to + authenticate with SSH. By default this is the empty string. + +* `ssh_port` (int) - The port that SSH will be listening on in the guest + virtual machine. By default this is 22. + +* `ssh_wait_timeout` (string) - The duration to wait for SSH to become + available. By default this is "20m", or 20 minutes. Note that this should + be quite long since the timer begins as soon as the virtual machine is booted. + + +* `vm_name` (string) - This is the name of the virtual machine when it is + imported as well as the name of the PVM directory when the virtual machine is + exported. By default this is "packer-BUILDNAME", where "BUILDNAME" is + the name of the build. + +## prlctl Commands +In order to perform extra customization of the virtual machine, a template can +define extra calls to `prlctl` to perform. +[prlctl](http://download.parallels.com/desktop/v4/wl/docs/en/Parallels_Command_Line_Reference_Guide/) +is the command-line interface to Parallels. It can be used to do things such as +set RAM, CPUs, etc. + +Extra `prlctl` commands are defined in the template in the `prlctl` section. +An example is shown below that sets the memory and number of CPUs within the +virtual machine: + +
+{
+  "prlctl": [
+    ["set", "{{.Name}}", "--memsize", "1024"],
+    ["set", "{{.Name}}", "--cpus", "2"]
+  ]
+}
+
+ +The value of `prlctl` is an array of commands to execute. These commands are +executed in the order defined. So in the above example, the memory will be set +followed by the CPUs. + +Each command itself is an array of strings, where each string is an argument to +`prlctl`. Each argument is treated as a +[configuration template](/docs/templates/configuration-templates.html). The only +available variable is `Name` which is replaced with the unique name of the VM, +which is required for many `prlctl` calls. diff --git a/website/source/docs/builders/parallels.html.markdown b/website/source/docs/builders/parallels.html.markdown new file mode 100644 index 000000000..89a7840b4 --- /dev/null +++ b/website/source/docs/builders/parallels.html.markdown @@ -0,0 +1,39 @@ +--- +layout: "docs" +page_title: "Parallels Builder" +--- + +# Parallels Builder + +The Parallels builder is able to create [Parallels Desktop for Mac](http://www.parallels.com/products/desktop/) virtual machines and export them in the PVM format. + +Packer actually comes with multiple builders able to create Parallels +machines, depending on the strategy you want to use to build the image. +Packer supports the following Parallels builders: + +* [parallels-iso](/docs/builders/parallels-iso.html) - Starts from + an ISO file, creates a brand new Parallels VM, installs an OS, + provisions software within the OS, then exports that machine to create + an image. This is best for people who want to start from scratch. + +* [parallels-pvm](/docs/builders/parallels-pvm.html) - This builder + imports an existing PVM file, runs provisioners on top of that VM, + and exports that machine to create an image. This is best if you have + an existing Parallels VM export you want to use as the source. As an + additional benefit, you can feed the artifact of this builder back into + itself to iterate on a machine. + + +## Requirements + +In addition to [Parallels Desktop for Mac](http://www.parallels.com/products/desktop/) this requires: + +- [Parallels Virtualization SDK 9 for Mac](http://download.parallels.com//desktop/v9/pde.hf1/ParallelsVirtualizationSDK-9.0.24172.951362.dmg) +- [prl-utils](https://github.com/rickard-von-essen/prl-utils/) + +The SDK can be installed by downloading and following the instructions in the dmg. The easiest way to install _prl-utils_ is using [Homebrew](http://brew.sh/) + + ``` + brew tap rickard-von-essen/homebrew-formulae + brew install --HEAD prl-utils + ``` diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 3c0bae143..5fe4f6eaf 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -38,6 +38,7 @@
  • QEMU
  • VirtualBox
  • VMware
  • +
  • Parallels
  • Custom
  • From 75e26ee955f516ae3be6fecd9f533870f7ad9796 Mon Sep 17 00:00:00 2001 From: YungSang Date: Wed, 9 Apr 2014 07:22:48 +0200 Subject: [PATCH 034/593] Added support for Parallels Desktop for Mac [GH-233] in the vagrant post-processor. The code originate from https://github.com/YungSang/packer-parallels --- post-processor/vagrant/parallels.go | 98 +++++++++++++++++++ post-processor/vagrant/parallels_test.go | 9 ++ post-processor/vagrant/post-processor.go | 14 ++- .../post-processors/vagrant.html.markdown | 1 + 4 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 post-processor/vagrant/parallels.go create mode 100644 post-processor/vagrant/parallels_test.go diff --git a/post-processor/vagrant/parallels.go b/post-processor/vagrant/parallels.go new file mode 100644 index 000000000..0e501c3ce --- /dev/null +++ b/post-processor/vagrant/parallels.go @@ -0,0 +1,98 @@ +package vagrant + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/going/toolkit/xmlpath" + "github.com/mitchellh/packer/packer" +) + +// These are the extensions of files that are unnecessary for the function +// of a Parallels virtual machine. +var UnnecessaryFileExtensions = []string{".log", ".backup", ".Backup"} + +type ParallelsProvider struct{} + +func (p *ParallelsProvider) KeepInputArtifact() bool { + return false +} + +func (p *ParallelsProvider) 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": "parallels"} + var configPath string + + // Copy all of the original contents into the temporary directory + for _, path := range artifact.Files() { + // If the file isn't critical to the function of the + // virtual machine, we get rid of it. + // It's done by the builder, but we need one more time + // because unregistering a vm creates config.pvs.backup again. + unnecessary := false + ext := filepath.Ext(path) + for _, unnecessaryExt := range UnnecessaryFileExtensions { + if unnecessaryExt == ext { + unnecessary = true + break + } + } + if unnecessary { + continue + } + + tmpPath := filepath.ToSlash(path) + pathRe := regexp.MustCompile(`^(.+?)([^/]+\.pvm/.+?)$`) + matches := pathRe.FindStringSubmatch(tmpPath) + var pvmPath string + if matches != nil { + pvmPath = filepath.FromSlash(matches[2]) + } else { + continue // Just copy a pvm + } + dstPath := filepath.Join(dir, pvmPath) + + ui.Message(fmt.Sprintf("Copying: %s", path)) + if err = CopyContents(dstPath, path); err != nil { + return + } + if strings.HasSuffix(dstPath, "/config.pvs") { + configPath = dstPath + } + } + + // Create the Vagrantfile from the template + var baseMacAddress string + baseMacAddress, err = findBaseMacAddress(configPath) + if err != nil { + ui.Message(fmt.Sprintf("Problem determining Vagarant Box MAC address: %s", err)) + } + + vagrantfile = fmt.Sprintf(parallelsVagrantfile, baseMacAddress) + + return +} + +func findBaseMacAddress(path string) (string, error) { + xpath := "/ParallelsVirtualMachine/Hardware/NetworkAdapter[@id='0']/MAC" + file, err := os.Open(path) + if err != nil { + return "", err + } + xpathComp := xmlpath.MustCompile(xpath) + root, err := xmlpath.Parse(file) + if err != nil { + return "", err + } + value, _ := xpathComp.String(root) + return value, nil +} + +var parallelsVagrantfile = ` +Vagrant.configure("2") do |config| + config.vm.base_mac = "%s" +end +` diff --git a/post-processor/vagrant/parallels_test.go b/post-processor/vagrant/parallels_test.go new file mode 100644 index 000000000..093680f82 --- /dev/null +++ b/post-processor/vagrant/parallels_test.go @@ -0,0 +1,9 @@ +package vagrant + +import ( + "testing" +) + +func TestParallelsProvider_impl(t *testing.T) { + var _ Provider = new(ParallelsProvider) +} diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go index e0076febf..1e094d68a 100644 --- a/post-processor/vagrant/post-processor.go +++ b/post-processor/vagrant/post-processor.go @@ -16,11 +16,12 @@ import ( ) var builtins = map[string]string{ - "mitchellh.amazonebs": "aws", - "mitchellh.amazon.instance": "aws", - "mitchellh.virtualbox": "virtualbox", - "mitchellh.vmware": "vmware", - "pearkes.digitalocean": "digitalocean", + "mitchellh.amazonebs": "aws", + "mitchellh.amazon.instance": "aws", + "mitchellh.virtualbox": "virtualbox", + "mitchellh.vmware": "vmware", + "pearkes.digitalocean": "digitalocean", + "rickard-von-essen.parallels": "parallels", } type Config struct { @@ -63,6 +64,7 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { } func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { + name, ok := builtins[artifact.BuilderId()] if !ok { return nil, false, fmt.Errorf( @@ -216,6 +218,8 @@ func providerForName(name string) Provider { return new(VBoxProvider) case "vmware": return new(VMwareProvider) + case "parallels": + return new(ParallelsProvider) default: return nil } diff --git a/website/source/docs/post-processors/vagrant.html.markdown b/website/source/docs/post-processors/vagrant.html.markdown index 204f235d0..72501d5ed 100644 --- a/website/source/docs/post-processors/vagrant.html.markdown +++ b/website/source/docs/post-processors/vagrant.html.markdown @@ -30,6 +30,7 @@ providers. * DigitalOcean * VirtualBox * VMware +* Parallels
    Support for additional providers is planned. If the From d08ee4adfd4942525b7dfd915636513deab5a8b3 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Wed, 30 Apr 2014 08:03:52 +0200 Subject: [PATCH 035/593] Added support for Parallels Desktop for Mac [GH-233] in the vagrant post-processor. Fixes https://github.com/rickard-von-essen/packer-parallels/issues/3 --- post-processor/vagrant/parallels.go | 42 +++---------------- post-processor/vagrant/util.go | 8 ++++ .../post-processors/vagrant.html.markdown | 2 +- 3 files changed, 14 insertions(+), 38 deletions(-) diff --git a/post-processor/vagrant/parallels.go b/post-processor/vagrant/parallels.go index 0e501c3ce..f5c291490 100644 --- a/post-processor/vagrant/parallels.go +++ b/post-processor/vagrant/parallels.go @@ -2,18 +2,15 @@ package vagrant import ( "fmt" - "os" "path/filepath" "regexp" - "strings" - "github.com/going/toolkit/xmlpath" "github.com/mitchellh/packer/packer" ) -// These are the extensions of files that are unnecessary for the function +// These are the extensions of files and directories that are unnecessary for the function // of a Parallels virtual machine. -var UnnecessaryFileExtensions = []string{".log", ".backup", ".Backup"} +var UnnecessaryFilesPatterns = []string{"\\.log$", "\\.backup$", "\\.Backup$", "\\.app/"} type ParallelsProvider struct{} @@ -24,18 +21,14 @@ func (p *ParallelsProvider) KeepInputArtifact() bool { func (p *ParallelsProvider) 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": "parallels"} - var configPath string // Copy all of the original contents into the temporary directory for _, path := range artifact.Files() { // If the file isn't critical to the function of the // virtual machine, we get rid of it. - // It's done by the builder, but we need one more time - // because unregistering a vm creates config.pvs.backup again. unnecessary := false - ext := filepath.Ext(path) - for _, unnecessaryExt := range UnnecessaryFileExtensions { - if unnecessaryExt == ext { + for _, unnecessaryPat := range UnnecessaryFilesPatterns { + if matched, _ := regexp.MatchString(unnecessaryPat, path); matched { unnecessary = true break } @@ -59,40 +52,15 @@ func (p *ParallelsProvider) Process(ui packer.Ui, artifact packer.Artifact, dir if err = CopyContents(dstPath, path); err != nil { return } - if strings.HasSuffix(dstPath, "/config.pvs") { - configPath = dstPath - } } // Create the Vagrantfile from the template - var baseMacAddress string - baseMacAddress, err = findBaseMacAddress(configPath) - if err != nil { - ui.Message(fmt.Sprintf("Problem determining Vagarant Box MAC address: %s", err)) - } - - vagrantfile = fmt.Sprintf(parallelsVagrantfile, baseMacAddress) + vagrantfile = fmt.Sprintf(parallelsVagrantfile) return } -func findBaseMacAddress(path string) (string, error) { - xpath := "/ParallelsVirtualMachine/Hardware/NetworkAdapter[@id='0']/MAC" - file, err := os.Open(path) - if err != nil { - return "", err - } - xpathComp := xmlpath.MustCompile(xpath) - root, err := xmlpath.Parse(file) - if err != nil { - return "", err - } - value, _ := xpathComp.String(root) - return value, nil -} - var parallelsVagrantfile = ` Vagrant.configure("2") do |config| - config.vm.base_mac = "%s" end ` diff --git a/post-processor/vagrant/util.go b/post-processor/vagrant/util.go index 3cce14a82..db695289a 100644 --- a/post-processor/vagrant/util.go +++ b/post-processor/vagrant/util.go @@ -21,6 +21,14 @@ func CopyContents(dst, src string) error { } defer srcF.Close() + dstDir, _ := filepath.Split(dst) + if dstDir != "" { + err := os.MkdirAll(dstDir, os.ModePerm) + if err != nil { + return err + } + } + dstF, err := os.Create(dst) if err != nil { return err diff --git a/website/source/docs/post-processors/vagrant.html.markdown b/website/source/docs/post-processors/vagrant.html.markdown index 72501d5ed..ed7f7708b 100644 --- a/website/source/docs/post-processors/vagrant.html.markdown +++ b/website/source/docs/post-processors/vagrant.html.markdown @@ -97,7 +97,7 @@ In the example above, the compression level will be set to 1 except for VMware, where it will be set to 0. The available provider names are: `aws`, `digitalocean`, `virtualbox`, -and `vmware`. +`vmware`, and `parallels`. ## Input Artifacts From 1c49987442ae7204cd9199053e078b98cf680577 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 1 May 2014 13:59:29 -0700 Subject: [PATCH 036/593] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0942bcadc..ac6cbdaa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ FEATURES: * **New builder:** `null` - The null builder does not produce any artifacts, but is useful for debugging provisioning scripts. [GH-970] + * **New builder:** `parallels-iso` and `parallels-pvm` - These can be + used to build Parallels virtual machines. [GH-1101] * **New provisioner:** `chef-client` - Provision using a the `chef-client` command, which talks to a Chef Server. [GH-855] * **New provisioner:** `puppet-server` - Provision using Puppet by From 3a68c8aaef3732b46c34d3efdfda7fd6fe215be0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 1 May 2014 13:59:52 -0700 Subject: [PATCH 037/593] builder/parallels: update builder ID We're using packer namespace now --- builder/parallels/common/artifact.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/parallels/common/artifact.go b/builder/parallels/common/artifact.go index 308b99f72..4930e0c32 100644 --- a/builder/parallels/common/artifact.go +++ b/builder/parallels/common/artifact.go @@ -9,7 +9,7 @@ import ( ) // This is the common builder ID to all of these artifacts. -const BuilderId = "rickard-von-essen.parallels" +const BuilderId = "packer.parallels" // These are the extensions of files and directories that are unnecessary for the function // of a Parallels virtual machine. From c1510d2b50bc612a9179beef5f83488eaa27f85c Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Fri, 2 May 2014 00:58:17 +0400 Subject: [PATCH 038/593] Parallels provider: fix typos --- builder/parallels/common/floppy_config.go | 2 +- builder/parallels/common/step_attach_floppy_test.go | 2 +- builder/parallels/iso/host_ip.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/builder/parallels/common/floppy_config.go b/builder/parallels/common/floppy_config.go index 35cd7aca4..5bcdd4b4a 100644 --- a/builder/parallels/common/floppy_config.go +++ b/builder/parallels/common/floppy_config.go @@ -7,7 +7,7 @@ import ( ) // FloppyConfig is configuration related to created floppy disks and attaching -// them to a VirtualBox machine. +// them to a Parallels virtual machine. type FloppyConfig struct { FloppyFiles []string `mapstructure:"floppy_files"` } diff --git a/builder/parallels/common/step_attach_floppy_test.go b/builder/parallels/common/step_attach_floppy_test.go index 2a4f8bf11..ab78025df 100644 --- a/builder/parallels/common/step_attach_floppy_test.go +++ b/builder/parallels/common/step_attach_floppy_test.go @@ -66,6 +66,6 @@ func TestStepAttachFloppy_noFloppy(t *testing.T) { } if len(driver.PrlctlCalls) > 0 { - t.Fatal("should not call vboxmanage") + t.Fatal("should not call prlctl") } } diff --git a/builder/parallels/iso/host_ip.go b/builder/parallels/iso/host_ip.go index 98a6d26df..12b3de6a2 100644 --- a/builder/parallels/iso/host_ip.go +++ b/builder/parallels/iso/host_ip.go @@ -1,7 +1,7 @@ package iso // Interface to help find the host IP that is available from within -// the VMware virtual machines. +// the Parallels virtual machines. type HostIPFinder interface { HostIP() (string, error) } From 9beac8d286d2e06ed90781a58a11472c69ee275d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 1 May 2014 14:13:22 -0700 Subject: [PATCH 039/593] provisioner/chef-client: proper ordering of args [GH-1100] --- provisioner/chef-client/provisioner.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/provisioner/chef-client/provisioner.go b/provisioner/chef-client/provisioner.go index 451019498..1e2847f54 100644 --- a/provisioner/chef-client/provisioner.go +++ b/provisioner/chef-client/provisioner.go @@ -342,7 +342,7 @@ func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir stri func (p *Provisioner) cleanNode(ui packer.Ui, comm packer.Communicator, node string) error { ui.Say("Cleaning up chef node...") - app := "knife node delete -y " + node + app := fmt.Sprintf("knife node delete %s -y", node) cmd := exec.Command("sh", "-c", app) out, err := cmd.Output() @@ -358,7 +358,7 @@ func (p *Provisioner) cleanNode(ui packer.Ui, comm packer.Communicator, node str func (p *Provisioner) cleanClient(ui packer.Ui, comm packer.Communicator, node string) error { ui.Say("Cleaning up chef client...") - app := "knife client delete -y " + node + app := fmt.Sprintf("knife client delete %s -y", node) cmd := exec.Command("sh", "-c", app) out, err := cmd.Output() From 19317fe92d85efc4964720d9b4b0e45c8323df38 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 1 May 2014 14:21:17 -0700 Subject: [PATCH 040/593] website: document temporary_key_pair_name [GH-1024] --- website/source/docs/builders/amazon-ebs.html.markdown | 3 +++ website/source/docs/builders/amazon-instance.html.markdown | 3 +++ 2 files changed, 6 insertions(+) diff --git a/website/source/docs/builders/amazon-ebs.html.markdown b/website/source/docs/builders/amazon-ebs.html.markdown index 58a43b47f..b3fd89e78 100644 --- a/website/source/docs/builders/amazon-ebs.html.markdown +++ b/website/source/docs/builders/amazon-ebs.html.markdown @@ -128,6 +128,9 @@ Optional: * `tags` (object of key/value strings) - Tags applied to the AMI. +* `temporary_key_pair_name` (string) - The name of the temporary keypair + to generate. By default, Packer generates a name with a UUID. + * `user_data` (string) - User data to apply when launching the instance. Note that you need to be careful about escaping characters due to the templates being JSON. It is often more convenient to use `user_data_file`, diff --git a/website/source/docs/builders/amazon-instance.html.markdown b/website/source/docs/builders/amazon-instance.html.markdown index bd5a8369e..71490aa35 100644 --- a/website/source/docs/builders/amazon-instance.html.markdown +++ b/website/source/docs/builders/amazon-instance.html.markdown @@ -167,6 +167,9 @@ Optional: * `tags` (object of key/value strings) - Tags applied to the AMI. +* `temporary_key_pair_name` (string) - The name of the temporary keypair + to generate. By default, Packer generates a name with a UUID. + * `user_data` (string) - User data to apply when launching the instance. Note that you need to be careful about escaping characters due to the templates being JSON. It is often more convenient to use `user_data_file`, From 5c5d62733f25756ec38d8606b91ff1030d144758 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 1 May 2014 14:24:19 -0700 Subject: [PATCH 041/593] fmt --- builder/digitalocean/builder.go | 10 +++++----- builder/parallels/iso/step_attach_iso.go | 2 +- builder/parallels/iso/step_create_disk.go | 2 +- builder/parallels/pvm/step_test.go | 2 +- common/config.go | 4 ++-- packer/template.go | 4 ++-- provisioner/chef-solo/provisioner_test.go | 8 ++++---- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/builder/digitalocean/builder.go b/builder/digitalocean/builder.go index d07bb0e52..670f8ed77 100644 --- a/builder/digitalocean/builder.go +++ b/builder/digitalocean/builder.go @@ -17,7 +17,7 @@ import ( // see https://api.digitalocean.com/images/?client_id=[client_id]&api_key=[api_key] // name="Ubuntu 12.04.4 x64", id=3101045, -const DefaultImage = "ubuntu-12-04-x64" +const DefaultImage = "ubuntu-12-04-x64" // see https://api.digitalocean.com/regions/?client_id=[client_id]&api_key=[api_key] // name="New York", id=1 @@ -25,7 +25,7 @@ const DefaultRegion = "nyc1" // see https://api.digitalocean.com/sizes/?client_id=[client_id]&api_key=[api_key] // name="512MB", id=66 (the smallest droplet size) -const DefaultSize = "512mb" +const DefaultSize = "512mb" // The unique id for the builder const BuilderId = "pearkes.digitalocean" @@ -42,9 +42,9 @@ type config struct { SizeID uint `mapstructure:"size_id"` ImageID uint `mapstructure:"image_id"` - Region string `mapstructure:"region"` - Size string `mapstructure:"size"` - Image string `mapstructure:"image"` + Region string `mapstructure:"region"` + Size string `mapstructure:"size"` + Image string `mapstructure:"image"` PrivateNetworking bool `mapstructure:"private_networking"` SnapshotName string `mapstructure:"snapshot_name"` diff --git a/builder/parallels/iso/step_attach_iso.go b/builder/parallels/iso/step_attach_iso.go index c18143409..c4937217c 100644 --- a/builder/parallels/iso/step_attach_iso.go +++ b/builder/parallels/iso/step_attach_iso.go @@ -3,8 +3,8 @@ package iso import ( "fmt" "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" parallelscommon "github.com/mitchellh/packer/builder/parallels/common" + "github.com/mitchellh/packer/packer" ) // This step attaches the ISO to the virtual machine. diff --git a/builder/parallels/iso/step_create_disk.go b/builder/parallels/iso/step_create_disk.go index 7571abce3..abb6ec2aa 100644 --- a/builder/parallels/iso/step_create_disk.go +++ b/builder/parallels/iso/step_create_disk.go @@ -3,8 +3,8 @@ package iso import ( "fmt" "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" parallelscommon "github.com/mitchellh/packer/builder/parallels/common" + "github.com/mitchellh/packer/packer" "strconv" ) diff --git a/builder/parallels/pvm/step_test.go b/builder/parallels/pvm/step_test.go index 9a5fd6e38..15d76de5b 100644 --- a/builder/parallels/pvm/step_test.go +++ b/builder/parallels/pvm/step_test.go @@ -3,8 +3,8 @@ package pvm import ( "bytes" "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" parallelscommon "github.com/mitchellh/packer/builder/parallels/common" + "github.com/mitchellh/packer/packer" "testing" ) diff --git a/common/config.go b/common/config.go index 0fe5ffc99..4a56cc356 100644 --- a/common/config.go +++ b/common/config.go @@ -68,7 +68,7 @@ func DecodeConfig(target interface{}, raws ...interface{}) (*mapstructure.Metada var md mapstructure.Metadata decoderConfig := &mapstructure.DecoderConfig{ - DecodeHook: mapstructure.ComposeDecodeHookFunc( + DecodeHook: mapstructure.ComposeDecodeHookFunc( decodeHook, mapstructure.StringToSliceHookFunc(","), ), @@ -221,7 +221,7 @@ func decodeConfigHook(raws []interface{}) (mapstructure.DecodeHookFunc, error) { // because internally Packer uses MsgPack for RPC and the MsgPack // codec turns strings into []uint8 if f == reflect.Slice { - dataVal := reflect.ValueOf(v) + dataVal := reflect.ValueOf(v) dataType := dataVal.Type() elemKind := dataType.Elem().Kind() if elemKind == reflect.Uint8 { diff --git a/packer/template.go b/packer/template.go index a96b6758d..ed71e90d6 100644 --- a/packer/template.go +++ b/packer/template.go @@ -131,8 +131,8 @@ func ParseTemplate(data []byte, vars map[string]string) (t *Template, err error) if vCur.LessThan(vReq) { return nil, fmt.Errorf( - "Template requires Packer version %s. " + - "Running version is %s.", + "Template requires Packer version %s. "+ + "Running version is %s.", vReq, vCur) } } diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go index 73e54de57..67fc1009a 100644 --- a/provisioner/chef-solo/provisioner_test.go +++ b/provisioner/chef-solo/provisioner_test.go @@ -279,11 +279,11 @@ func TestProvisionerPrepare_jsonNested(t *testing.T) { }, "bFalse": false, - "bTrue": true, - "bNil": nil, - "bStr": []uint8("bar"), + "bTrue": true, + "bNil": nil, + "bStr": []uint8("bar"), - "bInt": 1, + "bInt": 1, "bFloat": 4.5, } From 64be04841bc059583b3046d2eefd6a8db199f508 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 1 May 2014 20:25:27 -0700 Subject: [PATCH 042/593] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac6cbdaa6..52674e821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ IMPROVEMENTS: variables such as `OS_USERNAME`, `OS_PASSWORD`, etc. [GH-768] * builder/openstack: Support `openstack_provider` option to automatically fill defaults for different OpenStack variants. [GH-912] + * builder/openstack: Support security groups. [GH-848] * builder/qemu: User variable expansion in `ssh_key_path` [GH-918] * builder/qemu: Floppy disk files list can also include globs and directories. [GH-1086] From 5a4a5842c297fbc66900b2a0e6f46da2f2e73976 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 1 May 2014 20:26:07 -0700 Subject: [PATCH 043/593] website: update openstack for security groups --- website/source/docs/builders/openstack.html.markdown | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/source/docs/builders/openstack.html.markdown b/website/source/docs/builders/openstack.html.markdown index 35dd6c008..c6916232c 100644 --- a/website/source/docs/builders/openstack.html.markdown +++ b/website/source/docs/builders/openstack.html.markdown @@ -75,6 +75,9 @@ Optional: and "rackspace". If not specified, Packer will attempt to read this from the `SDK_PROVIDER` or `OS_AUTH_URL` environmental variables. +* `security_groups` (array of strings) - A list of security groups by name + to add to this instance. + * `ssh_port` (int) - The port that SSH will be available on. Defaults to port 22. From 493c8d9580631c65bf209b3d0bacd4f5dff78d4e Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Fri, 2 May 2014 14:46:11 +0400 Subject: [PATCH 044/593] builder/parallels: fixed Builder ID Caused by changing namespace to 'packer.*' (3a68c8a) --- post-processor/vagrant/post-processor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go index 1e094d68a..d926106b9 100644 --- a/post-processor/vagrant/post-processor.go +++ b/post-processor/vagrant/post-processor.go @@ -21,7 +21,7 @@ var builtins = map[string]string{ "mitchellh.virtualbox": "virtualbox", "mitchellh.vmware": "vmware", "pearkes.digitalocean": "digitalocean", - "rickard-von-essen.parallels": "parallels", + "packer.parallels": "parallels", } type Config struct { From 7ff781b25877fc1fa713d94918136e0fe61ecdec Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Sat, 3 May 2014 00:25:58 +0900 Subject: [PATCH 045/593] Revert the way to retrieve vm ip in VMware ESXi Driver --- builder/vmware/iso/driver_esx5.go | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index 8fbba7106..c99376a66 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -174,18 +174,35 @@ func (d *ESX5Driver) SSHAddress(state multistep.StateBag) (string, error) { return address.(string), nil } - r, err := d.run(nil, "vim-cmd", "vmsvc/get.guest", d.vmId, " | grep -m 1 ipAddress | awk -F'\"' '{print $2}'") + r, err := d.esxcli("network", "vm", "list") if err != nil { return "", err } - ipAddress := strings.TrimRight(r, "\n") + record, err := r.find("Name", config.VMName) + if err != nil { + return "", err + } + wid := record["WorldID"] + if wid == "" { + return "", errors.New("VM WorldID not found") + } + + r, err = d.esxcli("network", "vm", "port", "list", "-w", wid) + if err != nil { + return "", err + } + + record, err = r.read() + if err != nil { + return "", err + } - if ipAddress == "" { + if record["IPAddress"] == "0.0.0.0" { return "", errors.New("VM network port found, but no IP address") } - address := fmt.Sprintf("%s:%d", ipAddress, config.SSHPort) + address := fmt.Sprintf("%s:%d", record["IPAddress"], config.SSHPort) state.Put("vm_address", address) return address, nil } From c3257fd674177fd52cf35b986c98e7300149567f Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Sat, 3 May 2014 00:27:42 +0900 Subject: [PATCH 046/593] website: Document that the Remote ESXi Driver supports ESXi 5.1 and above, and needs to enable GuestIPHack --- website/source/docs/builders/vmware-iso.html.markdown | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/website/source/docs/builders/vmware-iso.html.markdown b/website/source/docs/builders/vmware-iso.html.markdown index 1276d2ead..ce3359140 100644 --- a/website/source/docs/builders/vmware-iso.html.markdown +++ b/website/source/docs/builders/vmware-iso.html.markdown @@ -323,6 +323,16 @@ In addition to using the desktop products of VMware locally to build virtual machines, Packer can use a remote VMware Hypervisor to build the virtual machine. +
    +Note: Packer supports ESXi 5.1 and above. +
    + +Before using a remote vSphere Hypervisor, you need to enable GuestIPHack by running the following command: + +``` +esxcli system settings advanced set -o /Net/GuestIPHack -i 1 +``` + When using a remote VMware Hypervisor, the builder still downloads the ISO and various files locally, and uploads these to the remote machine. Packer currently uses SSH to communicate to the ESXi machine rather than From be5adb92b5c4d5d95e5089be94c706b930fea7ba Mon Sep 17 00:00:00 2001 From: Nathan Hartwell Date: Fri, 2 May 2014 11:18:56 -0500 Subject: [PATCH 047/593] Appending wildcard to src causes problems It appears that the desired effect was to support src = "dir" -> dest/dir src = "dir/" -> dest but cp -R already handles this, provided the trailing slash does not get consumed by the shell. The wildcard causes problems when multiple files match the shell glob, e.g. UploadDir("/tmp", "./salt*", []) where my working dir contains - salt - salt-foo will error. --- builder/amazon/chroot/communicator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 22becd164..b6db9340b 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -82,7 +82,7 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error // TODO: remove any file copied if it appears in `exclude` chrootDest := filepath.Join(c.Chroot, dst) log.Printf("Uploading directory '%s' to '%s'", src, chrootDest) - cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp -R %s* %s", src, chrootDest)) + cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp -R '%s' %s", src, chrootDest)) if err != nil { return err } From 12e28f257f66299e3bb13a053bf06ccd236e7efd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 2 May 2014 10:13:29 -0700 Subject: [PATCH 048/593] v0.6.0 --- CHANGELOG.md | 2 +- packer/version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52674e821..99aeb4b35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.6.0 (unreleased) +## 0.6.0 (May 2, 2014) FEATURES: diff --git a/packer/version.go b/packer/version.go index 07e36e01b..0758435dd 100644 --- a/packer/version.go +++ b/packer/version.go @@ -15,7 +15,7 @@ const Version = "0.6.0" // Any pre-release marker for the version. If this is "" (empty string), // then it means that it is a final release. Otherwise, this is the // pre-release marker. -const VersionPrerelease = "dev" +const VersionPrerelease = "" type versionCommand byte From dbe40b9f558a2095088988eb082af9281e3bd4cf Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Sat, 3 May 2014 11:11:10 +0400 Subject: [PATCH 049/593] builder/parallels: Do not delete the first cdrom device Just detach the iso from 'cdrom0', but do not delete this device from VM confguration. --- builder/parallels/common/step_remove_devices.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/builder/parallels/common/step_remove_devices.go b/builder/parallels/common/step_remove_devices.go index 4d1b29611..0bcbc6eb3 100644 --- a/builder/parallels/common/step_remove_devices.go +++ b/builder/parallels/common/step_remove_devices.go @@ -35,7 +35,11 @@ func (s *StepRemoveDevices) Run(state multistep.StateBag) multistep.StepAction { } if _, ok := state.GetOk("attachedIso"); ok { - command := []string{"set", vmName, "--device-del", "cdrom0"} + command := []string{ + "set", vmName, + "--device-set", "cdrom0", + "--device", "Default CD/DVD-ROM", + } if err := driver.Prlctl(command...); err != nil { err := fmt.Errorf("Error detaching ISO: %s", err) From 5cac40b47c90a820aee2b1c5ef4cd45544012f5e Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Sat, 3 May 2014 18:23:20 +0900 Subject: [PATCH 050/593] Fixes #1106, Remote ESXi builder doesn't upload floppy --- builder/vmware/iso/builder.go | 4 ++++ builder/vmware/iso/driver_esx5.go | 3 +++ builder/vmware/iso/step_remote_upload.go | 6 +++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 06884ad89..2fb24b90d 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -334,6 +334,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, + &stepRemoteUpload{ + Key: "floppy_path", + Message: "Uploading Floppy to remote machine...", + }, &stepRemoteUpload{ Key: "iso_path", Message: "Uploading ISO to remote machine...", diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index c99376a66..2e9375859 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -260,6 +260,9 @@ func (d *ESX5Driver) String() string { } func (d *ESX5Driver) datastorePath(path string) string { + if filepath.IsAbs(path) { + return filepath.Join("/vmfs/volumes", d.Datastore, strings.Replace(path, "/", "", 1)) + } return filepath.Join("/vmfs/volumes", d.Datastore, path) } diff --git a/builder/vmware/iso/step_remote_upload.go b/builder/vmware/iso/step_remote_upload.go index 717b3dc13..e739196ab 100644 --- a/builder/vmware/iso/step_remote_upload.go +++ b/builder/vmware/iso/step_remote_upload.go @@ -24,8 +24,12 @@ func (s *stepRemoteUpload) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } + path, ok := state.Get(s.Key).(string) + if !ok { + return multistep.ActionContinue + } + ui.Say(s.Message) - path := state.Get(s.Key).(string) log.Printf("Remote uploading: %s", path) newPath, err := remote.UploadISO(path) if err != nil { From 57e8f8e15b7559a3bacd6f5f03302be46178cb9c Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Sat, 3 May 2014 14:49:07 +0400 Subject: [PATCH 051/593] builder/parallels: Fixed unit test [GH-1115] --- builder/parallels/common/step_remove_devices_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/builder/parallels/common/step_remove_devices_test.go b/builder/parallels/common/step_remove_devices_test.go index eb8638097..983b6faad 100644 --- a/builder/parallels/common/step_remove_devices_test.go +++ b/builder/parallels/common/step_remove_devices_test.go @@ -48,16 +48,19 @@ func TestStepRemoveDevices_attachedIso(t *testing.T) { t.Fatal("should NOT have error") } - // Test that ISO was removed + // Test that ISO was detached if len(driver.PrlctlCalls) != 1 { t.Fatalf("bad: %#v", driver.PrlctlCalls) } - if driver.PrlctlCalls[0][2] != "--device-del" { + if driver.PrlctlCalls[0][2] != "--device-set" { t.Fatalf("bad: %#v", driver.PrlctlCalls) } if driver.PrlctlCalls[0][3] != "cdrom0" { t.Fatalf("bad: %#v", driver.PrlctlCalls) } + if driver.PrlctlCalls[0][5] != "Default CD/DVD-ROM" { + t.Fatalf("bad: %#v", driver.PrlctlCalls) + } } func TestStepRemoveDevices_floppyPath(t *testing.T) { From c2013bf680008e2d43a4c7d533b264ac799a2bcd Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Sun, 4 May 2014 17:56:57 +0200 Subject: [PATCH 052/593] virtualbox-ovf support for guest_additions_mode Fixes #1035 "virtualbox-ovf" builder doesn't support "guest_additions_mode" option --- .../virtualbox/common/guest_addition_modes.go | 9 +++ .../step_attach_guest_additions.go | 19 +++--- .../step_download_guest_additions.go | 29 +++++---- .../step_upload_guest_additions.go | 22 ++++--- builder/virtualbox/iso/builder.go | 31 +++++----- builder/virtualbox/iso/builder_test.go | 5 +- builder/virtualbox/ovf/builder.go | 21 +++++-- builder/virtualbox/ovf/config.go | 61 +++++++++++++++++-- 8 files changed, 136 insertions(+), 61 deletions(-) create mode 100644 builder/virtualbox/common/guest_addition_modes.go rename builder/virtualbox/{iso => common}/step_attach_guest_additions.go (79%) rename builder/virtualbox/{iso => common}/step_download_guest_additions.go (87%) rename builder/virtualbox/{iso => common}/step_upload_guest_additions.go (71%) diff --git a/builder/virtualbox/common/guest_addition_modes.go b/builder/virtualbox/common/guest_addition_modes.go new file mode 100644 index 000000000..b713df7c8 --- /dev/null +++ b/builder/virtualbox/common/guest_addition_modes.go @@ -0,0 +1,9 @@ +package common + +// These are the different valid mode values for "guest_additions_mode" which +// determine how guest additions are delivered to the guest. +const ( + GuestAdditionsModeDisable string = "disable" + GuestAdditionsModeAttach = "attach" + GuestAdditionsModeUpload = "upload" +) diff --git a/builder/virtualbox/iso/step_attach_guest_additions.go b/builder/virtualbox/common/step_attach_guest_additions.go similarity index 79% rename from builder/virtualbox/iso/step_attach_guest_additions.go rename to builder/virtualbox/common/step_attach_guest_additions.go index f35710259..bb047ed9a 100644 --- a/builder/virtualbox/iso/step_attach_guest_additions.go +++ b/builder/virtualbox/common/step_attach_guest_additions.go @@ -1,9 +1,8 @@ -package iso +package common import ( "fmt" "github.com/mitchellh/multistep" - vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common" "github.com/mitchellh/packer/packer" "log" ) @@ -19,18 +18,18 @@ import ( // vmName string // // Produces: -type stepAttachGuestAdditions struct { - attachedPath string +type StepAttachGuestAdditions struct { + attachedPath string + GuestAdditionsMode string } -func (s *stepAttachGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*config) - driver := state.Get("driver").(vboxcommon.Driver) +func (s *StepAttachGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) // If we're not attaching the guest additions then just return - if config.GuestAdditionsMode != GuestAdditionsModeAttach { + if s.GuestAdditionsMode != GuestAdditionsModeAttach { log.Println("Not attaching guest additions since we're uploading.") return multistep.ActionContinue } @@ -61,12 +60,12 @@ func (s *stepAttachGuestAdditions) Run(state multistep.StateBag) multistep.StepA return multistep.ActionContinue } -func (s *stepAttachGuestAdditions) Cleanup(state multistep.StateBag) { +func (s *StepAttachGuestAdditions) Cleanup(state multistep.StateBag) { if s.attachedPath == "" { return } - driver := state.Get("driver").(vboxcommon.Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) diff --git a/builder/virtualbox/iso/step_download_guest_additions.go b/builder/virtualbox/common/step_download_guest_additions.go similarity index 87% rename from builder/virtualbox/iso/step_download_guest_additions.go rename to builder/virtualbox/common/step_download_guest_additions.go index dbb8b9d3c..9ee8a6f11 100644 --- a/builder/virtualbox/iso/step_download_guest_additions.go +++ b/builder/virtualbox/common/step_download_guest_additions.go @@ -1,10 +1,9 @@ -package iso +package common import ( "bytes" "fmt" "github.com/mitchellh/multistep" - vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" "io" @@ -28,16 +27,20 @@ type guestAdditionsUrlTemplate struct { // // Produces: // guest_additions_path string - Path to the guest additions. -type stepDownloadGuestAdditions struct{} +type StepDownloadGuestAdditions struct { + GuestAdditionsMode string + GuestAdditionsURL string + GuestAdditionsSHA256 string + Tpl *packer.ConfigTemplate +} -func (s *stepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { +func (s *StepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { var action multistep.StepAction - driver := state.Get("driver").(vboxcommon.Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) - config := state.Get("config").(*config) // If we've disabled guest additions, don't download - if config.GuestAdditionsMode == GuestAdditionsModeDisable { + if s.GuestAdditionsMode == GuestAdditionsModeDisable { log.Println("Not downloading guest additions since it is disabled.") return multistep.ActionContinue } @@ -59,8 +62,8 @@ func (s *stepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.Ste // Use provided version or get it from virtualbox.org var checksum string - if config.GuestAdditionsSHA256 != "" { - checksum = config.GuestAdditionsSHA256 + if s.GuestAdditionsSHA256 != "" { + checksum = s.GuestAdditionsSHA256 } else { checksum, action = s.downloadAdditionsSHA256(state, version, additionsName) if action != multistep.ActionContinue { @@ -69,13 +72,13 @@ func (s *stepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.Ste } // Use the provided source (URL or file path) or generate it - url := config.GuestAdditionsURL + url := s.GuestAdditionsURL if url != "" { tplData := &guestAdditionsUrlTemplate{ Version: version, } - url, err = config.tpl.Process(url, tplData) + url, err = s.Tpl.Process(url, tplData) if err != nil { err := fmt.Errorf("Error preparing guest additions url: %s", err) state.Put("error", err) @@ -110,9 +113,9 @@ func (s *stepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.Ste return downStep.Run(state) } -func (s *stepDownloadGuestAdditions) Cleanup(state multistep.StateBag) {} +func (s *StepDownloadGuestAdditions) Cleanup(state multistep.StateBag) {} -func (s *stepDownloadGuestAdditions) downloadAdditionsSHA256(state multistep.StateBag, additionsVersion string, additionsName string) (string, multistep.StepAction) { +func (s *StepDownloadGuestAdditions) downloadAdditionsSHA256(state multistep.StateBag, additionsVersion string, additionsName string) (string, multistep.StepAction) { // First things first, we get the list of checksums for the files available // for this version. checksumsUrl := fmt.Sprintf( diff --git a/builder/virtualbox/iso/step_upload_guest_additions.go b/builder/virtualbox/common/step_upload_guest_additions.go similarity index 71% rename from builder/virtualbox/iso/step_upload_guest_additions.go rename to builder/virtualbox/common/step_upload_guest_additions.go index 0ae72c000..5e8b6a86c 100644 --- a/builder/virtualbox/iso/step_upload_guest_additions.go +++ b/builder/virtualbox/common/step_upload_guest_additions.go @@ -1,9 +1,8 @@ -package iso +package common import ( "fmt" "github.com/mitchellh/multistep" - vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common" "github.com/mitchellh/packer/packer" "log" "os" @@ -14,16 +13,19 @@ type guestAdditionsPathTemplate struct { } // This step uploads the guest additions ISO to the VM. -type stepUploadGuestAdditions struct{} +type StepUploadGuestAdditions struct { + GuestAdditionsMode string + GuestAdditionsPath string + Tpl *packer.ConfigTemplate +} -func (s *stepUploadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { +func (s *StepUploadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { comm := state.Get("communicator").(packer.Communicator) - config := state.Get("config").(*config) - driver := state.Get("driver").(vboxcommon.Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) // If we're attaching then don't do this, since we attached. - if config.GuestAdditionsMode != GuestAdditionsModeUpload { + if s.GuestAdditionsMode != GuestAdditionsModeUpload { log.Println("Not uploading guest additions since mode is not upload") return multistep.ActionContinue } @@ -47,7 +49,7 @@ func (s *stepUploadGuestAdditions) Run(state multistep.StateBag) multistep.StepA Version: version, } - config.GuestAdditionsPath, err = config.tpl.Process(config.GuestAdditionsPath, tplData) + s.GuestAdditionsPath, err = s.Tpl.Process(s.GuestAdditionsPath, tplData) if err != nil { err := fmt.Errorf("Error preparing guest additions path: %s", err) state.Put("error", err) @@ -56,7 +58,7 @@ func (s *stepUploadGuestAdditions) Run(state multistep.StateBag) multistep.StepA } ui.Say("Uploading VirtualBox guest additions ISO...") - if err := comm.Upload(config.GuestAdditionsPath, f); err != nil { + if err := comm.Upload(s.GuestAdditionsPath, f); err != nil { state.Put("error", fmt.Errorf("Error uploading guest additions: %s", err)) return multistep.ActionHalt } @@ -64,4 +66,4 @@ func (s *stepUploadGuestAdditions) Run(state multistep.StateBag) multistep.StepA return multistep.ActionContinue } -func (s *stepUploadGuestAdditions) Cleanup(state multistep.StateBag) {} +func (s *StepUploadGuestAdditions) Cleanup(state multistep.StateBag) {} diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index 7861866b2..f41118a45 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -13,14 +13,6 @@ import ( const BuilderId = "mitchellh.virtualbox" -// These are the different valid mode values for "guest_additions_mode" which -// determine how guest additions are delivered to the guest. -const ( - GuestAdditionsModeDisable string = "disable" - GuestAdditionsModeAttach = "attach" - GuestAdditionsModeUpload = "upload" -) - type Builder struct { config config runner multistep.Runner @@ -220,9 +212,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { validMode := false validModes := []string{ - GuestAdditionsModeDisable, - GuestAdditionsModeAttach, - GuestAdditionsModeUpload, + vboxcommon.GuestAdditionsModeDisable, + vboxcommon.GuestAdditionsModeAttach, + vboxcommon.GuestAdditionsModeUpload, } for _, mode := range validModes { @@ -269,7 +261,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe } steps := []multistep.Step{ - new(stepDownloadGuestAdditions), + &vboxcommon.StepDownloadGuestAdditions{ + GuestAdditionsMode: b.config.GuestAdditionsMode, + GuestAdditionsURL: b.config.GuestAdditionsURL, + GuestAdditionsSHA256: b.config.GuestAdditionsSHA256, + Tpl: b.config.tpl, + }, &common.StepDownload{ Checksum: b.config.ISOChecksum, ChecksumType: b.config.ISOChecksumType, @@ -289,7 +286,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe new(stepCreateVM), new(stepCreateDisk), new(stepAttachISO), - new(stepAttachGuestAdditions), + &vboxcommon.StepAttachGuestAdditions{ + GuestAdditionsMode: b.config.GuestAdditionsMode, + }, new(vboxcommon.StepAttachFloppy), &vboxcommon.StepForwardSSH{ GuestPort: b.config.SSHPort, @@ -313,7 +312,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &vboxcommon.StepUploadVersion{ Path: b.config.VBoxVersionFile, }, - new(stepUploadGuestAdditions), + &vboxcommon.StepUploadGuestAdditions{ + GuestAdditionsMode: b.config.GuestAdditionsMode, + GuestAdditionsPath: b.config.GuestAdditionsPath, + Tpl: b.config.tpl, + }, new(common.StepProvision), &vboxcommon.StepShutdown{ Command: b.config.ShutdownCommand, diff --git a/builder/virtualbox/iso/builder_test.go b/builder/virtualbox/iso/builder_test.go index ddd562f15..c9d5414dd 100644 --- a/builder/virtualbox/iso/builder_test.go +++ b/builder/virtualbox/iso/builder_test.go @@ -1,6 +1,7 @@ package iso import ( + "github.com/mitchellh/packer/builder/virtualbox/common" "github.com/mitchellh/packer/packer" "reflect" "testing" @@ -37,7 +38,7 @@ func TestBuilderPrepare_Defaults(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.GuestAdditionsMode != GuestAdditionsModeUpload { + if b.config.GuestAdditionsMode != common.GuestAdditionsModeUpload { t.Errorf("bad guest additions mode: %s", b.config.GuestAdditionsMode) } @@ -111,7 +112,7 @@ func TestBuilderPrepare_GuestAdditionsMode(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.GuestAdditionsMode != GuestAdditionsModeAttach { + if b.config.GuestAdditionsMode != common.GuestAdditionsModeAttach { t.Fatalf("bad: %s", b.config.GuestAdditionsMode) } diff --git a/builder/virtualbox/ovf/builder.go b/builder/virtualbox/ovf/builder.go index ffc1a0d1b..312fc4701 100644 --- a/builder/virtualbox/ovf/builder.go +++ b/builder/virtualbox/ovf/builder.go @@ -42,6 +42,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe state := new(multistep.BasicStateBag) state.Put("config", b.config) state.Put("driver", driver) + state.Put("cache", cache) state.Put("hook", hook) state.Put("ui", ui) @@ -55,14 +56,20 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, + &vboxcommon.StepDownloadGuestAdditions{ + GuestAdditionsMode: b.config.GuestAdditionsMode, + GuestAdditionsURL: b.config.GuestAdditionsURL, + GuestAdditionsSHA256: b.config.GuestAdditionsSHA256, + Tpl: b.config.tpl, + }, &StepImport{ Name: b.config.VMName, SourcePath: b.config.SourcePath, ImportOpts: b.config.ImportOpts, }, - /* - new(stepAttachGuestAdditions), - */ + &vboxcommon.StepAttachGuestAdditions{ + GuestAdditionsMode: b.config.GuestAdditionsMode, + }, new(vboxcommon.StepAttachFloppy), &vboxcommon.StepForwardSSH{ GuestPort: b.config.SSHPort, @@ -85,9 +92,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &vboxcommon.StepUploadVersion{ Path: b.config.VBoxVersionFile, }, - /* - new(stepUploadGuestAdditions), - */ + &vboxcommon.StepUploadGuestAdditions{ + GuestAdditionsMode: b.config.GuestAdditionsMode, + GuestAdditionsPath: b.config.GuestAdditionsPath, + Tpl: b.config.tpl, + }, new(common.StepProvision), &vboxcommon.StepShutdown{ Command: b.config.ShutdownCommand, diff --git a/builder/virtualbox/ovf/config.go b/builder/virtualbox/ovf/config.go index 4b0c9111a..a8df48791 100644 --- a/builder/virtualbox/ovf/config.go +++ b/builder/virtualbox/ovf/config.go @@ -3,6 +3,7 @@ package ovf import ( "fmt" "os" + "strings" vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common" "github.com/mitchellh/packer/common" @@ -23,9 +24,13 @@ type Config struct { vboxcommon.VBoxManagePostConfig `mapstructure:",squash"` vboxcommon.VBoxVersionConfig `mapstructure:",squash"` - SourcePath string `mapstructure:"source_path"` - VMName string `mapstructure:"vm_name"` - ImportOpts string `mapstructure:"import_opts"` + SourcePath string `mapstructure:"source_path"` + GuestAdditionsMode string `mapstructure:"guest_additions_mode"` + GuestAdditionsPath string `mapstructure:"guest_additions_path"` + GuestAdditionsURL string `mapstructure:"guest_additions_url"` + GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"` + VMName string `mapstructure:"vm_name"` + ImportOpts string `mapstructure:"import_opts"` tpl *packer.ConfigTemplate } @@ -44,6 +49,13 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { c.tpl.UserVars = c.PackerUserVars // Defaults + if c.GuestAdditionsMode == "" { + c.GuestAdditionsMode = "upload" + } + + if c.GuestAdditionsPath == "" { + c.GuestAdditionsPath = "VBoxGuestAdditions.iso" + } if c.VMName == "" { c.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", c.PackerBuildName) } @@ -62,9 +74,11 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs = packer.MultiErrorAppend(errs, c.VBoxVersionConfig.Prepare(c.tpl)...) templates := map[string]*string{ - "source_path": &c.SourcePath, - "vm_name": &c.VMName, - "import_opts": &c.ImportOpts, + "guest_additions_mode": &c.GuestAdditionsMode, + "guest_additions_sha256": &c.GuestAdditionsSHA256, + "source_path": &c.SourcePath, + "vm_name": &c.VMName, + "import_opts": &c.ImportOpts, } for n, ptr := range templates { @@ -85,6 +99,41 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } } + validates := map[string]*string{ + "guest_additions_path": &c.GuestAdditionsPath, + "guest_additions_url": &c.GuestAdditionsURL, + } + + for n, ptr := range validates { + if err := c.tpl.Validate(*ptr); err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error parsing %s: %s", n, err)) + } + } + + validMode := false + validModes := []string{ + vboxcommon.GuestAdditionsModeDisable, + vboxcommon.GuestAdditionsModeAttach, + vboxcommon.GuestAdditionsModeUpload, + } + + for _, mode := range validModes { + if c.GuestAdditionsMode == mode { + validMode = true + break + } + } + + if !validMode { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("guest_additions_mode is invalid. Must be one of: %v", validModes)) + } + + if c.GuestAdditionsSHA256 != "" { + c.GuestAdditionsSHA256 = strings.ToLower(c.GuestAdditionsSHA256) + } + // Warnings var warnings []string if c.ShutdownCommand == "" { From d666b64fb89fc733ca344f45763b02dd7c18290f Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Sun, 4 May 2014 10:47:40 -0700 Subject: [PATCH 053/593] website: alphabetized configuration options, added missing ones, standardized text --- .../docs/basics/terminology.html.markdown | 2 +- .../docs/builders/amazon-chroot.html.markdown | 44 ++++++------ .../docs/builders/amazon-ebs.html.markdown | 54 +++++++-------- .../builders/amazon-instance.html.markdown | 51 +++++++------- .../docs/builders/digitalocean.html.markdown | 38 +++++----- .../source/docs/builders/docker.html.markdown | 6 +- .../docs/builders/googlecompute.markdown | 16 ++++- .../source/docs/builders/null.html.markdown | 6 +- .../docs/builders/openstack.html.markdown | 56 +++++++++------ .../docs/builders/parallels-iso.html.markdown | 69 ++++++++++--------- .../docs/builders/parallels-pvm.html.markdown | 37 +++++----- .../source/docs/builders/qemu.html.markdown | 38 +++++----- .../builders/virtualbox-iso.html.markdown | 24 +++---- .../builders/virtualbox-ovf.html.markdown | 28 ++++---- .../docs/builders/vmware-iso.html.markdown | 56 +++++++-------- .../docs/builders/vmware-vmx.html.markdown | 30 ++++---- .../docs/command-line/build.html.markdown | 8 +-- .../other/core-configuration.html.markdown | 2 +- .../post-processors/vagrant.html.markdown | 3 + .../post-processors/vsphere.html.markdown | 2 +- .../docs/provisioners/chef-solo.html.markdown | 10 +-- .../provisioners/puppet-server.html.markdown | 8 +-- .../salt-masterless.html.markdown | 24 +++---- .../docs/templates/introduction.html.markdown | 38 +++++----- 24 files changed, 344 insertions(+), 306 deletions(-) diff --git a/website/source/docs/basics/terminology.html.markdown b/website/source/docs/basics/terminology.html.markdown index e56f48c03..21e3d236b 100644 --- a/website/source/docs/basics/terminology.html.markdown +++ b/website/source/docs/basics/terminology.html.markdown @@ -33,7 +33,7 @@ Packer in the form of plugins. **Commands** are sub-commands for the `packer` program that perform some -job. An example some command is "build", which is invoked as `packer build`. +job. An example command is "build", which is invoked as `packer build`. Packer ships with a set of commands out of the box in order to define its [command-line interface](#). Commands can also be created and added to Packer in the form of plugins. diff --git a/website/source/docs/builders/amazon-chroot.html.markdown b/website/source/docs/builders/amazon-chroot.html.markdown index a2bd21aaf..088304185 100644 --- a/website/source/docs/builders/amazon-chroot.html.markdown +++ b/website/source/docs/builders/amazon-chroot.html.markdown @@ -51,11 +51,11 @@ There are many configuration options available for the builder. They are segmented below into two categories: required and optional parameters. Within each category, the available configuration keys are alphabetized. -Required: +### Required: * `access_key` (string) - The access key used to communicate with AWS. - If not specified, Packer will attempt to read this from environmental - variables `AWS_ACCESS_KEY_ID` or `AWS_ACCESS_KEY` (in that order). + If not specified, Packer will use the environment variables + `AWS_ACCESS_KEY_ID` or `AWS_ACCESS_KEY` (in that order), if set. If the environmental variables aren't set and Packer is running on an EC2 instance, Packer will check the instance metadata for IAM role keys. @@ -66,8 +66,8 @@ Required: [configuration templates](/docs/templates/configuration-templates.html) for more info) * `secret_key` (string) - The secret key used to communicate with AWS. - If not specified, Packer will attempt to read this from environmental - variables `AWS_SECRET_ACCESS_KEY` or `AWS_SECRET_KEY` (in that order). + If not specified, Packer will use the environment variables + `AWS_SECRET_ACCESS_KEY` or `AWS_SECRET_KEY` (in that order), if set. If the environmental variables aren't set and Packer is running on an EC2 instance, Packer will check the instance metadata for IAM role keys. @@ -76,37 +76,43 @@ Required: and provisioned on the currently running instance. This must be an EBS-backed AMI with a root volume snapshot that you have access to. -Optional: - -* `ami_virtualization_type` (string) - The type of virtualization for the AMI - you are building. This option is required to register HVM images. Can be - "paravirtual" (default) or "hvm". +### Optional: * `ami_description` (string) - The description to set for the resulting AMI(s). By default this description is empty. -* `ami_groups` (array of string) - A list of groups that have access +* `ami_groups` (array of strings) - A list of groups that have access to launch the resulting AMI(s). By default no groups have permission to launch the AMI. `all` will make the AMI publicly accessible. -* `ami_product_codes` (array of string) - A list of product codes to +* `ami_product_codes` (array of strings) - A list of product codes to associate with the AMI. By default no product codes are associated with the AMI. -* `ami_regions` (array of string) - A list of regions to copy the AMI to. +* `ami_regions` (array of strings) - A list of regions to copy the AMI to. Tags and attributes are copied along with the AMI. AMI copying takes time depending on the size of the AMI, but will generally take many minutes. -* `ami_users` (array of string) - A list of account IDs that have access +* `ami_users` (array of strings) - A list of account IDs that have access to launch the resulting AMI(s). By default no additional users other than the user creating the AMI has permissions to launch it. -* `chroot_mounts` (list of list of strings) - This is a list of additional +* `ami_virtualization_type` (string) - The type of virtualization for the AMI + you are building. This option is required to register HVM images. Can be + "paravirtual" (default) or "hvm". + +* `chroot_mounts` (array of array of strings) - This is a list of additional devices to mount into the chroot environment. This configuration parameter requires some additional documentation which is in the "Chroot Mounts" section below. Please read that section for more information on how to use this. -* `copy_files` (list of strings) - Paths to files on the running EC2 instance +* `command_wrapper` (string) - How to run shell commands. This + defaults to "{{.Command}}". This may be useful to set if you want to set + environmental variables or perhaps run it with `sudo` or so on. This is a + configuration template where the `.Command` variable is replaced with the + command to be run. + +* `copy_files` (array of strings) - Paths to files on the running EC2 instance that will be copied into the chroot environment prior to provisioning. This is useful, for example, to copy `/etc/resolv.conf` so that DNS lookups work. @@ -115,12 +121,6 @@ Optional: of the source AMI will be attached. This defaults to "" (empty string), which forces Packer to find an open device automatically. -* `command_wrapper` (string) - How to run shell commands. This - defaults to "{{.Command}}". This may be useful to set if you want to set - environmental variables or perhaps run it with `sudo` or so on. This is a - configuration template where the `.Command` variable is replaced with the - command to be run. - * `mount_path` (string) - The path where the volume will be mounted. This is where the chroot environment will be. This defaults to `packer-amazon-chroot-volumes/{{.Device}}`. This is a configuration diff --git a/website/source/docs/builders/amazon-ebs.html.markdown b/website/source/docs/builders/amazon-ebs.html.markdown index b3fd89e78..885095ada 100644 --- a/website/source/docs/builders/amazon-ebs.html.markdown +++ b/website/source/docs/builders/amazon-ebs.html.markdown @@ -29,11 +29,11 @@ There are many configuration options available for the builder. They are segmented below into two categories: required and optional parameters. Within each category, the available configuration keys are alphabetized. -Required: +### Required: * `access_key` (string) - The access key used to communicate with AWS. - If not specified, Packer will attempt to read this from environmental - variables `AWS_ACCESS_KEY_ID` or `AWS_ACCESS_KEY` (in that order). + If not specified, Packer will use the environment variables + `AWS_ACCESS_KEY_ID` or `AWS_ACCESS_KEY` (in that order), if set. * `ami_name` (string) - The name of the resulting AMI that will appear when managing AMIs in the AWS console or via APIs. This must be unique. @@ -47,8 +47,8 @@ Required: to launch the EC2 instance to create the AMI. * `secret_key` (string) - The secret key used to communicate with AWS. - If not specified, Packer will attempt to read this from environmental - variables `AWS_SECRET_ACCESS_KEY` or `AWS_SECRET_KEY` (in that order). + If not specified, Packer will use the environment variables + `AWS_SECRET_ACCESS_KEY` or `AWS_SECRET_KEY` (in that order), if set. * `source_ami` (string) - The initial AMI used as a base for the newly created machine. @@ -56,37 +56,44 @@ Required: * `ssh_username` (string) - The username to use in order to communicate over SSH to the running machine. -Optional: +### Optional: * `ami_block_device_mappings` (array of block device mappings) - Add the block device mappings to the AMI. The block device mappings allow for keys: "device\_name" (string), "virtual\_name" (string), "snapshot\_id" (string), - "volume\_type" (string), "volume\_size" (int), "delete\_on\_termination" - (bool), "no\_device" (bool), and "iops" (int). - -* `ami_virtualization_type` (string) - The type of virtualization for the AMI - you are building. This option is required to register HVM images. Can be - "paravirtual" (default) or "hvm". + "volume\_type" (string), "volume\_size" (integer), "delete\_on\_termination" + (boolean), "no\_device" (boolean), and "iops" (integer). * `ami_description` (string) - The description to set for the resulting AMI(s). By default this description is empty. -* `ami_groups` (array of string) - A list of groups that have access +* `ami_groups` (array of strings) - A list of groups that have access to launch the resulting AMI(s). By default no groups have permission to launch the AMI. `all` will make the AMI publicly accessible. -* `ami_product_codes` (array of string) - A list of product codes to +* `ami_product_codes` (array of strings) - A list of product codes to associate with the AMI. By default no product codes are associated with the AMI. -* `ami_regions` (array of string) - A list of regions to copy the AMI to. +* `ami_regions` (array of strings) - A list of regions to copy the AMI to. Tags and attributes are copied along with the AMI. AMI copying takes time depending on the size of the AMI, but will generally take many minutes. -* `ami_users` (array of string) - A list of account IDs that have access +* `ami_users` (array of strings) - A list of account IDs that have access to launch the resulting AMI(s). By default no additional users other than the user creating the AMI has permissions to launch it. +* `ami_virtualization_type` (string) - The type of virtualization for the AMI + you are building. This option is required to register HVM images. Can be + "paravirtual" (default) or "hvm". + +* `associate_public_ip_address` (boolean) - If using a non-default VPC, public + IP addresses are not provided by default. If this is toggled, your new + instance will get a Public IP. + +* `availability_zone` (string) - Destination availability zone to launch instance in. + Leave this empty to allow Amazon to auto-assign. + * `iam_instance_profile` (string) - The name of an [IAM instance profile](http://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html) to launch the EC2 instance with. @@ -105,15 +112,15 @@ Optional: access. Note that if this is specified, you must be sure the security group allows access to the `ssh_port` given below. -* `security_group_ids` (array of string) - A list of security groups as +* `security_group_ids` (array of strings) - A list of security groups as described above. Note that if this is specified, you must omit the security_group_id. -* `ssh_port` (int) - The port that SSH will be available on. This defaults +* `ssh_port` (integer) - The port that SSH will be available on. This defaults to port 22. -* `ssh_private_key_file` - Use this ssh private key file instead of a generated - ssh key pair for connecting to the instance. +* `ssh_private_key_file` (string) - Use this ssh private key file instead of + a generated ssh key pair for connecting to the instance. * `ssh_timeout` (string) - The time to wait for SSH to become available before timing out. The format of this value is a duration such as "5s" @@ -122,10 +129,6 @@ Optional: * `subnet_id` (string) - If using VPC, the ID of the subnet, such as "subnet-12345def", where Packer will launch the EC2 instance. -* `associate_public_ip_address` (bool) - If using a non-default VPC, public - IP addresses are not provided by default. If this is toggled, your new - instance will get a Public IP. - * `tags` (object of key/value strings) - Tags applied to the AMI. * `temporary_key_pair_name` (string) - The name of the temporary keypair @@ -142,9 +145,6 @@ Optional: * `vpc_id` (string) - If launching into a VPC subnet, Packer needs the VPC ID in order to create a temporary security group within the VPC. -* `availability_zone` (string) - Destination availability zone to launch instance in. - Leave this empty to allow Amazon to auto-assign. - ## Basic Example Here is a basic example. It is completely valid except for the access keys: diff --git a/website/source/docs/builders/amazon-instance.html.markdown b/website/source/docs/builders/amazon-instance.html.markdown index 71490aa35..5b11bb12f 100644 --- a/website/source/docs/builders/amazon-instance.html.markdown +++ b/website/source/docs/builders/amazon-instance.html.markdown @@ -36,11 +36,11 @@ There are many configuration options available for the builder. They are segmented below into two categories: required and optional parameters. Within each category, the available configuration keys are alphabetized. -Required: +### Required: * `access_key` (string) - The access key used to communicate with AWS. - If not specified, Packer will attempt to read this from environmental - variables `AWS_ACCESS_KEY_ID` or `AWS_ACCESS_KEY` (in that order). + If not specified, Packer will use the environment variables + `AWS_ACCESS_KEY_ID` or `AWS_ACCESS_KEY` (in that order), if set. * `account_id` (string) - Your AWS account ID. This is required for bundling the AMI. This is _not the same_ as the access key. You can find your @@ -61,8 +61,8 @@ Required: This bucket will be created if it doesn't exist. * `secret_key` (string) - The secret key used to communicate with AWS. - If not specified, Packer will attempt to read this from environmental - variables `AWS_SECRET_ACCESS_KEY` or `AWS_SECRET_KEY` (in that order). + If not specified, Packer will use the environment variables + `AWS_SECRET_ACCESS_KEY` or `AWS_SECRET_KEY` (in that order), if set. * `source_ami` (string) - The initial AMI used as a base for the newly created machine. @@ -78,38 +78,45 @@ Required: * `x509_key_path` (string) - The local path to the private key for the X509 certificate specified by `x509_cert_path`. This is used for bundling the AMI. -Optional: +### Optional: * `ami_block_device_mappings` (array of block device mappings) - Add the block device mappings to the AMI. The block device mappings allow for keys: "device\_name" (string), "virtual\_name" (string), "snapshot\_id" (string), - "volume\_type" (string), "volume\_size" (int), "delete\_on\_termination" - (bool), "no\_device" (bool), and "iops" (int). + "volume\_type" (string), "volume\_size" (integer), "delete\_on\_termination" + (boolean), "no\_device" (boolean), and "iops" (integer). See [amazon-ebs](/docs/builders/amazon-ebs.html) for an example template. -* `ami_virtualization_type` (string) - The type of virtualization for the AMI - you are building. This option is required to register HVM images. Can be - "paravirtual" (default) or "hvm". - * `ami_description` (string) - The description to set for the resulting AMI(s). By default this description is empty. -* `ami_groups` (array of string) - A list of groups that have access +* `ami_groups` (array of strings) - A list of groups that have access to launch the resulting AMI(s). By default no groups have permission to launch the AMI. `all` will make the AMI publicly accessible. -* `ami_product_codes` (array of string) - A list of product codes to +* `ami_product_codes` (array of strings) - A list of product codes to associate with the AMI. By default no product codes are associated with the AMI. -* `ami_regions` (array of string) - A list of regions to copy the AMI to. +* `ami_regions` (array of strings) - A list of regions to copy the AMI to. Tags and attributes are copied along with the AMI. AMI copying takes time depending on the size of the AMI, but will generally take many minutes. -* `ami_users` (array of string) - A list of account IDs that have access +* `ami_users` (array of strings) - A list of account IDs that have access to launch the resulting AMI(s). By default no additional users other than the user creating the AMI has permissions to launch it. +* `ami_virtualization_type` (string) - The type of virtualization for the AMI + you are building. This option is required to register HVM images. Can be + "paravirtual" (default) or "hvm". + +* `associate_public_ip_address` (boolean) - If using a non-default VPC, public + IP addresses are not provided by default. If this is toggled, your new + instance will get a Public IP. + +* `availability_zone` (string) - Destination availability zone to launch instance in. + Leave this empty to allow Amazon to auto-assign. + * `bundle_destination` (string) - The directory on the running instance where the bundled AMI will be saved prior to uploading. By default this is "/tmp". This directory must exist and be writable. @@ -144,15 +151,15 @@ Optional: access. Note that if this is specified, you must be sure the security group allows access to the `ssh_port` given below. -* `security_group_ids` (array of string) - A list of security groups as +* `security_group_ids` (array of strings) - A list of security groups as described above. Note that if this is specified, you must omit the security_group_id. -* `ssh_port` (int) - The port that SSH will be available on. This defaults +* `ssh_port` (integer) - The port that SSH will be available on. This defaults to port 22. -* `ssh_private_key_file` - Use this ssh private key file instead of a generated - ssh key pair for connecting to the instance. +* `ssh_private_key_file` (string) - Use this ssh private key file instead of + a generated ssh key pair for connecting to the instance. * `ssh_timeout` (string) - The time to wait for SSH to become available before timing out. The format of this value is a duration such as "5s" @@ -161,10 +168,6 @@ Optional: * `subnet_id` (string) - If using VPC, the ID of the subnet, such as "subnet-12345def", where Packer will launch the EC2 instance. -* `associate_public_ip_address` (bool) - If using a non-default VPC, public - IP addresses are not provided by default. If this is toggled, your new - instance will get a Public IP. - * `tags` (object of key/value strings) - Tags applied to the AMI. * `temporary_key_pair_name` (string) - The name of the temporary keypair diff --git a/website/source/docs/builders/digitalocean.html.markdown b/website/source/docs/builders/digitalocean.html.markdown index a493de93b..5c00ad6e7 100644 --- a/website/source/docs/builders/digitalocean.html.markdown +++ b/website/source/docs/builders/digitalocean.html.markdown @@ -21,35 +21,43 @@ There are many configuration options available for the builder. They are segmented below into two categories: required and optional parameters. Within each category, the available configuration keys are alphabetized. -Required: +### Required: * `api_key` (string) - The API key to use to access your account. You can retrieve this on the "API" page visible after logging into your account - on DigitalOcean. Alternatively, the builder looks for the environment - variable `DIGITALOCEAN_API_KEY`. + on DigitalOcean. + If not specified, Packer will use the environment variable + `DIGITALOCEAN_API_KEY`, if set. * `client_id` (string) - The client ID to use to access your account. You can find this on the "API" page visible after logging into your account on - DigitalOcean. Alternatively, the builder looks for the environment - variable `DIGITALOCEAN_CLIENT_ID`. + DigitalOcean. + If not specified, Packer will use the environment variable + `DIGITALOCEAN_CLIENT_ID`, if set. -Optional: +### Optional: + +* `droplet_name` (string) - The name assigned to the droplet. DigitalOcean + sets the hostname of the machine to this value. * `image` (string) - The name (or slug) of the base image to use. This is the image that will be used to launch a new droplet and provision it. This defaults to 'ubuntu-12-04-x64' which is the slug for "Ubuntu 12.04.4 x64". See https://developers.digitalocean.com/images/ for the accepted image names/slugs. -* `image_id` (int) - The ID of the base image to use. This is the image that +* `image_id` (integer) - The ID of the base image to use. This is the image that will be used to launch a new droplet and provision it. This setting is deprecated. Use `image` instead. +* `private_networking` (boolean) - Set to `true` to enable private networking + for the droplet being created. This defaults to `false`, or not enabled. + * `region` (string) - The name (or slug) of the region to launch the droplet in. Consequently, this is the region where the snapshot will be available. This defaults to "nyc1", which is the slug for "New York 1". See https://developers.digitalocean.com/regions/ for the accepted region names/slugs. -* `region_id` (int) - The ID of the region to launch the droplet in. Consequently, +* `region_id` (integer) - The ID of the region to launch the droplet in. Consequently, this is the region where the snapshot will be available. This setting is deprecated. Use `region` instead. @@ -57,21 +65,15 @@ Optional: This defaults to "512mb", which is the slug for "512MB". See https://developers.digitalocean.com/sizes/ for the accepted size names/slugs. -* `size_id` (int) - The ID of the droplet size to use. +* `size_id` (integer) - The ID of the droplet size to use. This setting is deprecated. Use `size` instead. -* `private_networking` (bool) - Set to `true` to enable private networking - for the droplet being created. This defaults to `false`, or not enabled. - * `snapshot_name` (string) - The name of the resulting snapshot that will appear in your account. This must be unique. To help make this unique, use a function like `timestamp` (see [configuration templates](/docs/templates/configuration-templates.html) for more info) -* `droplet_name` (string) - The name assigned to the droplet. DigitalOcean - sets the hostname of the machine to this value. - -* `ssh_port` (int) - The port that SSH will be available on. Defaults to port +* `ssh_port` (integer) - The port that SSH will be available on. Defaults to port 22. * `ssh_timeout` (string) - The time to wait for SSH to become available @@ -82,8 +84,8 @@ Optional: over SSH to the running droplet. Default is "root". * `state_timeout` (string) - The time to wait, as a duration string, -for a droplet to enter a desired state (such as "active") before -timing out. The default state timeout is "6m". + for a droplet to enter a desired state (such as "active") before + timing out. The default state timeout is "6m". ## Basic Example diff --git a/website/source/docs/builders/docker.html.markdown b/website/source/docs/builders/docker.html.markdown index 315d5ff63..6b9ee8cd0 100644 --- a/website/source/docs/builders/docker.html.markdown +++ b/website/source/docs/builders/docker.html.markdown @@ -44,7 +44,7 @@ Configuration options are organized below into two categories: required and optional. Within each category, the available options are alphabetized and described. -Required: +### Required: * `export_path` (string) - The path where the final container will be exported as a tar file. @@ -53,9 +53,9 @@ Required: be started. This image will be pulled from the Docker registry if it doesn't already exist. -Optional: +### Optional: -* `pull` (bool) - If true, the configured image will be pulled using +* `pull` (boolean) - If true, the configured image will be pulled using `docker pull` prior to use. Otherwise, it is assumed the image already exists and can be used. This defaults to true if not set. diff --git a/website/source/docs/builders/googlecompute.markdown b/website/source/docs/builders/googlecompute.markdown index bab2ffce3..9c29046dd 100644 --- a/website/source/docs/builders/googlecompute.markdown +++ b/website/source/docs/builders/googlecompute.markdown @@ -69,7 +69,7 @@ files obtained in the previous section. Configuration options are organized below into two categories: required and optional. Within each category, the available options are alphabetized and described. -Required: +### Required: * `bucket_name` (string) - The Google Cloud Storage bucket to store the images that are created. The bucket must already exist in your project. @@ -89,7 +89,7 @@ Required: * `zone` (string) - The zone in which to launch the instance used to create the image. Example: "us-central1-a" -Optional: +### Optional: * `image_name` (string) - The unique name of the resulting image. Defaults to `packer-{{timestamp}}`. @@ -99,6 +99,11 @@ Optional: * `instance_name` (string) - A name to give the launched instance. Beware that this must be unique. Defaults to "packer-{{uuid}}". +* `metadata` (object of key/value strings) + + * `machine_type` (string) - The machine type. Defaults to `n1-standard-1`. * `network` (string) - The Google Compute network to use for the launched @@ -107,7 +112,7 @@ Optional: * `passphrase` (string) - The passphrase to use if the `private_key_file` is encrypted. -* `ssh_port` (int) - The SSH port. Defaults to 22. +* `ssh_port` (integer) - The SSH port. Defaults to 22. * `ssh_timeout` (string) - The time to wait for SSH to become available. Defaults to "1m". @@ -117,6 +122,11 @@ Optional: * `state_timeout` (string) - The time to wait for instance state changes. Defaults to "5m". +* `tags` (array of strings) + + ## Gotchas Centos images have root ssh access disabled by default. Set `ssh_username` to any user, which will be created by packer with sudo access. diff --git a/website/source/docs/builders/null.html.markdown b/website/source/docs/builders/null.html.markdown index c1c6c1ebc..732026d03 100644 --- a/website/source/docs/builders/null.html.markdown +++ b/website/source/docs/builders/null.html.markdown @@ -30,7 +30,7 @@ Configuration options are organized below into two categories: required and optional. Within each category, the available options are alphabetized and described. -Required: +### Required: * `host` (string) - The hostname or IP address to connect to. @@ -42,7 +42,7 @@ Required: * `ssh_username` (string) - The username to be used for the ssh connection. -Optional: +### Optional: -* `port` (int) - port to connect to, defaults to 22. +* `port` (integer) - ssh port to connect to, defaults to 22. diff --git a/website/source/docs/builders/openstack.html.markdown b/website/source/docs/builders/openstack.html.markdown index c6916232c..d4ca59a81 100644 --- a/website/source/docs/builders/openstack.html.markdown +++ b/website/source/docs/builders/openstack.html.markdown @@ -24,7 +24,7 @@ There are many configuration options available for the builder. They are segmented below into two categories: required and optional parameters. Within each category, the available configuration keys are alphabetized. -Required: +### Required: * `flavor` (string) - The ID or full URL for the desired flavor for the server to be created. @@ -32,31 +32,32 @@ Required: * `image_name` (string) - The name of the resulting image. * `password` (string) - The password used to connect to the OpenStack service. - If not specified, Packer will attempt to read this from the - `SDK_PASSWORD` or `OS_PASSWORD` environment variable. + If not specified, Packer will use the environment variables + `SDK_PASSWORD` or `OS_PASSWORD` (in that order), if set. * `provider` (string) - The provider used to connect to the OpenStack service. - If not specified, Packer will attempt to read this from the - `SDK_PROVIDER` environment variable. For Rackspace this should be `rackspace-us` - or `rackspace-uk`. + If not specified, Packer will use the environment variable + `SDK_PROVIDER`, if set. + For Rackspace this should be `rackspace-us` or `rackspace-uk`. * `region` (string) - The name of the region, such as "DFW", in which - to launch the server to create the AMI. If not specified, Packer will - attempt to read this from the `SDK_REGION` or `OS_REGION_NAME` environmental - variables. + to launch the server to create the AMI. + If not specified, Packer will use the environment variables + `SDK_REGION` or `OS_REGION_NAME` (in that order), if set. * `source_image` (string) - The ID or full URL to the base image to use. This is the image that will be used to launch a new server and provision it. * `username` (string) - The username used to connect to the OpenStack service. - If not specified, Packer will attempt to read this from the - `SDK_USERNAME` or `OS_USERNAME` environment variable. + If not specified, Packer will use the environment variables + `SDK_USERNAME` or `OS_USERNAME` (in that order), if set. -Optional: +### Optional: * `api_key` (string) - The API key used to access OpenStack. Some OpenStack - installations require this. If not specified, Packer will attempt to - read this from the `SDK_API_KEY` environmental variable. + installations require this. + If not specified, Packer will use the environment variables + `SDK_API_KEY`, if set. * `floating_ip` (string) - A specific floating IP to assign to this instance. `use_floating_ip` must also be set to true for this to have an affect. @@ -65,20 +66,31 @@ Optional: to allocate a floating IP. `use_floating_ip` must also be set to true for this to have an affect. +* `openstack_provider` (string) + + * `project` (string) - The project name to boot the instance into. Some - OpenStack installations require this. If not specified, Packer will attempt - to read this from the `SDK_PROJECT` or `OS_TENANT_NAME` environmental - variables. + OpenStack installations require this. + If not specified, Packer will use the environment variables + `SDK_PROJECT` or `OS_TENANT_NAME` (in that order), if set. * `provider` (string) - A name of a provider that has a slightly different API model. Currently supported values are "openstack" (default), - and "rackspace". If not specified, Packer will attempt to read this from - the `SDK_PROVIDER` or `OS_AUTH_URL` environmental variables. + and "rackspace". + If not specified, Packer will use the environment variables + `SDK_PROVIDER` or `OS_AUTH_URL` (in that order), if set. + +* `proxy_url` (string) + * `security_groups` (array of strings) - A list of security groups by name to add to this instance. -* `ssh_port` (int) - The port that SSH will be available on. Defaults to port +* `ssh_port` (integer) - The port that SSH will be available on. Defaults to port 22. * `ssh_timeout` (string) - The time to wait for SSH to become available @@ -91,8 +103,8 @@ Optional: * `tenant_id` (string) - Tenant ID for accessing OpenStack if your installation requires this. -* `use_floating_ip` (bool) - Whether or not to use a floating IP for - the instance. +* `use_floating_ip` (boolean) - Whether or not to use a floating IP for + the instance. Defaults to false. ## Basic Example diff --git a/website/source/docs/builders/parallels-iso.html.markdown b/website/source/docs/builders/parallels-iso.html.markdown index e27152731..425f337ce 100644 --- a/website/source/docs/builders/parallels-iso.html.markdown +++ b/website/source/docs/builders/parallels-iso.html.markdown @@ -47,7 +47,7 @@ There are many configuration options available for the Parallels builder. They are organized below into two categories: required and optional. Within each category, the available options are alphabetized and described. -Required: +### Required: * `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO files are so large, this is required and Packer will verify it prior @@ -68,7 +68,7 @@ Required: * `ssh_username` (string) - The username to use to SSH into the machine once the OS is installed. -Optional: +### Optional: * `boot_command` (array of strings) - This is an array of commands to type when the virtual machine is first booted. The goal of these commands should @@ -83,22 +83,18 @@ Optional: five seconds and one minute 30 seconds, respectively. If this isn't specified, the default is 10 seconds. -* `disk_size` (int) - The size, in megabytes, of the hard disk to create +* `disk_size` (integer) - The size, in megabytes, of the hard disk to create for the VM. By default, this is 40000 (about 40 GB). -* `floppy_files` (array of strings) - A list of files to put onto a floppy - disk that is attached when the VM is booted for the first time. This is - most useful for unattended Windows installs, which look for an - `Autounattend.xml` file on removable media. By default no floppy will - be attached. The files listed in this configuration will all be put - into the root directory of the floppy disk; sub-directories are not supported. - -* `guest_os_type` (string) - The guest OS type being installed. By default - this is "other", but you can get _dramatic_ performance improvements by - setting this to the proper value. To view all available values for this - run `prlctl create x --ostype list`. Setting the correct value hints to - Parallels Desktop how to optimize the virtual hardware to work best with - that operating system. +* `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. * `guest_os_distribution` (string) - The guest OS distribution being installed. By default this is "other", but you can get dramatic @@ -107,6 +103,13 @@ Optional: Setting the correct value hints to Parallels how to optimize the virtual hardware to work best with that operating system. +* `guest_os_type` (string) - The guest OS type being installed. By default + this is "other", but you can get _dramatic_ performance improvements by + setting this to the proper value. To view all available values for this + run `prlctl create x --ostype list`. Setting the correct value hints to + Parallels Desktop how to optimize the virtual hardware to work best with + that operating system. + * `hard_drive_interface` (string) - The type of controller that the hard drives are attached to, defaults to "sata". Valid options are "sata", "ide", and "scsi". @@ -119,7 +122,7 @@ Optional: available as variables in `boot_command`. This is covered in more detail below. -* `http_port_min` and `http_port_max` (int) - These are the minimum and +* `http_port_min` and `http_port_max` (integer) - These are the minimum and maximum port to use for the HTTP server started to serve the `http_directory`. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to run the HTTP server. If you want to force the HTTP @@ -139,21 +142,6 @@ Optional: By default this is "output-BUILDNAME" where "BUILDNAME" is the name of the build. -* `prlctl` (array of array of strings) - Custom `prlctl` commands to execute in - order to further customize the virtual machine being created. The value of - this is an array of commands to execute. The commands are executed in the order - defined in the template. For each command, the command is defined itself as an - array of strings, where each string represents a single argument on the - command-line to `prlctl` (but excluding `prlctl` itself). Each arg is treated - as a [configuration template](/docs/templates/configuration-templates.html), - where the `Name` variable is replaced with the VM name. More details on how - to use `prlctl` are below. - -* `parallels_tools_mode` (string) - The method by which Parallels tools are - made available to the guest for installation. Valid options are "upload", - "attach", or "disable". The functions of each of these should be - self-explanatory. The default value is "upload". - * `parallels_tools_guest_path` (string) - The path on the guest virtual machine where the Parallels tools ISO will be uploaded. By default this is "prl-tools.iso" which should upload into the login directory of the user. @@ -165,6 +153,21 @@ Optional: the Parallels installation: "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso" +* `parallels_tools_mode` (string) - The method by which Parallels tools are + made available to the guest for installation. Valid options are "upload", + "attach", or "disable". The functions of each of these should be + self-explanatory. The default value is "upload". + +* `prlctl` (array of array of strings) - Custom `prlctl` commands to execute in + order to further customize the virtual machine being created. The value of + this is an array of commands to execute. The commands are executed in the order + defined in the template. For each command, the command is defined itself as an + array of strings, where each string represents a single argument on the + command-line to `prlctl` (but excluding `prlctl` itself). Each arg is treated + as a [configuration template](/docs/templates/configuration-templates.html), + where the `Name` variable is replaced with the VM name. More details on how + to use `prlctl` are below. + * `prlctl_version_file` (string) - The path within the virtual machine to upload a file that contains the `prlctl` version that was used to create the machine. This information can be useful for provisioning. By default this is @@ -187,7 +190,7 @@ Optional: * `ssh_password` (string) - The password for `ssh_username` to use to authenticate with SSH. By default this is the empty string. -* `ssh_port` (int) - The port that SSH will be listening on in the guest +* `ssh_port` (integer) - The port that SSH will be listening on in the guest virtual machine. By default this is 22. * `ssh_wait_timeout` (string) - The duration to wait for SSH to become diff --git a/website/source/docs/builders/parallels-pvm.html.markdown b/website/source/docs/builders/parallels-pvm.html.markdown index 0e32ab975..24ca7f439 100644 --- a/website/source/docs/builders/parallels-pvm.html.markdown +++ b/website/source/docs/builders/parallels-pvm.html.markdown @@ -43,7 +43,7 @@ There are many configuration options available for the Parallels builder. They are organized below into two categories: required and optional. Within each category, the available options are alphabetized and described. -Required: +### Required: * `source_path` (string) - The path to a PVM directory that acts as the source of this build. @@ -51,7 +51,7 @@ Required: * `ssh_username` (string) - The username to use to SSH into the machine once the OS is installed. -Optional: +### Optional: * `floppy_files` (array of strings) - A list of files to put onto a floppy disk that is attached when the VM is booted for the first time. This is @@ -67,21 +67,6 @@ Optional: By default this is "output-BUILDNAME" where "BUILDNAME" is the name of the build. -* `prlctl` (array of array of strings) - Custom `prlctl` commands to execute in - order to further customize the virtual machine being created. The value of - this is an array of commands to execute. The commands are executed in the order - defined in the template. For each command, the command is defined itself as an - array of strings, where each string represents a single argument on the - command-line to `prlctl` (but excluding `prlctl` itself). Each arg is treated - as a [configuration template](/docs/templates/configuration-templates.html), - where the `Name` variable is replaced with the VM name. More details on how - to use `prlctl` are below. - -* `parallels_tools_mode` (string) - The method by which Parallels tools are - made available to the guest for installation. Valid options are "upload", - "attach", or "disable". The functions of each of these should be - self-explanatory. The default value is "upload". - * `parallels_tools_guest_path` (string) - The path on the guest virtual machine where the Parallels tools ISO will be uploaded. By default this is "prl-tools.iso" which should upload into the login directory of the user. @@ -93,6 +78,21 @@ Optional: the Parallels installation: "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso" +* `parallels_tools_mode` (string) - The method by which Parallels tools are + made available to the guest for installation. Valid options are "upload", + "attach", or "disable". The functions of each of these should be + self-explanatory. The default value is "upload". + +* `prlctl` (array of array of strings) - Custom `prlctl` commands to execute in + order to further customize the virtual machine being created. The value of + this is an array of commands to execute. The commands are executed in the order + defined in the template. For each command, the command is defined itself as an + array of strings, where each string represents a single argument on the + command-line to `prlctl` (but excluding `prlctl` itself). Each arg is treated + as a [configuration template](/docs/templates/configuration-templates.html), + where the `Name` variable is replaced with the VM name. More details on how + to use `prlctl` are below. + * `prlctl_version_file` (string) - The path within the virtual machine to upload a file that contains the `prlctl` version that was used to create the machine. This information can be useful for provisioning. By default this is @@ -115,14 +115,13 @@ Optional: * `ssh_password` (string) - The password for `ssh_username` to use to authenticate with SSH. By default this is the empty string. -* `ssh_port` (int) - The port that SSH will be listening on in the guest +* `ssh_port` (integer) - The port that SSH will be listening on in the guest virtual machine. By default this is 22. * `ssh_wait_timeout` (string) - The duration to wait for SSH to become available. By default this is "20m", or 20 minutes. Note that this should be quite long since the timer begins as soon as the virtual machine is booted. - * `vm_name` (string) - This is the name of the virtual machine when it is imported as well as the name of the PVM directory when the virtual machine is exported. By default this is "packer-BUILDNAME", where "BUILDNAME" is diff --git a/website/source/docs/builders/qemu.html.markdown b/website/source/docs/builders/qemu.html.markdown index 9cb78d2e6..882aab816 100644 --- a/website/source/docs/builders/qemu.html.markdown +++ b/website/source/docs/builders/qemu.html.markdown @@ -70,7 +70,7 @@ There are many configuration options available for the Qemu builder. They are organized below into two categories: required and optional. Within each category, the available options are alphabetized and described. -Required: +### Required: * `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO files are so large, this is required and Packer will verify it prior @@ -88,7 +88,7 @@ Required: * `ssh_username` (string) - The username to use to SSH into the machine once the OS is installed. -Optional: +### Optional: * `accelerator` (string) - The accelerator type to use when running the VM. This may have a value of either "kvm" or "xen" and you must have that @@ -107,7 +107,7 @@ Optional: five seconds and one minute 30 seconds, respectively. If this isn't specified, the default is 10 seconds. -* `disk_size` (int) - The size, in megabytes, of the hard disk to create +* `disk_size` (integer) - The size, in megabytes, of the hard disk to create for the VM. By default, this is 40000 (about 40 GB). * `disk_interface` (string) - The interface to use for the disk. Allowed @@ -115,9 +115,6 @@ Optional: commands or kickstart type scripts must have proper adjustments for resulting device names. The Qemu builder uses "virtio" by default. -* `format` (string) - Either "qcow2" or "raw", this specifies the output - format of the virtual machine image. This defaults to "qcow2". - * `floppy_files` (array of strings) - A list of files to place onto a floppy disk that is attached when the VM is booted. This is most useful for unattended Windows installs, which look for an `Autounattend.xml` file @@ -128,7 +125,10 @@ Optional: characters (*, ?, and []) are allowed. Directory names are also allowed, which will add all the files found in the directory to the floppy. -* `headless` (bool) - Packer defaults to building virtual machines by +* `format` (string) - Either "qcow2" or "raw", this specifies the output + format of the virtual machine image. This defaults to "qcow2". + +* `headless` (boolean) - Packer defaults to building virtual machines by launching a GUI that shows the console of the machine being built. When this value is set to true, the machine will start without a console. @@ -140,7 +140,7 @@ Optional: available as variables in `boot_command`. This is covered in more detail below. -* `http_port_min` and `http_port_max` (int) - These are the minimum and +* `http_port_min` and `http_port_max` (integer) - These are the minimum and maximum port to use for the HTTP server started to serve the `http_directory`. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to run the HTTP server. If you want to force the HTTP @@ -157,6 +157,13 @@ Optional: values "ne2k_pci," "i82551," "i82557b," "i82559er," "rtl8139," "e1000," "pcnet" or "virtio." The Qemu builder uses "virtio" by default. +* `output_directory` (string) - This is the path to the directory where the + resulting virtual machine will be created. This may be relative or absolute. + If relative, the path is relative to the working directory when `packer` + is executed. This directory must not exist or be empty prior to running the builder. + By default this is "output-BUILDNAME" where "BUILDNAME" is the name + of the build. + * `qemuargs` (array of array of strings) - Allows complete control over the qemu command line (though not, at this time, qemu-img). Each array of strings makes up a command line switch that overrides matching default @@ -194,13 +201,6 @@ Optional: qemu-system-x86 -m 1024m --no-acpi -netdev user,id=mynet0,hostfwd=hostip:hostport-guestip:guestport -device virtio-net,netdev=mynet0" -* `output_directory` (string) - This is the path to the directory where the - resulting virtual machine will be created. This may be relative or absolute. - If relative, the path is relative to the working directory when `packer` - is executed. This directory must not exist or be empty prior to running the builder. - By default this is "output-BUILDNAME" where "BUILDNAME" is the name - of the build. - * `qemu_binary` (string) - The name of the Qemu binary to look for. This defaults to "qemu-system-x86_64", but may need to be changed for some platforms. For example "qemu-kvm", or "qemu-system-i386" may be a better @@ -229,7 +229,7 @@ Optional: * `ssh_password` (string) - The password for `ssh_username` to use to authenticate with SSH. By default this is the empty string. -* `ssh_port` (int) - The port that SSH will be listening on in the guest +* `ssh_port` (integer) - The port that SSH will be listening on in the guest virtual machine. By default this is 22. The Qemu builder will map, via port forward, a port on the host machine to the port listed here so machines outside the installing VM can access the VM. @@ -242,6 +242,12 @@ Optional: the new virtual machine, without the file extension. By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. +* `vnc_port_min` and `vnc_port_max` (integer) - The minimum and + maximum port to use for the VNC port on the host machine which is forwarded + to the VNC port on the guest machine. Because Packer often runs in parallel, + Packer will choose a randomly available port in this range to use as the + host port. + ## Boot Command The `boot_command` configuration is very important: it specifies the keys diff --git a/website/source/docs/builders/virtualbox-iso.html.markdown b/website/source/docs/builders/virtualbox-iso.html.markdown index 8e5eaa373..f435250cf 100644 --- a/website/source/docs/builders/virtualbox-iso.html.markdown +++ b/website/source/docs/builders/virtualbox-iso.html.markdown @@ -46,7 +46,7 @@ There are many configuration options available for the VirtualBox builder. They are organized below into two categories: required and optional. Within each category, the available options are alphabetized and described. -Required: +### Required: * `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO files are so large, this is required and Packer will verify it prior @@ -67,7 +67,7 @@ Required: * `ssh_username` (string) - The username to use to SSH into the machine once the OS is installed. -Optional: +### Optional: * `boot_command` (array of strings) - This is an array of commands to type when the virtual machine is first booted. The goal of these commands should @@ -82,9 +82,13 @@ Optional: five seconds and one minute 30 seconds, respectively. If this isn't specified, the default is 10 seconds. -* `disk_size` (int) - The size, in megabytes, of the hard disk to create +* `disk_size` (integer) - The size, in megabytes, of the hard disk to create for the VM. By default, this is 40000 (about 40 GB). +* `export_opts` (array of strings) - Additional options to pass to the `VBoxManage export`. + This can be useful for passing product information to include in the resulting + appliance file. + * `floppy_files` (array of strings) - A list of files to place onto a floppy disk that is attached when the VM is booted. This is most useful for unattended Windows installs, which look for an `Autounattend.xml` file @@ -131,7 +135,7 @@ Optional: hard drive is attached to, defaults to "ide". When set to "sata", the drive is attached to an AHCI SATA controller. -* `headless` (bool) - Packer defaults to building VirtualBox +* `headless` (boolean) - Packer defaults to building VirtualBox virtual machines by launching a GUI that shows the console of the machine being built. When this value is set to true, the machine will start without a console. @@ -144,7 +148,7 @@ Optional: available as variables in `boot_command`. This is covered in more detail below. -* `http_port_min` and `http_port_max` (int) - These are the minimum and +* `http_port_min` and `http_port_max` (integer) - These are the minimum and maximum port to use for the HTTP server started to serve the `http_directory`. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to run the HTTP server. If you want to force the HTTP @@ -173,7 +177,7 @@ Optional: If it doesn't shut down in this time, it is an error. By default, the timeout is "5m", or five minutes. -* `ssh_host_port_min` and `ssh_host_port_max` (uint) - The minimum and +* `ssh_host_port_min` and `ssh_host_port_max` (integer) - The minimum and maximum port to use for the SSH port on the host machine which is forwarded to the SSH port on the guest machine. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to use as the @@ -187,7 +191,7 @@ Optional: * `ssh_password` (string) - The password for `ssh_username` to use to authenticate with SSH. By default this is the empty string. -* `ssh_port` (int) - The port that SSH will be listening on in the guest +* `ssh_port` (integer) - The port that SSH will be listening on in the guest virtual machine. By default this is 22. * `ssh_wait_timeout` (string) - The duration to wait for SSH to become @@ -211,17 +215,13 @@ Optional: * `virtualbox_version_file` (string) - The path within the virtual machine to upload a file that contains the VirtualBox version that was used to create the machine. This information can be useful for provisioning. - By default this is ".vbox_version", which will generally upload it into + By default this is ".vbox_version", which will generally be upload it into the home directory. * `vm_name` (string) - This is the name of the OVF file for the new virtual machine, without the file extension. By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. -* `export_opts` (array of strings) - Additional options to pass to the `VBoxManage export`. - This can be useful for passing product information to include in the resulting - appliance file. - ## Boot Command The `boot_command` configuration is very important: it specifies the keys diff --git a/website/source/docs/builders/virtualbox-ovf.html.markdown b/website/source/docs/builders/virtualbox-ovf.html.markdown index e1a170d4c..4a41021b2 100644 --- a/website/source/docs/builders/virtualbox-ovf.html.markdown +++ b/website/source/docs/builders/virtualbox-ovf.html.markdown @@ -42,7 +42,7 @@ There are many configuration options available for the VirtualBox builder. They are organized below into two categories: required and optional. Within each category, the available options are alphabetized and described. -Required: +### Required: * `source_path` (string) - The path to an OVF or OVA file that acts as the source of this build. @@ -50,7 +50,11 @@ Required: * `ssh_username` (string) - The username to use to SSH into the machine once the OS is installed. -Optional: +### Optional: + +* `export_opts` (array of strings) - Additional options to pass to the `VBoxManage export`. + This can be useful for passing product information to include in the resulting + appliance file. * `floppy_files` (array of strings) - A list of files to place onto a floppy disk that is attached when the VM is booted. This is most useful @@ -87,11 +91,15 @@ Optional: By default the VirtualBox builder will go and download the proper guest additions ISO from the internet. -* `headless` (bool) - Packer defaults to building VirtualBox +* `headless` (boolean) - Packer defaults to building VirtualBox virtual machines by launching a GUI that shows the console of the machine being built. When this value is set to true, the machine will start without a console. +* `import_opts` (string) - Additional options to pass to the `VBoxManage import`. + This can be useful for passing "keepallmacs" or "keepnatmacs" options for existing + ovf images. + * `output_directory` (string) - This is the path to the directory where the resulting virtual machine will be created. This may be relative or absolute. If relative, the path is relative to the working directory when `packer` @@ -108,7 +116,7 @@ Optional: If it doesn't shut down in this time, it is an error. By default, the timeout is "5m", or five minutes. -* `ssh_host_port_min` and `ssh_host_port_max` (uint) - The minimum and +* `ssh_host_port_min` and `ssh_host_port_max` (integer) - The minimum and maximum port to use for the SSH port on the host machine which is forwarded to the SSH port on the guest machine. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to use as the @@ -122,7 +130,7 @@ Optional: * `ssh_password` (string) - The password for `ssh_username` to use to authenticate with SSH. By default this is the empty string. -* `ssh_port` (int) - The port that SSH will be listening on in the guest +* `ssh_port` (integer) - The port that SSH will be listening on in the guest virtual machine. By default this is 22. * `ssh_wait_timeout` (string) - The duration to wait for SSH to become @@ -146,7 +154,7 @@ Optional: * `virtualbox_version_file` (string) - The path within the virtual machine to upload a file that contains the VirtualBox version that was used to create the machine. This information can be useful for provisioning. - By default this is ".vbox_version", which will generally upload it into + By default this is ".vbox_version", which will generally be upload it into the home directory. * `vm_name` (string) - This is the name of the virtual machine when it is @@ -154,14 +162,6 @@ Optional: exported. By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. -* `import_opts` (string) - Additional options to pass to the `VBoxManage import`. - This can be useful for passing "keepallmacs" or "keepnatmacs" options for existing - ovf images. - -* `export_opts` (array of strings) - Additional options to pass to the `VBoxManage export`. - This can be useful for passing product information to include in the resulting - appliance file. - ## Guest Additions Packer will automatically download the proper guest additions for the diff --git a/website/source/docs/builders/vmware-iso.html.markdown b/website/source/docs/builders/vmware-iso.html.markdown index ce3359140..7e5f3437a 100644 --- a/website/source/docs/builders/vmware-iso.html.markdown +++ b/website/source/docs/builders/vmware-iso.html.markdown @@ -47,7 +47,7 @@ There are many configuration options available for the VMware builder. They are organized below into two categories: required and optional. Within each category, the available options are alphabetized and described. -Required: +### Required: * `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO files are so large, this is required and Packer will verify it prior @@ -68,7 +68,7 @@ Required: * `ssh_username` (string) - The username to use to SSH into the machine once the OS is installed. -Optional: +### Optional: * `boot_command` (array of strings) - This is an array of commands to type when the virtual machine is firsted booted. The goal of these commands should @@ -83,7 +83,7 @@ Optional: five seconds and one minute 30 seconds, respectively. If this isn't specified, the default is 10 seconds. -* `disk_size` (int) - The size of the hard disk for the VM in megabytes. +* `disk_size` (integer) - The size of the hard disk for the VM in megabytes. The builder uses expandable, not fixed-size virtual hard disks, so the actual file representing the disk will not use the full size unless it is full. By default this is set to 40,000 (about 40 GB). @@ -114,7 +114,7 @@ Optional: OS type, VMware may perform some optimizations or virtual hardware changes to better support the operating system running in the virtual machine. -* `headless` (bool) - Packer defaults to building VMware +* `headless` (boolean) - Packer defaults to building VMware virtual machines by launching a GUI that shows the console of the machine being built. When this value is set to true, the machine will start without a console. For VMware machines, Packer will output VNC @@ -129,7 +129,7 @@ Optional: available as variables in `boot_command`. This is covered in more detail below. -* `http_port_min` and `http_port_max` (int) - These are the minimum and +* `http_port_min` and `http_port_max` (integer) - These are the minimum and maximum port to use for the HTTP server started to serve the `http_directory`. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to run the HTTP server. If you want to force the HTTP @@ -149,11 +149,6 @@ Optional: By default this is "output-BUILDNAME" where "BUILDNAME" is the name of the build. -* `remote_type` (string) - The type of remote machine that will be used to - build this VM rather than a local desktop product. The only value accepted - for this currently is "esx5". If this is not set, a desktop product will be - used. By default, this is not set. - * `remote_datastore` (string) - The path to the datastore where the resulting VM will be stored when it is built on the remote machine. By default this is "datastore1". This only has an effect if `remote_type` is enabled. @@ -165,15 +160,14 @@ Optional: access the remote machine. By default this is empty. This only has an effect if `remote_type` is enabled. +* `remote_type` (string) - The type of remote machine that will be used to + build this VM rather than a local desktop product. The only value accepted + for this currently is "esx5". If this is not set, a desktop product will be + used. By default, this is not set. + * `remote_username` (string) - The username for the SSH user that will access the remote machine. This is required if `remote_type` is enabled. -* `skip_compaction` (bool) - VMware-created disks are defragmented - and compacted at the end of the build process using `vmware-vdiskmanager`. - In certain rare cases, this might actually end up making the resulting disks - slightly larger. If you find this to be the case, you can disable compaction - using this configuration value. - * `shutdown_command` (string) - The command to use to gracefully shut down the machine once all the provisioning is done. By default this is an empty string, which tells Packer to just forcefully shut down the machine. @@ -183,21 +177,27 @@ Optional: If it doesn't shut down in this time, it is an error. By default, the timeout is "5m", or five minutes. +* `skip_compaction` (boolean) - VMware-created disks are defragmented + and compacted at the end of the build process using `vmware-vdiskmanager`. + In certain rare cases, this might actually end up making the resulting disks + slightly larger. If you find this to be the case, you can disable compaction + using this configuration value. + +* `ssh_host` (string) - Hostname or IP address of the host. By default, DHCP + is used to connect to the host and this field is not used. + * `ssh_key_path` (string) - Path to a private key to use for authenticating with SSH. By default this is not set (key-based auth won't be used). The associated public key is expected to already be configured on the VM being prepared by some other process (kickstart, etc.). -* `ssh_host` (string) - Hostname or IP address of the host. By default, DHCP - is used to connect to the host and this field is not used. - * `ssh_password` (string) - The password for `ssh_username` to use to authenticate with SSH. By default this is the empty string. -* `ssh_port` (int) - The port that SSH will listen on within the virtual +* `ssh_port` (integer) - The port that SSH will listen on within the virtual machine. By default this is 22. -* `ssh_skip_request_pty` (bool) - If true, a pty will not be requested as +* `ssh_skip_request_pty` (boolean) - If true, a pty will not be requested as part of the SSH connection. By default, this is "false", so a pty _will_ be requested. @@ -223,16 +223,10 @@ Optional: * `vmdk_name` (string) - The filename of the virtual disk that'll be created, without the extension. This defaults to "packer". -* `vmx_data` (object, string keys and string values) - Arbitrary key/values +* `vmx_data` (object of key/value strings) - Arbitrary key/values to enter into the virtual machine VMX file. This is for advanced users who want to set properties such as memory, CPU, etc. -* `vnc_port_min` and `vnc_port_max` (int) - The minimum and maximum port to - use for VNC access to the virtual machine. The builder uses VNC to type - the initial `boot_command`. Because Packer generally runs in parallel, Packer - uses a randomly chosen port in this range that appears available. By default - this is 5900 to 6000. The minimum and maximum ports are inclusive. - * `vmx_template_path` (string) - Path to a [configuration template](/docs/templates/configuration-templates.html) that defines the contents of the virtual machine VMX file for VMware. This is @@ -240,6 +234,12 @@ Optional: non-functional. See below for more information. For basic VMX modifications, try `vmx_data` first. +* `vnc_port_min` and `vnc_port_max` (integer) - The minimum and maximum port to + use for VNC access to the virtual machine. The builder uses VNC to type + the initial `boot_command`. Because Packer generally runs in parallel, Packer + uses a randomly chosen port in this range that appears available. By default + this is 5900 to 6000. The minimum and maximum ports are inclusive. + ## Boot Command The `boot_command` configuration is very important: it specifies the keys diff --git a/website/source/docs/builders/vmware-vmx.html.markdown b/website/source/docs/builders/vmware-vmx.html.markdown index eacaa82c0..2f1ecfa73 100644 --- a/website/source/docs/builders/vmware-vmx.html.markdown +++ b/website/source/docs/builders/vmware-vmx.html.markdown @@ -42,20 +42,14 @@ There are many configuration options available for the VMware builder. They are organized below into two categories: required and optional. Within each category, the available options are alphabetized and described. -Required: +### Required: * `source_path` (string) - Path to the source VMX file to clone. * `ssh_username` (string) - The username to use to SSH into the machine once the OS is installed. -Optional: - -* `boot_wait` (string) - The time to wait after booting the initial virtual - machine before typing the `boot_command`. The value of this should be - a duration. Examples are "5s" and "1m30s" which will cause Packer to wait - five seconds and one minute 30 seconds, respectively. If this isn't specified, - the default is 10 seconds. +### Optional: * `floppy_files` (array of strings) - A list of files to place onto a floppy disk that is attached when the VM is booted. This is most useful @@ -71,7 +65,7 @@ Optional: is "/Applications/VMware Fusion.app" but this setting allows you to customize this. -* `headless` (bool) - Packer defaults to building VMware +* `headless` (boolean) - Packer defaults to building VMware virtual machines by launching a GUI that shows the console of the machine being built. When this value is set to true, the machine will start without a console. For VMware machines, Packer will output VNC @@ -85,12 +79,6 @@ Optional: By default this is "output-BUILDNAME" where "BUILDNAME" is the name of the build. -* `skip_compaction` (bool) - VMware-created disks are defragmented - and compacted at the end of the build process using `vmware-vdiskmanager`. - In certain rare cases, this might actually end up making the resulting disks - slightly larger. If you find this to be the case, you can disable compaction - using this configuration value. - * `shutdown_command` (string) - The command to use to gracefully shut down the machine once all the provisioning is done. By default this is an empty string, which tells Packer to just forcefully shut down the machine. @@ -100,6 +88,12 @@ Optional: If it doesn't shut down in this time, it is an error. By default, the timeout is "5m", or five minutes. +* `skip_compaction` (boolean) - VMware-created disks are defragmented + and compacted at the end of the build process using `vmware-vdiskmanager`. + In certain rare cases, this might actually end up making the resulting disks + slightly larger. If you find this to be the case, you can disable compaction + using this configuration value. + * `ssh_key_path` (string) - Path to a private key to use for authenticating with SSH. By default this is not set (key-based auth won't be used). The associated public key is expected to already be configured on the @@ -108,10 +102,10 @@ Optional: * `ssh_password` (string) - The password for `ssh_username` to use to authenticate with SSH. By default this is the empty string. -* `ssh_port` (int) - The port that SSH will listen on within the virtual +* `ssh_port` (integer) - The port that SSH will listen on within the virtual machine. By default this is 22. -* `ssh_skip_request_pty` (bool) - If true, a pty will not be requested as +* `ssh_skip_request_pty` (boolean) - If true, a pty will not be requested as part of the SSH connection. By default, this is "false", so a pty _will_ be requested. @@ -123,6 +117,6 @@ Optional: machine, without the file extension. By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. -* `vmx_data` (object, string keys and string values) - Arbitrary key/values +* `vmx_data` (object of key/value strings) - Arbitrary key/values to enter into the virtual machine VMX file. This is for advanced users who want to set properties such as memory, CPU, etc. diff --git a/website/source/docs/command-line/build.html.markdown b/website/source/docs/command-line/build.html.markdown index 198bd85b4..ff111b477 100644 --- a/website/source/docs/command-line/build.html.markdown +++ b/website/source/docs/command-line/build.html.markdown @@ -18,16 +18,16 @@ artifacts that are created will be outputted at the end of the build. between each step, waiting for keyboard input before continuing. This will allow the user to inspect state and so on. +* `-except=foo,bar,baz` - Builds all the builds except those with the given + comma-separated names. Build names by default are the names of their builders, + unless a specific `name` attribute is specified within the configuration. + * `-force` - Forces a builder to run when artifacts from a previous build prevent a build from running. The exact behavior of a forced build is left to the builder. In general, a builder supporting the forced build will remove the artifacts from the previous build. This will allow the user to repeat a build without having to manually clean these artifacts beforehand. -* `-except=foo,bar,baz` - Builds all the builds except those with the given - comma-separated names. Build names by default are the names of their builders, - unless a specific `name` attribute is specified within the configuration. - * `-only=foo,bar,baz` - Only build the builds with the given comma-separated names. Build names by default are the names of their builders, unless a specific `name` attribute is specified within the configuration. diff --git a/website/source/docs/other/core-configuration.html.markdown b/website/source/docs/other/core-configuration.html.markdown index 8592b2e08..42705384a 100644 --- a/website/source/docs/other/core-configuration.html.markdown +++ b/website/source/docs/other/core-configuration.html.markdown @@ -25,7 +25,7 @@ The format of the configuration file is basic JSON. Below is the list of all available configuration parameters for the core configuration file. None of these are required, since all have sane defaults. -* `plugin_min_port` and `plugin_max_port` (int) - These are the minimum and +* `plugin_min_port` and `plugin_max_port` (integer) - These are the minimum and maximum ports that Packer uses for communication with plugins, since plugin communication happens over TCP connections on your local host. By default these are 10,000 and 25,000, respectively. Be sure to set a fairly diff --git a/website/source/docs/post-processors/vagrant.html.markdown b/website/source/docs/post-processors/vagrant.html.markdown index ed7f7708b..bffcafddd 100644 --- a/website/source/docs/post-processors/vagrant.html.markdown +++ b/website/source/docs/post-processors/vagrant.html.markdown @@ -59,6 +59,9 @@ below, with more details about certain options in following sections. of the Vagrant box (regardless of their paths). They can then be used from the Vagrantfile. +* `keep_input_artifact` (boolean) - If set to true, do not delete the + `output_directory` on a successful build. Defaults to false. + * `output` (string) - The full path to the box file that will be created by this post-processor. This is a [configuration template](/docs/templates/configuration-templates.html). diff --git a/website/source/docs/post-processors/vsphere.html.markdown b/website/source/docs/post-processors/vsphere.html.markdown index 8dde0e8fb..408d8b327 100644 --- a/website/source/docs/post-processors/vsphere.html.markdown +++ b/website/source/docs/post-processors/vsphere.html.markdown @@ -43,7 +43,7 @@ Optional: * `disk_mode` (string) - Target disk format. See `ovftool` manual for available options. By default, "thick" will be used. -* `insecure` (bool) - Whether or not the connection to vSphere can be done +* `insecure` (boolean) - Whether or not the connection to vSphere can be done over an insecure connection. By default this is false. * `vm_folder` (string) - The folder within the datastore to store the VM. diff --git a/website/source/docs/provisioners/chef-solo.html.markdown b/website/source/docs/provisioners/chef-solo.html.markdown index 5e68d933b..e3852cb20 100644 --- a/website/source/docs/provisioners/chef-solo.html.markdown +++ b/website/source/docs/provisioners/chef-solo.html.markdown @@ -63,15 +63,15 @@ configuration is actually required, but at least `run_list` is recommended. * `json` (object) - An arbitrary mapping of JSON that will be available as node attributes while running Chef. -* `remote_cookbook_paths` (array of string) - A list of paths on the remote - machine where cookbooks will already exist. These may exist from a previous - provisioner or step. If specified, Chef will be configured to look for - cookbooks here. By default, this is empty. - * `prevent_sudo` (boolean) - By default, the configured commands that are executed to install and run Chef are executed with `sudo`. If this is true, then the sudo will be omitted. +* `remote_cookbook_paths` (array of strings) - A list of paths on the remote + machine where cookbooks will already exist. These may exist from a previous + provisioner or step. If specified, Chef will be configured to look for + cookbooks here. By default, this is empty. + * `roles_path` (string) - The path to the "roles" directory on your local filesystem. These will be uploaded to the remote machine in the directory specified by the `staging_directory`. By default, this is empty. diff --git a/website/source/docs/provisioners/puppet-server.html.markdown b/website/source/docs/provisioners/puppet-server.html.markdown index 413e1831e..11a7bbd96 100644 --- a/website/source/docs/provisioners/puppet-server.html.markdown +++ b/website/source/docs/provisioners/puppet-server.html.markdown @@ -54,16 +54,16 @@ required. They are listed below: * `options` (string) - Additional command line options to pass to `puppet agent` when Puppet is ran. +* `prevent_sudo` (boolean) - By default, the configured commands that are + executed to run Puppet are executed with `sudo`. If this is true, + then the sudo will be omitted. + * `puppet_node` (string) - The name of the node. If this isn't set, the fully qualified domain name will be used. * `puppet_server` (string) - Hostname of the Puppet server. By default "puppet" will be used. -* `prevent_sudo` (boolean) - By default, the configured commands that are - executed to run Puppet are executed with `sudo`. If this is true, - then the sudo will be omitted. - * `staging_directory` (string) - This is the directory where all the configuration of Puppet by Packer will be placed. By default this is "/tmp/packer-puppet-server". This directory doesn't need to exist but must have proper permissions so that diff --git a/website/source/docs/provisioners/salt-masterless.html.markdown b/website/source/docs/provisioners/salt-masterless.html.markdown index c0dbead7c..76128a87c 100644 --- a/website/source/docs/provisioners/salt-masterless.html.markdown +++ b/website/source/docs/provisioners/salt-masterless.html.markdown @@ -27,27 +27,27 @@ The reference of available configuration options is listed below. The only requi Optional: -* `local_state_tree` (string) - The path to your local - [state tree](http://docs.saltstack.com/ref/states/highstate.html#the-salt-state-tree). - This will be uploaded to the `/srv/salt` on the remote. - -* `local_pillar_roots` (string) - The path to your local - [pillar roots](http://docs.saltstack.com/ref/configuration/master.html#pillar-configuration). - This will be uploaded to the `/srv/pillar` on the remote. - -* `skip_bootstrap` (boolean) - By default the salt provisioner runs - [salt bootstrap](https://github.com/saltstack/salt-bootstrap) to install - salt. Set this to true to skip this step. - * `bootstrap_args` (string) - Arguments to send to the bootstrap script. Usage is somewhat documented on [github](https://github.com/saltstack/salt-bootstrap), but the [script itself](https://github.com/saltstack/salt-bootstrap/blob/develop/bootstrap-salt.sh) has more detailed usage instructions. By default, no arguments are sent to the script. +* `local_pillar_roots` (string) - The path to your local + [pillar roots](http://docs.saltstack.com/ref/configuration/master.html#pillar-configuration). + This will be uploaded to the `/srv/pillar` on the remote. + +* `local_state_tree` (string) - The path to your local + [state tree](http://docs.saltstack.com/ref/states/highstate.html#the-salt-state-tree). + This will be uploaded to the `/srv/salt` on the remote. + * `minion_config` (string) - The path to your local [minion config](http://docs.saltstack.com/topics/configuration.html). This will be uploaded to the `/etc/salt` on the remote. +* `skip_bootstrap` (boolean) - By default the salt provisioner runs + [salt bootstrap](https://github.com/saltstack/salt-bootstrap) to install + salt. Set this to true to skip this step. + * `temp_config_dir` (string) - Where your local state tree will be copied before moving to the `/srv/salt` directory. Default is `/tmp/salt`. diff --git a/website/source/docs/templates/introduction.html.markdown b/website/source/docs/templates/introduction.html.markdown index 6b11cc851..1bf3bc282 100644 --- a/website/source/docs/templates/introduction.html.markdown +++ b/website/source/docs/templates/introduction.html.markdown @@ -20,28 +20,15 @@ A template is a JSON object that has a set of keys configuring various components of Packer. The available keys within a template are listed below. Along with each key, it is noted whether it is required or not. -* `description` (optional) is a string providing a description of what - the template does. This output is used only in the - [inspect command](/docs/command-line/inspect.html). - * `builders` (_required_) is an array of one or more objects that defines the builders that will be used to create machine images for this template, and configures each of those builders. For more information on how to define and configure a builder, read the sub-section on [configuring builders in templates](/docs/templates/builders.html). -* `provisioners` (optional) is an array of one or more objects that defines - the provisioners that will be used to install and configure software for - the machines created by each of the builders. This is an optional field. - If it is not specified, then no provisioners will be run. For more - information on how to define and configure a provisioner, read the - sub-section on [configuring provisioners in templates](/docs/templates/provisioners.html). - -* `post-processors` (optional) is an array of one or more objects that defines the - various post-processing steps to take with the built images. This is an optional - field. If not specified, then no post-processing will be done. For more - information on what post-processors do and how they're defined, read the - sub-section on [configuring post-processors in templates](/docs/templates/post-processors.html). +* `description` (optional) is a string providing a description of what + the template does. This output is used only in the + [inspect command](/docs/command-line/inspect.html). * `min_packer_version` (optional) is a string that has a minimum Packer version that is required to parse the template. This can be used to @@ -49,6 +36,25 @@ Along with each key, it is noted whether it is required or not. max version can't be specified because Packer retains backwards compatibility with `packer fix`. +* `post-processors` (optional) is an array of one or more objects that defines the + various post-processing steps to take with the built images. If not specified, + then no post-processing will be done. For more + information on what post-processors do and how they're defined, read the + sub-section on [configuring post-processors in templates](/docs/templates/post-processors.html). + +* `provisioners` (optional) is an array of one or more objects that defines + the provisioners that will be used to install and configure software for + the machines created by each of the builders. If it is not specified, + then no provisioners will be run. For more + information on how to define and configure a provisioner, read the + sub-section on [configuring provisioners in templates](/docs/templates/provisioners.html). + +* `variables` (optional) is an array of one or more key/value strings that defines + user variables contained in the template. + If it is not specified, then no variables are defined. + For more information on how to define and use user variables, read the + sub-section on [user variables in templates](/docs/templates/user-variables.html). + ## Example Template Below is an example of a basic template that is nearly fully functional. It is just From c9d9ba859d0eeb9ac1a9c887e7048f1547095402 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Sun, 4 May 2014 10:53:57 -0700 Subject: [PATCH 054/593] website: alphabetized configuration options (really) --- website/source/docs/builders/googlecompute.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/docs/builders/googlecompute.markdown b/website/source/docs/builders/googlecompute.markdown index 9c29046dd..8814319d1 100644 --- a/website/source/docs/builders/googlecompute.markdown +++ b/website/source/docs/builders/googlecompute.markdown @@ -99,13 +99,13 @@ each category, the available options are alphabetized and described. * `instance_name` (string) - A name to give the launched instance. Beware that this must be unique. Defaults to "packer-{{uuid}}". +* `machine_type` (string) - The machine type. Defaults to `n1-standard-1`. + * `metadata` (object of key/value strings) -* `machine_type` (string) - The machine type. Defaults to `n1-standard-1`. - * `network` (string) - The Google Compute network to use for the launched instance. Defaults to `default`. From 1b0f770ab6cd231d27db96896e6c14ed3c5f4b18 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Sun, 4 May 2014 11:05:50 -0700 Subject: [PATCH 055/593] website: documented floppy_path and iso_path --- website/source/docs/builders/vmware-iso.html.markdown | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/source/docs/builders/vmware-iso.html.markdown b/website/source/docs/builders/vmware-iso.html.markdown index 7e5f3437a..b1ee47517 100644 --- a/website/source/docs/builders/vmware-iso.html.markdown +++ b/website/source/docs/builders/vmware-iso.html.markdown @@ -105,6 +105,9 @@ each category, the available options are alphabetized and described. characters (*, ?, and []) are allowed. Directory names are also allowed, which will add all the files found in the directory to the floppy. +* `floppy_path` (string) - Remote path to upload floppy file to. + This is required if `remote_type` is enabled, and `floppy_files` is used. + * `fusion_app_path` (string) - Path to "VMware Fusion.app". By default this is "/Applications/VMware Fusion.app" but this setting allows you to customize this. @@ -136,6 +139,9 @@ each category, the available options are alphabetized and described. server to be on one port, make this minimum and maximum port the same. By default the values are 8000 and 9000, respectively. +* `iso_path` (string) - Remote path to upload iso file to. + This is required if `remote_type` is enabled. + * `iso_urls` (array of strings) - Multiple URLs for the ISO to download. Packer will try these in order. If anything goes wrong attempting to download or while downloading a single URL, it will move on to the next. All URLs From 035f5dacfff453d6a090b54d8b7990e53a0ddf0e Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Sun, 4 May 2014 12:39:14 -0700 Subject: [PATCH 056/593] Revert "website: documented floppy_path and iso_path" This reverts commit 1b0f770ab6cd231d27db96896e6c14ed3c5f4b18. --- website/source/docs/builders/vmware-iso.html.markdown | 6 ------ 1 file changed, 6 deletions(-) diff --git a/website/source/docs/builders/vmware-iso.html.markdown b/website/source/docs/builders/vmware-iso.html.markdown index b1ee47517..7e5f3437a 100644 --- a/website/source/docs/builders/vmware-iso.html.markdown +++ b/website/source/docs/builders/vmware-iso.html.markdown @@ -105,9 +105,6 @@ each category, the available options are alphabetized and described. characters (*, ?, and []) are allowed. Directory names are also allowed, which will add all the files found in the directory to the floppy. -* `floppy_path` (string) - Remote path to upload floppy file to. - This is required if `remote_type` is enabled, and `floppy_files` is used. - * `fusion_app_path` (string) - Path to "VMware Fusion.app". By default this is "/Applications/VMware Fusion.app" but this setting allows you to customize this. @@ -139,9 +136,6 @@ each category, the available options are alphabetized and described. server to be on one port, make this minimum and maximum port the same. By default the values are 8000 and 9000, respectively. -* `iso_path` (string) - Remote path to upload iso file to. - This is required if `remote_type` is enabled. - * `iso_urls` (array of strings) - Multiple URLs for the ISO to download. Packer will try these in order. If anything goes wrong attempting to download or while downloading a single URL, it will move on to the next. All URLs From cfb62da090c69126ab62f1e7d5493e73d95a67e0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 4 May 2014 14:01:39 -0700 Subject: [PATCH 057/593] Update CHANGELOG.md --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99aeb4b35..09ba93f98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.6.1 (unreleased) + +BUG FIXES: + + * builder/parallels: Do not delete entire CDROM device. + * builder/virtualbox-ovf: Supports guest additions options. [GH-1120] + ## 0.6.0 (May 2, 2014) FEATURES: From 5437c0b4eabd9f5aba985366c9be98d35f1e4e22 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 4 May 2014 14:14:34 -0700 Subject: [PATCH 058/593] Update version for dev --- packer/version.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packer/version.go b/packer/version.go index 0758435dd..94582624c 100644 --- a/packer/version.go +++ b/packer/version.go @@ -10,12 +10,12 @@ import ( var GitCommit string // The version of packer. -const Version = "0.6.0" +const Version = "0.6.1" // Any pre-release marker for the version. If this is "" (empty string), // then it means that it is a final release. Otherwise, this is the // pre-release marker. -const VersionPrerelease = "" +const VersionPrerelease = "dev" type versionCommand byte From 6a3619600a6d4c7dc07c00f4473a6742f402e5ef Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Mon, 5 May 2014 08:48:48 -0700 Subject: [PATCH 059/593] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09ba93f98..3b183146b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ BUG FIXES: * builder/parallels: Do not delete entire CDROM device. * builder/virtualbox-ovf: Supports guest additions options. [GH-1120] + * builder/vmware: Remote ESXi builder now uploads floppy. [GH-1106] ## 0.6.0 (May 2, 2014) From 0048eaf7ffbf96dfdb33d3cd04f5f95010b76807 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Mon, 5 May 2014 08:49:33 -0700 Subject: [PATCH 060/593] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b183146b..e490848ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ BUG FIXES: - * builder/parallels: Do not delete entire CDROM device. + * builder/parallels: Do not delete entire CDROM device. [GH-1115] * builder/virtualbox-ovf: Supports guest additions options. [GH-1120] * builder/vmware: Remote ESXi builder now uploads floppy. [GH-1106] From 9b31c5c8eb76fc913a8b5d7e24278bf816172a82 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Mon, 5 May 2014 09:03:00 -0700 Subject: [PATCH 061/593] Update docs.erb Sort builders alphabetically --- website/source/layouts/docs.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 5fe4f6eaf..0f82752a4 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -35,10 +35,10 @@
  • Docker
  • Google Compute Engine
  • OpenStack
  • +
  • Parallels
  • QEMU
  • VirtualBox
  • VMware
  • -
  • Parallels
  • Custom
  • From 7b09052845dbea7d0117904ce5e4543da0d37264 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Mon, 5 May 2014 11:46:41 -0700 Subject: [PATCH 062/593] website: fix default compression # see http://golang.org/src/pkg/compress/flate/deflate.go#L406 --- website/source/docs/post-processors/vagrant.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/docs/post-processors/vagrant.html.markdown b/website/source/docs/post-processors/vagrant.html.markdown index bffcafddd..2261d0479 100644 --- a/website/source/docs/post-processors/vagrant.html.markdown +++ b/website/source/docs/post-processors/vagrant.html.markdown @@ -28,9 +28,9 @@ providers. * AWS * DigitalOcean +* Parallels * VirtualBox * VMware -* Parallels
    Support for additional providers is planned. If the @@ -52,7 +52,7 @@ below, with more details about certain options in following sections. * `compression_level` (integer) - An integer repesenting the compression level to use when creating the Vagrant box. Valid values range from 0 to 9, with 0 being no compression and 9 being - the best compression. By default, compression is enabled at level 1. + the best compression. By default, compression is enabled at level 6. * `include` (array of strings) - Paths to files to include in the Vagrant box. These files will each be copied into the top level directory From 13135cbf349685bfe5d2a401101d59eb705656b4 Mon Sep 17 00:00:00 2001 From: Nika Jones Date: Tue, 6 May 2014 04:08:08 -0700 Subject: [PATCH 063/593] Fixes #1114, Adds upper and lower as filters for the template engine. --- packer/config_template.go | 3 +++ packer/config_template_test.go | 36 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/packer/config_template.go b/packer/config_template.go index d76e3e672..2f4df9aba 100644 --- a/packer/config_template.go +++ b/packer/config_template.go @@ -6,6 +6,7 @@ import ( "github.com/mitchellh/packer/common/uuid" "os" "strconv" + "strings" "text/template" "time" ) @@ -43,6 +44,8 @@ func NewConfigTemplate() (*ConfigTemplate, error) { "timestamp": templateTimestamp, "user": result.templateUser, "uuid": templateUuid, + "upper": strings.ToUpper, + "lower": strings.ToLower, }) return result, nil diff --git a/packer/config_template_test.go b/packer/config_template_test.go index 8604f2c43..7501f2249 100644 --- a/packer/config_template_test.go +++ b/packer/config_template_test.go @@ -130,6 +130,42 @@ func TestConfigTemplateProcess_uuid(t *testing.T) { } } +func TestConfigTemplateProcess_upper(t *testing.T) { + tpl, err := NewConfigTemplate() + if err != nil { + t.Fatalf("err: %s", err) + } + + tpl.UserVars["foo"] = "bar" + + result, err := tpl.Process(`{{user "foo" | upper}}`, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + if result != "BAR" { + t.Fatalf("bad: %s", result) + } +} + +func TestConfigTemplateProcess_lower(t *testing.T) { + tpl, err := NewConfigTemplate() + if err != nil { + t.Fatalf("err: %s", err) + } + + tpl.UserVars["foo"] = "BAR" + + result, err := tpl.Process(`{{user "foo" | lower}}`, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + if result != "bar" { + t.Fatalf("bad: %s", result) + } +} + func TestConfigTemplateValidate(t *testing.T) { tpl, err := NewConfigTemplate() if err != nil { From 2ee90b29c34846452c78692ac8c62365fcba719b Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Mon, 5 May 2014 17:44:25 +0100 Subject: [PATCH 064/593] Allow OpenStack builder to skip certificate verification --- builder/openstack/access_config.go | 16 +++++++++++++++- .../source/docs/builders/openstack.html.markdown | 3 +++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/builder/openstack/access_config.go b/builder/openstack/access_config.go index 151288679..475b859d7 100644 --- a/builder/openstack/access_config.go +++ b/builder/openstack/access_config.go @@ -1,6 +1,7 @@ package openstack import ( + "crypto/tls" "fmt" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" @@ -21,6 +22,7 @@ type AccessConfig struct { RawRegion string `mapstructure:"region"` ProxyUrl string `mapstructure:"proxy_url"` TenantId string `mapstructure:"tenant_id"` + Insecure bool `mapstructure:"insecure"` } // Auth returns a valid Auth object for access to openstack services, or @@ -51,6 +53,14 @@ func (c *AccessConfig) Auth() (gophercloud.AccessProvider, error) { Password: c.Password, } + default_transport := &http.Transport{} + + if c.Insecure { + cfg := new(tls.Config) + cfg.InsecureSkipVerify = true + default_transport.TLSClientConfig = cfg + } + // For corporate networks it may be the case where we want our API calls // to be sent through a separate HTTP proxy than external traffic. if c.ProxyUrl != "" { @@ -61,7 +71,11 @@ func (c *AccessConfig) Auth() (gophercloud.AccessProvider, error) { // The gophercloud.Context has a UseCustomClient method which // would allow us to override with a new instance of http.Client. - http.DefaultTransport = &http.Transport{Proxy: http.ProxyURL(url)} + default_transport.Proxy = http.ProxyURL(url) + } + + if c.Insecure || c.ProxyUrl != "" { + http.DefaultTransport = default_transport } return gophercloud.Authenticate(c.Provider, authoptions) diff --git a/website/source/docs/builders/openstack.html.markdown b/website/source/docs/builders/openstack.html.markdown index d4ca59a81..130c7a7d9 100644 --- a/website/source/docs/builders/openstack.html.markdown +++ b/website/source/docs/builders/openstack.html.markdown @@ -66,6 +66,9 @@ each category, the available configuration keys are alphabetized. to allocate a floating IP. `use_floating_ip` must also be set to true for this to have an affect. +* `insecure` (boolean) - Whether or not the connection to OpenStack can be done + over an insecure connection. By default this is false. + * `openstack_provider` (string) +* `networks` (array of strings) - A list of networks by UUID to attach + to this instance. * `security_groups` (array of strings) - A list of security groups by name to add to this instance. From e2acb8e9884356ac5e431c6ec50e4c5b0bc6277d Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Sun, 15 Jun 2014 12:24:10 -0700 Subject: [PATCH 156/593] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19227c3fa..083fa3041 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ IMPROVEMENTS: BUG FIXES: * core: `isotime` is the same time during the entire build. [GH-1153] + * builder/openstack: Force IPv4 addresses from address pools [GH-1258] * builder/parallels: Do not delete entire CDROM device. [GH-1115] * builder/virtualbox-ovf: Supports guest additions options. [GH-1120] * builder/vmware: Remote ESXi builder now uploads floppy. [GH-1106] From d99eff49f7aed83ca8645cbd2b92618097f22eb5 Mon Sep 17 00:00:00 2001 From: Ian Delahorne Date: Sun, 15 Jun 2014 18:46:02 -0500 Subject: [PATCH 157/593] Reorder documentation into alphabetical order --- website/source/docs/builders/openstack.html.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/source/docs/builders/openstack.html.markdown b/website/source/docs/builders/openstack.html.markdown index b144dffeb..613e03ff0 100644 --- a/website/source/docs/builders/openstack.html.markdown +++ b/website/source/docs/builders/openstack.html.markdown @@ -69,6 +69,9 @@ each category, the available configuration keys are alphabetized. * `insecure` (boolean) - Whether or not the connection to OpenStack can be done over an insecure connection. By default this is false. +* `networks` (array of strings) - A list of networks by UUID to attach + to this instance. + * `openstack_provider` (string) -* `networks` (array of strings) - A list of networks by UUID to attach - to this instance. - * `security_groups` (array of strings) - A list of security groups by name to add to this instance. From 9058061a38c1608e0221c083b94aa0fe080de680 Mon Sep 17 00:00:00 2001 From: Thordur Bjornsson Date: Mon, 16 Jun 2014 13:10:52 +0200 Subject: [PATCH 158/593] Don't use a real domain in examples. Use example.com instead. --- website/source/docs/templates/post-processors.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/templates/post-processors.html.markdown b/website/source/docs/templates/post-processors.html.markdown index c1607f7aa..43864aa61 100644 --- a/website/source/docs/templates/post-processors.html.markdown +++ b/website/source/docs/templates/post-processors.html.markdown @@ -81,7 +81,7 @@ compressed then uploaded, but the compressed result is not kept. "post-processors": [ [ "compress", - { "type": "upload", "endpoint": "http://fake.com" } + { "type": "upload", "endpoint": "http://example.com" } ] ] } From 7d4efdc23675c63f0856944290620d327bbbeaf6 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Mon, 16 Jun 2014 15:53:37 -0400 Subject: [PATCH 159/593] post-processor/vagrant-cloud: initial commit --- config.go | 3 +- plugin/post-processor-vagrant-cloud/main.go | 15 ++ .../post-processor-vagrant-cloud/main_test.go | 1 + post-processor/vagrant-cloud/api.go | 97 ++++++++++++ post-processor/vagrant-cloud/artifact.go | 40 +++++ post-processor/vagrant-cloud/artifact_test.go | 14 ++ .../vagrant-cloud/post-processor.go | 142 ++++++++++++++++++ .../vagrant-cloud/post-processor_test.go | 41 +++++ post-processor/vagrant/artifact.go | 2 +- post-processor/vagrant/artifact_test.go | 7 + 10 files changed, 360 insertions(+), 2 deletions(-) create mode 100644 plugin/post-processor-vagrant-cloud/main.go create mode 100644 plugin/post-processor-vagrant-cloud/main_test.go create mode 100644 post-processor/vagrant-cloud/api.go create mode 100644 post-processor/vagrant-cloud/artifact.go create mode 100644 post-processor/vagrant-cloud/artifact_test.go create mode 100644 post-processor/vagrant-cloud/post-processor.go create mode 100644 post-processor/vagrant-cloud/post-processor_test.go diff --git a/config.go b/config.go index 9f1485aa8..44d362d26 100644 --- a/config.go +++ b/config.go @@ -47,7 +47,8 @@ const defaultConfig = ` "vagrant": "packer-post-processor-vagrant", "vsphere": "packer-post-processor-vsphere", "docker-push": "packer-post-processor-docker-push", - "docker-import": "packer-post-processor-docker-import" + "docker-import": "packer-post-processor-docker-import", + "vagrant-cloud": "packer-post-processor-vagrant-cloud" }, "provisioners": { diff --git a/plugin/post-processor-vagrant-cloud/main.go b/plugin/post-processor-vagrant-cloud/main.go new file mode 100644 index 000000000..b5e3e7044 --- /dev/null +++ b/plugin/post-processor-vagrant-cloud/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/packer/plugin" + "github.com/mitchellh/packer/post-processor/vagrant-cloud" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterPostProcessor(new(vagrantcloud.PostProcessor)) + server.Serve() +} diff --git a/plugin/post-processor-vagrant-cloud/main_test.go b/plugin/post-processor-vagrant-cloud/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/plugin/post-processor-vagrant-cloud/main_test.go @@ -0,0 +1 @@ +package main diff --git a/post-processor/vagrant-cloud/api.go b/post-processor/vagrant-cloud/api.go new file mode 100644 index 000000000..ba809c358 --- /dev/null +++ b/post-processor/vagrant-cloud/api.go @@ -0,0 +1,97 @@ +package vagrantcloud + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "net/url" + "strings" +) + +type Box struct { + Tag string `json:"tag"` +} + +type VagrantCloudClient struct { + // The http client for communicating + client *http.Client + + // The base URL of the API + BaseURL string + + // Access token + AccessToken string +} + +func (v VagrantCloudClient) New(baseUrl string, token string) *VagrantCloudClient { + c := &VagrantCloudClient{ + client: &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + }, + }, + BaseURL: baseUrl, + AccessToken: token, + } + return c +} + +func decodeBody(resp *http.Response, out interface{}) error { + defer resp.Body.Close() + dec := json.NewDecoder(resp.Body) + return dec.Decode(out) +} + +// encodeBody is used to encode a request body +func encodeBody(obj interface{}) (io.Reader, error) { + buf := bytes.NewBuffer(nil) + enc := json.NewEncoder(buf) + if err := enc.Encode(obj); err != nil { + return nil, err + } + return buf, nil +} + +func (v VagrantCloudClient) Box(tag string) (*Box, error) { + resp, err := v.Get(tag) + + if err != nil { + return nil, fmt.Errorf("Error retrieving box: %s", err) + } + + box := &Box{} + + if err = decodeBody(resp, box); err != nil { + return nil, fmt.Errorf("Error parsing box response: %s", err) + } + + return box, nil +} + +func (v VagrantCloudClient) Get(path string) (*http.Response, error) { + params := url.Values{} + params.Set("access_token", v.AccessToken) + reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) + + // Scrub API key for logs + scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) + log.Printf("Post-Processor Vagrant Cloud API GET: %s", scrubbedUrl) + + req, err := http.NewRequest("GET", reqUrl, nil) + resp, err := v.client.Do(req) + + log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) + + return resp, err +} + +func (v VagrantCloudClient) Post(path string, body map[string]interface{}) (map[string]interface{}, error) { + + // Scrub API key for logs + scrubbedUrl := strings.Replace(path, v.AccessToken, "ACCESS_TOKEN", -1) + log.Printf("Post-Processor Vagrant Cloud API POST: %s. \n\n Body: %s", scrubbedUrl, body) + return nil, nil +} diff --git a/post-processor/vagrant-cloud/artifact.go b/post-processor/vagrant-cloud/artifact.go new file mode 100644 index 000000000..cebef8696 --- /dev/null +++ b/post-processor/vagrant-cloud/artifact.go @@ -0,0 +1,40 @@ +package vagrantcloud + +import ( + "fmt" + "os" +) + +const BuilderId = "pearkes.post-processor.vagrant-cloud" + +type Artifact struct { + Path string + Provider string +} + +func NewArtifact(provider, path string) *Artifact { + return &Artifact{ + Path: path, + Provider: provider, + } +} + +func (*Artifact) BuilderId() string { + return BuilderId +} + +func (a *Artifact) Files() []string { + return []string{a.Path} +} + +func (a *Artifact) Id() string { + return "" +} + +func (a *Artifact) String() string { + return fmt.Sprintf("'%s' provider box: %s", a.Provider, a.Path) +} + +func (a *Artifact) Destroy() error { + return os.Remove(a.Path) +} diff --git a/post-processor/vagrant-cloud/artifact_test.go b/post-processor/vagrant-cloud/artifact_test.go new file mode 100644 index 000000000..b95e04511 --- /dev/null +++ b/post-processor/vagrant-cloud/artifact_test.go @@ -0,0 +1,14 @@ +package vagrantcloud + +import ( + "github.com/mitchellh/packer/packer" + "testing" +) + +func TestArtifact_ImplementsArtifact(t *testing.T) { + var raw interface{} + raw = &Artifact{} + if _, ok := raw.(packer.Artifact); !ok { + t.Fatalf("Artifact should be a Artifact") + } +} diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go new file mode 100644 index 000000000..b62542c6d --- /dev/null +++ b/post-processor/vagrant-cloud/post-processor.go @@ -0,0 +1,142 @@ +// vagrant_cloud implements the packer.PostProcessor interface and adds a +// post-processor that uploads artifacts from the vagrant post-processor +// to Vagrant Cloud (vagrantcloud.com) +package vagrantcloud + +import ( + "fmt" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" +) + +const VAGRANT_CLOUD_URL = "https://vagrantcloud.com" + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + Tag string `mapstructure:"box_tag"` + Version string `mapstructure:"version"` + + AccessToken string `mapstructure:"access_token"` + VagrantCloudUrl string `mapstructure:"vagrant_cloud_url"` + + tpl *packer.ConfigTemplate +} + +type PostProcessor struct { + config Config + client *VagrantCloudClient +} + +func (p *PostProcessor) Configure(raws ...interface{}) error { + _, err := common.DecodeConfig(&p.config, raws...) + if err != nil { + return err + } + + p.config.tpl, err = packer.NewConfigTemplate() + if err != nil { + return err + } + p.config.tpl.UserVars = p.config.PackerUserVars + + // Default configuration + if p.config.VagrantCloudUrl == "" { + p.config.VagrantCloudUrl = VAGRANT_CLOUD_URL + } + + // Accumulate any errors + errs := new(packer.MultiError) + + // required configuration + templates := map[string]*string{ + "box_tag": &p.config.Tag, + "version": &p.config.Version, + "access_token": &p.config.AccessToken, + } + + for key, ptr := range templates { + if *ptr == "" { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("%s must be set", key)) + } + } + + // Template process + for key, ptr := range templates { + *ptr, err = p.config.tpl.Process(*ptr, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing %s: %s", key, err)) + } + } + + if len(errs.Errors) > 0 { + return errs + } + + return nil +} + +func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { + config := p.config + + // Only accepts input from the vagrant post-processor + if artifact.BuilderId() != "mitchellh.post-processor.vagrant" { + return nil, false, fmt.Errorf( + "Unknown artifact type, requires box from vagrant post-processor: %s", artifact.BuilderId()) + } + + // The name of the provider for vagrant cloud, and vagrant + provider := providerFromBuilderName(artifact.Id()) + version := p.config.Version + tag := p.config.Tag + + // create the HTTP client + p.client = VagrantCloudClient{}.New(p.config.VagrantCloudUrl, p.config.AccessToken) + + ui.Say(fmt.Sprintf("Verifying box is accessible: %s", tag)) + + box, err := p.client.Box(tag) + + if err != nil { + return nil, false, err + } + + if box.Tag != tag { + ui.Say(fmt.Sprintf("Could not verify box is correct: %s", tag)) + return nil, false, err + } + + ui.Say(fmt.Sprintf("Creating Version %s", version)) + ui.Say(fmt.Sprintf("Creating Provider %s", version)) + ui.Say(fmt.Sprintf("Uploading Box %s", version)) + ui.Say(fmt.Sprintf("Verifying upload %s", version)) + ui.Say(fmt.Sprintf("Releasing version %s", version)) + + return NewArtifact(provider, config.Tag), true, nil +} + +// Runs a cleanup if the post processor fails to upload +func (p *PostProcessor) Cleanup() { + // Delete the version +} + +// converts a packer builder name to the corresponding vagrant +// provider +func providerFromBuilderName(name string) string { + switch name { + case "aws": + return "aws" + case "digitalocean": + return "digitalocean" + case "virtualbox": + return "virtualbox" + case "vmware": + return "vmware_desktop" + case "parallels": + return "parallels" + default: + return name + } +} diff --git a/post-processor/vagrant-cloud/post-processor_test.go b/post-processor/vagrant-cloud/post-processor_test.go new file mode 100644 index 000000000..1a302fc4c --- /dev/null +++ b/post-processor/vagrant-cloud/post-processor_test.go @@ -0,0 +1,41 @@ +package vagrantcloud + +import ( + "bytes" + "github.com/mitchellh/packer/packer" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{} +} + +func testPP(t *testing.T) *PostProcessor { + var p PostProcessor + if err := p.Configure(testConfig()); err != nil { + t.Fatalf("err: %s", err) + } + + return &p +} + +func testUi() *packer.BasicUi { + return &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + } +} + +func TestPostProcessor_ImplementsPostProcessor(t *testing.T) { + var _ packer.PostProcessor = new(PostProcessor) +} + +func TestproviderFromBuilderName(t *testing.T) { + if providerFromBuilderName("foobar") != "foobar" { + t.Fatal("should copy unknown provider") + } + + if providerFromBuilderName("vmware") != "vmware_desktop" { + t.Fatal("should convert provider") + } +} diff --git a/post-processor/vagrant/artifact.go b/post-processor/vagrant/artifact.go index 1b4885b52..c4cfe394b 100644 --- a/post-processor/vagrant/artifact.go +++ b/post-processor/vagrant/artifact.go @@ -28,7 +28,7 @@ func (a *Artifact) Files() []string { } func (a *Artifact) Id() string { - return "" + return a.Provider } func (a *Artifact) String() string { diff --git a/post-processor/vagrant/artifact_test.go b/post-processor/vagrant/artifact_test.go index 5c711dad2..6e16285a2 100644 --- a/post-processor/vagrant/artifact_test.go +++ b/post-processor/vagrant/artifact_test.go @@ -12,3 +12,10 @@ func TestArtifact_ImplementsArtifact(t *testing.T) { t.Fatalf("Artifact should be a Artifact") } } + +func TestArtifact_Id(t *testing.T) { + artifact := NewArtifact("vmware", "./") + if artifact.Id() != "vmware" { + t.Fatalf("should return name as Id") + } +} From df79ff14bb7c68cffcd2b7b646478ad0676904eb Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Mon, 16 Jun 2014 17:56:21 -0700 Subject: [PATCH 160/593] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 083fa3041..551e68ba6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ IMPROVEMENTS: * builder/amazon-ebs: Support encrypted EBS volumes [GH-1194] * builder/ansible: Add `playbook_dir` option. [GH-1000] + * builder/openstack: Add ability to configure networks. [GH-1261] * builder/openstack: Skip certificate verification. [GH-1121] * builder/virtualbox/all: Attempt to use local guest additions ISO before downloading from internet. [GH-1123] From 9dc06b608eae2f934680f7ddbef2e495c0c449c9 Mon Sep 17 00:00:00 2001 From: StefanScherer Date: Fri, 20 Jun 2014 01:07:40 +0200 Subject: [PATCH 161/593] increase VMware cleanup timeout to 120 seconds --- builder/vmware/common/step_shutdown.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/vmware/common/step_shutdown.go b/builder/vmware/common/step_shutdown.go index 378d699b1..1b5b95f95 100644 --- a/builder/vmware/common/step_shutdown.go +++ b/builder/vmware/common/step_shutdown.go @@ -102,7 +102,7 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { ui.Message("Waiting for VMware to clean up after itself...") lockRegex := regexp.MustCompile(`(?i)\.lck$`) - timer := time.After(15 * time.Second) + timer := time.After(120 * time.Second) LockWaitLoop: for { files, err := dir.ListFiles() From 5efeb66fdbf35ca4725105b45840c6e43c3c1224 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Thu, 19 Jun 2014 16:43:52 -0700 Subject: [PATCH 162/593] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 551e68ba6..20686d0a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ IMPROVEMENTS: * builder/openstack: Skip certificate verification. [GH-1121] * builder/virtualbox/all: Attempt to use local guest additions ISO before downloading from internet. [GH-1123] + * builder/vmware/all: Increase cleanup timeout to 120 seconds [GH-1167] * builder/vmware/all: Add `vmx_data_post` for modifying VMX data after shutdown. [GH-1149] * builder/vmware/vmx: Supports tools uploading. [GH-1154] From 2eafb03f162e7bebcd85d91baee41f763b485721 Mon Sep 17 00:00:00 2001 From: Andreas Kohn Date: Fri, 20 Jun 2014 14:13:53 +0200 Subject: [PATCH 163/593] Update to CentOS 6.5 --- website/source/docs/builders/qemu.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/docs/builders/qemu.html.markdown b/website/source/docs/builders/qemu.html.markdown index 882aab816..e333ed933 100644 --- a/website/source/docs/builders/qemu.html.markdown +++ b/website/source/docs/builders/qemu.html.markdown @@ -27,8 +27,8 @@ paths to files, URLS for ISOs and checksums. [ { "type": "qemu", - "iso_url": "http://mirror.raystedman.net/centos/6/isos/x86_64/CentOS-6.4-x86_64-minimal.iso", - "iso_checksum": "4a5fa01c81cc300f4729136e28ebe600", + "iso_url": "http://mirror.raystedman.net/centos/6/isos/x86_64/CentOS-6.5-x86_64-minimal.iso", + "iso_checksum": "0d9dc37b5dd4befa1c440d2174e88a87", "iso_checksum_type": "md5", "output_directory": "output_centos_tdhtest", "ssh_wait_timeout": "30s", From 792c8ac964b282307b0af6b30bd1aac901a53cc2 Mon Sep 17 00:00:00 2001 From: Andreas Kohn Date: Fri, 20 Jun 2014 14:14:15 +0200 Subject: [PATCH 164/593] Improve the boot command sequence Use a single command line, avoid the graphical installer, and use {{ .HTTPIP }} rather than the actual IP. Also wait a bit before entering the commands to increase the chance of actually being in the boot menu. --- website/source/docs/builders/qemu.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/docs/builders/qemu.html.markdown b/website/source/docs/builders/qemu.html.markdown index e333ed933..2d29adc05 100644 --- a/website/source/docs/builders/qemu.html.markdown +++ b/website/source/docs/builders/qemu.html.markdown @@ -49,10 +49,10 @@ paths to files, URLS for ISOs and checksums. "vm_name": "tdhtest", "net_device": "virtio-net", "disk_interface": "virtio", + "boot_wait": "5s", "boot_command": [ - "", - " ks=http://10.0.2.2:{{ .HTTPPort }}/centos6-ks.cfg" + " text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/centos6-ks.cfg" ] } ] From baa9fb9b7668b38ffd8f494080730b6a2563b0e6 Mon Sep 17 00:00:00 2001 From: Andreas Kohn Date: Fri, 20 Jun 2014 14:16:35 +0200 Subject: [PATCH 165/593] Update the kickstart link Instead of pointing at a specific (in this case actually non-working!) revision, point to the github UI for the head revision. The user is expected to find the 'raw' link there, but the page allows to also quickly look at changes etc, which might be helpful for people unfamiliar with kickstart files. --- website/source/docs/builders/qemu.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/builders/qemu.html.markdown b/website/source/docs/builders/qemu.html.markdown index 2d29adc05..2430c737f 100644 --- a/website/source/docs/builders/qemu.html.markdown +++ b/website/source/docs/builders/qemu.html.markdown @@ -60,7 +60,7 @@ paths to files, URLS for ISOs and checksums. A working CentOS 6.x kickstart file can be found -[at this URL](https://gist.github.com/mitchellh/7328271/raw/c91e0c4fa19c171a40b016c6c8f251f90d2ad0ba/centos6-ks.cfg), adapted from an unknown source. +[at this URL](https://gist.github.com/mitchellh/7328271/#file-centos6-ks-cfg), adapted from an unknown source. Place this file in the http directory with the proper name. For the example above, it should go into "httpdir" with a name of "centos6-ks.cfg". From c9c2ba180e1315910ffc6661ab6a5e987babdbf0 Mon Sep 17 00:00:00 2001 From: Andreas Kohn Date: Fri, 20 Jun 2014 14:22:22 +0200 Subject: [PATCH 166/593] Properly escape security_group_id to avoid markdown interpretation of the '_' --- website/source/docs/builders/amazon-ebs.html.markdown | 2 +- website/source/docs/builders/amazon-instance.html.markdown | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/docs/builders/amazon-ebs.html.markdown b/website/source/docs/builders/amazon-ebs.html.markdown index f46e2d0da..4ecaf3cf8 100644 --- a/website/source/docs/builders/amazon-ebs.html.markdown +++ b/website/source/docs/builders/amazon-ebs.html.markdown @@ -115,7 +115,7 @@ each category, the available configuration keys are alphabetized. * `security_group_ids` (array of strings) - A list of security groups as described above. Note that if this is specified, you must omit the - security_group_id. + `security_group_id`. * `ssh_port` (integer) - The port that SSH will be available on. This defaults to port 22. diff --git a/website/source/docs/builders/amazon-instance.html.markdown b/website/source/docs/builders/amazon-instance.html.markdown index 238eaead2..9f2744811 100644 --- a/website/source/docs/builders/amazon-instance.html.markdown +++ b/website/source/docs/builders/amazon-instance.html.markdown @@ -153,7 +153,7 @@ each category, the available configuration keys are alphabetized. * `security_group_ids` (array of strings) - A list of security groups as described above. Note that if this is specified, you must omit the - security_group_id. + `security_group_id`. * `ssh_port` (integer) - The port that SSH will be available on. This defaults to port 22. From a67836270166ba2402ead2b05622ec7f203bca8e Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Fri, 20 Jun 2014 11:20:27 -0400 Subject: [PATCH 167/593] post-processor/vagrant-cloud: add api items --- post-processor/vagrant-cloud/box.go | 44 ++++++++++++++ .../vagrant-cloud/{api.go => client.go} | 55 +++++++++++------- .../vagrant-cloud/post-processor.go | 13 ++++- post-processor/vagrant-cloud/provider.go | 44 ++++++++++++++ post-processor/vagrant-cloud/version.go | 58 +++++++++++++++++++ 5 files changed, 191 insertions(+), 23 deletions(-) create mode 100644 post-processor/vagrant-cloud/box.go rename post-processor/vagrant-cloud/{api.go => client.go} (58%) create mode 100644 post-processor/vagrant-cloud/provider.go create mode 100644 post-processor/vagrant-cloud/version.go diff --git a/post-processor/vagrant-cloud/box.go b/post-processor/vagrant-cloud/box.go new file mode 100644 index 000000000..e8a67d44c --- /dev/null +++ b/post-processor/vagrant-cloud/box.go @@ -0,0 +1,44 @@ +package vagrantcloud + +import ( + "fmt" +) + +type Box struct { + client *VagrantCloudClient + Tag string `json:"tag"` +} + +// https://vagrantcloud.com/docs/boxes +func (v VagrantCloudClient) Box(tag string) (*Box, error) { + resp, err := v.Get(tag) + + if err != nil { + return nil, fmt.Errorf("Error retrieving box: %s", err) + } + + box := &Box{} + + if err = decodeBody(resp, box); err != nil { + return nil, fmt.Errorf("Error parsing box response: %s", err) + } + + return box, nil +} + +// Save persist the provider over HTTP to Vagrant Cloud +func (b Box) Save(tag string) (bool, error) { + resp, err := b.client.Get(tag) + + if err != nil { + return false, fmt.Errorf("Error retrieving box: %s", err) + } + + box := &Box{} + + if err = decodeBody(resp, box); err != nil { + return false, fmt.Errorf("Error parsing box response: %s", err) + } + + return true, nil +} diff --git a/post-processor/vagrant-cloud/api.go b/post-processor/vagrant-cloud/client.go similarity index 58% rename from post-processor/vagrant-cloud/api.go rename to post-processor/vagrant-cloud/client.go index ba809c358..1dba74a94 100644 --- a/post-processor/vagrant-cloud/api.go +++ b/post-processor/vagrant-cloud/client.go @@ -11,10 +11,6 @@ import ( "strings" ) -type Box struct { - Tag string `json:"tag"` -} - type VagrantCloudClient struct { // The http client for communicating client *http.Client @@ -55,32 +51,33 @@ func encodeBody(obj interface{}) (io.Reader, error) { return buf, nil } -func (v VagrantCloudClient) Box(tag string) (*Box, error) { - resp, err := v.Get(tag) +func (v VagrantCloudClient) Get(path string) (*http.Response, error) { + params := url.Values{} + params.Set("access_token", v.AccessToken) + reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) - if err != nil { - return nil, fmt.Errorf("Error retrieving box: %s", err) - } + // Scrub API key for logs + scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) + log.Printf("Post-Processor Vagrant Cloud API GET: %s", scrubbedUrl) - box := &Box{} + req, err := http.NewRequest("GET", reqUrl, nil) + resp, err := v.client.Do(req) - if err = decodeBody(resp, box); err != nil { - return nil, fmt.Errorf("Error parsing box response: %s", err) - } + log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) - return box, nil + return resp, err } -func (v VagrantCloudClient) Get(path string) (*http.Response, error) { +func (v VagrantCloudClient) Delete(path string) (*http.Response, error) { params := url.Values{} params.Set("access_token", v.AccessToken) reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) // Scrub API key for logs scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) - log.Printf("Post-Processor Vagrant Cloud API GET: %s", scrubbedUrl) + log.Printf("Post-Processor Vagrant Cloud API DELETE: %s", scrubbedUrl) - req, err := http.NewRequest("GET", reqUrl, nil) + req, err := http.NewRequest("DELETE", reqUrl, nil) resp, err := v.client.Do(req) log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) @@ -88,10 +85,26 @@ func (v VagrantCloudClient) Get(path string) (*http.Response, error) { return resp, err } -func (v VagrantCloudClient) Post(path string, body map[string]interface{}) (map[string]interface{}, error) { +func (v VagrantCloudClient) Post(path string, body interface{}) (*http.Response, error) { + params := url.Values{} + params.Set("access_token", v.AccessToken) + reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) + + encBody, err := encodeBody(body) + + if err != nil { + return nil, fmt.Errorf("Error encoding body for request: %s", err) + } // Scrub API key for logs - scrubbedUrl := strings.Replace(path, v.AccessToken, "ACCESS_TOKEN", -1) - log.Printf("Post-Processor Vagrant Cloud API POST: %s. \n\n Body: %s", scrubbedUrl, body) - return nil, nil + scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) + log.Printf("Post-Processor Vagrant Cloud API POST: %s. \n\n Body: %s", scrubbedUrl, encBody) + + req, err := http.NewRequest("POST", reqUrl, encBody) + + resp, err := v.client.Do(req) + + log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) + + return resp, err } diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go index b62542c6d..8b2eda710 100644 --- a/post-processor/vagrant-cloud/post-processor.go +++ b/post-processor/vagrant-cloud/post-processor.go @@ -81,6 +81,8 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { config := p.config + fmt.Println(artifact) + // Only accepts input from the vagrant post-processor if artifact.BuilderId() != "mitchellh.post-processor.vagrant" { return nil, false, fmt.Errorf( @@ -89,7 +91,6 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac // The name of the provider for vagrant cloud, and vagrant provider := providerFromBuilderName(artifact.Id()) - version := p.config.Version tag := p.config.Tag // create the HTTP client @@ -108,7 +109,14 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac return nil, false, err } - ui.Say(fmt.Sprintf("Creating Version %s", version)) + ui.Say(fmt.Sprintf("Creating Version %s", p.config.Version)) + + // Create the new version for the box + version := Version{Version: p.config.Version} + if ok, err := version.Create(); !ok { + return nil, false, err + } + ui.Say(fmt.Sprintf("Creating Provider %s", version)) ui.Say(fmt.Sprintf("Uploading Box %s", version)) ui.Say(fmt.Sprintf("Verifying upload %s", version)) @@ -120,6 +128,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac // Runs a cleanup if the post processor fails to upload func (p *PostProcessor) Cleanup() { // Delete the version + } // converts a packer builder name to the corresponding vagrant diff --git a/post-processor/vagrant-cloud/provider.go b/post-processor/vagrant-cloud/provider.go new file mode 100644 index 000000000..d8c3d201e --- /dev/null +++ b/post-processor/vagrant-cloud/provider.go @@ -0,0 +1,44 @@ +package vagrantcloud + +import ( + "fmt" +) + +type Provider struct { + client *VagrantCloudClient + Name string `json:"name"` +} + +// https://vagrantcloud.com/docs/providers +func (v VagrantCloudClient) Provider(tag string) (*Box, error) { + resp, err := v.Get(tag) + + if err != nil { + return nil, fmt.Errorf("Error retrieving box: %s", err) + } + + box := &Box{} + + if err = decodeBody(resp, box); err != nil { + return nil, fmt.Errorf("Error parsing box response: %s", err) + } + + return box, nil +} + +// Save persist the box over HTTP to Vagrant Cloud +func (p Provider) Save(name string) (bool, error) { + resp, err := p.client.Get(name) + + if err != nil { + return false, fmt.Errorf("Error retrieving box: %s", err) + } + + provider := &Provider{} + + if err = decodeBody(resp, provider); err != nil { + return false, fmt.Errorf("Error parsing box response: %s", err) + } + + return true, nil +} diff --git a/post-processor/vagrant-cloud/version.go b/post-processor/vagrant-cloud/version.go new file mode 100644 index 000000000..5558f2967 --- /dev/null +++ b/post-processor/vagrant-cloud/version.go @@ -0,0 +1,58 @@ +package vagrantcloud + +import ( + "fmt" +) + +type Version struct { + client *VagrantCloudClient + Version string `json:"version"` + Number string `json:"number"` +} + +// https://vagrantcloud.com/docs/versions +func (v VagrantCloudClient) Version(number string) (*Version, error) { + resp, err := v.Get(number) + + if err != nil { + return nil, fmt.Errorf("Error retrieving version: %s", err) + } + + version := &Version{} + + if err = decodeBody(resp, version); err != nil { + return nil, fmt.Errorf("Error parsing version response: %s", err) + } + + return version, nil +} + +// Save persists the Version over HTTP to Vagrant Cloud +func (v Version) Create() (bool, error) { + resp, err := v.client.Post(v.Number, v) + + if err != nil { + return false, fmt.Errorf("Error retrieving box: %s", err) + } + + if err = decodeBody(resp, v); err != nil { + return false, fmt.Errorf("Error parsing box response: %s", err) + } + + return true, nil +} + +// Deletes the Version over HTTP to Vagrant Cloud +func (v Version) Destroy() (bool, error) { + resp, err := v.client.Delete(v.Number) + + if err != nil { + return false, fmt.Errorf("Error destroying version: %s", err) + } + + if err = decodeBody(resp, v); err != nil { + return false, fmt.Errorf("Error parsing box response: %s", err) + } + + return true, nil +} From 48961e77aa7d70d7da7ce03582d084826dfb574b Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Mon, 23 Jun 2014 07:35:53 +0200 Subject: [PATCH 168/593] Updated CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20686d0a4..49c1a7696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,11 @@ IMPROVEMENTS: * builder/ansible: Add `playbook_dir` option. [GH-1000] * builder/openstack: Add ability to configure networks. [GH-1261] * builder/openstack: Skip certificate verification. [GH-1121] + * builder/parallels/all: Add ability to select interface to connect to. + * builder/parallels/pvm: Support `boot_command`. [GH-1082] * builder/virtualbox/all: Attempt to use local guest additions ISO before downloading from internet. [GH-1123] + * builder/virtualbox/ovf: Supports `guest_additions_mode` [GH-1035] * builder/vmware/all: Increase cleanup timeout to 120 seconds [GH-1167] * builder/vmware/all: Add `vmx_data_post` for modifying VMX data after shutdown. [GH-1149] @@ -18,6 +21,8 @@ BUG FIXES: * core: `isotime` is the same time during the entire build. [GH-1153] * builder/openstack: Force IPv4 addresses from address pools [GH-1258] * builder/parallels: Do not delete entire CDROM device. [GH-1115] + * builder/parallels: Errors while creating floppy disk. [GH-1225] + * builder/parallels: Errors while removing floppy drive. [GH-1226] * builder/virtualbox-ovf: Supports guest additions options. [GH-1120] * builder/vmware: Remote ESXi builder now uploads floppy. [GH-1106] * post-processor/vsphere: Accept DOMAIN\account usernames [GH-1178] From abc4350f75999d7dc6040045f7ff20bab3b5f4bc Mon Sep 17 00:00:00 2001 From: v-vlshch Date: Mon, 23 Jun 2014 11:19:06 -0700 Subject: [PATCH 169/593] Hyper-V support added --- post-processor/vagrant/hyperv.go | 30 ++++++++++++++++++++++++ post-processor/vagrant/post-processor.go | 3 +++ 2 files changed, 33 insertions(+) create mode 100644 post-processor/vagrant/hyperv.go diff --git a/post-processor/vagrant/hyperv.go b/post-processor/vagrant/hyperv.go new file mode 100644 index 000000000..d5fa0ce52 --- /dev/null +++ b/post-processor/vagrant/hyperv.go @@ -0,0 +1,30 @@ +package vagrant + +import ( + "fmt" + "github.com/mitchellh/packer/packer" + "path/filepath" +) + +type HypervProvider struct{} + +func (p *HypervProvider) KeepInputArtifact() bool { + return false +} + +func (p *HypervProvider) 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": "hyperv"} + + // Copy all of the original contents into the temporary directory + for _, path := range artifact.Files() { + ui.Message(fmt.Sprintf("Copying: %s", path)) + + dstPath := filepath.Join(dir, filepath.Base(path)) + if err = CopyContents(dstPath, path); err != nil { + return + } + } + + return +} diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go index 39feebb2c..8fb486edb 100644 --- a/post-processor/vagrant/post-processor.go +++ b/post-processor/vagrant/post-processor.go @@ -22,6 +22,7 @@ var builtins = map[string]string{ "mitchellh.vmware": "vmware", "pearkes.digitalocean": "digitalocean", "packer.parallels": "parallels", + "MSOpenTech.hyperv": "hyperv", } type Config struct { @@ -220,6 +221,8 @@ func providerForName(name string) Provider { return new(VMwareProvider) case "parallels": return new(ParallelsProvider) + case "hyperv": + return new(HypervProvider) default: return nil } From c899051c9cd4f54c79d3a6ca12d4e3bfefdc138d Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Mon, 23 Jun 2014 15:48:51 -0400 Subject: [PATCH 170/593] post-processor/vagrant-cloud: use multistep --- post-processor/vagrant-cloud/box.go | 44 ----------- post-processor/vagrant-cloud/client.go | 24 +++++- .../vagrant-cloud/post-processor.go | 70 +++++++++-------- .../vagrant-cloud/step_create_provider.go | 15 ++++ .../vagrant-cloud/step_create_version.go | 77 +++++++++++++++++++ .../vagrant-cloud/step_prepare_upload.go | 15 ++++ post-processor/vagrant-cloud/step_upload.go | 15 ++++ .../vagrant-cloud/step_verify_box.go | 52 +++++++++++++ .../vagrant-cloud/step_verify_upload.go | 15 ++++ post-processor/vagrant-cloud/version.go | 58 -------------- 10 files changed, 249 insertions(+), 136 deletions(-) delete mode 100644 post-processor/vagrant-cloud/box.go create mode 100644 post-processor/vagrant-cloud/step_create_provider.go create mode 100644 post-processor/vagrant-cloud/step_create_version.go create mode 100644 post-processor/vagrant-cloud/step_prepare_upload.go create mode 100644 post-processor/vagrant-cloud/step_upload.go create mode 100644 post-processor/vagrant-cloud/step_verify_box.go create mode 100644 post-processor/vagrant-cloud/step_verify_upload.go delete mode 100644 post-processor/vagrant-cloud/version.go diff --git a/post-processor/vagrant-cloud/box.go b/post-processor/vagrant-cloud/box.go deleted file mode 100644 index e8a67d44c..000000000 --- a/post-processor/vagrant-cloud/box.go +++ /dev/null @@ -1,44 +0,0 @@ -package vagrantcloud - -import ( - "fmt" -) - -type Box struct { - client *VagrantCloudClient - Tag string `json:"tag"` -} - -// https://vagrantcloud.com/docs/boxes -func (v VagrantCloudClient) Box(tag string) (*Box, error) { - resp, err := v.Get(tag) - - if err != nil { - return nil, fmt.Errorf("Error retrieving box: %s", err) - } - - box := &Box{} - - if err = decodeBody(resp, box); err != nil { - return nil, fmt.Errorf("Error parsing box response: %s", err) - } - - return box, nil -} - -// Save persist the provider over HTTP to Vagrant Cloud -func (b Box) Save(tag string) (bool, error) { - resp, err := b.client.Get(tag) - - if err != nil { - return false, fmt.Errorf("Error retrieving box: %s", err) - } - - box := &Box{} - - if err = decodeBody(resp, box); err != nil { - return false, fmt.Errorf("Error parsing box response: %s", err) - } - - return true, nil -} diff --git a/post-processor/vagrant-cloud/client.go b/post-processor/vagrant-cloud/client.go index 1dba74a94..fe47d8471 100644 --- a/post-processor/vagrant-cloud/client.go +++ b/post-processor/vagrant-cloud/client.go @@ -22,6 +22,19 @@ type VagrantCloudClient struct { AccessToken string } +type VagrantCloudErrors struct { + Errors map[string][]string `json:"errors"` +} + +func (v VagrantCloudErrors) FormatErrors() string { + errs := make([]string, 0) + for e := range v.Errors { + msg := fmt.Sprintf("%s %s", e, strings.Join(v.Errors[e], ",")) + errs = append(errs, msg) + } + return strings.Join(errs, ". ") +} + func (v VagrantCloudClient) New(baseUrl string, token string) *VagrantCloudClient { c := &VagrantCloudClient{ client: &http.Client{ @@ -54,7 +67,7 @@ func encodeBody(obj interface{}) (io.Reader, error) { func (v VagrantCloudClient) Get(path string) (*http.Response, error) { params := url.Values{} params.Set("access_token", v.AccessToken) - reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) + reqUrl := fmt.Sprintf("%s/%s?%s", v.BaseURL, path, params.Encode()) // Scrub API key for logs scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) @@ -71,7 +84,7 @@ func (v VagrantCloudClient) Get(path string) (*http.Response, error) { func (v VagrantCloudClient) Delete(path string) (*http.Response, error) { params := url.Values{} params.Set("access_token", v.AccessToken) - reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) + reqUrl := fmt.Sprintf("%s/%s?%s", v.BaseURL, path, params.Encode()) // Scrub API key for logs scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) @@ -88,10 +101,14 @@ func (v VagrantCloudClient) Delete(path string) (*http.Response, error) { func (v VagrantCloudClient) Post(path string, body interface{}) (*http.Response, error) { params := url.Values{} params.Set("access_token", v.AccessToken) - reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) + reqUrl := fmt.Sprintf("%s/%s?%s", v.BaseURL, path, params.Encode()) + + log.Println(reqUrl) encBody, err := encodeBody(body) + log.Println(encBody) + if err != nil { return nil, fmt.Errorf("Error encoding body for request: %s", err) } @@ -101,6 +118,7 @@ func (v VagrantCloudClient) Post(path string, body interface{}) (*http.Response, log.Printf("Post-Processor Vagrant Cloud API POST: %s. \n\n Body: %s", scrubbedUrl, encBody) req, err := http.NewRequest("POST", reqUrl, encBody) + req.Header.Add("Content-Type", "application/json") resp, err := v.client.Do(req) diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go index 8b2eda710..9e1e261ca 100644 --- a/post-processor/vagrant-cloud/post-processor.go +++ b/post-processor/vagrant-cloud/post-processor.go @@ -5,11 +5,13 @@ package vagrantcloud import ( "fmt" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" + "log" ) -const VAGRANT_CLOUD_URL = "https://vagrantcloud.com" +const VAGRANT_CLOUD_URL = "https://vagrantcloud.com/api/v1" type Config struct { common.PackerConfig `mapstructure:",squash"` @@ -26,6 +28,7 @@ type Config struct { type PostProcessor struct { config Config client *VagrantCloudClient + runner multistep.Runner } func (p *PostProcessor) Configure(raws ...interface{}) error { @@ -79,56 +82,61 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { } func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { - config := p.config - - fmt.Println(artifact) - // Only accepts input from the vagrant post-processor if artifact.BuilderId() != "mitchellh.post-processor.vagrant" { return nil, false, fmt.Errorf( "Unknown artifact type, requires box from vagrant post-processor: %s", artifact.BuilderId()) } - // The name of the provider for vagrant cloud, and vagrant - provider := providerFromBuilderName(artifact.Id()) - tag := p.config.Tag - // create the HTTP client p.client = VagrantCloudClient{}.New(p.config.VagrantCloudUrl, p.config.AccessToken) - ui.Say(fmt.Sprintf("Verifying box is accessible: %s", tag)) - - box, err := p.client.Box(tag) - - if err != nil { - return nil, false, err + // Set up the state + state := new(multistep.BasicStateBag) + state.Put("config", p.config) + state.Put("client", p.client) + state.Put("artifact", artifact) + state.Put("ui", ui) + + // Build the steps + steps := []multistep.Step{ + new(stepVerifyBox), + new(stepCreateVersion), + new(stepCreateProvider), + new(stepPrepareUpload), + new(stepUpload), + new(stepVerifyUpload), } - if box.Tag != tag { - ui.Say(fmt.Sprintf("Could not verify box is correct: %s", tag)) - return nil, false, err + // Run the steps + if p.config.PackerDebug { + p.runner = &multistep.DebugRunner{ + Steps: steps, + PauseFn: common.MultistepDebugFn(ui), + } + } else { + p.runner = &multistep.BasicRunner{Steps: steps} } - ui.Say(fmt.Sprintf("Creating Version %s", p.config.Version)) + p.runner.Run(state) - // Create the new version for the box - version := Version{Version: p.config.Version} - if ok, err := version.Create(); !ok { - return nil, false, err + // If there was an error, return that + if rawErr, ok := state.GetOk("error"); ok { + return nil, false, rawErr.(error) } - ui.Say(fmt.Sprintf("Creating Provider %s", version)) - ui.Say(fmt.Sprintf("Uploading Box %s", version)) - ui.Say(fmt.Sprintf("Verifying upload %s", version)) - ui.Say(fmt.Sprintf("Releasing version %s", version)) + // // The name of the provider for vagrant cloud, and vagrant + provider := providerFromBuilderName(artifact.Id()) - return NewArtifact(provider, config.Tag), true, nil + return NewArtifact(provider, p.config.Tag), true, nil } // Runs a cleanup if the post processor fails to upload -func (p *PostProcessor) Cleanup() { - // Delete the version - +func (p *PostProcessor) Cancel() { + if p.runner != nil { + log.Println("Cancelling the step runner...") + p.runner.Cancel() + } } // converts a packer builder name to the corresponding vagrant diff --git a/post-processor/vagrant-cloud/step_create_provider.go b/post-processor/vagrant-cloud/step_create_provider.go new file mode 100644 index 000000000..7816f5016 --- /dev/null +++ b/post-processor/vagrant-cloud/step_create_provider.go @@ -0,0 +1,15 @@ +package vagrantcloud + +import ( + "github.com/mitchellh/multistep" +) + +type stepCreateProvider struct { +} + +func (s *stepCreateProvider) Run(state multistep.StateBag) multistep.StepAction { + return multistep.ActionContinue +} + +func (s *stepCreateProvider) Cleanup(state multistep.StateBag) { +} diff --git a/post-processor/vagrant-cloud/step_create_version.go b/post-processor/vagrant-cloud/step_create_version.go new file mode 100644 index 000000000..054baacd0 --- /dev/null +++ b/post-processor/vagrant-cloud/step_create_version.go @@ -0,0 +1,77 @@ +package vagrantcloud + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type Version struct { + Version string `json:"version"` + Number uint `json:"number"` +} + +type NewVersion struct { + Version string `json:"version"` +} + +type stepCreateVersion struct { + number uint // number of the version, if needed in cleanup +} + +func (s *stepCreateVersion) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + config := state.Get("config").(Config) + box := state.Get("box").(*Box) + + path := fmt.Sprintf("box/%s/versions", box.Tag) + + // Wrap the version in a version object for the API + wrapper := make(map[string]interface{}) + wrapper["version"] = NewVersion{Version: config.Version} + + ui.Say(fmt.Sprintf("Creating version: %s", config.Version)) + + resp, err := client.Post(path, wrapper) + version := &Version{} + + if err != nil || (resp.StatusCode != 200) { + cloudErrors := &VagrantCloudErrors{}; + err = decodeBody(resp, cloudErrors); + state.Put("error", fmt.Errorf("Error creating version: %s", cloudErrors.FormatErrors())) + return multistep.ActionHalt + } + + if err = decodeBody(resp, version); err != nil { + state.Put("error", fmt.Errorf("Error parsing version response: %s", err)) + return multistep.ActionHalt + } + + // Save the number for cleanup + s.number = version.Number + + state.Put("version", version) + + return multistep.ActionContinue +} + +func (s *stepCreateVersion) Cleanup(state multistep.StateBag) { + // If we didn't save the version number, it likely doesn't exist + if (s.number == 0) { + return + } + + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + + path := fmt.Sprintf("box/%s/version/%s", box.Tag, s.number) + + // No need for resp from the cleanup DELETE + _, err := client.Delete(path) + + if err != nil { + ui.Error(fmt.Sprintf("Error destroying version: %s", err)) + } +} diff --git a/post-processor/vagrant-cloud/step_prepare_upload.go b/post-processor/vagrant-cloud/step_prepare_upload.go new file mode 100644 index 000000000..1b3d61ac3 --- /dev/null +++ b/post-processor/vagrant-cloud/step_prepare_upload.go @@ -0,0 +1,15 @@ +package vagrantcloud + +import ( + "github.com/mitchellh/multistep" +) + +type stepPrepareUpload struct { +} + +func (s *stepPrepareUpload) Run(state multistep.StateBag) multistep.StepAction { + return multistep.ActionContinue +} + +func (s *stepPrepareUpload) Cleanup(state multistep.StateBag) { +} diff --git a/post-processor/vagrant-cloud/step_upload.go b/post-processor/vagrant-cloud/step_upload.go new file mode 100644 index 000000000..5f3b27a55 --- /dev/null +++ b/post-processor/vagrant-cloud/step_upload.go @@ -0,0 +1,15 @@ +package vagrantcloud + +import ( + "github.com/mitchellh/multistep" +) + +type stepUpload struct { +} + +func (s *stepUpload) Run(state multistep.StateBag) multistep.StepAction { + return multistep.ActionContinue +} + +func (s *stepUpload) Cleanup(state multistep.StateBag) { +} diff --git a/post-processor/vagrant-cloud/step_verify_box.go b/post-processor/vagrant-cloud/step_verify_box.go new file mode 100644 index 000000000..784f7f7e7 --- /dev/null +++ b/post-processor/vagrant-cloud/step_verify_box.go @@ -0,0 +1,52 @@ +package vagrantcloud + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type Box struct { + Tag string `json:"tag"` +} + +type stepVerifyBox struct { +} + +func (s *stepVerifyBox) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + config := state.Get("config").(Config) + + ui.Say(fmt.Sprintf("Verifying box is accessible: %s", config.Tag)) + + path := fmt.Sprintf("box/%s", config.Tag) + resp, err := client.Get(path) + + if err != nil { + state.Put("error", fmt.Errorf("Error retrieving box: %s", err)) + return multistep.ActionHalt + } + + box := &Box{} + + if err = decodeBody(resp, box); err != nil { + state.Put("error", fmt.Errorf("Error parsing box response: %s", err)) + return multistep.ActionHalt + } + + if box.Tag != config.Tag { + state.Put("error", fmt.Errorf("Could not verify box: %s", err)) + return multistep.ActionHalt + } + + // Keep the box in state for later + state.Put("box", box) + + // Box exists and is accessible + return multistep.ActionContinue +} + +func (s *stepVerifyBox) Cleanup(state multistep.StateBag) { + // no cleanup needed +} diff --git a/post-processor/vagrant-cloud/step_verify_upload.go b/post-processor/vagrant-cloud/step_verify_upload.go new file mode 100644 index 000000000..d665589ce --- /dev/null +++ b/post-processor/vagrant-cloud/step_verify_upload.go @@ -0,0 +1,15 @@ +package vagrantcloud + +import ( + "github.com/mitchellh/multistep" +) + +type stepVerifyUpload struct { +} + +func (s *stepVerifyUpload) Run(state multistep.StateBag) multistep.StepAction { + return multistep.ActionContinue +} + +func (s *stepVerifyUpload) Cleanup(state multistep.StateBag) { +} diff --git a/post-processor/vagrant-cloud/version.go b/post-processor/vagrant-cloud/version.go deleted file mode 100644 index 5558f2967..000000000 --- a/post-processor/vagrant-cloud/version.go +++ /dev/null @@ -1,58 +0,0 @@ -package vagrantcloud - -import ( - "fmt" -) - -type Version struct { - client *VagrantCloudClient - Version string `json:"version"` - Number string `json:"number"` -} - -// https://vagrantcloud.com/docs/versions -func (v VagrantCloudClient) Version(number string) (*Version, error) { - resp, err := v.Get(number) - - if err != nil { - return nil, fmt.Errorf("Error retrieving version: %s", err) - } - - version := &Version{} - - if err = decodeBody(resp, version); err != nil { - return nil, fmt.Errorf("Error parsing version response: %s", err) - } - - return version, nil -} - -// Save persists the Version over HTTP to Vagrant Cloud -func (v Version) Create() (bool, error) { - resp, err := v.client.Post(v.Number, v) - - if err != nil { - return false, fmt.Errorf("Error retrieving box: %s", err) - } - - if err = decodeBody(resp, v); err != nil { - return false, fmt.Errorf("Error parsing box response: %s", err) - } - - return true, nil -} - -// Deletes the Version over HTTP to Vagrant Cloud -func (v Version) Destroy() (bool, error) { - resp, err := v.client.Delete(v.Number) - - if err != nil { - return false, fmt.Errorf("Error destroying version: %s", err) - } - - if err = decodeBody(resp, v); err != nil { - return false, fmt.Errorf("Error parsing box response: %s", err) - } - - return true, nil -} From 26abac69993178ae51926b12dd46fdfa2c377e4d Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Tue, 24 Jun 2014 15:58:45 -0400 Subject: [PATCH 171/593] post-processor/vagrant-cloud: steps for create, upload and release --- post-processor/vagrant-cloud/client.go | 63 +++++++++++++- .../vagrant-cloud/post-processor.go | 18 +++- post-processor/vagrant-cloud/provider.go | 44 ---------- .../vagrant-cloud/step_create_provider.go | 71 +++++++++++++++ .../vagrant-cloud/step_create_version.go | 76 +++++++++------- .../vagrant-cloud/step_prepare_upload.go | 37 ++++++++ .../vagrant-cloud/step_release_version.go | 36 ++++++++ post-processor/vagrant-cloud/step_upload.go | 18 ++++ .../vagrant-cloud/step_verify_box.go | 12 ++- .../vagrant-cloud/step_verify_upload.go | 87 ++++++++++++++++++- 10 files changed, 379 insertions(+), 83 deletions(-) delete mode 100644 post-processor/vagrant-cloud/provider.go create mode 100644 post-processor/vagrant-cloud/step_release_version.go diff --git a/post-processor/vagrant-cloud/client.go b/post-processor/vagrant-cloud/client.go index fe47d8471..19b029576 100644 --- a/post-processor/vagrant-cloud/client.go +++ b/post-processor/vagrant-cloud/client.go @@ -6,8 +6,11 @@ import ( "fmt" "io" "log" + "mime/multipart" "net/http" "net/url" + "os" + "path/filepath" "strings" ) @@ -74,6 +77,7 @@ func (v VagrantCloudClient) Get(path string) (*http.Response, error) { log.Printf("Post-Processor Vagrant Cloud API GET: %s", scrubbedUrl) req, err := http.NewRequest("GET", reqUrl, nil) + req.Header.Add("Content-Type", "application/json") resp, err := v.client.Do(req) log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) @@ -91,6 +95,7 @@ func (v VagrantCloudClient) Delete(path string) (*http.Response, error) { log.Printf("Post-Processor Vagrant Cloud API DELETE: %s", scrubbedUrl) req, err := http.NewRequest("DELETE", reqUrl, nil) + req.Header.Add("Content-Type", "application/json") resp, err := v.client.Do(req) log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) @@ -98,13 +103,48 @@ func (v VagrantCloudClient) Delete(path string) (*http.Response, error) { return resp, err } +func (v VagrantCloudClient) Upload(path string, url string) (*http.Response, error) { + file, err := os.Open(path) + + if err != nil { + return nil, fmt.Errorf("Error opening file for upload: %s", err) + } + + defer file.Close() + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", filepath.Base(path)) + if err != nil { + return nil, err + } + + _, err = io.Copy(part, file) + + if err != nil { + return nil, fmt.Errorf("Error uploading file: %s", err) + } + + request, err := http.NewRequest("PUT", url, body) + + if err != nil { + return nil, fmt.Errorf("Error preparing upload request: %s", err) + } + + log.Printf("Post-Processor Vagrant Cloud API Upload: %s %s", path, url) + + resp, err := v.client.Do(request) + + log.Printf("Post-Processor Vagrant Cloud Upload Response: \n\n%s", resp) + + return resp, err +} + func (v VagrantCloudClient) Post(path string, body interface{}) (*http.Response, error) { params := url.Values{} params.Set("access_token", v.AccessToken) reqUrl := fmt.Sprintf("%s/%s?%s", v.BaseURL, path, params.Encode()) - log.Println(reqUrl) - encBody, err := encodeBody(body) log.Println(encBody) @@ -126,3 +166,22 @@ func (v VagrantCloudClient) Post(path string, body interface{}) (*http.Response, return resp, err } + +func (v VagrantCloudClient) Put(path string) (*http.Response, error) { + params := url.Values{} + params.Set("access_token", v.AccessToken) + reqUrl := fmt.Sprintf("%s/%s?%s", v.BaseURL, path, params.Encode()) + + // Scrub API key for logs + scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) + log.Printf("Post-Processor Vagrant Cloud API PUT: %s", scrubbedUrl) + + req, err := http.NewRequest("PUT", reqUrl, nil) + req.Header.Add("Content-Type", "application/json") + + resp, err := v.client.Do(req) + + log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) + + return resp, err +} diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go index 9e1e261ca..a655819f0 100644 --- a/post-processor/vagrant-cloud/post-processor.go +++ b/post-processor/vagrant-cloud/post-processor.go @@ -9,6 +9,7 @@ import ( "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" "log" + "strings" ) const VAGRANT_CLOUD_URL = "https://vagrantcloud.com/api/v1" @@ -88,15 +89,26 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac "Unknown artifact type, requires box from vagrant post-processor: %s", artifact.BuilderId()) } + // We assume that there is only one .box file to upload + if !strings.HasSuffix(artifact.Files()[0], ".box") { + return nil, false, fmt.Errorf( + "Unknown files in artifact from vagrant post-processor: %s", artifact.Files()) + } + // create the HTTP client p.client = VagrantCloudClient{}.New(p.config.VagrantCloudUrl, p.config.AccessToken) + // The name of the provider for vagrant cloud, and vagrant + providerName := providerFromBuilderName(artifact.Id()) + // Set up the state state := new(multistep.BasicStateBag) state.Put("config", p.config) state.Put("client", p.client) state.Put("artifact", artifact) + state.Put("artifactFilePath", artifact.Files()[0]) state.Put("ui", ui) + state.Put("providerName", providerName) // Build the steps steps := []multistep.Step{ @@ -106,6 +118,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac new(stepPrepareUpload), new(stepUpload), new(stepVerifyUpload), + new(stepReleaseVersion), } // Run the steps @@ -125,10 +138,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac return nil, false, rawErr.(error) } - // // The name of the provider for vagrant cloud, and vagrant - provider := providerFromBuilderName(artifact.Id()) - - return NewArtifact(provider, p.config.Tag), true, nil + return NewArtifact(providerName, p.config.Tag), true, nil } // Runs a cleanup if the post processor fails to upload diff --git a/post-processor/vagrant-cloud/provider.go b/post-processor/vagrant-cloud/provider.go deleted file mode 100644 index d8c3d201e..000000000 --- a/post-processor/vagrant-cloud/provider.go +++ /dev/null @@ -1,44 +0,0 @@ -package vagrantcloud - -import ( - "fmt" -) - -type Provider struct { - client *VagrantCloudClient - Name string `json:"name"` -} - -// https://vagrantcloud.com/docs/providers -func (v VagrantCloudClient) Provider(tag string) (*Box, error) { - resp, err := v.Get(tag) - - if err != nil { - return nil, fmt.Errorf("Error retrieving box: %s", err) - } - - box := &Box{} - - if err = decodeBody(resp, box); err != nil { - return nil, fmt.Errorf("Error parsing box response: %s", err) - } - - return box, nil -} - -// Save persist the box over HTTP to Vagrant Cloud -func (p Provider) Save(name string) (bool, error) { - resp, err := p.client.Get(name) - - if err != nil { - return false, fmt.Errorf("Error retrieving box: %s", err) - } - - provider := &Provider{} - - if err = decodeBody(resp, provider); err != nil { - return false, fmt.Errorf("Error parsing box response: %s", err) - } - - return true, nil -} diff --git a/post-processor/vagrant-cloud/step_create_provider.go b/post-processor/vagrant-cloud/step_create_provider.go index 7816f5016..e149ddba1 100644 --- a/post-processor/vagrant-cloud/step_create_provider.go +++ b/post-processor/vagrant-cloud/step_create_provider.go @@ -1,15 +1,86 @@ package vagrantcloud import ( + "fmt" "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" ) +type Provider struct { + Name string `json:"name"` + HostedToken string `json:"hosted_token,omitempty"` + UploadUrl string `json:"upload_url,omitempty"` +} + type stepCreateProvider struct { + name string // the name of the provider } func (s *stepCreateProvider) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + version := state.Get("version").(*Version) + providerName := state.Get("providerName").(string) + + path := fmt.Sprintf("box/%s/version/%v/providers", box.Tag, version.Number) + + provider := &Provider{Name: providerName} + + // Wrap the provider in a provider object for the API + wrapper := make(map[string]interface{}) + wrapper["provider"] = provider + + ui.Say(fmt.Sprintf("Creating provider: %s", providerName)) + + resp, err := client.Post(path, wrapper) + + if err != nil || (resp.StatusCode != 200) { + cloudErrors := &VagrantCloudErrors{} + err = decodeBody(resp, cloudErrors) + state.Put("error", fmt.Errorf("Error creating provider: %s", cloudErrors.FormatErrors())) + return multistep.ActionHalt + } + + if err = decodeBody(resp, provider); err != nil { + state.Put("error", fmt.Errorf("Error parsing provider response: %s", err)) + return multistep.ActionHalt + } + + // Save the name for cleanup + s.name = provider.Name + + state.Put("provider", provider) + return multistep.ActionContinue } func (s *stepCreateProvider) Cleanup(state multistep.StateBag) { + // If we didn't save the provider name, it likely doesn't exist + if s.name == "" { + return + } + + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + // Return if we didn't cancel or halt, and thus need + // no cleanup + if !cancelled && !halted { + return + } + + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + version := state.Get("version").(*Version) + + path := fmt.Sprintf("box/%s/version/%v/provider/%s", box.Tag, version.Number, s.name) + + // No need for resp from the cleanup DELETE + _, err := client.Delete(path) + + if err != nil { + ui.Error(fmt.Sprintf("Error destroying provider: %s", err)) + } } diff --git a/post-processor/vagrant-cloud/step_create_version.go b/post-processor/vagrant-cloud/step_create_version.go index 054baacd0..211604875 100644 --- a/post-processor/vagrant-cloud/step_create_version.go +++ b/post-processor/vagrant-cloud/step_create_version.go @@ -2,17 +2,13 @@ package vagrantcloud import ( "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" ) type Version struct { Version string `json:"version"` - Number uint `json:"number"` -} - -type NewVersion struct { - Version string `json:"version"` + Number uint `json:"number,omitempty"` } type stepCreateVersion struct { @@ -25,53 +21,71 @@ func (s *stepCreateVersion) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(Config) box := state.Get("box").(*Box) - path := fmt.Sprintf("box/%s/versions", box.Tag) + if hasVersion, v := box.HasVersion(config.Version); hasVersion { + ui.Say(fmt.Sprintf("Version exists: %s", config.Version)) + state.Put("version", v) + return multistep.ActionContinue + } + + path := fmt.Sprintf("box/%s/versions", box.Tag) + + version := &Version{Version: config.Version} // Wrap the version in a version object for the API wrapper := make(map[string]interface{}) - wrapper["version"] = NewVersion{Version: config.Version} + wrapper["version"] = version - ui.Say(fmt.Sprintf("Creating version: %s", config.Version)) + ui.Say(fmt.Sprintf("Creating version: %s", config.Version)) resp, err := client.Post(path, wrapper) - version := &Version{} if err != nil || (resp.StatusCode != 200) { - cloudErrors := &VagrantCloudErrors{}; - err = decodeBody(resp, cloudErrors); - state.Put("error", fmt.Errorf("Error creating version: %s", cloudErrors.FormatErrors())) + cloudErrors := &VagrantCloudErrors{} + err = decodeBody(resp, cloudErrors) + state.Put("error", fmt.Errorf("Error creating version: %s", cloudErrors.FormatErrors())) return multistep.ActionHalt } if err = decodeBody(resp, version); err != nil { state.Put("error", fmt.Errorf("Error parsing version response: %s", err)) - return multistep.ActionHalt + return multistep.ActionHalt } - // Save the number for cleanup - s.number = version.Number + // Save the number for cleanup + s.number = version.Number - state.Put("version", version) + state.Put("version", version) return multistep.ActionContinue } func (s *stepCreateVersion) Cleanup(state multistep.StateBag) { - // If we didn't save the version number, it likely doesn't exist - if (s.number == 0) { - return - } + // If we didn't save the version number, it likely doesn't exist or + // already existed + if s.number == 0 { + return + } - client := state.Get("client").(*VagrantCloudClient) - ui := state.Get("ui").(packer.Ui) - box := state.Get("box").(*Box) + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) - path := fmt.Sprintf("box/%s/version/%s", box.Tag, s.number) + // Return if we didn't cancel or halt, and thus need + // no cleanup + if !cancelled && !halted { + return + } - // No need for resp from the cleanup DELETE - _, err := client.Delete(path) + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + + path := fmt.Sprintf("box/%s/version/%v", box.Tag, s.number) + + // No need for resp from the cleanup DELETE + _, err := client.Delete(path) + + if err != nil { + ui.Error(fmt.Sprintf("Error destroying version: %s", err)) + } - if err != nil { - ui.Error(fmt.Sprintf("Error destroying version: %s", err)) - } } diff --git a/post-processor/vagrant-cloud/step_prepare_upload.go b/post-processor/vagrant-cloud/step_prepare_upload.go index 1b3d61ac3..5c7fee2b9 100644 --- a/post-processor/vagrant-cloud/step_prepare_upload.go +++ b/post-processor/vagrant-cloud/step_prepare_upload.go @@ -1,15 +1,52 @@ package vagrantcloud import ( + "fmt" "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" ) +type Upload struct { + Token string `json:"token"` + UploadPath string `json:"upload_path"` +} + type stepPrepareUpload struct { } func (s *stepPrepareUpload) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + version := state.Get("version").(*Version) + provider := state.Get("provider").(*Provider) + artifactFilePath := state.Get("artifactFilePath").(string) + + path := fmt.Sprintf("box/%s/version/%v/provider/%s/upload", box.Tag, version.Number, provider.Name) + upload := &Upload{} + + ui.Say(fmt.Sprintf("Preparing upload of box: %s", artifactFilePath)) + + resp, err := client.Get(path) + + if err != nil || (resp.StatusCode != 200) { + cloudErrors := &VagrantCloudErrors{} + err = decodeBody(resp, cloudErrors) + state.Put("error", fmt.Errorf("Error preparing upload: %s", cloudErrors.FormatErrors())) + return multistep.ActionHalt + } + + if err = decodeBody(resp, upload); err != nil { + state.Put("error", fmt.Errorf("Error parsing upload response: %s", err)) + return multistep.ActionHalt + } + + // Save the upload details to the state + state.Put("upload", upload) + return multistep.ActionContinue } func (s *stepPrepareUpload) Cleanup(state multistep.StateBag) { + // No cleanup } diff --git a/post-processor/vagrant-cloud/step_release_version.go b/post-processor/vagrant-cloud/step_release_version.go new file mode 100644 index 000000000..0065c6100 --- /dev/null +++ b/post-processor/vagrant-cloud/step_release_version.go @@ -0,0 +1,36 @@ +package vagrantcloud + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type stepReleaseVersion struct { +} + +func (s *stepReleaseVersion) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + version := state.Get("version").(*Version) + + path := fmt.Sprintf("box/%s/version/%v/release", box.Tag, version.Number) + + ui.Say(fmt.Sprintf("Releasing version: %s", version.Version)) + + resp, err := client.Put(path) + + if err != nil || (resp.StatusCode != 200) { + cloudErrors := &VagrantCloudErrors{} + err = decodeBody(resp, cloudErrors) + state.Put("error", fmt.Errorf("Error releasing version: %s", cloudErrors.FormatErrors())) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepReleaseVersion) Cleanup(state multistep.StateBag) { + // No cleanup +} diff --git a/post-processor/vagrant-cloud/step_upload.go b/post-processor/vagrant-cloud/step_upload.go index 5f3b27a55..4fb43028c 100644 --- a/post-processor/vagrant-cloud/step_upload.go +++ b/post-processor/vagrant-cloud/step_upload.go @@ -1,15 +1,33 @@ package vagrantcloud import ( + "fmt" "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" ) type stepUpload struct { } func (s *stepUpload) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + upload := state.Get("upload").(*Upload) + artifactFilePath := state.Get("artifactFilePath").(string) + url := upload.UploadPath + + ui.Say(fmt.Sprintf("Uploading box: %s", artifactFilePath)) + + resp, err := client.Upload(artifactFilePath, url) + + if err != nil || (resp.StatusCode != 200) { + state.Put("error", fmt.Errorf("Error uploading Box: %s", resp.Body)) + return multistep.ActionHalt + } + return multistep.ActionContinue } func (s *stepUpload) Cleanup(state multistep.StateBag) { + // No cleanup } diff --git a/post-processor/vagrant-cloud/step_verify_box.go b/post-processor/vagrant-cloud/step_verify_box.go index 784f7f7e7..95aaa0c0f 100644 --- a/post-processor/vagrant-cloud/step_verify_box.go +++ b/post-processor/vagrant-cloud/step_verify_box.go @@ -7,7 +7,17 @@ import ( ) type Box struct { - Tag string `json:"tag"` + Tag string `json:"tag"` + Versions []*Version `json:"versions"` +} + +func (b *Box) HasVersion(version string) (bool, *Version) { + for _, v := range b.Versions { + if v.Version == version { + return true, v + } + } + return false, nil } type stepVerifyBox struct { diff --git a/post-processor/vagrant-cloud/step_verify_upload.go b/post-processor/vagrant-cloud/step_verify_upload.go index d665589ce..eb787886f 100644 --- a/post-processor/vagrant-cloud/step_verify_upload.go +++ b/post-processor/vagrant-cloud/step_verify_upload.go @@ -1,15 +1,100 @@ package vagrantcloud import ( + "fmt" "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "time" ) type stepVerifyUpload struct { } func (s *stepVerifyUpload) Run(state multistep.StateBag) multistep.StepAction { - return multistep.ActionContinue + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + version := state.Get("version").(*Version) + upload := state.Get("upload").(*Upload) + provider := state.Get("provider").(*Provider) + + path := fmt.Sprintf("box/%s/version/%v/provider/%s", box.Tag, version.Number, provider.Name) + + providerCheck := &Provider{} + + ui.Say(fmt.Sprintf("Verifying provider upload: %s", provider.Name)) + + done := make(chan struct{}) + defer close(done) + + result := make(chan error, 1) + + go func() { + attempts := 0 + for { + attempts += 1 + + log.Printf("Checking token match for provider.. (attempt: %d)", attempts) + + resp, err := client.Get(path) + + if err != nil || (resp.StatusCode != 200) { + cloudErrors := &VagrantCloudErrors{} + err = decodeBody(resp, cloudErrors) + err = fmt.Errorf("Error retrieving provider: %s", cloudErrors.FormatErrors()) + result <- err + return + } + + if err = decodeBody(resp, providerCheck); err != nil { + err = fmt.Errorf("Error parsing provider response: %s", err) + result <- err + return + } + + if err != nil { + result <- err + return + } + + if upload.Token == providerCheck.HostedToken { + // success! + result <- nil + return + } + + // Wait 3 seconds in between + time.Sleep(3 * time.Second) + + // Verify we shouldn't exit + select { + case <-done: + // We finished, so just exit the goroutine + return + default: + // Keep going + } + } + }() + + log.Printf("Waiting for up to 600 seconds for provider hosted token to match %s", upload.Token) + + select { + case err := <-result: + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + log.Printf("Box succesfully verified %s == %s", upload.Token, providerCheck.HostedToken) + return multistep.ActionContinue + case <-time.After(600 * time.Second): + state.Put("error", fmt.Errorf("Timeout while waiting to for upload to verify token '%s'", upload.Token)) + return multistep.ActionHalt + } } func (s *stepVerifyUpload) Cleanup(state multistep.StateBag) { + // No cleanup } From 979752cc573bad630c141b49d56b622cf5adf53b Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Tue, 24 Jun 2014 16:11:56 -0400 Subject: [PATCH 172/593] post-processor/vagrant-cloud: remove extra log statement --- post-processor/vagrant-cloud/client.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/post-processor/vagrant-cloud/client.go b/post-processor/vagrant-cloud/client.go index 19b029576..783745c67 100644 --- a/post-processor/vagrant-cloud/client.go +++ b/post-processor/vagrant-cloud/client.go @@ -147,8 +147,6 @@ func (v VagrantCloudClient) Post(path string, body interface{}) (*http.Response, encBody, err := encodeBody(body) - log.Println(encBody) - if err != nil { return nil, fmt.Errorf("Error encoding body for request: %s", err) } From 2b8ebe65e4dcb37a727f094b79a3b828486ef3fe Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Wed, 25 Jun 2014 11:20:24 +0900 Subject: [PATCH 173/593] Resolves #1256, add warning mesasge to VMware ESXi driver --- builder/vmware/common/step_upload_tools.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/vmware/common/step_upload_tools.go b/builder/vmware/common/step_upload_tools.go index b3402bdd9..b714a3dc4 100644 --- a/builder/vmware/common/step_upload_tools.go +++ b/builder/vmware/common/step_upload_tools.go @@ -23,7 +23,7 @@ func (c *StepUploadTools) Run(state multistep.StateBag) multistep.StepAction { if c.RemoteType == "esx5" { if err := driver.ToolsInstall(); err != nil { - state.Put("error", fmt.Errorf("Couldn't mount VMware tools ISO.")) + state.Put("error", fmt.Errorf("Couldn't mount VMware tools ISO. Please check the 'guest_os_type' in your template.json.")) } return multistep.ActionContinue } From f3848068214d67e03a1a875633c9894c2fdc7b16 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 10:32:17 -0400 Subject: [PATCH 174/593] post-processor/vagrant-cloud: add no_release and version_description --- post-processor/vagrant-cloud/post-processor.go | 6 ++++-- post-processor/vagrant-cloud/step_create_version.go | 5 +++-- post-processor/vagrant-cloud/step_release_version.go | 6 ++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go index a655819f0..8a7f53ebc 100644 --- a/post-processor/vagrant-cloud/post-processor.go +++ b/post-processor/vagrant-cloud/post-processor.go @@ -17,8 +17,10 @@ const VAGRANT_CLOUD_URL = "https://vagrantcloud.com/api/v1" type Config struct { common.PackerConfig `mapstructure:",squash"` - Tag string `mapstructure:"box_tag"` - Version string `mapstructure:"version"` + Tag string `mapstructure:"box_tag"` + Version string `mapstructure:"version"` + VersionDescription string `mapstructure:"version_description"` + NoRelease bool `mapstructure:"no_release"` AccessToken string `mapstructure:"access_token"` VagrantCloudUrl string `mapstructure:"vagrant_cloud_url"` diff --git a/post-processor/vagrant-cloud/step_create_version.go b/post-processor/vagrant-cloud/step_create_version.go index 211604875..882a5971d 100644 --- a/post-processor/vagrant-cloud/step_create_version.go +++ b/post-processor/vagrant-cloud/step_create_version.go @@ -8,7 +8,8 @@ import ( type Version struct { Version string `json:"version"` - Number uint `json:"number,omitempty"` + Description string `json:"description,omitempty"` + Number uint `json:"number,omitempty"` } type stepCreateVersion struct { @@ -29,7 +30,7 @@ func (s *stepCreateVersion) Run(state multistep.StateBag) multistep.StepAction { path := fmt.Sprintf("box/%s/versions", box.Tag) - version := &Version{Version: config.Version} + version := &Version{Version: config.Version, Description: config.VersionDescription} // Wrap the version in a version object for the API wrapper := make(map[string]interface{}) diff --git a/post-processor/vagrant-cloud/step_release_version.go b/post-processor/vagrant-cloud/step_release_version.go index 0065c6100..4e40f5ca3 100644 --- a/post-processor/vagrant-cloud/step_release_version.go +++ b/post-processor/vagrant-cloud/step_release_version.go @@ -14,6 +14,12 @@ func (s *stepReleaseVersion) Run(state multistep.StateBag) multistep.StepAction ui := state.Get("ui").(packer.Ui) box := state.Get("box").(*Box) version := state.Get("version").(*Version) + config := state.Get("config").(Config) + + if config.NoRelease { + ui.Say(fmt.Sprintf("Not releasing version due to configuration: %s", version.Version)) + return multistep.ActionContinue + } path := fmt.Sprintf("box/%s/version/%v/release", box.Tag, version.Number) From a0c153824f2664d944efca9273a7f4963df1e2a7 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 10:56:09 -0400 Subject: [PATCH 175/593] post-processor/vagrant-cloud: tests for configuration --- .../vagrant-cloud/post-processor_test.go | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/post-processor/vagrant-cloud/post-processor_test.go b/post-processor/vagrant-cloud/post-processor_test.go index 1a302fc4c..ff99314c4 100644 --- a/post-processor/vagrant-cloud/post-processor_test.go +++ b/post-processor/vagrant-cloud/post-processor_test.go @@ -6,17 +6,35 @@ import ( "testing" ) -func testConfig() map[string]interface{} { - return map[string]interface{}{} +func testGoodConfig() map[string]interface{} { + return map[string]interface{}{ + "access_token": "foo", + "version_description": "bar", + "box_tag": "hashicorp/precise64", + "version": "0.5", + } +} + +func testBadConfig() map[string]interface{} { + return map[string]interface{}{ + "access_token": "foo", + "box_tag": "baz", + "version_description": "bar", + } } -func testPP(t *testing.T) *PostProcessor { +func TestPostProcessor_Configure_Good(t *testing.T) { var p PostProcessor - if err := p.Configure(testConfig()); err != nil { + if err := p.Configure(testGoodConfig()); err != nil { t.Fatalf("err: %s", err) } +} - return &p +func TestPostProcessor_Configure_Bad(t *testing.T) { + var p PostProcessor + if err := p.Configure(testBadConfig()); err == nil { + t.Fatalf("should have err") + } } func testUi() *packer.BasicUi { From 6a3c9921d2077df141e57b62d8d911a1243e8d56 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 11:29:25 -0400 Subject: [PATCH 176/593] post-processor/vagrant-cloud: better errors in box verifcation --- post-processor/vagrant-cloud/step_verify_box.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/post-processor/vagrant-cloud/step_verify_box.go b/post-processor/vagrant-cloud/step_verify_box.go index 95aaa0c0f..32ae9a5b6 100644 --- a/post-processor/vagrant-cloud/step_verify_box.go +++ b/post-processor/vagrant-cloud/step_verify_box.go @@ -33,8 +33,10 @@ func (s *stepVerifyBox) Run(state multistep.StateBag) multistep.StepAction { path := fmt.Sprintf("box/%s", config.Tag) resp, err := client.Get(path) - if err != nil { - state.Put("error", fmt.Errorf("Error retrieving box: %s", err)) + if err != nil || (resp.StatusCode != 200) { + cloudErrors := &VagrantCloudErrors{} + err = decodeBody(resp, cloudErrors) + state.Put("error", fmt.Errorf("Error retrieving box: %s", cloudErrors.FormatErrors())) return multistep.ActionHalt } @@ -46,7 +48,7 @@ func (s *stepVerifyBox) Run(state multistep.StateBag) multistep.StepAction { } if box.Tag != config.Tag { - state.Put("error", fmt.Errorf("Could not verify box: %s", err)) + state.Put("error", fmt.Errorf("Could not verify box: %s", config.Tag)) return multistep.ActionHalt } From 34164b25229718b6b2928ee6981ac5a0ac6d71f5 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 11:44:08 -0400 Subject: [PATCH 177/593] post-processor/vagrant-cloud: add docs --- .../vagrant-cloud.html.markdown | 102 ++++++++++++++++++ website/source/layouts/docs.erb | 1 + 2 files changed, 103 insertions(+) create mode 100644 website/source/docs/post-processors/vagrant-cloud.html.markdown diff --git a/website/source/docs/post-processors/vagrant-cloud.html.markdown b/website/source/docs/post-processors/vagrant-cloud.html.markdown new file mode 100644 index 000000000..03d65442d --- /dev/null +++ b/website/source/docs/post-processors/vagrant-cloud.html.markdown @@ -0,0 +1,102 @@ +--- +layout: "docs" +page_title: "Vagrant Cloud Post-Processor" +--- + +# Vagrant Cloud Post-Processor + +Type: `vagrant-cloud` + +The Vagrant Cloud post-processor recieves a Vagrant box from the `vagrant` +post-processor and pushes it to Vagrant Cloud. [Vagrant Cloud](https://vagrantcloud.com) +hosts and serves boxes to Vagrant, allowing you to version and distribute +boxes to an organization in a simple way. + +You'll need to be familiar with Vagrant Cloud, have an upgraded account +to enable box hosting, and be distributing your box via the [shorthand name](http://docs.vagrantup.com/v2/cli/box.html) +configuration. + +## Workflow + +It's important to understand the workflow that using this post-processor +enforces in order to take full advantage of Vagrant and Vagrant Cloud. + +The use of this processor assume that you currently distribute, or plan +to distrubute, boxes via Vagrant Cloud. It also assumes you create Vagrant +Boxes and deliver them to your team in some fashion. + +Here is an example workflow: + +1. You use Packer to build a Vagrant Box for the `virtualbox` provider +2. The `vagrant-cloud` post-processor is configured to point to the box `hashicorp/foobar` on Vagrant Cloud +via the `box_tag` configuration +2. The post-processor receives the box from the `vagrant` post-processor +3. It then creates the configured version, or verifies the existence of it, on Vagrant Cloud +4. A provider matching the name of the Vagrant provider is then created +5. The box is uploaded to Vagrant Cloud +6. The upload is verified +7. The version is released and available to users of the box + + +## Configuration + +The configuration allows you to specify the target box that you have +access to on Vagrant Cloud, as well as authentication and version information. + +### Required: + +* `access_token` (string) - Your access token for the Vagrant Cloud API. + This can be generated on your [tokens page](https://vagrantcloud.com/account/tokens). + +* `box_tag` (string) - The shorthand tag for your box that maps to + Vagrant Cloud, i.e `hashicorp/precise64` for `vagrantcloud.com/hashicorp/precise64` + +* `version` (string) - The version number, typically incrementing a previous version. + The version string is validated based on [Semantic Versioning](http://semver.org/). The string must match + a pattern that could be semver, and doesn't validate that the version comes after + your previous versions. + + +### Optional: + +* `version_description` (string) - Optionally markdown text used as a full-length + and in-depth description of the version, typically for denoting changes introduced + +* `no_release` (string) - If set to true, does not release the version +on Vagrant Cloud, making it active. You can manually release the version +via the API or Web UI. Defaults to false. + + +## Use with Vagrant Post-Processor + +You'll need to use the Vagrant post-processor before using this post-processor. +An example configuration is below. Note the use of the array specifying +the execution order. + +```json +{ + "variables": { + "version": "", + "cloud_token": "" + }, + "builders": [{ + ... + }], + "post-processors": [ + [{ + "type": "vagrant", + "include": ["image.iso"], + "vagrantfile_template": "vagrantfile.tpl", + "output": "proxycore_{{.Provider}}.box", + }, + { + "type": "vagrant-cloud", + "box_tag": "hashicorp/precise64", + "access_token": "{{user `cloud_token`}}", + "version": "{{user `version`}}", + "version_description": "- added rabbitmq\n- modified db config\n- compacted log files" + }] + ] +} + +``` diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 83897136e..ea395d2f9 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -61,6 +61,7 @@
  • docker-import
  • docker-push
  • Vagrant
  • +
  • Vagrant Cloud
  • vSphere
  • From 560da2765161b5719788f1dfa92efe41a3ce9025 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 11:45:53 -0400 Subject: [PATCH 178/593] website: simplify example for vagrant cloud post processor --- .../source/docs/post-processors/vagrant-cloud.html.markdown | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/source/docs/post-processors/vagrant-cloud.html.markdown b/website/source/docs/post-processors/vagrant-cloud.html.markdown index 03d65442d..cf2bb6ef0 100644 --- a/website/source/docs/post-processors/vagrant-cloud.html.markdown +++ b/website/source/docs/post-processors/vagrant-cloud.html.markdown @@ -93,8 +93,7 @@ the execution order. "type": "vagrant-cloud", "box_tag": "hashicorp/precise64", "access_token": "{{user `cloud_token`}}", - "version": "{{user `version`}}", - "version_description": "- added rabbitmq\n- modified db config\n- compacted log files" + "version": "{{user `version`}}" }] ] } From 5b2aaf3393960eed760a93e581f05602370f3ebf Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 11:46:27 -0400 Subject: [PATCH 179/593] website: correct example configuration for vagrant pp --- website/source/docs/post-processors/vagrant-cloud.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/post-processors/vagrant-cloud.html.markdown b/website/source/docs/post-processors/vagrant-cloud.html.markdown index cf2bb6ef0..d95ee9343 100644 --- a/website/source/docs/post-processors/vagrant-cloud.html.markdown +++ b/website/source/docs/post-processors/vagrant-cloud.html.markdown @@ -87,7 +87,7 @@ the execution order. "type": "vagrant", "include": ["image.iso"], "vagrantfile_template": "vagrantfile.tpl", - "output": "proxycore_{{.Provider}}.box", + "output": "proxycore_{{.Provider}}.box" }, { "type": "vagrant-cloud", From 450ba0bd9df80288342c26c1ef30a2c7cb557b82 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 11:54:49 -0400 Subject: [PATCH 180/593] post-processor/vagrant-cloud: improve error for upload failures --- post-processor/vagrant-cloud/step_upload.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post-processor/vagrant-cloud/step_upload.go b/post-processor/vagrant-cloud/step_upload.go index 4fb43028c..0e8d20606 100644 --- a/post-processor/vagrant-cloud/step_upload.go +++ b/post-processor/vagrant-cloud/step_upload.go @@ -21,7 +21,7 @@ func (s *stepUpload) Run(state multistep.StateBag) multistep.StepAction { resp, err := client.Upload(artifactFilePath, url) if err != nil || (resp.StatusCode != 200) { - state.Put("error", fmt.Errorf("Error uploading Box: %s", resp.Body)) + state.Put("error", fmt.Errorf("Error uploading Box: %s", err)) return multistep.ActionHalt } From 46535e3a3cb7147b0d194b5b7017457ae52bbb8e Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 12:46:25 -0400 Subject: [PATCH 181/593] post-processor/vagrant-cloud: better logging, document vcloud url --- .../vagrant-cloud/step_create_provider.go | 13 +++++++--- .../vagrant-cloud/step_create_version.go | 25 +++++++++++-------- .../vagrant-cloud/step_prepare_upload.go | 2 ++ .../vagrant-cloud/step_release_version.go | 8 +++--- post-processor/vagrant-cloud/step_upload.go | 4 +++ .../vagrant-cloud/step_verify_box.go | 2 ++ .../vagrant-cloud/step_verify_upload.go | 3 +++ .../vagrant-cloud.html.markdown | 3 +++ 8 files changed, 43 insertions(+), 17 deletions(-) diff --git a/post-processor/vagrant-cloud/step_create_provider.go b/post-processor/vagrant-cloud/step_create_provider.go index e149ddba1..887932c4f 100644 --- a/post-processor/vagrant-cloud/step_create_provider.go +++ b/post-processor/vagrant-cloud/step_create_provider.go @@ -56,8 +56,15 @@ func (s *stepCreateProvider) Run(state multistep.StateBag) multistep.StepAction } func (s *stepCreateProvider) Cleanup(state multistep.StateBag) { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + version := state.Get("version").(*Version) + // If we didn't save the provider name, it likely doesn't exist if s.name == "" { + ui.Say("Cleaning up provider") + ui.Message("Provider was not created, not deleting") return } @@ -70,10 +77,8 @@ func (s *stepCreateProvider) Cleanup(state multistep.StateBag) { return } - client := state.Get("client").(*VagrantCloudClient) - ui := state.Get("ui").(packer.Ui) - box := state.Get("box").(*Box) - version := state.Get("version").(*Version) + ui.Say("Cleaning up provider") + ui.Message(fmt.Sprintf("Deleting provider: %s", s.name)) path := fmt.Sprintf("box/%s/version/%v/provider/%s", box.Tag, version.Number, s.name) diff --git a/post-processor/vagrant-cloud/step_create_version.go b/post-processor/vagrant-cloud/step_create_version.go index 882a5971d..8108577cc 100644 --- a/post-processor/vagrant-cloud/step_create_version.go +++ b/post-processor/vagrant-cloud/step_create_version.go @@ -7,9 +7,9 @@ import ( ) type Version struct { - Version string `json:"version"` - Description string `json:"description,omitempty"` - Number uint `json:"number,omitempty"` + Version string `json:"version"` + Description string `json:"description,omitempty"` + Number uint `json:"number,omitempty"` } type stepCreateVersion struct { @@ -22,8 +22,10 @@ func (s *stepCreateVersion) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(Config) box := state.Get("box").(*Box) + ui.Say(fmt.Sprintf("Creating version: %s", config.Version)) + if hasVersion, v := box.HasVersion(config.Version); hasVersion { - ui.Say(fmt.Sprintf("Version exists: %s", config.Version)) + ui.Message(fmt.Sprintf("Version exists, skipping creation")) state.Put("version", v) return multistep.ActionContinue } @@ -36,8 +38,6 @@ func (s *stepCreateVersion) Run(state multistep.StateBag) multistep.StepAction { wrapper := make(map[string]interface{}) wrapper["version"] = version - ui.Say(fmt.Sprintf("Creating version: %s", config.Version)) - resp, err := client.Post(path, wrapper) if err != nil || (resp.StatusCode != 200) { @@ -61,9 +61,15 @@ func (s *stepCreateVersion) Run(state multistep.StateBag) multistep.StepAction { } func (s *stepCreateVersion) Cleanup(state multistep.StateBag) { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + config := state.Get("config").(Config) + box := state.Get("box").(*Box) + // If we didn't save the version number, it likely doesn't exist or // already existed if s.number == 0 { + ui.Message("Version was not created or previously existed, not deleting") return } @@ -76,12 +82,11 @@ func (s *stepCreateVersion) Cleanup(state multistep.StateBag) { return } - client := state.Get("client").(*VagrantCloudClient) - ui := state.Get("ui").(packer.Ui) - box := state.Get("box").(*Box) - path := fmt.Sprintf("box/%s/version/%v", box.Tag, s.number) + ui.Say("Cleaning up version") + ui.Message(fmt.Sprintf("Deleting version: %s", config.Version)) + // No need for resp from the cleanup DELETE _, err := client.Delete(path) diff --git a/post-processor/vagrant-cloud/step_prepare_upload.go b/post-processor/vagrant-cloud/step_prepare_upload.go index 5c7fee2b9..5c82d02c3 100644 --- a/post-processor/vagrant-cloud/step_prepare_upload.go +++ b/post-processor/vagrant-cloud/step_prepare_upload.go @@ -41,6 +41,8 @@ func (s *stepPrepareUpload) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + ui.Message(fmt.Sprintf("Box upload prepared with token %s", upload.Token)) + // Save the upload details to the state state.Put("upload", upload) diff --git a/post-processor/vagrant-cloud/step_release_version.go b/post-processor/vagrant-cloud/step_release_version.go index 4e40f5ca3..43512bdca 100644 --- a/post-processor/vagrant-cloud/step_release_version.go +++ b/post-processor/vagrant-cloud/step_release_version.go @@ -16,15 +16,15 @@ func (s *stepReleaseVersion) Run(state multistep.StateBag) multistep.StepAction version := state.Get("version").(*Version) config := state.Get("config").(Config) + ui.Say(fmt.Sprintf("Releasing version: %s", version.Version)) + if config.NoRelease { - ui.Say(fmt.Sprintf("Not releasing version due to configuration: %s", version.Version)) + ui.Message("Not releasing version due to configuration") return multistep.ActionContinue } path := fmt.Sprintf("box/%s/version/%v/release", box.Tag, version.Number) - ui.Say(fmt.Sprintf("Releasing version: %s", version.Version)) - resp, err := client.Put(path) if err != nil || (resp.StatusCode != 200) { @@ -34,6 +34,8 @@ func (s *stepReleaseVersion) Run(state multistep.StateBag) multistep.StepAction return multistep.ActionHalt } + ui.Message(fmt.Sprintf("Version successfully released and available")) + return multistep.ActionContinue } diff --git a/post-processor/vagrant-cloud/step_upload.go b/post-processor/vagrant-cloud/step_upload.go index 0e8d20606..f82f125f8 100644 --- a/post-processor/vagrant-cloud/step_upload.go +++ b/post-processor/vagrant-cloud/step_upload.go @@ -18,6 +18,8 @@ func (s *stepUpload) Run(state multistep.StateBag) multistep.StepAction { ui.Say(fmt.Sprintf("Uploading box: %s", artifactFilePath)) + ui.Message("Depending on your internet connection and the size of the box, this may take some time") + resp, err := client.Upload(artifactFilePath, url) if err != nil || (resp.StatusCode != 200) { @@ -25,6 +27,8 @@ func (s *stepUpload) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + ui.Message("Box succesfully uploaded") + return multistep.ActionContinue } diff --git a/post-processor/vagrant-cloud/step_verify_box.go b/post-processor/vagrant-cloud/step_verify_box.go index 32ae9a5b6..bbbc3b4b7 100644 --- a/post-processor/vagrant-cloud/step_verify_box.go +++ b/post-processor/vagrant-cloud/step_verify_box.go @@ -52,6 +52,8 @@ func (s *stepVerifyBox) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + ui.Message("Box accessible and matches tag") + // Keep the box in state for later state.Put("box", box) diff --git a/post-processor/vagrant-cloud/step_verify_upload.go b/post-processor/vagrant-cloud/step_verify_upload.go index eb787886f..120bd647d 100644 --- a/post-processor/vagrant-cloud/step_verify_upload.go +++ b/post-processor/vagrant-cloud/step_verify_upload.go @@ -78,6 +78,7 @@ func (s *stepVerifyUpload) Run(state multistep.StateBag) multistep.StepAction { } }() + ui.Message("Waiting for upload token match") log.Printf("Waiting for up to 600 seconds for provider hosted token to match %s", upload.Token) select { @@ -87,7 +88,9 @@ func (s *stepVerifyUpload) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + ui.Message(fmt.Sprintf("Upload succesfully verified with token %s", providerCheck.HostedToken)) log.Printf("Box succesfully verified %s == %s", upload.Token, providerCheck.HostedToken) + return multistep.ActionContinue case <-time.After(600 * time.Second): state.Put("error", fmt.Errorf("Timeout while waiting to for upload to verify token '%s'", upload.Token)) diff --git a/website/source/docs/post-processors/vagrant-cloud.html.markdown b/website/source/docs/post-processors/vagrant-cloud.html.markdown index d95ee9343..499234262 100644 --- a/website/source/docs/post-processors/vagrant-cloud.html.markdown +++ b/website/source/docs/post-processors/vagrant-cloud.html.markdown @@ -66,6 +66,9 @@ access to on Vagrant Cloud, as well as authentication and version information. on Vagrant Cloud, making it active. You can manually release the version via the API or Web UI. Defaults to false. +* `vagrant_cloud_url` (string) - Override the base URL for Vagrant Cloud. This +is useful if you're using Vagrant Private Cloud in your own network. Defaults +to `https://vagrantcloud.com/api/v1` ## Use with Vagrant Post-Processor From e528cd7c8cf765321e1395fe815f93bb5c0b4388 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 12:49:44 -0400 Subject: [PATCH 182/593] post-processor/vagrant-cloud: fix artifact --- post-processor/vagrant-cloud/artifact.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/post-processor/vagrant-cloud/artifact.go b/post-processor/vagrant-cloud/artifact.go index cebef8696..775bc2998 100644 --- a/post-processor/vagrant-cloud/artifact.go +++ b/post-processor/vagrant-cloud/artifact.go @@ -2,19 +2,18 @@ package vagrantcloud import ( "fmt" - "os" ) const BuilderId = "pearkes.post-processor.vagrant-cloud" type Artifact struct { - Path string + Tag string Provider string } -func NewArtifact(provider, path string) *Artifact { +func NewArtifact(provider, tag string) *Artifact { return &Artifact{ - Path: path, + Tag: tag, Provider: provider, } } @@ -24,7 +23,7 @@ func (*Artifact) BuilderId() string { } func (a *Artifact) Files() []string { - return []string{a.Path} + return nil } func (a *Artifact) Id() string { @@ -32,9 +31,9 @@ func (a *Artifact) Id() string { } func (a *Artifact) String() string { - return fmt.Sprintf("'%s' provider box: %s", a.Provider, a.Path) + return fmt.Sprintf("'%s': %s", a.Provider, a.Tag) } func (a *Artifact) Destroy() error { - return os.Remove(a.Path) + return nil } From 80fa018a3622c5bb61675791ac422511e0289f41 Mon Sep 17 00:00:00 2001 From: Carlos Sanchez Date: Thu, 26 Jun 2014 13:50:06 +0200 Subject: [PATCH 183/593] [GCE] gsutil update fails in newer images, use gcloud googlecompute: It looks like you are trying to run "/usr/local/bin/../share/google/google-cloud-sdk/bin/bootstrapping/gsutil.py update". googlecompute: The "update" command is no longer needed with the Cloud SDK. googlecompute: To update, run: gcloud components update --- builder/googlecompute/step_update_gsutil.go | 8 ++++---- builder/googlecompute/step_update_gsutil_test.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/builder/googlecompute/step_update_gsutil.go b/builder/googlecompute/step_update_gsutil.go index 9062c500f..0955be53e 100644 --- a/builder/googlecompute/step_update_gsutil.go +++ b/builder/googlecompute/step_update_gsutil.go @@ -28,18 +28,18 @@ func (s *StepUpdateGsutil) Run(state multistep.StateBag) multistep.StepAction { sudoPrefix = "sudo " } - gsutilUpdateCmd := "/usr/local/bin/gsutil update -n -f" + gsutilUpdateCmd := "/usr/local/bin/gcloud -q components update" cmd := new(packer.RemoteCmd) cmd.Command = fmt.Sprintf("%s%s", sudoPrefix, gsutilUpdateCmd) - ui.Say("Updating gsutil...") + ui.Say("Updating gcloud components...") err := cmd.StartWithUi(comm, ui) if err == nil && cmd.ExitStatus != 0 { err = fmt.Errorf( - "gsutil update exited with non-zero exit status: %d", cmd.ExitStatus) + "gcloud components update exited with non-zero exit status: %d", cmd.ExitStatus) } if err != nil { - err := fmt.Errorf("Error updating gsutil: %s", err) + err := fmt.Errorf("Error updating gcloud components: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt diff --git a/builder/googlecompute/step_update_gsutil_test.go b/builder/googlecompute/step_update_gsutil_test.go index 966857f93..7dd0eb3ac 100644 --- a/builder/googlecompute/step_update_gsutil_test.go +++ b/builder/googlecompute/step_update_gsutil_test.go @@ -32,7 +32,7 @@ func TestStepUpdateGsutil(t *testing.T) { if strings.HasPrefix(comm.StartCmd.Command, "sudo") { t.Fatal("should not sudo") } - if !strings.Contains(comm.StartCmd.Command, "gsutil update") { + if !strings.Contains(comm.StartCmd.Command, "gcloud -q components update") { t.Fatalf("bad command: %#v", comm.StartCmd.Command) } } @@ -79,7 +79,7 @@ func TestStepUpdateGsutil_nonRoot(t *testing.T) { if !strings.HasPrefix(comm.StartCmd.Command, "sudo") { t.Fatal("should sudo") } - if !strings.Contains(comm.StartCmd.Command, "gsutil update") { + if !strings.Contains(comm.StartCmd.Command, "gcloud -q components update") { t.Fatalf("bad command: %#v", comm.StartCmd.Command) } } From b23fe724cb7b80e5a4a73efeb80a6556383b9ff2 Mon Sep 17 00:00:00 2001 From: Carlos Sanchez Date: Thu, 26 Jun 2014 13:51:49 +0200 Subject: [PATCH 184/593] Add gox installation instructions to Contributing --- CONTRIBUTING.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe691d1f6..de8b8fb60 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,19 +56,32 @@ following steps in order to be able to compile and test Packer. 1. Install Go. Make sure the Go version is at least Go 1.2. Packer will not work with anything less than Go 1.2. On a Mac, you can `brew install go` to install Go 1.2. -2. Set and export the `GOPATH` environment variable. For example, you can - add `export GOPATH=$HOME/Documents/golang` to your `.bash_profile`. +2. Set and export the `GOPATH` environment variable and update your `PATH`. + For example, you can add to your `.bash_profile`. -3. Download the Packer source (and its dependencies) by running + ``` + export GOPATH=$HOME/Documents/golang + export PATH=$PATH:$GOPATH/bin + ``` + +3. Install and build `gox` with + + ``` + go get github.com/mitchellh/gox + cd $GOPATH/src/github.com/mitchellh/gox + go build + ``` + +4. Download the Packer source (and its dependencies) by running `go get github.com/mitchellh/packer`. This will download the Packer source to `$GOPATH/src/github.com/mitchellh/packer`. -4. Make your changes to the Packer source. You can run `make` from the main +5. Make your changes to the Packer source. You can run `make` from the main source directory to recompile all the binaries. Any compilation errors will be shown when the binaries are rebuilding. -5. Test your changes by running `make test` and then running +6. Test your changes by running `make test` and then running `$GOPATH/src/github.com/mitchellh/packer/bin/packer` to build a machine. -6. If everything works well and the tests pass, run `go fmt` on your code +7. If everything works well and the tests pass, run `go fmt` on your code before submitting a pull request. From cf3913d05128d012ef56285d50f89227787d0a7d Mon Sep 17 00:00:00 2001 From: Neil Edwards Date: Sat, 28 Jun 2014 00:24:37 -0400 Subject: [PATCH 185/593] Images link is 404, now must use API --- website/source/docs/builders/digitalocean.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/builders/digitalocean.html.markdown b/website/source/docs/builders/digitalocean.html.markdown index 5c00ad6e7..6e0aad49a 100644 --- a/website/source/docs/builders/digitalocean.html.markdown +++ b/website/source/docs/builders/digitalocean.html.markdown @@ -43,7 +43,7 @@ each category, the available configuration keys are alphabetized. * `image` (string) - The name (or slug) of the base image to use. This is the image that will be used to launch a new droplet and provision it. This defaults to 'ubuntu-12-04-x64' which is the slug for "Ubuntu 12.04.4 x64". - See https://developers.digitalocean.com/images/ for the accepted image names/slugs. + See https://developers.digitalocean.com/#list-all-images for deatils on how to get a list of the the accepted image names/slugs. * `image_id` (integer) - The ID of the base image to use. This is the image that will be used to launch a new droplet and provision it. From c67661d54dda7dc3ce5a32e2ca401aa96b28d9c0 Mon Sep 17 00:00:00 2001 From: Neil Edwards Date: Sat, 28 Jun 2014 00:25:43 -0400 Subject: [PATCH 186/593] Spelling --- website/source/docs/builders/digitalocean.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/builders/digitalocean.html.markdown b/website/source/docs/builders/digitalocean.html.markdown index 6e0aad49a..ab971c136 100644 --- a/website/source/docs/builders/digitalocean.html.markdown +++ b/website/source/docs/builders/digitalocean.html.markdown @@ -43,7 +43,7 @@ each category, the available configuration keys are alphabetized. * `image` (string) - The name (or slug) of the base image to use. This is the image that will be used to launch a new droplet and provision it. This defaults to 'ubuntu-12-04-x64' which is the slug for "Ubuntu 12.04.4 x64". - See https://developers.digitalocean.com/#list-all-images for deatils on how to get a list of the the accepted image names/slugs. + See https://developers.digitalocean.com/#list-all-images for details on how to get a list of the the accepted image names/slugs. * `image_id` (integer) - The ID of the base image to use. This is the image that will be used to launch a new droplet and provision it. From 946843982fae3fc048c8aea8777b8638386aa300 Mon Sep 17 00:00:00 2001 From: Ian Delahorne Date: Sun, 29 Jun 2014 23:45:50 -0500 Subject: [PATCH 187/593] Workaround for gophercloud.ServerById crashing, fixes #1257 gophercloud.ServerById is broken in v0.1.0 - it will crash if you feed it a non-existing server ID (see [rackspace/gophercloud #168](https://github.com/rackspace/gophercloud/issues/168)) Instead, list all servers and iterate over them. If the server id isn't found, return "DELETED" as a state. Not perfect but it works until next version of gophercloud is released. --- builder/openstack/server.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/builder/openstack/server.go b/builder/openstack/server.go index fb48659b1..47dee0557 100644 --- a/builder/openstack/server.go +++ b/builder/openstack/server.go @@ -33,12 +33,22 @@ type StateChangeConf struct { // an openstacn server. func ServerStateRefreshFunc(csp gophercloud.CloudServersProvider, s *gophercloud.Server) StateRefreshFunc { return func() (interface{}, string, int, error) { - resp, err := csp.ServerById(s.Id) + servers, err := csp.ListServers() if err != nil { log.Printf("Error on ServerStateRefresh: %s", err) return nil, "", 0, err } - + var resp *gophercloud.Server + found := false + for _, server := range servers { + if server.Id == s.Id { + found = true + resp = &server + } + } + if found == false { + return nil, "DELETED", 0, nil + } return resp, resp.Status, resp.Progress, nil } } From 3af14d3dc7334303deb2585f5ed99435538d50fe Mon Sep 17 00:00:00 2001 From: Ian Delahorne Date: Sun, 29 Jun 2014 23:50:01 -0500 Subject: [PATCH 188/593] Fix typo --- builder/openstack/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/openstack/server.go b/builder/openstack/server.go index 47dee0557..8a8f0f01f 100644 --- a/builder/openstack/server.go +++ b/builder/openstack/server.go @@ -30,7 +30,7 @@ type StateChangeConf struct { } // ServerStateRefreshFunc returns a StateRefreshFunc that is used to watch -// an openstacn server. +// an openstack server. func ServerStateRefreshFunc(csp gophercloud.CloudServersProvider, s *gophercloud.Server) StateRefreshFunc { return func() (interface{}, string, int, error) { servers, err := csp.ListServers() From 2d1e1cc83ebb1dc632c62adea93c460b70c49329 Mon Sep 17 00:00:00 2001 From: Ian Delahorne Date: Mon, 30 Jun 2014 09:07:36 -0500 Subject: [PATCH 189/593] Sort AMI names in Artifact.String - fixes random test failures With go tip, the output from Artifact.String will sometimes be output in a different order than the tests. Sort the AMI strings before outputting. See https://travis-ci.org/mitchellh/packer/jobs/28748467 for an example of this failure. --- builder/amazon/common/artifact.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builder/amazon/common/artifact.go b/builder/amazon/common/artifact.go index 3913833d7..3dc33b4b9 100644 --- a/builder/amazon/common/artifact.go +++ b/builder/amazon/common/artifact.go @@ -6,6 +6,7 @@ import ( "github.com/mitchellh/goamz/ec2" "github.com/mitchellh/packer/packer" "log" + "sort" "strings" ) @@ -46,6 +47,7 @@ func (a *Artifact) String() string { amiStrings = append(amiStrings, single) } + sort.Sort(sort.StringSlice(amiStrings)) return fmt.Sprintf("AMIs were created:\n\n%s", strings.Join(amiStrings, "\n")) } From 753a990cd5494847779ae6da5fc37460516e8453 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Mon, 30 Jun 2014 10:54:56 -0700 Subject: [PATCH 190/593] Update CHANGELOG.md builder/amazon-common: Sort AMI strings before outputting [GH-1305] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49c1a7696..33ec42e66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ IMPROVEMENTS: + * builder/amazon-common: Sort AMI strings before outputting [GH-1305] * builder/amazon-ebs: Support encrypted EBS volumes [GH-1194] * builder/ansible: Add `playbook_dir` option. [GH-1000] * builder/openstack: Add ability to configure networks. [GH-1261] From 25ff0f04e698be63eb5312977f9a1f4cbd3d6387 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Mon, 30 Jun 2014 10:58:41 -0700 Subject: [PATCH 191/593] Update CHANGELOG.md builder/openstack: Workaround for gophercloud.ServerById crashing [GH-1257] --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33ec42e66..bf60e53d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ IMPROVEMENTS: - * builder/amazon-common: Sort AMI strings before outputting [GH-1305] * builder/amazon-ebs: Support encrypted EBS volumes [GH-1194] * builder/ansible: Add `playbook_dir` option. [GH-1000] * builder/openstack: Add ability to configure networks. [GH-1261] @@ -19,7 +18,9 @@ IMPROVEMENTS: BUG FIXES: + * builder/amazon-common: Sort AMI strings before outputting [GH-1305] * core: `isotime` is the same time during the entire build. [GH-1153] + * builder/openstack: Workaround for gophercloud.ServerById crashing [GH-1257] * builder/openstack: Force IPv4 addresses from address pools [GH-1258] * builder/parallels: Do not delete entire CDROM device. [GH-1115] * builder/parallels: Errors while creating floppy disk. [GH-1225] From 0da4aa6f2417c3e3a4417af7d49441c0b4b6e7fb Mon Sep 17 00:00:00 2001 From: Ian Delahorne Date: Mon, 30 Jun 2014 19:51:48 -0500 Subject: [PATCH 192/593] Replace ListServers hack with update ServerById [gophercloud#168](https://github.com/rackspace/gophercloud/issues/168) has been patched to not panic on non-existing server id's. If an error is returned, check if the error is a 404 first before bailing. --- builder/openstack/server.go | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/builder/openstack/server.go b/builder/openstack/server.go index 8a8f0f01f..dbe2f52ef 100644 --- a/builder/openstack/server.go +++ b/builder/openstack/server.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "github.com/mitchellh/multistep" + "github.com/racker/perigee" "github.com/rackspace/gophercloud" "log" "time" @@ -33,22 +34,18 @@ type StateChangeConf struct { // an openstack server. func ServerStateRefreshFunc(csp gophercloud.CloudServersProvider, s *gophercloud.Server) StateRefreshFunc { return func() (interface{}, string, int, error) { - servers, err := csp.ListServers() + resp, err := csp.ServerById(s.Id) if err != nil { - log.Printf("Error on ServerStateRefresh: %s", err) - return nil, "", 0, err - } - var resp *gophercloud.Server - found := false - for _, server := range servers { - if server.Id == s.Id { - found = true - resp = &server + urce, ok := err.(*perigee.UnexpectedResponseCodeError) + if ok && (urce.Actual == 404) { + log.Printf("404 on ServerStateRefresh, returning DELETED") + + return nil, "DELETED", 0, nil + } else { + log.Printf("Error on ServerStateRefresh: %s", err) + return nil, "", 0, err } } - if found == false { - return nil, "DELETED", 0, nil - } return resp, resp.Status, resp.Progress, nil } } From 982e523ba2e0fb83f2396aa372cd4fba9285929f Mon Sep 17 00:00:00 2001 From: yuuzi41 Date: Wed, 2 Jul 2014 21:55:47 +0900 Subject: [PATCH 193/593] fix invalid esx5 path separator in windows before, this code had joining path elements by filepath module. filepath module generate path string with backslash-joined in Windows. but ESX require path string with slash-joined. it means that this code generate illegal path string in windows. illegal path string raised "Error creating disk". this patch fixes path separator from backslash to slash in windows. from this, creating disk would succeed without error. --- builder/vmware/iso/driver_esx5.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index cf9ca9dbf..d72c65062 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -64,7 +64,7 @@ func (d *ESX5Driver) Stop(vmxPathLocal string) error { } func (d *ESX5Driver) Register(vmxPathLocal string) error { - vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal)) + vmxPath := filepath.ToSlash(filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))) if err := d.upload(vmxPath, vmxPathLocal); err != nil { return err } @@ -92,7 +92,7 @@ func (d *ESX5Driver) UploadISO(localPath string) (string, error) { } finalPath := d.datastorePath(targetFile) - if err := d.mkdir(filepath.Dir(finalPath)); err != nil { + if err := d.mkdir(filepath.ToSlash(filepath.Dir(finalPath))); err != nil { return "", err } @@ -233,7 +233,7 @@ func (d *ESX5Driver) ListFiles() ([]string, error) { continue } - files = append(files, filepath.Join(d.outputDir, string(line))) + files = append(files, filepath.ToSlash(filepath.Join(d.outputDir, string(line)))) } return files, nil @@ -261,7 +261,7 @@ func (d *ESX5Driver) String() string { func (d *ESX5Driver) datastorePath(path string) string { baseDir := filepath.Base(filepath.Dir(path)) - return filepath.Join("/vmfs/volumes", d.Datastore, baseDir, filepath.Base(path)) + return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.Datastore, baseDir, filepath.Base(path))) } func (d *ESX5Driver) connect() error { From cbfb6ec307bb1383d24831e41f52202cf2969fe5 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Sat, 5 Jul 2014 07:03:33 -0700 Subject: [PATCH 194/593] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf60e53d4..1b4b56e60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ BUG FIXES: * builder/parallels: Errors while creating floppy disk. [GH-1225] * builder/parallels: Errors while removing floppy drive. [GH-1226] * builder/virtualbox-ovf: Supports guest additions options. [GH-1120] + * builder/vmware-iso: Fix esx5 path separator in windows. [GH-1316] * builder/vmware: Remote ESXi builder now uploads floppy. [GH-1106] * post-processor/vsphere: Accept DOMAIN\account usernames [GH-1178] From bd1a0d07fb6b4fdb1907b21cb81a8e7157108995 Mon Sep 17 00:00:00 2001 From: Nina Berg Date: Tue, 8 Jul 2014 13:15:17 -0400 Subject: [PATCH 195/593] Added some variables to amazon-ebs builder and chef-client provisioner --- builder/amazon/common/run_config.go | 49 ++++++++++++++------------ provisioner/chef-client/provisioner.go | 42 ++++++++++++---------- 2 files changed, 49 insertions(+), 42 deletions(-) diff --git a/builder/amazon/common/run_config.go b/builder/amazon/common/run_config.go index c50c22f7e..d7dd6ec99 100644 --- a/builder/amazon/common/run_config.go +++ b/builder/amazon/common/run_config.go @@ -42,6 +42,32 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { } } + templates := map[string]*string{ + "iam_instance_profile": &c.IamInstanceProfile, + "instance_type": &c.InstanceType, + "ssh_timeout": &c.RawSSHTimeout, + "ssh_username": &c.SSHUsername, + "ssh_private_key_file": &c.SSHPrivateKeyFile, + "source_ami": &c.SourceAmi, + "subnet_id": &c.SubnetId, + "temporary_key_pair_name": &c.TemporaryKeyPairName, + "vpc_id": &c.VpcId, + "availability_zone": &c.AvailabilityZone, + "user_data": &c.UserData, + "user_data_file": &c.UserDataFile, + "security_group_id": &c.SecurityGroupId, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append( + errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + // Defaults if c.SSHPort == 0 { c.SSHPort = 22 @@ -57,7 +83,6 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { // Validation var err error - errs := make([]error, 0) if c.SourceAmi == "" { errs = append(errs, errors.New("A source_ami must be specified")) } @@ -87,28 +112,6 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { } } - templates := map[string]*string{ - "iam_instance_profile": &c.IamInstanceProfile, - "instance_type": &c.InstanceType, - "ssh_timeout": &c.RawSSHTimeout, - "ssh_username": &c.SSHUsername, - "ssh_private_key_file": &c.SSHPrivateKeyFile, - "source_ami": &c.SourceAmi, - "subnet_id": &c.SubnetId, - "temporary_key_pair_name": &c.TemporaryKeyPairName, - "vpc_id": &c.VpcId, - "availability_zone": &c.AvailabilityZone, - } - - for n, ptr := range templates { - var err error - *ptr, err = t.Process(*ptr, nil) - if err != nil { - errs = append( - errs, fmt.Errorf("Error processing %s: %s", n, err)) - } - } - sliceTemplates := map[string][]string{ "security_group_ids": c.SecurityGroupIds, } diff --git a/provisioner/chef-client/provisioner.go b/provisioner/chef-client/provisioner.go index 1e2847f54..83ea57efc 100644 --- a/provisioner/chef-client/provisioner.go +++ b/provisioner/chef-client/provisioner.go @@ -71,6 +71,29 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } p.config.tpl.UserVars = p.config.PackerUserVars + // Accumulate any errors + errs := common.CheckUnusedConfig(md) + + templates := map[string]*string{ + "config_template": &p.config.ConfigTemplate, + "node_name": &p.config.NodeName, + "staging_dir": &p.config.StagingDir, + "chef_server_url": &p.config.ServerUrl, + "execute_command": &p.config.ExecuteCommand, + "install_command": &p.config.InstallCommand, + "validation_key_path": &p.config.ValidationKeyPath, + "validation_client_name": &p.config.ValidationClientName, + } + + for n, ptr := range templates { + var err error + *ptr, err = p.config.tpl.Process(*ptr, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + if p.config.ExecuteCommand == "" { p.config.ExecuteCommand = "{{if .Sudo}}sudo {{end}}chef-client " + "--no-color -c {{.ConfigPath}} -j {{.JsonPath}}" @@ -90,25 +113,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { p.config.StagingDir = "/tmp/packer-chef-client" } - // Accumulate any errors - errs := common.CheckUnusedConfig(md) - - templates := map[string]*string{ - "config_template": &p.config.ConfigTemplate, - "node_name": &p.config.NodeName, - "staging_dir": &p.config.StagingDir, - "chef_server_url": &p.config.ServerUrl, - } - - for n, ptr := range templates { - var err error - *ptr, err = p.config.tpl.Process(*ptr, nil) - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Error processing %s: %s", n, err)) - } - } - sliceTemplates := map[string][]string{ "run_list": p.config.RunList, } From 18dddb516b78a4c4ff83adc5e3f6ae4dc9d9a91d Mon Sep 17 00:00:00 2001 From: sawanoboly Date: Fri, 11 Jul 2014 19:12:34 +0900 Subject: [PATCH 196/593] Fix remotePaths when provisioning Linux from Windows --- provisioner/chef-client/provisioner.go | 4 ++-- provisioner/chef-solo/provisioner.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/provisioner/chef-client/provisioner.go b/provisioner/chef-client/provisioner.go index 1e2847f54..c880691c3 100644 --- a/provisioner/chef-client/provisioner.go +++ b/provisioner/chef-client/provisioner.go @@ -286,7 +286,7 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, nodeN return "", err } - remotePath := filepath.Join(p.config.StagingDir, "client.rb") + remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "client.rb")) if err := comm.Upload(remotePath, bytes.NewReader([]byte(configString))); err != nil { return "", err } @@ -315,7 +315,7 @@ func (p *Provisioner) createJson(ui packer.Ui, comm packer.Communicator) (string } // Upload the bytes - remotePath := filepath.Join(p.config.StagingDir, "first-boot.json") + remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "first-boot.json")) if err := comm.Upload(remotePath, bytes.NewReader(jsonBytes)); err != nil { return "", err } diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 1e5a47241..2166a9d0c 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -378,7 +378,7 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local return "", err } - remotePath := filepath.Join(p.config.StagingDir, "solo.rb") + remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "solo.rb")) if err := comm.Upload(remotePath, bytes.NewReader([]byte(configString))); err != nil { return "", err } @@ -407,7 +407,7 @@ func (p *Provisioner) createJson(ui packer.Ui, comm packer.Communicator) (string } // Upload the bytes - remotePath := filepath.Join(p.config.StagingDir, "node.json") + remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "node.json")) if err := comm.Upload(remotePath, bytes.NewReader(jsonBytes)); err != nil { return "", err } From aa844ef4c78fbee5b9f07a8051ca1c391b9bc7eb Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Fri, 11 Jul 2014 06:21:16 -0700 Subject: [PATCH 197/593] Update vagrant-cloud.html.markdown alphabetize options --- .../source/docs/post-processors/vagrant-cloud.html.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/source/docs/post-processors/vagrant-cloud.html.markdown b/website/source/docs/post-processors/vagrant-cloud.html.markdown index 499234262..273b0cf32 100644 --- a/website/source/docs/post-processors/vagrant-cloud.html.markdown +++ b/website/source/docs/post-processors/vagrant-cloud.html.markdown @@ -59,9 +59,6 @@ access to on Vagrant Cloud, as well as authentication and version information. ### Optional: -* `version_description` (string) - Optionally markdown text used as a full-length - and in-depth description of the version, typically for denoting changes introduced - * `no_release` (string) - If set to true, does not release the version on Vagrant Cloud, making it active. You can manually release the version via the API or Web UI. Defaults to false. @@ -70,6 +67,9 @@ via the API or Web UI. Defaults to false. is useful if you're using Vagrant Private Cloud in your own network. Defaults to `https://vagrantcloud.com/api/v1` +* `version_description` (string) - Optionally markdown text used as a full-length + and in-depth description of the version, typically for denoting changes introduced + ## Use with Vagrant Post-Processor You'll need to use the Vagrant post-processor before using this post-processor. From 197c8664dd417a9fe048f6d16bce43a5ca9f87d5 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Fri, 11 Jul 2014 07:23:04 -0700 Subject: [PATCH 198/593] Update CHANGELOG.md provisioner/chef-*: Fix remotePaths for Windows [GH-394] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b4b56e60..38155c4f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ BUG FIXES: * builder/vmware-iso: Fix esx5 path separator in windows. [GH-1316] * builder/vmware: Remote ESXi builder now uploads floppy. [GH-1106] * post-processor/vsphere: Accept DOMAIN\account usernames [GH-1178] + * provisioner/chef-*: Fix remotePaths for Windows [GH-394] ## 0.6.0 (May 2, 2014) From a2dd87795dfa96efac497e3317cb78a3b3807506 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Fri, 11 Jul 2014 07:28:24 -0700 Subject: [PATCH 199/593] Update CHANGELOG.md **New post processor:** `vagrant-cloud` - Push box files generated by vagrant post processor to Vagrant Cloud. [GH-1289] --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38155c4f6..a9335a44f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## 0.6.1 (unreleased) +FEATURES: + + * **New post processor:** `vagrant-cloud` - Push box files generated by + vagrant post processor to Vagrant Cloud. [GH-1289] + IMPROVEMENTS: * builder/amazon-ebs: Support encrypted EBS volumes [GH-1194] From a03cf62a29c6972188c7d13812d6f0ca67b47ddd Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Fri, 11 Jul 2014 14:27:24 -0400 Subject: [PATCH 200/593] website: add warning to vagrant-cloud docs --- .../docs/post-processors/vagrant-cloud.html.markdown | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/website/source/docs/post-processors/vagrant-cloud.html.markdown b/website/source/docs/post-processors/vagrant-cloud.html.markdown index 273b0cf32..b43009232 100644 --- a/website/source/docs/post-processors/vagrant-cloud.html.markdown +++ b/website/source/docs/post-processors/vagrant-cloud.html.markdown @@ -5,6 +5,14 @@ page_title: "Vagrant Cloud Post-Processor" # Vagrant Cloud Post-Processor +
    +

    +This is an unreleased feature. To use it now, +please build Packer from source. A release is scheduled for the week +of 07/14/2014. +

    +
    + Type: `vagrant-cloud` The Vagrant Cloud post-processor recieves a Vagrant box from the `vagrant` From 7549735bd31985e77bdad376fb7ac0241cb68b45 Mon Sep 17 00:00:00 2001 From: Hiroaki Nakamura Date: Sat, 12 Jul 2014 19:08:10 +0900 Subject: [PATCH 201/593] Accept VirtualBox release candidate version (ex. 4.3.14_RC1 from 4.3.14_RC1r94870) --- builder/virtualbox/common/driver_4_2.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builder/virtualbox/common/driver_4_2.go b/builder/virtualbox/common/driver_4_2.go index fc383c5b8..901822174 100644 --- a/builder/virtualbox/common/driver_4_2.go +++ b/builder/virtualbox/common/driver_4_2.go @@ -186,9 +186,9 @@ func (d *VBox42Driver) Version() (string, error) { return "", fmt.Errorf("VirtualBox is not properly setup: %s", versionOutput) } - versionRe := regexp.MustCompile("[^.0-9]") - matches := versionRe.Split(versionOutput, 2) - if len(matches) == 0 || matches[0] == "" { + versionRe := regexp.MustCompile("^[.0-9]+(?:_RC[0-9]+)?") + matches := versionRe.FindAllString(versionOutput, 1) + if matches == nil { return "", fmt.Errorf("No version found: %s", versionOutput) } From 49e5b9d21d0b74fe6f2b85c76a5f266b73b2eb58 Mon Sep 17 00:00:00 2001 From: Tehmasp Chaudhri Date: Wed, 16 Jul 2014 16:49:26 -0600 Subject: [PATCH 202/593] Changed to reflect the new httpS endpoint for Chef, since Chef 11.x --- website/source/docs/provisioners/chef-client.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/provisioners/chef-client.html.markdown b/website/source/docs/provisioners/chef-client.html.markdown index 236e3b4c8..15cb4dcb1 100644 --- a/website/source/docs/provisioners/chef-client.html.markdown +++ b/website/source/docs/provisioners/chef-client.html.markdown @@ -23,7 +23,7 @@ remote machine and run Chef client.
     {
       "type": "chef-client",
    -  "server_url": "http://mychefserver.com:4000/"
    +  "server_url": "https://mychefserver.com/"
     }
     
    From d7268a11f74bc2a142153e8fc21fe4d33f1437e8 Mon Sep 17 00:00:00 2001 From: iweb Date: Thu, 17 Jul 2014 15:01:19 +0100 Subject: [PATCH 203/593] Add support for building QEMU images from pre-existing image files If the `disk_image` parameter is set to true treat the ISO location as the source of the disk image, copy it to the destination, resize and boot it. --- builder/qemu/builder.go | 3 +++ builder/qemu/step_copy_disk.go | 45 ++++++++++++++++++++++++++++++++ builder/qemu/step_create_disk.go | 4 +++ builder/qemu/step_resize_disk.go | 43 ++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 builder/qemu/step_copy_disk.go create mode 100644 builder/qemu/step_resize_disk.go diff --git a/builder/qemu/builder.go b/builder/qemu/builder.go index 7f404a566..8479e0a03 100644 --- a/builder/qemu/builder.go +++ b/builder/qemu/builder.go @@ -61,6 +61,7 @@ type config struct { FloppyFiles []string `mapstructure:"floppy_files"` Format string `mapstructure:"format"` Headless bool `mapstructure:"headless"` + DiskImage bool `mapstructure:"disk_image"` HTTPDir string `mapstructure:"http_directory"` HTTPPortMin uint `mapstructure:"http_port_min"` HTTPPortMax uint `mapstructure:"http_port_max"` @@ -396,6 +397,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Files: b.config.FloppyFiles, }, new(stepCreateDisk), + new(stepCopyDisk), + new(stepResizeDisk), new(stepHTTPServer), new(stepForwardSSH), new(stepConfigureVNC), diff --git a/builder/qemu/step_copy_disk.go b/builder/qemu/step_copy_disk.go new file mode 100644 index 000000000..c3645926b --- /dev/null +++ b/builder/qemu/step_copy_disk.go @@ -0,0 +1,45 @@ +package qemu + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "path/filepath" + "strings" +) + +// This step copies the virtual disk that will be used as the +// hard drive for the virtual machine. +type stepCopyDisk struct{} + +func (s *stepCopyDisk) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*config) + driver := state.Get("driver").(Driver) + isoPath := state.Get("iso_path").(string) + ui := state.Get("ui").(packer.Ui) + path := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", config.VMName, + strings.ToLower(config.Format))) + + command := []string{ + "convert", + "-f", config.Format, + isoPath, + path, + } + + if config.DiskImage == false { + return multistep.ActionContinue + } + + ui.Say("Copying hard drive...") + if err := driver.QemuImg(command...); err != nil { + err := fmt.Errorf("Error creating hard drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepCopyDisk) Cleanup(state multistep.StateBag) {} diff --git a/builder/qemu/step_create_disk.go b/builder/qemu/step_create_disk.go index 7e6f09b7d..7545a19f0 100644 --- a/builder/qemu/step_create_disk.go +++ b/builder/qemu/step_create_disk.go @@ -26,6 +26,10 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction { fmt.Sprintf("%vM", config.DiskSize), } + if config.DiskImage == true { + return multistep.ActionContinue + } + ui.Say("Creating hard drive...") if err := driver.QemuImg(command...); err != nil { err := fmt.Errorf("Error creating hard drive: %s", err) diff --git a/builder/qemu/step_resize_disk.go b/builder/qemu/step_resize_disk.go new file mode 100644 index 000000000..d81a4f127 --- /dev/null +++ b/builder/qemu/step_resize_disk.go @@ -0,0 +1,43 @@ +package qemu + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "path/filepath" + "strings" +) + +// This step resizes the virtual disk that will be used as the +// hard drive for the virtual machine. +type stepResizeDisk struct{} + +func (s *stepResizeDisk) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*config) + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + path := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", config.VMName, + strings.ToLower(config.Format))) + + command := []string{ + "resize", + path, + fmt.Sprintf("%vM", config.DiskSize), + } + + if config.DiskImage == false { + return multistep.ActionContinue + } + + ui.Say("Resizing hard drive...") + if err := driver.QemuImg(command...); err != nil { + err := fmt.Errorf("Error creating hard drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepResizeDisk) Cleanup(state multistep.StateBag) {} From ddd794c38ed574a984561f1752618c67b990a99e Mon Sep 17 00:00:00 2001 From: Aaron Brady Date: Thu, 17 Jul 2014 15:30:54 +0100 Subject: [PATCH 204/593] Update QEMU builder documentation with `disk_image` flag --- website/source/docs/builders/qemu.html.markdown | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/source/docs/builders/qemu.html.markdown b/website/source/docs/builders/qemu.html.markdown index 2430c737f..c757b5fb8 100644 --- a/website/source/docs/builders/qemu.html.markdown +++ b/website/source/docs/builders/qemu.html.markdown @@ -248,6 +248,11 @@ each category, the available options are alphabetized and described. Packer will choose a randomly available port in this range to use as the host port. +* `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. + ## Boot Command The `boot_command` configuration is very important: it specifies the keys From e932ec69782a803e7e1c93be2ee5c3130bc44647 Mon Sep 17 00:00:00 2001 From: Florian Noeding Date: Tue, 20 May 2014 17:55:48 +0200 Subject: [PATCH 205/593] amazon builders now handle templating of user_data and user_data_file --- builder/amazon/common/run_config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builder/amazon/common/run_config.go b/builder/amazon/common/run_config.go index c50c22f7e..e3805c229 100644 --- a/builder/amazon/common/run_config.go +++ b/builder/amazon/common/run_config.go @@ -98,6 +98,8 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { "temporary_key_pair_name": &c.TemporaryKeyPairName, "vpc_id": &c.VpcId, "availability_zone": &c.AvailabilityZone, + "user_data": &c.UserData, + "user_data_file": &c.UserDataFile, } for n, ptr := range templates { From de29431a43ee29f2ec70d79a345155cb5c6a83ac Mon Sep 17 00:00:00 2001 From: Florian Noeding Date: Tue, 6 May 2014 18:45:35 +0200 Subject: [PATCH 206/593] null-builder: added support for templating --- builder/null/config.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/builder/null/config.go b/builder/null/config.go index 94f3b21e7..4823df8ae 100644 --- a/builder/null/config.go +++ b/builder/null/config.go @@ -32,14 +32,28 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { c.tpl.UserVars = c.PackerUserVars - // Defaults if c.Port == 0 { c.Port = 22 } - // (none so far) errs := common.CheckUnusedConfig(md) + templates := map[string]*string{ + "host": &c.Host, + "ssh_username": &c.SSHUsername, + "ssh_password": &c.SSHPassword, + "ssh_private_key_file": &c.SSHPrivateKeyFile, + } + + for n, ptr := range templates { + var err error + *ptr, err = c.tpl.Process(*ptr, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + if c.Host == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("host must be specified")) From c75a671c1e8c10c6aeed2c71e42c6d3db60929d0 Mon Sep 17 00:00:00 2001 From: Misha Brukman Date: Thu, 17 Jul 2014 15:33:09 -0400 Subject: [PATCH 207/593] Cleaned up Go formatting with gofmt. --- post-processor/vsphere/post-processor.go | 4 ++-- provisioner/ansible-local/provisioner.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/post-processor/vsphere/post-processor.go b/post-processor/vsphere/post-processor.go index 7827494bd..c9a1b6018 100644 --- a/post-processor/vsphere/post-processor.go +++ b/post-processor/vsphere/post-processor.go @@ -3,12 +3,12 @@ package vsphere import ( "bytes" "fmt" - "log" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" + "log" + "net/url" "os/exec" "strings" - "net/url" ) var builtins = map[string]string{ diff --git a/provisioner/ansible-local/provisioner.go b/provisioner/ansible-local/provisioner.go index 861ee8cbc..e336024b8 100644 --- a/provisioner/ansible-local/provisioner.go +++ b/provisioner/ansible-local/provisioner.go @@ -29,7 +29,7 @@ type Config struct { // The playbook dir to upload. PlaybookDir string `mapstructure:"playbook_dir"` - + // The main playbook file to execute. PlaybookFile string `mapstructure:"playbook_file"` @@ -133,7 +133,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { errs = packer.MultiErrorAppend(errs, err) } } - + // Check that the group_vars directory exists, if configured if len(p.config.GroupVars) > 0 { if err := validateDirConfig(p.config.GroupVars, "group_vars"); err != nil { From 9cf711cfcb4236008526494e42c24d80da370afd Mon Sep 17 00:00:00 2001 From: Tehmasp Chaudhri Date: Thu, 17 Jul 2014 16:41:58 -0600 Subject: [PATCH 208/593] Fixed typo --- packer/config_template.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packer/config_template.go b/packer/config_template.go index b1084546d..6ee8f0ae1 100644 --- a/packer/config_template.go +++ b/packer/config_template.go @@ -90,7 +90,7 @@ func (t *ConfigTemplate) nextTemplateName() string { func (t *ConfigTemplate) templateUser(n string) (string, error) { result, ok := t.UserVars[n] if !ok { - return "", fmt.Errorf("uknown user var: %s", n) + return "", fmt.Errorf("unknown user var: %s", n) } return result, nil From 267876d3b3efd7aeb7a960e01b22b30b6fc40f1b Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Fri, 18 Jul 2014 10:43:21 +1000 Subject: [PATCH 209/593] Fixed Rackspace example As per https://github.com/mitchellh/packer/issues/1142, it's critical to add openstack_provider: rackspace in order to get anything to work reliably with Rackspace and Packer in 6.0. Question: should the openstack_provider field now be marked as required? --- website/source/docs/builders/openstack.html.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/source/docs/builders/openstack.html.markdown b/website/source/docs/builders/openstack.html.markdown index 613e03ff0..3f8c5bb87 100644 --- a/website/source/docs/builders/openstack.html.markdown +++ b/website/source/docs/builders/openstack.html.markdown @@ -120,7 +120,8 @@ Ubuntu 12.04 LTS (Precise Pangolin) on Rackspace OpenStack cloud offering. { "type": "openstack", "username": "", - "password": "", + "api_key": "", + "openstack_provider": "rackspace", "provider": "rackspace-us", "region": "DFW", "ssh_username": "root", From 073a813e880aef531fa5b0acd4e7eefb7192f3ea Mon Sep 17 00:00:00 2001 From: stefanlasiewski Date: Fri, 18 Jul 2014 10:15:05 -0700 Subject: [PATCH 210/593] Problem: While running through the getting-started examples, I ran into a strange error: packer $ packer validate example.json Failed to parse template: Error in line 16, char 2: invalid character '"' after object key:value pair "provisioners": [{ packer $ The error was not immediately obvious, as this is my first time working with Packer, and like many users this also happens to be my first time working with JSON. Solution: I was missing a comma after the 'builders' configuration. I'm sure this is a common problem, so let's specifically mention that in the document. --- website/source/intro/getting-started/provision.html.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/source/intro/getting-started/provision.html.markdown b/website/source/intro/getting-started/provision.html.markdown index 4276ae089..437605b08 100644 --- a/website/source/intro/getting-started/provision.html.markdown +++ b/website/source/intro/getting-started/provision.html.markdown @@ -60,7 +60,8 @@ of time to initialize. The sleep makes sure that the OS properly initializes. Hopefully it is obvious, but the `builders` section shouldn't actually contain "...", it should be the contents setup in the previous page -of the getting started guide. +of the getting started guide. Also note the comma after the `"builders": [...]` +array, which was not necessary in the previous lesson. To configure the provisioners, we add a new section `provisioners` to the template, alongside the `builders` configuration. The provisioners section From 47f073c44297d474f4e86aa98e8dc6fa06c27433 Mon Sep 17 00:00:00 2001 From: stefanlasiewski Date: Fri, 18 Jul 2014 10:15:05 -0700 Subject: [PATCH 211/593] Problem: Docs should mention the comma after `"builders": [...]` While running through the getting-started examples, I ran into a strange error: packer $ packer validate example.json Failed to parse template: Error in line 16, char 2: invalid character '"' after object key:value pair "provisioners": [{ packer $ The error was not immediately obvious, as this is my first time working with Packer, and like many users this also happens to be my first time working with JSON. Solution: I was missing a comma after the 'builders' configuration. I'm sure this is a common problem, so let's specifically mention that in the document. --- website/source/intro/getting-started/provision.html.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/source/intro/getting-started/provision.html.markdown b/website/source/intro/getting-started/provision.html.markdown index 4276ae089..437605b08 100644 --- a/website/source/intro/getting-started/provision.html.markdown +++ b/website/source/intro/getting-started/provision.html.markdown @@ -60,7 +60,8 @@ of time to initialize. The sleep makes sure that the OS properly initializes. Hopefully it is obvious, but the `builders` section shouldn't actually contain "...", it should be the contents setup in the previous page -of the getting started guide. +of the getting started guide. Also note the comma after the `"builders": [...]` +array, which was not necessary in the previous lesson. To configure the provisioners, we add a new section `provisioners` to the template, alongside the `builders` configuration. The provisioners section From 633275dac42cb4203a95c78c814287028fc2cd88 Mon Sep 17 00:00:00 2001 From: stefanlasiewski Date: Fri, 18 Jul 2014 10:26:32 -0700 Subject: [PATCH 212/593] Use the phrase 'not present', which is more clear then 'not necessary'. --- website/source/intro/getting-started/provision.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/intro/getting-started/provision.html.markdown b/website/source/intro/getting-started/provision.html.markdown index 437605b08..6ae1dcca1 100644 --- a/website/source/intro/getting-started/provision.html.markdown +++ b/website/source/intro/getting-started/provision.html.markdown @@ -61,7 +61,7 @@ of time to initialize. The sleep makes sure that the OS properly initializes. Hopefully it is obvious, but the `builders` section shouldn't actually contain "...", it should be the contents setup in the previous page of the getting started guide. Also note the comma after the `"builders": [...]` -array, which was not necessary in the previous lesson. +section, which was not present in the previous lesson. To configure the provisioners, we add a new section `provisioners` to the template, alongside the `builders` configuration. The provisioners section From 01130da729da2bb8c6b04f4047d3719b8847996b Mon Sep 17 00:00:00 2001 From: Misha Brukman Date: Fri, 18 Jul 2014 15:08:08 -0400 Subject: [PATCH 213/593] builder/googlecompute: added support for all standard VM images Also fixed the error message to be clear that the image was not found in any of the projects that we attempted to search, rather than keep the error message from the last project, which may be confusing to users. --- builder/googlecompute/driver_gce.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/builder/googlecompute/driver_gce.go b/builder/googlecompute/driver_gce.go index 83552aa09..3924d38d1 100644 --- a/builder/googlecompute/driver_gce.go +++ b/builder/googlecompute/driver_gce.go @@ -229,7 +229,7 @@ func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error { } func (d *driverGCE) getImage(name string) (image *compute.Image, err error) { - projects := []string{d.projectId, "debian-cloud", "centos-cloud"} + projects := []string{d.projectId, "centos-cloud", "debian-cloud", "google-containers", "rhel-cloud", "suse-cloud"} for _, project := range projects { image, err = d.service.Images.Get(project, name).Do() if err == nil && image != nil && image.SelfLink != "" { @@ -238,10 +238,7 @@ func (d *driverGCE) getImage(name string) (image *compute.Image, err error) { image = nil } - if err == nil { - err = fmt.Errorf("Image could not be found: %s", name) - } - + err = fmt.Errorf("Image %s could not be found in any of these projects: %s", name, projects) return } From 02e7161b299fd555ce2f49604e1f211c57e0705d Mon Sep 17 00:00:00 2001 From: Sudharshan S Date: Sat, 19 Jul 2014 22:02:49 +0530 Subject: [PATCH 214/593] Install gox during build time Just so that we can just run `make` without installing gox separatelyy --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index a39e8ee0e..0303d8843 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ all: deps deps: @$(ECHO) "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)" @go get -d -v ./... + @go get github.com/mitchellh/gox @echo $(DEPS) | xargs -n1 go get -d updatedeps: From 0a5cbb9fb5890374590316accc12db90ab293b37 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 19 Jul 2014 09:37:05 -0700 Subject: [PATCH 215/593] Update vagrant.html.markdown --- website/source/docs/post-processors/vagrant.html.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/website/source/docs/post-processors/vagrant.html.markdown b/website/source/docs/post-processors/vagrant.html.markdown index 2261d0479..6950b2d90 100644 --- a/website/source/docs/post-processors/vagrant.html.markdown +++ b/website/source/docs/post-processors/vagrant.html.markdown @@ -28,6 +28,7 @@ providers. * AWS * DigitalOcean +* Hyper-V * Parallels * VirtualBox * VMware From ff1fb83d63efa92ebac9119f76289e07dc584309 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 19 Jul 2014 09:42:52 -0700 Subject: [PATCH 216/593] Update CHANGELOG.md From eff9bbdac83c25aa6419e224c1f40ad21ac33cc5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 19 Jul 2014 09:42:58 -0700 Subject: [PATCH 217/593] Update CHANGELOG.md From 4ecd2f558e8ab5702ac36ba90c7d6fa7b0873591 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 19 Jul 2014 09:43:05 -0700 Subject: [PATCH 218/593] Update CHANGELOG.md From be143cf53dc46c86711aeb79d8fc7f79b0cce40f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 19 Jul 2014 09:43:33 -0700 Subject: [PATCH 219/593] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9335a44f..c522dfce6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ BUG FIXES: * builder/virtualbox-ovf: Supports guest additions options. [GH-1120] * builder/vmware-iso: Fix esx5 path separator in windows. [GH-1316] * builder/vmware: Remote ESXi builder now uploads floppy. [GH-1106] + * builder/vmware: Remote ESXi builder no longer re-uploads ISO every + time. [GH-1244] * post-processor/vsphere: Accept DOMAIN\account usernames [GH-1178] * provisioner/chef-*: Fix remotePaths for Windows [GH-394] From 2c489110b13190e20102be32c134184be8c5d6b2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 19 Jul 2014 09:43:50 -0700 Subject: [PATCH 220/593] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c522dfce6..bd1b50bef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ FEATURES: * **New post processor:** `vagrant-cloud` - Push box files generated by vagrant post processor to Vagrant Cloud. [GH-1289] + * Vagrant post-processor can now packer Hyper-V boxes. IMPROVEMENTS: From bc208a5b505bbe81cd47c5f5860f88ffef495fb8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 19 Jul 2014 09:48:55 -0700 Subject: [PATCH 221/593] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd1b50bef..934a603aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ FEATURES: IMPROVEMENTS: + * builder/amazon: Support for enhanced networking on HVM images. [GH-1228] * builder/amazon-ebs: Support encrypted EBS volumes [GH-1194] * builder/ansible: Add `playbook_dir` option. [GH-1000] * builder/openstack: Add ability to configure networks. [GH-1261] From 3f7c09a21a1eb34abcd66c866304a222a7ed482c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 19 Jul 2014 09:50:39 -0700 Subject: [PATCH 222/593] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 934a603aa..3134825be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,8 +25,10 @@ IMPROVEMENTS: BUG FIXES: - * builder/amazon-common: Sort AMI strings before outputting [GH-1305] * core: `isotime` is the same time during the entire build. [GH-1153] + * builder/amazon-common: Sort AMI strings before outputting [GH-1305] + * builder/amazon: User data can use templates/variables. [GH-1343] + * builder/null: SSH info can use templates/variables. [GH-1343] * builder/openstack: Workaround for gophercloud.ServerById crashing [GH-1257] * builder/openstack: Force IPv4 addresses from address pools [GH-1258] * builder/parallels: Do not delete entire CDROM device. [GH-1115] From e27523eb009dbc949e340d7b176ce4ffa03a6a4f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 19 Jul 2014 15:28:01 -0700 Subject: [PATCH 223/593] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3134825be..a05da419f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ BUG FIXES: * core: `isotime` is the same time during the entire build. [GH-1153] * builder/amazon-common: Sort AMI strings before outputting [GH-1305] * builder/amazon: User data can use templates/variables. [GH-1343] + * builder/amazon: Can now build AMIs in GovCloud. * builder/null: SSH info can use templates/variables. [GH-1343] * builder/openstack: Workaround for gophercloud.ServerById crashing [GH-1257] * builder/openstack: Force IPv4 addresses from address pools [GH-1258] From 8b24d990943ff496a3bd951cb63f5fa03cff1c2f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 20 Jul 2014 11:22:58 -0700 Subject: [PATCH 224/593] v0.6.1 --- CHANGELOG.md | 4 ++-- packer/version.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a05da419f..06cd94083 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ -## 0.6.1 (unreleased) +## 0.6.1 (July 20, 2014) FEATURES: * **New post processor:** `vagrant-cloud` - Push box files generated by vagrant post processor to Vagrant Cloud. [GH-1289] - * Vagrant post-processor can now packer Hyper-V boxes. + * Vagrant post-processor can now packer Hyper-V boxes. IMPROVEMENTS: diff --git a/packer/version.go b/packer/version.go index 94582624c..dda91be3d 100644 --- a/packer/version.go +++ b/packer/version.go @@ -15,7 +15,7 @@ const Version = "0.6.1" // Any pre-release marker for the version. If this is "" (empty string), // then it means that it is a final release. Otherwise, this is the // pre-release marker. -const VersionPrerelease = "dev" +const VersionPrerelease = "" type versionCommand byte From 1eac1cc0ddb7d7a611cc8f61fe7b391f636a801f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 20 Jul 2014 13:17:58 -0700 Subject: [PATCH 225/593] up version for dev --- CHANGELOG.md | 4 ++++ packer/version.go | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06cd94083..48cded4db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.2 (unreleased) + + + ## 0.6.1 (July 20, 2014) FEATURES: diff --git a/packer/version.go b/packer/version.go index dda91be3d..39cb8b092 100644 --- a/packer/version.go +++ b/packer/version.go @@ -10,12 +10,12 @@ import ( var GitCommit string // The version of packer. -const Version = "0.6.1" +const Version = "0.6.2" // Any pre-release marker for the version. If this is "" (empty string), // then it means that it is a final release. Otherwise, this is the // pre-release marker. -const VersionPrerelease = "" +const VersionPrerelease = "dev" type versionCommand byte From 4ea700d798b1e28ef27344027ef5ac10552b135a Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Sun, 20 Jul 2014 16:32:01 -0400 Subject: [PATCH 226/593] website: vagrant-cloud pp is now released --- .../docs/post-processors/vagrant-cloud.html.markdown | 8 -------- 1 file changed, 8 deletions(-) diff --git a/website/source/docs/post-processors/vagrant-cloud.html.markdown b/website/source/docs/post-processors/vagrant-cloud.html.markdown index b43009232..273b0cf32 100644 --- a/website/source/docs/post-processors/vagrant-cloud.html.markdown +++ b/website/source/docs/post-processors/vagrant-cloud.html.markdown @@ -5,14 +5,6 @@ page_title: "Vagrant Cloud Post-Processor" # Vagrant Cloud Post-Processor -
    -

    -This is an unreleased feature. To use it now, -please build Packer from source. A release is scheduled for the week -of 07/14/2014. -

    -
    - Type: `vagrant-cloud` The Vagrant Cloud post-processor recieves a Vagrant box from the `vagrant` From e62e122625d97856ddf92dd8e2d2b56f7c737bda Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Mon, 21 Jul 2014 10:30:51 -0400 Subject: [PATCH 227/593] post-processor/vagrant-cloud: fix multi-part corruption --- post-processor/vagrant-cloud/client.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/post-processor/vagrant-cloud/client.go b/post-processor/vagrant-cloud/client.go index 783745c67..104f13832 100644 --- a/post-processor/vagrant-cloud/client.go +++ b/post-processor/vagrant-cloud/client.go @@ -6,11 +6,9 @@ import ( "fmt" "io" "log" - "mime/multipart" "net/http" "net/url" "os" - "path/filepath" "strings" ) @@ -113,13 +111,8 @@ func (v VagrantCloudClient) Upload(path string, url string) (*http.Response, err defer file.Close() body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("file", filepath.Base(path)) - if err != nil { - return nil, err - } - _, err = io.Copy(part, file) + _, err = io.Copy(body, file) if err != nil { return nil, fmt.Errorf("Error uploading file: %s", err) From 1baa63f0609ba1d05072b869c96f58620f2bb876 Mon Sep 17 00:00:00 2001 From: Andy Thompson Date: Sun, 20 Jul 2014 19:57:10 +0100 Subject: [PATCH 228/593] Add a docker commit step to the docker builder that runs instead of export if export_path not present --- builder/docker/builder.go | 12 +++- builder/docker/config.go | 6 +- builder/docker/config_test.go | 12 +++- builder/docker/driver.go | 3 + builder/docker/driver_docker.go | 17 ++++++ builder/docker/driver_mock.go | 11 ++++ builder/docker/step_commit.go | 40 +++++++++++++ builder/docker/step_commit_test.go | 95 ++++++++++++++++++++++++++++++ builder/docker/step_export.go | 5 ++ builder/docker/step_export_test.go | 22 +++++++ 10 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 builder/docker/step_commit.go create mode 100644 builder/docker/step_commit_test.go diff --git a/builder/docker/builder.go b/builder/docker/builder.go index f8c3832e9..a4659219d 100644 --- a/builder/docker/builder.go +++ b/builder/docker/builder.go @@ -35,6 +35,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &StepPull{}, &StepRun{}, &StepProvision{}, + &StepCommit{}, &StepExport{}, } @@ -64,8 +65,17 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, rawErr.(error) } + var artifact packer.Artifact // No errors, must've worked - artifact := &ExportArtifact{path: b.config.ExportPath} + if b.config.Export { + artifact = &ExportArtifact{path: b.config.ExportPath} + } else { + artifact = &ImportArtifact{ + IdValue: state.Get("image_id").(string), + BuilderIdValue: "packer.post-processor.docker-import", + Driver: driver, + } + } return artifact, nil } diff --git a/builder/docker/config.go b/builder/docker/config.go index 045bf19b8..1045ff26a 100644 --- a/builder/docker/config.go +++ b/builder/docker/config.go @@ -10,6 +10,7 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` ExportPath string `mapstructure:"export_path"` + Export bool Image string Pull bool RunCommand []string `mapstructure:"run_command"` @@ -71,10 +72,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } } - if c.ExportPath == "" { - errs = packer.MultiErrorAppend(errs, - fmt.Errorf("export_path must be specified")) - } + c.Export = c.ExportPath != "" if c.Image == "" { errs = packer.MultiErrorAppend(errs, diff --git a/builder/docker/config_test.go b/builder/docker/config_test.go index 3b279ab22..ea65d8114 100644 --- a/builder/docker/config_test.go +++ b/builder/docker/config_test.go @@ -46,13 +46,19 @@ func TestConfigPrepare_exportPath(t *testing.T) { // No export path delete(raw, "export_path") - _, warns, errs := NewConfig(raw) - testConfigErr(t, warns, errs) + c, warns, errs := NewConfig(raw) + testConfigOk(t, warns, errs) + if c.Export { + t.Fatal("should not export") + } // Good export path raw["export_path"] = "good" - _, warns, errs = NewConfig(raw) + c, warns, errs = NewConfig(raw) testConfigOk(t, warns, errs) + if !c.Export { + t.Fatal("should export") + } } func TestConfigPrepare_image(t *testing.T) { diff --git a/builder/docker/driver.go b/builder/docker/driver.go index aaed2fc52..f0cf55821 100644 --- a/builder/docker/driver.go +++ b/builder/docker/driver.go @@ -8,6 +8,9 @@ import ( // Docker. The Driver interface also allows the steps to be tested since // a mock driver can be shimmed in. type Driver interface { + // Commit the container to a tag + Commit(id string) (string, error) + // Delete an image that is imported into Docker DeleteImage(id string) error diff --git a/builder/docker/driver_docker.go b/builder/docker/driver_docker.go index 5c43e0078..c9b1557e9 100644 --- a/builder/docker/driver_docker.go +++ b/builder/docker/driver_docker.go @@ -35,6 +35,23 @@ func (d *DockerDriver) DeleteImage(id string) error { return nil } +func (d *DockerDriver) Commit(id string) (string, error) { + var stdout bytes.Buffer + cmd := exec.Command("docker", "commit", id) + cmd.Stdout = &stdout + + if err := cmd.Start(); err != nil { + return "", err + } + + if err := cmd.Wait(); err != nil { + err = fmt.Errorf("Error committing container: %s", err) + return "", err + } + + return strings.TrimSpace(stdout.String()), nil +} + func (d *DockerDriver) Export(id string, dst io.Writer) error { var stderr bytes.Buffer cmd := exec.Command("docker", "export", id) diff --git a/builder/docker/driver_mock.go b/builder/docker/driver_mock.go index a48bb99f8..e45c34e88 100644 --- a/builder/docker/driver_mock.go +++ b/builder/docker/driver_mock.go @@ -6,6 +6,11 @@ import ( // MockDriver is a driver implementation that can be used for tests. type MockDriver struct { + CommitCalled bool + CommitContainerId string + CommitImageId string + CommitErr error + DeleteImageCalled bool DeleteImageId string DeleteImageErr error @@ -39,6 +44,12 @@ type MockDriver struct { VerifyCalled bool } +func (d *MockDriver) Commit(id string) (string, error) { + d.CommitCalled = true + d.CommitContainerId = id + return d.CommitImageId, d.CommitErr +} + func (d *MockDriver) DeleteImage(id string) error { d.DeleteImageCalled = true d.DeleteImageId = id diff --git a/builder/docker/step_commit.go b/builder/docker/step_commit.go new file mode 100644 index 000000000..8b6489495 --- /dev/null +++ b/builder/docker/step_commit.go @@ -0,0 +1,40 @@ +package docker + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// StepCommit commits the container to a image. +type StepCommit struct { + imageId string +} + +func (s *StepCommit) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + driver := state.Get("driver").(Driver) + containerId := state.Get("container_id").(string) + ui := state.Get("ui").(packer.Ui) + + if config.Export { + return multistep.ActionContinue + } + + ui.Say("Committing the container") + imageId, err := driver.Commit(containerId) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Save the container ID + s.imageId = imageId + state.Put("image_id", s.imageId) + ui.Message(fmt.Sprintf("Image ID: %s", s.imageId)) + + return multistep.ActionContinue +} + +func (s *StepCommit) Cleanup(state multistep.StateBag) {} diff --git a/builder/docker/step_commit_test.go b/builder/docker/step_commit_test.go new file mode 100644 index 000000000..6e1520dea --- /dev/null +++ b/builder/docker/step_commit_test.go @@ -0,0 +1,95 @@ +package docker + +import ( + "errors" + "github.com/mitchellh/multistep" + "testing" +) + +func testStepCommitState(t *testing.T) multistep.StateBag { + state := testState(t) + state.Put("container_id", "foo") + return state +} + +func TestStepCommit_impl(t *testing.T) { + var _ multistep.Step = new(StepCommit) +} + +func TestStepCommit(t *testing.T) { + state := testStepCommitState(t) + step := new(StepCommit) + defer step.Cleanup(state) + + config := state.Get("config").(*Config) + config.Export = false + driver := state.Get("driver").(*MockDriver) + driver.CommitImageId = "bar" + + // run the step + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + + // verify we did the right thing + if !driver.CommitCalled { + t.Fatal("should've called") + } + + // verify the ID is saved + idRaw, ok := state.GetOk("image_id") + if !ok { + t.Fatal("should've saved ID") + } + + id := idRaw.(string) + if id != driver.CommitImageId { + t.Fatalf("bad: %#v", id) + } +} + +func TestStepCommit_skip(t *testing.T) { + state := testStepCommitState(t) + step := new(StepCommit) + defer step.Cleanup(state) + + config := state.Get("config").(*Config) + config.Export = true + driver := state.Get("driver").(*MockDriver) + + // run the step + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + + // verify we did the right thing + if driver.CommitCalled { + t.Fatal("shouldn't have called") + } + + // verify the ID is not saved + if _, ok := state.GetOk("image_id"); ok { + t.Fatal("shouldn't save image ID") + } +} + +func TestStepCommit_error(t *testing.T) { + state := testStepCommitState(t) + step := new(StepCommit) + defer step.Cleanup(state) + + config := state.Get("config").(*Config) + config.Export = false + driver := state.Get("driver").(*MockDriver) + driver.CommitErr = errors.New("foo") + + // run the step + if action := step.Run(state); action != multistep.ActionHalt { + t.Fatalf("bad action: %#v", action) + } + + // verify the ID is not saved + if _, ok := state.GetOk("image_id"); ok { + t.Fatal("shouldn't save image ID") + } +} diff --git a/builder/docker/step_export.go b/builder/docker/step_export.go index 69d6f483c..4530236f8 100644 --- a/builder/docker/step_export.go +++ b/builder/docker/step_export.go @@ -12,6 +12,11 @@ type StepExport struct{} func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) + + if !config.Export { + return multistep.ActionContinue + } + driver := state.Get("driver").(Driver) containerId := state.Get("container_id").(string) ui := state.Get("ui").(packer.Ui) diff --git a/builder/docker/step_export_test.go b/builder/docker/step_export_test.go index d07d547c2..d0232965b 100644 --- a/builder/docker/step_export_test.go +++ b/builder/docker/step_export_test.go @@ -34,6 +34,7 @@ func TestStepExport(t *testing.T) { config := state.Get("config").(*Config) config.ExportPath = tf.Name() + config.Export = true driver := state.Get("driver").(*MockDriver) driver.ExportReader = bytes.NewReader([]byte("data!")) @@ -61,6 +62,26 @@ func TestStepExport(t *testing.T) { } } +func TestStepExport_skip(t *testing.T) { + state := testStepExportState(t) + step := new(StepExport) + defer step.Cleanup(state) + + config := state.Get("config").(*Config) + config.Export = false + driver := state.Get("driver").(*MockDriver) + + // run the step + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + + // verify we did the right thing + if driver.ExportCalled { + t.Fatal("shouldn't have exported") + } +} + func TestStepExport_error(t *testing.T) { state := testStepExportState(t) step := new(StepExport) @@ -79,6 +100,7 @@ func TestStepExport_error(t *testing.T) { config := state.Get("config").(*Config) config.ExportPath = tf.Name() + config.Export = true driver := state.Get("driver").(*MockDriver) driver.ExportError = errors.New("foo") From bf16683140b9972e3a1e523376126da38da7e7ee Mon Sep 17 00:00:00 2001 From: Andy Thompson Date: Sun, 20 Jul 2014 19:58:03 +0100 Subject: [PATCH 229/593] Add a docker tag post processor --- builder/docker/driver.go | 3 + builder/docker/driver_docker.go | 15 +++ builder/docker/driver_mock.go | 12 ++ config.go | 1 + plugin/post-processor-docker-tag/main.go | 15 +++ plugin/post-processor-docker-tag/main_test.go | 1 + post-processor/docker-tag/post-processor.go | 103 ++++++++++++++++++ .../docker-tag/post-processor_test.go | 72 ++++++++++++ 8 files changed, 222 insertions(+) create mode 100644 plugin/post-processor-docker-tag/main.go create mode 100644 plugin/post-processor-docker-tag/main_test.go create mode 100644 post-processor/docker-tag/post-processor.go create mode 100644 post-processor/docker-tag/post-processor_test.go diff --git a/builder/docker/driver.go b/builder/docker/driver.go index f0cf55821..d54eac5a1 100644 --- a/builder/docker/driver.go +++ b/builder/docker/driver.go @@ -33,6 +33,9 @@ type Driver interface { // StopContainer forcibly stops a container. StopContainer(id string) error + // TagImage tags the image with the given ID + TagImage(id string, repo string) error + // Verify verifies that the driver can run Verify() error } diff --git a/builder/docker/driver_docker.go b/builder/docker/driver_docker.go index c9b1557e9..d334eef2a 100644 --- a/builder/docker/driver_docker.go +++ b/builder/docker/driver_docker.go @@ -169,6 +169,21 @@ func (d *DockerDriver) StopContainer(id string) error { return exec.Command("docker", "kill", id).Run() } +func (d *DockerDriver) TagImage(id string, repo string) error { + cmd := exec.Command("docker", "tag", id, repo) + + if err := cmd.Start(); err != nil { + return err + } + + if err := cmd.Wait(); err != nil { + err = fmt.Errorf("Error tagging image: %s", err) + return err + } + + return nil +} + func (d *DockerDriver) Verify() error { if _, err := exec.LookPath("docker"); err != nil { return err diff --git a/builder/docker/driver_mock.go b/builder/docker/driver_mock.go index e45c34e88..8afa4fd85 100644 --- a/builder/docker/driver_mock.go +++ b/builder/docker/driver_mock.go @@ -25,6 +25,11 @@ type MockDriver struct { PushName string PushErr error + TagImageCalled bool + TagImageImageId string + TagImageRepo string + TagImageErr error + ExportReader io.Reader ExportError error PullError error @@ -101,6 +106,13 @@ func (d *MockDriver) StopContainer(id string) error { return d.StopError } +func (d *MockDriver) TagImage(id string, repo string) error { + d.TagImageCalled = true + d.TagImageImageId = id + d.TagImageRepo = repo + return d.TagImageErr +} + func (d *MockDriver) Verify() error { d.VerifyCalled = true return d.VerifyError diff --git a/config.go b/config.go index 44d362d26..4a6b52547 100644 --- a/config.go +++ b/config.go @@ -48,6 +48,7 @@ const defaultConfig = ` "vsphere": "packer-post-processor-vsphere", "docker-push": "packer-post-processor-docker-push", "docker-import": "packer-post-processor-docker-import", + "docker-tag": "packer-post-processor-docker-tag", "vagrant-cloud": "packer-post-processor-vagrant-cloud" }, diff --git a/plugin/post-processor-docker-tag/main.go b/plugin/post-processor-docker-tag/main.go new file mode 100644 index 000000000..e226ff697 --- /dev/null +++ b/plugin/post-processor-docker-tag/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/packer/plugin" + "github.com/mitchellh/packer/post-processor/docker-tag" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterPostProcessor(new(dockertag.PostProcessor)) + server.Serve() +} diff --git a/plugin/post-processor-docker-tag/main_test.go b/plugin/post-processor-docker-tag/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/plugin/post-processor-docker-tag/main_test.go @@ -0,0 +1 @@ +package main diff --git a/post-processor/docker-tag/post-processor.go b/post-processor/docker-tag/post-processor.go new file mode 100644 index 000000000..d68b48e4c --- /dev/null +++ b/post-processor/docker-tag/post-processor.go @@ -0,0 +1,103 @@ +package dockertag + +import ( + "fmt" + "github.com/mitchellh/packer/builder/docker" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/post-processor/docker-import" +) + +const BuilderId = "packer.post-processor.docker-tag" + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + Repository string `mapstructure:"repository"` + Tag string `mapstructure:"tag"` + + tpl *packer.ConfigTemplate +} + +type PostProcessor struct { + Driver docker.Driver + + config Config +} + +func (p *PostProcessor) Configure(raws ...interface{}) error { + _, err := common.DecodeConfig(&p.config, raws...) + if err != nil { + return err + } + + p.config.tpl, err = packer.NewConfigTemplate() + if err != nil { + return err + } + p.config.tpl.UserVars = p.config.PackerUserVars + + // Accumulate any errors + errs := new(packer.MultiError) + + templates := map[string]*string{ + "repository": &p.config.Repository, + "tag": &p.config.Tag, + } + + for key, ptr := range templates { + if *ptr == "" { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("%s must be set", key)) + } + + *ptr, err = p.config.tpl.Process(*ptr, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing %s: %s", key, err)) + } + } + + if len(errs.Errors) > 0 { + return errs + } + + return nil + +} + +func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { + if artifact.BuilderId() != dockerimport.BuilderId { + err := fmt.Errorf( + "Unknown artifact type: %s\nCan only tag from Docker builder artifacts.", + artifact.BuilderId()) + return nil, false, err + } + + driver := p.Driver + if driver == nil { + // If no driver is set, then we use the real driver + driver = &docker.DockerDriver{Tpl: p.config.tpl, Ui: ui} + } + + importRepo := p.config.Repository + if p.config.Tag != "" { + importRepo += ":" + p.config.Tag + } + + ui.Message("Tagging image: " + artifact.Id()) + ui.Message("Repository: " + importRepo) + err := driver.TagImage(artifact.Id(), importRepo) + if err != nil { + return nil, false, err + } + + // Build the artifact + artifact = &docker.ImportArtifact{ + BuilderIdValue: BuilderId, + Driver: driver, + IdValue: importRepo, + } + + return artifact, true, nil +} diff --git a/post-processor/docker-tag/post-processor_test.go b/post-processor/docker-tag/post-processor_test.go new file mode 100644 index 000000000..925419a10 --- /dev/null +++ b/post-processor/docker-tag/post-processor_test.go @@ -0,0 +1,72 @@ +package dockertag + +import ( + "bytes" + "github.com/mitchellh/packer/builder/docker" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/post-processor/docker-import" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + "repository": "foo", + "tag": "bar", + } +} + +func testPP(t *testing.T) *PostProcessor { + var p PostProcessor + if err := p.Configure(testConfig()); err != nil { + t.Fatalf("err: %s", err) + } + + return &p +} + +func testUi() *packer.BasicUi { + return &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + } +} + +func TestPostProcessor_ImplementsPostProcessor(t *testing.T) { + var _ packer.PostProcessor = new(PostProcessor) +} + +func TestPostProcessor_PostProcess(t *testing.T) { + driver := &docker.MockDriver{} + p := &PostProcessor{Driver: driver} + _, err := common.DecodeConfig(&p.config, testConfig()) + if err != nil { + t.Fatalf("err %s", err) + } + + artifact := &packer.MockArtifact{ + BuilderIdValue: dockerimport.BuilderId, + IdValue: "1234567890abcdef", + } + + result, keep, err := p.PostProcess(testUi(), artifact) + if _, ok := result.(packer.Artifact); !ok { + t.Fatal("should be instance of Artifact") + } + if !keep { + t.Fatal("should keep") + } + if err != nil { + t.Fatalf("err: %s", err) + } + + if !driver.TagImageCalled { + t.Fatal("should call TagImage") + } + if driver.TagImageImageId != "1234567890abcdef" { + t.Fatal("bad image id") + } + if driver.TagImageRepo != "foo:bar" { + t.Fatal("bad repo") + } +} From ed446782f9782536dfdb95bf00942c1784b8352a Mon Sep 17 00:00:00 2001 From: Andy Thompson Date: Sun, 20 Jul 2014 21:58:07 +0100 Subject: [PATCH 230/593] Add a docker save post processor --- builder/docker/driver.go | 3 + builder/docker/driver_docker.go | 20 ++++ builder/docker/driver_mock.go | 19 ++++ config.go | 1 + plugin/post-processor-docker-save/main.go | 15 +++ .../post-processor-docker-save/main_test.go | 1 + post-processor/docker-save/post-processor.go | 104 ++++++++++++++++++ .../docker-save/post-processor_test.go | 31 ++++++ 8 files changed, 194 insertions(+) create mode 100644 plugin/post-processor-docker-save/main.go create mode 100644 plugin/post-processor-docker-save/main_test.go create mode 100644 post-processor/docker-save/post-processor.go create mode 100644 post-processor/docker-save/post-processor_test.go diff --git a/builder/docker/driver.go b/builder/docker/driver.go index d54eac5a1..ab326a148 100644 --- a/builder/docker/driver.go +++ b/builder/docker/driver.go @@ -26,6 +26,9 @@ type Driver interface { // Push pushes an image to a Docker index/registry. Push(name string) error + // Save an image with the given ID to the given writer. + SaveImage(id string, dst io.Writer) error + // StartContainer starts a container and returns the ID for that container, // along with a potential error. StartContainer(*ContainerConfig) (string, error) diff --git a/builder/docker/driver_docker.go b/builder/docker/driver_docker.go index d334eef2a..78c91f76f 100644 --- a/builder/docker/driver_docker.go +++ b/builder/docker/driver_docker.go @@ -115,6 +115,26 @@ func (d *DockerDriver) Push(name string) error { return runAndStream(cmd, d.Ui) } +func (d *DockerDriver) SaveImage(id string, dst io.Writer) error { + var stderr bytes.Buffer + cmd := exec.Command("docker", "save", id) + cmd.Stdout = dst + cmd.Stderr = &stderr + + log.Printf("Exporting image: %s", id) + if err := cmd.Start(); err != nil { + return err + } + + if err := cmd.Wait(); err != nil { + err = fmt.Errorf("Error exporting: %s\nStderr: %s", + err, stderr.String()) + return err + } + + return nil +} + func (d *DockerDriver) StartContainer(config *ContainerConfig) (string, error) { // Build up the template data var tplData startContainerTemplate diff --git a/builder/docker/driver_mock.go b/builder/docker/driver_mock.go index 8afa4fd85..cf623f011 100644 --- a/builder/docker/driver_mock.go +++ b/builder/docker/driver_mock.go @@ -25,6 +25,11 @@ type MockDriver struct { PushName string PushErr error + SaveImageCalled bool + SaveImageId string + SaveImageReader io.Reader + SaveImageError error + TagImageCalled bool TagImageImageId string TagImageRepo string @@ -94,6 +99,20 @@ func (d *MockDriver) Push(name string) error { return d.PushErr } +func (d *MockDriver) SaveImage(id string, dst io.Writer) error { + d.SaveImageCalled = true + d.SaveImageId = id + + if d.SaveImageReader != nil { + _, err := io.Copy(dst, d.SaveImageReader) + if err != nil { + return err + } + } + + return d.SaveImageError +} + func (d *MockDriver) StartContainer(config *ContainerConfig) (string, error) { d.StartCalled = true d.StartConfig = config diff --git a/config.go b/config.go index 4a6b52547..7c1f54677 100644 --- a/config.go +++ b/config.go @@ -48,6 +48,7 @@ const defaultConfig = ` "vsphere": "packer-post-processor-vsphere", "docker-push": "packer-post-processor-docker-push", "docker-import": "packer-post-processor-docker-import", + "docker-save": "packer-post-processor-docker-save", "docker-tag": "packer-post-processor-docker-tag", "vagrant-cloud": "packer-post-processor-vagrant-cloud" }, diff --git a/plugin/post-processor-docker-save/main.go b/plugin/post-processor-docker-save/main.go new file mode 100644 index 000000000..d5f5ec636 --- /dev/null +++ b/plugin/post-processor-docker-save/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/packer/plugin" + "github.com/mitchellh/packer/post-processor/docker-save" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterPostProcessor(new(dockersave.PostProcessor)) + server.Serve() +} diff --git a/plugin/post-processor-docker-save/main_test.go b/plugin/post-processor-docker-save/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/plugin/post-processor-docker-save/main_test.go @@ -0,0 +1 @@ +package main diff --git a/post-processor/docker-save/post-processor.go b/post-processor/docker-save/post-processor.go new file mode 100644 index 000000000..6a2d86298 --- /dev/null +++ b/post-processor/docker-save/post-processor.go @@ -0,0 +1,104 @@ +package dockersave + +import ( + "fmt" + "github.com/mitchellh/packer/builder/docker" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/post-processor/docker-import" + "os" +) + +const BuilderId = "packer.post-processor.docker-save" + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + Path string `mapstructure:"path"` + + tpl *packer.ConfigTemplate +} + +type PostProcessor struct { + Driver docker.Driver + + config Config +} + +func (p *PostProcessor) Configure(raws ...interface{}) error { + _, err := common.DecodeConfig(&p.config, raws...) + if err != nil { + return err + } + + p.config.tpl, err = packer.NewConfigTemplate() + if err != nil { + return err + } + p.config.tpl.UserVars = p.config.PackerUserVars + + // Accumulate any errors + errs := new(packer.MultiError) + + templates := map[string]*string{ + "path": &p.config.Path, + } + + for key, ptr := range templates { + if *ptr == "" { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("%s must be set", key)) + } + + *ptr, err = p.config.tpl.Process(*ptr, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing %s: %s", key, err)) + } + } + + if len(errs.Errors) > 0 { + return errs + } + + return nil + +} + +func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { + if artifact.BuilderId() != dockerimport.BuilderId { + err := fmt.Errorf( + "Unknown artifact type: %s\nCan only save Docker builder artifacts.", + artifact.BuilderId()) + return nil, false, err + } + + path := p.config.Path + + // Open the file that we're going to write to + f, err := os.Create(path) + if err != nil { + err := fmt.Errorf("Error creating output file: %s", err) + return nil, false, err + } + + driver := p.Driver + if driver == nil { + // If no driver is set, then we use the real driver + driver = &docker.DockerDriver{Tpl: p.config.tpl, Ui: ui} + } + + ui.Message("Saving image: " + artifact.Id()) + + if err := driver.SaveImage(artifact.Id(), f); err != nil { + f.Close() + os.Remove(f.Name()) + + return nil, false, err + } + + f.Close() + ui.Message("Saved to: " + path) + + return artifact, true, nil +} diff --git a/post-processor/docker-save/post-processor_test.go b/post-processor/docker-save/post-processor_test.go new file mode 100644 index 000000000..2d29d58b9 --- /dev/null +++ b/post-processor/docker-save/post-processor_test.go @@ -0,0 +1,31 @@ +package dockersave + +import ( + "bytes" + "github.com/mitchellh/packer/packer" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{} +} + +func testPP(t *testing.T) *PostProcessor { + var p PostProcessor + if err := p.Configure(testConfig()); err != nil { + t.Fatalf("err: %s", err) + } + + return &p +} + +func testUi() *packer.BasicUi { + return &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + } +} + +func TestPostProcessor_ImplementsPostProcessor(t *testing.T) { + var _ packer.PostProcessor = new(PostProcessor) +} From 4a98ab377aa56c18a09ac57bf6c230854005ffbd Mon Sep 17 00:00:00 2001 From: Andy Thompson Date: Mon, 21 Jul 2014 20:39:17 +0100 Subject: [PATCH 231/593] Add Stderr capture to Commit and TagImage calls. --- builder/docker/driver_docker.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/builder/docker/driver_docker.go b/builder/docker/driver_docker.go index 78c91f76f..6a43ced5f 100644 --- a/builder/docker/driver_docker.go +++ b/builder/docker/driver_docker.go @@ -37,15 +37,19 @@ func (d *DockerDriver) DeleteImage(id string) error { func (d *DockerDriver) Commit(id string) (string, error) { var stdout bytes.Buffer + var stderr bytes.Buffer + cmd := exec.Command("docker", "commit", id) cmd.Stdout = &stdout + cmd.Stderr = &stderr if err := cmd.Start(); err != nil { return "", err } if err := cmd.Wait(); err != nil { - err = fmt.Errorf("Error committing container: %s", err) + err = fmt.Errorf("Error committing container: %s\nStderr: %s", + err, stderr.String()) return "", err } @@ -190,14 +194,17 @@ func (d *DockerDriver) StopContainer(id string) error { } func (d *DockerDriver) TagImage(id string, repo string) error { + var stderr bytes.Buffer cmd := exec.Command("docker", "tag", id, repo) + cmd.Stderr = &stderr if err := cmd.Start(); err != nil { return err } if err := cmd.Wait(); err != nil { - err = fmt.Errorf("Error tagging image: %s", err) + err = fmt.Errorf("Error tagging image: %s\nStderr: %s", + err, stderr.String()) return err } From ca84e2ac55b11c39668372cd0e74ec5eaa1cb34b Mon Sep 17 00:00:00 2001 From: James Massara Date: Mon, 21 Jul 2014 15:28:47 -0700 Subject: [PATCH 232/593] Fix bug with getting ec2 connection instead of source image --- builder/amazon/chroot/step_check_root_device.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/amazon/chroot/step_check_root_device.go b/builder/amazon/chroot/step_check_root_device.go index d4d202f25..da18599aa 100644 --- a/builder/amazon/chroot/step_check_root_device.go +++ b/builder/amazon/chroot/step_check_root_device.go @@ -12,7 +12,7 @@ import ( type StepCheckRootDevice struct{} func (s *StepCheckRootDevice) Run(state multistep.StateBag) multistep.StepAction { - image := state.Get("ec2").(*ec2.Image) + image := state.Get("source_image").(*ec2.Image) ui := state.Get("ui").(packer.Ui) ui.Say("Checking the root device on source AMI...") From 05ebc8f316e22e8b6126665d003a69cc4088f297 Mon Sep 17 00:00:00 2001 From: Shawn Neal Date: Mon, 21 Jul 2014 17:06:43 -0700 Subject: [PATCH 233/593] Do not re-add floppy disk files to VMX This commit fixes errors like this in the vsphere post-processor when using floppy files in the builder step: Error: File (/var/folders/zl/57c1vmr532z_ryf1scw53_b9ycmxh7/T/packer964492999) could not be found The configure VMX step re-adds the floppy files, so we need to configure the VMX and _then_ clean the VMX in that order. --- builder/vmware/iso/builder.go | 2 +- builder/vmware/vmx/builder.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index d1f24fa64..ff77ef9cf 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -367,10 +367,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Timeout: b.config.ShutdownTimeout, }, &vmwcommon.StepCleanFiles{}, - &vmwcommon.StepCleanVMX{}, &vmwcommon.StepConfigureVMX{ CustomData: b.config.VMXDataPost, }, + &vmwcommon.StepCleanVMX{}, &vmwcommon.StepCompactDisk{ Skip: b.config.SkipCompaction, }, diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index e75f62b57..a1e7ae963 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -94,10 +94,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Timeout: b.config.ShutdownTimeout, }, &vmwcommon.StepCleanFiles{}, - &vmwcommon.StepCleanVMX{}, &vmwcommon.StepConfigureVMX{ CustomData: b.config.VMXDataPost, }, + &vmwcommon.StepCleanVMX{}, &vmwcommon.StepCompactDisk{ Skip: b.config.SkipCompaction, }, From 116317614465bd26a3e72ac983f79007ac3aa36d Mon Sep 17 00:00:00 2001 From: Paul Morie Date: Mon, 21 Jul 2014 20:56:19 -0400 Subject: [PATCH 234/593] Fix typo in docker builder docs --- website/source/docs/builders/docker.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/builders/docker.html.markdown b/website/source/docs/builders/docker.html.markdown index 6b9ee8cd0..e395e25d2 100644 --- a/website/source/docs/builders/docker.html.markdown +++ b/website/source/docs/builders/docker.html.markdown @@ -101,7 +101,7 @@ and `docker push`, respectively. This builder allows you to build Docker images _without_ Dockerfiles. -With this builder, you can repeatably create Docker images without the use +With this builder, you can repeatably create Docker images without the use of a Dockerfile. You don't need to know the syntax or semantics of Dockerfiles. Instead, you can just provide shell scripts, Chef recipes, Puppet manifests, etc. to provision your Docker container just like you would a regular From b30f901fc3ba469a31866e7c8faef019fd72a8ca Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Mon, 21 Jul 2014 18:10:27 -0700 Subject: [PATCH 235/593] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48cded4db..0bebdd672 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## 0.6.2 (unreleased) +BUG FIXES: + * builder/vmware/vmx: Do not re-add floppy disk files to VMX [GH-1361] ## 0.6.1 (July 20, 2014) From 97eb744c8769563c0eca162e6068e499b7b0a13a Mon Sep 17 00:00:00 2001 From: Henry Huang Date: Tue, 22 Jul 2014 23:40:38 +0800 Subject: [PATCH 236/593] De-register the unavailable image in the cleanup --- builder/amazon/ebs/step_create_ami.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/builder/amazon/ebs/step_create_ami.go b/builder/amazon/ebs/step_create_ami.go index 18f1f833a..dd14c9621 100644 --- a/builder/amazon/ebs/step_create_ami.go +++ b/builder/amazon/ebs/step_create_ami.go @@ -34,15 +34,6 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - imagesResp, err := ec2conn.Images([]string{createResp.ImageId}, nil) - if err != nil { - err := fmt.Errorf("Error searching for AMI: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - s.image = &imagesResp.Images[0] - // Set the AMI ID in the state ui.Message(fmt.Sprintf("AMI: %s", createResp.ImageId)) amis := make(map[string]string) @@ -65,11 +56,20 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + imagesResp, err := ec2conn.Images([]string{createResp.ImageId}, nil) + if err != nil { + err := fmt.Errorf("Error searching for AMI: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + s.image = &imagesResp.Images[0] + return multistep.ActionContinue } func (s *stepCreateAMI) Cleanup(state multistep.StateBag) { - if s.image == nil { + if s.image == nil || s.image.State == "available" { return } From dc555d6b5bd6355183b8983ade529577b586534a Mon Sep 17 00:00:00 2001 From: Andrey Levkin Date: Thu, 24 Jul 2014 15:16:57 +0400 Subject: [PATCH 237/593] Change creating boxes for customizing metadata.json. --- post-processor/vagrant/post-processor.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go index 8fb486edb..25275bc24 100644 --- a/post-processor/vagrant/post-processor.go +++ b/post-processor/vagrant/post-processor.go @@ -101,16 +101,6 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac } defer os.RemoveAll(dir) - // Copy all of the includes files into the temporary directory - for _, src := range config.Include { - ui.Message(fmt.Sprintf("Copying from include: %s", src)) - dst := filepath.Join(dir, filepath.Base(src)) - if err := CopyContents(dst, src); err != nil { - err = fmt.Errorf("Error copying include file: %s\n\n%s", src, err) - return nil, false, err - } - } - // Run the provider processing step vagrantfile, metadata, err := provider.Process(ui, artifact, dir) if err != nil { @@ -122,6 +112,16 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac return nil, false, err } + // Copy all of the includes files into the temporary directory + for _, src := range config.Include { + ui.Message(fmt.Sprintf("Copying from include: %s", src)) + dst := filepath.Join(dir, filepath.Base(src)) + if err := CopyContents(dst, src); err != nil { + err = fmt.Errorf("Error copying include file: %s\n\n%s", src, err) + return nil, false, err + } + } + // Write our Vagrantfile var customVagrantfile string if config.VagrantfileTemplate != "" { From e0c628508892ffcac9de49869097b0c9ef9a5adc Mon Sep 17 00:00:00 2001 From: Andrey Levkin Date: Thu, 24 Jul 2014 17:39:21 +0400 Subject: [PATCH 238/593] Change creating boxes for customizing metadata.json --- post-processor/vagrant/post-processor.go | 20 ++++++++++---------- post-processor/vagrant/util.go | 17 ++++++++++------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go index 25275bc24..8fb486edb 100644 --- a/post-processor/vagrant/post-processor.go +++ b/post-processor/vagrant/post-processor.go @@ -101,6 +101,16 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac } defer os.RemoveAll(dir) + // Copy all of the includes files into the temporary directory + for _, src := range config.Include { + ui.Message(fmt.Sprintf("Copying from include: %s", src)) + dst := filepath.Join(dir, filepath.Base(src)) + if err := CopyContents(dst, src); err != nil { + err = fmt.Errorf("Error copying include file: %s\n\n%s", src, err) + return nil, false, err + } + } + // Run the provider processing step vagrantfile, metadata, err := provider.Process(ui, artifact, dir) if err != nil { @@ -112,16 +122,6 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac return nil, false, err } - // Copy all of the includes files into the temporary directory - for _, src := range config.Include { - ui.Message(fmt.Sprintf("Copying from include: %s", src)) - dst := filepath.Join(dir, filepath.Base(src)) - if err := CopyContents(dst, src); err != nil { - err = fmt.Errorf("Error copying include file: %s\n\n%s", src, err) - return nil, false, err - } - } - // Write our Vagrantfile var customVagrantfile string if config.VagrantfileTemplate != "" { diff --git a/post-processor/vagrant/util.go b/post-processor/vagrant/util.go index db695289a..6c558a1c4 100644 --- a/post-processor/vagrant/util.go +++ b/post-processor/vagrant/util.go @@ -130,12 +130,15 @@ func DirToBox(dst, dir string, ui packer.Ui, level int) error { // WriteMetadata writes the "metadata.json" file for a Vagrant box. func WriteMetadata(dir string, contents interface{}) error { - f, err := os.Create(filepath.Join(dir, "metadata.json")) - if err != nil { - return err - } - defer f.Close() + if _, err := os.Stat(filepath.Join(dir, "metadata.json")); os.IsNotExist(err) { + f, err := os.Create(filepath.Join(dir, "metadata.json")) + if err != nil { + return err + } + defer f.Close() - enc := json.NewEncoder(f) - return enc.Encode(contents) + enc := json.NewEncoder(f) + return enc.Encode(contents) + } + return nil } From d62edd6c057af4f147c5c44521a2e7646f41aa6a Mon Sep 17 00:00:00 2001 From: abishopric Date: Thu, 24 Jul 2014 16:30:30 -0700 Subject: [PATCH 239/593] Print the evaluated bundle commands --- builder/amazon/instance/builder.go | 8 ++++++-- builder/amazon/instance/step_bundle_volume.go | 10 +++++++++- builder/amazon/instance/step_upload_bundle.go | 10 +++++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index 7edd027de..50f1a0610 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -223,8 +223,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &common.StepProvision{}, &StepUploadX509Cert{}, - &StepBundleVolume{}, - &StepUploadBundle{}, + &StepBundleVolume{ + Debug: b.config.PackerDebug, + }, + &StepUploadBundle{ + Debug: b.config.PackerDebug, + }, &StepRegisterAMI{}, &awscommon.StepAMIRegionCopy{ Regions: b.config.AMIRegions, diff --git a/builder/amazon/instance/step_bundle_volume.go b/builder/amazon/instance/step_bundle_volume.go index 4dfbd4fc4..736e1adb2 100644 --- a/builder/amazon/instance/step_bundle_volume.go +++ b/builder/amazon/instance/step_bundle_volume.go @@ -2,6 +2,7 @@ package instance import ( "fmt" + "github.com/mitchellh/goamz/ec2" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" @@ -17,7 +18,9 @@ type bundleCmdData struct { PrivatePath string } -type StepBundleVolume struct{} +type StepBundleVolume struct { + Debug bool +} func (s *StepBundleVolume) Run(state multistep.StateBag) multistep.StepAction { comm := state.Get("communicator").(packer.Communicator) @@ -48,6 +51,11 @@ func (s *StepBundleVolume) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Bundling the volume...") cmd := new(packer.RemoteCmd) cmd.Command = config.BundleVolCommand + + if s.Debug { + ui.Say(fmt.Sprintf("Running: %s", config.BundleVolCommand)) + } + if err := cmd.StartWithUi(comm, ui); err != nil { state.Put("error", fmt.Errorf("Error bundling volume: %s", err)) ui.Error(state.Get("error").(error).Error()) diff --git a/builder/amazon/instance/step_upload_bundle.go b/builder/amazon/instance/step_upload_bundle.go index 0ae1a404d..f5ec7da95 100644 --- a/builder/amazon/instance/step_upload_bundle.go +++ b/builder/amazon/instance/step_upload_bundle.go @@ -2,6 +2,7 @@ package instance import ( "fmt" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" ) @@ -15,7 +16,9 @@ type uploadCmdData struct { SecretKey string } -type StepUploadBundle struct{} +type StepUploadBundle struct { + Debug bool +} func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction { comm := state.Get("communicator").(packer.Communicator) @@ -49,6 +52,11 @@ func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Uploading the bundle...") cmd := &packer.RemoteCmd{Command: config.BundleUploadCommand} + + if s.Debug { + ui.Say(fmt.Sprintf("Running: %s", config.BundleUploadCommand)) + } + if err := cmd.StartWithUi(comm, ui); err != nil { state.Put("error", fmt.Errorf("Error uploading volume: %s", err)) ui.Error(state.Get("error").(error).Error()) From 5b56970b09981f750fcb315adac1e29983c40e65 Mon Sep 17 00:00:00 2001 From: Misha Brukman Date: Thu, 24 Jul 2014 20:39:04 -0400 Subject: [PATCH 240/593] Added CoreOS, OpenSUSE, and Windows projects for searching images. --- builder/googlecompute/driver_gce.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/googlecompute/driver_gce.go b/builder/googlecompute/driver_gce.go index 3924d38d1..9c155ae81 100644 --- a/builder/googlecompute/driver_gce.go +++ b/builder/googlecompute/driver_gce.go @@ -229,7 +229,7 @@ func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error { } func (d *driverGCE) getImage(name string) (image *compute.Image, err error) { - projects := []string{d.projectId, "centos-cloud", "debian-cloud", "google-containers", "rhel-cloud", "suse-cloud"} + projects := []string{d.projectId, "centos-cloud", "coreos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "windows-cloud"} for _, project := range projects { image, err = d.service.Images.Get(project, name).Do() if err == nil && image != nil && image.SelfLink != "" { From 3bfeb5485382bee0a83fdc353f29b31147f8ed74 Mon Sep 17 00:00:00 2001 From: yveslaroche Date: Fri, 25 Jul 2014 14:32:38 +0100 Subject: [PATCH 241/593] Fix Ansible inventory path on Windows Convert the path to use a '/' separator. --- provisioner/ansible-local/provisioner.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/provisioner/ansible-local/provisioner.go b/provisioner/ansible-local/provisioner.go index e336024b8..904abc57a 100644 --- a/provisioner/ansible-local/provisioner.go +++ b/provisioner/ansible-local/provisioner.go @@ -191,7 +191,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { if len(p.config.InventoryFile) > 0 { ui.Message("Uploading inventory file...") src := p.config.InventoryFile - dst := filepath.Join(p.config.StagingDir, filepath.Base(src)) + dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(src))) if err := p.uploadFile(ui, comm, dst, src); err != nil { return fmt.Errorf("Error uploading inventory file: %s", err) } @@ -259,7 +259,7 @@ func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator) err // with a single host. inventory := "\"127.0.0.1,\"" if len(p.config.InventoryFile) > 0 { - inventory = filepath.Join(p.config.StagingDir, filepath.Base(p.config.InventoryFile)) + inventory = filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(p.config.InventoryFile))) } extraArgs := "" From 4a85aefe0fdb517c5a22259fd7f0a5cea37c0e0f Mon Sep 17 00:00:00 2001 From: Misha Brukman Date: Fri, 25 Jul 2014 17:21:12 -0400 Subject: [PATCH 242/593] Rename "Gsutil" to "Gcloud" now that we're updating "gcloud" and not "gsutil". Also renamed files accordingly. --- builder/googlecompute/builder.go | 2 +- ...ep_update_gsutil.go => step_update_gcloud.go} | 8 ++++---- ...gsutil_test.go => step_update_gcloud_test.go} | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) rename builder/googlecompute/{step_update_gsutil.go => step_update_gcloud.go} (85%) rename builder/googlecompute/{step_update_gsutil_test.go => step_update_gcloud_test.go} (83%) diff --git a/builder/googlecompute/builder.go b/builder/googlecompute/builder.go index 6cd72ba11..84c60d9a3 100644 --- a/builder/googlecompute/builder.go +++ b/builder/googlecompute/builder.go @@ -65,7 +65,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SSHWaitTimeout: 5 * time.Minute, }, new(common.StepProvision), - new(StepUpdateGsutil), + new(StepUpdateGcloud), new(StepCreateImage), new(StepUploadImage), new(StepRegisterImage), diff --git a/builder/googlecompute/step_update_gsutil.go b/builder/googlecompute/step_update_gcloud.go similarity index 85% rename from builder/googlecompute/step_update_gsutil.go rename to builder/googlecompute/step_update_gcloud.go index 0955be53e..3bfc6f2a7 100644 --- a/builder/googlecompute/step_update_gsutil.go +++ b/builder/googlecompute/step_update_gcloud.go @@ -7,9 +7,9 @@ import ( "github.com/mitchellh/packer/packer" ) -// StepUpdateGsutil represents a Packer build step that updates the gsutil +// StepUpdateGcloud represents a Packer build step that updates the gsutil // utility to the latest version available. -type StepUpdateGsutil int +type StepUpdateGcloud int // Run executes the Packer build step that updates the gsutil utility to the // latest version available. @@ -17,7 +17,7 @@ type StepUpdateGsutil int // This step is required to prevent the image creation process from hanging; // the image creation process utilizes the gcimagebundle cli tool which will // prompt to update gsutil if a newer version is available. -func (s *StepUpdateGsutil) Run(state multistep.StateBag) multistep.StepAction { +func (s *StepUpdateGcloud) Run(state multistep.StateBag) multistep.StepAction { comm := state.Get("communicator").(packer.Communicator) config := state.Get("config").(*Config) ui := state.Get("ui").(packer.Ui) @@ -49,4 +49,4 @@ func (s *StepUpdateGsutil) Run(state multistep.StateBag) multistep.StepAction { } // Cleanup. -func (s *StepUpdateGsutil) Cleanup(state multistep.StateBag) {} +func (s *StepUpdateGcloud) Cleanup(state multistep.StateBag) {} diff --git a/builder/googlecompute/step_update_gsutil_test.go b/builder/googlecompute/step_update_gcloud_test.go similarity index 83% rename from builder/googlecompute/step_update_gsutil_test.go rename to builder/googlecompute/step_update_gcloud_test.go index 7dd0eb3ac..b01406f10 100644 --- a/builder/googlecompute/step_update_gsutil_test.go +++ b/builder/googlecompute/step_update_gcloud_test.go @@ -8,13 +8,13 @@ import ( "github.com/mitchellh/packer/packer" ) -func TestStepUpdateGsutil_impl(t *testing.T) { - var _ multistep.Step = new(StepUpdateGsutil) +func TestStepUpdateGcloud_impl(t *testing.T) { + var _ multistep.Step = new(StepUpdateGcloud) } -func TestStepUpdateGsutil(t *testing.T) { +func TestStepUpdateGcloud(t *testing.T) { state := testState(t) - step := new(StepUpdateGsutil) + step := new(StepUpdateGcloud) defer step.Cleanup(state) comm := new(packer.MockCommunicator) @@ -37,9 +37,9 @@ func TestStepUpdateGsutil(t *testing.T) { } } -func TestStepUpdateGsutil_badExitStatus(t *testing.T) { +func TestStepUpdateGcloud_badExitStatus(t *testing.T) { state := testState(t) - step := new(StepUpdateGsutil) + step := new(StepUpdateGcloud) defer step.Cleanup(state) comm := new(packer.MockCommunicator) @@ -56,9 +56,9 @@ func TestStepUpdateGsutil_badExitStatus(t *testing.T) { } } -func TestStepUpdateGsutil_nonRoot(t *testing.T) { +func TestStepUpdateGcloud_nonRoot(t *testing.T) { state := testState(t) - step := new(StepUpdateGsutil) + step := new(StepUpdateGcloud) defer step.Cleanup(state) comm := new(packer.MockCommunicator) From 11816ba60419808ee7fe0a719df2b5ae1d435629 Mon Sep 17 00:00:00 2001 From: Jacob Gillespie Date: Mon, 28 Jul 2014 11:06:26 -0500 Subject: [PATCH 243/593] Update GCE source_image to debian-7-wheezy-v20140718 --- website/source/docs/builders/googlecompute.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/builders/googlecompute.markdown b/website/source/docs/builders/googlecompute.markdown index 6d9c3d0d8..c5410d78b 100644 --- a/website/source/docs/builders/googlecompute.markdown +++ b/website/source/docs/builders/googlecompute.markdown @@ -59,7 +59,7 @@ files obtained in the previous section. "client_secrets_file": "client_secret.json", "private_key_file": "XXXXXX-privatekey.p12", "project_id": "my-project", - "source_image": "debian-7-wheezy-v20140415", + "source_image": "debian-7-wheezy-v20140718", "zone": "us-central1-a" } From 0d07960f3e051bd8a439558095fb6f4aba81500e Mon Sep 17 00:00:00 2001 From: Matthew Baker Date: Tue, 29 Jul 2014 07:40:29 -0700 Subject: [PATCH 244/593] Disabling SDL gui mode --- builder/qemu/step_run.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/builder/qemu/step_run.go b/builder/qemu/step_run.go index 858c8424c..b4687ff7c 100644 --- a/builder/qemu/step_run.go +++ b/builder/qemu/step_run.go @@ -61,7 +61,6 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error sshHostPort := state.Get("sshHostPort").(uint) ui := state.Get("ui").(packer.Ui) - guiArgument := "sdl" vnc := fmt.Sprintf("0.0.0.0:%d", vncPort-5900) vmName := config.VMName imgPath := filepath.Join(config.OutputDir, @@ -71,13 +70,12 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error ui.Message("WARNING: The VM will be started in headless mode, as configured.\n" + "In headless mode, errors during the boot sequence or OS setup\n" + "won't be easily visible. Use at your own discretion.") - guiArgument = "none" } defaultArgs := make(map[string]string) defaultArgs["-name"] = vmName - defaultArgs["-machine"] = fmt.Sprintf("type=pc-1.0,accel=%s", config.Accelerator) - defaultArgs["-display"] = guiArgument + // defaultArgs["-machine"] = fmt.Sprintf("type=pc,accel=%s", config.Accelerator) + // defaultArgs["-machine"] = "type=pc" defaultArgs["-netdev"] = "user,id=user.0" defaultArgs["-device"] = fmt.Sprintf("%s,netdev=user.0", config.NetDevice) defaultArgs["-drive"] = fmt.Sprintf("file=%s,if=%s", imgPath, config.DiskInterface) From c87c88fce21f5404e2f25c0193a4d566627e8448 Mon Sep 17 00:00:00 2001 From: Matthew Baker Date: Tue, 29 Jul 2014 07:41:07 -0700 Subject: [PATCH 245/593] OEL available net device --- builder/qemu/builder.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builder/qemu/builder.go b/builder/qemu/builder.go index 7f404a566..248f4a5a2 100644 --- a/builder/qemu/builder.go +++ b/builder/qemu/builder.go @@ -26,6 +26,7 @@ var netDevice = map[string]bool{ "pcnet": true, "virtio": true, "virtio-net": true, + "virtio-net-pci": true, "usb-net": true, "i82559a": true, "i82559b": true, From a59ee93bcac73f58eabe77aebbe3b21e80da967c Mon Sep 17 00:00:00 2001 From: Peter Sankauskas Date: Tue, 29 Jul 2014 16:07:49 -0700 Subject: [PATCH 246/593] To be able to build both PV and HVM images, it is not possible to use both /dev/sd[f-p] and [1-16] as the HVM images get attached at /dev/sdf but must be mounted a /dev/sdf1. This reduces the number of simultaneous packer runs possible significantly, but unless you are Netflix, who have Aminator anyway, this is probably never going to be an issue --- builder/amazon/chroot/device.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/builder/amazon/chroot/device.go b/builder/amazon/chroot/device.go index 77b7b444a..a0c0b9dc8 100644 --- a/builder/amazon/chroot/device.go +++ b/builder/amazon/chroot/device.go @@ -27,11 +27,12 @@ func AvailableDevice() (string, error) { continue } - for i := 1; i < 16; i++ { - device := fmt.Sprintf("/dev/%s%c%d", prefix, letter, i) - if _, err := os.Stat(device); err != nil { - return device, nil - } + // To be able to build both Paravirtual and HVM images, the unnumbered + // device and the first numbered one must be available. + // E.g. /dev/xvdf and /dev/xvdf1 + numbered_device := fmt.Sprintf("%s%d", device, 1) + if _, err := os.Stat(numbered_device); err != nil { + return device, nil } } From 6b751cac392d5856189ade81d544e1f11b4fa1a0 Mon Sep 17 00:00:00 2001 From: Peter Sankauskas Date: Tue, 29 Jul 2014 16:15:53 -0700 Subject: [PATCH 247/593] Formatting --- builder/amazon/chroot/device.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/amazon/chroot/device.go b/builder/amazon/chroot/device.go index a0c0b9dc8..02a68433c 100644 --- a/builder/amazon/chroot/device.go +++ b/builder/amazon/chroot/device.go @@ -28,7 +28,7 @@ func AvailableDevice() (string, error) { } // To be able to build both Paravirtual and HVM images, the unnumbered - // device and the first numbered one must be available. + // device and the first numbered one must be available. // E.g. /dev/xvdf and /dev/xvdf1 numbered_device := fmt.Sprintf("%s%d", device, 1) if _, err := os.Stat(numbered_device); err != nil { From 460e2da2485d1392b6a28103e37f98331d85e625 Mon Sep 17 00:00:00 2001 From: Peter Sankauskas Date: Tue, 29 Jul 2014 21:55:20 -0700 Subject: [PATCH 248/593] The mount command for a PV image that is attached to /dev/sdf is: mount /dev/xvdf /mnt/point while for an HVM image that is attached to /dev/sdf, its mount command is mount /dev/xvdf1 /mnt/point so this code enabled that --- builder/amazon/chroot/step_mount_device.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/builder/amazon/chroot/step_mount_device.go b/builder/amazon/chroot/step_mount_device.go index a6419774f..3c3d959c1 100644 --- a/builder/amazon/chroot/step_mount_device.go +++ b/builder/amazon/chroot/step_mount_device.go @@ -3,6 +3,7 @@ package chroot import ( "bytes" "fmt" + "github.com/mitchellh/goamz/ec2" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" @@ -26,6 +27,7 @@ type StepMountDevice struct { func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) ui := state.Get("ui").(packer.Ui) + image := state.Get("source_image").(*ec2.Image) device := state.Get("device").(string) wrappedCommand := state.Get("wrappedCommand").(CommandWrapper) @@ -57,10 +59,17 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + log.Printf("Source image virtualization type is: %s", image.VirtualizationType) + deviceMount := device + if image.VirtualizationType == "hvm" { + deviceMount = fmt.Sprintf("%s%d", device, 1) + } + state.Put("deviceMount", deviceMount) + ui.Say("Mounting the root device...") stderr := new(bytes.Buffer) mountCommand, err := wrappedCommand( - fmt.Sprintf("mount %s %s", device, mountPath)) + fmt.Sprintf("mount %s %s", deviceMount, mountPath)) if err != nil { err := fmt.Errorf("Error creating mount command: %s", err) state.Put("error", err) From a2c0b104f0504e26a4be149784320f7859585c99 Mon Sep 17 00:00:00 2001 From: Peter Sankauskas Date: Tue, 29 Jul 2014 21:56:37 -0700 Subject: [PATCH 249/593] Adding the conditional necessary to construct the right options for both PV and HVM images. Also adding a test to make sure it is doing the right thing --- builder/amazon/chroot/step_register_ami.go | 26 ++++-- .../amazon/chroot/step_register_ami_test.go | 83 +++++++++++++++++++ 2 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 builder/amazon/chroot/step_register_ami_test.go diff --git a/builder/amazon/chroot/step_register_ami.go b/builder/amazon/chroot/step_register_ami.go index 69ec6dbb6..62e6a3ff0 100644 --- a/builder/amazon/chroot/step_register_ami.go +++ b/builder/amazon/chroot/step_register_ami.go @@ -30,14 +30,7 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction { blockDevices[i] = newDevice } - registerOpts := &ec2.RegisterImage{ - Name: config.AMIName, - Architecture: image.Architecture, - KernelId: image.KernelId, - RamdiskId: image.RamdiskId, - RootDeviceName: image.RootDeviceName, - BlockDevices: blockDevices, - } + registerOpts := buildRegisterOpts(config, image, blockDevices) // Set SriovNetSupport to "simple". See http://goo.gl/icuXh5 if config.AMIEnhancedNetworking { @@ -77,3 +70,20 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction { } func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {} + +func buildRegisterOpts(config *Config, image *ec2.Image, blockDevices []ec2.BlockDeviceMapping) *ec2.RegisterImage { + registerOpts := &ec2.RegisterImage{ + Name: config.AMIName, + Architecture: image.Architecture, + RootDeviceName: image.RootDeviceName, + BlockDevices: blockDevices, + VirtType: config.AMIVirtType, + } + + if config.AMIVirtType != "hvm" { + registerOpts.KernelId = image.KernelId + registerOpts.RamdiskId = image.RamdiskId + } + + return registerOpts +} diff --git a/builder/amazon/chroot/step_register_ami_test.go b/builder/amazon/chroot/step_register_ami_test.go new file mode 100644 index 000000000..7d75687bc --- /dev/null +++ b/builder/amazon/chroot/step_register_ami_test.go @@ -0,0 +1,83 @@ +package chroot + +import ( + "fmt" + "testing" + + "github.com/kr/pretty" + "github.com/mitchellh/goamz/ec2" + // awscommon "github.com/mitchellh/packer/builder/amazon/common" +) + +func testImage() ec2.Image { + return ec2.Image{ + Id: "ami-abcd1234", + Name: "ami_test_name", + Architecture: "x86_64", + KernelId: "aki-abcd1234", + } +} + +func TestStepRegisterAmi_buildRegisterOpts_pv(t *testing.T) { + config := Config{} + config.AMIName = "test_ami_name" + config.AMIDescription = "test_ami_description" + config.AMIVirtType = "paravirtual" + + image := testImage() + + blockDevices := []ec2.BlockDeviceMapping{} + + opts := buildRegisterOpts(&config, &image, blockDevices) + + fmt.Println("******** PAS ********") + fmt.Printf("%# v", pretty.Formatter(opts)) + + expected := config.AMIVirtType + if opts.VirtType != expected { + t.Fatalf("Unexpected VirtType value: expected %s got %s\n", expected, opts.VirtType) + } + + expected = config.AMIName + if opts.Name != expected { + t.Fatalf("Unexpected Name value: expected %s got %s\n", expected, opts.Name) + } + + expected = image.KernelId + if opts.KernelId != expected { + t.Fatalf("Unexpected KernelId value: expected %s got %s\n", expected, opts.KernelId) + } + +} + +func TestStepRegisterAmi_buildRegisterOpts_hvm(t *testing.T) { + config := Config{} + config.AMIName = "test_ami_name" + config.AMIDescription = "test_ami_description" + config.AMIVirtType = "hvm" + + image := testImage() + + blockDevices := []ec2.BlockDeviceMapping{} + + opts := buildRegisterOpts(&config, &image, blockDevices) + + fmt.Println("******** PAS ********") + fmt.Printf("%# v", pretty.Formatter(opts)) + + expected := config.AMIVirtType + if opts.VirtType != expected { + t.Fatalf("Unexpected VirtType value: expected %s got %s\n", expected, opts.VirtType) + } + + expected = config.AMIName + if opts.Name != expected { + t.Fatalf("Unexpected Name value: expected %s got %s\n", expected, opts.Name) + } + + expected = "" + if opts.KernelId != expected { + t.Fatalf("Unexpected KernelId value: expected %s got %s\n", expected, opts.KernelId) + } + +} From e878495f6792ffed61fe8410ccffda1ab06af386 Mon Sep 17 00:00:00 2001 From: Peter Sankauskas Date: Tue, 29 Jul 2014 22:10:49 -0700 Subject: [PATCH 250/593] Removing my helper script --- scripts/linuxcompile.sh | 60 ----------------------------------------- 1 file changed, 60 deletions(-) delete mode 100755 scripts/linuxcompile.sh diff --git a/scripts/linuxcompile.sh b/scripts/linuxcompile.sh deleted file mode 100755 index a7f693d38..000000000 --- a/scripts/linuxcompile.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash -# -# To be able to cross compile: -# cd /usr/local/go/src -# sudo GOOS=linux GOARCH=amd64 ./make.bash --no-clean -# -# This script only builds the application from source. -set -e - -NO_COLOR="\x1b[0m" -OK_COLOR="\x1b[32;01m" -ERROR_COLOR="\x1b[31;01m" -WARN_COLOR="\x1b[33;01m" - -# http://stackoverflow.com/questions/4023830/bash-how-compare-two-strings-in-version-format -verify_go () { - if [[ $1 == $2 ]]; then - return 0 - fi - - local IFS=. - local i ver1=($1) ver2=($2) - - for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do - ver1[i]=0 - done - - for ((i=0; i<${#ver1[@]}; i++)); do - if [[ -z ${ver2[i]} ]]; then - ver2[i]=0 - fi - if ((10#${ver1[i]} > 10#${ver2[i]})); then - echo -e "${ERROR_COLOR}==> Required Go version $1 not installed. Found $2 instead" - exit 1 - fi - done -} - -GO_MINIMUM_VERSION=1.2 -GO_INSTALLED_VERSION=$(go version | cut -d ' ' -f 3) -GO_INSTALLED_VERSION=${GO_INSTALLED_VERSION#"go"} - -echo -e "${OK_COLOR}==> Verifying Go" -verify_go $GO_MINIMUM_VERSION $GO_INSTALLED_VERSION - -# Get the parent directory of where this script is. -SOURCE="${BASH_SOURCE[0]}" -while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done -DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" - -# Change into that directory -cd $DIR - -# Compile the thing -export XC_ARCH=amd64 -export XC_OS=linux -./scripts/compile.sh - -echo "-- DONE --" -echo "Binaries are in ./pkg/linux_amd64/" From ab9f0bc3c8b46f943048a672377fa40688164a6e Mon Sep 17 00:00:00 2001 From: Peter Sankauskas Date: Tue, 29 Jul 2014 22:18:43 -0700 Subject: [PATCH 251/593] Removing my debugging output --- builder/amazon/chroot/step_register_ami_test.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/builder/amazon/chroot/step_register_ami_test.go b/builder/amazon/chroot/step_register_ami_test.go index 7d75687bc..f85106a8d 100644 --- a/builder/amazon/chroot/step_register_ami_test.go +++ b/builder/amazon/chroot/step_register_ami_test.go @@ -1,12 +1,8 @@ package chroot import ( - "fmt" "testing" - - "github.com/kr/pretty" "github.com/mitchellh/goamz/ec2" - // awscommon "github.com/mitchellh/packer/builder/amazon/common" ) func testImage() ec2.Image { @@ -30,9 +26,6 @@ func TestStepRegisterAmi_buildRegisterOpts_pv(t *testing.T) { opts := buildRegisterOpts(&config, &image, blockDevices) - fmt.Println("******** PAS ********") - fmt.Printf("%# v", pretty.Formatter(opts)) - expected := config.AMIVirtType if opts.VirtType != expected { t.Fatalf("Unexpected VirtType value: expected %s got %s\n", expected, opts.VirtType) @@ -62,9 +55,6 @@ func TestStepRegisterAmi_buildRegisterOpts_hvm(t *testing.T) { opts := buildRegisterOpts(&config, &image, blockDevices) - fmt.Println("******** PAS ********") - fmt.Printf("%# v", pretty.Formatter(opts)) - expected := config.AMIVirtType if opts.VirtType != expected { t.Fatalf("Unexpected VirtType value: expected %s got %s\n", expected, opts.VirtType) From 7816fc0bf92f66f1797c9952c71630ad5c877b73 Mon Sep 17 00:00:00 2001 From: Tristan Helmich Date: Fri, 1 Aug 2014 19:31:10 +0200 Subject: [PATCH 252/593] vagrantcloud post-processor selfhosted box feature --- .../vagrant-cloud/post-processor.go | 45 +++++++++++++++---- .../vagrant-cloud/step_create_provider.go | 6 +++ .../vagrant-cloud/step_release_version.go | 5 +++ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go index 8a7f53ebc..f6a5b2dfe 100644 --- a/post-processor/vagrant-cloud/post-processor.go +++ b/post-processor/vagrant-cloud/post-processor.go @@ -1,6 +1,7 @@ // vagrant_cloud implements the packer.PostProcessor interface and adds a // post-processor that uploads artifacts from the vagrant post-processor -// to Vagrant Cloud (vagrantcloud.com) +// to Vagrant Cloud (vagrantcloud.com) or manages self hosted boxes on the +// Vagrant Cloud package vagrantcloud import ( @@ -25,9 +26,16 @@ type Config struct { AccessToken string `mapstructure:"access_token"` VagrantCloudUrl string `mapstructure:"vagrant_cloud_url"` + BoxDownloadUrl string `mapstructure:"box_download_url"` + tpl *packer.ConfigTemplate } +type boxDownloadUrlTemplate struct { + ArtifactId string + Provider string +} + type PostProcessor struct { config Config client *VagrantCloudClient @@ -103,6 +111,14 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac // The name of the provider for vagrant cloud, and vagrant providerName := providerFromBuilderName(artifact.Id()) + boxDownloadUrl, err := p.config.tpl.Process(p.config.BoxDownloadUrl, &boxDownloadUrlTemplate { + ArtifactId: artifact.Id(), + Provider: providerName, + }) + if err != nil { + return nil, false, fmt.Errorf("Error processing box_download_url: %s", err) + } + // Set up the state state := new(multistep.BasicStateBag) state.Put("config", p.config) @@ -111,16 +127,27 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac state.Put("artifactFilePath", artifact.Files()[0]) state.Put("ui", ui) state.Put("providerName", providerName) + state.Put("boxDownloadUrl", boxDownloadUrl) // Build the steps - steps := []multistep.Step{ - new(stepVerifyBox), - new(stepCreateVersion), - new(stepCreateProvider), - new(stepPrepareUpload), - new(stepUpload), - new(stepVerifyUpload), - new(stepReleaseVersion), + steps := []multistep.Step{} + if p.config.BoxDownloadUrl == "" { + steps = []multistep.Step{ + new(stepVerifyBox), + new(stepCreateVersion), + new(stepCreateProvider), + new(stepPrepareUpload), + new(stepUpload), + new(stepVerifyUpload), + new(stepReleaseVersion), + } + } else { + steps = []multistep.Step{ + new(stepVerifyBox), + new(stepCreateVersion), + new(stepCreateProvider), + new(stepReleaseVersion), + } } // Run the steps diff --git a/post-processor/vagrant-cloud/step_create_provider.go b/post-processor/vagrant-cloud/step_create_provider.go index 887932c4f..c2d6772bd 100644 --- a/post-processor/vagrant-cloud/step_create_provider.go +++ b/post-processor/vagrant-cloud/step_create_provider.go @@ -8,6 +8,7 @@ import ( type Provider struct { Name string `json:"name"` + Url string `json:"url,omitempty"` HostedToken string `json:"hosted_token,omitempty"` UploadUrl string `json:"upload_url,omitempty"` } @@ -22,11 +23,16 @@ func (s *stepCreateProvider) Run(state multistep.StateBag) multistep.StepAction box := state.Get("box").(*Box) version := state.Get("version").(*Version) providerName := state.Get("providerName").(string) + downloadUrl := state.Get("boxDownloadUrl").(string) path := fmt.Sprintf("box/%s/version/%v/providers", box.Tag, version.Number) provider := &Provider{Name: providerName} + if downloadUrl != "" { + provider.Url = downloadUrl + } + // Wrap the provider in a provider object for the API wrapper := make(map[string]interface{}) wrapper["provider"] = provider diff --git a/post-processor/vagrant-cloud/step_release_version.go b/post-processor/vagrant-cloud/step_release_version.go index 43512bdca..d8809f9ca 100644 --- a/post-processor/vagrant-cloud/step_release_version.go +++ b/post-processor/vagrant-cloud/step_release_version.go @@ -2,6 +2,7 @@ package vagrantcloud import ( "fmt" + "strings" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" ) @@ -30,6 +31,10 @@ func (s *stepReleaseVersion) Run(state multistep.StateBag) multistep.StepAction if err != nil || (resp.StatusCode != 200) { cloudErrors := &VagrantCloudErrors{} err = decodeBody(resp, cloudErrors) + if strings.Contains(cloudErrors.FormatErrors(), "already been released") { + ui.Message("Not releasing version, already released") + return multistep.ActionContinue + } state.Put("error", fmt.Errorf("Error releasing version: %s", cloudErrors.FormatErrors())) return multistep.ActionHalt } From 849458f29dc4700cc5b943e8e83689cbb0296eab Mon Sep 17 00:00:00 2001 From: Tristan Helmich Date: Sat, 2 Aug 2014 15:14:29 +0200 Subject: [PATCH 253/593] Update documentation: New box_download_url setting --- .../source/docs/post-processors/vagrant-cloud.html.markdown | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/source/docs/post-processors/vagrant-cloud.html.markdown b/website/source/docs/post-processors/vagrant-cloud.html.markdown index 273b0cf32..cc1bccaf9 100644 --- a/website/source/docs/post-processors/vagrant-cloud.html.markdown +++ b/website/source/docs/post-processors/vagrant-cloud.html.markdown @@ -70,6 +70,9 @@ to `https://vagrantcloud.com/api/v1` * `version_description` (string) - Optionally markdown text used as a full-length and in-depth description of the version, typically for denoting changes introduced +* `box_download_url` (string) - Optional URL for a self-hosted box. If this is set +the box will not be uploaded to the Vagrant Cloud. + ## Use with Vagrant Post-Processor You'll need to use the Vagrant post-processor before using this post-processor. From 65f71daf734035ab80a1a1fe4517d9bf5e428d89 Mon Sep 17 00:00:00 2001 From: Matthias Schmitz Date: Mon, 21 Jul 2014 23:01:08 +0200 Subject: [PATCH 254/593] Allow 'tcg' as accelerator in builder-qemu * Using 'tcg' as accelerator for qemu-system allows packer to run in a virtual machine as no kvm or xen support is needed. * Also document the default behavior if no accelerator is given in the documentation. --- builder/qemu/builder.go | 4 ++-- website/source/docs/builders/qemu.html.markdown | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/qemu/builder.go b/builder/qemu/builder.go index 7f404a566..f320c9480 100644 --- a/builder/qemu/builder.go +++ b/builder/qemu/builder.go @@ -249,9 +249,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs, errors.New("invalid format, only 'qcow2' or 'raw' are allowed")) } - if !(b.config.Accelerator == "kvm" || b.config.Accelerator == "xen") { + if !(b.config.Accelerator == "kvm" || b.config.Accelerator == "xen" || b.config.Accelerator == "tcg") { errs = packer.MultiErrorAppend( - errs, errors.New("invalid format, only 'kvm' or 'xen' are allowed")) + errs, errors.New("invalid format, only 'kvm', 'xen' or 'tcg' are allowed")) } if _, ok := netDevice[b.config.NetDevice]; !ok { diff --git a/website/source/docs/builders/qemu.html.markdown b/website/source/docs/builders/qemu.html.markdown index 2430c737f..1d36d88ce 100644 --- a/website/source/docs/builders/qemu.html.markdown +++ b/website/source/docs/builders/qemu.html.markdown @@ -91,8 +91,8 @@ each category, the available options are alphabetized and described. ### Optional: * `accelerator` (string) - The accelerator type to use when running the VM. - This may have a value of either "kvm" or "xen" and you must have that - support in on the machine on which you run the builder. + This may have a value of either "kvm", "xen" or "tcg" and you must have that + support on the machine on which you run the builder. By default "kvm" is used. * `boot_command` (array of strings) - This is an array of commands to type when the virtual machine is first booted. The goal of these commands should From 9220d644d147acdba4157d267d5ad62ae0ae2935 Mon Sep 17 00:00:00 2001 From: Matthew Baker Date: Wed, 6 Aug 2014 02:43:38 -0700 Subject: [PATCH 255/593] Making graphics and hardware virtualisation optional and configurable --- builder/qemu/step_run.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/builder/qemu/step_run.go b/builder/qemu/step_run.go index b4687ff7c..7624ab68f 100644 --- a/builder/qemu/step_run.go +++ b/builder/qemu/step_run.go @@ -66,16 +66,23 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error imgPath := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", vmName, strings.ToLower(config.Format))) + defaultArgs := make(map[string]string) + if config.Headless == true { ui.Message("WARNING: The VM will be started in headless mode, as configured.\n" + "In headless mode, errors during the boot sequence or OS setup\n" + "won't be easily visible. Use at your own discretion.") + } else { + defaultArgs["-display"] = "sdl" } - defaultArgs := make(map[string]string) defaultArgs["-name"] = vmName - // defaultArgs["-machine"] = fmt.Sprintf("type=pc,accel=%s", config.Accelerator) - // defaultArgs["-machine"] = "type=pc" + if config.Accelerator != "none" { + defaultArgs["-machine"] = fmt.Sprintf("type=pc,accel=%s", config.Accelerator) + } else { + ui.Message("WARNING: The VM will be started with no hardware acceleration.\n" + + "The installation will take considerably longer to finish\n") + } defaultArgs["-netdev"] = "user,id=user.0" defaultArgs["-device"] = fmt.Sprintf("%s,netdev=user.0", config.NetDevice) defaultArgs["-drive"] = fmt.Sprintf("file=%s,if=%s", imgPath, config.DiskInterface) From 41cdb33d65f46a230a6ebf6b12f37231e23bd295 Mon Sep 17 00:00:00 2001 From: Carlos Valiente Date: Wed, 6 Aug 2014 22:59:38 +0100 Subject: [PATCH 256/593] Update vagrant-cloud.html.markdown --- website/source/docs/post-processors/vagrant-cloud.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/post-processors/vagrant-cloud.html.markdown b/website/source/docs/post-processors/vagrant-cloud.html.markdown index 273b0cf32..c30c56726 100644 --- a/website/source/docs/post-processors/vagrant-cloud.html.markdown +++ b/website/source/docs/post-processors/vagrant-cloud.html.markdown @@ -7,7 +7,7 @@ page_title: "Vagrant Cloud Post-Processor" Type: `vagrant-cloud` -The Vagrant Cloud post-processor recieves a Vagrant box from the `vagrant` +The Vagrant Cloud post-processor receives a Vagrant box from the `vagrant` post-processor and pushes it to Vagrant Cloud. [Vagrant Cloud](https://vagrantcloud.com) hosts and serves boxes to Vagrant, allowing you to version and distribute boxes to an organization in a simple way. From 99d15abfd2ba2afe49201f51c2039cdeedea16ce Mon Sep 17 00:00:00 2001 From: Matthew Baker Date: Thu, 7 Aug 2014 02:51:24 -0700 Subject: [PATCH 257/593] adding allowed values for accelerator --- builder/qemu/builder.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/qemu/builder.go b/builder/qemu/builder.go index 248f4a5a2..a3ea940c1 100644 --- a/builder/qemu/builder.go +++ b/builder/qemu/builder.go @@ -250,9 +250,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs, errors.New("invalid format, only 'qcow2' or 'raw' are allowed")) } - if !(b.config.Accelerator == "kvm" || b.config.Accelerator == "xen") { + if !(b.config.Accelerator == "kvm" || b.config.Accelerator == "xen" || b.config.Accelerator == "none") { errs = packer.MultiErrorAppend( - errs, errors.New("invalid format, only 'kvm' or 'xen' are allowed")) + errs, errors.New("invalid format, only 'kvm' or 'xen' or 'none' are allowed")) } if _, ok := netDevice[b.config.NetDevice]; !ok { From b8c13d3e4d72eb0b72d103f079a95fba21571666 Mon Sep 17 00:00:00 2001 From: Matthew Baker Date: Thu, 7 Aug 2014 04:08:50 -0700 Subject: [PATCH 258/593] Keeping the -machine type=pc if accelerator method set to "none" --- builder/qemu/step_run.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/builder/qemu/step_run.go b/builder/qemu/step_run.go index 7624ab68f..67be8ae6d 100644 --- a/builder/qemu/step_run.go +++ b/builder/qemu/step_run.go @@ -77,12 +77,14 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error } defaultArgs["-name"] = vmName + accelMethod := "" if config.Accelerator != "none" { - defaultArgs["-machine"] = fmt.Sprintf("type=pc,accel=%s", config.Accelerator) + accelMethod = fmt.Sprintf(",accel=%s", config.Accelerator) } else { ui.Message("WARNING: The VM will be started with no hardware acceleration.\n" + - "The installation will take considerably longer to finish\n") + "The installation may take considerably longer to finish\n") } + defaultArgs["-machine"] = fmt.Sprintf("type=pc%s", accelMethod) defaultArgs["-netdev"] = "user,id=user.0" defaultArgs["-device"] = fmt.Sprintf("%s,netdev=user.0", config.NetDevice) defaultArgs["-drive"] = fmt.Sprintf("file=%s,if=%s", imgPath, config.DiskInterface) From 100200e1ae45a534c569b021103a5f103d1b06af Mon Sep 17 00:00:00 2001 From: Ian Unruh Date: Thu, 7 Aug 2014 11:11:08 -0400 Subject: [PATCH 259/593] Add templating to VMware SSH host option --- builder/vmware/common/ssh_config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builder/vmware/common/ssh_config.go b/builder/vmware/common/ssh_config.go index 8bb616c17..f92b28931 100644 --- a/builder/vmware/common/ssh_config.go +++ b/builder/vmware/common/ssh_config.go @@ -32,6 +32,7 @@ func (c *SSHConfig) Prepare(t *packer.ConfigTemplate) []error { } templates := map[string]*string{ + "ssh_host": &c.SSHHost, "ssh_key_path": &c.SSHKeyPath, "ssh_password": &c.SSHPassword, "ssh_username": &c.SSHUser, From fe37d9b6e19123491d16adb9ff31fe83b78c947e Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Fri, 8 Aug 2014 08:12:48 +0200 Subject: [PATCH 260/593] parallels-iso: ISO not removed from VM after install [GH-1338] Also remove floppy and prl-tools ISO if applicable. --- .../parallels/common/step_attach_floppy.go | 18 +++++++++++++-- .../common/step_attach_parallels_tools.go | 23 +++++++++++++++++-- builder/parallels/iso/step_attach_iso.go | 21 ++++++++++++++++- 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/builder/parallels/common/step_attach_floppy.go b/builder/parallels/common/step_attach_floppy.go index efc4088fb..8085cf96c 100644 --- a/builder/parallels/common/step_attach_floppy.go +++ b/builder/parallels/common/step_attach_floppy.go @@ -44,7 +44,7 @@ func (s *StepAttachFloppy) Run(state multistep.StateBag) multistep.StepAction { } ui.Say("Attaching floppy disk...") - // Create the floppy disk controller + // Attaching the floppy disk add_command := []string{ "set", vmName, "--device-add", "fdd", @@ -62,4 +62,18 @@ func (s *StepAttachFloppy) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } -func (s *StepAttachFloppy) Cleanup(state multistep.StateBag) {} +func (s *StepAttachFloppy) Cleanup(state multistep.StateBag) { + driver := state.Get("driver").(Driver) + vmName := state.Get("vmName").(string) + + if s.floppyPath == "" { + return + } + + log.Println("Detaching floppy disk...") + command := []string{ + "set", vmName, + "--device-del", "fdd0", + } + driver.Prlctl(command...) +} diff --git a/builder/parallels/common/step_attach_parallels_tools.go b/builder/parallels/common/step_attach_parallels_tools.go index 250f2e50c..0d626c844 100644 --- a/builder/parallels/common/step_attach_parallels_tools.go +++ b/builder/parallels/common/step_attach_parallels_tools.go @@ -34,7 +34,7 @@ func (s *StepAttachParallelsTools) Run(state multistep.StateBag) multistep.StepA } // Attach the guest additions to the computer - log.Println("Attaching Parallels Tools ISO onto IDE controller...") + ui.Say("Attaching Parallels Tools ISO onto IDE controller...") command := []string{ "set", vmName, "--device-add", "cdrom", @@ -53,4 +53,23 @@ func (s *StepAttachParallelsTools) Run(state multistep.StateBag) multistep.StepA return multistep.ActionContinue } -func (s *StepAttachParallelsTools) Cleanup(state multistep.StateBag) {} +func (s *StepAttachParallelsTools) Cleanup(state multistep.StateBag) { + if _, ok := state.GetOk("attachedToolsIso"); !ok { + return + } + + driver := state.Get("driver").(Driver) + vmName := state.Get("vmName").(string) + + log.Println("Detaching Parallels Tools ISO...") + cdDevice := "cdrom0" + if _, ok := state.GetOk("attachedIso"); ok { + cdDevice = "cdrom1" + } + + command := []string{ + "set", vmName, + "--device-del", cdDevice, + } + driver.Prlctl(command...) +} diff --git a/builder/parallels/iso/step_attach_iso.go b/builder/parallels/iso/step_attach_iso.go index c4937217c..7f9f699ad 100644 --- a/builder/parallels/iso/step_attach_iso.go +++ b/builder/parallels/iso/step_attach_iso.go @@ -5,6 +5,7 @@ import ( "github.com/mitchellh/multistep" parallelscommon "github.com/mitchellh/packer/builder/parallels/common" "github.com/mitchellh/packer/packer" + "log" ) // This step attaches the ISO to the virtual machine. @@ -23,6 +24,7 @@ func (s *stepAttachISO) Run(state multistep.StateBag) multistep.StepAction { vmName := state.Get("vmName").(string) // Attach the disk to the controller + ui.Say("Attaching ISO onto IDE controller...") command := []string{ "set", vmName, "--device-set", "cdrom0", @@ -42,4 +44,21 @@ func (s *stepAttachISO) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } -func (s *stepAttachISO) Cleanup(state multistep.StateBag) {} +func (s *stepAttachISO) Cleanup(state multistep.StateBag) { + if _, ok := state.GetOk("attachedIso"); !ok { + return + } + + driver := state.Get("driver").(parallelscommon.Driver) + vmName := state.Get("vmName").(string) + + command := []string{ + "set", vmName, + "--device-set", "cdrom0", + "--enable", "--disconnect", + } + + // Remove the ISO, ignore errors + log.Println("Detaching ISO...") + driver.Prlctl(command...) +} From 8bc696ce9eea8b3cd7afbf38fa230e6a8eae571f Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Thu, 7 Aug 2014 15:34:08 -0400 Subject: [PATCH 261/593] add disk size to google compute, so as to avoid disk size errors on exporting the image --- builder/googlecompute/config.go | 5 +++++ builder/googlecompute/driver.go | 1 + builder/googlecompute/driver_gce.go | 1 + builder/googlecompute/step_create_image.go | 4 ++-- builder/googlecompute/step_create_instance.go | 1 + website/source/docs/builders/googlecompute.markdown | 3 +++ 6 files changed, 13 insertions(+), 2 deletions(-) diff --git a/builder/googlecompute/config.go b/builder/googlecompute/config.go index a47e628f5..803963eb3 100644 --- a/builder/googlecompute/config.go +++ b/builder/googlecompute/config.go @@ -18,6 +18,7 @@ type Config struct { BucketName string `mapstructure:"bucket_name"` ClientSecretsFile string `mapstructure:"client_secrets_file"` + DiskSizeGb int64 `mapstructure:"disk_size"` ImageName string `mapstructure:"image_name"` ImageDescription string `mapstructure:"image_description"` InstanceName string `mapstructure:"instance_name"` @@ -64,6 +65,10 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { c.Network = "default" } + if c.DiskSizeGb == 0 { + c.DiskSizeGb = 10 + } + if c.ImageDescription == "" { c.ImageDescription = "Created by Packer" } diff --git a/builder/googlecompute/driver.go b/builder/googlecompute/driver.go index 743440e24..f4b6006a9 100644 --- a/builder/googlecompute/driver.go +++ b/builder/googlecompute/driver.go @@ -25,6 +25,7 @@ type Driver interface { type InstanceConfig struct { Description string + DiskSizeGb int64 Image string MachineType string Metadata map[string]string diff --git a/builder/googlecompute/driver_gce.go b/builder/googlecompute/driver_gce.go index 9c155ae81..65d7c7bcd 100644 --- a/builder/googlecompute/driver_gce.go +++ b/builder/googlecompute/driver_gce.go @@ -177,6 +177,7 @@ func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) { AutoDelete: true, InitializeParams: &compute.AttachedDiskInitializeParams{ SourceImage: image.SelfLink, + DiskSizeGb: c.DiskSizeGb, }, }, }, diff --git a/builder/googlecompute/step_create_image.go b/builder/googlecompute/step_create_image.go index 249b59186..ef8026a99 100644 --- a/builder/googlecompute/step_create_image.go +++ b/builder/googlecompute/step_create_image.go @@ -31,8 +31,8 @@ func (s *StepCreateImage) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Creating image...") cmd := new(packer.RemoteCmd) - cmd.Command = fmt.Sprintf("%s%s --output_file_name %s", - sudoPrefix, imageBundleCmd, imageFilename) + cmd.Command = fmt.Sprintf("%s%s --output_file_name %s --fssize %d", + sudoPrefix, imageBundleCmd, imageFilename, config.DiskSizeGb*1024*1024*1024) err := cmd.StartWithUi(comm, ui) if err == nil && cmd.ExitStatus != 0 { err = fmt.Errorf( diff --git a/builder/googlecompute/step_create_instance.go b/builder/googlecompute/step_create_instance.go index ff94d5d38..3eab82021 100644 --- a/builder/googlecompute/step_create_instance.go +++ b/builder/googlecompute/step_create_instance.go @@ -28,6 +28,7 @@ func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction errCh, err := driver.RunInstance(&InstanceConfig{ Description: "New instance created by Packer", + DiskSizeGb: config.DiskSizeGb, Image: config.SourceImage, MachineType: config.MachineType, Metadata: map[string]string{ diff --git a/website/source/docs/builders/googlecompute.markdown b/website/source/docs/builders/googlecompute.markdown index c5410d78b..6d3c6216d 100644 --- a/website/source/docs/builders/googlecompute.markdown +++ b/website/source/docs/builders/googlecompute.markdown @@ -91,6 +91,9 @@ each category, the available options are alphabetized and described. ### Optional: +* `disk_size` (integer) - The size of the disk in GB. + This defaults to 10, which is 10GB. + * `image_name` (string) - The unique name of the resulting image. Defaults to `packer-{{timestamp}}`. From eacae832ad7ec5e9f9bc8b5a3146365f49f20f8b Mon Sep 17 00:00:00 2001 From: notogawa Date: Mon, 11 Aug 2014 16:18:12 +0900 Subject: [PATCH 262/593] Fixes #1334, Add power on retry to ESXi Driver. --- builder/vmware/iso/driver_esx5.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index c422e20c9..600990ff1 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -56,7 +56,21 @@ func (d *ESX5Driver) IsRunning(string) (bool, error) { } func (d *ESX5Driver) Start(vmxPathLocal string, headless bool) error { - return d.sh("vim-cmd", "vmsvc/power.on", d.vmId) + for i := 0; i < 20; i++ { + err := d.sh("vim-cmd", "vmsvc/power.on", d.vmId) + if err != nil { + return err + } + time.Sleep((time.Duration(i) * time.Second) + 1) + running, err := d.IsRunning(vmxPathLocal) + if err != nil { + return err + } + if running { + return nil + } + } + return errors.New("Retry limit exceeded") } func (d *ESX5Driver) Stop(vmxPathLocal string) error { From 965d2739077a5c337a5c5aa633eb35bcf95a034e Mon Sep 17 00:00:00 2001 From: Hiroyuki Sato Date: Tue, 12 Aug 2014 16:02:03 +0900 Subject: [PATCH 263/593] Add machine_type option for qemu builder. On CentOS7, ``pc-1.0'' qemu-kvm machine type are not supported. Supported machines are: none empty machine pc RHEL 7.0.0 PC (i440FX + PIIX, 1996) (alias of pc-i440fx-rhel7.0.0) pc-i440fx-rhel7.0.0 RHEL 7.0.0 PC (i440FX + PIIX, 1996) (default) rhel6.5.0 RHEL 6.5.0 PC rhel6.4.0 RHEL 6.4.0 PC rhel6.3.0 RHEL 6.3.0 PC rhel6.2.0 RHEL 6.2.0 PC rhel6.1.0 RHEL 6.1.0 PC rhel6.0.0 RHEL 6.0.0 PC q35 RHEL-7.0.0 PC (Q35 + ICH9, 2009) (alias of pc-q35-rhel7.0.0) pc-q35-rhel7.0.0 RHEL-7.0.0 PC (Q35 + ICH9, 2009) --- builder/qemu/builder.go | 6 ++++++ builder/qemu/step_run.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/builder/qemu/builder.go b/builder/qemu/builder.go index 7f404a566..301884ed0 100644 --- a/builder/qemu/builder.go +++ b/builder/qemu/builder.go @@ -67,6 +67,7 @@ type config struct { ISOChecksum string `mapstructure:"iso_checksum"` ISOChecksumType string `mapstructure:"iso_checksum_type"` ISOUrls []string `mapstructure:"iso_urls"` + MachineType string `mapstructure:"machine_type"` NetDevice string `mapstructure:"net_device"` OutputDir string `mapstructure:"output_directory"` QemuArgs [][]string `mapstructure:"qemuargs"` @@ -127,6 +128,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.HTTPPortMax = 9000 } + if b.config.MachineType == "" { + b.config.MachineType = "pc-1.0" + } + if b.config.OutputDir == "" { b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName) } @@ -205,6 +210,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { "shutdown_timeout": &b.config.RawShutdownTimeout, "ssh_wait_timeout": &b.config.RawSSHWaitTimeout, "accelerator": &b.config.Accelerator, + "machine_type": &b.config.MachineType, "net_device": &b.config.NetDevice, "disk_interface": &b.config.DiskInterface, } diff --git a/builder/qemu/step_run.go b/builder/qemu/step_run.go index 858c8424c..649ba63ee 100644 --- a/builder/qemu/step_run.go +++ b/builder/qemu/step_run.go @@ -76,7 +76,7 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error defaultArgs := make(map[string]string) defaultArgs["-name"] = vmName - defaultArgs["-machine"] = fmt.Sprintf("type=pc-1.0,accel=%s", config.Accelerator) + defaultArgs["-machine"] = fmt.Sprintf("type=%s,accel=%s", config.MachineType, config.Accelerator) defaultArgs["-display"] = guiArgument defaultArgs["-netdev"] = "user,id=user.0" defaultArgs["-device"] = fmt.Sprintf("%s,netdev=user.0", config.NetDevice) From 031c25154f0050ba1616196df64afbccb01a76f2 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 12 Aug 2014 08:29:00 -0700 Subject: [PATCH 264/593] openstack builder error out when gets blank keypair --- builder/openstack/step_key_pair.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/builder/openstack/step_key_pair.go b/builder/openstack/step_key_pair.go index 8f729b40f..40e93df30 100644 --- a/builder/openstack/step_key_pair.go +++ b/builder/openstack/step_key_pair.go @@ -29,6 +29,10 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err)) return multistep.ActionHalt } + if keyResp.PrivateKey == "" { + state.Put("error", fmt.Errorf("The temporary keypair returned was blank")) + return multistep.ActionHalt + } // If we're in debug mode, output the private key to the working // directory. From 51f55dda48b9c6188c451091989fa23e47a76f11 Mon Sep 17 00:00:00 2001 From: Chris Farmiloe Date: Tue, 12 Aug 2014 18:11:27 +0200 Subject: [PATCH 265/593] fix panic when vagrant cloud response fails --- post-processor/vagrant-cloud/step_verify_box.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/post-processor/vagrant-cloud/step_verify_box.go b/post-processor/vagrant-cloud/step_verify_box.go index bbbc3b4b7..cc3f48b85 100644 --- a/post-processor/vagrant-cloud/step_verify_box.go +++ b/post-processor/vagrant-cloud/step_verify_box.go @@ -33,7 +33,12 @@ func (s *stepVerifyBox) Run(state multistep.StateBag) multistep.StepAction { path := fmt.Sprintf("box/%s", config.Tag) resp, err := client.Get(path) - if err != nil || (resp.StatusCode != 200) { + if err != nil { + state.Put("error", fmt.Errorf("Error retrieving box: %s", err)) + return multistep.ActionHalt + } + + if resp.StatusCode != 200 { cloudErrors := &VagrantCloudErrors{} err = decodeBody(resp, cloudErrors) state.Put("error", fmt.Errorf("Error retrieving box: %s", cloudErrors.FormatErrors())) From 70cc5551337b2098e75b88dcf914cf30cb9ae810 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Tue, 12 Aug 2014 09:19:20 -0700 Subject: [PATCH 266/593] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bebdd672..2eafc4504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ BUG FIXES: + * builder/googlecompute: add `disk_size` option. [GH-1397] + * builder/parallels-iso: ISO not removed from VM after install [GH-1338] * builder/vmware/vmx: Do not re-add floppy disk files to VMX [GH-1361] ## 0.6.1 (July 20, 2014) From 69500b6943a3264122cab5dee539c059f21a506f Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 15 Aug 2014 00:32:53 -0700 Subject: [PATCH 267/593] OpenStack builder: Make region not required Perhaps "region" is required when using a public provider like Rackspace? It's not required for my private cloud from Metacloud. I suspect a lot of private clouds have only a single region and thus don't need "region" to be specified. --- builder/openstack/access_config.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/builder/openstack/access_config.go b/builder/openstack/access_config.go index 475b859d7..25affa8c1 100644 --- a/builder/openstack/access_config.go +++ b/builder/openstack/access_config.go @@ -115,10 +115,6 @@ func (c *AccessConfig) Prepare(t *packer.ConfigTemplate) []error { } } - if c.Region() == "" { - errs = append(errs, fmt.Errorf("region must be specified")) - } - if len(errs) > 0 { return errs } From 9bd33d1e0b94e93ccb2f2ece8b29c09c9c68e5b2 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 15 Aug 2014 11:19:04 -0700 Subject: [PATCH 268/593] Make region required for rackspace but not other providers Update test to make it pass. --- builder/openstack/access_config.go | 6 ++++++ builder/openstack/access_config_test.go | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/builder/openstack/access_config.go b/builder/openstack/access_config.go index 25affa8c1..45e256bb6 100644 --- a/builder/openstack/access_config.go +++ b/builder/openstack/access_config.go @@ -115,6 +115,12 @@ func (c *AccessConfig) Prepare(t *packer.ConfigTemplate) []error { } } + if strings.HasPrefix(c.Provider, "rackspace") { + if c.Region() == "" { + errs = append(errs, fmt.Errorf("region must be specified when using rackspace")) + } + } + if len(errs) > 0 { return errs } diff --git a/builder/openstack/access_config_test.go b/builder/openstack/access_config_test.go index 7a9e5201e..5c92216e3 100644 --- a/builder/openstack/access_config_test.go +++ b/builder/openstack/access_config_test.go @@ -8,13 +8,22 @@ func testAccessConfig() *AccessConfig { return &AccessConfig{} } -func TestAccessConfigPrepare_NoRegion(t *testing.T) { +func TestAccessConfigPrepare_NoRegion_Rackspace(t *testing.T) { c := testAccessConfig() + c.Provider = "rackspace-us" if err := c.Prepare(nil); err == nil { t.Fatalf("shouldn't have err: %s", err) } } +func TestAccessConfigPrepare_NoRegion_PrivateCloud(t *testing.T) { + c := testAccessConfig() + c.Provider = "http://some-keystone-server:5000/v2.0" + if err := c.Prepare(nil); err != nil { + t.Fatalf("shouldn't have err: %s", err) + } +} + func TestAccessConfigPrepare_Region(t *testing.T) { dfw := "DFW" c := testAccessConfig() From c79121617cd3c2366481b55e1ddee38ba68deeaf Mon Sep 17 00:00:00 2001 From: Tim Dysinger Date: Mon, 18 Aug 2014 08:42:32 -1000 Subject: [PATCH 269/593] Use Region not S3Endpoint --- builder/amazon/instance/builder.go | 2 +- builder/amazon/instance/step_upload_bundle.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index 7edd027de..4646ad1d2 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -75,7 +75,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { "-s {{.SecretKey}} " + "-d {{.BundleDirectory}} " + "--batch " + - "--url {{.S3Endpoint}} " + + "--region {{.Region}} " + "--retry" } diff --git a/builder/amazon/instance/step_upload_bundle.go b/builder/amazon/instance/step_upload_bundle.go index 0ae1a404d..ec654aa61 100644 --- a/builder/amazon/instance/step_upload_bundle.go +++ b/builder/amazon/instance/step_upload_bundle.go @@ -11,7 +11,7 @@ type uploadCmdData struct { BucketName string BundleDirectory string ManifestPath string - S3Endpoint string + Region string SecretKey string } @@ -37,7 +37,7 @@ func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction { BucketName: config.S3Bucket, BundleDirectory: config.BundleDestination, ManifestPath: manifestPath, - S3Endpoint: region.S3Endpoint, + Region: region.Name, SecretKey: config.SecretKey, }) if err != nil { From 6acbc91ff7d293fb3a1a5a865c6b8b98404bb4d0 Mon Sep 17 00:00:00 2001 From: "Jason A. Beranek" Date: Mon, 18 Aug 2014 20:15:15 -0500 Subject: [PATCH 270/593] vmware-iso: Fix error vnc min/max ports [GH-1288] --- builder/vmware/iso/step_configure_vnc.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/builder/vmware/iso/step_configure_vnc.go b/builder/vmware/iso/step_configure_vnc.go index 1f194be76..1d8a068c7 100644 --- a/builder/vmware/iso/step_configure_vnc.go +++ b/builder/vmware/iso/step_configure_vnc.go @@ -34,7 +34,12 @@ func (stepConfigureVNC) VNCAddress(portMin, portMax uint) (string, uint) { var vncPort uint portRange := int(portMax - portMin) for { - vncPort = uint(rand.Intn(portRange)) + portMin + if portRange > 0 { + vncPort = uint(rand.Intn(portRange)) + portMin + } else { + vncPort = portMin + } + log.Printf("Trying port: %d", vncPort) l, err := net.Listen("tcp", fmt.Sprintf(":%d", vncPort)) if err == nil { From 2c41d59e2109444c35f8c798bcf39a2619c59d03 Mon Sep 17 00:00:00 2001 From: "Jason A. Beranek" Date: Mon, 18 Aug 2014 20:28:25 -0500 Subject: [PATCH 271/593] vmware-iso: Update VNCAddressFinder interface to return errors from VNCAddress() --- builder/vmware/iso/driver_esx5.go | 10 ++++++++-- builder/vmware/iso/step_configure_vnc.go | 12 +++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index c422e20c9..b96f2c940 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -148,7 +148,7 @@ func (d *ESX5Driver) HostIP() (string, error) { return host, err } -func (d *ESX5Driver) VNCAddress(portMin, portMax uint) (string, uint) { +func (d *ESX5Driver) VNCAddress(portMin, portMax uint) (string, uint, error) { var vncPort uint // TODO(dougm) use esxcli network ip connection list for port := portMin; port <= portMax; port++ { @@ -170,7 +170,13 @@ func (d *ESX5Driver) VNCAddress(portMin, portMax uint) (string, uint) { } } - return d.Host, vncPort + if vncPort == 0 { + err := fmt.Errorf("Unable to find available VNC port between %d and %d", + portMin, portMax) + return d.Host, vncPort, err + } + + return d.Host, vncPort, nil } func (d *ESX5Driver) SSHAddress(state multistep.StateBag) (string, error) { diff --git a/builder/vmware/iso/step_configure_vnc.go b/builder/vmware/iso/step_configure_vnc.go index 1d8a068c7..a09d92baa 100644 --- a/builder/vmware/iso/step_configure_vnc.go +++ b/builder/vmware/iso/step_configure_vnc.go @@ -24,10 +24,10 @@ import ( type stepConfigureVNC struct{} type VNCAddressFinder interface { - VNCAddress(uint, uint) (string, uint) + VNCAddress(uint, uint) (string, uint, error) } -func (stepConfigureVNC) VNCAddress(portMin, portMax uint) (string, uint) { +func (stepConfigureVNC) VNCAddress(portMin, portMax uint) (string, uint, error) { // Find an open VNC port. Note that this can still fail later on // because we have to release the port at some point. But this does its // best. @@ -47,7 +47,7 @@ func (stepConfigureVNC) VNCAddress(portMin, portMax uint) (string, uint) { break } } - return "127.0.0.1", vncPort + return "127.0.0.1", vncPort, nil } func (s *stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction { @@ -79,10 +79,8 @@ func (s *stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction { vncFinder = s } log.Printf("Looking for available port between %d and %d", config.VNCPortMin, config.VNCPortMax) - vncIp, vncPort := vncFinder.VNCAddress(config.VNCPortMin, config.VNCPortMax) - if vncPort == 0 { - err := fmt.Errorf("Unable to find available VNC port between %d and %d", - config.VNCPortMin, config.VNCPortMax) + vncIp, vncPort, err := vncFinder.VNCAddress(config.VNCPortMin, config.VNCPortMax) + if err != nil { state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt From 93e4475d6a0f273e3a257d1912a4a9238203c3e8 Mon Sep 17 00:00:00 2001 From: "Jason A. Beranek" Date: Mon, 18 Aug 2014 21:50:48 -0500 Subject: [PATCH 272/593] vmware-iso/driver-esxi: Detect VNC in cross-platform way [GH-1372] Use VMware calls to determine ports being listened to, and determine free VNC port --- builder/vmware/iso/driver_esx5.go | 46 +++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index b96f2c940..565fd5ee4 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -16,7 +16,6 @@ import ( "os" "path/filepath" "strings" - "syscall" "time" ) @@ -150,23 +149,46 @@ func (d *ESX5Driver) HostIP() (string, error) { func (d *ESX5Driver) VNCAddress(portMin, portMax uint) (string, uint, error) { var vncPort uint - // TODO(dougm) use esxcli network ip connection list + + //Process ports ESXi is listening on to determine which are available + r, err := d.esxcli("network", "ip", "connection", "list") + if err != nil { + err = fmt.Errorf("Could not retrieve network information for ESXi: %v", err) + return "", 0, err + } + + listenPorts := make(map[string]bool) + for record, err := r.read(); record != nil && err == nil; record, err = r.read() { + if record["State"] == "LISTEN" { + splitAddress := strings.Split(record["LocalAddress"], ":") + log.Print(splitAddress) + port := splitAddress[len(splitAddress)-1] + log.Printf("ESXi Listening on: %s", port) + listenPorts[port] = true + } + } + for port := portMin; port <= portMax; port++ { + if _, ok := listenPorts[string(port)]; ok { + log.Printf("Port %d in use", port) + continue + } address := fmt.Sprintf("%s:%d", d.Host, port) log.Printf("Trying address: %s...", address) l, err := net.DialTimeout("tcp", address, 1*time.Second) + log.Printf("Dial complete address: %s...", address) - if err == nil { - log.Printf("%s in use", address) - l.Close() - } else if e, ok := err.(*net.OpError); ok { - if e.Err == syscall.ECONNREFUSED { - // then port should be available for listening - vncPort = port - break - } else if e.Timeout() { - log.Printf("Timeout connecting to: %s (check firewall rules)", address) + if err != nil { + if e, ok := err.(*net.OpError); ok { + if e.Timeout() { + log.Printf("Timeout connecting to: %s (check firewall rules)", address) + } else { + vncPort = port + break + } } + } else { + defer l.Close() } } From e0b6943c1c35115c81e1746586c04ee6e0b5f071 Mon Sep 17 00:00:00 2001 From: Nick Presta Date: Tue, 19 Aug 2014 21:26:01 -0400 Subject: [PATCH 273/593] Updating check for version when on devel. --- scripts/devcompile.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/devcompile.sh b/scripts/devcompile.sh index 13a4a7740..0b362ccb6 100755 --- a/scripts/devcompile.sh +++ b/scripts/devcompile.sh @@ -14,6 +14,10 @@ verify_go () { return 0 fi + if [[ "$2" == "devel" ]]; then + return 0 + fi + local IFS=. local i ver1=($1) ver2=($2) From 3cf90cb9641bc7f4673379e62e45a8738884e3af Mon Sep 17 00:00:00 2001 From: Nick Presta Date: Wed, 20 Aug 2014 00:08:22 -0400 Subject: [PATCH 274/593] Updating Ubuntu 12.04 ISO location and checksum --- website/source/docs/builders/virtualbox-iso.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/docs/builders/virtualbox-iso.html.markdown b/website/source/docs/builders/virtualbox-iso.html.markdown index 2626a4ebe..a7596cf61 100644 --- a/website/source/docs/builders/virtualbox-iso.html.markdown +++ b/website/source/docs/builders/virtualbox-iso.html.markdown @@ -26,8 +26,8 @@ Ubuntu to self-install. Still, the example serves to show the basic configuratio { "type": "virtualbox-iso", "guest_os_type": "Ubuntu_64", - "iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.3-server-amd64.iso", - "iso_checksum": "2cbe868812a871242cdcdd8f2fd6feb9", + "iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.5-server-amd64.iso", + "iso_checksum": "769474248a3897f4865817446f9a4a53", "iso_checksum_type": "md5", "ssh_username": "packer", "ssh_password": "packer", From 8f237b7b94d32380af4f906ad3897157c242c29c Mon Sep 17 00:00:00 2001 From: Matt Page Date: Wed, 20 Aug 2014 10:20:28 -0700 Subject: [PATCH 275/593] Allow specifying project for source images in GCE Within GCE, images may be shared across projects. Prior to this commit, there was no way to inform the GCE builder that a source image belonged to a specific project. This adds an optional 'source_image_project_id' key to the GCE builder config. --- builder/googlecompute/config.go | 70 ++++++++++--------- builder/googlecompute/driver.go | 7 +- builder/googlecompute/driver_gce.go | 10 +-- builder/googlecompute/step_create_instance.go | 10 ++- 4 files changed, 56 insertions(+), 41 deletions(-) diff --git a/builder/googlecompute/config.go b/builder/googlecompute/config.go index 803963eb3..52df02f3a 100644 --- a/builder/googlecompute/config.go +++ b/builder/googlecompute/config.go @@ -16,25 +16,26 @@ import ( type Config struct { common.PackerConfig `mapstructure:",squash"` - BucketName string `mapstructure:"bucket_name"` - ClientSecretsFile string `mapstructure:"client_secrets_file"` - DiskSizeGb int64 `mapstructure:"disk_size"` - ImageName string `mapstructure:"image_name"` - ImageDescription string `mapstructure:"image_description"` - InstanceName string `mapstructure:"instance_name"` - MachineType string `mapstructure:"machine_type"` - Metadata map[string]string `mapstructure:"metadata"` - Network string `mapstructure:"network"` - Passphrase string `mapstructure:"passphrase"` - PrivateKeyFile string `mapstructure:"private_key_file"` - ProjectId string `mapstructure:"project_id"` - SourceImage string `mapstructure:"source_image"` - SSHUsername string `mapstructure:"ssh_username"` - SSHPort uint `mapstructure:"ssh_port"` - RawSSHTimeout string `mapstructure:"ssh_timeout"` - RawStateTimeout string `mapstructure:"state_timeout"` - Tags []string `mapstructure:"tags"` - Zone string `mapstructure:"zone"` + BucketName string `mapstructure:"bucket_name"` + ClientSecretsFile string `mapstructure:"client_secrets_file"` + DiskSizeGb int64 `mapstructure:"disk_size"` + ImageName string `mapstructure:"image_name"` + ImageDescription string `mapstructure:"image_description"` + InstanceName string `mapstructure:"instance_name"` + MachineType string `mapstructure:"machine_type"` + Metadata map[string]string `mapstructure:"metadata"` + Network string `mapstructure:"network"` + Passphrase string `mapstructure:"passphrase"` + PrivateKeyFile string `mapstructure:"private_key_file"` + ProjectId string `mapstructure:"project_id"` + SourceImage string `mapstructure:"source_image"` + SourceImageProjectId string `mapstructure:"source_image_project_id"` + SSHUsername string `mapstructure:"ssh_username"` + SSHPort uint `mapstructure:"ssh_port"` + RawSSHTimeout string `mapstructure:"ssh_timeout"` + RawStateTimeout string `mapstructure:"state_timeout"` + Tags []string `mapstructure:"tags"` + Zone string `mapstructure:"zone"` clientSecrets *clientSecrets instanceName string @@ -103,21 +104,22 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { // Process Templates templates := map[string]*string{ - "bucket_name": &c.BucketName, - "client_secrets_file": &c.ClientSecretsFile, - "image_name": &c.ImageName, - "image_description": &c.ImageDescription, - "instance_name": &c.InstanceName, - "machine_type": &c.MachineType, - "network": &c.Network, - "passphrase": &c.Passphrase, - "private_key_file": &c.PrivateKeyFile, - "project_id": &c.ProjectId, - "source_image": &c.SourceImage, - "ssh_username": &c.SSHUsername, - "ssh_timeout": &c.RawSSHTimeout, - "state_timeout": &c.RawStateTimeout, - "zone": &c.Zone, + "bucket_name": &c.BucketName, + "client_secrets_file": &c.ClientSecretsFile, + "image_name": &c.ImageName, + "image_description": &c.ImageDescription, + "instance_name": &c.InstanceName, + "machine_type": &c.MachineType, + "network": &c.Network, + "passphrase": &c.Passphrase, + "private_key_file": &c.PrivateKeyFile, + "project_id": &c.ProjectId, + "source_image": &c.SourceImage, + "source_image_project_id": &c.SourceImageProjectId, + "ssh_username": &c.SSHUsername, + "ssh_timeout": &c.RawSSHTimeout, + "state_timeout": &c.RawStateTimeout, + "zone": &c.Zone, } for n, ptr := range templates { diff --git a/builder/googlecompute/driver.go b/builder/googlecompute/driver.go index f4b6006a9..da1b52088 100644 --- a/builder/googlecompute/driver.go +++ b/builder/googlecompute/driver.go @@ -23,10 +23,15 @@ type Driver interface { WaitForInstance(state, zone, name string) <-chan error } +type Image struct { + Name string + ProjectId string +} + type InstanceConfig struct { Description string DiskSizeGb int64 - Image string + Image Image MachineType string Metadata map[string]string Name string diff --git a/builder/googlecompute/driver_gce.go b/builder/googlecompute/driver_gce.go index 65d7c7bcd..1439b7e3a 100644 --- a/builder/googlecompute/driver_gce.go +++ b/builder/googlecompute/driver_gce.go @@ -134,7 +134,7 @@ func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) { } // Get the image - d.ui.Message(fmt.Sprintf("Loading image: %s", c.Image)) + d.ui.Message(fmt.Sprintf("Loading image: %s in project %s", c.Image.Name, c.Image.ProjectId)) image, err := d.getImage(c.Image) if err != nil { return nil, err @@ -229,17 +229,17 @@ func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error { return errCh } -func (d *driverGCE) getImage(name string) (image *compute.Image, err error) { - projects := []string{d.projectId, "centos-cloud", "coreos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "windows-cloud"} +func (d *driverGCE) getImage(img Image) (image *compute.Image, err error) { + projects := []string{img.ProjectId, "centos-cloud", "coreos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "windows-cloud"} for _, project := range projects { - image, err = d.service.Images.Get(project, name).Do() + image, err = d.service.Images.Get(project, img.Name).Do() if err == nil && image != nil && image.SelfLink != "" { return } image = nil } - err = fmt.Errorf("Image %s could not be found in any of these projects: %s", name, projects) + err = fmt.Errorf("Image %s could not be found in any of these projects: %s", img.Name, projects) return } diff --git a/builder/googlecompute/step_create_instance.go b/builder/googlecompute/step_create_instance.go index 3eab82021..d35dc4157 100644 --- a/builder/googlecompute/step_create_instance.go +++ b/builder/googlecompute/step_create_instance.go @@ -16,6 +16,14 @@ type StepCreateInstance struct { instanceName string } +func (config *Config) getImage() (Image) { + project := config.ProjectId + if config.SourceImageProjectId != "" { + project = config.SourceImageProjectId + } + return Image{Name: config.SourceImage, ProjectId: project} +} + // Run executes the Packer build step that creates a GCE instance. func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) @@ -29,7 +37,7 @@ func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction errCh, err := driver.RunInstance(&InstanceConfig{ Description: "New instance created by Packer", DiskSizeGb: config.DiskSizeGb, - Image: config.SourceImage, + Image: config.getImage(), MachineType: config.MachineType, Metadata: map[string]string{ "sshKeys": fmt.Sprintf("%s:%s", config.SSHUsername, sshPublicKey), From ebdfa2bc56ec0354dc2accd609b27288d28d8317 Mon Sep 17 00:00:00 2001 From: "Jason A. Beranek" Date: Wed, 20 Aug 2014 20:42:05 -0500 Subject: [PATCH 276/593] vmware-iso/driver-esxi: Fix VNC detection of used ports Fixes error in earlier commit that didn't properly detect a port was listened to. --- builder/vmware/iso/driver_esx5.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index 565fd5ee4..211bc119e 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -169,14 +169,13 @@ func (d *ESX5Driver) VNCAddress(portMin, portMax uint) (string, uint, error) { } for port := portMin; port <= portMax; port++ { - if _, ok := listenPorts[string(port)]; ok { + if _, ok := listenPorts[fmt.Sprintf("%d", port)]; ok { log.Printf("Port %d in use", port) continue } address := fmt.Sprintf("%s:%d", d.Host, port) log.Printf("Trying address: %s...", address) l, err := net.DialTimeout("tcp", address, 1*time.Second) - log.Printf("Dial complete address: %s...", address) if err != nil { if e, ok := err.(*net.OpError); ok { From 2e3b34789a297c71d139d476c1a00f173571a3b1 Mon Sep 17 00:00:00 2001 From: RH Becker Date: Thu, 21 Aug 2014 17:29:20 -0700 Subject: [PATCH 277/593] Add table of contents link to orphan doc: `other/environmental-variables.html`. --- website/source/layouts/docs.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index ea395d2f9..2296d8550 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -69,6 +69,7 @@
  • Other

  • Core Configuration
  • Debugging
  • +
  • Environmental Variables for Packer
    • From e3015d7fe1136581647c71ebdfa3559ed7cc9d5f Mon Sep 17 00:00:00 2001 From: RH Becker Date: Thu, 21 Aug 2014 17:41:35 -0700 Subject: [PATCH 278/593] Add `page_title` attribute to each documentation file for which it didn't already exist, matching the value to each page's h1, respectively. --- website/source/docs/basics/terminology.html.markdown | 1 + website/source/docs/builders/custom.html.markdown | 1 + website/source/docs/builders/digitalocean.html.markdown | 1 + website/source/docs/builders/docker.html.markdown | 1 + website/source/docs/builders/googlecompute.markdown | 1 + website/source/docs/builders/null.html.markdown | 1 + website/source/docs/builders/openstack.html.markdown | 1 + website/source/docs/builders/qemu.html.markdown | 1 + website/source/docs/command-line/introduction.html.markdown | 1 + website/source/docs/extend/command.html.markdown | 1 + website/source/docs/extend/developing-plugins.html.markdown | 1 + website/source/docs/extend/post-processor.html.markdown | 1 + website/source/docs/extend/provisioner.html.markdown | 1 + website/source/docs/index.html.markdown | 1 + website/source/docs/other/core-configuration.html.markdown | 1 + website/source/docs/other/debugging.html.markdown | 1 + website/source/docs/other/environmental-variables.html.markdown | 1 + website/source/docs/templates/builders.html.markdown | 1 + website/source/docs/templates/introduction.html.markdown | 1 + website/source/docs/templates/post-processors.html.markdown | 1 + website/source/docs/templates/provisioners.html.markdown | 1 + 21 files changed, 21 insertions(+) diff --git a/website/source/docs/basics/terminology.html.markdown b/website/source/docs/basics/terminology.html.markdown index 21e3d236b..f41882a48 100644 --- a/website/source/docs/basics/terminology.html.markdown +++ b/website/source/docs/basics/terminology.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Packer Terminology" --- # Packer Terminology diff --git a/website/source/docs/builders/custom.html.markdown b/website/source/docs/builders/custom.html.markdown index 9898d057b..2b087a48a 100644 --- a/website/source/docs/builders/custom.html.markdown +++ b/website/source/docs/builders/custom.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Custom Builder" --- # Custom Builder diff --git a/website/source/docs/builders/digitalocean.html.markdown b/website/source/docs/builders/digitalocean.html.markdown index ab971c136..f7ff676ea 100644 --- a/website/source/docs/builders/digitalocean.html.markdown +++ b/website/source/docs/builders/digitalocean.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "DigitalOcean Builder" --- # DigitalOcean Builder diff --git a/website/source/docs/builders/docker.html.markdown b/website/source/docs/builders/docker.html.markdown index e395e25d2..aa564271c 100644 --- a/website/source/docs/builders/docker.html.markdown +++ b/website/source/docs/builders/docker.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Docker Builder" --- # Docker Builder diff --git a/website/source/docs/builders/googlecompute.markdown b/website/source/docs/builders/googlecompute.markdown index 6d3c6216d..b8e304d61 100644 --- a/website/source/docs/builders/googlecompute.markdown +++ b/website/source/docs/builders/googlecompute.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Google Compute Builder" --- # Google Compute Builder diff --git a/website/source/docs/builders/null.html.markdown b/website/source/docs/builders/null.html.markdown index ce822a474..e9a14dda2 100644 --- a/website/source/docs/builders/null.html.markdown +++ b/website/source/docs/builders/null.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Null Builder" --- # Null Builder diff --git a/website/source/docs/builders/openstack.html.markdown b/website/source/docs/builders/openstack.html.markdown index 3f8c5bb87..b9d23b9ce 100644 --- a/website/source/docs/builders/openstack.html.markdown +++ b/website/source/docs/builders/openstack.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "OpenStack Builder" --- # OpenStack Builder diff --git a/website/source/docs/builders/qemu.html.markdown b/website/source/docs/builders/qemu.html.markdown index 2430c737f..c81e77a6c 100644 --- a/website/source/docs/builders/qemu.html.markdown +++ b/website/source/docs/builders/qemu.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "QEMU Builder" --- # QEMU Builder diff --git a/website/source/docs/command-line/introduction.html.markdown b/website/source/docs/command-line/introduction.html.markdown index fd4ada4d3..f28755e7e 100644 --- a/website/source/docs/command-line/introduction.html.markdown +++ b/website/source/docs/command-line/introduction.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Packer Command-Line" --- # Packer Command-Line diff --git a/website/source/docs/extend/command.html.markdown b/website/source/docs/extend/command.html.markdown index 703b09547..58211ca47 100644 --- a/website/source/docs/extend/command.html.markdown +++ b/website/source/docs/extend/command.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Custom Command Development" --- # Custom Command Development diff --git a/website/source/docs/extend/developing-plugins.html.markdown b/website/source/docs/extend/developing-plugins.html.markdown index 1aa233e82..09bee3f0e 100644 --- a/website/source/docs/extend/developing-plugins.html.markdown +++ b/website/source/docs/extend/developing-plugins.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Developing Plugins" --- # Developing Plugins diff --git a/website/source/docs/extend/post-processor.html.markdown b/website/source/docs/extend/post-processor.html.markdown index b453dda29..a00e90b92 100644 --- a/website/source/docs/extend/post-processor.html.markdown +++ b/website/source/docs/extend/post-processor.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Custom Post-Processor Development" --- # Custom Post-Processor Development diff --git a/website/source/docs/extend/provisioner.html.markdown b/website/source/docs/extend/provisioner.html.markdown index bf6628b87..121338bb0 100644 --- a/website/source/docs/extend/provisioner.html.markdown +++ b/website/source/docs/extend/provisioner.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Custom Provisioner Development" --- # Custom Provisioner Development diff --git a/website/source/docs/index.html.markdown b/website/source/docs/index.html.markdown index 2996da011..738030663 100644 --- a/website/source/docs/index.html.markdown +++ b/website/source/docs/index.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Packer Documentation" --- # Packer Documentation diff --git a/website/source/docs/other/core-configuration.html.markdown b/website/source/docs/other/core-configuration.html.markdown index 42705384a..ea7d7f6d6 100644 --- a/website/source/docs/other/core-configuration.html.markdown +++ b/website/source/docs/other/core-configuration.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Core Configuration" --- # Core Configuration diff --git a/website/source/docs/other/debugging.html.markdown b/website/source/docs/other/debugging.html.markdown index 9513e463c..ab82cf3b6 100644 --- a/website/source/docs/other/debugging.html.markdown +++ b/website/source/docs/other/debugging.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Debugging Packer" --- # Debugging Packer diff --git a/website/source/docs/other/environmental-variables.html.markdown b/website/source/docs/other/environmental-variables.html.markdown index 2d5c15f59..4a4e8907d 100644 --- a/website/source/docs/other/environmental-variables.html.markdown +++ b/website/source/docs/other/environmental-variables.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Environmental Variables for Packer" --- # Environmental Variables for Packer diff --git a/website/source/docs/templates/builders.html.markdown b/website/source/docs/templates/builders.html.markdown index d82e20b49..1a46f54bd 100644 --- a/website/source/docs/templates/builders.html.markdown +++ b/website/source/docs/templates/builders.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Templates: Builders" --- # Templates: Builders diff --git a/website/source/docs/templates/introduction.html.markdown b/website/source/docs/templates/introduction.html.markdown index 1bf3bc282..f2eb08129 100644 --- a/website/source/docs/templates/introduction.html.markdown +++ b/website/source/docs/templates/introduction.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Templates" --- # Templates diff --git a/website/source/docs/templates/post-processors.html.markdown b/website/source/docs/templates/post-processors.html.markdown index 43864aa61..ccc9edbb7 100644 --- a/website/source/docs/templates/post-processors.html.markdown +++ b/website/source/docs/templates/post-processors.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Templates: Post-Processors" --- # Templates: Post-Processors diff --git a/website/source/docs/templates/provisioners.html.markdown b/website/source/docs/templates/provisioners.html.markdown index 8f29d550e..c26d3e163 100644 --- a/website/source/docs/templates/provisioners.html.markdown +++ b/website/source/docs/templates/provisioners.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Templates: Provisioners" --- # Templates: Provisioners From d2a9a98eeb4bb7c273e4f21bda0a8ae479bf2fff Mon Sep 17 00:00:00 2001 From: Fabio Rapposelli Date: Fri, 22 Aug 2014 10:07:36 +0200 Subject: [PATCH 279/593] Fixed multiple VMware typos. --- builder/vmware/common/driver.go | 4 ++-- builder/vmware/common/driver_fusion5.go | 2 +- builder/vmware/common/driver_fusion6.go | 2 +- builder/vmware/common/driver_workstation9.go | 2 +- builder/vmware/common/driver_workstation_unix.go | 6 +++--- website/source/community/index.html.markdown | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index 35a358cc4..ddefbd9d8 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -151,7 +151,7 @@ func normalizeVersion(version string) (string, error) { i, err := strconv.Atoi(version) if err != nil { return "", fmt.Errorf( - "VMWare version '%s' is not numeric", version) + "VMware version '%s' is not numeric", version) } return fmt.Sprintf("%02d", i), nil @@ -170,7 +170,7 @@ func compareVersions(versionFound string, versionWanted string) error { if found < wanted { return fmt.Errorf( - "VMWare WS version %s, or greater, is required. Found version: %s", versionWanted, versionFound) + "VMware WS version %s, or greater, is required. Found version: %s", versionWanted, versionFound) } return nil diff --git a/builder/vmware/common/driver_fusion5.go b/builder/vmware/common/driver_fusion5.go index ee6cdd6c1..8bcbb8d30 100644 --- a/builder/vmware/common/driver_fusion5.go +++ b/builder/vmware/common/driver_fusion5.go @@ -12,7 +12,7 @@ import ( "github.com/mitchellh/multistep" ) -// Fusion5Driver is a driver that can run VMWare Fusion 5. +// Fusion5Driver is a driver that can run VMware Fusion 5. type Fusion5Driver struct { // This is the path to the "VMware Fusion.app" AppPath string diff --git a/builder/vmware/common/driver_fusion6.go b/builder/vmware/common/driver_fusion6.go index 98c466cb9..1d71a1780 100644 --- a/builder/vmware/common/driver_fusion6.go +++ b/builder/vmware/common/driver_fusion6.go @@ -11,7 +11,7 @@ import ( "strings" ) -// Fusion6Driver is a driver that can run VMWare Fusion 5. +// Fusion6Driver is a driver that can run VMware Fusion 5. type Fusion6Driver struct { Fusion5Driver } diff --git a/builder/vmware/common/driver_workstation9.go b/builder/vmware/common/driver_workstation9.go index f47809558..4c72c72b3 100644 --- a/builder/vmware/common/driver_workstation9.go +++ b/builder/vmware/common/driver_workstation9.go @@ -23,7 +23,7 @@ type Workstation9Driver struct { } func (d *Workstation9Driver) Clone(dst, src string) error { - return errors.New("Cloning is not supported with VMWare WS version 9. Please use VMWare WS version 10, or greater.") + return errors.New("Cloning is not supported with VMware WS version 9. Please use VMware WS version 10, or greater.") } func (d *Workstation9Driver) CompactDisk(diskPath string) error { diff --git a/builder/vmware/common/driver_workstation_unix.go b/builder/vmware/common/driver_workstation_unix.go index 63b379e7c..74fd3ba0a 100644 --- a/builder/vmware/common/driver_workstation_unix.go +++ b/builder/vmware/common/driver_workstation_unix.go @@ -53,7 +53,7 @@ func workstationVmnetnatConfPath() string { func workstationVerifyVersion(version string) error { if runtime.GOOS != "linux" { - return fmt.Errorf("The VMWare WS version %s driver is only supported on Linux, and Windows, at the moment. Your OS: %s", version, runtime.GOOS) + return fmt.Errorf("The VMware WS version %s driver is only supported on Linux, and Windows, at the moment. Your OS: %s", version, runtime.GOOS) } //TODO(pmyjavec) there is a better way to find this, how? @@ -71,9 +71,9 @@ func workstationVerifyVersion(version string) error { matches := versionRe.FindStringSubmatch(stderr.String()) if matches == nil { return fmt.Errorf( - "Could not find VMWare WS version in output: %s", stderr.String()) + "Could not find VMware WS version in output: %s", stderr.String()) } - log.Printf("Detected VMWare WS version: %s", matches[1]) + log.Printf("Detected VMware WS version: %s", matches[1]) return compareVersions(matches[1], version) } diff --git a/website/source/community/index.html.markdown b/website/source/community/index.html.markdown index a9307ef82..4fedf4349 100644 --- a/website/source/community/index.html.markdown +++ b/website/source/community/index.html.markdown @@ -66,7 +66,7 @@ list as contributors come and go.

      Ross Smith II (@rasa)

      -Ross Smith maintains our VMWare builder on Windows, and provides other valuable assistance. +Ross Smith maintains our VMware builder on Windows, and provides other valuable assistance. Ross is an open source enthusist, published author, and freelance consultant.

    From 732c767bfd0d345fe6a390ebc8b1802be4ff9632 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Sat, 23 Aug 2014 20:23:13 +0200 Subject: [PATCH 280/593] parallels: Support for Parallels Desktop 10 --- builder/parallels/common/driver_9.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/builder/parallels/common/driver_9.go b/builder/parallels/common/driver_9.go index 6ef8787c2..20255beb9 100644 --- a/builder/parallels/common/driver_9.go +++ b/builder/parallels/common/driver_9.go @@ -3,15 +3,17 @@ package common import ( "bytes" "fmt" - "github.com/going/toolkit/xmlpath" "log" "os" "os/exec" "regexp" "strings" "time" + + "github.com/going/toolkit/xmlpath" ) +// Driver supporting Parallels Desktop for Mac v. 9 & 10 type Parallels9Driver struct { // This is the path to the "prlctl" application. PrlctlPath string @@ -135,8 +137,8 @@ func (d *Parallels9Driver) Prlctl(args ...string) error { func (d *Parallels9Driver) Verify() error { version, _ := d.Version() - if !strings.HasPrefix(version, "9.") { - return fmt.Errorf("The packer-parallels builder plugin only supports Parallels Desktop v. 9. You have: %s!\n", version) + if !(strings.HasPrefix(version, "9.") || strings.HasPrefix(version, "10.")) { + return fmt.Errorf("The packer-parallels builder plugin only supports Parallels Desktop v. 9 & 10. You have: %s!\n", version) } return nil } From 9ad2b986734323deab58e50674c883c3064b8d96 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Mon, 11 Aug 2014 09:30:46 +0400 Subject: [PATCH 281/593] builder/parallels: Removed config parameter 'ostype' We have to set either 'ostype' or 'distribution', but not both. --- builder/parallels/iso/builder.go | 9 +++------ builder/parallels/iso/builder_test.go | 4 ++-- builder/parallels/iso/step_create_vm.go | 1 - 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/builder/parallels/iso/builder.go b/builder/parallels/iso/builder.go index 3b940fdc9..0fa38d5fb 100644 --- a/builder/parallels/iso/builder.go +++ b/builder/parallels/iso/builder.go @@ -33,7 +33,6 @@ type config struct { ParallelsToolsMode string `mapstructure:"parallels_tools_mode"` ParallelsToolsGuestPath string `mapstructure:"parallels_tools_guest_path"` ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"` - GuestOSType string `mapstructure:"guest_os_type"` GuestOSDistribution string `mapstructure:"guest_os_distribution"` HardDriveInterface string `mapstructure:"hard_drive_interface"` HostInterfaces []string `mapstructure:"host_interfaces"` @@ -47,6 +46,9 @@ type config struct { RawSingleISOUrl string `mapstructure:"iso_url"` + // Deprecated parameters + GuestOSType string `mapstructure:"guest_os_type"` + tpl *packer.ConfigTemplate } @@ -95,10 +97,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.HardDriveInterface = "sata" } - if b.config.GuestOSType == "" { - b.config.GuestOSType = "other" - } - if b.config.GuestOSDistribution == "" { b.config.GuestOSDistribution = "other" } @@ -125,7 +123,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { "parallels_tools_mode": &b.config.ParallelsToolsMode, "parallels_tools_host_path": &b.config.ParallelsToolsHostPath, "parallels_tools_guest_path": &b.config.ParallelsToolsGuestPath, - "guest_os_type": &b.config.GuestOSType, "guest_os_distribution": &b.config.GuestOSDistribution, "hard_drive_interface": &b.config.HardDriveInterface, "http_directory": &b.config.HTTPDir, diff --git a/builder/parallels/iso/builder_test.go b/builder/parallels/iso/builder_test.go index ba9cecdd8..f20f544d6 100644 --- a/builder/parallels/iso/builder_test.go +++ b/builder/parallels/iso/builder_test.go @@ -42,8 +42,8 @@ func TestBuilderPrepare_Defaults(t *testing.T) { t.Errorf("bad parallels tools mode: %s", b.config.ParallelsToolsMode) } - if b.config.GuestOSType != "other" { - t.Errorf("bad guest OS type: %s", b.config.GuestOSType) + if b.config.GuestOSDistribution != "other" { + t.Errorf("bad guest OS distribution: %s", b.config.GuestOSDistribution) } if b.config.VMName != "packer-foo" { diff --git a/builder/parallels/iso/step_create_vm.go b/builder/parallels/iso/step_create_vm.go index b91503ef6..1b2468cfe 100644 --- a/builder/parallels/iso/step_create_vm.go +++ b/builder/parallels/iso/step_create_vm.go @@ -28,7 +28,6 @@ func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction { commands := make([][]string, 8) commands[0] = []string{ "create", name, - "--ostype", config.GuestOSType, "--distribution", config.GuestOSDistribution, "--dst", path, "--vmtype", "vm", From 10be7e27debda0200d136ac2797075b6d1a49edc Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Mon, 11 Aug 2014 10:18:35 +0400 Subject: [PATCH 282/593] builder/parallels: Added platform check --- builder/parallels/common/driver.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/builder/parallels/common/driver.go b/builder/parallels/common/driver.go index c28f0f9f4..5b91771a3 100644 --- a/builder/parallels/common/driver.go +++ b/builder/parallels/common/driver.go @@ -45,6 +45,11 @@ type Driver interface { func NewDriver() (Driver, error) { var prlctlPath string + if runtime.GOOS != "darwin" { + return nil, fmt.Errorf( + "Parallels builder works only on \"darwin\" platform!") + } + if prlctlPath == "" { var err error prlctlPath, err = exec.LookPath("prlctl") From fc2d695b0fbbc9041ccff80b14c09f2a745f4afa Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Wed, 20 Aug 2014 15:49:29 +0400 Subject: [PATCH 283/593] builder/parallels: Added compatibility with Parallels Desktop 10 --- builder/parallels/common/driver.go | 30 +++++++++++++++++--- builder/parallels/common/driver_10.go | 6 ++++ builder/parallels/common/driver_9.go | 41 +++++++++++++++------------ 3 files changed, 55 insertions(+), 22 deletions(-) create mode 100644 builder/parallels/common/driver_10.go diff --git a/builder/parallels/common/driver.go b/builder/parallels/common/driver.go index 5b91771a3..ab9693461 100644 --- a/builder/parallels/common/driver.go +++ b/builder/parallels/common/driver.go @@ -1,8 +1,11 @@ package common import ( + "fmt" "log" "os/exec" + "runtime" + "strings" ) // A driver is able to talk to Parallels and perform certain @@ -43,7 +46,9 @@ type Driver interface { } func NewDriver() (Driver, error) { + var drivers map[string]Driver var prlctlPath string + var supportedVersions []string if runtime.GOOS != "darwin" { return nil, fmt.Errorf( @@ -59,10 +64,27 @@ func NewDriver() (Driver, error) { } log.Printf("prlctl path: %s", prlctlPath) - driver := &Parallels9Driver{prlctlPath} - if err := driver.Verify(); err != nil { - return nil, err + + drivers = map[string]Driver{ + "10": &Parallels10Driver{ + Parallels9Driver: Parallels9Driver{ + PrlctlPath: prlctlPath, + }, + }, + "9": &Parallels9Driver{ + PrlctlPath: prlctlPath, + }, + } + + for v, d := range drivers { + version, _ := d.Version() + if strings.HasPrefix(version, v) { + return d, nil + } + supportedVersions = append(supportedVersions, v) } - return driver, nil + return nil, fmt.Errorf( + "Unable to initialize any driver. Supported Parallels Desktop versions: "+ + "%s\n", strings.Join(supportedVersions, ", ")) } diff --git a/builder/parallels/common/driver_10.go b/builder/parallels/common/driver_10.go new file mode 100644 index 000000000..ae39240f4 --- /dev/null +++ b/builder/parallels/common/driver_10.go @@ -0,0 +1,6 @@ +package common + +// Parallels10Driver are inherited from Parallels9Driver. +type Parallels10Driver struct { + Parallels9Driver +} diff --git a/builder/parallels/common/driver_9.go b/builder/parallels/common/driver_9.go index 20255beb9..54e93a4bf 100644 --- a/builder/parallels/common/driver_9.go +++ b/builder/parallels/common/driver_9.go @@ -13,7 +13,6 @@ import ( "github.com/going/toolkit/xmlpath" ) -// Driver supporting Parallels Desktop for Mac v. 9 & 10 type Parallels9Driver struct { // This is the path to the "prlctl" application. PrlctlPath string @@ -72,6 +71,20 @@ func getConfigValueFromXpath(path, xpath string) (string, error) { return value, nil } +// Finds an application bundle by identifier (for "darwin" platform only) +func getAppPath(bundleId string) (string, error) { + cmd := exec.Command("mdfind", "kMDItemCFBundleIdentifier ==", bundleId) + out, err := cmd.Output() + if err != nil { + return "", err + } + if string(out) == "" { + return "", fmt.Errorf( + "Could not detect Parallels Desktop! Make sure it is properly installed.") + } + return string(out), nil +} + func (d *Parallels9Driver) IsRunning(name string) (bool, error) { var stdout bytes.Buffer @@ -136,32 +149,24 @@ func (d *Parallels9Driver) Prlctl(args ...string) error { } func (d *Parallels9Driver) Verify() error { - version, _ := d.Version() - if !(strings.HasPrefix(version, "9.") || strings.HasPrefix(version, "10.")) { - return fmt.Errorf("The packer-parallels builder plugin only supports Parallels Desktop v. 9 & 10. You have: %s!\n", version) - } return nil } func (d *Parallels9Driver) Version() (string, error) { - var stdout bytes.Buffer - - cmd := exec.Command(d.PrlctlPath, "--version") - cmd.Stdout = &stdout - if err := cmd.Run(); err != nil { + out, err := exec.Command(d.PrlctlPath, "--version").Output() + if err != nil { return "", err } - versionOutput := strings.TrimSpace(stdout.String()) - re := regexp.MustCompile("prlctl version ([0-9\\.]+)") - verMatch := re.FindAllStringSubmatch(versionOutput, 1) - - if len(verMatch) != 1 { - return "", fmt.Errorf("prlctl version not found!\n") + versionRe := regexp.MustCompile(`prlctl version (\d+\.\d+.\d+)`) + matches := versionRe.FindStringSubmatch(string(out)) + if matches == nil { + return "", fmt.Errorf( + "Could not find Parallels Desktop version in output:\n%s", string(out)) } - version := verMatch[0][1] - log.Printf("prlctl version: %s\n", version) + version := matches[1] + log.Printf("Parallels Desktop version: %s", version) return version, nil } From f9cddd2e18ecaff6a08981201928472188ed9f46 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Tue, 26 Aug 2014 01:00:58 +0200 Subject: [PATCH 284/593] builder/parallels: Added some navigation keys. Fixes [GH-1442] --- .../parallels/common/step_type_boot_command.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/builder/parallels/common/step_type_boot_command.go b/builder/parallels/common/step_type_boot_command.go index 38c8cc5ef..440371577 100644 --- a/builder/parallels/common/step_type_boot_command.go +++ b/builder/parallels/common/step_type_boot_command.go @@ -2,13 +2,14 @@ package common import ( "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" "log" "strings" "time" "unicode" "unicode/utf8" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" ) const KeyLeftShift uint32 = 0xFFE1 @@ -162,6 +163,17 @@ func scancodes(message string) []string { special[""] = []string{"1c", "9c"} special[""] = []string{"0f", "8f"} + special[""] = []string{"48", "c8"} + special[""] = []string{"50", "d0"} + special[""] = []string{"4b", "cb"} + special[""] = []string{"4d", "cd"} + special[""] = []string{"39", "b9"} + special[""] = []string{"52", "d2"} + special[""] = []string{"47", "c7"} + special[""] = []string{"4f", "cf"} + special[""] = []string{"49", "c9"} + special[""] = []string{"51", "d1"} + shiftedChars := "!@#$%^&*()_+{}:\"~|<>?" scancodeIndex := make(map[string]uint) From 008eb58c57502c6e875dc2c150a2d9931f8ce9c4 Mon Sep 17 00:00:00 2001 From: Ross Smith II Date: Tue, 26 Aug 2014 14:06:52 -0700 Subject: [PATCH 285/593] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eafc4504..b32cca755 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ BUG FIXES: * builder/googlecompute: add `disk_size` option. [GH-1397] * builder/parallels-iso: ISO not removed from VM after install [GH-1338] + * builder/parallels/all: Add support for Parallels Desktop 10 [GH-1438] + * builder/parallels/all: Added some navigation keys [GH-1442] * builder/vmware/vmx: Do not re-add floppy disk files to VMX [GH-1361] ## 0.6.1 (July 20, 2014) From 4166c637329d5006e21a027e4201d82efabe7144 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Thu, 28 Aug 2014 16:49:52 +0400 Subject: [PATCH 286/593] add ability to set api url in template Signed-off-by: Vasiliy Tolstov --- builder/digitalocean/api.go | 17 ++++++++--------- builder/digitalocean/builder.go | 20 ++++++++++++++++---- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/builder/digitalocean/api.go b/builder/digitalocean/api.go index e9525c0bf..86798633f 100644 --- a/builder/digitalocean/api.go +++ b/builder/digitalocean/api.go @@ -8,7 +8,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/mitchellh/mapstructure" "io/ioutil" "log" "net/http" @@ -16,9 +15,9 @@ import ( "strconv" "strings" "time" -) -const DIGITALOCEAN_API_URL = "https://api.digitalocean.com" + "github.com/mitchellh/mapstructure" +) type Image struct { Id uint @@ -55,23 +54,23 @@ type DigitalOceanClient struct { // The http client for communicating client *http.Client - // The base URL of the API - BaseURL string - // Credentials ClientID string APIKey string + + // The base URL of the API + APIURL string } // Creates a new client for communicating with DO -func (d DigitalOceanClient) New(client string, key string) *DigitalOceanClient { +func (d DigitalOceanClient) New(client string, key string, url string) *DigitalOceanClient { c := &DigitalOceanClient{ client: &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, }, }, - BaseURL: DIGITALOCEAN_API_URL, + APIURL: url, ClientID: client, APIKey: key, } @@ -229,7 +228,7 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin params.Set("client_id", d.ClientID) params.Set("api_key", d.APIKey) - url := fmt.Sprintf("%s/%s?%s", DIGITALOCEAN_API_URL, path, params.Encode()) + url := fmt.Sprintf("%s/%s?%s", d.APIURL, path, params.Encode()) // Do some basic scrubbing so sensitive information doesn't appear in logs scrubbedUrl := strings.Replace(url, d.ClientID, "CLIENT_ID", -1) diff --git a/builder/digitalocean/builder.go b/builder/digitalocean/builder.go index 670f8ed77..282681636 100644 --- a/builder/digitalocean/builder.go +++ b/builder/digitalocean/builder.go @@ -6,13 +6,14 @@ package digitalocean import ( "errors" "fmt" + "log" + "os" + "time" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/packer" - "log" - "os" - "time" ) // see https://api.digitalocean.com/images/?client_id=[client_id]&api_key=[api_key] @@ -38,6 +39,7 @@ type config struct { ClientID string `mapstructure:"client_id"` APIKey string `mapstructure:"api_key"` + APIURL string `mapstructure:"api_url"` RegionID uint `mapstructure:"region_id"` SizeID uint `mapstructure:"size_id"` ImageID uint `mapstructure:"image_id"` @@ -94,6 +96,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.ClientID = os.Getenv("DIGITALOCEAN_CLIENT_ID") } + if b.config.APIURL == "" { + // Default to environment variable for api_url, if it exists + b.config.APIURL = os.Getenv("DIGITALOCEAN_API_URL") + } + if b.config.Region == "" { if b.config.RegionID != 0 { b.config.Region = fmt.Sprintf("%v", b.config.RegionID) @@ -153,6 +160,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { templates := map[string]*string{ "client_id": &b.config.ClientID, "api_key": &b.config.APIKey, + "api_url": &b.config.APIURL, "snapshot_name": &b.config.SnapshotName, "droplet_name": &b.config.DropletName, "ssh_username": &b.config.SSHUsername, @@ -175,6 +183,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs, errors.New("a client_id must be specified")) } + if b.config.APIURL == "" { + b.config.APIURL = "https://api.digitalocean.com" + } + if b.config.APIKey == "" { errs = packer.MultiErrorAppend( errs, errors.New("an api_key must be specified")) @@ -204,7 +216,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { // Initialize the DO API client - client := DigitalOceanClient{}.New(b.config.ClientID, b.config.APIKey) + client := DigitalOceanClient{}.New(b.config.ClientID, b.config.APIKey, b.config.APIURL) // Set up the state state := new(multistep.BasicStateBag) From e18f0f7f5b04a298d8fde881b850a617e498d903 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Thu, 28 Aug 2014 22:24:31 +0400 Subject: [PATCH 287/593] fix missing parts Signed-off-by: Vasiliy Tolstov --- builder/digitalocean/step_create_droplet.go | 3 ++- builder/digitalocean/step_create_ssh_key.go | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/builder/digitalocean/step_create_droplet.go b/builder/digitalocean/step_create_droplet.go index 79a235413..a00cecac9 100644 --- a/builder/digitalocean/step_create_droplet.go +++ b/builder/digitalocean/step_create_droplet.go @@ -2,6 +2,7 @@ package digitalocean import ( "fmt" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" ) @@ -53,7 +54,7 @@ func (s *stepCreateDroplet) Cleanup(state multistep.StateBag) { err := client.DestroyDroplet(s.dropletId) if err != nil { curlstr := fmt.Sprintf("curl '%v/droplets/%v/destroy?client_id=%v&api_key=%v'", - DIGITALOCEAN_API_URL, s.dropletId, c.ClientID, c.APIKey) + c.APIURL, s.dropletId, c.ClientID, c.APIKey) ui.Error(fmt.Sprintf( "Error destroying droplet. Please destroy it manually: %v", curlstr)) diff --git a/builder/digitalocean/step_create_ssh_key.go b/builder/digitalocean/step_create_ssh_key.go index e686b1ec7..cecb4f8d5 100644 --- a/builder/digitalocean/step_create_ssh_key.go +++ b/builder/digitalocean/step_create_ssh_key.go @@ -1,16 +1,17 @@ package digitalocean import ( - "code.google.com/p/gosshold/ssh" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" + "log" + + "code.google.com/p/gosshold/ssh" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/packer" - "log" ) type stepCreateSSHKey struct { @@ -78,7 +79,7 @@ func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) { err := client.DestroyKey(s.keyId) curlstr := fmt.Sprintf("curl '%v/ssh_keys/%v/destroy?client_id=%v&api_key=%v'", - DIGITALOCEAN_API_URL, s.keyId, c.ClientID, c.APIKey) + c.APIURL, s.keyId, c.ClientID, c.APIKey) if err != nil { log.Printf("Error cleaning up ssh key: %v", err.Error()) From bebca3bfae6637766a86a80ddbdea84e4358f949 Mon Sep 17 00:00:00 2001 From: Ulf Mansson Date: Fri, 29 Aug 2014 09:22:27 +0200 Subject: [PATCH 288/593] It's not necessary to use an instance-store image to build an instance-store image, see this example http://sorcery.smugmug.com/2014/01/29/instance-store-hvm-amis-for-amazon-ec2/ so there should be no restriction on the ExpectedRootDevice --- builder/amazon/instance/builder.go | 1 - 1 file changed, 1 deletion(-) diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index 7edd027de..5035c47f0 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -204,7 +204,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &awscommon.StepRunSourceInstance{ Debug: b.config.PackerDebug, - ExpectedRootDevice: "instance-store", InstanceType: b.config.InstanceType, IamInstanceProfile: b.config.IamInstanceProfile, UserData: b.config.UserData, From 845cdabe72df3f55a8d2a38096c4c680dc0e6681 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 1 Sep 2014 21:22:51 -0700 Subject: [PATCH 289/593] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b32cca755..47c11e90a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 0.6.2 (unreleased) +IMPROVEMENTS: + + * builder/amazon-instance: EBS AMIs can be used as a source. [GH-1453] + BUG FIXES: * builder/googlecompute: add `disk_size` option. [GH-1397] From 917060a597a8b3656813fc0a6863575e5f36af9d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 1 Sep 2014 21:24:21 -0700 Subject: [PATCH 290/593] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47c11e90a..0ea7e89ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ IMPROVEMENTS: * builder/amazon-instance: EBS AMIs can be used as a source. [GH-1453] + * builder/digitalocean: Can set API URL endpoint. [GH-1448] BUG FIXES: From a0a7457e2b05a26cf8ee9b2298c89380c8185826 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 1 Sep 2014 21:25:50 -0700 Subject: [PATCH 291/593] website: clean up layout --- website/source/layouts/docs.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 2296d8550..08b063cd2 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -69,7 +69,7 @@
  • Other

  • Core Configuration
  • Debugging
  • -
  • Environmental Variables for Packer
  • +
  • Environmental Variables
    • From 2bd41206b2fc3af59aa7902a07652b6c365423ca Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 1 Sep 2014 21:33:28 -0700 Subject: [PATCH 292/593] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ea7e89ad..b14a1f0de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ IMPROVEMENTS: BUG FIXES: + * builder/amazon-instance: Fix deprecation warning for `ec2-bundle-vol` + [GH-1424] * builder/googlecompute: add `disk_size` option. [GH-1397] * builder/parallels-iso: ISO not removed from VM after install [GH-1338] * builder/parallels/all: Add support for Parallels Desktop 10 [GH-1438] From 6b07803dfc4e14af089eea324c3f992ba7395b64 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 1 Sep 2014 21:36:39 -0700 Subject: [PATCH 293/593] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b14a1f0de..963ad2a3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ BUG FIXES: * builder/amazon-instance: Fix deprecation warning for `ec2-bundle-vol` [GH-1424] * builder/googlecompute: add `disk_size` option. [GH-1397] + * builder/openstack: Region is not required. [GH-1418] * builder/parallels-iso: ISO not removed from VM after install [GH-1338] * builder/parallels/all: Add support for Parallels Desktop 10 [GH-1438] * builder/parallels/all: Added some navigation keys [GH-1442] From 909d5ccb54d0b619f2d468ce00cb48db4b694a8f Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Fri, 22 Aug 2014 13:06:55 +0400 Subject: [PATCH 294/593] builder/parallels: 'ToolsIsoPath' added --- builder/parallels/common/driver.go | 3 +++ builder/parallels/common/driver_9.go | 25 +++++++++++++++++++++---- builder/parallels/common/driver_mock.go | 11 +++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/builder/parallels/common/driver.go b/builder/parallels/common/driver.go index ab9693461..0a276a6a1 100644 --- a/builder/parallels/common/driver.go +++ b/builder/parallels/common/driver.go @@ -27,6 +27,9 @@ type Driver interface { // Prlctl executes the given Prlctl command Prlctl(...string) error + // Get the path to the Parallels Tools ISO for the given flavor. + ToolsIsoPath(string) (string, error) + // Verify checks to make sure that this driver should function // properly. If there is any indication the driver can't function, // this will return an error. diff --git a/builder/parallels/common/driver_9.go b/builder/parallels/common/driver_9.go index 54e93a4bf..3bc608520 100644 --- a/builder/parallels/common/driver_9.go +++ b/builder/parallels/common/driver_9.go @@ -6,6 +6,7 @@ import ( "log" "os" "os/exec" + "path/filepath" "regexp" "strings" "time" @@ -73,16 +74,21 @@ func getConfigValueFromXpath(path, xpath string) (string, error) { // Finds an application bundle by identifier (for "darwin" platform only) func getAppPath(bundleId string) (string, error) { + var stdout bytes.Buffer + cmd := exec.Command("mdfind", "kMDItemCFBundleIdentifier ==", bundleId) - out, err := cmd.Output() - if err != nil { + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { return "", err } - if string(out) == "" { + + pathOutput := strings.TrimSpace(stdout.String()) + if pathOutput == "" { return "", fmt.Errorf( "Could not detect Parallels Desktop! Make sure it is properly installed.") } - return string(out), nil + + return pathOutput, nil } func (d *Parallels9Driver) IsRunning(name string) (bool, error) { @@ -251,3 +257,14 @@ func (d *Parallels9Driver) IpAddress(mac string) (string, error) { log.Printf("Found IP lease: %s for MAC address %s\n", ip, mac) return ip, nil } + +func (d *Parallels9Driver) ToolsIsoPath(k string) (string, error) { + appPath, err := getAppPath("com.parallels.desktop.console") + if err != nil { + return "", err + } + + toolsPath := filepath.Join(appPath, "Contents", "Resources", "Tools", "prl-tools-"+k+".iso") + log.Printf("Parallels Tools path: '%s'", toolsPath) + return toolsPath, nil +} diff --git a/builder/parallels/common/driver_mock.go b/builder/parallels/common/driver_mock.go index e54c5cd5a..acb3f9533 100644 --- a/builder/parallels/common/driver_mock.go +++ b/builder/parallels/common/driver_mock.go @@ -31,6 +31,11 @@ type DriverMock struct { SendKeyScanCodesCalls [][]string SendKeyScanCodesErrs []error + ToolsIsoPathCalled bool + ToolsIsoPathFlavor string + ToolsIsoPathResult string + ToolsIsoPathErr error + MacName string MacReturn string MacError error @@ -98,3 +103,9 @@ func (d *DriverMock) IpAddress(mac string) (string, error) { d.IpAddressMac = mac return d.IpAddressReturn, d.IpAddressError } + +func (d *DriverMock) ToolsIsoPath(flavor string) (string, error) { + d.ToolsIsoPathCalled = true + d.ToolsIsoPathFlavor = flavor + return d.ToolsIsoPathResult, d.ToolsIsoPathErr +} From 13b355eaf088d04abd3912ce33b2a45011b1976c Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Tue, 2 Sep 2014 11:53:21 +0400 Subject: [PATCH 295/593] builder/parallels: Added 'ToolsConfig' struct --- builder/parallels/common/tools_config.go | 78 +++++++++++ builder/parallels/common/tools_config_test.go | 123 ++++++++++++++++++ builder/parallels/common/tools_modes.go | 9 -- builder/parallels/iso/builder.go | 107 +++++---------- builder/parallels/iso/builder_test.go | 122 +++-------------- builder/parallels/pvm/config.go | 58 ++------- 6 files changed, 262 insertions(+), 235 deletions(-) create mode 100644 builder/parallels/common/tools_config.go create mode 100644 builder/parallels/common/tools_config_test.go delete mode 100644 builder/parallels/common/tools_modes.go diff --git a/builder/parallels/common/tools_config.go b/builder/parallels/common/tools_config.go new file mode 100644 index 000000000..19993fb77 --- /dev/null +++ b/builder/parallels/common/tools_config.go @@ -0,0 +1,78 @@ +package common + +import ( + "errors" + "fmt" + "github.com/mitchellh/packer/packer" + "text/template" +) + +// These are the different valid mode values for "parallels_tools_mode" which +// determine how guest additions are delivered to the guest. +const ( + ParallelsToolsModeDisable string = "disable" + ParallelsToolsModeAttach = "attach" + ParallelsToolsModeUpload = "upload" +) + +type ToolsConfig struct { + ParallelsToolsFlavor string `mapstructure:"parallels_tools_flavor"` + ParallelsToolsGuestPath string `mapstructure:"parallels_tools_guest_path"` + ParallelsToolsMode string `mapstructure:"parallels_tools_mode"` +} + +func (c *ToolsConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.ParallelsToolsMode == "" { + c.ParallelsToolsMode = ParallelsToolsModeUpload + } + + if c.ParallelsToolsGuestPath == "" { + c.ParallelsToolsGuestPath = "prl-tools-{{.Flavor}}.iso" + } + + templates := map[string]*string{ + "parallels_tools_flavor": &c.ParallelsToolsFlavor, + "parallels_tools_mode": &c.ParallelsToolsMode, + } + + var err error + errs := make([]error, 0) + for n, ptr := range templates { + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + if _, err := template.New("path").Parse(c.ParallelsToolsGuestPath); err != nil { + errs = append(errs, fmt.Errorf("parallels_tools_guest_path invalid: %s", err)) + } + + validMode := false + validModes := []string{ + ParallelsToolsModeDisable, + ParallelsToolsModeAttach, + ParallelsToolsModeUpload, + } + + for _, mode := range validModes { + if c.ParallelsToolsMode == mode { + validMode = true + break + } + } + + if !validMode { + errs = append(errs, + fmt.Errorf("parallels_tools_mode is invalid. Must be one of: %v", + validModes)) + } + + if c.ParallelsToolsFlavor == "" { + if c.ParallelsToolsMode != ParallelsToolsModeDisable { + errs = append(errs, errors.New("parallels_tools_flavor must be specified.")) + } + } + + return errs +} diff --git a/builder/parallels/common/tools_config_test.go b/builder/parallels/common/tools_config_test.go new file mode 100644 index 000000000..5f5a8a35d --- /dev/null +++ b/builder/parallels/common/tools_config_test.go @@ -0,0 +1,123 @@ +package common + +import ( + "testing" +) + +func testToolsConfig() *ToolsConfig { + return &ToolsConfig{ + ParallelsToolsFlavor: "foo", + ParallelsToolsGuestPath: "foo", + ParallelsToolsMode: "attach", + } +} + +func TestToolsConfigPrepare(t *testing.T) { + c := testToolsConfig() + errs := c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("bad err: %#v", errs) + } +} + +func TestToolsConfigPrepare_ParallelsToolsMode(t *testing.T) { + var c *ToolsConfig + var errs []error + + // Test default mode + c = testToolsConfig() + c.ParallelsToolsMode = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } + if c.ParallelsToolsMode != ParallelsToolsModeUpload { + t.Errorf("bad parallels tools mode: %s", c.ParallelsToolsMode) + } + + // Test another mode + c = testToolsConfig() + c.ParallelsToolsMode = "attach" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } + if c.ParallelsToolsMode != ParallelsToolsModeAttach { + t.Fatalf("bad mode: %s", c.ParallelsToolsMode) + } + + // Test invalid mode + c = testToolsConfig() + c.ParallelsToolsMode = "invalid_mode" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should have error") + } +} + +func TestToolsConfigPrepare_ParallelsToolsGuestPath(t *testing.T) { + var c *ToolsConfig + var errs []error + + // Test default path + c = testToolsConfig() + c.ParallelsToolsGuestPath = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } + if c.ParallelsToolsGuestPath == "" { + t.Fatal("should not be empty") + } + + // Test with a bad value + c = testToolsConfig() + c.ParallelsToolsGuestPath = "{{{nope}" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should have error") + } + + // Test with a good one + c = testToolsConfig() + c.ParallelsToolsGuestPath = "foo" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %s", errs) + } + + if c.ParallelsToolsGuestPath != "foo" { + t.Fatalf("bad guest path: %s", c.ParallelsToolsGuestPath) + } +} + +func TestToolsConfigPrepare_ParallelsToolsFlavor(t *testing.T) { + var c *ToolsConfig + var errs []error + + // Test with a default value + c = testToolsConfig() + c.ParallelsToolsFlavor = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should have error") + } + + // Test with an bad value + c = testToolsConfig() + c.ParallelsToolsMode = "attach" + c.ParallelsToolsFlavor = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should have error") + } + + // Test with a good one + c = testToolsConfig() + c.ParallelsToolsMode = "disable" + c.ParallelsToolsFlavor = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %s", errs) + } +} diff --git a/builder/parallels/common/tools_modes.go b/builder/parallels/common/tools_modes.go deleted file mode 100644 index 3523fbca7..000000000 --- a/builder/parallels/common/tools_modes.go +++ /dev/null @@ -1,9 +0,0 @@ -package common - -// These are the different valid mode values for "parallels_tools_mode" which -// determine how guest additions are delivered to the guest. -const ( - ParallelsToolsModeDisable string = "disable" - ParallelsToolsModeAttach = "attach" - ParallelsToolsModeUpload = "upload" -) diff --git a/builder/parallels/iso/builder.go b/builder/parallels/iso/builder.go index 0fa38d5fb..79022a0a7 100644 --- a/builder/parallels/iso/builder.go +++ b/builder/parallels/iso/builder.go @@ -22,32 +22,31 @@ type config struct { common.PackerConfig `mapstructure:",squash"` parallelscommon.FloppyConfig `mapstructure:",squash"` parallelscommon.OutputConfig `mapstructure:",squash"` + parallelscommon.PrlctlConfig `mapstructure:",squash"` + parallelscommon.PrlctlVersionConfig `mapstructure:",squash"` parallelscommon.RunConfig `mapstructure:",squash"` parallelscommon.ShutdownConfig `mapstructure:",squash"` parallelscommon.SSHConfig `mapstructure:",squash"` - parallelscommon.PrlctlConfig `mapstructure:",squash"` - parallelscommon.PrlctlVersionConfig `mapstructure:",squash"` - - BootCommand []string `mapstructure:"boot_command"` - DiskSize uint `mapstructure:"disk_size"` - ParallelsToolsMode string `mapstructure:"parallels_tools_mode"` - ParallelsToolsGuestPath string `mapstructure:"parallels_tools_guest_path"` - ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"` - GuestOSDistribution string `mapstructure:"guest_os_distribution"` - HardDriveInterface string `mapstructure:"hard_drive_interface"` - HostInterfaces []string `mapstructure:"host_interfaces"` - HTTPDir string `mapstructure:"http_directory"` - HTTPPortMin uint `mapstructure:"http_port_min"` - HTTPPortMax uint `mapstructure:"http_port_max"` - ISOChecksum string `mapstructure:"iso_checksum"` - ISOChecksumType string `mapstructure:"iso_checksum_type"` - ISOUrls []string `mapstructure:"iso_urls"` - VMName string `mapstructure:"vm_name"` + parallelscommon.ToolsConfig `mapstructure:",squash"` + + BootCommand []string `mapstructure:"boot_command"` + DiskSize uint `mapstructure:"disk_size"` + GuestOSDistribution string `mapstructure:"guest_os_distribution"` + HardDriveInterface string `mapstructure:"hard_drive_interface"` + HostInterfaces []string `mapstructure:"host_interfaces"` + HTTPDir string `mapstructure:"http_directory"` + HTTPPortMin uint `mapstructure:"http_port_min"` + HTTPPortMax uint `mapstructure:"http_port_max"` + ISOChecksum string `mapstructure:"iso_checksum"` + ISOChecksumType string `mapstructure:"iso_checksum_type"` + ISOUrls []string `mapstructure:"iso_urls"` + VMName string `mapstructure:"vm_name"` RawSingleISOUrl string `mapstructure:"iso_url"` // Deprecated parameters - GuestOSType string `mapstructure:"guest_os_type"` + GuestOSType string `mapstructure:"guest_os_type"` + ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"` tpl *packer.ConfigTemplate } @@ -71,28 +70,17 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend( errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...) - errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...) - errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) errs = packer.MultiErrorAppend(errs, b.config.PrlctlConfig.Prepare(b.config.tpl)...) errs = packer.MultiErrorAppend(errs, b.config.PrlctlVersionConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(b.config.tpl)...) warnings := make([]string, 0) if b.config.DiskSize == 0 { b.config.DiskSize = 40000 } - if b.config.ParallelsToolsMode == "" { - b.config.ParallelsToolsMode = "upload" - } - - if b.config.ParallelsToolsGuestPath == "" { - b.config.ParallelsToolsGuestPath = "prl-tools.iso" - } - - if b.config.ParallelsToolsHostPath == "" { - b.config.ParallelsToolsHostPath = "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso" - } - if b.config.HardDriveInterface == "" { b.config.HardDriveInterface = "sata" } @@ -120,16 +108,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // Errors templates := map[string]*string{ - "parallels_tools_mode": &b.config.ParallelsToolsMode, - "parallels_tools_host_path": &b.config.ParallelsToolsHostPath, - "parallels_tools_guest_path": &b.config.ParallelsToolsGuestPath, - "guest_os_distribution": &b.config.GuestOSDistribution, - "hard_drive_interface": &b.config.HardDriveInterface, - "http_directory": &b.config.HTTPDir, - "iso_checksum": &b.config.ISOChecksum, - "iso_checksum_type": &b.config.ISOChecksumType, - "iso_url": &b.config.RawSingleISOUrl, - "vm_name": &b.config.VMName, + "guest_os_distribution": &b.config.GuestOSDistribution, + "hard_drive_interface": &b.config.HardDriveInterface, + "http_directory": &b.config.HTTPDir, + "iso_checksum": &b.config.ISOChecksum, + "iso_checksum_type": &b.config.ISOChecksumType, + "iso_url": &b.config.RawSingleISOUrl, + "vm_name": &b.config.VMName, } for n, ptr := range templates { @@ -150,17 +135,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - validates := map[string]*string{ - "parallels_tools_guest_path": &b.config.ParallelsToolsGuestPath, - } - - for n, ptr := range validates { - if err := b.config.tpl.Validate(*ptr); err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Error parsing %s: %s", n, err)) - } - } - for i, command := range b.config.BootCommand { if err := b.config.tpl.Validate(command); err != nil { errs = packer.MultiErrorAppend(errs, @@ -217,25 +191,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - validMode := false - validModes := []string{ - parallelscommon.ParallelsToolsModeDisable, - parallelscommon.ParallelsToolsModeAttach, - parallelscommon.ParallelsToolsModeUpload, - } - - for _, mode := range validModes { - if b.config.ParallelsToolsMode == mode { - validMode = true - break - } - } - - if !validMode { - errs = packer.MultiErrorAppend(errs, - fmt.Errorf("parallels_tools_mode is invalid. Must be one of: %v", validModes)) - } - // Warnings if b.config.ISOChecksumType == "none" { warnings = append(warnings, @@ -249,6 +204,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { "will forcibly halt the virtual machine, which may result in data loss.") } + if b.config.ParallelsToolsHostPath != "" { + warnings = append(warnings, + "A 'parallels_tools_host_path' has been deprecated and not in use anymore\n"+ + "You can remove it from your Packer template.") + } + if errs != nil && len(errs.Errors) > 0 { return warnings, errs } diff --git a/builder/parallels/iso/builder_test.go b/builder/parallels/iso/builder_test.go index f20f544d6..ac5a3a87c 100644 --- a/builder/parallels/iso/builder_test.go +++ b/builder/parallels/iso/builder_test.go @@ -1,7 +1,6 @@ package iso import ( - "github.com/mitchellh/packer/builder/parallels/common" "github.com/mitchellh/packer/packer" "reflect" "testing" @@ -38,10 +37,6 @@ func TestBuilderPrepare_Defaults(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.ParallelsToolsMode != common.ParallelsToolsModeUpload { - t.Errorf("bad parallels tools mode: %s", b.config.ParallelsToolsMode) - } - if b.config.GuestOSDistribution != "other" { t.Errorf("bad guest OS distribution: %s", b.config.GuestOSDistribution) } @@ -83,107 +78,6 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { } } -func TestBuilderPrepare_ParallelsToolsMode(t *testing.T) { - var b Builder - config := testConfig() - - // test default mode - delete(config, "parallels_tools_mode") - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("bad err: %s", err) - } - - // Test another mode - config["parallels_tools_mode"] = "attach" - 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) - } - - if b.config.ParallelsToolsMode != common.ParallelsToolsModeAttach { - t.Fatalf("bad: %s", b.config.ParallelsToolsMode) - } - - // Test bad mode - config["parllels_tools_mode"] = "teleport" - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should error") - } -} - -func TestBuilderPrepare_ParallelsToolsGuestPath(t *testing.T) { - var b Builder - config := testConfig() - - delete(config, "parallesl_tools_guest_path") - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("bad err: %s", err) - } - - if b.config.ParallelsToolsGuestPath != "prl-tools.iso" { - t.Fatalf("bad: %s", b.config.ParallelsToolsGuestPath) - } - - config["parallels_tools_guest_path"] = "foo" - 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) - } - - if b.config.ParallelsToolsGuestPath != "foo" { - t.Fatalf("bad size: %s", b.config.ParallelsToolsGuestPath) - } -} - -func TestBuilderPrepare_ParallelsToolsHostPath(t *testing.T) { - var b Builder - config := testConfig() - - config["parallels_tools_host_path"] = "" - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("err: %s", err) - } - - if b.config.ParallelsToolsHostPath != "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso" { - t.Fatalf("bad: %s", b.config.ParallelsToolsHostPath) - } - - config["parallels_tools_host_path"] = "./prl-tools-lin.iso" - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Errorf("should not have error: %s", err) - } -} - func TestBuilderPrepare_HardDriveInterface(t *testing.T) { var b Builder config := testConfig() @@ -434,3 +328,19 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { t.Fatalf("bad: %#v", b.config.ISOUrls) } } + +func TestBuilderPrepare_ParallelsToolsHostPath(t *testing.T) { + var b Builder + config := testConfig() + delete(config, "parallels_tools_host_path") + + // Test that it is deprecated + config["parallels_tools_host_path"] = "/path/to/iso" + warns, err := b.Prepare(config) + if len(warns) == 0 { + t.Fatalf("should have warning") + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } +} diff --git a/builder/parallels/pvm/config.go b/builder/parallels/pvm/config.go index 84bc7fbdd..a3c20bb5d 100644 --- a/builder/parallels/pvm/config.go +++ b/builder/parallels/pvm/config.go @@ -13,18 +13,16 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` parallelscommon.FloppyConfig `mapstructure:",squash"` parallelscommon.OutputConfig `mapstructure:",squash"` + parallelscommon.PrlctlConfig `mapstructure:",squash"` + parallelscommon.PrlctlVersionConfig `mapstructure:",squash"` parallelscommon.RunConfig `mapstructure:",squash"` parallelscommon.SSHConfig `mapstructure:",squash"` parallelscommon.ShutdownConfig `mapstructure:",squash"` - parallelscommon.PrlctlConfig `mapstructure:",squash"` - parallelscommon.PrlctlVersionConfig `mapstructure:",squash"` + parallelscommon.ToolsConfig `mapstructure:",squash"` - BootCommand []string `mapstructure:"boot_command"` - ParallelsToolsMode string `mapstructure:"parallels_tools_mode"` - ParallelsToolsGuestPath string `mapstructure:"parallels_tools_guest_path"` - ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"` - SourcePath string `mapstructure:"source_path"` - VMName string `mapstructure:"vm_name"` + BootCommand []string `mapstructure:"boot_command"` + SourcePath string `mapstructure:"source_path"` + VMName string `mapstructure:"vm_name"` tpl *packer.ConfigTemplate } @@ -42,19 +40,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } c.tpl.UserVars = c.PackerUserVars - // Defaults - if c.ParallelsToolsMode == "" { - c.ParallelsToolsMode = "disable" - } - - if c.ParallelsToolsGuestPath == "" { - c.ParallelsToolsGuestPath = "prl-tools.iso" - } - - if c.ParallelsToolsHostPath == "" { - c.ParallelsToolsHostPath = "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso" - } - if c.VMName == "" { c.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", c.PackerBuildName) } @@ -63,18 +48,16 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs := common.CheckUnusedConfig(md) errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(c.tpl, &c.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, c.PrlctlConfig.Prepare(c.tpl)...) + errs = packer.MultiErrorAppend(errs, c.PrlctlVersionConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(c.tpl)...) - errs = packer.MultiErrorAppend(errs, c.PrlctlConfig.Prepare(c.tpl)...) - errs = packer.MultiErrorAppend(errs, c.PrlctlVersionConfig.Prepare(c.tpl)...) + errs = packer.MultiErrorAppend(errs, c.ToolsConfig.Prepare(c.tpl)...) templates := map[string]*string{ - "parallels_tools_mode": &c.ParallelsToolsMode, - "parallels_tools_host_paht": &c.ParallelsToolsHostPath, - "parallels_tools_guest_path": &c.ParallelsToolsGuestPath, - "source_path": &c.SourcePath, - "vm_name": &c.VMName, + "source_path": &c.SourcePath, + "vm_name": &c.VMName, } for n, ptr := range templates { @@ -93,25 +76,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } } - validMode := false - validModes := []string{ - parallelscommon.ParallelsToolsModeDisable, - parallelscommon.ParallelsToolsModeAttach, - parallelscommon.ParallelsToolsModeUpload, - } - - for _, mode := range validModes { - if c.ParallelsToolsMode == mode { - validMode = true - break - } - } - - if !validMode { - errs = packer.MultiErrorAppend(errs, - fmt.Errorf("parallels_tools_mode is invalid. Must be one of: %v", validModes)) - } - if c.SourcePath == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required")) } else { From 40c169f757adaf008fc0050e74a134f4eac81d21 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Tue, 2 Sep 2014 12:42:12 +0400 Subject: [PATCH 296/593] builder/parallels: Added 'StepPrepareParallelsTools' --- .../common/step_prepare_parallels_tools.go | 47 +++++++ .../step_prepare_parallels_tools_test.go | 115 ++++++++++++++++++ builder/parallels/iso/builder.go | 4 + builder/parallels/pvm/builder.go | 4 + 4 files changed, 170 insertions(+) create mode 100644 builder/parallels/common/step_prepare_parallels_tools.go create mode 100644 builder/parallels/common/step_prepare_parallels_tools_test.go diff --git a/builder/parallels/common/step_prepare_parallels_tools.go b/builder/parallels/common/step_prepare_parallels_tools.go new file mode 100644 index 000000000..752268fd7 --- /dev/null +++ b/builder/parallels/common/step_prepare_parallels_tools.go @@ -0,0 +1,47 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "os" +) + +// This step prepares parameters related to Parallels Tools. +// +// Uses: +// driver Driver +// +// Produces: +// parallels_tools_path string +type StepPrepareParallelsTools struct { + ParallelsToolsFlavor string + ParallelsToolsMode string +} + +func (s *StepPrepareParallelsTools) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + + if s.ParallelsToolsMode == ParallelsToolsModeDisable { + return multistep.ActionContinue + } + + path, err := driver.ToolsIsoPath(s.ParallelsToolsFlavor) + + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + if _, err := os.Stat(path); err != nil { + state.Put("error", fmt.Errorf( + "Couldn't find Parallels Tools for the '%s' flavor! Please, check the\n"+ + "value of 'parallels_tools_flavor'. Valid flavors are: 'win', 'lin',\n"+ + "'mac', 'os2' and 'other'", s.ParallelsToolsFlavor)) + return multistep.ActionHalt + } + + state.Put("parallels_tools_path", path) + return multistep.ActionContinue +} + +func (s *StepPrepareParallelsTools) Cleanup(multistep.StateBag) {} diff --git a/builder/parallels/common/step_prepare_parallels_tools_test.go b/builder/parallels/common/step_prepare_parallels_tools_test.go new file mode 100644 index 000000000..931ecb972 --- /dev/null +++ b/builder/parallels/common/step_prepare_parallels_tools_test.go @@ -0,0 +1,115 @@ +package common + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepPrepareParallelsTools_impl(t *testing.T) { + var _ multistep.Step = new(StepPrepareParallelsTools) +} + +func TestStepPrepareParallelsTools(t *testing.T) { + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + tf.Close() + defer os.Remove(tf.Name()) + + state := testState(t) + step := &StepPrepareParallelsTools{ + ParallelsToolsMode: "", + ParallelsToolsFlavor: "foo", + } + + driver := state.Get("driver").(*DriverMock) + + // Mock results + driver.ToolsIsoPathResult = tf.Name() + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if !driver.ToolsIsoPathCalled { + t.Fatal("tools iso path should be called") + } + if driver.ToolsIsoPathFlavor != "foo" { + t.Fatalf("bad: %#v", driver.ToolsIsoPathFlavor) + } + + // Test the resulting state + path, ok := state.GetOk("parallels_tools_path") + if !ok { + t.Fatal("should have parallels_tools_path") + } + if path != tf.Name() { + t.Fatalf("bad: %#v", path) + } +} + +func TestStepPrepareParallelsTools_disabled(t *testing.T) { + state := testState(t) + step := &StepPrepareParallelsTools{ + ParallelsToolsFlavor: "foo", + ParallelsToolsMode: ParallelsToolsModeDisable, + } + + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if driver.ToolsIsoPathCalled { + t.Fatal("tools iso path should NOT be called") + } +} + +func TestStepPrepareParallelsTools_nonExist(t *testing.T) { + state := testState(t) + step := &StepPrepareParallelsTools{ + ParallelsToolsFlavor: "foo", + ParallelsToolsMode: "", + } + + driver := state.Get("driver").(*DriverMock) + + // Mock results + driver.ToolsIsoPathResult = "foo" + + // Test the run + if action := step.Run(state); action != multistep.ActionHalt { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); !ok { + t.Fatal("should have error") + } + + // Test the driver + if !driver.ToolsIsoPathCalled { + t.Fatal("tools iso path should be called") + } + if driver.ToolsIsoPathFlavor != "foo" { + t.Fatalf("bad: %#v", driver.ToolsIsoPathFlavor) + } + + // Test the resulting state + if _, ok := state.GetOk("parallels_tools_path"); ok { + t.Fatal("should NOT have parallels_tools_path") + } +} diff --git a/builder/parallels/iso/builder.go b/builder/parallels/iso/builder.go index 79022a0a7..5fa8eca1f 100644 --- a/builder/parallels/iso/builder.go +++ b/builder/parallels/iso/builder.go @@ -225,6 +225,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe } steps := []multistep.Step{ + ¶llelscommon.StepPrepareParallelsTools{ + ParallelsToolsFlavor: b.config.ParallelsToolsFlavor, + ParallelsToolsMode: b.config.ParallelsToolsMode, + }, &common.StepDownload{ Checksum: b.config.ISOChecksum, ChecksumType: b.config.ISOChecksumType, diff --git a/builder/parallels/pvm/builder.go b/builder/parallels/pvm/builder.go index aabdc8d30..a7931024c 100644 --- a/builder/parallels/pvm/builder.go +++ b/builder/parallels/pvm/builder.go @@ -47,6 +47,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Build the steps. steps := []multistep.Step{ + ¶llelscommon.StepPrepareParallelsTools{ + ParallelsToolsMode: b.config.ParallelsToolsMode, + ParallelsToolsFlavor: b.config.ParallelsToolsFlavor, + }, ¶llelscommon.StepOutputDir{ Force: b.config.PackerForce, Path: b.config.OutputDir, From bed627028867f5744918ca5fd248fe359eebc46d Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Tue, 2 Sep 2014 13:28:04 +0400 Subject: [PATCH 297/593] builder/parallels: Detect path to Parallels Tools ISO automatically. 'parallels_tools_host_path' param is deprecated 'parallels_tools_flavor' is added (mandatory). --- .../common/step_attach_parallels_tools.go | 19 +++++++---- .../common/step_upload_parallels_tools.go | 32 ++++++++++++------- builder/parallels/iso/builder.go | 5 ++- builder/parallels/iso/builder_test.go | 11 ++++--- builder/parallels/pvm/builder.go | 5 ++- builder/parallels/pvm/config_test.go | 5 +-- 6 files changed, 46 insertions(+), 31 deletions(-) diff --git a/builder/parallels/common/step_attach_parallels_tools.go b/builder/parallels/common/step_attach_parallels_tools.go index 0d626c844..b80ddd87e 100644 --- a/builder/parallels/common/step_attach_parallels_tools.go +++ b/builder/parallels/common/step_attach_parallels_tools.go @@ -7,19 +7,19 @@ import ( "log" ) -// This step attaches the Parallels Tools as a inserted CD onto +// This step attaches the Parallels Tools as an inserted CD onto // the virtual machine. // // Uses: // driver Driver -// toolsPath string +// parallels_tools_path string // ui packer.Ui // vmName string // // Produces: +// attachedToolsIso boolean type StepAttachParallelsTools struct { - ParallelsToolsHostPath string - ParallelsToolsMode string + ParallelsToolsMode string } func (s *StepAttachParallelsTools) Run(state multistep.StateBag) multistep.StepAction { @@ -33,12 +33,15 @@ func (s *StepAttachParallelsTools) Run(state multistep.StateBag) multistep.StepA return multistep.ActionContinue } + // Get the Paralells Tools path on the host machine + parallelsToolsPath := state.Get("parallels_tools_path").(string) + // Attach the guest additions to the computer ui.Say("Attaching Parallels Tools ISO onto IDE controller...") command := []string{ "set", vmName, "--device-add", "cdrom", - "--image", s.ParallelsToolsHostPath, + "--image", parallelsToolsPath, } if err := driver.Prlctl(command...); err != nil { err := fmt.Errorf("Error attaching Parallels Tools: %s", err) @@ -59,6 +62,7 @@ func (s *StepAttachParallelsTools) Cleanup(state multistep.StateBag) { } driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) log.Println("Detaching Parallels Tools ISO...") @@ -71,5 +75,8 @@ func (s *StepAttachParallelsTools) Cleanup(state multistep.StateBag) { "set", vmName, "--device-del", cdDevice, } - driver.Prlctl(command...) + + if err := driver.Prlctl(command...); err != nil { + ui.Error(fmt.Sprintf("Error detaching Parallels Tools ISO: %s", err)) + } } diff --git a/builder/parallels/common/step_upload_parallels_tools.go b/builder/parallels/common/step_upload_parallels_tools.go index 374a8163e..9c421a22b 100644 --- a/builder/parallels/common/step_upload_parallels_tools.go +++ b/builder/parallels/common/step_upload_parallels_tools.go @@ -8,13 +8,21 @@ import ( "os" ) +// This step uploads the Parallels Tools ISO to the virtual machine. +// +// Uses: +// communicator packer.Communicator +// parallels_tools_path string +// ui packer.Ui +// +// Produces: type toolsPathTemplate struct { - Version string + Flavor string } // This step uploads the guest additions ISO to the VM. type StepUploadParallelsTools struct { - ParallelsToolsHostPath string + ParallelsToolsFlavor string ParallelsToolsGuestPath string ParallelsToolsMode string Tpl *packer.ConfigTemplate @@ -22,7 +30,6 @@ type StepUploadParallelsTools struct { func (s *StepUploadParallelsTools) Run(state multistep.StateBag) multistep.StepAction { comm := state.Get("communicator").(packer.Communicator) - driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) // If we're attaching then don't do this, since we attached. @@ -31,20 +38,18 @@ func (s *StepUploadParallelsTools) Run(state multistep.StateBag) multistep.StepA return multistep.ActionContinue } - version, err := driver.Version() - if err != nil { - state.Put("error", fmt.Errorf("Error reading version for Parallels Tools upload: %s", err)) - return multistep.ActionHalt - } + // Get the Paralells Tools path on the host machine + parallelsToolsPath := state.Get("parallels_tools_path").(string) - f, err := os.Open(s.ParallelsToolsHostPath) + f, err := os.Open(parallelsToolsPath) if err != nil { state.Put("error", fmt.Errorf("Error opening Parallels Tools ISO: %s", err)) return multistep.ActionHalt } + defer f.Close() tplData := &toolsPathTemplate{ - Version: version, + Flavor: s.ParallelsToolsFlavor, } s.ParallelsToolsGuestPath, err = s.Tpl.Process(s.ParallelsToolsGuestPath, tplData) @@ -55,9 +60,12 @@ func (s *StepUploadParallelsTools) Run(state multistep.StateBag) multistep.StepA return multistep.ActionHalt } - ui.Say("Uploading Parallels Tools ISO...") + ui.Say(fmt.Sprintf("Uploading Parallels Tools for '%s' to path: '%s'", + s.ParallelsToolsFlavor, s.ParallelsToolsGuestPath)) if err := comm.Upload(s.ParallelsToolsGuestPath, f); err != nil { - state.Put("error", fmt.Errorf("Error uploading Parallels Tools: %s", err)) + err := fmt.Errorf("Error uploading Parallels Tools: %s", err) + state.Put("error", err) + ui.Error(err.Error()) return multistep.ActionHalt } diff --git a/builder/parallels/iso/builder.go b/builder/parallels/iso/builder.go index 5fa8eca1f..980f2b5d3 100644 --- a/builder/parallels/iso/builder.go +++ b/builder/parallels/iso/builder.go @@ -248,8 +248,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe new(stepCreateDisk), new(stepAttachISO), ¶llelscommon.StepAttachParallelsTools{ - ParallelsToolsHostPath: b.config.ParallelsToolsHostPath, - ParallelsToolsMode: b.config.ParallelsToolsMode, + ParallelsToolsMode: b.config.ParallelsToolsMode, }, new(parallelscommon.StepAttachFloppy), ¶llelscommon.StepPrlctl{ @@ -275,8 +274,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Path: b.config.PrlctlVersionFile, }, ¶llelscommon.StepUploadParallelsTools{ + ParallelsToolsFlavor: b.config.ParallelsToolsFlavor, ParallelsToolsGuestPath: b.config.ParallelsToolsGuestPath, - ParallelsToolsHostPath: b.config.ParallelsToolsHostPath, ParallelsToolsMode: b.config.ParallelsToolsMode, Tpl: b.config.tpl, }, diff --git a/builder/parallels/iso/builder_test.go b/builder/parallels/iso/builder_test.go index ac5a3a87c..fce9e14a4 100644 --- a/builder/parallels/iso/builder_test.go +++ b/builder/parallels/iso/builder_test.go @@ -8,11 +8,12 @@ import ( func testConfig() map[string]interface{} { return map[string]interface{}{ - "iso_checksum": "foo", - "iso_checksum_type": "md5", - "iso_url": "http://www.google.com/", - "shutdown_command": "yes", - "ssh_username": "foo", + "iso_checksum": "foo", + "iso_checksum_type": "md5", + "iso_url": "http://www.google.com/", + "shutdown_command": "yes", + "ssh_username": "foo", + "parallels_tools_flavor": "lin", packer.BuildNameConfigKey: "foo", } diff --git a/builder/parallels/pvm/builder.go b/builder/parallels/pvm/builder.go index a7931024c..83ac73b12 100644 --- a/builder/parallels/pvm/builder.go +++ b/builder/parallels/pvm/builder.go @@ -63,8 +63,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SourcePath: b.config.SourcePath, }, ¶llelscommon.StepAttachParallelsTools{ - ParallelsToolsHostPath: b.config.ParallelsToolsHostPath, - ParallelsToolsMode: b.config.ParallelsToolsMode, + ParallelsToolsMode: b.config.ParallelsToolsMode, }, new(parallelscommon.StepAttachFloppy), ¶llelscommon.StepPrlctl{ @@ -90,8 +89,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Path: b.config.PrlctlVersionFile, }, ¶llelscommon.StepUploadParallelsTools{ + ParallelsToolsFlavor: b.config.ParallelsToolsFlavor, ParallelsToolsGuestPath: b.config.ParallelsToolsGuestPath, - ParallelsToolsHostPath: b.config.ParallelsToolsHostPath, ParallelsToolsMode: b.config.ParallelsToolsMode, Tpl: b.config.tpl, }, diff --git a/builder/parallels/pvm/config_test.go b/builder/parallels/pvm/config_test.go index 1fb15849b..c4270c366 100644 --- a/builder/parallels/pvm/config_test.go +++ b/builder/parallels/pvm/config_test.go @@ -8,8 +8,9 @@ import ( func testConfig(t *testing.T) map[string]interface{} { return map[string]interface{}{ - "ssh_username": "foo", - "shutdown_command": "foo", + "ssh_username": "foo", + "shutdown_command": "foo", + "parallels_tools_flavor": "lin", } } From 16cb6f60c8072f4f88b81fb19cc06cefa5c09b6d Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Tue, 2 Sep 2014 16:00:25 +0400 Subject: [PATCH 298/593] builder/parallels: "guest_os_distribution" renamed to "guest_os_type". "guest_os_distribution" become deprecated and is not required anymore. But if it is defined (in outdated templates),then the value will be copied to "guest_os_type" and warning will be displayed. --- builder/parallels/iso/builder.go | 54 +++++++++++++++---------- builder/parallels/iso/builder_test.go | 23 ++++++++++- builder/parallels/iso/step_create_vm.go | 2 +- 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/builder/parallels/iso/builder.go b/builder/parallels/iso/builder.go index 980f2b5d3..b177bb27d 100644 --- a/builder/parallels/iso/builder.go +++ b/builder/parallels/iso/builder.go @@ -29,23 +29,23 @@ type config struct { parallelscommon.SSHConfig `mapstructure:",squash"` parallelscommon.ToolsConfig `mapstructure:",squash"` - BootCommand []string `mapstructure:"boot_command"` - DiskSize uint `mapstructure:"disk_size"` - GuestOSDistribution string `mapstructure:"guest_os_distribution"` - HardDriveInterface string `mapstructure:"hard_drive_interface"` - HostInterfaces []string `mapstructure:"host_interfaces"` - HTTPDir string `mapstructure:"http_directory"` - HTTPPortMin uint `mapstructure:"http_port_min"` - HTTPPortMax uint `mapstructure:"http_port_max"` - ISOChecksum string `mapstructure:"iso_checksum"` - ISOChecksumType string `mapstructure:"iso_checksum_type"` - ISOUrls []string `mapstructure:"iso_urls"` - VMName string `mapstructure:"vm_name"` + BootCommand []string `mapstructure:"boot_command"` + DiskSize uint `mapstructure:"disk_size"` + GuestOSType string `mapstructure:"guest_os_type"` + HardDriveInterface string `mapstructure:"hard_drive_interface"` + HostInterfaces []string `mapstructure:"host_interfaces"` + HTTPDir string `mapstructure:"http_directory"` + HTTPPortMin uint `mapstructure:"http_port_min"` + HTTPPortMax uint `mapstructure:"http_port_max"` + ISOChecksum string `mapstructure:"iso_checksum"` + ISOChecksumType string `mapstructure:"iso_checksum_type"` + ISOUrls []string `mapstructure:"iso_urls"` + VMName string `mapstructure:"vm_name"` RawSingleISOUrl string `mapstructure:"iso_url"` // Deprecated parameters - GuestOSType string `mapstructure:"guest_os_type"` + GuestOSDistribution string `mapstructure:"guest_os_distribution"` ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"` tpl *packer.ConfigTemplate @@ -85,8 +85,18 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.HardDriveInterface = "sata" } - if b.config.GuestOSDistribution == "" { - b.config.GuestOSDistribution = "other" + if b.config.GuestOSType == "" { + b.config.GuestOSType = "other" + } + + if b.config.GuestOSDistribution != "" { + // Compatibility with older templates: + // Use value of 'guest_os_distribution' if it is defined. + b.config.GuestOSType = b.config.GuestOSDistribution + warnings = append(warnings, + "A 'guest_os_distribution' has been completely replaced with 'guest_os_type'\n"+ + "It is recommended to remove it and assign the previous value to 'guest_os_type'.\n"+ + "Run it to see all available values: `prlctl create x -d list` ") } if b.config.HTTPPortMin == 0 { @@ -108,13 +118,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // Errors templates := map[string]*string{ - "guest_os_distribution": &b.config.GuestOSDistribution, - "hard_drive_interface": &b.config.HardDriveInterface, - "http_directory": &b.config.HTTPDir, - "iso_checksum": &b.config.ISOChecksum, - "iso_checksum_type": &b.config.ISOChecksumType, - "iso_url": &b.config.RawSingleISOUrl, - "vm_name": &b.config.VMName, + "guest_os_type": &b.config.GuestOSType, + "hard_drive_interface": &b.config.HardDriveInterface, + "http_directory": &b.config.HTTPDir, + "iso_checksum": &b.config.ISOChecksum, + "iso_checksum_type": &b.config.ISOChecksumType, + "iso_url": &b.config.RawSingleISOUrl, + "vm_name": &b.config.VMName, } for n, ptr := range templates { diff --git a/builder/parallels/iso/builder_test.go b/builder/parallels/iso/builder_test.go index fce9e14a4..9c7f56f6b 100644 --- a/builder/parallels/iso/builder_test.go +++ b/builder/parallels/iso/builder_test.go @@ -38,8 +38,8 @@ func TestBuilderPrepare_Defaults(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.GuestOSDistribution != "other" { - t.Errorf("bad guest OS distribution: %s", b.config.GuestOSDistribution) + if b.config.GuestOSType != "other" { + t.Errorf("bad guest OS type: %s", b.config.GuestOSType) } if b.config.VMName != "packer-foo" { @@ -79,6 +79,25 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { } } +func TestBuilderPrepare_GuestOSType(t *testing.T) { + var b Builder + config := testConfig() + delete(config, "guest_os_distribution") + + // Test deprecated parameter + config["guest_os_distribution"] = "bolgenos" + warns, err := b.Prepare(config) + if len(warns) == 0 { + t.Fatalf("should have warning") + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + if b.config.GuestOSType != "bolgenos" { + t.Fatalf("bad: %s", b.config.GuestOSType) + } +} + func TestBuilderPrepare_HardDriveInterface(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/parallels/iso/step_create_vm.go b/builder/parallels/iso/step_create_vm.go index 1b2468cfe..9da4b678c 100644 --- a/builder/parallels/iso/step_create_vm.go +++ b/builder/parallels/iso/step_create_vm.go @@ -28,7 +28,7 @@ func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction { commands := make([][]string, 8) commands[0] = []string{ "create", name, - "--distribution", config.GuestOSDistribution, + "--distribution", config.GuestOSType, "--dst", path, "--vmtype", "vm", } From 394c83e56a9b0c87165cc14c11ae3cef57c867a2 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Tue, 2 Sep 2014 16:21:46 +0400 Subject: [PATCH 299/593] website: Changed Parallels builder docs --- .../docs/builders/parallels-iso.html.markdown | 48 +++++++------------ .../docs/builders/parallels-pvm.html.markdown | 29 +++++------ 2 files changed, 32 insertions(+), 45 deletions(-) diff --git a/website/source/docs/builders/parallels-iso.html.markdown b/website/source/docs/builders/parallels-iso.html.markdown index a79c96813..9f8cb4400 100644 --- a/website/source/docs/builders/parallels-iso.html.markdown +++ b/website/source/docs/builders/parallels-iso.html.markdown @@ -26,10 +26,11 @@ Ubuntu to self-install. Still, the example serves to show the basic configuratio
       {
         "type": "parallels-iso",
      -  "guest_os_type": "Ubuntu_64",
      +  "guest_os_type": "ubuntu",
         "iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.3-server-amd64.iso",
         "iso_checksum": "2cbe868812a871242cdcdd8f2fd6feb9",
         "iso_checksum_type": "md5",
      +  "parallels_tools_flavor": "lin"
         "ssh_username": "packer",
         "ssh_password": "packer",
         "ssh_wait_timeout": "30s",
      @@ -68,6 +69,10 @@ each category, the available options are alphabetized and described.
       * `ssh_username` (string) - The username to use to SSH into the machine
         once the OS is installed.
       
      +* `parallels_tools_flavor` (string) - The flavor of the Parallels Tools ISO to
      +  install into the VM. Valid values are "win", "lin", "mac", "os2" and "other".
      +  This can be ommited only if `parallels_tools_mode` is "disable".
      +
       ### Optional:
       
       * `boot_command` (array of strings) - This is an array of commands to type
      @@ -96,17 +101,10 @@ each category, the available options are alphabetized and described.
         characters (*, ?, and []) are allowed. Directory names are also allowed,
         which will add all the files found in the directory to the floppy.
       
      -* `guest_os_distribution` (string) - The guest OS distribution being
      -  installed. By default this is "other", but you can get dramatic
      -  performance improvements by setting this to the proper value. To
      -  view all available values for this run `prlctl create x --distribution list`.
      -  Setting the correct value hints to Parallels how to optimize the virtual
      -  hardware to work best with that operating system.
      -
       * `guest_os_type` (string) - The guest OS type being installed. By default
         this is "other", but you can get _dramatic_ performance improvements by
         setting this to the proper value. To view all available values for this
      -  run `prlctl create x --ostype list`. Setting the correct value hints to
      +  run `prlctl create x --distribution list`. Setting the correct value hints to
         Parallels Desktop how to optimize the virtual hardware to work best with
         that operating system.
       
      @@ -148,18 +146,14 @@ each category, the available options are alphabetized and described.
         By default this is "output-BUILDNAME" where "BUILDNAME" is the name
         of the build.
       
      -* `parallels_tools_guest_path` (string) - The path on the guest virtual machine
      -  where the Parallels tools ISO will be uploaded. By default this is
      -  "prl-tools.iso" which should upload into the login directory of the user.
      -  This is a configuration template where the `Version` variable is replaced
      -  with the prlctl version.
      +* `parallels_tools_guest_path` (string) - The path in the VM to upload Parallels
      +  Tools. This only takes effect if `parallels_tools_mode` is not "disable".
      +  This is a [configuration template](/docs/templates/configuration-templates.html)
      +  that has a single valid variable: `Flavor`, which will be the value of
      +  `parallels_tools_flavor`. By default the upload path is set to
      +  `prl-tools-{{.Flavor}}.iso`.
       
      -* `parallels_tools_host_path` (string) - The path to the Parallels Tools ISO to
      -  upload. By default the Parallels builder will use the "other" OS tools ISO from
      -  the Parallels installation:
      -  "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso"
      -
      -* `parallels_tools_mode` (string) - The method by which Parallels tools are
      +* `parallels_tools_mode` (string) - The method by which Parallels Tools are
         made available to the guest for installation. Valid options are "upload",
         "attach", or "disable". The functions of each of these should be
         self-explanatory. The default value is "upload".
      @@ -260,20 +254,12 @@ an Ubuntu 12.04 installer:
       ]
       
      -## Parallels Tools -After the virtual machine is up and the operating system is installed, Packer -uploads the Parallels Tools into the virtual machine. The path where they are -uploaded is controllable by `parallels_tools_path`, and defaults to -"prl-tools.iso". Without an absolute path, it is uploaded to the home directory -of the SSH user. Parallels Tools ISO's can be found in: -"/Applications/Parallels Desktop.app/Contents/Resources/Tools/" - ## prlctl Commands In order to perform extra customization of the virtual machine, a template can define extra calls to `prlctl` to perform. -[prlctl](http://download.parallels.com/desktop/v4/wl/docs/en/Parallels_Command_Line_Reference_Guide/) -is the command-line interface to Parallels. It can be used to do things such as -set RAM, CPUs, etc. +[prlctl](http://download.parallels.com/desktop/v9/ga/docs/en_US/Parallels%20Command%20Line%20Reference%20Guide.pdf) +is the command-line interface to Parallels Desktop. It can be used to configure +the virtual machine, such as set RAM, CPUs, etc. Extra `prlctl` commands are defined in the template in the `prlctl` section. An example is shown below that sets the memory and number of CPUs within the diff --git a/website/source/docs/builders/parallels-pvm.html.markdown b/website/source/docs/builders/parallels-pvm.html.markdown index 74a81e9d3..8b0624291 100644 --- a/website/source/docs/builders/parallels-pvm.html.markdown +++ b/website/source/docs/builders/parallels-pvm.html.markdown @@ -25,6 +25,7 @@ the settings here.
       {
         "type": "parallels-pvm",
      +  "parallels_tools_flavor": "lin"
         "source_path": "source.pvm",
         "ssh_username": "packer",
         "ssh_password": "packer",
      @@ -51,6 +52,10 @@ each category, the available options are alphabetized and described.
       * `ssh_username` (string) - The username to use to SSH into the machine
         once the OS is installed.
       
      +* `parallels_tools_flavor` (string) - The flavor of the Parallels Tools ISO to
      +  install into the VM. Valid values are "win", "lin", "mac", "os2" and "other".
      +  This can be ommited only if `parallels_tools_mode` is "disable".
      +
       ### Optional:
       
       * `boot_command` (array of strings) - This is an array of commands to type
      @@ -80,18 +85,14 @@ each category, the available options are alphabetized and described.
         By default this is "output-BUILDNAME" where "BUILDNAME" is the name
         of the build.
       
      -* `parallels_tools_guest_path` (string) - The path on the guest virtual machine
      -  where the Parallels tools ISO will be uploaded. By default this is
      -  "prl-tools.iso" which should upload into the login directory of the user.
      -  This is a configuration template where the `Version` variable is replaced
      -  with the prlctl version.
      -
      -* `parallels_tools_host_path` (string) - The path to the Parallels Tools ISO to
      -  upload. By default the Parallels builder will use the "other" OS tools ISO from
      -  the Parallels installation:
      -  "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso"
      +* `parallels_tools_guest_path` (string) - The path in the VM to upload Parallels
      +  Tools. This only takes effect if `parallels_tools_mode` is not "disable".
      +  This is a [configuration template](/docs/templates/configuration-templates.html)
      +  that has a single valid variable: `Flavor`, which will be the value of
      +  `parallels_tools_flavor`. By default the upload path is set to
      +  `prl-tools-{{.Flavor}}.iso`.
       
      -* `parallels_tools_mode` (string) - The method by which Parallels tools are
      +* `parallels_tools_mode` (string) - The method by which Parallels Tools are
         made available to the guest for installation. Valid options are "upload",
         "attach", or "disable". The functions of each of these should be
         self-explanatory. The default value is "upload".
      @@ -179,9 +180,9 @@ The available variables are:
       ## prlctl Commands
       In order to perform extra customization of the virtual machine, a template can
       define extra calls to `prlctl` to perform.
      -[prlctl](http://download.parallels.com/desktop/v4/wl/docs/en/Parallels_Command_Line_Reference_Guide/)
      -is the command-line interface to Parallels. It can be used to do things such as
      -set RAM, CPUs, etc.
      +[prlctl](http://download.parallels.com/desktop/v9/ga/docs/en_US/Parallels%20Command%20Line%20Reference%20Guide.pdf)
      +is the command-line interface to Parallels Desktop. It can be used to configure
      +the virtual machine, such as set RAM, CPUs, etc.
       
       Extra `prlctl` commands are defined in the template in the `prlctl` section.
       An example is shown below that sets the memory and number of CPUs within the
      
      From 1a6db1ff2908d253e969825dea738325ea340931 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 09:09:49 -0700
      Subject: [PATCH 300/593] Update CHANGELOG
      
      ---
       CHANGELOG.md | 1 +
       1 file changed, 1 insertion(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index 963ad2a3b..19316821a 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -4,6 +4,7 @@ IMPROVEMENTS:
       
         * builder/amazon-instance: EBS AMIs can be used as a source. [GH-1453]
         * builder/digitalocean: Can set API URL endpoint. [GH-1448]
      +  * builder/parallels-*: Path to tools ISO is calculated automatically. [GH-1455]
       
       BUG FIXES:
       
      
      From fbf675876d3eb6bf8f20f9c780f52bd23a9fe030 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 09:20:25 -0700
      Subject: [PATCH 301/593] Update CHANGELOG
      
      ---
       CHANGELOG.md | 3 ++-
       1 file changed, 2 insertions(+), 1 deletion(-)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index 19316821a..6f11d96f6 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -4,7 +4,7 @@ IMPROVEMENTS:
       
         * builder/amazon-instance: EBS AMIs can be used as a source. [GH-1453]
         * builder/digitalocean: Can set API URL endpoint. [GH-1448]
      -  * builder/parallels-*: Path to tools ISO is calculated automatically. [GH-1455]
      +  * builder/parallels/all Path to tools ISO is calculated automatically. [GH-1455]
       
       BUG FIXES:
       
      @@ -15,6 +15,7 @@ BUG FIXES:
         * builder/parallels-iso: ISO not removed from VM after install [GH-1338]
         * builder/parallels/all: Add support for Parallels Desktop 10 [GH-1438]
         * builder/parallels/all: Added some navigation keys [GH-1442]
      +  * builder/vmware/all: `ssh_host` accepts templates. [GH-1396]
         * builder/vmware/vmx: Do not re-add floppy disk files to VMX [GH-1361]
       
       ## 0.6.1 (July 20, 2014)
      
      From 9cf046d993d7ecbff5d92ca498f1ed7944dd71a0 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 09:25:26 -0700
      Subject: [PATCH 302/593] Update CHANGELOG
      
      ---
       CHANGELOG.md | 2 ++
       1 file changed, 2 insertions(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index 6f11d96f6..01405310a 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -5,6 +5,7 @@ IMPROVEMENTS:
         * builder/amazon-instance: EBS AMIs can be used as a source. [GH-1453]
         * builder/digitalocean: Can set API URL endpoint. [GH-1448]
         * builder/parallels/all Path to tools ISO is calculated automatically. [GH-1455]
      +  * builder/qemu: Can specify "none" acceleration type. [GH-1395]
       
       BUG FIXES:
       
      @@ -15,6 +16,7 @@ BUG FIXES:
         * builder/parallels-iso: ISO not removed from VM after install [GH-1338]
         * builder/parallels/all: Add support for Parallels Desktop 10 [GH-1438]
         * builder/parallels/all: Added some navigation keys [GH-1442]
      +  * builder/qemu: If headless, sdl display won't be used. [GH-1395]
         * builder/vmware/all: `ssh_host` accepts templates. [GH-1396]
         * builder/vmware/vmx: Do not re-add floppy disk files to VMX [GH-1361]
       
      
      From df8e2f4f84013e6ee2fae47cf03971c9f5c1473b Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 09:32:14 -0700
      Subject: [PATCH 303/593] Update CHANGELOG
      
      ---
       CHANGELOG.md | 1 +
       1 file changed, 1 insertion(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index 01405310a..96141fd2e 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -6,6 +6,7 @@ IMPROVEMENTS:
         * builder/digitalocean: Can set API URL endpoint. [GH-1448]
         * builder/parallels/all Path to tools ISO is calculated automatically. [GH-1455]
         * builder/qemu: Can specify "none" acceleration type. [GH-1395]
      +  * builder/qemu: Can specify "tcg" acceleration type. [GH-1395]
       
       BUG FIXES:
       
      
      From 4ef3874467c5fa977818bc2393e8d87c4fc8e6e9 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 09:35:59 -0700
      Subject: [PATCH 304/593] builder/virtualbox-*: seed RNG [GH-1386]
      
      ---
       CHANGELOG.md                      |  1 +
       builder/virtualbox/iso/builder.go | 10 ++++++++--
       builder/virtualbox/ovf/builder.go |  5 +++++
       3 files changed, 14 insertions(+), 2 deletions(-)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index 96141fd2e..d9fc8acd5 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -18,6 +18,7 @@ BUG FIXES:
         * builder/parallels/all: Add support for Parallels Desktop 10 [GH-1438]
         * builder/parallels/all: Added some navigation keys [GH-1442]
         * builder/qemu: If headless, sdl display won't be used. [GH-1395]
      +  * builder/virtualbox/all: Seed RNG to avoid same ports. [GH-1386]
         * builder/vmware/all: `ssh_host` accepts templates. [GH-1396]
         * builder/vmware/vmx: Do not re-add floppy disk files to VMX [GH-1361]
       
      diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go
      index f41118a45..a632ca694 100644
      --- a/builder/virtualbox/iso/builder.go
      +++ b/builder/virtualbox/iso/builder.go
      @@ -3,12 +3,15 @@ package iso
       import (
       	"errors"
       	"fmt"
      +	"log"
      +	"math/rand"
      +	"strings"
      +	"time"
      +
       	"github.com/mitchellh/multistep"
       	vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
       	"github.com/mitchellh/packer/common"
       	"github.com/mitchellh/packer/packer"
      -	"log"
      -	"strings"
       )
       
       const BuilderId = "mitchellh.virtualbox"
      @@ -254,6 +257,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
       }
       
       func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
      +	// Seed the random number generator
      +	rand.Seed(time.Now().UTC().UnixNano())
      +
       	// Create the driver that we'll use to communicate with VirtualBox
       	driver, err := vboxcommon.NewDriver()
       	if err != nil {
      diff --git a/builder/virtualbox/ovf/builder.go b/builder/virtualbox/ovf/builder.go
      index 312fc4701..398b34a7a 100644
      --- a/builder/virtualbox/ovf/builder.go
      +++ b/builder/virtualbox/ovf/builder.go
      @@ -4,6 +4,8 @@ import (
       	"errors"
       	"fmt"
       	"log"
      +	"math/rand"
      +	"time"
       
       	"github.com/mitchellh/multistep"
       	vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
      @@ -32,6 +34,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
       // Run executes a Packer build and returns a packer.Artifact representing
       // a VirtualBox appliance.
       func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
      +	// Seed the random number generator
      +	rand.Seed(time.Now().UTC().UnixNano())
      +
       	// Create the driver that we'll use to communicate with VirtualBox
       	driver, err := vboxcommon.NewDriver()
       	if err != nil {
      
      From 02091537a8be686072a2d1821e9459d7ff839121 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 09:46:46 -0700
      Subject: [PATCH 305/593] Update CHANGELOG
      
      ---
       CHANGELOG.md | 1 +
       1 file changed, 1 insertion(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index d9fc8acd5..f4547a31d 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -10,6 +10,7 @@ IMPROVEMENTS:
       
       BUG FIXES:
       
      +  * builder/amazon-chroot: Fix crash in root device check. [GH-1360]
         * builder/amazon-instance: Fix deprecation warning for `ec2-bundle-vol`
             [GH-1424]
         * builder/googlecompute: add `disk_size` option. [GH-1397]
      
      From c9264b22c1b885bb98eca3bbd31aa2711059e106 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 09:48:14 -0700
      Subject: [PATCH 306/593] Update CHANGELOG
      
      ---
       CHANGELOG.md | 1 +
       1 file changed, 1 insertion(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index f4547a31d..c6efed0d0 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -10,6 +10,7 @@ IMPROVEMENTS:
       
       BUG FIXES:
       
      +  * builder/amazon-chroot: Can properly build HVM images now. [GH-1360]
         * builder/amazon-chroot: Fix crash in root device check. [GH-1360]
         * builder/amazon-instance: Fix deprecation warning for `ec2-bundle-vol`
             [GH-1424]
      
      From 551a7747874b32621f1ff0c18bbc239800ca249f Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 09:51:49 -0700
      Subject: [PATCH 307/593] Update CHANGELOG
      
      ---
       CHANGELOG.md | 1 +
       1 file changed, 1 insertion(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index c6efed0d0..ec709133e 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -23,6 +23,7 @@ BUG FIXES:
         * builder/virtualbox/all: Seed RNG to avoid same ports. [GH-1386]
         * builder/vmware/all: `ssh_host` accepts templates. [GH-1396]
         * builder/vmware/vmx: Do not re-add floppy disk files to VMX [GH-1361]
      +  * provisioner/ansible-local: Use proper path on Windows. [GH-1375]
       
       ## 0.6.1 (July 20, 2014)
       
      
      From 67bd0a38ee52e80f91e4618c2a54e373c091af59 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 09:53:26 -0700
      Subject: [PATCH 308/593] Update CHANGELOG
      
      ---
       CHANGELOG.md | 1 +
       1 file changed, 1 insertion(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index ec709133e..6c4d76d9c 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -23,6 +23,7 @@ BUG FIXES:
         * builder/virtualbox/all: Seed RNG to avoid same ports. [GH-1386]
         * builder/vmware/all: `ssh_host` accepts templates. [GH-1396]
         * builder/vmware/vmx: Do not re-add floppy disk files to VMX [GH-1361]
      +  * post-processor/vagrant: Can supply your own metadata.json. [GH-1143]
         * provisioner/ansible-local: Use proper path on Windows. [GH-1375]
       
       ## 0.6.1 (July 20, 2014)
      
      From 7c11d2a313a5f9516dd415bf59d93fc92cfe9844 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 09:54:55 -0700
      Subject: [PATCH 309/593] post-processor/vagrant: style
      
      ---
       post-processor/vagrant/util.go | 1 +
       1 file changed, 1 insertion(+)
      
      diff --git a/post-processor/vagrant/util.go b/post-processor/vagrant/util.go
      index 6c558a1c4..a9e745fe6 100644
      --- a/post-processor/vagrant/util.go
      +++ b/post-processor/vagrant/util.go
      @@ -140,5 +140,6 @@ func WriteMetadata(dir string, contents interface{}) error {
       		enc := json.NewEncoder(f)
       		return enc.Encode(contents)
       	}
      +
       	return nil
       }
      
      From 752162c234ff6a4f75ce6ec95d0c8b5ea94af74c Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 09:55:06 -0700
      Subject: [PATCH 310/593] fmt
      
      ---
       builder/amazon/chroot/step_register_ami_test.go | 2 +-
       builder/googlecompute/step_create_instance.go   | 2 +-
       builder/qemu/step_run.go                        | 2 +-
       3 files changed, 3 insertions(+), 3 deletions(-)
      
      diff --git a/builder/amazon/chroot/step_register_ami_test.go b/builder/amazon/chroot/step_register_ami_test.go
      index f85106a8d..393b95c8b 100644
      --- a/builder/amazon/chroot/step_register_ami_test.go
      +++ b/builder/amazon/chroot/step_register_ami_test.go
      @@ -1,8 +1,8 @@
       package chroot
       
       import (
      -	"testing"
       	"github.com/mitchellh/goamz/ec2"
      +	"testing"
       )
       
       func testImage() ec2.Image {
      diff --git a/builder/googlecompute/step_create_instance.go b/builder/googlecompute/step_create_instance.go
      index d35dc4157..7e6a68973 100644
      --- a/builder/googlecompute/step_create_instance.go
      +++ b/builder/googlecompute/step_create_instance.go
      @@ -16,7 +16,7 @@ type StepCreateInstance struct {
       	instanceName string
       }
       
      -func (config *Config) getImage() (Image) {
      +func (config *Config) getImage() Image {
       	project := config.ProjectId
       	if config.SourceImageProjectId != "" {
       		project = config.SourceImageProjectId
      diff --git a/builder/qemu/step_run.go b/builder/qemu/step_run.go
      index 176697670..5520a0395 100644
      --- a/builder/qemu/step_run.go
      +++ b/builder/qemu/step_run.go
      @@ -92,7 +92,7 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
       		defaultArgs["-machine"] += fmt.Sprintf(",accel=%s", config.Accelerator)
       	} else {
       		ui.Message("WARNING: The VM will be started with no hardware acceleration.\n" +
      -		"The installation may take considerably longer to finish.\n")
      +			"The installation may take considerably longer to finish.\n")
       	}
       
       	// Determine if we have a floppy disk to attach
      
      From 893b79c4233cd4db7cb61825cd3c2a71d70ed713 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 10:02:39 -0700
      Subject: [PATCH 311/593] Update CHANGELOG
      
      ---
       CHANGELOG.md | 1 +
       1 file changed, 1 insertion(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index 6c4d76d9c..fe7fd74ee 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -22,6 +22,7 @@ BUG FIXES:
         * builder/qemu: If headless, sdl display won't be used. [GH-1395]
         * builder/virtualbox/all: Seed RNG to avoid same ports. [GH-1386]
         * builder/vmware/all: `ssh_host` accepts templates. [GH-1396]
      +  * builder/vmware/all: Don't remount floppy in VMX post step. [GH-1239]
         * builder/vmware/vmx: Do not re-add floppy disk files to VMX [GH-1361]
         * post-processor/vagrant: Can supply your own metadata.json. [GH-1143]
         * provisioner/ansible-local: Use proper path on Windows. [GH-1375]
      
      From 2a9cb50b17ba447d086da394bae8c6e1de12ba8d Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 10:17:52 -0700
      Subject: [PATCH 312/593] builder/amazon: fix broken build
      
      ---
       builder/amazon/common/run_config.go | 24 ------------------------
       1 file changed, 24 deletions(-)
      
      diff --git a/builder/amazon/common/run_config.go b/builder/amazon/common/run_config.go
      index ea97af41e..d7dd6ec99 100644
      --- a/builder/amazon/common/run_config.go
      +++ b/builder/amazon/common/run_config.go
      @@ -112,30 +112,6 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
       		}
       	}
       
      -	templates := map[string]*string{
      -		"iam_instance_profile":    &c.IamInstanceProfile,
      -		"instance_type":           &c.InstanceType,
      -		"ssh_timeout":             &c.RawSSHTimeout,
      -		"ssh_username":            &c.SSHUsername,
      -		"ssh_private_key_file":    &c.SSHPrivateKeyFile,
      -		"source_ami":              &c.SourceAmi,
      -		"subnet_id":               &c.SubnetId,
      -		"temporary_key_pair_name": &c.TemporaryKeyPairName,
      -		"vpc_id":                  &c.VpcId,
      -		"availability_zone":       &c.AvailabilityZone,
      -		"user_data":               &c.UserData,
      -		"user_data_file":          &c.UserDataFile,
      -	}
      -
      -	for n, ptr := range templates {
      -		var err error
      -		*ptr, err = t.Process(*ptr, nil)
      -		if err != nil {
      -			errs = append(
      -				errs, fmt.Errorf("Error processing %s: %s", n, err))
      -		}
      -	}
      -
       	sliceTemplates := map[string][]string{
       		"security_group_ids": c.SecurityGroupIds,
       	}
      
      From 01193bf45f7b5295202872f1430b058da6e84d6a Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 10:57:28 -0700
      Subject: [PATCH 313/593] Update CHANGELOG
      
      ---
       CHANGELOG.md | 1 +
       1 file changed, 1 insertion(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index fe7fd74ee..c5a9bd8be 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -7,6 +7,7 @@ IMPROVEMENTS:
         * builder/parallels/all Path to tools ISO is calculated automatically. [GH-1455]
         * builder/qemu: Can specify "none" acceleration type. [GH-1395]
         * builder/qemu: Can specify "tcg" acceleration type. [GH-1395]
      +  * builder/virtualbox/all: `iso_interface` option to mount ISO with SATA. [GH-1200]
       
       BUG FIXES:
       
      
      From 35b51c8492a69bc1ae55d0394912a7f34e3a4d7a Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 11:08:49 -0700
      Subject: [PATCH 314/593] Update CHANGELOG
      
      ---
       CHANGELOG.md | 4 ++++
       1 file changed, 4 insertions(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index c5a9bd8be..358b1b79c 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -1,5 +1,9 @@
       ## 0.6.2 (unreleased)
       
      +FEATURES:
      +
      +  * builder/vmware: VMware Player 6 is now supported. [GH-1168]
      +
       IMPROVEMENTS:
       
         * builder/amazon-instance: EBS AMIs can be used as a source. [GH-1453]
      
      From 07802250378145e7590792eefd0c52a2402e3313 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 11:10:06 -0700
      Subject: [PATCH 315/593] Update CHANGELOG
      
      ---
       CHANGELOG.md | 1 +
       1 file changed, 1 insertion(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index 358b1b79c..d698fd12d 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -12,6 +12,7 @@ IMPROVEMENTS:
         * builder/qemu: Can specify "none" acceleration type. [GH-1395]
         * builder/qemu: Can specify "tcg" acceleration type. [GH-1395]
         * builder/virtualbox/all: `iso_interface` option to mount ISO with SATA. [GH-1200]
      +  * builder/vmware-vmx: Proper `floppy_files` support. [GH-1057]
       
       BUG FIXES:
       
      
      From ae276fd833f25f5db890d0af39822d01da0cb57e Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 11:11:58 -0700
      Subject: [PATCH 316/593] Next version will be 0.7.0 (update changelog)
      
      ---
       CHANGELOG.md | 2 +-
       1 file changed, 1 insertion(+), 1 deletion(-)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index d698fd12d..cad724715 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -1,4 +1,4 @@
      -## 0.6.2 (unreleased)
      +## 0.7.0 (unreleased)
       
       FEATURES:
       
      
      From 90d4bcdbe81ce2debeca67e18a4d1c175f76fe53 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 11:16:39 -0700
      Subject: [PATCH 317/593] builder/digitalocean: region supports vars [GH-1452]
      
      ---
       CHANGELOG.md                    | 1 +
       builder/digitalocean/builder.go | 3 +++
       2 files changed, 4 insertions(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index cad724715..9d17f7d99 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -8,6 +8,7 @@ IMPROVEMENTS:
       
         * builder/amazon-instance: EBS AMIs can be used as a source. [GH-1453]
         * builder/digitalocean: Can set API URL endpoint. [GH-1448]
      +  * builder/digitalocean: Region supports variables. [GH-1452]
         * builder/parallels/all Path to tools ISO is calculated automatically. [GH-1455]
         * builder/qemu: Can specify "none" acceleration type. [GH-1395]
         * builder/qemu: Can specify "tcg" acceleration type. [GH-1395]
      diff --git a/builder/digitalocean/builder.go b/builder/digitalocean/builder.go
      index 282681636..73a72252d 100644
      --- a/builder/digitalocean/builder.go
      +++ b/builder/digitalocean/builder.go
      @@ -158,6 +158,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
       	}
       
       	templates := map[string]*string{
      +		"region":        &b.config.Region,
      +		"size":          &b.config.Size,
      +		"image":         &b.config.Image,
       		"client_id":     &b.config.ClientID,
       		"api_key":       &b.config.APIKey,
       		"api_url":       &b.config.APIURL,
      
      From 6bbf64c5ab697801376286998c1f5ee70738458f Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 11:19:12 -0700
      Subject: [PATCH 318/593] builder/qemu: use 512M for -m [GH-1444]
      
      ---
       CHANGELOG.md             | 1 +
       builder/qemu/step_run.go | 2 +-
       2 files changed, 2 insertions(+), 1 deletion(-)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index 9d17f7d99..31d2ca3d2 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -27,6 +27,7 @@ BUG FIXES:
         * builder/parallels/all: Add support for Parallels Desktop 10 [GH-1438]
         * builder/parallels/all: Added some navigation keys [GH-1442]
         * builder/qemu: If headless, sdl display won't be used. [GH-1395]
      +  * builder/qemu: Use `512M` as `-m` default. [GH-1444]
         * builder/virtualbox/all: Seed RNG to avoid same ports. [GH-1386]
         * builder/vmware/all: `ssh_host` accepts templates. [GH-1396]
         * builder/vmware/all: Don't remount floppy in VMX post step. [GH-1239]
      diff --git a/builder/qemu/step_run.go b/builder/qemu/step_run.go
      index 5520a0395..6d4b43d6e 100644
      --- a/builder/qemu/step_run.go
      +++ b/builder/qemu/step_run.go
      @@ -83,7 +83,7 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
       	defaultArgs["-drive"] = fmt.Sprintf("file=%s,if=%s", imgPath, config.DiskInterface)
       	defaultArgs["-cdrom"] = isoPath
       	defaultArgs["-boot"] = bootDrive
      -	defaultArgs["-m"] = "512m"
      +	defaultArgs["-m"] = "512M"
       	defaultArgs["-redir"] = fmt.Sprintf("tcp:%v::22", sshHostPort)
       	defaultArgs["-vnc"] = vnc
       
      
      From 062e86e21894965ba78905e305ad8abae63a2529 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 14:05:22 -0700
      Subject: [PATCH 319/593] packer/rpc: MuxBroker
      
      ---
       packer/rpc/mux_broker.go      | 160 ++++++++++++++++++++++++++++++++++
       packer/rpc/mux_broker_test.go |  82 +++++++++++++++++
       2 files changed, 242 insertions(+)
       create mode 100644 packer/rpc/mux_broker.go
       create mode 100644 packer/rpc/mux_broker_test.go
      
      diff --git a/packer/rpc/mux_broker.go b/packer/rpc/mux_broker.go
      new file mode 100644
      index 000000000..2e1061c9d
      --- /dev/null
      +++ b/packer/rpc/mux_broker.go
      @@ -0,0 +1,160 @@
      +package rpc
      +
      +import (
      +	"encoding/binary"
      +	"fmt"
      +	"net"
      +	"sync"
      +	"time"
      +
      +	"github.com/hashicorp/yamux"
      +)
      +
      +// muxBroker is responsible for brokering multiplexed connections by unique ID.
      +//
      +// This allows a plugin to request a channel with a specific ID to connect to
      +// or accept a connection from, and the broker handles the details of
      +// holding these channels open while they're being negotiated.
      +type muxBroker struct {
      +	session *yamux.Session
      +	streams map[uint32]*muxBrokerPending
      +
      +	sync.Mutex
      +}
      +
      +type muxBrokerPending struct {
      +	ch     chan net.Conn
      +	doneCh chan struct{}
      +}
      +
      +func newMuxBroker(s *yamux.Session) *muxBroker {
      +	return &muxBroker{
      +		session: s,
      +		streams: make(map[uint32]*muxBrokerPending),
      +	}
      +}
      +
      +// Accept accepts a connection by ID.
      +//
      +// This should not be called multiple times with the same ID at one time.
      +func (m *muxBroker) Accept(id uint32) (net.Conn, error) {
      +	var c net.Conn
      +	p := m.getStream(id)
      +	select {
      +	case c = <-p.ch:
      +		close(p.doneCh)
      +	case <-time.After(5 * time.Second):
      +		m.Lock()
      +		defer m.Unlock()
      +		delete(m.streams, id)
      +
      +		return nil, fmt.Errorf("timeout waiting for accept")
      +	}
      +
      +	// Ack our connection
      +	if err := binary.Write(c, binary.LittleEndian, id); err != nil {
      +		c.Close()
      +		return nil, err
      +	}
      +
      +	return c, nil
      +}
      +
      +// Dial opens a connection by ID.
      +func (m *muxBroker) Dial(id uint32) (net.Conn, error) {
      +	// Open the stream
      +	stream, err := m.session.OpenStream()
      +	if err != nil {
      +		return nil, err
      +	}
      +
      +	// Write the stream ID onto the wire.
      +	if err := binary.Write(stream, binary.LittleEndian, id); err != nil {
      +		stream.Close()
      +		return nil, err
      +	}
      +
      +	// Read the ack that we connected. Then we're off!
      +	var ack uint32
      +	if err := binary.Read(stream, binary.LittleEndian, &ack); err != nil {
      +		stream.Close()
      +		return nil, err
      +	}
      +	if ack != id {
      +		stream.Close()
      +		return nil, fmt.Errorf("bad ack: %d (expected %d)", ack, id)
      +	}
      +
      +	return stream, nil
      +}
      +
      +// Run starts the brokering and should be executed in a goroutine, since it
      +// blocks forever, or until the session closes.
      +func (m *muxBroker) Run() {
      +	for {
      +		stream, err := m.session.AcceptStream()
      +		if err != nil {
      +			// Once we receive an error, just exit
      +			break
      +		}
      +
      +		// Read the stream ID from the stream
      +		var id uint32
      +		if err := binary.Read(stream, binary.LittleEndian, &id); err != nil {
      +			stream.Close()
      +			continue
      +		}
      +
      +		// Initialize the waiter
      +		p := m.getStream(id)
      +		select {
      +		case p.ch <- stream:
      +		default:
      +		}
      +
      +		// Wait for a timeout
      +		go m.timeoutWait(id, p)
      +	}
      +}
      +
      +func (m *muxBroker) getStream(id uint32) *muxBrokerPending {
      +	m.Lock()
      +	defer m.Unlock()
      +
      +	p, ok := m.streams[id]
      +	if ok {
      +		return p
      +	}
      +
      +	m.streams[id] = &muxBrokerPending{
      +		ch:     make(chan net.Conn, 1),
      +		doneCh: make(chan struct{}),
      +	}
      +	return m.streams[id]
      +}
      +
      +func (m *muxBroker) timeoutWait(id uint32, p *muxBrokerPending) {
      +	// Wait for the stream to either be picked up and connected, or
      +	// for a timeout.
      +	timeout := false
      +	select {
      +	case <-p.doneCh:
      +	case <-time.After(5 * time.Second):
      +		timeout = true
      +	}
      +
      +	m.Lock()
      +	defer m.Unlock()
      +
      +	// Delete the stream so no one else can grab it
      +	delete(m.streams, id)
      +
      +	// If we timed out, then check if we have a channel in the buffer,
      +	// and if so, close it.
      +	if timeout {
      +		select {
      +		case s := <-p.ch:
      +			s.Close()
      +		}
      +	}
      +}
      diff --git a/packer/rpc/mux_broker_test.go b/packer/rpc/mux_broker_test.go
      new file mode 100644
      index 000000000..88739a0ff
      --- /dev/null
      +++ b/packer/rpc/mux_broker_test.go
      @@ -0,0 +1,82 @@
      +package rpc
      +
      +import (
      +	"net"
      +	"testing"
      +
      +	"github.com/hashicorp/yamux"
      +)
      +
      +func TestMuxBroker(t *testing.T) {
      +	c, s := testYamux(t)
      +	defer c.Close()
      +	defer s.Close()
      +
      +	bc := newMuxBroker(c)
      +	bs := newMuxBroker(s)
      +	go bc.Run()
      +	go bs.Run()
      +
      +	go func() {
      +		c, err := bc.Dial(5)
      +		if err != nil {
      +			t.Fatalf("err: %s", err)
      +		}
      +
      +		if _, err := c.Write([]byte{42}); err != nil {
      +			t.Fatalf("err: %s", err)
      +		}
      +	}()
      +
      +	client, err := bs.Accept(5)
      +	if err != nil {
      +		t.Fatalf("err: %s", err)
      +	}
      +
      +	var data [1]byte
      +	if _, err := client.Read(data[:]); err != nil {
      +		t.Fatalf("err: %s", err)
      +	}
      +
      +	if data[0] != 42 {
      +		t.Fatalf("bad: %d", data[0])
      +	}
      +}
      +
      +func testYamux(t *testing.T) (client *yamux.Session, server *yamux.Session) {
      +	l, err := net.Listen("tcp", "127.0.0.1:0")
      +	if err != nil {
      +		t.Fatalf("err: %s", err)
      +	}
      +
      +	// Server side
      +	doneCh := make(chan struct{})
      +	go func() {
      +		defer close(doneCh)
      +		conn, err := l.Accept()
      +		l.Close()
      +		if err != nil {
      +			t.Fatalf("err: %s", err)
      +		}
      +
      +		server, err = yamux.Server(conn, nil)
      +		if err != nil {
      +			t.Fatalf("err: %s", err)
      +		}
      +	}()
      +
      +	// Client side
      +	conn, err := net.Dial("tcp", l.Addr().String())
      +	if err != nil {
      +		t.Fatalf("err: %s", err)
      +	}
      +	client, err = yamux.Client(conn, nil)
      +	if err != nil {
      +		t.Fatalf("err: %s", err)
      +	}
      +
      +	// Wait for the server
      +	<-doneCh
      +
      +	return
      +}
      
      From 9ffa0b8e25dfc4b0b68de681c08ab40b010d0318 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 14:23:06 -0700
      Subject: [PATCH 320/593] packer/rpc: no more muxconn
      
      ---
       packer/rpc/build.go          |   4 +-
       packer/rpc/builder.go        |   4 +-
       packer/rpc/client.go         |  13 +-
       packer/rpc/command.go        |   4 +-
       packer/rpc/communicator.go   |   6 +-
       packer/rpc/environment.go    |   4 +-
       packer/rpc/hook.go           |   4 +-
       packer/rpc/mux_broker.go     |  31 ++
       packer/rpc/muxconn.go        | 605 -----------------------------------
       packer/rpc/muxconn_test.go   | 311 ------------------
       packer/rpc/post_processor.go |   4 +-
       packer/rpc/provisioner.go    |   4 +-
       packer/rpc/server.go         |  10 +-
       13 files changed, 64 insertions(+), 940 deletions(-)
       delete mode 100644 packer/rpc/muxconn.go
       delete mode 100644 packer/rpc/muxconn_test.go
      
      diff --git a/packer/rpc/build.go b/packer/rpc/build.go
      index c2e6dceca..4d0b7edf4 100644
      --- a/packer/rpc/build.go
      +++ b/packer/rpc/build.go
      @@ -9,14 +9,14 @@ import (
       // over an RPC connection.
       type build struct {
       	client *rpc.Client
      -	mux    *MuxConn
      +	mux    *muxBroker
       }
       
       // BuildServer wraps a packer.Build implementation and makes it exportable
       // as part of a Golang RPC server.
       type BuildServer struct {
       	build packer.Build
      -	mux   *MuxConn
      +	mux   *muxBroker
       }
       
       type BuildPrepareResponse struct {
      diff --git a/packer/rpc/builder.go b/packer/rpc/builder.go
      index 5e3f429b5..0e2464bd7 100644
      --- a/packer/rpc/builder.go
      +++ b/packer/rpc/builder.go
      @@ -10,14 +10,14 @@ import (
       // over an RPC connection.
       type builder struct {
       	client *rpc.Client
      -	mux    *MuxConn
      +	mux    *muxBroker
       }
       
       // BuilderServer wraps a packer.Builder implementation and makes it exportable
       // as part of a Golang RPC server.
       type BuilderServer struct {
       	builder packer.Builder
      -	mux     *MuxConn
      +	mux     *muxBroker
       }
       
       type BuilderPrepareArgs struct {
      diff --git a/packer/rpc/client.go b/packer/rpc/client.go
      index 0cc8fc869..7e2ff00fc 100644
      --- a/packer/rpc/client.go
      +++ b/packer/rpc/client.go
      @@ -12,22 +12,29 @@ import (
       // Establishing a connection is up to the user, the Client can just
       // communicate over any ReadWriteCloser.
       type Client struct {
      -	mux      *MuxConn
      +	mux      *muxBroker
       	client   *rpc.Client
       	closeMux bool
       }
       
       func NewClient(rwc io.ReadWriteCloser) (*Client, error) {
      -	result, err := newClientWithMux(NewMuxConn(rwc), 0)
      +	mux, err := newMuxBrokerClient(rwc)
       	if err != nil {
       		return nil, err
       	}
      +	go mux.Run()
      +
      +	result, err := newClientWithMux(mux, 0)
      +	if err != nil {
      +		mux.Close()
      +		return nil, err
      +	}
       
       	result.closeMux = true
       	return result, err
       }
       
      -func newClientWithMux(mux *MuxConn, streamId uint32) (*Client, error) {
      +func newClientWithMux(mux *muxBroker, streamId uint32) (*Client, error) {
       	clientConn, err := mux.Dial(streamId)
       	if err != nil {
       		return nil, err
      diff --git a/packer/rpc/command.go b/packer/rpc/command.go
      index 1484e5225..b5e5ccd52 100644
      --- a/packer/rpc/command.go
      +++ b/packer/rpc/command.go
      @@ -9,14 +9,14 @@ import (
       // command is actually executed over an RPC connection.
       type command struct {
       	client *rpc.Client
      -	mux    *MuxConn
      +	mux    *muxBroker
       }
       
       // A CommandServer wraps a packer.Command and makes it exportable as part
       // of a Golang RPC server.
       type CommandServer struct {
       	command packer.Command
      -	mux     *MuxConn
      +	mux     *muxBroker
       }
       
       type CommandRunArgs struct {
      diff --git a/packer/rpc/communicator.go b/packer/rpc/communicator.go
      index 9ac539323..e1d6cb649 100644
      --- a/packer/rpc/communicator.go
      +++ b/packer/rpc/communicator.go
      @@ -12,14 +12,14 @@ import (
       // executed over an RPC connection.
       type communicator struct {
       	client *rpc.Client
      -	mux    *MuxConn
      +	mux    *muxBroker
       }
       
       // CommunicatorServer wraps a packer.Communicator implementation and makes
       // it exportable as part of a Golang RPC server.
       type CommunicatorServer struct {
       	c   packer.Communicator
      -	mux *MuxConn
      +	mux *muxBroker
       }
       
       type CommandFinished struct {
      @@ -252,7 +252,7 @@ func (c *CommunicatorServer) Download(args *CommunicatorDownloadArgs, reply *int
       	return
       }
       
      -func serveSingleCopy(name string, mux *MuxConn, id uint32, dst io.Writer, src io.Reader) {
      +func serveSingleCopy(name string, mux *muxBroker, id uint32, dst io.Writer, src io.Reader) {
       	conn, err := mux.Accept(id)
       	if err != nil {
       		log.Printf("[ERR] '%s' accept error: %s", name, err)
      diff --git a/packer/rpc/environment.go b/packer/rpc/environment.go
      index 644807bc4..5048b54ea 100644
      --- a/packer/rpc/environment.go
      +++ b/packer/rpc/environment.go
      @@ -10,14 +10,14 @@ import (
       // where the actual environment is executed over an RPC connection.
       type Environment struct {
       	client *rpc.Client
      -	mux    *MuxConn
      +	mux    *muxBroker
       }
       
       // A EnvironmentServer wraps a packer.Environment and makes it exportable
       // as part of a Golang RPC server.
       type EnvironmentServer struct {
       	env packer.Environment
      -	mux *MuxConn
      +	mux *muxBroker
       }
       
       type EnvironmentCliArgs struct {
      diff --git a/packer/rpc/hook.go b/packer/rpc/hook.go
      index 6f149d41a..4aa7d75bc 100644
      --- a/packer/rpc/hook.go
      +++ b/packer/rpc/hook.go
      @@ -10,14 +10,14 @@ import (
       // over an RPC connection.
       type hook struct {
       	client *rpc.Client
      -	mux    *MuxConn
      +	mux    *muxBroker
       }
       
       // HookServer wraps a packer.Hook implementation and makes it exportable
       // as part of a Golang RPC server.
       type HookServer struct {
       	hook packer.Hook
      -	mux  *MuxConn
      +	mux  *muxBroker
       }
       
       type HookRunArgs struct {
      diff --git a/packer/rpc/mux_broker.go b/packer/rpc/mux_broker.go
      index 2e1061c9d..7af76f640 100644
      --- a/packer/rpc/mux_broker.go
      +++ b/packer/rpc/mux_broker.go
      @@ -3,8 +3,10 @@ package rpc
       import (
       	"encoding/binary"
       	"fmt"
      +	"io"
       	"net"
       	"sync"
      +	"sync/atomic"
       	"time"
       
       	"github.com/hashicorp/yamux"
      @@ -16,6 +18,7 @@ import (
       // or accept a connection from, and the broker handles the details of
       // holding these channels open while they're being negotiated.
       type muxBroker struct {
      +	nextId  uint32
       	session *yamux.Session
       	streams map[uint32]*muxBrokerPending
       
      @@ -34,6 +37,24 @@ func newMuxBroker(s *yamux.Session) *muxBroker {
       	}
       }
       
      +func newMuxBrokerClient(rwc io.ReadWriteCloser) (*muxBroker, error) {
      +	s, err := yamux.Client(rwc, nil)
      +	if err != nil {
      +		return nil, err
      +	}
      +
      +	return newMuxBroker(s), nil
      +}
      +
      +func newMuxBrokerServer(rwc io.ReadWriteCloser) (*muxBroker, error) {
      +	s, err := yamux.Server(rwc, nil)
      +	if err != nil {
      +		return nil, err
      +	}
      +
      +	return newMuxBroker(s), nil
      +}
      +
       // Accept accepts a connection by ID.
       //
       // This should not be called multiple times with the same ID at one time.
      @@ -60,6 +81,11 @@ func (m *muxBroker) Accept(id uint32) (net.Conn, error) {
       	return c, nil
       }
       
      +// Close closes the connection and all sub-connections.
      +func (m *muxBroker) Close() error {
      +	return m.session.Close()
      +}
      +
       // Dial opens a connection by ID.
       func (m *muxBroker) Dial(id uint32) (net.Conn, error) {
       	// Open the stream
      @@ -88,6 +114,11 @@ func (m *muxBroker) Dial(id uint32) (net.Conn, error) {
       	return stream, nil
       }
       
      +// NextId returns a unique ID to use next.
      +func (m *muxBroker) NextId() uint32 {
      +	return atomic.AddUint32(&m.nextId, 1)
      +}
      +
       // Run starts the brokering and should be executed in a goroutine, since it
       // blocks forever, or until the session closes.
       func (m *muxBroker) Run() {
      diff --git a/packer/rpc/muxconn.go b/packer/rpc/muxconn.go
      deleted file mode 100644
      index bc9de8e72..000000000
      --- a/packer/rpc/muxconn.go
      +++ /dev/null
      @@ -1,605 +0,0 @@
      -package rpc
      -
      -import (
      -	"encoding/binary"
      -	"fmt"
      -	"io"
      -	"log"
      -	"sync"
      -	"time"
      -)
      -
      -// MuxConn is able to multiplex multiple streams on top of any
      -// io.ReadWriteCloser. These streams act like TCP connections (Dial, Accept,
      -// Close, full duplex, etc.).
      -//
      -// The underlying io.ReadWriteCloser is expected to guarantee delivery
      -// and ordering, such as TCP. Congestion control and such aren't implemented
      -// by the streams, so that is also up to the underlying connection.
      -//
      -// MuxConn works using a fairly dumb multiplexing technique of simply
      -// framing every piece of data sent into a prefix + data format. Streams
      -// are established using a subset of the TCP protocol. Only a subset is
      -// necessary since we assume ordering on the underlying RWC.
      -type MuxConn struct {
      -	curId         uint32
      -	rwc           io.ReadWriteCloser
      -	streamsAccept map[uint32]*Stream
      -	streamsDial   map[uint32]*Stream
      -	muAccept      sync.RWMutex
      -	muDial        sync.RWMutex
      -	wlock         sync.Mutex
      -	doneCh        chan struct{}
      -}
      -
      -type muxPacketFrom byte
      -type muxPacketType byte
      -
      -const (
      -	muxPacketFromAccept muxPacketFrom = iota
      -	muxPacketFromDial
      -)
      -
      -const (
      -	muxPacketSyn muxPacketType = iota
      -	muxPacketSynAck
      -	muxPacketAck
      -	muxPacketFin
      -	muxPacketData
      -)
      -
      -func (f muxPacketFrom) String() string {
      -	switch f {
      -	case muxPacketFromAccept:
      -		return "accept"
      -	case muxPacketFromDial:
      -		return "dial"
      -	default:
      -		panic("unknown from type")
      -	}
      -}
      -
      -// Create a new MuxConn around any io.ReadWriteCloser.
      -func NewMuxConn(rwc io.ReadWriteCloser) *MuxConn {
      -	m := &MuxConn{
      -		rwc:           rwc,
      -		streamsAccept: make(map[uint32]*Stream),
      -		streamsDial:   make(map[uint32]*Stream),
      -		doneCh:        make(chan struct{}),
      -	}
      -
      -	go m.cleaner()
      -	go m.loop()
      -
      -	return m
      -}
      -
      -// Close closes the underlying io.ReadWriteCloser. This will also close
      -// all streams that are open.
      -func (m *MuxConn) Close() error {
      -	m.muAccept.Lock()
      -	m.muDial.Lock()
      -	defer m.muAccept.Unlock()
      -	defer m.muDial.Unlock()
      -
      -	// Close all the streams
      -	for _, w := range m.streamsAccept {
      -		w.Close()
      -	}
      -	for _, w := range m.streamsDial {
      -		w.Close()
      -	}
      -	m.streamsAccept = make(map[uint32]*Stream)
      -	m.streamsDial = make(map[uint32]*Stream)
      -
      -	// Close the actual connection. This will also force the loop
      -	// to end since it'll read EOF or closed connection.
      -	return m.rwc.Close()
      -}
      -
      -// Accept accepts a multiplexed connection with the given ID. This
      -// will block until a request is made to connect.
      -func (m *MuxConn) Accept(id uint32) (io.ReadWriteCloser, error) {
      -	//log.Printf("[TRACE] %p: Accept on stream ID: %d", m, id)
      -
      -	// Get the stream. It is okay if it is already in the list of streams
      -	// because we may have prematurely received a syn for it.
      -	m.muAccept.Lock()
      -	stream, ok := m.streamsAccept[id]
      -	if !ok {
      -		stream = newStream(muxPacketFromAccept, id, m)
      -		m.streamsAccept[id] = stream
      -	}
      -	m.muAccept.Unlock()
      -
      -	stream.mu.Lock()
      -	defer stream.mu.Unlock()
      -
      -	// If the stream isn't closed, then it is already open somehow
      -	if stream.state != streamStateSynRecv && stream.state != streamStateClosed {
      -		panic(fmt.Sprintf(
      -			"Stream %d already open in bad state: %d", id, stream.state))
      -	}
      -
      -	if stream.state == streamStateClosed {
      -		// Go into the listening state and wait for a syn
      -		stream.setState(streamStateListen)
      -		if err := stream.waitState(streamStateSynRecv); err != nil {
      -			return nil, err
      -		}
      -	}
      -
      -	if stream.state == streamStateSynRecv {
      -		// Send a syn-ack
      -		if _, err := stream.write(muxPacketSynAck, nil); err != nil {
      -			return nil, err
      -		}
      -	}
      -
      -	if err := stream.waitState(streamStateEstablished); err != nil {
      -		return nil, err
      -	}
      -
      -	return stream, nil
      -}
      -
      -// Dial opens a connection to the remote end using the given stream ID.
      -// An Accept on the remote end will only work with if the IDs match.
      -func (m *MuxConn) Dial(id uint32) (io.ReadWriteCloser, error) {
      -	//log.Printf("[TRACE] %p: Dial on stream ID: %d", m, id)
      -
      -	m.muDial.Lock()
      -
      -	// If we have any streams with this ID, then it is a failure. The
      -	// reaper should clear out old streams once in awhile.
      -	if stream, ok := m.streamsDial[id]; ok {
      -		m.muDial.Unlock()
      -		panic(fmt.Sprintf(
      -			"Stream %d already open for dial. State: %d",
      -			id, stream.state))
      -	}
      -
      -	// Create the new stream and put it in our list. We can then
      -	// unlock because dialing will no longer be allowed on that ID.
      -	stream := newStream(muxPacketFromDial, id, m)
      -	m.streamsDial[id] = stream
      -
      -	// Don't let anyone else mess with this stream
      -	stream.mu.Lock()
      -	defer stream.mu.Unlock()
      -
      -	m.muDial.Unlock()
      -
      -	// Open a connection
      -	if _, err := stream.write(muxPacketSyn, nil); err != nil {
      -		return nil, err
      -	}
      -
      -	// It is safe to set the state after the write above because
      -	// we hold the stream lock.
      -	stream.setState(streamStateSynSent)
      -
      -	if err := stream.waitState(streamStateEstablished); err != nil {
      -		return nil, err
      -	}
      -
      -	stream.write(muxPacketAck, nil)
      -	return stream, nil
      -}
      -
      -// NextId returns the next available listen stream ID that isn't currently
      -// taken.
      -func (m *MuxConn) NextId() uint32 {
      -	m.muAccept.Lock()
      -	defer m.muAccept.Unlock()
      -
      -	for {
      -		// We never use stream ID 0 because 0 is the zero value of a uint32
      -		// and we want to reserve that for "not in use"
      -		if m.curId == 0 {
      -			m.curId = 1
      -		}
      -
      -		result := m.curId
      -		m.curId += 1
      -		if _, ok := m.streamsAccept[result]; !ok {
      -			return result
      -		}
      -	}
      -}
      -
      -func (m *MuxConn) cleaner() {
      -	checks := []struct {
      -		Map  *map[uint32]*Stream
      -		Lock *sync.RWMutex
      -	}{
      -		{&m.streamsAccept, &m.muAccept},
      -		{&m.streamsDial, &m.muDial},
      -	}
      -
      -	for {
      -		done := false
      -		select {
      -		case <-time.After(500 * time.Millisecond):
      -		case <-m.doneCh:
      -			done = true
      -		}
      -
      -		for _, check := range checks {
      -			check.Lock.Lock()
      -			for id, s := range *check.Map {
      -				s.mu.Lock()
      -
      -				if done && s.state != streamStateClosed {
      -					s.closeWriter()
      -				}
      -
      -				if s.state == streamStateClosed {
      -					// Only clean up the streams that have been closed
      -					// for a certain amount of time.
      -					since := time.Now().UTC().Sub(s.stateUpdated)
      -					if since > 2*time.Second {
      -						delete(*check.Map, id)
      -					}
      -				}
      -
      -				s.mu.Unlock()
      -			}
      -			check.Lock.Unlock()
      -		}
      -
      -		if done {
      -			return
      -		}
      -	}
      -}
      -
      -func (m *MuxConn) loop() {
      -	// Force close every stream that we know about when we exit so
      -	// that they all read EOF and don't block forever.
      -	defer func() {
      -		log.Printf("[INFO] Mux connection loop exiting")
      -		close(m.doneCh)
      -	}()
      -
      -	var from muxPacketFrom
      -	var id uint32
      -	var packetType muxPacketType
      -	var length int32
      -	for {
      -		if err := binary.Read(m.rwc, binary.BigEndian, &from); err != nil {
      -			log.Printf("[ERR] Error reading stream direction: %s", err)
      -			return
      -		}
      -		if err := binary.Read(m.rwc, binary.BigEndian, &id); err != nil {
      -			log.Printf("[ERR] Error reading stream ID: %s", err)
      -			return
      -		}
      -		if err := binary.Read(m.rwc, binary.BigEndian, &packetType); err != nil {
      -			log.Printf("[ERR] Error reading packet type: %s", err)
      -			return
      -		}
      -		if err := binary.Read(m.rwc, binary.BigEndian, &length); err != nil {
      -			log.Printf("[ERR] Error reading length: %s", err)
      -			return
      -		}
      -
      -		// TODO(mitchellh): probably would be better to re-use a buffer...
      -		data := make([]byte, length)
      -		n := 0
      -		for n < int(length) {
      -			if n2, err := m.rwc.Read(data[n:]); err != nil {
      -				log.Printf("[ERR] Error reading data: %s", err)
      -				return
      -			} else {
      -				n += n2
      -			}
      -		}
      -
      -		// Get the proper stream. Note that the map we look into is
      -		// opposite the "from" because if the dial side is talking to
      -		// us, we need to look into the accept map, and so on.
      -		//
      -		// Note: we also switch the "from" value so that logging
      -		// below is correct.
      -		var stream *Stream
      -		switch from {
      -		case muxPacketFromDial:
      -			m.muAccept.Lock()
      -			stream = m.streamsAccept[id]
      -			m.muAccept.Unlock()
      -
      -			from = muxPacketFromAccept
      -		case muxPacketFromAccept:
      -			m.muDial.Lock()
      -			stream = m.streamsDial[id]
      -			m.muDial.Unlock()
      -
      -			from = muxPacketFromDial
      -		default:
      -			panic(fmt.Sprintf("Unknown stream direction: %d", from))
      -		}
      -
      -		if stream == nil && packetType != muxPacketSyn {
      -			log.Printf(
      -				"[WARN] %p: Non-existent stream %d (%s) received packer %d",
      -				m, id, from, packetType)
      -			continue
      -		}
      -
      -		//log.Printf("[TRACE] %p: Stream %d (%s) received packet %d", m, id, from, packetType)
      -		switch packetType {
      -		case muxPacketSyn:
      -			// If the stream is nil, this is the only case where we'll
      -			// automatically create the stream struct.
      -			if stream == nil {
      -				var ok bool
      -
      -				m.muAccept.Lock()
      -				stream, ok = m.streamsAccept[id]
      -				if !ok {
      -					stream = newStream(muxPacketFromAccept, id, m)
      -					m.streamsAccept[id] = stream
      -				}
      -				m.muAccept.Unlock()
      -			}
      -
      -			stream.mu.Lock()
      -			switch stream.state {
      -			case streamStateClosed:
      -				fallthrough
      -			case streamStateListen:
      -				stream.setState(streamStateSynRecv)
      -			default:
      -				log.Printf("[ERR] Syn received for stream in state: %d", stream.state)
      -			}
      -			stream.mu.Unlock()
      -		case muxPacketAck:
      -			stream.mu.Lock()
      -			switch stream.state {
      -			case streamStateSynRecv:
      -				stream.setState(streamStateEstablished)
      -			case streamStateFinWait1:
      -				stream.setState(streamStateFinWait2)
      -			case streamStateLastAck:
      -				stream.closeWriter()
      -				fallthrough
      -			case streamStateClosing:
      -				stream.setState(streamStateClosed)
      -			default:
      -				log.Printf("[ERR] Ack received for stream in state: %d", stream.state)
      -			}
      -			stream.mu.Unlock()
      -		case muxPacketSynAck:
      -			stream.mu.Lock()
      -			switch stream.state {
      -			case streamStateSynSent:
      -				stream.setState(streamStateEstablished)
      -			default:
      -				log.Printf("[ERR] SynAck received for stream in state: %d", stream.state)
      -			}
      -			stream.mu.Unlock()
      -		case muxPacketFin:
      -			stream.mu.Lock()
      -			switch stream.state {
      -			case streamStateEstablished:
      -				stream.closeWriter()
      -				stream.setState(streamStateCloseWait)
      -				stream.write(muxPacketAck, nil)
      -			case streamStateFinWait2:
      -				stream.closeWriter()
      -				stream.setState(streamStateClosed)
      -				stream.write(muxPacketAck, nil)
      -			case streamStateFinWait1:
      -				stream.closeWriter()
      -				stream.setState(streamStateClosing)
      -				stream.write(muxPacketAck, nil)
      -			default:
      -				log.Printf("[ERR] Fin received for stream %d in state: %d", id, stream.state)
      -			}
      -			stream.mu.Unlock()
      -
      -		case muxPacketData:
      -			stream.mu.Lock()
      -			switch stream.state {
      -			case streamStateFinWait1:
      -				fallthrough
      -			case streamStateFinWait2:
      -				fallthrough
      -			case streamStateEstablished:
      -				if len(data) > 0 && stream.writeCh != nil {
      -					//log.Printf("[TRACE] %p: Stream %d (%s) WRITE-START", m, id, from)
      -					stream.writeCh <- data
      -					//log.Printf("[TRACE] %p: Stream %d (%s) WRITE-END", m, id, from)
      -				}
      -			default:
      -				log.Printf("[ERR] Data received for stream in state: %d", stream.state)
      -			}
      -			stream.mu.Unlock()
      -		}
      -	}
      -}
      -
      -func (m *MuxConn) write(from muxPacketFrom, id uint32, dataType muxPacketType, p []byte) (int, error) {
      -	m.wlock.Lock()
      -	defer m.wlock.Unlock()
      -
      -	if err := binary.Write(m.rwc, binary.BigEndian, from); err != nil {
      -		return 0, err
      -	}
      -	if err := binary.Write(m.rwc, binary.BigEndian, id); err != nil {
      -		return 0, err
      -	}
      -	if err := binary.Write(m.rwc, binary.BigEndian, byte(dataType)); err != nil {
      -		return 0, err
      -	}
      -	if err := binary.Write(m.rwc, binary.BigEndian, int32(len(p))); err != nil {
      -		return 0, err
      -	}
      -
      -	// Write all the bytes. If we don't write all the bytes, report an error
      -	var err error = nil
      -	n := 0
      -	for n < len(p) {
      -		var n2 int
      -		n2, err = m.rwc.Write(p[n:])
      -		n += n2
      -		if err != nil {
      -			log.Printf("[ERR] %p: Stream %d (%s) write error: %s", m, id, from, err)
      -			break
      -		}
      -	}
      -
      -	return n, err
      -}
      -
      -// Stream is a single stream of data and implements io.ReadWriteCloser.
      -// A Stream is full-duplex so you can write data as well as read data.
      -type Stream struct {
      -	from         muxPacketFrom
      -	id           uint32
      -	mux          *MuxConn
      -	reader       io.Reader
      -	state        streamState
      -	stateChange  map[chan<- streamState]struct{}
      -	stateUpdated time.Time
      -	mu           sync.Mutex
      -	writeCh      chan<- []byte
      -}
      -
      -type streamState byte
      -
      -const (
      -	streamStateClosed streamState = iota
      -	streamStateListen
      -	streamStateSynRecv
      -	streamStateSynSent
      -	streamStateEstablished
      -	streamStateFinWait1
      -	streamStateFinWait2
      -	streamStateCloseWait
      -	streamStateClosing
      -	streamStateLastAck
      -)
      -
      -func newStream(from muxPacketFrom, id uint32, m *MuxConn) *Stream {
      -	// Create the stream object and channel where data will be sent to
      -	dataR, dataW := io.Pipe()
      -	writeCh := make(chan []byte, 4096)
      -
      -	// Set the data channel so we can write to it.
      -	stream := &Stream{
      -		from:        from,
      -		id:          id,
      -		mux:         m,
      -		reader:      dataR,
      -		writeCh:     writeCh,
      -		stateChange: make(map[chan<- streamState]struct{}),
      -	}
      -	stream.setState(streamStateClosed)
      -
      -	// Start the goroutine that will read from the queue and write
      -	// data out.
      -	go func() {
      -		defer dataW.Close()
      -
      -		drain := false
      -		for {
      -			data := <-writeCh
      -			if data == nil {
      -				// A nil is a tombstone letting us know we're done
      -				// accepting data.
      -				return
      -			}
      -
      -			if drain {
      -				// We're draining, meaning we're just waiting for the
      -				// write channel to close.
      -				continue
      -			}
      -
      -			if _, err := dataW.Write(data); err != nil {
      -				drain = true
      -			}
      -		}
      -	}()
      -
      -	return stream
      -}
      -
      -func (s *Stream) Close() error {
      -	s.mu.Lock()
      -	defer s.mu.Unlock()
      -
      -	if s.state != streamStateEstablished && s.state != streamStateCloseWait {
      -		return fmt.Errorf("Stream in bad state: %d", s.state)
      -	}
      -
      -	if s.state == streamStateEstablished {
      -		s.setState(streamStateFinWait1)
      -	} else {
      -		s.setState(streamStateLastAck)
      -	}
      -
      -	s.write(muxPacketFin, nil)
      -	return nil
      -}
      -
      -func (s *Stream) Read(p []byte) (int, error) {
      -	return s.reader.Read(p)
      -}
      -
      -func (s *Stream) Write(p []byte) (int, error) {
      -	s.mu.Lock()
      -	state := s.state
      -	s.mu.Unlock()
      -
      -	if state != streamStateEstablished && state != streamStateCloseWait {
      -		return 0, fmt.Errorf("Stream %d in bad state to send: %d", s.id, state)
      -	}
      -
      -	return s.write(muxPacketData, p)
      -}
      -
      -func (s *Stream) closeWriter() {
      -	if s.writeCh != nil {
      -		s.writeCh <- nil
      -		s.writeCh = nil
      -	}
      -}
      -
      -func (s *Stream) setState(state streamState) {
      -	//log.Printf("[TRACE] %p: Stream %d (%s) went to state %d", s.mux, s.id, s.from, state)
      -	s.state = state
      -	s.stateUpdated = time.Now().UTC()
      -	for ch, _ := range s.stateChange {
      -		select {
      -		case ch <- state:
      -		default:
      -		}
      -	}
      -}
      -
      -func (s *Stream) waitState(target streamState) error {
      -	// Register a state change listener to wait for changes
      -	stateCh := make(chan streamState, 10)
      -	s.stateChange[stateCh] = struct{}{}
      -	s.mu.Unlock()
      -
      -	defer func() {
      -		s.mu.Lock()
      -		delete(s.stateChange, stateCh)
      -	}()
      -
      -	//log.Printf("[TRACE] %p: Stream %d (%s) waiting for state: %d", s.mux, s.id, s.from, target)
      -	state := <-stateCh
      -	if state == target {
      -		return nil
      -	} else {
      -		return fmt.Errorf("Stream %d went to bad state: %d", s.id, state)
      -	}
      -}
      -
      -func (s *Stream) write(dataType muxPacketType, p []byte) (int, error) {
      -	return s.mux.write(s.from, s.id, dataType, p)
      -}
      diff --git a/packer/rpc/muxconn_test.go b/packer/rpc/muxconn_test.go
      deleted file mode 100644
      index 27a77fb46..000000000
      --- a/packer/rpc/muxconn_test.go
      +++ /dev/null
      @@ -1,311 +0,0 @@
      -package rpc
      -
      -import (
      -	"io"
      -	"net"
      -	"sync"
      -	"testing"
      -)
      -
      -func readStream(t *testing.T, s io.Reader) string {
      -	var data [1024]byte
      -	n, err := s.Read(data[:])
      -	if err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -
      -	return string(data[0:n])
      -}
      -
      -func testMux(t *testing.T) (client *MuxConn, server *MuxConn) {
      -	l, err := net.Listen("tcp", "127.0.0.1:0")
      -	if err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -
      -	// Server side
      -	doneCh := make(chan struct{})
      -	go func() {
      -		defer close(doneCh)
      -		conn, err := l.Accept()
      -		l.Close()
      -		if err != nil {
      -			t.Fatalf("err: %s", err)
      -		}
      -
      -		server = NewMuxConn(conn)
      -	}()
      -
      -	// Client side
      -	conn, err := net.Dial("tcp", l.Addr().String())
      -	if err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -	client = NewMuxConn(conn)
      -
      -	// Wait for the server
      -	<-doneCh
      -
      -	return
      -}
      -
      -func TestMuxConn(t *testing.T) {
      -	client, server := testMux(t)
      -	defer client.Close()
      -	defer server.Close()
      -
      -	// When the server is done
      -	doneCh := make(chan struct{})
      -
      -	// The server side
      -	go func() {
      -		defer close(doneCh)
      -
      -		s0, err := server.Accept(0)
      -		if err != nil {
      -			t.Fatalf("err: %s", err)
      -		}
      -
      -		s1, err := server.Dial(1)
      -		if err != nil {
      -			t.Fatalf("err: %s", err)
      -		}
      -
      -		var wg sync.WaitGroup
      -		wg.Add(2)
      -
      -		go func() {
      -			defer wg.Done()
      -			defer s1.Close()
      -			data := readStream(t, s1)
      -			if data != "another" {
      -				t.Fatalf("bad: %#v", data)
      -			}
      -		}()
      -
      -		go func() {
      -			defer wg.Done()
      -			defer s0.Close()
      -			data := readStream(t, s0)
      -			if data != "hello" {
      -				t.Fatalf("bad: %#v", data)
      -			}
      -		}()
      -
      -		wg.Wait()
      -	}()
      -
      -	s0, err := client.Dial(0)
      -	if err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -
      -	s1, err := client.Accept(1)
      -	if err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -
      -	if _, err := s0.Write([]byte("hello")); err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -	if _, err := s1.Write([]byte("another")); err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -
      -	s0.Close()
      -	s1.Close()
      -
      -	// Wait for the server to be done
      -	<-doneCh
      -}
      -
      -func TestMuxConn_lotsOfData(t *testing.T) {
      -	client, server := testMux(t)
      -	defer client.Close()
      -	defer server.Close()
      -
      -	// When the server is done
      -	doneCh := make(chan struct{})
      -
      -	// The server side
      -	go func() {
      -		defer close(doneCh)
      -
      -		s0, err := server.Accept(0)
      -		if err != nil {
      -			t.Fatalf("err: %s", err)
      -		}
      -
      -		var data [1024]byte
      -		for {
      -			n, err := s0.Read(data[:])
      -			if err == io.EOF {
      -				break
      -			}
      -
      -			dataString := string(data[0:n])
      -			if dataString != "hello" {
      -				t.Fatalf("bad: %#v", dataString)
      -			}
      -		}
      -
      -		s0.Close()
      -	}()
      -
      -	s0, err := client.Dial(0)
      -	if err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -
      -	for i := 0; i < 4096*4; i++ {
      -		if _, err := s0.Write([]byte("hello")); err != nil {
      -			t.Fatalf("err: %s", err)
      -		}
      -	}
      -
      -	if err := s0.Close(); err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -
      -	// Wait for the server to be done
      -	<-doneCh
      -}
      -
      -// This tests that even when the client end is closed, data can be
      -// read from the server.
      -func TestMuxConn_clientCloseRead(t *testing.T) {
      -	client, server := testMux(t)
      -	defer client.Close()
      -	defer server.Close()
      -
      -	// This channel will be closed when we close
      -	waitCh := make(chan struct{})
      -
      -	go func() {
      -		conn, err := server.Accept(0)
      -		if err != nil {
      -			t.Fatalf("err: %s", err)
      -		}
      -
      -		<-waitCh
      -
      -		_, err = conn.Write([]byte("foo"))
      -		if err != nil {
      -			t.Fatalf("err: %s", err)
      -		}
      -
      -		conn.Close()
      -	}()
      -
      -	s0, err := client.Dial(0)
      -	if err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -
      -	if err := s0.Close(); err != nil {
      -		t.Fatalf("bad: %s", err)
      -	}
      -
      -	// Close this to continue on on the server-side
      -	close(waitCh)
      -
      -	var data [1024]byte
      -	n, err := s0.Read(data[:])
      -	if string(data[:n]) != "foo" {
      -		t.Fatalf("bad: %#v", string(data[:n]))
      -	}
      -}
      -
      -func TestMuxConn_socketClose(t *testing.T) {
      -	client, server := testMux(t)
      -	defer client.Close()
      -	defer server.Close()
      -
      -	go func() {
      -		_, err := server.Accept(0)
      -		if err != nil {
      -			t.Fatalf("err: %s", err)
      -		}
      -
      -		server.rwc.Close()
      -	}()
      -
      -	s0, err := client.Dial(0)
      -	if err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -
      -	var data [1024]byte
      -	_, err = s0.Read(data[:])
      -	if err != io.EOF {
      -		t.Fatalf("err: %s", err)
      -	}
      -}
      -
      -func TestMuxConn_clientClosesStreams(t *testing.T) {
      -	client, server := testMux(t)
      -	defer client.Close()
      -	defer server.Close()
      -
      -	go func() {
      -		conn, err := server.Accept(0)
      -		if err != nil {
      -			t.Fatalf("err: %s", err)
      -		}
      -		conn.Close()
      -	}()
      -
      -	s0, err := client.Dial(0)
      -	if err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -
      -	var data [1024]byte
      -	_, err = s0.Read(data[:])
      -	if err != io.EOF {
      -		t.Fatalf("err: %s", err)
      -	}
      -}
      -
      -func TestMuxConn_serverClosesStreams(t *testing.T) {
      -	client, server := testMux(t)
      -	defer client.Close()
      -	defer server.Close()
      -	go server.Accept(0)
      -
      -	s0, err := client.Dial(0)
      -	if err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -
      -	if err := server.Close(); err != nil {
      -		t.Fatalf("err: %s", err)
      -	}
      -
      -	// This should block forever since we never write onto this stream.
      -	var data [1024]byte
      -	_, err = s0.Read(data[:])
      -	if err != io.EOF {
      -		t.Fatalf("err: %s", err)
      -	}
      -}
      -
      -func TestMuxConnNextId(t *testing.T) {
      -	client, server := testMux(t)
      -	defer client.Close()
      -	defer server.Close()
      -
      -	a := client.NextId()
      -	b := client.NextId()
      -
      -	if a != 1 || b != 2 {
      -		t.Fatalf("IDs should increment")
      -	}
      -
      -	a = server.NextId()
      -	b = server.NextId()
      -
      -	if a != 1 || b != 2 {
      -		t.Fatalf("IDs should increment: %d %d", a, b)
      -	}
      -}
      diff --git a/packer/rpc/post_processor.go b/packer/rpc/post_processor.go
      index 3a22c1a8a..b183780b9 100644
      --- a/packer/rpc/post_processor.go
      +++ b/packer/rpc/post_processor.go
      @@ -9,14 +9,14 @@ import (
       // executed over an RPC connection.
       type postProcessor struct {
       	client *rpc.Client
      -	mux    *MuxConn
      +	mux    *muxBroker
       }
       
       // PostProcessorServer wraps a packer.PostProcessor implementation and makes it
       // exportable as part of a Golang RPC server.
       type PostProcessorServer struct {
       	client *rpc.Client
      -	mux    *MuxConn
      +	mux    *muxBroker
       	p      packer.PostProcessor
       }
       
      diff --git a/packer/rpc/provisioner.go b/packer/rpc/provisioner.go
      index e2346cd21..08d31700a 100644
      --- a/packer/rpc/provisioner.go
      +++ b/packer/rpc/provisioner.go
      @@ -10,14 +10,14 @@ import (
       // executed over an RPC connection.
       type provisioner struct {
       	client *rpc.Client
      -	mux    *MuxConn
      +	mux    *muxBroker
       }
       
       // ProvisionerServer wraps a packer.Provisioner implementation and makes it
       // exportable as part of a Golang RPC server.
       type ProvisionerServer struct {
       	p   packer.Provisioner
      -	mux *MuxConn
      +	mux *muxBroker
       }
       
       type ProvisionerPrepareArgs struct {
      diff --git a/packer/rpc/server.go b/packer/rpc/server.go
      index df50f48ad..ca9691870 100644
      --- a/packer/rpc/server.go
      +++ b/packer/rpc/server.go
      @@ -29,7 +29,7 @@ const (
       // Server represents an RPC server for Packer. This must be paired on
       // the other side with a Client.
       type Server struct {
      -	mux      *MuxConn
      +	mux      *muxBroker
       	streamId uint32
       	server   *rpc.Server
       	closeMux bool
      @@ -37,12 +37,14 @@ type Server struct {
       
       // NewServer returns a new Packer RPC server.
       func NewServer(conn io.ReadWriteCloser) *Server {
      -	result := newServerWithMux(NewMuxConn(conn), 0)
      +	mux, _ := newMuxBrokerServer(conn)
      +	result := newServerWithMux(mux, 0)
       	result.closeMux = true
      +	go mux.Run()
       	return result
       }
       
      -func newServerWithMux(mux *MuxConn, streamId uint32) *Server {
      +func newServerWithMux(mux *muxBroker, streamId uint32) *Server {
       	return &Server{
       		mux:      mux,
       		streamId: streamId,
      @@ -140,11 +142,11 @@ func (s *Server) Serve() {
       	// Accept a connection on stream ID 0, which is always used for
       	// normal client to server connections.
       	stream, err := s.mux.Accept(s.streamId)
      -	defer stream.Close()
       	if err != nil {
       		log.Printf("[ERR] Error retrieving stream for serving: %s", err)
       		return
       	}
      +	defer stream.Close()
       
       	var h codec.MsgpackHandle
       	rpcCodec := codec.GoRpc.ServerCodec(stream, &h)
      
      From b7c604795ed6b07c4391d128d131d3569214343b Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 14:28:21 -0700
      Subject: [PATCH 321/593] packer/plugin: increase version for Yamux
      
      ---
       packer/plugin/server.go | 2 +-
       1 file changed, 1 insertion(+), 1 deletion(-)
      
      diff --git a/packer/plugin/server.go b/packer/plugin/server.go
      index a36f8beda..83292c320 100644
      --- a/packer/plugin/server.go
      +++ b/packer/plugin/server.go
      @@ -33,7 +33,7 @@ const MagicCookieValue = "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d69
       // The APIVersion is outputted along with the RPC address. The plugin
       // client validates this API version and will show an error if it doesn't
       // know how to speak it.
      -const APIVersion = "3"
      +const APIVersion = "4"
       
       // Server waits for a connection to this plugin and returns a Packer
       // RPC server that you can use to register components and serve them.
      
      From 2788ccc653e5b646bec3c71bfe0a167ac1826860 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 15:13:55 -0700
      Subject: [PATCH 322/593] Update scripts to latest HashiCorp style
      
      ---
       Makefile              | 45 ++++++++-----------------
       README.md             | 58 +++++++++++++++++---------------
       scripts/build.sh      | 77 ++++++++++++++++++++++++++++++++++++++++++
       scripts/compile.sh    | 52 -----------------------------
       scripts/devcompile.sh | 67 -------------------------------------
       scripts/dist.sh       | 78 +++++++++++++++++--------------------------
       6 files changed, 151 insertions(+), 226 deletions(-)
       create mode 100755 scripts/build.sh
       delete mode 100755 scripts/compile.sh
       delete mode 100755 scripts/devcompile.sh
      
      diff --git a/Makefile b/Makefile
      index 0303d8843..892406ce0 100644
      --- a/Makefile
      +++ b/Makefile
      @@ -1,39 +1,20 @@
      -NO_COLOR=\033[0m
      -OK_COLOR=\033[32;01m
      -ERROR_COLOR=\033[31;01m
      -WARN_COLOR=\033[33;01m
      -DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
      -UNAME := $(shell uname -s)
      -ifeq ($(UNAME),Darwin)
      -ECHO=echo
      -else
      -ECHO=/bin/echo -e
      -endif
      +TEST?=./...
       
      -all: deps
      -	@mkdir -p bin/
      -	@$(ECHO) "$(OK_COLOR)==> Building$(NO_COLOR)"
      -	@bash --norc -i ./scripts/devcompile.sh
      +default: test
       
      -deps:
      -	@$(ECHO) "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)"
      -	@go get -d -v ./...
      -	@go get github.com/mitchellh/gox
      -	@echo $(DEPS) | xargs -n1 go get -d
      +bin:
      +	@sh -c "$(CURDIR)/scripts/build.sh"
       
      -updatedeps:
      -	@$(ECHO) "$(OK_COLOR)==> Updating all dependencies$(NO_COLOR)"
      -	@go get -d -v -u ./...
      -	@echo $(DEPS) | xargs -n1 go get -d -u
      +dev:
      +	@TF_DEV=1 sh -c "$(CURDIR)/scripts/build.sh"
       
      -clean:
      -	@rm -rf bin/ local/ pkg/ src/ website/.sass-cache website/build
      +test:
      +	go test $(TEST) $(TESTARGS) -timeout=10s
       
      -format:
      -	go fmt ./...
      +testrace:
      +	go test -race $(TEST) $(TESTARGS)
       
      -test: deps
      -	@$(ECHO) "$(OK_COLOR)==> Testing Packer...$(NO_COLOR)"
      -	go test ./...
      +updatedeps:
      +	go get -u -v ./...
       
      -.PHONY: all clean deps format test updatedeps
      +.PHONY: bin default test updatedeps
      diff --git a/README.md b/README.md
      index b56be4a2a..6104e59d3 100644
      --- a/README.md
      +++ b/README.md
      @@ -78,40 +78,44 @@ http://www.packer.io/docs
       
       ## Developing Packer
       
      -If you wish to work on Packer itself, you'll first need [Go](http://golang.org)
      -installed (version 1.2+ is _required_). Make sure you have Go properly installed,
      -including setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH).
      +If you wish to work on Packer itself or any of its built-in providers,
      +you'll first need [Go](http://www.golang.org) installed (version 1.2+ is
      +_required_). Make sure Go is properly installed, including setting up
      +a [GOPATH](http://golang.org/doc/code.html#GOPATH).
       
      -For some additional dependencies, Go needs [Mercurial](http://mercurial.selenic.com/)
      -and [Bazaar](http://bazaar.canonical.com/en/) to be installed.
      -Packer itself doesn't require these, but a dependency of a dependency does.
      +Next, install the following software packages, which are needed for some dependencies:
       
      -You'll also need [`gox`](https://github.com/mitchellh/gox)
      -to compile packer. You can install that with:
      +- [Bazaar](http://bazaar.canonical.com/en/)
      +- [Git](http://git-scm.com/)
      +- [Mercurial](http://mercurial.selenic.com/)
       
      -```
      -$ go get -u github.com/mitchellh/gox
      -```
      +Then, install [Gox](https://github.com/mitchellh/gox), which is used
      +as a compilation tool on top of Go:
       
      -Next, clone this repository into `$GOPATH/src/github.com/mitchellh/packer` and
      -then just type `make`. In a few moments, you'll have a working `packer` executable:
      +    $ go get -u github.com/mitchellh/gox
       
      -```
      -$ make
      -...
      -$ bin/packer
      -...
      -```
      +Next, clone this repository into `$GOPATH/src/github.com/mitchellh/packer`.
      +Install the necessary dependencies by running `make updatedeps` and then just
      +type `make`. This will compile some more dependencies and then run the tests. If
      +this exits with exit status 0, then everything is working!
      +
      +    $ make updatedeps
      +    ...
      +    $ make
      +    ...
       
      -If you need to cross-compile Packer for other platforms, take a look at
      -`scripts/dist.sh`.
      +To compile a development version of Packer and the built-in plugins,
      +run `make dev`. This will put Packer binaries in the `bin` folder:
       
      -You can run tests by typing `make test`.
      +    $ make dev
      +    ...
      +    $ bin/packer
      +    ...
       
      -This will run tests for Packer core along with all the core builders and commands and such that come with Packer.
       
      -If you make any changes to the code, run `make format` in order to automatically
      -format the code according to Go standards.
      +If you're developing a specific package, you can run tests for just that
      +package by specifying the `TEST` variable. For example below, only
      +`packer` package tests will be run.
       
      -When new dependencies are added to packer you can use `make updatedeps` to
      -get the latest and subsequently use `make` to compile and generate the `packer` binary.
      +    $ make test TEST=./packer
      +    ...
      diff --git a/scripts/build.sh b/scripts/build.sh
      new file mode 100755
      index 000000000..bbb250349
      --- /dev/null
      +++ b/scripts/build.sh
      @@ -0,0 +1,77 @@
      +#!/bin/bash
      +#
      +# This script builds the application from source for multiple platforms.
      +set -e
      +
      +# Get the parent directory of where this script is.
      +SOURCE="${BASH_SOURCE[0]}"
      +while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
      +DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
      +
      +# Change into that directory
      +cd $DIR
      +
      +# Get the git commit
      +GIT_COMMIT=$(git rev-parse HEAD)
      +GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)
      +
      +# Determine the arch/os combos we're building for
      +XC_ARCH=${XC_ARCH:-"386 amd64 arm"}
      +XC_OS=${XC_OS:-linux darwin windows freebsd openbsd}
      +
      +# Install dependencies
      +echo "==> Getting dependencies..."
      +go get ./...
      +
      +# Delete the old dir
      +echo "==> Removing old directory..."
      +rm -f bin/*
      +rm -rf pkg/*
      +mkdir -p bin/
      +
      +# If its dev mode, only build for ourself
      +if [ "${TF_DEV}x" != "x" ]; then
      +    XC_OS=$(go env GOOS)
      +    XC_ARCH=$(go env GOARCH)
      +fi
      +
      +# Build!
      +echo "==> Building..."
      +gox \
      +    -os="${XC_OS}" \
      +    -arch="${XC_ARCH}" \
      +    -ldflags "-X main.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \
      +    -output "pkg/{{.OS}}_{{.Arch}}/packer-{{.Dir}}" \
      +    ./...
      +
      +# Make sure "packer-packer" is renamed properly
      +for PLATFORM in $(find ./pkg -mindepth 1 -maxdepth 1 -type d); do
      +    set +e
      +    mv ${PLATFORM}/packer-packer ${PLATFORM}/packer 2>/dev/null
      +    mv ${PLATFORM}/packer-packer ${PLATFORM}/packer 2>/dev/null
      +    set -e
      +done
      +
      +# Copy our OS/Arch to the bin/ directory
      +DEV_PLATFORM="./pkg/$(go env GOOS)_$(go env GOARCH)"
      +for F in $(find ${DEV_PLATFORM} -mindepth 1 -maxdepth 1 -type f); do
      +    cp ${F} bin/
      +done
      +
      +if [ "${TF_DEV}x" = "x" ]; then
      +    # Zip and copy to the dist dir
      +    echo "==> Packaging..."
      +    for PLATFORM in $(find ./pkg -mindepth 1 -maxdepth 1 -type d); do
      +        OSARCH=$(basename ${PLATFORM})
      +        echo "--> ${OSARCH}"
      +
      +        pushd $PLATFORM >/dev/null 2>&1
      +        zip ../${OSARCH}.zip ./*
      +        popd >/dev/null 2>&1
      +    done
      +fi
      +
      +# Done!
      +echo
      +echo "==> Results:"
      +ls -hl bin/
      diff --git a/scripts/compile.sh b/scripts/compile.sh
      deleted file mode 100755
      index 24b6d8736..000000000
      --- a/scripts/compile.sh
      +++ /dev/null
      @@ -1,52 +0,0 @@
      -#!/bin/bash
      -#
      -# This script compiles Packer for various platforms (specified by the
      -# PACKER_OS and PACKER_ARCH environmental variables).
      -set -e
      -
      -NO_COLOR="\x1b[0m"
      -OK_COLOR="\x1b[32;01m"
      -ERROR_COLOR="\x1b[31;01m"
      -WARN_COLOR="\x1b[33;01m"
      -
      -# Get the parent directory of where this script is.
      -SOURCE="${BASH_SOURCE[0]}"
      -while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
      -DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
      -
      -# Change into that directory
      -cd $DIR
      -
      -# Get the git commit
      -GIT_COMMIT=$(git rev-parse HEAD)
      -GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)
      -
      -# Determine the arch/os combos we're building for
      -XC_ARCH=${XC_ARCH:-"386 amd64 arm"}
      -XC_OS=${XC_OS:-linux darwin windows freebsd openbsd}
      -
      -# Make sure that if we're killed, we kill all our subprocseses
      -trap "kill 0" SIGINT SIGTERM EXIT
      -
      -echo -e "${OK_COLOR}==> Installing dependencies to speed up builds...${NO_COLOR}"
      -go get ./...
      -
      -echo -e "${OK_COLOR}==> Beginning compile...${NO_COLOR}"
      -rm -rf pkg/
      -gox \
      -    -os="${XC_OS}" \
      -    -arch="${XC_ARCH}" \
      -    -ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \
      -    -output "pkg/{{.OS}}_{{.Arch}}/packer-{{.Dir}}" \
      -    ./...
      -
      -# Make sure "packer-packer" is renamed properly
      -for PLATFORM in $(find ./pkg -mindepth 1 -maxdepth 1 -type d); do
      -    set +e
      -    mv ${PLATFORM}/packer-packer*.exe ${PLATFORM}/packer.exe 2>/dev/null
      -    mv ${PLATFORM}/packer-packer* ${PLATFORM}/packer 2>/dev/null
      -    set -e
      -done
      -
      -# Reset signal trapping to avoid "Terminated: 15" at the end
      -trap - SIGINT SIGTERM EXIT
      diff --git a/scripts/devcompile.sh b/scripts/devcompile.sh
      deleted file mode 100755
      index 0b362ccb6..000000000
      --- a/scripts/devcompile.sh
      +++ /dev/null
      @@ -1,67 +0,0 @@
      -#!/bin/bash
      -#
      -# This script only builds the application from source.
      -set -e
      -
      -NO_COLOR="\x1b[0m"
      -OK_COLOR="\x1b[32;01m"
      -ERROR_COLOR="\x1b[31;01m"
      -WARN_COLOR="\x1b[33;01m"
      -
      -# http://stackoverflow.com/questions/4023830/bash-how-compare-two-strings-in-version-format
      -verify_go () {
      -    if [[ $1 == $2 ]]; then
      -        return 0
      -    fi
      -
      -    if [[ "$2" == "devel" ]]; then
      -        return 0
      -    fi
      -
      -    local IFS=.
      -    local i ver1=($1) ver2=($2)
      -
      -    for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do
      -        ver1[i]=0
      -    done
      -
      -    for ((i=0; i<${#ver1[@]}; i++)); do
      -        if [[ -z ${ver2[i]} ]]; then
      -            ver2[i]=0
      -        fi
      -        if ((10#${ver1[i]} > 10#${ver2[i]})); then
      -            echo -e "${ERROR_COLOR}==> Required Go version $1 not installed. Found $2 instead"
      -            exit 1
      -        fi
      -    done
      -}
      -
      -GO_MINIMUM_VERSION=1.2
      -GO_INSTALLED_VERSION=$(go version | cut -d ' ' -f 3)
      -GO_INSTALLED_VERSION=${GO_INSTALLED_VERSION#"go"}
      -
      -echo -e "${OK_COLOR}==> Verifying Go"
      -verify_go $GO_MINIMUM_VERSION $GO_INSTALLED_VERSION
      -
      -# Get the parent directory of where this script is.
      -SOURCE="${BASH_SOURCE[0]}"
      -while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
      -DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
      -
      -# Change into that directory
      -cd $DIR
      -
      -# Compile the thing
      -export XC_ARCH=$(go env GOARCH)
      -export XC_OS=$(go env GOOS)
      -./scripts/compile.sh
      -
      -# Move all the compiled things to the PATH
      -case $(uname) in
      -    CYGWIN*)
      -        GOPATH="$(cygpath $GOPATH)"
      -        ;;
      -esac
      -IFS=: MAIN_GOPATH=( $GOPATH )
      -cp pkg/${XC_OS}_${XC_ARCH}/* ${MAIN_GOPATH}/bin
      -cp pkg/${XC_OS}_${XC_ARCH}/* ./bin
      diff --git a/scripts/dist.sh b/scripts/dist.sh
      index 0768ff0ae..2a18c1923 100755
      --- a/scripts/dist.sh
      +++ b/scripts/dist.sh
      @@ -1,6 +1,19 @@
       #!/bin/bash
       set -e
       
      +# Get the version from the command line
      +VERSION=$1
      +if [ -z $VERSION ]; then
      +    echo "Please specify a version."
      +    exit 1
      +fi
      +
      +# Make sure we have a bintray API key
      +if [ -z $BINTRAY_API_KEY ]; then
      +    echo "Please set your bintray API key in the BINTRAY_API_KEY env var."
      +    exit 1
      +fi
      +
       # Get the parent directory of where this script is.
       SOURCE="${BASH_SOURCE[0]}"
       while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
      @@ -9,59 +22,28 @@ DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
       # Change into that dir because we expect that
       cd $DIR
       
      -# Determine the version that we're building based on the contents
      -# of packer/version.go.
      -VERSION=$(grep "const Version " packer/version.go | sed -E 's/.*"(.+)"$/\1/')
      -VERSIONDIR="${VERSION}"
      -PREVERSION=$(grep "const VersionPrerelease " packer/version.go | sed -E 's/.*"(.*)"$/\1/')
      -if [ ! -z $PREVERSION ]; then
      -    PREVERSION="${PREVERSION}.$(date -u +%s)"
      -    VERSIONDIR="${VERSIONDIR}-${PREVERSION}"
      -fi
      -
      -# This function waits for all background tasks to complete
      -waitAll() {
      -    RESULT=0
      -    for job in `jobs -p`; do
      -        wait $job
      -        if [ $? -ne 0 ]; then
      -            RESULT=1
      -        fi
      -    done
      -
      -    if [ $RESULT -ne 0 ]; then
      -        exit $RESULT
      -    fi
      -}
      -
      -# Compile the main project
      -./scripts/compile.sh
      -
      -# Make sure that if we're killed, we kill all our subprocseses
      -trap "kill 0" SIGINT SIGTERM EXIT
      -
      -# Zip all the packages
      +# Zip all the files
      +rm -rf ./pkg/dist
       mkdir -p ./pkg/dist
      -for PLATFORM in $(find ./pkg -mindepth 1 -maxdepth 1 -type d); do
      -    PLATFORM_NAME=$(basename ${PLATFORM})
      -    ARCHIVE_NAME="${VERSIONDIR}_${PLATFORM_NAME}"
      -
      -    if [ $PLATFORM_NAME = "dist" ]; then
      -        continue
      -    fi
      -
      -    (
      -    pushd ${PLATFORM}
      -    zip ${DIR}/pkg/dist/${ARCHIVE_NAME}.zip ./*
      -    popd
      -    ) &
      +for FILENAME in $(find ./pkg -mindepth 1 -maxdepth 1 -type f); do
      +    FILENAME=$(basename $FILENAME)
      +    cp ./pkg/${FILENAME} ./pkg/dist/packer_${VERSION}_${FILENAME}
       done
       
      -waitAll
      -
       # Make the checksums
       pushd ./pkg/dist
      -shasum -a256 * > ./${VERSIONDIR}_SHA256SUMS
      +shasum -a256 * > ./packer_${VERSION}_SHA256SUMS
       popd
       
      +# Upload
      +for ARCHIVE in ./pkg/dist/*; do
      +    ARCHIVE_NAME=$(basename ${ARCHIVE})
      +
      +    echo Uploading: $ARCHIVE_NAME
      +    curl \
      +        -T ${ARCHIVE} \
      +        -umitchellh:${BINTRAY_API_KEY} \
      +        "https://api.bintray.com/content/mitchellh/packer/packer/${VERSION}/${ARCHIVE_NAME}"
      +done
      +
       exit 0
      
      From 7097c75610c43c655b8c24b80f34c086a4529b5f Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 15:14:42 -0700
      Subject: [PATCH 323/593] Update travis
      
      ---
       .travis.yml | 5 +++--
       1 file changed, 3 insertions(+), 2 deletions(-)
      
      diff --git a/.travis.yml b/.travis.yml
      index 1a81bef54..6e00ed8ae 100644
      --- a/.travis.yml
      +++ b/.travis.yml
      @@ -2,11 +2,12 @@ language: go
       
       go:
           - 1.2
      +    - 1.3
           - tip
       
      -install: make deps
      +install: make updatedeps
       script:
      -    - go test ./...
      +    - make test
           #- go test -race ./...
       
       notifications:
      
      From 60182afb0bc82ff5fddc027ad56a00d26e1f1f5e Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 15:29:37 -0700
      Subject: [PATCH 324/593] Automatically discover plugins
      
      ---
       config.go | 143 +++++++++++++++++++++++++++++++++---------------------
       packer.go |  12 +++--
       2 files changed, 94 insertions(+), 61 deletions(-)
      
      diff --git a/config.go b/config.go
      index 44d362d26..26a1c19a7 100644
      --- a/config.go
      +++ b/config.go
      @@ -2,67 +2,16 @@ package main
       
       import (
       	"encoding/json"
      -	"github.com/mitchellh/osext"
      -	"github.com/mitchellh/packer/packer"
      -	"github.com/mitchellh/packer/packer/plugin"
       	"io"
       	"log"
       	"os/exec"
       	"path/filepath"
      -)
      +	"strings"
       
      -// This is the default, built-in configuration that ships with
      -// Packer.
      -const defaultConfig = `
      -{
      -	"plugin_min_port": 10000,
      -	"plugin_max_port": 25000,
      -
      -	"builders": {
      -		"amazon-ebs": "packer-builder-amazon-ebs",
      -		"amazon-chroot": "packer-builder-amazon-chroot",
      -		"amazon-instance": "packer-builder-amazon-instance",
      -		"digitalocean": "packer-builder-digitalocean",
      -		"docker": "packer-builder-docker",
      -		"googlecompute": "packer-builder-googlecompute",
      -		"openstack": "packer-builder-openstack",
      -		"qemu": "packer-builder-qemu",
      -		"virtualbox-iso": "packer-builder-virtualbox-iso",
      -		"virtualbox-ovf": "packer-builder-virtualbox-ovf",
      -		"vmware-iso": "packer-builder-vmware-iso",
      -		"vmware-vmx": "packer-builder-vmware-vmx",
      -		"parallels-iso": "packer-builder-parallels-iso",
      -		"parallels-pvm": "packer-builder-parallels-pvm",
      -		"null": "packer-builder-null"
      -	},
      -
      -	"commands": {
      -		"build": "packer-command-build",
      -		"fix": "packer-command-fix",
      -		"inspect": "packer-command-inspect",
      -		"validate": "packer-command-validate"
      -	},
      -
      -	"post-processors": {
      -		"vagrant": "packer-post-processor-vagrant",
      -		"vsphere": "packer-post-processor-vsphere",
      -		"docker-push": "packer-post-processor-docker-push",
      -		"docker-import": "packer-post-processor-docker-import",
      -		"vagrant-cloud": "packer-post-processor-vagrant-cloud"
      -	},
      -
      -	"provisioners": {
      -		"ansible-local": "packer-provisioner-ansible-local",
      -		"chef-client": "packer-provisioner-chef-client",
      -		"chef-solo": "packer-provisioner-chef-solo",
      -		"file": "packer-provisioner-file",
      -		"puppet-masterless": "packer-provisioner-puppet-masterless",
      -		"puppet-server": "packer-provisioner-puppet-server",
      -		"shell": "packer-provisioner-shell",
      -		"salt-masterless": "packer-provisioner-salt-masterless"
      -	}
      -}
      -`
      +	"github.com/mitchellh/osext"
      +	"github.com/mitchellh/packer/packer"
      +	"github.com/mitchellh/packer/packer/plugin"
      +)
       
       type config struct {
       	PluginMinPort uint
      @@ -81,6 +30,30 @@ func decodeConfig(r io.Reader, c *config) error {
       	return decoder.Decode(c)
       }
       
      +// Discover discovers plugins.
      +//
      +// This looks in the directory of the executable and the CWD, in that
      +// order for priority.
      +func (c *config) Discover() error {
      +	// Look in the cwd.
      +	if err := c.discover("."); err != nil {
      +		return err
      +	}
      +
      +	// Next, look in the same directory as the executable. Any conflicts
      +	// will overwrite those found in our current directory.
      +	exePath, err := osext.Executable()
      +	if err != nil {
      +		log.Printf("[ERR] Error loading exe directory: %s", err)
      +	} else {
      +		if err := c.discover(filepath.Dir(exePath)); err != nil {
      +			return err
      +		}
      +	}
      +
      +	return nil
      +}
      +
       // Returns an array of defined command names.
       func (c *config) CommandNames() (result []string) {
       	result = make([]string, 0, len(c.Commands))
      @@ -149,6 +122,64 @@ func (c *config) LoadProvisioner(name string) (packer.Provisioner, error) {
       	return c.pluginClient(bin).Provisioner()
       }
       
      +func (c *config) discover(path string) error {
      +	var err error
      +	err = c.discoverSingle(
      +		filepath.Join(path, "packer-builder-*"), &c.Builders)
      +	if err != nil {
      +		return err
      +	}
      +
      +	err = c.discoverSingle(
      +		filepath.Join(path, "packer-command-*"), &c.Commands)
      +	if err != nil {
      +		return err
      +	}
      +
      +	err = c.discoverSingle(
      +		filepath.Join(path, "packer-post-processor-*"), &c.PostProcessors)
      +	if err != nil {
      +		return err
      +	}
      +
      +	err = c.discoverSingle(
      +		filepath.Join(path, "packer-provisioner-*"), &c.Provisioners)
      +	if err != nil {
      +		return err
      +	}
      +
      +	return nil
      +}
      +
      +func (c *config) discoverSingle(glob string, m *map[string]string) error {
      +	matches, err := filepath.Glob(glob)
      +	if err != nil {
      +		return err
      +	}
      +
      +	if *m == nil {
      +		*m = make(map[string]string)
      +	}
      +
      +	prefix := filepath.Base(glob)
      +	prefix = prefix[:strings.Index(prefix, "*")]
      +	for _, match := range matches {
      +		file := filepath.Base(match)
      +
      +		// If the filename has a ".", trim up to there
      +		if idx := strings.Index(file, "."); idx >= 0 {
      +			file = file[:idx]
      +		}
      +
      +		// Look for foo-bar-baz. The plugin name is "baz"
      +		plugin := file[len(prefix):]
      +		log.Printf("[DEBUG] Discoverd plugin: %s = %s", plugin, match)
      +		(*m)[plugin] = match
      +	}
      +
      +	return nil
      +}
      +
       func (c *config) pluginClient(path string) *plugin.Client {
       	originalPath := path
       
      diff --git a/packer.go b/packer.go
      index 0674e503b..8bccf37cc 100644
      --- a/packer.go
      +++ b/packer.go
      @@ -2,17 +2,17 @@
       package main
       
       import (
      -	"bytes"
       	"fmt"
      -	"github.com/mitchellh/packer/packer"
      -	"github.com/mitchellh/packer/packer/plugin"
      -	"github.com/mitchellh/panicwrap"
       	"io"
       	"io/ioutil"
       	"log"
       	"os"
       	"path/filepath"
       	"runtime"
      +
      +	"github.com/mitchellh/packer/packer"
      +	"github.com/mitchellh/packer/packer/plugin"
      +	"github.com/mitchellh/panicwrap"
       )
       
       func main() {
      @@ -173,7 +173,9 @@ func extractMachineReadable(args []string) ([]string, bool) {
       
       func loadConfig() (*config, error) {
       	var config config
      -	if err := decodeConfig(bytes.NewBufferString(defaultConfig), &config); err != nil {
      +	config.PluginMinPort = 10000
      +	config.PluginMaxPort = 25000
      +	if err := config.Discover(); err != nil {
       		return nil, err
       	}
       
      
      From 31876427e35dbbc4167e1ab356a63a8f1ec28c4b Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Tue, 2 Sep 2014 15:30:14 -0700
      Subject: [PATCH 325/593] Update CHANGELOG
      
      ---
       CHANGELOG.md | 3 +++
       1 file changed, 3 insertions(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index 31d2ca3d2..15ac117bd 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -2,6 +2,9 @@
       
       FEATURES:
       
      +  * core: Plugins are automatically discovered if they're named properly.
      +      Packer will look in the PWD and the directory with `packer` for
      +      binaries named `packer-TYPE-NAME`.
         * builder/vmware: VMware Player 6 is now supported. [GH-1168]
       
       IMPROVEMENTS:
      
      From 414bf1748d54a4da7085fbd3b4c182c1bc1efb44 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Wed, 3 Sep 2014 20:12:10 -0700
      Subject: [PATCH 326/593] website: fix escaped underscores [GH-1458]
      
      ---
       .../environmental-variables.html.markdown     | 24 +++++++++++++------
       1 file changed, 17 insertions(+), 7 deletions(-)
      
      diff --git a/website/source/docs/other/environmental-variables.html.markdown b/website/source/docs/other/environmental-variables.html.markdown
      index 4a4e8907d..495889ab4 100644
      --- a/website/source/docs/other/environmental-variables.html.markdown
      +++ b/website/source/docs/other/environmental-variables.html.markdown
      @@ -7,16 +7,26 @@ page_title: "Environmental Variables for Packer"
       
       Packer uses a variety of environmental variables. A listing and description of each can be found below:
       
      -* 'PACKER_CACHE_DIR' - The location of the packer cache.
      +* `PACKER_CACHE_DIR` - The location of the packer cache.
       
      -* 'PACKER_CONFIG' - The location of the core configuration file. The format of the configuration file is basic JSON. See the [core configuration page](docs/other/core-configuration.html).
      +* `PACKER_CONFIG` - The location of the core configuration file. The format
      +     of the configuration file is basic JSON.
      +     See the [core configuration page](docs/other/core-configuration.html).
       
      -* 'PACKER_LOG' - Setting this to any value will enable the logger. See the [debugging page](docs/other/debugging.html).
      +* `PACKER_LOG` - Setting this to any value will enable the logger.
      +     See the [debugging page](docs/other/debugging.html).
       
      -* 'PACKER_LOG_PATH' - The location of the log file. Note: 'PACKER_LOG' must be set for any logging to occur. See the [debugging page](docs/other/debugging.html).
      +* `PACKER_LOG_PATH` - The location of the log file. Note: `PACKER_LOG` must
      +     be set for any logging to occur. See the [debugging page](docs/other/debugging.html).
       
      -* 'PACKER_NO_COLOR' - Setting this to any value will disable color in the terminal.
      +* `PACKER_NO_COLOR` - Setting this to any value will disable color in the terminal.
       
      -* 'PACKER_PLUGIN_MAX_PORT' - The maximum port that Packer uses for communication with plugins, since plugin communication happens over TCP connections on your local host. The default is 25,000. See the [core configuration page](docs/other/core-configuration.html).
      +* `PACKER_PLUGIN_MAX_PORT` - The maximum port that Packer uses for
      +     communication with plugins, since plugin communication happens over
      +     TCP connections on your local host. The default is 25,000.
      +     See the [core configuration page](docs/other/core-configuration.html).
       
      -* 'PACKER_PLUGIN_MIN_PORT' - The minimum port that Packer uses for communication with plugins, since plugin communication happens over TCP connections on your local host. The default is 10,000. See the [core configuration page](docs/other/core-configuration.html).
      +* `PACKER_PLUGIN_MIN_PORT` - The minimum port that Packer uses for
      +     communication with plugins, since plugin communication happens
      +     over TCP connections on your local host. The default is 10,000.
      +     See the [core configuration page](docs/other/core-configuration.html).
      
      From 76a82216368b42935fffb0ead6756df93987b3bb Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Wed, 3 Sep 2014 20:23:39 -0700
      Subject: [PATCH 327/593] builder/*: extract key path to ssh.Signer
      
      ---
       builder/parallels/common/ssh.go         | 28 ++++-------------------
       builder/parallels/common/ssh_config.go  |  6 +++--
       builder/qemu/builder.go                 | 10 +++++----
       builder/qemu/ssh.go                     | 28 ++++-------------------
       builder/virtualbox/common/ssh.go        | 28 ++++-------------------
       builder/virtualbox/common/ssh_config.go |  6 +++--
       builder/vmware/common/ssh.go            | 25 +++------------------
       builder/vmware/common/ssh_config.go     |  3 ++-
       common/ssh/key.go                       | 30 +++++++++++++++++++++++++
       9 files changed, 61 insertions(+), 103 deletions(-)
       create mode 100644 common/ssh/key.go
      
      diff --git a/builder/parallels/common/ssh.go b/builder/parallels/common/ssh.go
      index 04aed86a2..e914583c1 100644
      --- a/builder/parallels/common/ssh.go
      +++ b/builder/parallels/common/ssh.go
      @@ -1,12 +1,12 @@
       package common
       
       import (
      -	"code.google.com/p/go.crypto/ssh"
       	"fmt"
      +
      +	"code.google.com/p/go.crypto/ssh"
       	"github.com/mitchellh/multistep"
      +	commonssh "github.com/mitchellh/packer/common/ssh"
       	packerssh "github.com/mitchellh/packer/communicator/ssh"
      -	"io/ioutil"
      -	"os"
       )
       
       func SSHAddress(state multistep.StateBag) (string, error) {
      @@ -35,7 +35,7 @@ func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig
       		}
       
       		if config.SSHKeyPath != "" {
      -			signer, err := sshKeyToSigner(config.SSHKeyPath)
      +			signer, err := commonssh.FileSigner(config.SSHKeyPath)
       			if err != nil {
       				return nil, err
       			}
      @@ -49,23 +49,3 @@ func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig
       		}, nil
       	}
       }
      -
      -func sshKeyToSigner(path string) (ssh.Signer, error) {
      -	f, err := os.Open(path)
      -	if err != nil {
      -		return nil, err
      -	}
      -	defer f.Close()
      -
      -	keyBytes, err := ioutil.ReadAll(f)
      -	if err != nil {
      -		return nil, err
      -	}
      -
      -	signer, err := ssh.ParsePrivateKey(keyBytes)
      -	if err != nil {
      -		return nil, fmt.Errorf("Error setting up SSH config: %s", err)
      -	}
      -
      -	return signer, nil
      -}
      diff --git a/builder/parallels/common/ssh_config.go b/builder/parallels/common/ssh_config.go
      index f28fb3029..d89daa103 100644
      --- a/builder/parallels/common/ssh_config.go
      +++ b/builder/parallels/common/ssh_config.go
      @@ -3,9 +3,11 @@ package common
       import (
       	"errors"
       	"fmt"
      -	"github.com/mitchellh/packer/packer"
       	"os"
       	"time"
      +
      +	commonssh "github.com/mitchellh/packer/common/ssh"
      +	"github.com/mitchellh/packer/packer"
       )
       
       type SSHConfig struct {
      @@ -46,7 +48,7 @@ func (c *SSHConfig) Prepare(t *packer.ConfigTemplate) []error {
       	if c.SSHKeyPath != "" {
       		if _, err := os.Stat(c.SSHKeyPath); err != nil {
       			errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
      -		} else if _, err := sshKeyToSigner(c.SSHKeyPath); err != nil {
      +		} else if _, err := commonssh.FileSigner(c.SSHKeyPath); err != nil {
       			errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
       		}
       	}
      diff --git a/builder/qemu/builder.go b/builder/qemu/builder.go
      index 957f21fe2..971613dd8 100644
      --- a/builder/qemu/builder.go
      +++ b/builder/qemu/builder.go
      @@ -3,15 +3,17 @@ package qemu
       import (
       	"errors"
       	"fmt"
      -	"github.com/mitchellh/multistep"
      -	"github.com/mitchellh/packer/common"
      -	"github.com/mitchellh/packer/packer"
       	"log"
       	"os"
       	"os/exec"
       	"path/filepath"
       	"strings"
       	"time"
      +
      +	"github.com/mitchellh/multistep"
      +	"github.com/mitchellh/packer/common"
      +	commonssh "github.com/mitchellh/packer/common/ssh"
      +	"github.com/mitchellh/packer/packer"
       )
       
       const BuilderId = "transcend.qemu"
      @@ -352,7 +354,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
       		if _, err := os.Stat(b.config.SSHKeyPath); err != nil {
       			errs = packer.MultiErrorAppend(
       				errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
      -		} else if _, err := sshKeyToSigner(b.config.SSHKeyPath); err != nil {
      +		} else if _, err := commonssh.FileSigner(b.config.SSHKeyPath); err != nil {
       			errs = packer.MultiErrorAppend(
       				errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
       		}
      diff --git a/builder/qemu/ssh.go b/builder/qemu/ssh.go
      index d22f611bc..de7858166 100644
      --- a/builder/qemu/ssh.go
      +++ b/builder/qemu/ssh.go
      @@ -1,12 +1,12 @@
       package qemu
       
       import (
      -	gossh "code.google.com/p/go.crypto/ssh"
       	"fmt"
      +
      +	gossh "code.google.com/p/go.crypto/ssh"
       	"github.com/mitchellh/multistep"
      +	commonssh "github.com/mitchellh/packer/common/ssh"
       	"github.com/mitchellh/packer/communicator/ssh"
      -	"io/ioutil"
      -	"os"
       )
       
       func sshAddress(state multistep.StateBag) (string, error) {
      @@ -24,7 +24,7 @@ func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
       	}
       
       	if config.SSHKeyPath != "" {
      -		signer, err := sshKeyToSigner(config.SSHKeyPath)
      +		signer, err := commonssh.FileSigner(config.SSHKeyPath)
       		if err != nil {
       			return nil, err
       		}
      @@ -37,23 +37,3 @@ func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
       		Auth: auth,
       	}, nil
       }
      -
      -func sshKeyToSigner(path string) (gossh.Signer, error) {
      -	f, err := os.Open(path)
      -	if err != nil {
      -		return nil, err
      -	}
      -	defer f.Close()
      -
      -	keyBytes, err := ioutil.ReadAll(f)
      -	if err != nil {
      -		return nil, err
      -	}
      -
      -	signer, err := gossh.ParsePrivateKey(keyBytes)
      -	if err != nil {
      -		return nil, fmt.Errorf("Error setting up SSH config: %s", err)
      -	}
      -
      -	return signer, nil
      -}
      diff --git a/builder/virtualbox/common/ssh.go b/builder/virtualbox/common/ssh.go
      index 41188bf45..0cfc05d2c 100644
      --- a/builder/virtualbox/common/ssh.go
      +++ b/builder/virtualbox/common/ssh.go
      @@ -1,12 +1,12 @@
       package common
       
       import (
      -	gossh "code.google.com/p/go.crypto/ssh"
       	"fmt"
      +
      +	gossh "code.google.com/p/go.crypto/ssh"
       	"github.com/mitchellh/multistep"
      +	commonssh "github.com/mitchellh/packer/common/ssh"
       	"github.com/mitchellh/packer/communicator/ssh"
      -	"io/ioutil"
      -	"os"
       )
       
       func SSHAddress(state multistep.StateBag) (string, error) {
      @@ -23,7 +23,7 @@ func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*gossh.ClientConf
       		}
       
       		if config.SSHKeyPath != "" {
      -			signer, err := sshKeyToSigner(config.SSHKeyPath)
      +			signer, err := commonssh.FileSigner(config.SSHKeyPath)
       			if err != nil {
       				return nil, err
       			}
      @@ -37,23 +37,3 @@ func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*gossh.ClientConf
       		}, nil
       	}
       }
      -
      -func sshKeyToSigner(path string) (gossh.Signer, error) {
      -	f, err := os.Open(path)
      -	if err != nil {
      -		return nil, err
      -	}
      -	defer f.Close()
      -
      -	keyBytes, err := ioutil.ReadAll(f)
      -	if err != nil {
      -		return nil, err
      -	}
      -
      -	signer, err := gossh.ParsePrivateKey(keyBytes)
      -	if err != nil {
      -		return nil, fmt.Errorf("Error setting up SSH config: %s", err)
      -	}
      -
      -	return signer, nil
      -}
      diff --git a/builder/virtualbox/common/ssh_config.go b/builder/virtualbox/common/ssh_config.go
      index 00c6167c6..908fbb7d8 100644
      --- a/builder/virtualbox/common/ssh_config.go
      +++ b/builder/virtualbox/common/ssh_config.go
      @@ -3,9 +3,11 @@ package common
       import (
       	"errors"
       	"fmt"
      -	"github.com/mitchellh/packer/packer"
       	"os"
       	"time"
      +
      +	commonssh "github.com/mitchellh/packer/common/ssh"
      +	"github.com/mitchellh/packer/packer"
       )
       
       type SSHConfig struct {
      @@ -56,7 +58,7 @@ func (c *SSHConfig) Prepare(t *packer.ConfigTemplate) []error {
       	if c.SSHKeyPath != "" {
       		if _, err := os.Stat(c.SSHKeyPath); err != nil {
       			errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
      -		} else if _, err := sshKeyToSigner(c.SSHKeyPath); err != nil {
      +		} else if _, err := commonssh.FileSigner(c.SSHKeyPath); err != nil {
       			errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
       		}
       	}
      diff --git a/builder/vmware/common/ssh.go b/builder/vmware/common/ssh.go
      index a931dd734..5d2ca40e2 100644
      --- a/builder/vmware/common/ssh.go
      +++ b/builder/vmware/common/ssh.go
      @@ -1,14 +1,15 @@
       package common
       
       import (
      -	gossh "code.google.com/p/go.crypto/ssh"
       	"errors"
       	"fmt"
       	"io/ioutil"
       	"log"
       	"os"
       
      +	gossh "code.google.com/p/go.crypto/ssh"
       	"github.com/mitchellh/multistep"
      +	commonssh "github.com/mitchellh/packer/common/ssh"
       	"github.com/mitchellh/packer/communicator/ssh"
       )
       
      @@ -74,7 +75,7 @@ func SSHConfigFunc(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientCon
       		}
       
       		if config.SSHKeyPath != "" {
      -			signer, err := sshKeyToSigner(config.SSHKeyPath)
      +			signer, err := commonssh.FileSigner(config.SSHKeyPath)
       			if err != nil {
       				return nil, err
       			}
      @@ -88,23 +89,3 @@ func SSHConfigFunc(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientCon
       		}, nil
       	}
       }
      -
      -func sshKeyToSigner(path string) (gossh.Signer, error) {
      -	f, err := os.Open(path)
      -	if err != nil {
      -		return nil, err
      -	}
      -	defer f.Close()
      -
      -	keyBytes, err := ioutil.ReadAll(f)
      -	if err != nil {
      -		return nil, err
      -	}
      -
      -	signer, err := gossh.ParsePrivateKey(keyBytes)
      -	if err != nil {
      -		return nil, fmt.Errorf("Error setting up SSH config: %s", err)
      -	}
      -
      -	return signer, nil
      -}
      diff --git a/builder/vmware/common/ssh_config.go b/builder/vmware/common/ssh_config.go
      index f92b28931..75d0a4b75 100644
      --- a/builder/vmware/common/ssh_config.go
      +++ b/builder/vmware/common/ssh_config.go
      @@ -7,6 +7,7 @@ import (
       	"os"
       	"time"
       
      +	commonssh "github.com/mitchellh/packer/common/ssh"
       	"github.com/mitchellh/packer/packer"
       )
       
      @@ -51,7 +52,7 @@ func (c *SSHConfig) Prepare(t *packer.ConfigTemplate) []error {
       	if c.SSHKeyPath != "" {
       		if _, err := os.Stat(c.SSHKeyPath); err != nil {
       			errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
      -		} else if _, err := sshKeyToSigner(c.SSHKeyPath); err != nil {
      +		} else if _, err := commonssh.FileSigner(c.SSHKeyPath); err != nil {
       			errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
       		}
       	}
      diff --git a/common/ssh/key.go b/common/ssh/key.go
      new file mode 100644
      index 000000000..b26981f3d
      --- /dev/null
      +++ b/common/ssh/key.go
      @@ -0,0 +1,30 @@
      +package ssh
      +
      +import (
      +	"fmt"
      +	"io/ioutil"
      +	"os"
      +
      +	"code.google.com/p/go.crypto/ssh"
      +)
      +
      +// FileSigner returns an ssh.Signer for a key file.
      +func FileSigner(path string) (ssh.Signer, error) {
      +	f, err := os.Open(path)
      +	if err != nil {
      +		return nil, err
      +	}
      +	defer f.Close()
      +
      +	keyBytes, err := ioutil.ReadAll(f)
      +	if err != nil {
      +		return nil, err
      +	}
      +
      +	signer, err := ssh.ParsePrivateKey(keyBytes)
      +	if err != nil {
      +		return nil, fmt.Errorf("Error setting up SSH config: %s", err)
      +	}
      +
      +	return signer, nil
      +}
      
      From 01abbc44609dcd617b8d9d371aa1da9500cc8bad Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Wed, 3 Sep 2014 20:25:31 -0700
      Subject: [PATCH 328/593] common/ssh: error if encrypted key is used
      
      ---
       CHANGELOG.md      |  2 ++
       common/ssh/key.go | 14 ++++++++++++++
       2 files changed, 16 insertions(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index 15ac117bd..4bb88c5a6 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -20,6 +20,8 @@ IMPROVEMENTS:
       
       BUG FIXES:
       
      +  * core: nicer error message if an encrypted private key is used for
      +      SSH. [GH-1445]
         * builder/amazon-chroot: Can properly build HVM images now. [GH-1360]
         * builder/amazon-chroot: Fix crash in root device check. [GH-1360]
         * builder/amazon-instance: Fix deprecation warning for `ec2-bundle-vol`
      diff --git a/common/ssh/key.go b/common/ssh/key.go
      index b26981f3d..11a4b0742 100644
      --- a/common/ssh/key.go
      +++ b/common/ssh/key.go
      @@ -1,6 +1,7 @@
       package ssh
       
       import (
      +	"encoding/pem"
       	"fmt"
       	"io/ioutil"
       	"os"
      @@ -21,6 +22,19 @@ func FileSigner(path string) (ssh.Signer, error) {
       		return nil, err
       	}
       
      +	// We parse the private key on our own first so that we can
      +	// show a nicer error if the private key has a password.
      +	block, _ := pem.Decode(keyBytes)
      +	if block == nil {
      +		return nil, fmt.Errorf(
      +			"Failed to read key '%s': no key found", path)
      +	}
      +	if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
      +		return nil, fmt.Errorf(
      +			"Failed to read key '%s': password protected keys are\n"+
      +				"not supported. Please decrypt the key prior to use.", path)
      +	}
      +
       	signer, err := ssh.ParsePrivateKey(keyBytes)
       	if err != nil {
       		return nil, fmt.Errorf("Error setting up SSH config: %s", err)
      
      From 94f385ecf99acb97dae597c008f77502a6141715 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Wed, 3 Sep 2014 20:30:16 -0700
      Subject: [PATCH 329/593] builder/virtualbox: error if guest additions URL
       can't be read [GH-1439]
      
      ---
       CHANGELOG.md                                               | 2 ++
       builder/virtualbox/common/step_download_guest_additions.go | 7 +++++++
       2 files changed, 9 insertions(+)
      
      diff --git a/CHANGELOG.md b/CHANGELOG.md
      index 4bb88c5a6..450d2480b 100644
      --- a/CHANGELOG.md
      +++ b/CHANGELOG.md
      @@ -34,6 +34,8 @@ BUG FIXES:
         * builder/qemu: If headless, sdl display won't be used. [GH-1395]
         * builder/qemu: Use `512M` as `-m` default. [GH-1444]
         * builder/virtualbox/all: Seed RNG to avoid same ports. [GH-1386]
      +  * builder/virtualbox/all: Better error if guest additions URL couldn't be
      +      detected. [GH-1439]
         * builder/vmware/all: `ssh_host` accepts templates. [GH-1396]
         * builder/vmware/all: Don't remount floppy in VMX post step. [GH-1239]
         * builder/vmware/vmx: Do not re-add floppy disk files to VMX [GH-1361]
      diff --git a/builder/virtualbox/common/step_download_guest_additions.go b/builder/virtualbox/common/step_download_guest_additions.go
      index 9a5a46bf2..85af049ea 100644
      --- a/builder/virtualbox/common/step_download_guest_additions.go
      +++ b/builder/virtualbox/common/step_download_guest_additions.go
      @@ -91,6 +91,13 @@ func (s *StepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.Ste
       				additionsName)
       		}
       	}
      +	if url == "" {
      +		err := fmt.Errorf("Couldn't detect guest additions URL.\n" +
      +			"Please specify `guest_additions_url` manually.")
      +		state.Put("error", err)
      +		ui.Error(err.Error())
      +		return multi.ActionHalt
      +	}
       
       	if checksumType != "none" {
       		if s.GuestAdditionsSHA256 != "" {
      
      From 28de1225799e3227368dd68098b8115977828af5 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Wed, 3 Sep 2014 20:33:22 -0700
      Subject: [PATCH 330/593] builder/virtualbox: fix typo
      
      ---
       builder/virtualbox/common/step_download_guest_additions.go | 2 +-
       1 file changed, 1 insertion(+), 1 deletion(-)
      
      diff --git a/builder/virtualbox/common/step_download_guest_additions.go b/builder/virtualbox/common/step_download_guest_additions.go
      index 85af049ea..029c7d85b 100644
      --- a/builder/virtualbox/common/step_download_guest_additions.go
      +++ b/builder/virtualbox/common/step_download_guest_additions.go
      @@ -96,7 +96,7 @@ func (s *StepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.Ste
       			"Please specify `guest_additions_url` manually.")
       		state.Put("error", err)
       		ui.Error(err.Error())
      -		return multi.ActionHalt
      +		return multistep.ActionHalt
       	}
       
       	if checksumType != "none" {
      
      From 32c88c20efa7d2316e62a47460f05799a5b60559 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Wed, 3 Sep 2014 20:33:38 -0700
      Subject: [PATCH 331/593] Don't notify IRC
      
      ---
       .travis.yml | 7 -------
       1 file changed, 7 deletions(-)
      
      diff --git a/.travis.yml b/.travis.yml
      index 6e00ed8ae..3fd29699c 100644
      --- a/.travis.yml
      +++ b/.travis.yml
      @@ -10,13 +10,6 @@ script:
           - make test
           #- go test -race ./...
       
      -notifications:
      -    irc:
      -        channels:
      -            - "chat.freenode.net#packer-tool"
      -        on_success: change
      -        on_failure: always
      -
       matrix:
           allow_failures:
               - go: tip
      
      From 23e2387d61821231c1040c2a09a53bb999fc6ec6 Mon Sep 17 00:00:00 2001
      From: Mitchell Hashimoto 
      Date: Wed, 3 Sep 2014 20:38:01 -0700
      Subject: [PATCH 332/593] website: document needing knife [GH-1429]
      
      ---
       website/source/docs/provisioners/chef-client.html.markdown | 3 +++
       1 file changed, 3 insertions(+)
      
      diff --git a/website/source/docs/provisioners/chef-client.html.markdown b/website/source/docs/provisioners/chef-client.html.markdown
      index 15cb4dcb1..92a3af381 100644
      --- a/website/source/docs/provisioners/chef-client.html.markdown
      +++ b/website/source/docs/provisioners/chef-client.html.markdown
      @@ -27,6 +27,9 @@ remote machine and run Chef client.
       }
       
      +Note: to properly clean up the Chef node and client, you must have +`knife` on your path and properly configured. + ## Configuration Reference The reference of available configuration options is listed below. No From 749baa19c4940b4da67c7f23bb81cf0d92fde5ff Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 3 Sep 2014 20:54:02 -0700 Subject: [PATCH 333/593] builder/virtualbox/iso: append timestamp to name [GH-1365] --- CHANGELOG.md | 2 ++ builder/virtualbox/iso/builder.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 450d2480b..9d715b702 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ BUG FIXES: * builder/virtualbox/all: Seed RNG to avoid same ports. [GH-1386] * builder/virtualbox/all: Better error if guest additions URL couldn't be detected. [GH-1439] + * builder/virtualbox/iso: Append timestamp to default name for parallel + builds. [GH-1365] * builder/vmware/all: `ssh_host` accepts templates. [GH-1396] * builder/vmware/all: Don't remount floppy in VMX post step. [GH-1239] * builder/vmware/vmx: Do not re-add floppy disk files to VMX [GH-1361] diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index 466e1f687..f0acf83be 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -116,7 +116,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if b.config.VMName == "" { - b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) + b.config.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", b.config.PackerBuildName) } // Errors From f0bc4fd0b750a88101c829ab60eb482a38c0e8be Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 3 Sep 2014 21:01:23 -0700 Subject: [PATCH 334/593] website: update supported builders --- website/source/index.html.erb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/source/index.html.erb b/website/source/index.html.erb index 655b48737..4bb3a1d8b 100644 --- a/website/source/index.html.erb +++ b/website/source/index.html.erb @@ -53,7 +53,8 @@

      Works Great With

      Out of the box Packer comes with support to build images for - Amazon EC2, DigitalOcean, VirtualBox, and VMware. Support for + Amazon EC2, DigitalOcean, Google Compute Engine, QEMU, + VirtualBox, VMware, and more. Support for more platforms is on the way, and anyone can add new platforms via plugins.

      From c0f9dbde4108dbca8cb4d04345a44f989ca39675 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 3 Sep 2014 21:08:57 -0700 Subject: [PATCH 335/593] builder/virtualbox: search VBOX_MSI_INSTALL_PATH [GH-1337] --- CHANGELOG.md | 2 ++ builder/virtualbox/common/driver.go | 30 ++++++++++++++++++++--------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d715b702..980c0c500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ BUG FIXES: * builder/parallels/all: Added some navigation keys [GH-1442] * builder/qemu: If headless, sdl display won't be used. [GH-1395] * builder/qemu: Use `512M` as `-m` default. [GH-1444] + * builder/virtualbox/all: Search `VBOX_MSI_INSTALL_PATH` for path to + `VBoxManage` on Windows. [GH-1337] * builder/virtualbox/all: Seed RNG to avoid same ports. [GH-1386] * builder/virtualbox/all: Better error if guest additions URL couldn't be detected. [GH-1439] diff --git a/builder/virtualbox/common/driver.go b/builder/virtualbox/common/driver.go index 08d86eb4a..b95bf81ae 100644 --- a/builder/virtualbox/common/driver.go +++ b/builder/virtualbox/common/driver.go @@ -55,15 +55,16 @@ func NewDriver() (Driver, error) { // On Windows, we check VBOX_INSTALL_PATH env var for the path if runtime.GOOS == "windows" { - if installPath := os.Getenv("VBOX_INSTALL_PATH"); installPath != "" { - log.Printf("[DEBUG] builder/virtualbox: VBOX_INSTALL_PATH: %s", - installPath) - for _, path := range strings.Split(installPath, ";") { - path = filepath.Join(path, "VBoxManage.exe") - if _, err := os.Stat(path); err == nil { - vboxmanagePath = path - break - } + vars := []string{"VBOX_INSTALL_PATH", "VBOX_MSI_INSTALL_PATH"} + for _, key := range vars { + value := os.Getenv(key) + if value != "" { + log.Printf( + "[DEBUG] builder/virtualbox: %s = %s", key, value) + vboxmanagePath = findVBoxManageWindows(value) + } + if vboxmanagePath != "" { + break } } } @@ -84,3 +85,14 @@ func NewDriver() (Driver, error) { return driver, nil } + +func findVBoxManageWindows(paths string) string { + for _, path := range strings.Split(paths, ";") { + path = filepath.Join(path, "VBoxManage.exe") + if _, err := os.Stat(path); err == nil { + return path + } + } + + return "" +} From e9c2628a7797c49055d1e0d825ee100dbb412606 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 3 Sep 2014 21:11:43 -0700 Subject: [PATCH 336/593] packer/plugin: clean up log output by only using the base --- packer/plugin/client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packer/plugin/client.go b/packer/plugin/client.go index a06403f4f..15960f3dc 100644 --- a/packer/plugin/client.go +++ b/packer/plugin/client.go @@ -12,6 +12,7 @@ import ( "net" "os" "os/exec" + "path/filepath" "strings" "sync" "time" @@ -355,7 +356,7 @@ func (c *Client) logStderr(r io.Reader) { c.config.Stderr.Write([]byte(line)) line = strings.TrimRightFunc(line, unicode.IsSpace) - log.Printf("%s: %s", c.config.Cmd.Path, line) + log.Printf("%s: %s", filepath.Base(c.config.Cmd.Path), line) } if err == io.EOF { From ad51098a3eb869f847190c0e9e882ded092dbc68 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 3 Sep 2014 21:17:53 -0700 Subject: [PATCH 337/593] packer/rpc: don't panic on cache errors [GH-1328] --- CHANGELOG.md | 1 + packer/rpc/cache.go | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 980c0c500..5f39c64b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ BUG FIXES: * core: nicer error message if an encrypted private key is used for SSH. [GH-1445] + * core: Fix crash that could happen with a well timed double Ctrl-C. [GH-1328] * builder/amazon-chroot: Can properly build HVM images now. [GH-1360] * builder/amazon-chroot: Fix crash in root device check. [GH-1360] * builder/amazon-instance: Fix deprecation warning for `ec2-bundle-vol` diff --git a/packer/rpc/cache.go b/packer/rpc/cache.go index 184286411..a4539ae66 100644 --- a/packer/rpc/cache.go +++ b/packer/rpc/cache.go @@ -2,6 +2,7 @@ package rpc import ( "github.com/mitchellh/packer/packer" + "log" "net/rpc" ) @@ -24,7 +25,8 @@ type CacheRLockResponse struct { func (c *cache) Lock(key string) (result string) { if err := c.client.Call("Cache.Lock", key, &result); err != nil { - panic(err) + log.Printf("[ERR] Cache.Lock error: %s", err) + return } return @@ -33,7 +35,8 @@ func (c *cache) Lock(key string) (result string) { func (c *cache) RLock(key string) (string, bool) { var result CacheRLockResponse if err := c.client.Call("Cache.RLock", key, &result); err != nil { - panic(err) + log.Printf("[ERR] Cache.RLock error: %s", err) + return "", false } return result.Path, result.Exists @@ -41,13 +44,15 @@ func (c *cache) RLock(key string) (string, bool) { func (c *cache) Unlock(key string) { if err := c.client.Call("Cache.Unlock", key, new(interface{})); err != nil { - panic(err) + log.Printf("[ERR] Cache.Unlock error: %s", err) + return } } func (c *cache) RUnlock(key string) { if err := c.client.Call("Cache.RUnlock", key, new(interface{})); err != nil { - panic(err) + log.Printf("[ERR] Cache.RUnlock error: %s", err) + return } } From dc74c2bbddffc8e4d9891ef674874a76d2bd4a29 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 3 Sep 2014 21:27:43 -0700 Subject: [PATCH 338/593] builder/amazon: tests --- builder/amazon/common/block_device_test.go | 63 ++++++++++++---------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/builder/amazon/common/block_device_test.go b/builder/amazon/common/block_device_test.go index 2e671e3d6..838b23aec 100644 --- a/builder/amazon/common/block_device_test.go +++ b/builder/amazon/common/block_device_test.go @@ -7,38 +7,47 @@ import ( ) func TestBlockDevice(t *testing.T) { - ec2Mapping := []ec2.BlockDeviceMapping{ - ec2.BlockDeviceMapping{ - DeviceName: "/dev/sdb", - VirtualName: "ephemeral0", - SnapshotId: "snap-1234", - VolumeType: "standard", - VolumeSize: 8, - DeleteOnTermination: true, - IOPS: 1000, + cases := []struct { + Config *BlockDevice + Result *ec2.BlockDeviceMapping + }{ + { + Config: &BlockDevice{ + DeviceName: "/dev/sdb", + VirtualName: "ephemeral0", + SnapshotId: "snap-1234", + VolumeType: "standard", + VolumeSize: 8, + DeleteOnTermination: true, + IOPS: 1000, + }, + + Result: &ec2.BlockDeviceMapping{ + DeviceName: "/dev/sdb", + VirtualName: "ephemeral0", + SnapshotId: "snap-1234", + VolumeType: "standard", + VolumeSize: 8, + DeleteOnTermination: true, + IOPS: 1000, + }, }, } - blockDevice := BlockDevice{ - DeviceName: "/dev/sdb", - VirtualName: "ephemeral0", - SnapshotId: "snap-1234", - VolumeType: "standard", - VolumeSize: 8, - DeleteOnTermination: true, - IOPS: 1000, - } + for _, tc := range cases { + blockDevices := BlockDevices{ + AMIMappings: []BlockDevice{*tc.Config}, + LaunchMappings: []BlockDevice{*tc.Config}, + } - blockDevices := BlockDevices{ - AMIMappings: []BlockDevice{blockDevice}, - LaunchMappings: []BlockDevice{blockDevice}, - } + expected := []ec2.BlockDeviceMapping{*tc.Result} - if !reflect.DeepEqual(ec2Mapping, blockDevices.BuildAMIDevices()) { - t.Fatalf("bad: %#v", ec2Mapping) - } + if !reflect.DeepEqual(expected, blockDevices.BuildAMIDevices()) { + t.Fatalf("bad: %#v", expected) + } - if !reflect.DeepEqual(ec2Mapping, blockDevices.BuildLaunchDevices()) { - t.Fatalf("bad: %#v", ec2Mapping) + if !reflect.DeepEqual(expected, blockDevices.BuildLaunchDevices()) { + t.Fatalf("bad: %#v", expected) + } } } From 67afff5edefd19990d44dbb2fce1ba36de59e233 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 3 Sep 2014 21:27:54 -0700 Subject: [PATCH 339/593] fmt --- builder/vmware/common/driver_player5.go | 2 +- builder/vmware/common/step_configure_vmx.go | 4 ++-- builder/vmware/iso/builder.go | 2 +- builder/vmware/vmx/builder.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/builder/vmware/common/driver_player5.go b/builder/vmware/common/driver_player5.go index 2444ca662..5bb80a0f2 100644 --- a/builder/vmware/common/driver_player5.go +++ b/builder/vmware/common/driver_player5.go @@ -198,4 +198,4 @@ func (d *Player5Driver) DhcpLeasesPath(device string) string { func (d *Player5Driver) VmnetnatConfPath() string { return playerVmnetnatConfPath() -} \ No newline at end of file +} diff --git a/builder/vmware/common/step_configure_vmx.go b/builder/vmware/common/step_configure_vmx.go index 556975023..401d53055 100644 --- a/builder/vmware/common/step_configure_vmx.go +++ b/builder/vmware/common/step_configure_vmx.go @@ -18,7 +18,7 @@ import ( // vmx_path string type StepConfigureVMX struct { CustomData map[string]string - SkipFloppy bool + SkipFloppy bool } func (s *StepConfigureVMX) Run(state multistep.StateBag) multistep.StepAction { @@ -58,7 +58,7 @@ func (s *StepConfigureVMX) Run(state multistep.StateBag) multistep.StepAction { } // Set a floppy disk, but only if we should - if ! s.SkipFloppy { + if !s.SkipFloppy { // Set a floppy disk if we have one if floppyPathRaw, ok := state.GetOk("floppy_path"); ok { log.Println("Floppy path present, setting in VMX") diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index f9c752e23..7c90b298d 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -369,7 +369,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &vmwcommon.StepCleanFiles{}, &vmwcommon.StepConfigureVMX{ CustomData: b.config.VMXDataPost, - SkipFloppy: true, + SkipFloppy: true, }, &vmwcommon.StepCleanVMX{}, &vmwcommon.StepCompactDisk{ diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index fb61b2559..61c5f9da6 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -96,7 +96,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &vmwcommon.StepCleanFiles{}, &vmwcommon.StepConfigureVMX{ CustomData: b.config.VMXDataPost, - SkipFloppy: true, + SkipFloppy: true, }, &vmwcommon.StepCleanVMX{}, &vmwcommon.StepCompactDisk{ From c7dffaf12f175659598e4d296ebd3c4c52b48b20 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 3 Sep 2014 21:28:37 -0700 Subject: [PATCH 340/593] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f39c64b9..881dcba65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ BUG FIXES: * builder/amazon-chroot: Fix crash in root device check. [GH-1360] * builder/amazon-instance: Fix deprecation warning for `ec2-bundle-vol` [GH-1424] + * builder/amazon/all: `delete_on_termination` set to false will work. * builder/googlecompute: add `disk_size` option. [GH-1397] * builder/openstack: Region is not required. [GH-1418] * builder/parallels-iso: ISO not removed from VM after install [GH-1338] From 847cb02eb38821394a4f2a13f378764932bb8fc3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 3 Sep 2014 21:31:34 -0700 Subject: [PATCH 341/593] packer: fuzzy search broken pipe too [GH-1314] --- CHANGELOG.md | 3 ++- packer/ui.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 881dcba65..ad4417fd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,8 @@ BUG FIXES: * core: nicer error message if an encrypted private key is used for SSH. [GH-1445] - * core: Fix crash that could happen with a well timed double Ctrl-C. [GH-1328] + * core: Fix crash that could happen with a well timed double Ctrl-C. + [GH-1328] [GH-1314] * builder/amazon-chroot: Can properly build HVM images now. [GH-1360] * builder/amazon-chroot: Fix crash in root device check. [GH-1360] * builder/amazon-instance: Fix deprecation warning for `ec2-bundle-vol` diff --git a/packer/ui.go b/packer/ui.go index 4b082b38e..afd21a488 100644 --- a/packer/ui.go +++ b/packer/ui.go @@ -289,7 +289,7 @@ func (u *MachineReadableUi) Machine(category string, args ...string) { _, err := fmt.Fprintf(u.Writer, "%d,%s,%s,%s\n", now.Unix(), target, category, argsString) if err != nil { - if err == syscall.EPIPE { + if err == syscall.EPIPE || strings.Contains(err.Error(), "broken pipe") { // Ignore epipe errors because that just means that the file // is probably closed or going to /dev/null or something. } else { From 0d35473d415c5505520d9560b87457893575ac0b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 4 Sep 2014 11:24:01 -0700 Subject: [PATCH 342/593] communicator/ssh: make TCP keep-alive period shorter [GH-1232] --- CHANGELOG.md | 1 + communicator/ssh/connect.go | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad4417fd8..0822340f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ BUG FIXES: SSH. [GH-1445] * core: Fix crash that could happen with a well timed double Ctrl-C. [GH-1328] [GH-1314] + * core: SSH TCP keepalive period is now 5 seconds (shorter). [GH-1232] * builder/amazon-chroot: Can properly build HVM images now. [GH-1360] * builder/amazon-chroot: Fix crash in root device check. [GH-1360] * builder/amazon-instance: Fix deprecation warning for `ec2-bundle-vol` diff --git a/communicator/ssh/connect.go b/communicator/ssh/connect.go index 1a01e024c..b280f3ead 100644 --- a/communicator/ssh/connect.go +++ b/communicator/ssh/connect.go @@ -17,6 +17,7 @@ func ConnectFunc(network, addr string) func() (net.Conn, error) { if tcpConn, ok := c.(*net.TCPConn); ok { tcpConn.SetKeepAlive(true) + tcpConn.SetKeepAlivePeriod(5 * time.Second) } return c, nil From a2f06c2b5fc4c43a95f926d1a93641e13fd7e0ed Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 4 Sep 2014 11:26:53 -0700 Subject: [PATCH 343/593] website: fix docs [GH-1222] --- website/source/docs/post-processors/docker-import.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/post-processors/docker-import.html.markdown b/website/source/docs/post-processors/docker-import.html.markdown index e2ca3c93b..5bfa399d4 100644 --- a/website/source/docs/post-processors/docker-import.html.markdown +++ b/website/source/docs/post-processors/docker-import.html.markdown @@ -17,7 +17,7 @@ to a registry. ## Configuration The configuration for this post-processor is extremely simple. At least -a repository is required. The tag is optional. +a repository is required. * `repository` (string) - The repository of the imported image. From 84237ec98f104e2fa20e33f7ea81fd14f61c508b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 4 Sep 2014 11:35:27 -0700 Subject: [PATCH 344/593] Add Vagrantfile for cross cmpiling --- .gitignore | 1 - Vagrantfile | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 Vagrantfile diff --git a/.gitignore b/.gitignore index d9d37c96f..a0e0b48d8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,4 @@ /website/build .DS_Store .vagrant -Vagrantfile test/.env diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 000000000..82d2d96b9 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,46 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +$script = < - - -