From d37f287f8c7f2693b8aa03fa392a4a94459aa52f Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 1 Aug 2013 20:05:23 -0400 Subject: [PATCH 001/173] Added basic Puppet provisioner --- config.go | 3 +- plugin/provisioner-puppet/main.go | 10 + provisioner/puppet/provisioner.go | 262 +++++++++++++++++++++++++ provisioner/puppet/provisioner_test.go | 20 ++ 4 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 plugin/provisioner-puppet/main.go create mode 100644 provisioner/puppet/provisioner.go create mode 100644 provisioner/puppet/provisioner_test.go diff --git a/config.go b/config.go index 9b2c22a44..207662e4d 100644 --- a/config.go +++ b/config.go @@ -43,7 +43,8 @@ const defaultConfig = ` "chef-solo": "packer-provisioner-chef-solo", "file": "packer-provisioner-file", "shell": "packer-provisioner-shell", - "salt-masterless": "packer-provisioner-salt-masterless" + "salt-masterless": "packer-provisioner-salt-masterless", + "puppet": "packer-provisioner-puppet" } } ` diff --git a/plugin/provisioner-puppet/main.go b/plugin/provisioner-puppet/main.go new file mode 100644 index 000000000..09f330bce --- /dev/null +++ b/plugin/provisioner-puppet/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/mitchellh/packer/packer/plugin" + "github.com/mitchellh/packer/provisioner/puppet" +) + +func main() { + plugin.ServeProvisioner(new(puppet.Provisioner)) +} diff --git a/provisioner/puppet/provisioner.go b/provisioner/puppet/provisioner.go new file mode 100644 index 000000000..5eb6d7320 --- /dev/null +++ b/provisioner/puppet/provisioner.go @@ -0,0 +1,262 @@ +// This package implements a provisioner for Packer that executes +// Puppet within the remote machine +package puppet + +import ( + "bytes" + "fmt" + "github.com/mitchellh/iochan" + "github.com/mitchellh/mapstructure" + "github.com/mitchellh/packer/packer" + "io" + "log" + "os" + "path/filepath" + "strings" + "text/template" +) + +const ( + RemoteStagingPath = "/tmp/provision/puppet" + DefaultModulePath = "modules" + DefaultManifestPath = "manifests" + DefaultManifestFile = "site.pp" +) + +var Ui packer.Ui + +type config struct { + // An array of local paths of modules to upload. + ModulePath string `mapstructure:"module_path"` + + // Path to the manifests + ManifestPath string `mapstructure:"manifest_path"` + + // Manifest file + ManifestFile string `mapstructure:"manifest_file"` + + // Option to avoid sudo use when executing commands. Defaults to false. + PreventSudo bool `mapstructure:"prevent_sudo"` +} + +type Provisioner struct { + config config +} + +type ExecuteManifestTemplate struct { + Sudo bool + Modulepath string + Manifest string +} + +func (p *Provisioner) Prepare(raws ...interface{}) error { + errs := make([]error, 0) + for _, raw := range raws { + if err := mapstructure.Decode(raw, &p.config); err != nil { + return err + } + } + + if p.config.ModulePath == "" { + p.config.ModulePath = DefaultModulePath + } + + if p.config.ManifestPath == "" { + p.config.ManifestPath = DefaultManifestPath + } + + if p.config.ManifestFile == "" { + p.config.ManifestFile = DefaultManifestFile + } + + if p.config.ModulePath != "" { + pFileInfo, err := os.Stat(p.config.ModulePath) + + if err != nil || !pFileInfo.IsDir() { + errs = append(errs, fmt.Errorf("Bad module path '%s': %s", p.config.ModulePath, err)) + } + } + + if p.config.ManifestPath != "" { + pFileInfo, err := os.Stat(p.config.ManifestPath) + + if err != nil || !pFileInfo.IsDir() { + errs = append(errs, fmt.Errorf("Bad manifest path '%s': %s", p.config.ManifestPath, err)) + } + } + + if p.config.ManifestFile != "" { + path := filepath.Join(p.config.ManifestPath, p.config.ManifestFile) + if _, err := os.Stat(path); os.IsNotExist(err) { + errs = append(errs, fmt.Errorf("No manifest file '%s': %s", path, err)) + } + } + + if len(errs) > 0 { + return &packer.MultiError{errs} + } + + return nil +} + +func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { + var err error + Ui = ui + + err = CreateRemoteDirectory(RemoteStagingPath, comm) + if err != nil { + return fmt.Errorf("Error creating remote staging directory: %s", err) + } + + // Upload all modules + ui.Say(fmt.Sprintf("Copying module path: %s", p.config.ModulePath)) + err = UploadLocalDirectory(p.config.ModulePath, comm) + if err != nil { + return fmt.Errorf("Error uploading modules: %s", err) + } + + // Upload manifests + ui.Say(fmt.Sprintf("Copying manifests: %s", p.config.ManifestPath)) + err = UploadLocalDirectory(p.config.ManifestPath, comm) + if err != nil { + return fmt.Errorf("Error uploading manifests: %s", err) + } + + // Execute Puppet + ui.Say("Beginning Puppet run") + + // Compile the command + var command bytes.Buffer + mpath := filepath.Join(RemoteStagingPath, p.config.ManifestPath) + manifest := filepath.Join(mpath, p.config.ManifestFile) + modulepath := filepath.Join(RemoteStagingPath, p.config.ModulePath) + t := template.Must(template.New("puppet-run").Parse("{{if .Sudo}}sudo {{end}}puppet apply --verbose --modulepath={{.Modulepath}} {{.Manifest}}")) + t.Execute(&command, &ExecuteManifestTemplate{!p.config.PreventSudo, modulepath, manifest}) + + err = executeCommand(command.String(), comm) + if err != nil { + return fmt.Errorf("Error running Puppet: %s", err) + } + + return nil +} + +func (p *Provisioner) Cancel() { + // Just hard quit. It isn't a big deal if what we're doing keeps + // running on the other side. + os.Exit(0) +} + +func UploadLocalDirectory(localDir string, comm packer.Communicator) (err error) { + visitPath := func(path string, f os.FileInfo, err error) (err2 error) { + var remotePath = RemoteStagingPath + "/" + path + if f.IsDir() { + // Make remote directory + err = CreateRemoteDirectory(remotePath, comm) + if err != nil { + return err + } + } else { + // Upload file to existing directory + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("Error opening file: %s", err) + } + + err = comm.Upload(remotePath, file) + if err != nil { + return fmt.Errorf("Error uploading file: %s", err) + } + } + return + } + + log.Printf("Uploading directory %s", localDir) + err = filepath.Walk(localDir, visitPath) + if err != nil { + return fmt.Errorf("Error uploading modules %s: %s", localDir, err) + } + + return nil +} + +func CreateRemoteDirectory(path string, comm packer.Communicator) (err error) { + log.Printf("Creating remote directory: %s ", path) + + var copyCommand = []string{"mkdir -p", path} + + var cmd packer.RemoteCmd + cmd.Command = strings.Join(copyCommand, " ") + + var stdout bytes.Buffer + cmd.Stdout = &stdout + + // Start the command + if err := comm.Start(&cmd); err != nil { + return fmt.Errorf("Unable to create remote directory %s: %d", path, err) + } + + // Wait for it to complete + cmd.Wait() + + return +} + +func executeCommand(command string, comm packer.Communicator) (err error) { + // Setup the remote command + stdout_r, stdout_w := io.Pipe() + stderr_r, stderr_w := io.Pipe() + + var cmd packer.RemoteCmd + cmd.Command = command + cmd.Stdout = stdout_w + cmd.Stderr = stderr_w + + log.Printf("Executing command: %s", cmd.Command) + err = comm.Start(&cmd) + if err != nil { + return fmt.Errorf("Failed executing command: %s", err) + } + + exitChan := make(chan int, 1) + stdoutChan := iochan.DelimReader(stdout_r, '\n') + stderrChan := iochan.DelimReader(stderr_r, '\n') + + go func() { + defer stdout_w.Close() + defer stderr_w.Close() + + cmd.Wait() + exitChan <- cmd.ExitStatus + }() + +OutputLoop: + for { + select { + case output := <-stderrChan: + Ui.Message(strings.TrimSpace(output)) + case output := <-stdoutChan: + Ui.Message(strings.TrimSpace(output)) + case exitStatus := <-exitChan: + log.Printf("Puppet provisioner exited with status %d", exitStatus) + + if exitStatus != 0 { + return fmt.Errorf("Command exited with non-zero exit status: %d", exitStatus) + } + + break OutputLoop + } + } + + // Make sure we finish off stdout/stderr because we may have gotten + // a message from the exit channel first. + for output := range stdoutChan { + Ui.Message(output) + } + + for output := range stderrChan { + Ui.Message(output) + } + + return nil +} diff --git a/provisioner/puppet/provisioner_test.go b/provisioner/puppet/provisioner_test.go new file mode 100644 index 000000000..8e62e4649 --- /dev/null +++ b/provisioner/puppet/provisioner_test.go @@ -0,0 +1,20 @@ +package puppet + +import ( + "github.com/mitchellh/packer/packer" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + // "inline": []interface{}{"foo", "bar"}, + } +} + +func TestProvisioner_Impl(t *testing.T) { + var raw interface{} + raw = &Provisioner{} + if _, ok := raw.(packer.Provisioner); !ok { + t.Fatalf("must be a Provisioner") + } +} From 5bca569fa48b8bd51d1349a55cee5f3721ef31b3 Mon Sep 17 00:00:00 2001 From: James Massara Date: Wed, 4 Sep 2013 16:06:06 -0700 Subject: [PATCH 002/173] builder/amazon: Re-ordered steps for AMI region copying so tags and launch permissions are also applied to copied AMIs --- builder/amazon/chroot/builder.go | 7 ++--- builder/amazon/common/step_ami_region_copy.go | 20 ------------- builder/amazon/common/step_create_tags.go | 29 ++++++++++--------- .../common/step_modify_ami_attributes.go | 23 ++++++++------- builder/amazon/ebs/builder.go | 7 ++--- builder/amazon/instance/builder.go | 7 ++--- 6 files changed, 38 insertions(+), 55 deletions(-) diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index 013394ee7..f2f3a10e8 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -189,15 +189,14 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &StepEarlyCleanup{}, &StepSnapshot{}, &StepRegisterAMI{}, + &awscommon.StepAMIRegionCopy{ + Regions: b.config.AMIRegions, + }, &awscommon.StepModifyAMIAttributes{ Description: b.config.AMIDescription, Users: b.config.AMIUsers, Groups: b.config.AMIGroups, }, - &awscommon.StepAMIRegionCopy{ - Regions: b.config.AMIRegions, - Tags: b.config.AMITags, - }, &awscommon.StepCreateTags{ Tags: b.config.AMITags, }, diff --git a/builder/amazon/common/step_ami_region_copy.go b/builder/amazon/common/step_ami_region_copy.go index 109e6de6d..8d50f5dfc 100644 --- a/builder/amazon/common/step_ami_region_copy.go +++ b/builder/amazon/common/step_ami_region_copy.go @@ -10,7 +10,6 @@ import ( type StepAMIRegionCopy struct { Regions []string - Tags map[string]string } func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction { @@ -49,25 +48,6 @@ func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - // Need to re-apply Tags since they are not copied with the AMI - if len(s.Tags) > 0 { - ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", resp.ImageId)) - - var ec2Tags []ec2.Tag - for key, value := range s.Tags { - ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value)) - ec2Tags = append(ec2Tags, ec2.Tag{key, value}) - } - - _, err := regionconn.CreateTags([]string{resp.ImageId}, ec2Tags) - if err != nil { - err := fmt.Errorf("Error adding tags to AMI (%s): %s", resp.ImageId, err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - } - amis[region] = resp.ImageId } diff --git a/builder/amazon/common/step_create_tags.go b/builder/amazon/common/step_create_tags.go index 030211509..a204ca321 100644 --- a/builder/amazon/common/step_create_tags.go +++ b/builder/amazon/common/step_create_tags.go @@ -2,6 +2,7 @@ package common import ( "fmt" + "github.com/mitchellh/goamz/aws" "github.com/mitchellh/goamz/ec2" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" @@ -15,23 +16,25 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction { ec2conn := state.Get("ec2").(*ec2.EC2) ui := state.Get("ui").(packer.Ui) amis := state.Get("amis").(map[string]string) - ami := amis[ec2conn.Region.Name] if len(s.Tags) > 0 { - ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami)) + for region, ami := range amis { + ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami)) - var ec2Tags []ec2.Tag - for key, value := range s.Tags { - ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value)) - ec2Tags = append(ec2Tags, ec2.Tag{key, value}) - } + var ec2Tags []ec2.Tag + for key, value := range s.Tags { + ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value)) + ec2Tags = append(ec2Tags, ec2.Tag{key, value}) + } - _, err := ec2conn.CreateTags([]string{ami}, ec2Tags) - if err != nil { - err := fmt.Errorf("Error adding tags to AMI (%s): %s", ami, err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt + regionconn := ec2.New(ec2conn.Auth, aws.Regions[region]) + _, err := regionconn.CreateTags([]string{ami}, ec2Tags) + if err != nil { + err := fmt.Errorf("Error adding tags to AMI (%s): %s", ami, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } } } diff --git a/builder/amazon/common/step_modify_ami_attributes.go b/builder/amazon/common/step_modify_ami_attributes.go index 25ba79c2c..533d4cfd9 100644 --- a/builder/amazon/common/step_modify_ami_attributes.go +++ b/builder/amazon/common/step_modify_ami_attributes.go @@ -2,6 +2,7 @@ package common import ( "fmt" + "github.com/mitchellh/goamz/aws" "github.com/mitchellh/goamz/ec2" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" @@ -18,7 +19,6 @@ func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAc ec2conn := state.Get("ec2").(*ec2.EC2) ui := state.Get("ui").(packer.Ui) amis := state.Get("amis").(map[string]string) - ami := amis[ec2conn.Region.Name] // Determine if there is any work to do. valid := false @@ -59,15 +59,18 @@ func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAc } } - ui.Say("Modifying AMI attributes...") - for name, opts := range options { - ui.Message(fmt.Sprintf("Modifying: %s", name)) - _, err := ec2conn.ModifyImageAttribute(ami, opts) - if err != nil { - err := fmt.Errorf("Error modify AMI attributes: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt + for region, ami := range amis { + ui.Say(fmt.Sprintf("Modifying attributes on AMI (%s)...", ami)) + regionconn := ec2.New(ec2conn.Auth, aws.Regions[region]) + for name, opts := range options { + ui.Message(fmt.Sprintf("Modifying: %s", name)) + _, err := regionconn.ModifyImageAttribute(ami, opts) + if err != nil { + err := fmt.Errorf("Error modify AMI attributes: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } } } diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index 5c6cd16dd..ca899ab6f 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -109,15 +109,14 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepProvision{}, &stepStopInstance{}, &stepCreateAMI{}, + &awscommon.StepAMIRegionCopy{ + Regions: b.config.AMIRegions, + }, &awscommon.StepModifyAMIAttributes{ Description: b.config.AMIDescription, Users: b.config.AMIUsers, Groups: b.config.AMIGroups, }, - &awscommon.StepAMIRegionCopy{ - Regions: b.config.AMIRegions, - Tags: b.config.AMITags, - }, &awscommon.StepCreateTags{ Tags: b.config.AMITags, }, diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index 2fd5b0664..10fd3516e 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -214,16 +214,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &StepBundleVolume{}, &StepUploadBundle{}, &StepRegisterAMI{}, + &awscommon.StepAMIRegionCopy{ + Regions: b.config.AMIRegions, + }, &awscommon.StepModifyAMIAttributes{ Description: b.config.AMIDescription, Users: b.config.AMIUsers, Groups: b.config.AMIGroups, ProductCodes: b.config.AMIProductCodes, }, - &awscommon.StepAMIRegionCopy{ - Regions: b.config.AMIRegions, - Tags: b.config.AMITags, - }, &awscommon.StepCreateTags{ Tags: b.config.AMITags, }, From 985ce790d0b3952822b27520619c07be21d26df1 Mon Sep 17 00:00:00 2001 From: James Massara Date: Wed, 4 Sep 2013 17:48:00 -0700 Subject: [PATCH 003/173] Also deregister AMI if it was copied to other regions --- builder/amazon/common/artifact.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/builder/amazon/common/artifact.go b/builder/amazon/common/artifact.go index af6d1f2d4..3913833d7 100644 --- a/builder/amazon/common/artifact.go +++ b/builder/amazon/common/artifact.go @@ -2,6 +2,7 @@ package common import ( "fmt" + "github.com/mitchellh/goamz/aws" "github.com/mitchellh/goamz/ec2" "github.com/mitchellh/packer/packer" "log" @@ -51,9 +52,10 @@ func (a *Artifact) String() string { func (a *Artifact) Destroy() error { errors := make([]error, 0) - for _, imageId := range a.Amis { - log.Printf("Deregistering image ID: %s", imageId) - if _, err := a.Conn.DeregisterImage(imageId); err != nil { + for region, imageId := range a.Amis { + log.Printf("Deregistering image ID (%s) from region (%s)", imageId, region) + regionconn := ec2.New(a.Conn.Auth, aws.Regions[region]) + if _, err := regionconn.DeregisterImage(imageId); err != nil { errors = append(errors, err) } From 302871113aad717a6c15d618efc9ad02a21e27de Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Sep 2013 21:20:41 -0700 Subject: [PATCH 004/173] builder/digitalocean: retry on any pending event errors /cc @pearkes - I hate this thing. --- CHANGELOG.md | 1 + builder/digitalocean/api.go | 80 ++++++++++++--------- builder/digitalocean/builder.go | 16 ----- builder/digitalocean/builder_test.go | 32 --------- builder/digitalocean/step_create_droplet.go | 7 -- builder/digitalocean/step_power_off.go | 17 ----- builder/digitalocean/step_shutdown.go | 10 --- builder/digitalocean/wait.go | 35 ++++----- 8 files changed, 59 insertions(+), 139 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 947c655b2..f39554ab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ IMPROVEMENTS: +* builder/digitalocean: Retry on any pending event errors. * builder/openstack: Can now specify a project. [GH-382] BUG FIXES: diff --git a/builder/digitalocean/api.go b/builder/digitalocean/api.go index fbd1b7d50..384b4e8c9 100644 --- a/builder/digitalocean/api.go +++ b/builder/digitalocean/api.go @@ -14,6 +14,7 @@ import ( "net/http" "net/url" "strings" + "time" ) const DIGITALOCEAN_API_URL = "https://api.digitalocean.com" @@ -191,46 +192,57 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin url := fmt.Sprintf("%s/%s?%s", DIGITALOCEAN_API_URL, path, params.Encode()) - var decodedResponse map[string]interface{} - // Do some basic scrubbing so sensitive information doesn't appear in logs scrubbedUrl := strings.Replace(url, d.ClientID, "CLIENT_ID", -1) scrubbedUrl = strings.Replace(scrubbedUrl, d.APIKey, "API_KEY", -1) log.Printf("sending new request to digitalocean: %s", scrubbedUrl) - resp, err := client.Get(url) - if err != nil { - return decodedResponse, err - } - - body, err := ioutil.ReadAll(resp.Body) - - resp.Body.Close() - if err != nil { - return decodedResponse, err - } - - log.Printf("response from digitalocean: %s", body) - - err = json.Unmarshal(body, &decodedResponse) - - // Check for bad JSON - if err != nil { - err = errors.New(fmt.Sprintf("Failed to decode JSON response (HTTP %v) from DigitalOcean: %s", - resp.StatusCode, body)) - return decodedResponse, err - } - - // Check for errors sent by digitalocean - status := decodedResponse["status"] - if status != "OK" { - // Get the actual error message if there is one - if status == "ERROR" { - status = decodedResponse["error_message"] + var lastErr error + for attempts := 1; attempts < 5; attempts++ { + resp, err := client.Get(url) + if err != nil { + return nil, err } - err = errors.New(fmt.Sprintf("Received bad response (HTTP %v) from DigitalOcean: %s", resp.StatusCode, status)) - return decodedResponse, err + + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return nil, err + } + + log.Printf("response from digitalocean: %s", body) + + var decodedResponse map[string]interface{} + err = json.Unmarshal(body, &decodedResponse) + if err != nil { + err = errors.New(fmt.Sprintf("Failed to decode JSON response (HTTP %v) from DigitalOcean: %s", + resp.StatusCode, body)) + return decodedResponse, err + } + + // Check for errors sent by digitalocean + status := decodedResponse["status"].(string) + if status == "OK" { + return decodedResponse, nil + } + + if status == "ERROR" { + status = decodedResponse["error_message"].(string) + } + + lastErr = errors.New(fmt.Sprintf("Received error from DigitalOcean (%d): %s", + resp.StatusCode, status)) + log.Println(lastErr) + if strings.Contains(status, "has a pending event") { + // Retry, DigitalOcean sends these dumb "pending event" + // errors all the time. + time.Sleep(5 * time.Second) + continue + } + + // Some other kind of error. Just return. + return decodedResponse, lastErr } - return decodedResponse, nil + return nil, lastErr } diff --git a/builder/digitalocean/builder.go b/builder/digitalocean/builder.go index 25129e01a..8e74ec9b1 100644 --- a/builder/digitalocean/builder.go +++ b/builder/digitalocean/builder.go @@ -34,13 +34,11 @@ type config struct { SSHPort uint `mapstructure:"ssh_port"` RawSSHTimeout string `mapstructure:"ssh_timeout"` - RawEventDelay string `mapstructure:"event_delay"` RawStateTimeout string `mapstructure:"state_timeout"` // These are unexported since they're set by other fields // being set. sshTimeout time.Duration - eventDelay time.Duration stateTimeout time.Duration tpl *packer.ConfigTemplate @@ -113,12 +111,6 @@ func (b *Builder) Prepare(raws ...interface{}) error { b.config.RawSSHTimeout = "1m" } - if b.config.RawEventDelay == "" { - // Default to 5 second delays after creating events - // to allow DO to process - b.config.RawEventDelay = "5s" - } - if b.config.RawStateTimeout == "" { // Default to 6 minute timeouts waiting for // desired state. i.e waiting for droplet to become active @@ -131,7 +123,6 @@ func (b *Builder) Prepare(raws ...interface{}) error { "snapshot_name": &b.config.SnapshotName, "ssh_username": &b.config.SSHUsername, "ssh_timeout": &b.config.RawSSHTimeout, - "event_delay": &b.config.RawEventDelay, "state_timeout": &b.config.RawStateTimeout, } @@ -162,13 +153,6 @@ func (b *Builder) Prepare(raws ...interface{}) error { } b.config.sshTimeout = sshTimeout - eventDelay, err := time.ParseDuration(b.config.RawEventDelay) - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Failed parsing event_delay: %s", err)) - } - b.config.eventDelay = eventDelay - stateTimeout, err := time.ParseDuration(b.config.RawStateTimeout) if err != nil { errs = packer.MultiErrorAppend( diff --git a/builder/digitalocean/builder_test.go b/builder/digitalocean/builder_test.go index af84323ab..cd19f0f31 100644 --- a/builder/digitalocean/builder_test.go +++ b/builder/digitalocean/builder_test.go @@ -258,38 +258,6 @@ func TestBuilderPrepare_SSHTimeout(t *testing.T) { } -func TestBuilderPrepare_EventDelay(t *testing.T) { - var b Builder - config := testConfig() - - // Test default - err := b.Prepare(config) - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - if b.config.RawEventDelay != "5s" { - t.Errorf("invalid: %d", b.config.RawEventDelay) - } - - // Test set - config["event_delay"] = "10s" - b = Builder{} - err = b.Prepare(config) - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - // Test bad - config["event_delay"] = "tubes" - b = Builder{} - err = b.Prepare(config) - if err == nil { - t.Fatal("should have error") - } - -} - func TestBuilderPrepare_StateTimeout(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/digitalocean/step_create_droplet.go b/builder/digitalocean/step_create_droplet.go index dcc369ac4..2bdc321b2 100644 --- a/builder/digitalocean/step_create_droplet.go +++ b/builder/digitalocean/step_create_droplet.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "log" "time" ) @@ -56,12 +55,6 @@ func (s *stepCreateDroplet) Cleanup(state multistep.StateBag) { // Destroy the droplet we just created ui.Say("Destroying droplet...") - // Sleep arbitrarily before sending destroy request - // Otherwise we get "pending event" errors, even though there isn't - // one. - log.Printf("Sleeping for %v, event_delay", c.RawEventDelay) - time.Sleep(c.eventDelay) - var err error for i := 0; i < 5; i++ { err = client.DestroyDroplet(s.dropletId) diff --git a/builder/digitalocean/step_power_off.go b/builder/digitalocean/step_power_off.go index 553436f01..bc27fbadd 100644 --- a/builder/digitalocean/step_power_off.go +++ b/builder/digitalocean/step_power_off.go @@ -5,26 +5,17 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" - "time" ) type stepPowerOff struct{} func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { client := state.Get("client").(*DigitalOceanClient) - c := state.Get("config").(config) ui := state.Get("ui").(packer.Ui) dropletId := state.Get("droplet_id").(uint) - // Sleep arbitrarily before sending power off request - // Otherwise we get "pending event" errors, even though there isn't - // one. - log.Printf("Sleeping for %v, event_delay", c.RawEventDelay) - time.Sleep(c.eventDelay) - // Poweroff the droplet so it can be snapshot err := client.PowerOffDroplet(dropletId) - if err != nil { err := fmt.Errorf("Error powering off droplet: %s", err) state.Put("error", err) @@ -33,14 +24,6 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { } log.Println("Waiting for poweroff event to complete...") - - // This arbitrary sleep is because we can't wait for the state - // of the droplet to be 'off', as stepShutdown should already - // have accomplished that, and the state indicator is the same. - // We just have to assume that this event will process quickly. - log.Printf("Sleeping for %v, event_delay", c.RawEventDelay) - time.Sleep(c.eventDelay) - return multistep.ActionContinue } diff --git a/builder/digitalocean/step_shutdown.go b/builder/digitalocean/step_shutdown.go index 31c9ae8ea..01275d47d 100644 --- a/builder/digitalocean/step_shutdown.go +++ b/builder/digitalocean/step_shutdown.go @@ -4,8 +4,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "log" - "time" ) type stepShutdown struct{} @@ -16,14 +14,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) dropletId := state.Get("droplet_id").(uint) - // Sleep arbitrarily before sending the request - // Otherwise we get "pending event" errors, even though there isn't - // one. - log.Printf("Sleeping for %v, event_delay", c.RawEventDelay) - time.Sleep(c.eventDelay) - err := client.ShutdownDroplet(dropletId) - if err != nil { err := fmt.Errorf("Error shutting down droplet: %s", err) state.Put("error", err) @@ -32,7 +23,6 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { } ui.Say("Waiting for droplet to shutdown...") - err = waitForDropletState("off", dropletId, client, c) if err != nil { err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err) diff --git a/builder/digitalocean/wait.go b/builder/digitalocean/wait.go index 75ad01f9d..f770eb7db 100644 --- a/builder/digitalocean/wait.go +++ b/builder/digitalocean/wait.go @@ -1,7 +1,7 @@ package digitalocean import ( - "errors" + "fmt" "log" "time" ) @@ -9,8 +9,7 @@ import ( // waitForState simply blocks until the droplet is in // a state we expect, while eventually timing out. func waitForDropletState(desiredState string, dropletId uint, client *DigitalOceanClient, c config) error { - active := make(chan bool, 1) - + result := make(chan error, 1) go func() { attempts := 0 for { @@ -19,36 +18,26 @@ func waitForDropletState(desiredState string, dropletId uint, client *DigitalOce log.Printf("Checking droplet status... (attempt: %d)", attempts) _, status, err := client.DropletStatus(dropletId) if err != nil { - log.Println(err) - break + result <- err + return } if status == desiredState { - break + result <- nil + return } // Wait 3 seconds in between time.Sleep(3 * time.Second) } - - active <- true }() log.Printf("Waiting for up to %s for droplet to become %s", c.RawStateTimeout, desiredState) - timeout := time.After(c.stateTimeout) - -ActiveWaitLoop: - for { - select { - case <-active: - // We connected. Just break the loop. - break ActiveWaitLoop - case <-timeout: - err := errors.New("Timeout while waiting to for droplet to become active") - return err - } + select { + case err := <-result: + return err + case <-time.After(c.stateTimeout): + err := fmt.Errorf("Timeout while waiting to for droplet to become '%s'", desiredState) + return err } - - // If we got this far, there were no errors - return nil } From 524499f9423dc677d7e3fe16d8e1d2f372f7bd19 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Sep 2013 21:25:47 -0700 Subject: [PATCH 005/173] Update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f39554ab0..44f1762a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## 0.3.7 (unreleased) +BACKWARDS INCOMPATIBILITIES: + +* The "event_delay" option for the DigitalOcean builder is now gone. + The builder automatically waits for events to go away. Run your templates + through `packer fix` to get rid of these. + IMPROVEMENTS: * builder/digitalocean: Retry on any pending event errors. From 306ebcf042e27c653ea97ef6ab7dd613dac8fb79 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Sep 2013 21:29:07 -0700 Subject: [PATCH 006/173] builder/digitalocean: looser pending event string matching. Most APIs return "has a pending event" but power off seems to return "already a pending event". WTF. Also, just wtf at the DigitalOcean API. I can't wait until they change their english and this breaks. Because it is definitely going to happen, and it's going to be such a fun time. --- builder/digitalocean/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/digitalocean/api.go b/builder/digitalocean/api.go index 384b4e8c9..2118cf5ed 100644 --- a/builder/digitalocean/api.go +++ b/builder/digitalocean/api.go @@ -233,7 +233,7 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin lastErr = errors.New(fmt.Sprintf("Received error from DigitalOcean (%d): %s", resp.StatusCode, status)) log.Println(lastErr) - if strings.Contains(status, "has a pending event") { + if strings.Contains(status, "a pending event") { // Retry, DigitalOcean sends these dumb "pending event" // errors all the time. time.Sleep(5 * time.Second) From 82b46816d331797c8f65d3ac57bee7cf9bca57f1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Sep 2013 21:59:58 -0700 Subject: [PATCH 007/173] builder/digitalocean: retry power off a number of times See comment. --- builder/digitalocean/step_droplet_info.go | 2 +- builder/digitalocean/step_power_off.go | 24 ++++++++++++++++++++--- builder/digitalocean/step_shutdown.go | 3 +-- builder/digitalocean/step_snapshot.go | 2 +- builder/digitalocean/wait.go | 6 +++--- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/builder/digitalocean/step_droplet_info.go b/builder/digitalocean/step_droplet_info.go index 6adfe0cd4..b9350c531 100644 --- a/builder/digitalocean/step_droplet_info.go +++ b/builder/digitalocean/step_droplet_info.go @@ -16,7 +16,7 @@ func (s *stepDropletInfo) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Waiting for droplet to become active...") - err := waitForDropletState("active", dropletId, client, c) + err := waitForDropletState("active", dropletId, client, c.stateTimeout) if err != nil { err := fmt.Errorf("Error waiting for droplet to become active: %s", err) state.Put("error", err) diff --git a/builder/digitalocean/step_power_off.go b/builder/digitalocean/step_power_off.go index bc27fbadd..e0b551863 100644 --- a/builder/digitalocean/step_power_off.go +++ b/builder/digitalocean/step_power_off.go @@ -5,6 +5,7 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" + "time" ) type stepPowerOff struct{} @@ -14,10 +15,27 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) dropletId := state.Get("droplet_id").(uint) - // Poweroff the droplet so it can be snapshot - err := client.PowerOffDroplet(dropletId) + // Gracefully power off the droplet. We have to retry this a number + // of times because sometimes it says it completed when it actually + // did absolutely nothing (*ALAKAZAM!* magic!). We give up after + // a pretty arbitrary amount of time. + var err error + ui.Say("Gracefully shutting down droplet...") + for attempts := 1; attempts <= 10; attempts++ { + log.Printf("PowerOffDroplet attempt #%d...", attempts) + err := client.PowerOffDroplet(dropletId) + if err != nil { + err := fmt.Errorf("Error powering off droplet: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + err = waitForDropletState("off", dropletId, client, 20*time.Second) + } + if err != nil { - err := fmt.Errorf("Error powering off droplet: %s", err) + err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt diff --git a/builder/digitalocean/step_shutdown.go b/builder/digitalocean/step_shutdown.go index 01275d47d..3bbbf83f8 100644 --- a/builder/digitalocean/step_shutdown.go +++ b/builder/digitalocean/step_shutdown.go @@ -22,8 +22,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - ui.Say("Waiting for droplet to shutdown...") - err = waitForDropletState("off", dropletId, client, c) + err = waitForDropletState("off", dropletId, client, c.stateTimeout) if err != nil { err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err) state.Put("error", err) diff --git a/builder/digitalocean/step_snapshot.go b/builder/digitalocean/step_snapshot.go index c9072116d..b5a531df3 100644 --- a/builder/digitalocean/step_snapshot.go +++ b/builder/digitalocean/step_snapshot.go @@ -26,7 +26,7 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction { } ui.Say("Waiting for snapshot to complete...") - err = waitForDropletState("active", dropletId, client, c) + err = waitForDropletState("active", dropletId, client, c.stateTimeout) if err != nil { err := fmt.Errorf("Error waiting for snapshot to complete: %s", err) state.Put("error", err) diff --git a/builder/digitalocean/wait.go b/builder/digitalocean/wait.go index f770eb7db..388e27978 100644 --- a/builder/digitalocean/wait.go +++ b/builder/digitalocean/wait.go @@ -8,7 +8,7 @@ import ( // waitForState simply blocks until the droplet is in // a state we expect, while eventually timing out. -func waitForDropletState(desiredState string, dropletId uint, client *DigitalOceanClient, c config) error { +func waitForDropletState(desiredState string, dropletId uint, client *DigitalOceanClient, timeout time.Duration) error { result := make(chan error, 1) go func() { attempts := 0 @@ -32,11 +32,11 @@ func waitForDropletState(desiredState string, dropletId uint, client *DigitalOce } }() - log.Printf("Waiting for up to %s for droplet to become %s", c.RawStateTimeout, desiredState) + log.Printf("Waiting for up to %d seconds for droplet to become %s", timeout, desiredState) select { case err := <-result: return err - case <-time.After(c.stateTimeout): + case <-time.After(timeout): err := fmt.Errorf("Timeout while waiting to for droplet to become '%s'", desiredState) return err } From 39f7a5a6173bff3df12610b411067ee82b460170 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Sep 2013 22:07:19 -0700 Subject: [PATCH 008/173] builder/digitalocean: break loop if we reach off state Otherwise we get stuck in a long loop. Whoops. --- builder/digitalocean/step_power_off.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/builder/digitalocean/step_power_off.go b/builder/digitalocean/step_power_off.go index e0b551863..a29116cae 100644 --- a/builder/digitalocean/step_power_off.go +++ b/builder/digitalocean/step_power_off.go @@ -32,6 +32,10 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { } err = waitForDropletState("off", dropletId, client, 20*time.Second) + if err == nil { + // We reached the state! + break + } } if err != nil { From 4af230a4dc61f4609e9c7a176f6b48ee26f0b955 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Sep 2013 22:14:30 -0700 Subject: [PATCH 009/173] builder/digitalocean: actually, we want to try hard on shutdown --- builder/digitalocean/step_power_off.go | 44 +++++++++++++------------- builder/digitalocean/step_shutdown.go | 29 ++++++++++++----- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/builder/digitalocean/step_power_off.go b/builder/digitalocean/step_power_off.go index a29116cae..9964db802 100644 --- a/builder/digitalocean/step_power_off.go +++ b/builder/digitalocean/step_power_off.go @@ -15,31 +15,31 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) dropletId := state.Get("droplet_id").(uint) - // Gracefully power off the droplet. We have to retry this a number - // of times because sometimes it says it completed when it actually - // did absolutely nothing (*ALAKAZAM!* magic!). We give up after - // a pretty arbitrary amount of time. - var err error - ui.Say("Gracefully shutting down droplet...") - for attempts := 1; attempts <= 10; attempts++ { - log.Printf("PowerOffDroplet attempt #%d...", attempts) - err := client.PowerOffDroplet(dropletId) - if err != nil { - err := fmt.Errorf("Error powering off droplet: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - err = waitForDropletState("off", dropletId, client, 20*time.Second) - if err == nil { - // We reached the state! - break - } + _, status, err := client.DropletStatus(dropletId) + if err != nil { + err := fmt.Errorf("Error checking droplet state: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt } + if status == "off" { + // Droplet is already off, don't do anything + return multistep.ActionContinue + } + + // Pull the plug on the Droplet + ui.Say("Forcefully shutting down Droplet...") + err = client.PowerOffDroplet(dropletId) + if err != nil { + err := fmt.Errorf("Error powering off droplet: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + err = waitForDropletState("off", dropletId, client, 20*time.Second) if err != nil { - err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt diff --git a/builder/digitalocean/step_shutdown.go b/builder/digitalocean/step_shutdown.go index 3bbbf83f8..16996bb02 100644 --- a/builder/digitalocean/step_shutdown.go +++ b/builder/digitalocean/step_shutdown.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" + "log" ) type stepShutdown struct{} @@ -14,17 +15,29 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) dropletId := state.Get("droplet_id").(uint) - err := client.ShutdownDroplet(dropletId) - if err != nil { - err := fmt.Errorf("Error shutting down droplet: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt + // Gracefully power off the droplet. We have to retry this a number + // of times because sometimes it says it completed when it actually + // did absolutely nothing (*ALAKAZAM!* magic!). We give up after + // a pretty arbitrary amount of time. + var err error + ui.Say("Gracefully shutting down droplet...") + for attempts := 1; attempts <= 10; attempts++ { + log.Printf("ShutdownDropetl attempt #%d...", attempts) + err := client.ShutdownDroplet(dropletId) + if err != nil { + err := fmt.Errorf("Error shutting down droplet: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + err = waitForDropletState("off", dropletId, client, c.stateTimeout) + if err == nil { + break + } } - err = waitForDropletState("off", dropletId, client, c.stateTimeout) if err != nil { - err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt From aedd1555d343f394781d9b74514dda5bccbe40f4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Sep 2013 22:21:59 -0700 Subject: [PATCH 010/173] builder/digitalocean: properly cleanup goroutines for status checking --- builder/digitalocean/wait.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/builder/digitalocean/wait.go b/builder/digitalocean/wait.go index 388e27978..ba2b03cb0 100644 --- a/builder/digitalocean/wait.go +++ b/builder/digitalocean/wait.go @@ -9,6 +9,9 @@ import ( // waitForState simply blocks until the droplet is in // a state we expect, while eventually timing out. func waitForDropletState(desiredState string, dropletId uint, client *DigitalOceanClient, timeout time.Duration) error { + done := make(chan struct{}) + defer close(done) + result := make(chan error, 1) go func() { attempts := 0 @@ -29,6 +32,15 @@ func waitForDropletState(desiredState string, dropletId uint, client *DigitalOce // 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 + } } }() From 8643c70e35285a4412e50aec8c96586efbcd45b3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Sep 2013 22:26:05 -0700 Subject: [PATCH 011/173] builder/digitalocean: proper timeouts so poweroff retries --- builder/digitalocean/step_power_off.go | 4 ++-- builder/digitalocean/step_shutdown.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/builder/digitalocean/step_power_off.go b/builder/digitalocean/step_power_off.go index 9964db802..06f05c52f 100644 --- a/builder/digitalocean/step_power_off.go +++ b/builder/digitalocean/step_power_off.go @@ -5,13 +5,13 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" - "time" ) type stepPowerOff struct{} func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { client := state.Get("client").(*DigitalOceanClient) + c := state.Get("config").(config) ui := state.Get("ui").(packer.Ui) dropletId := state.Get("droplet_id").(uint) @@ -38,7 +38,7 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - err = waitForDropletState("off", dropletId, client, 20*time.Second) + err = waitForDropletState("off", dropletId, client, c.stateTimeout) if err != nil { state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/digitalocean/step_shutdown.go b/builder/digitalocean/step_shutdown.go index 16996bb02..c50dc0b95 100644 --- a/builder/digitalocean/step_shutdown.go +++ b/builder/digitalocean/step_shutdown.go @@ -5,13 +5,13 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" + "time" ) type stepShutdown struct{} func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { client := state.Get("client").(*DigitalOceanClient) - c := state.Get("config").(config) ui := state.Get("ui").(packer.Ui) dropletId := state.Get("droplet_id").(uint) @@ -22,7 +22,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { var err error ui.Say("Gracefully shutting down droplet...") for attempts := 1; attempts <= 10; attempts++ { - log.Printf("ShutdownDropetl attempt #%d...", attempts) + log.Printf("ShutdownDroplet attempt #%d...", attempts) err := client.ShutdownDroplet(dropletId) if err != nil { err := fmt.Errorf("Error shutting down droplet: %s", err) @@ -31,7 +31,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - err = waitForDropletState("off", dropletId, client, c.stateTimeout) + err = waitForDropletState("off", dropletId, client, 20*time.Second) if err == nil { break } From 94d369f5580b8e4cb91a3d204353863afd3bba50 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Sep 2013 22:51:28 -0700 Subject: [PATCH 012/173] builder/digitalocean: give up on graceful shutdown more quickly --- builder/digitalocean/step_power_off.go | 2 +- builder/digitalocean/step_shutdown.go | 58 ++++++++++++++++++-------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/builder/digitalocean/step_power_off.go b/builder/digitalocean/step_power_off.go index 06f05c52f..9aa5e30b8 100644 --- a/builder/digitalocean/step_power_off.go +++ b/builder/digitalocean/step_power_off.go @@ -38,6 +38,7 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + log.Println("Waiting for poweroff event to complete...") err = waitForDropletState("off", dropletId, client, c.stateTimeout) if err != nil { state.Put("error", err) @@ -45,7 +46,6 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - log.Println("Waiting for poweroff event to complete...") return multistep.ActionContinue } diff --git a/builder/digitalocean/step_shutdown.go b/builder/digitalocean/step_shutdown.go index c50dc0b95..7ef71bd7c 100644 --- a/builder/digitalocean/step_shutdown.go +++ b/builder/digitalocean/step_shutdown.go @@ -12,6 +12,7 @@ type stepShutdown struct{} func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { client := state.Get("client").(*DigitalOceanClient) + c := state.Get("config").(config) ui := state.Get("ui").(packer.Ui) dropletId := state.Get("droplet_id").(uint) @@ -19,30 +20,53 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { // of times because sometimes it says it completed when it actually // did absolutely nothing (*ALAKAZAM!* magic!). We give up after // a pretty arbitrary amount of time. - var err error ui.Say("Gracefully shutting down droplet...") - for attempts := 1; attempts <= 10; attempts++ { - log.Printf("ShutdownDroplet attempt #%d...", attempts) - err := client.ShutdownDroplet(dropletId) - if err != nil { - err := fmt.Errorf("Error shutting down droplet: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - err = waitForDropletState("off", dropletId, client, 20*time.Second) - if err == nil { - break - } - } - + err := client.ShutdownDroplet(dropletId) if err != nil { + // If we get an error the first time, actually report it + err := fmt.Errorf("Error shutting down droplet: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } + // A channel we use as a flag to end our goroutines + done := make(chan struct{}) + shutdownRetryDone := make(chan struct{}) + + // Make sure we wait for the shutdown retry goroutine to end + // before moving on. + defer func() { + close(done) + <-shutdownRetryDone + }() + + // Start a goroutine that just keeps trying to shut down the + // droplet. + go func() { + defer close(shutdownRetryDone) + + for attempts := 2; attempts > 0; attempts++ { + log.Printf("ShutdownDroplet attempt #%d...", attempts) + err := client.ShutdownDroplet(dropletId) + if err != nil { + log.Printf("Shutdown retry error: %s", err) + } + + select { + case <-done: + return + case <-time.After(20 * time.Second): + // Retry! + } + } + }() + + err = waitForDropletState("off", dropletId, client, 2 * time.Minute) + if err != nil { + log.Printf("Error waiting for graceful off: %s", err) + } + return multistep.ActionContinue } From 78302f2071fd026020fb22971eef35849f10de25 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Sep 2013 22:53:37 -0700 Subject: [PATCH 013/173] builder/digitalocean: get compilation working again --- builder/digitalocean/step_shutdown.go | 1 - 1 file changed, 1 deletion(-) diff --git a/builder/digitalocean/step_shutdown.go b/builder/digitalocean/step_shutdown.go index 7ef71bd7c..b7bfd4d9d 100644 --- a/builder/digitalocean/step_shutdown.go +++ b/builder/digitalocean/step_shutdown.go @@ -12,7 +12,6 @@ type stepShutdown struct{} func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { client := state.Get("client").(*DigitalOceanClient) - c := state.Get("config").(config) ui := state.Get("ui").(packer.Ui) dropletId := state.Get("droplet_id").(uint) From 362c32015d42da5ae9b122e3b4faa2fcaafc57a6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Sep 2013 23:00:54 -0700 Subject: [PATCH 014/173] builder/digitalocean: retry pending events a lot more --- builder/digitalocean/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/digitalocean/api.go b/builder/digitalocean/api.go index 2118cf5ed..5abc12faa 100644 --- a/builder/digitalocean/api.go +++ b/builder/digitalocean/api.go @@ -198,7 +198,7 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin log.Printf("sending new request to digitalocean: %s", scrubbedUrl) var lastErr error - for attempts := 1; attempts < 5; attempts++ { + for attempts := 1; attempts < 10; attempts++ { resp, err := client.Get(url) if err != nil { return nil, err From 1df07357136db883498509a178ef4b0fb14c54ab Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Sep 2013 23:01:37 -0700 Subject: [PATCH 015/173] builder/digitalocean: retry longer and don't retry shutdown --- builder/digitalocean/step_create_droplet.go | 12 +----------- builder/digitalocean/step_shutdown.go | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/builder/digitalocean/step_create_droplet.go b/builder/digitalocean/step_create_droplet.go index 2bdc321b2..c97cd192d 100644 --- a/builder/digitalocean/step_create_droplet.go +++ b/builder/digitalocean/step_create_droplet.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "time" ) type stepCreateDroplet struct { @@ -55,16 +54,7 @@ func (s *stepCreateDroplet) Cleanup(state multistep.StateBag) { // Destroy the droplet we just created ui.Say("Destroying droplet...") - var err error - for i := 0; i < 5; i++ { - err = client.DestroyDroplet(s.dropletId) - if err == nil { - break - } - - time.Sleep(2 * time.Second) - } - + 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) diff --git a/builder/digitalocean/step_shutdown.go b/builder/digitalocean/step_shutdown.go index b7bfd4d9d..fc36dbad4 100644 --- a/builder/digitalocean/step_shutdown.go +++ b/builder/digitalocean/step_shutdown.go @@ -61,7 +61,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { } }() - err = waitForDropletState("off", dropletId, client, 2 * time.Minute) + err = waitForDropletState("off", dropletId, client, 2*time.Minute) if err != nil { log.Printf("Error waiting for graceful off: %s", err) } From f953874448d876140ae7ccce54f0dc1bd14f2b03 Mon Sep 17 00:00:00 2001 From: Ben Griffiths Date: Thu, 5 Sep 2013 13:28:31 +0100 Subject: [PATCH 016/173] Allow customization of SSH Key pair pattern Defined in a template using ssh_keypair_pattern. Defaults to "packer %s" --- builder/amazon/common/run_config.go | 5 +++++ builder/amazon/common/run_config_test.go | 21 +++++++++++++++++++++ builder/amazon/common/step_key_pair.go | 7 ++++--- builder/amazon/ebs/builder.go | 5 +++-- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/builder/amazon/common/run_config.go b/builder/amazon/common/run_config.go index 7ed6f85a6..6c162d052 100644 --- a/builder/amazon/common/run_config.go +++ b/builder/amazon/common/run_config.go @@ -22,6 +22,7 @@ type RunConfig struct { SecurityGroupId string `mapstructure:"security_group_id"` SubnetId string `mapstructure:"subnet_id"` VpcId string `mapstructure:"vpc_id"` + SSHKeyPairPattern string `mapstructure:"ssh_keypair_pattern"` // Unexported fields that are calculated from others sshTimeout time.Duration @@ -45,6 +46,10 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { c.RawSSHTimeout = "1m" } + if c.SSHKeyPairPattern == "" { + c.SSHKeyPairPattern = "packer %s" + } + // Validation var err error errs := make([]error, 0) diff --git a/builder/amazon/common/run_config_test.go b/builder/amazon/common/run_config_test.go index d13320686..a9f054fc2 100644 --- a/builder/amazon/common/run_config_test.go +++ b/builder/amazon/common/run_config_test.go @@ -126,3 +126,24 @@ func TestRunConfigPrepare_UserDataFile(t *testing.T) { t.Fatalf("err: %s", err) } } + +func TestRunConfigPrepare_SSHKeyPairPattern(t *testing.T) { + c := testConfig() + c.SSHKeyPairPattern = "" + if err := c.Prepare(nil); len(err) != 0 { + t.Fatalf("err: %s", err) + } + + if c.SSHKeyPairPattern != "packer %s" { + t.Fatalf("invalid value: %s", c.SSHKeyPairPattern) + } + + c.SSHKeyPairPattern = "valid-%s" + if err := c.Prepare(nil); len(err) != 0 { + t.Fatalf("err: %s", err) + } + + if c.SSHKeyPairPattern != "valid-%s" { + t.Fatalf("invalid value: %s", c.SSHKeyPairPattern) + } +} diff --git a/builder/amazon/common/step_key_pair.go b/builder/amazon/common/step_key_pair.go index 63c0247bc..772e390c5 100644 --- a/builder/amazon/common/step_key_pair.go +++ b/builder/amazon/common/step_key_pair.go @@ -13,8 +13,9 @@ import ( ) type StepKeyPair struct { - Debug bool - DebugKeyPath string + Debug bool + DebugKeyPath string + KeyPairPattern string keyName string } @@ -24,7 +25,7 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) ui.Say("Creating temporary keypair for this instance...") - keyName := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw())) + keyName := fmt.Sprintf(s.KeyPairPattern, hex.EncodeToString(identifier.NewUUID().Raw())) log.Printf("temporary keypair name: %s", keyName) keyResp, err := ec2conn.CreateKeyPair(keyName) if err != nil { diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index 5c6cd16dd..59de87b33 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -82,8 +82,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Build the steps steps := []multistep.Step{ &awscommon.StepKeyPair{ - Debug: b.config.PackerDebug, - DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName), + Debug: b.config.PackerDebug, + DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName), + KeyPairPattern: b.config.SSHKeyPairPattern, }, &awscommon.StepSecurityGroup{ SecurityGroupId: b.config.SecurityGroupId, From dbc24d935759d598197d9c46d3612c783860e1eb Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Thu, 5 Sep 2013 11:00:08 -0700 Subject: [PATCH 017/173] Enable customization of VirtualBox disk controller with `hard_drive_interface` option (SATA only). --- builder/virtualbox/builder.go | 5 +++++ builder/virtualbox/step_create_disk.go | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/builder/virtualbox/builder.go b/builder/virtualbox/builder.go index 703a9ba92..b7bc58c16 100644 --- a/builder/virtualbox/builder.go +++ b/builder/virtualbox/builder.go @@ -33,6 +33,7 @@ type config struct { GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"` GuestOSType string `mapstructure:"guest_os_type"` Headless bool `mapstructure:"headless"` + HardDriveInterface string `mapstructure:"hard_drive_interface"` HTTPDir string `mapstructure:"http_directory"` HTTPPortMin uint `mapstructure:"http_port_min"` HTTPPortMax uint `mapstructure:"http_port_max"` @@ -89,6 +90,10 @@ func (b *Builder) Prepare(raws ...interface{}) error { b.config.GuestAdditionsPath = "VBoxGuestAdditions.iso" } + if b.config.HardDriveInterface == "" { + b.config.HardDriveInterface = "ide" + } + if b.config.GuestOSType == "" { b.config.GuestOSType = "Other" } diff --git a/builder/virtualbox/step_create_disk.go b/builder/virtualbox/step_create_disk.go index cb456ed0a..80abeeb75 100644 --- a/builder/virtualbox/step_create_disk.go +++ b/builder/virtualbox/step_create_disk.go @@ -39,7 +39,9 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - // Add the IDE controller so we can later attach the disk + // Add the IDE controller so we can later attach the disk. + // When the hard disk controller is not IDE, this device is still used + // by VirtualBox to deliver the guest extensions. controllerName := "IDE Controller" err = driver.VBoxManage("storagectl", vmName, "--name", controllerName, "--add", "ide") if err != nil { @@ -49,6 +51,22 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + if config.HardDriveInterface == "sata" { + controllerName = "SATA Controller" + command = []string{ + "storagectl", vmName, + "--name", controllerName, + "--add", "sata", + "--sataportcount", "1", + } + if err := driver.VBoxManage(command...); err != nil { + err := fmt.Errorf("Error creating disk controller: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + // Attach the disk to the controller command = []string{ "storageattach", vmName, From 96caaa067bfd97014af8fad2ed2f3cc81597521a Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Thu, 5 Sep 2013 11:08:55 -0700 Subject: [PATCH 018/173] Document the `hard_drive_interface` option. --- website/source/docs/builders/virtualbox.html.markdown | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/source/docs/builders/virtualbox.html.markdown b/website/source/docs/builders/virtualbox.html.markdown index 8b38740fb..c4cbe6a39 100644 --- a/website/source/docs/builders/virtualbox.html.markdown +++ b/website/source/docs/builders/virtualbox.html.markdown @@ -108,6 +108,10 @@ Optional: how to optimize the virtual hardware to work best with that operating system. +* `hard_drive_interface` (string) - The type of controller that the primary + 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 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 From fab369bf159e238a5c4dad10942cd72c51772eef Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 5 Sep 2013 11:09:37 -0700 Subject: [PATCH 019/173] Adding support for isotime template variable --- packer/config_template.go | 5 +++++ packer/config_template_test.go | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/packer/config_template.go b/packer/config_template.go index 354208cbc..b829521ae 100644 --- a/packer/config_template.go +++ b/packer/config_template.go @@ -26,6 +26,7 @@ func NewConfigTemplate() (*ConfigTemplate, error) { result.root = template.New("configTemplateRoot") result.root.Funcs(template.FuncMap{ + "isotime": templateISOTime, "timestamp": templateTimestamp, "user": result.templateUser, }) @@ -79,3 +80,7 @@ func (t *ConfigTemplate) templateUser(n string) (string, error) { func templateTimestamp() string { return strconv.FormatInt(time.Now().UTC().Unix(), 10) } + +func templateISOTime() string { + return time.Now().UTC().Format(time.RFC3339) +} diff --git a/packer/config_template_test.go b/packer/config_template_test.go index 4c4864cda..646565418 100644 --- a/packer/config_template_test.go +++ b/packer/config_template_test.go @@ -7,6 +7,28 @@ import ( "time" ) +func TestConfigTemplateProcess_isotime(t *testing.T) { + tpl, err := NewConfigTemplate() + if err != nil { + t.Fatalf("err: %s", err) + } + + result, err := tpl.Process(`{{isotime}}`, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + val, err := time.Parse(time.RFC3339, result) + if err != nil { + t.Fatalf("err: %s", err) + } + + currentTime := time.Now().UTC() + if currentTime.Sub(val) > 2*time.Second { + t.Fatalf("val: %d (current: %d)", val, currentTime) + } +} + func TestConfigTemplateProcess_timestamp(t *testing.T) { tpl, err := NewConfigTemplate() if err != nil { From 4e2ab0396a02bc1326414f1f6d0f579ad34d1381 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Sep 2013 12:02:18 -0700 Subject: [PATCH 020/173] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44f1762a0..77bb699c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ IMPROVEMENTS: * builder/digitalocean: Retry on any pending event errors. * builder/openstack: Can now specify a project. [GH-382] +* builder/virtualbox: Can now attach hard drive over SATA. [GH-391] BUG FIXES: From e732d86142538f6d15101b804f06f061f1454a66 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Sep 2013 12:07:58 -0700 Subject: [PATCH 021/173] builder/virtualbox: process hdd interface as template, validate --- builder/virtualbox/builder.go | 8 ++++++- builder/virtualbox/builder_test.go | 32 ++++++++++++++++++++++++++ builder/virtualbox/step_create_disk.go | 3 +++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/builder/virtualbox/builder.go b/builder/virtualbox/builder.go index b7bc58c16..18b16867a 100644 --- a/builder/virtualbox/builder.go +++ b/builder/virtualbox/builder.go @@ -32,8 +32,8 @@ type config struct { GuestAdditionsURL string `mapstructure:"guest_additions_url"` GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"` GuestOSType string `mapstructure:"guest_os_type"` - Headless bool `mapstructure:"headless"` HardDriveInterface string `mapstructure:"hard_drive_interface"` + Headless bool `mapstructure:"headless"` HTTPDir string `mapstructure:"http_directory"` HTTPPortMin uint `mapstructure:"http_port_min"` HTTPPortMax uint `mapstructure:"http_port_max"` @@ -146,6 +146,7 @@ func (b *Builder) Prepare(raws ...interface{}) error { templates := map[string]*string{ "guest_additions_sha256": &b.config.GuestAdditionsSHA256, "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, @@ -214,6 +215,11 @@ func (b *Builder) Prepare(raws ...interface{}) error { errs, errors.New("invalid format, only 'ovf' or 'ova' are allowed")) } + if b.config.HardDriveInterface != "ide" && b.config.HardDriveInterface != "sata" { + errs = packer.MultiErrorAppend( + errs, errors.New("hard_drive_interface can only be ide or sata")) + } + if b.config.HTTPPortMin > b.config.HTTPPortMax { errs = packer.MultiErrorAppend( errs, errors.New("http_port_min must be less than http_port_max")) diff --git a/builder/virtualbox/builder_test.go b/builder/virtualbox/builder_test.go index 4ea869fef..35ea79bf9 100644 --- a/builder/virtualbox/builder_test.go +++ b/builder/virtualbox/builder_test.go @@ -252,6 +252,38 @@ func TestBuilderPrepare_GuestAdditionsURL(t *testing.T) { } } +func TestBuilderPrepare_HardDriveInterface(t *testing.T) { + var b Builder + config := testConfig() + + // Test a default boot_wait + delete(config, "hard_drive_interface") + err := b.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if b.config.HardDriveInterface != "ide" { + t.Fatalf("bad: %s", b.config.HardDriveInterface) + } + + // Test with a bad + config["hard_drive_interface"] = "fake" + b = Builder{} + err = b.Prepare(config) + if err == nil { + t.Fatal("should have error") + } + + // Test with a good + config["hard_drive_interface"] = "sata" + b = Builder{} + err = b.Prepare(config) + if err != nil { + t.Fatalf("should not have error: %s", err) + } +} + func TestBuilderPrepare_HTTPPort(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/virtualbox/step_create_disk.go b/builder/virtualbox/step_create_disk.go index 80abeeb75..14005cc85 100644 --- a/builder/virtualbox/step_create_disk.go +++ b/builder/virtualbox/step_create_disk.go @@ -51,6 +51,9 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + // Add a SATA controller if we were asked to use SATA. We still attach + // the IDE controller above because some other things (disks) require + // that. if config.HardDriveInterface == "sata" { controllerName = "SATA Controller" command = []string{ From 687352fd5a0bcd215148928ad9765c167173160e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Sep 2013 12:10:40 -0700 Subject: [PATCH 022/173] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77bb699c1..4c4202bb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ BACKWARDS INCOMPATIBILITIES: IMPROVEMENTS: +* builder/amazon/all: Ability to specify the format of the temporary + keypair created. [GH-389] * builder/digitalocean: Retry on any pending event errors. * builder/openstack: Can now specify a project. [GH-382] * builder/virtualbox: Can now attach hard drive over SATA. [GH-391] From 4642a6aee124fda4efe6134bdd113831a9e07067 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Sep 2013 12:19:56 -0700 Subject: [PATCH 023/173] packer: uuid function --- packer/config_template.go | 7 +++++++ packer/config_template_test.go | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/packer/config_template.go b/packer/config_template.go index 354208cbc..5a8e48d1e 100644 --- a/packer/config_template.go +++ b/packer/config_template.go @@ -2,6 +2,8 @@ package packer import ( "bytes" + "cgl.tideland.biz/identifier" + "encoding/hex" "fmt" "strconv" "text/template" @@ -28,6 +30,7 @@ func NewConfigTemplate() (*ConfigTemplate, error) { result.root.Funcs(template.FuncMap{ "timestamp": templateTimestamp, "user": result.templateUser, + "uuid": templateUuid, }) return result, nil @@ -79,3 +82,7 @@ func (t *ConfigTemplate) templateUser(n string) (string, error) { func templateTimestamp() string { return strconv.FormatInt(time.Now().UTC().Unix(), 10) } + +func templateUuid() string { + return hex.EncodeToString(identifier.NewUUID().Raw()) +} diff --git a/packer/config_template_test.go b/packer/config_template_test.go index 4c4864cda..fed328922 100644 --- a/packer/config_template_test.go +++ b/packer/config_template_test.go @@ -47,6 +47,22 @@ func TestConfigTemplateProcess_user(t *testing.T) { } } +func TestConfigTemplateProcess_uuid(t *testing.T) { + tpl, err := NewConfigTemplate() + if err != nil { + t.Fatalf("err: %s", err) + } + + result, err := tpl.Process(`{{uuid}}`, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + if len(result) != 32 { + t.Fatalf("err: %s", result) + } +} + func TestConfigTemplateValidate(t *testing.T) { tpl, err := NewConfigTemplate() if err != nil { From 615c65320adc5f5bc10da1956a5f5fd9be29735b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Sep 2013 12:20:57 -0700 Subject: [PATCH 024/173] Update CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c4202bb4..e68c1c260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ BACKWARDS INCOMPATIBILITIES: The builder automatically waits for events to go away. Run your templates through `packer fix` to get rid of these. +FEATURES: + +* New globally available template function: `uuid`. Generates a new random + UUID. + IMPROVEMENTS: * builder/amazon/all: Ability to specify the format of the temporary From ed7e0847fd9018e13ce34caa977997020f89a4e7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Sep 2013 12:23:08 -0700 Subject: [PATCH 025/173] builder/amazon/all: TemporaryKeyPairName /cc @whostolebenfrog - I actually simplified things quite a bit. I added a "uuid" global template function so it just uses that now. I renamed it so that it is clear it is a temporary keypair. --- builder/amazon/common/run_config.go | 45 ++++++++++++------------ builder/amazon/common/run_config_test.go | 17 +++------ builder/amazon/common/step_key_pair.go | 19 ++++------ builder/amazon/ebs/builder.go | 6 ++-- builder/amazon/instance/builder.go | 1 + 5 files changed, 38 insertions(+), 50 deletions(-) diff --git a/builder/amazon/common/run_config.go b/builder/amazon/common/run_config.go index 6c162d052..b21999260 100644 --- a/builder/amazon/common/run_config.go +++ b/builder/amazon/common/run_config.go @@ -11,18 +11,18 @@ import ( // RunConfig contains configuration for running an instance from a source // AMI and details on how to access that launched image. type RunConfig struct { - SourceAmi string `mapstructure:"source_ami"` - IamInstanceProfile string `mapstructure:"iam_instance_profile"` - InstanceType string `mapstructure:"instance_type"` - UserData string `mapstructure:"user_data"` - UserDataFile string `mapstructure:"user_data_file"` - RawSSHTimeout string `mapstructure:"ssh_timeout"` - SSHUsername string `mapstructure:"ssh_username"` - SSHPort int `mapstructure:"ssh_port"` - SecurityGroupId string `mapstructure:"security_group_id"` - SubnetId string `mapstructure:"subnet_id"` - VpcId string `mapstructure:"vpc_id"` - SSHKeyPairPattern string `mapstructure:"ssh_keypair_pattern"` + SourceAmi string `mapstructure:"source_ami"` + IamInstanceProfile string `mapstructure:"iam_instance_profile"` + InstanceType string `mapstructure:"instance_type"` + UserData string `mapstructure:"user_data"` + UserDataFile string `mapstructure:"user_data_file"` + RawSSHTimeout string `mapstructure:"ssh_timeout"` + SSHUsername string `mapstructure:"ssh_username"` + SSHPort int `mapstructure:"ssh_port"` + SecurityGroupId string `mapstructure:"security_group_id"` + SubnetId string `mapstructure:"subnet_id"` + TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"` + VpcId string `mapstructure:"vpc_id"` // Unexported fields that are calculated from others sshTimeout time.Duration @@ -46,8 +46,8 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { c.RawSSHTimeout = "1m" } - if c.SSHKeyPairPattern == "" { - c.SSHKeyPairPattern = "packer %s" + if c.TemporaryKeyPairName == "" { + c.TemporaryKeyPairName = "packer {{uuid}}" } // Validation @@ -74,14 +74,15 @@ 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, - "security_group_id": &c.SecurityGroupId, - "ssh_username": &c.SSHUsername, - "source_ami": &c.SourceAmi, - "subnet_id": &c.SubnetId, - "vpc_id": &c.VpcId, + "iam_instance_profile": &c.IamInstanceProfile, + "instance_type": &c.InstanceType, + "ssh_timeout": &c.RawSSHTimeout, + "security_group_id": &c.SecurityGroupId, + "ssh_username": &c.SSHUsername, + "source_ami": &c.SourceAmi, + "subnet_id": &c.SubnetId, + "temporary_key_pair_name": &c.TemporaryKeyPairName, + "vpc_id": &c.VpcId, } for n, ptr := range templates { diff --git a/builder/amazon/common/run_config_test.go b/builder/amazon/common/run_config_test.go index a9f054fc2..1d376b1dd 100644 --- a/builder/amazon/common/run_config_test.go +++ b/builder/amazon/common/run_config_test.go @@ -127,23 +127,14 @@ func TestRunConfigPrepare_UserDataFile(t *testing.T) { } } -func TestRunConfigPrepare_SSHKeyPairPattern(t *testing.T) { +func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) { c := testConfig() - c.SSHKeyPairPattern = "" + c.TemporaryKeyPairName = "" if err := c.Prepare(nil); len(err) != 0 { t.Fatalf("err: %s", err) } - if c.SSHKeyPairPattern != "packer %s" { - t.Fatalf("invalid value: %s", c.SSHKeyPairPattern) - } - - c.SSHKeyPairPattern = "valid-%s" - if err := c.Prepare(nil); len(err) != 0 { - t.Fatalf("err: %s", err) - } - - if c.SSHKeyPairPattern != "valid-%s" { - t.Fatalf("invalid value: %s", c.SSHKeyPairPattern) + if c.TemporaryKeyPairName == "" { + t.Fatal("keypair empty") } } diff --git a/builder/amazon/common/step_key_pair.go b/builder/amazon/common/step_key_pair.go index 772e390c5..93ef6625d 100644 --- a/builder/amazon/common/step_key_pair.go +++ b/builder/amazon/common/step_key_pair.go @@ -1,21 +1,18 @@ package common import ( - "cgl.tideland.biz/identifier" - "encoding/hex" "fmt" "github.com/mitchellh/goamz/ec2" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "log" "os" "runtime" ) type StepKeyPair struct { - Debug bool - DebugKeyPath string - KeyPairPattern string + Debug bool + DebugKeyPath string + KeyPairName string keyName string } @@ -24,20 +21,18 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { ec2conn := state.Get("ec2").(*ec2.EC2) ui := state.Get("ui").(packer.Ui) - ui.Say("Creating temporary keypair for this instance...") - keyName := fmt.Sprintf(s.KeyPairPattern, hex.EncodeToString(identifier.NewUUID().Raw())) - log.Printf("temporary keypair name: %s", keyName) - keyResp, err := ec2conn.CreateKeyPair(keyName) + ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.KeyPairName)) + keyResp, err := ec2conn.CreateKeyPair(s.KeyPairName) if err != nil { state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err)) return multistep.ActionHalt } // Set the keyname so we know to delete it later - s.keyName = keyName + s.keyName = s.KeyPairName // Set some state data for use in future steps - state.Put("keyPair", keyName) + state.Put("keyPair", s.keyName) state.Put("privateKey", keyResp.KeyMaterial) // If we're in debug mode, output the private key to the working diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index 59de87b33..b89a9b71c 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -82,9 +82,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Build the steps steps := []multistep.Step{ &awscommon.StepKeyPair{ - Debug: b.config.PackerDebug, - DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName), - KeyPairPattern: b.config.SSHKeyPairPattern, + Debug: b.config.PackerDebug, + DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName), + KeyPairName: b.config.TemporaryKeyPairName, }, &awscommon.StepSecurityGroup{ SecurityGroupId: b.config.SecurityGroupId, diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index 2fd5b0664..a507d71ef 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -187,6 +187,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &awscommon.StepKeyPair{ Debug: b.config.PackerDebug, DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName), + KeyPairName: b.config.TemporaryKeyPairName, }, &awscommon.StepSecurityGroup{ SecurityGroupId: b.config.SecurityGroupId, From 96f12e14dcdebc21ccbb937fa8a0e1a8d0745c09 Mon Sep 17 00:00:00 2001 From: ttres Date: Thu, 5 Sep 2013 16:27:38 -0300 Subject: [PATCH 026/173] Minor URL typo URLs with special characters should escape them. --- website/source/docs/provisioners/shell.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/provisioners/shell.html.markdown b/website/source/docs/provisioners/shell.html.markdown index 86bfae91b..d3a895b3a 100644 --- a/website/source/docs/provisioners/shell.html.markdown +++ b/website/source/docs/provisioners/shell.html.markdown @@ -59,7 +59,7 @@ Optional parameters: `environment_vars`, if configured. * `inline_shebang` (string) - The - [shebang](http://en.wikipedia.org/wiki/Shebang_(Unix)) value to use when + [shebang](http://en.wikipedia.org/wiki/Shebang_%28Unix%29) value to use when running commands specified by `inline`. By default, this is `/bin/sh`. If you're not using `inline`, then this configuration has no effect. From 3fc00a2dfa87cdf513de986c527df974957719ac Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Sep 2013 13:42:44 -0700 Subject: [PATCH 027/173] builder/vmware: auto-answer dialogs [GH-393] --- CHANGELOG.md | 1 + builder/vmware/step_create_vmx.go | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e68c1c260..06522a93f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ IMPROVEMENTS: BUG FIXES: +* builder/vmware: Autoanswer VMware dialogs. [GH-393] * command/inspect: Fix weird output for default values for optional vars. ## 0.3.6 (September 2, 2013) diff --git a/builder/vmware/step_create_vmx.go b/builder/vmware/step_create_vmx.go index 311b7a9ca..14fb59b5b 100644 --- a/builder/vmware/step_create_vmx.go +++ b/builder/vmware/step_create_vmx.go @@ -137,6 +137,7 @@ ide1:0.fileName = "{{ .ISOPath }}" ide1:0.deviceType = "cdrom-image" isolation.tools.hgfs.disable = "FALSE" memsize = "512" +msg.autoAnswer = "true" nvram = "{{ .Name }}.nvram" pciBridge0.pciSlotNumber = "17" pciBridge0.present = "TRUE" From acd9f3c9a3331564ee7dad34389962ad2cc1417d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Sep 2013 13:44:57 -0700 Subject: [PATCH 028/173] builder/vmware: always set msg.AutoAnswer --- builder/vmware/step_create_vmx.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/builder/vmware/step_create_vmx.go b/builder/vmware/step_create_vmx.go index 14fb59b5b..e2f7c028a 100644 --- a/builder/vmware/step_create_vmx.go +++ b/builder/vmware/step_create_vmx.go @@ -88,6 +88,9 @@ func (stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction { vmxData["floppy0.fileName"] = floppyPathRaw.(string) } + // Set this so that no dialogs ever appear from Packer. + vmxData["msg.autoAnswer"] = "true" + vmxPath := filepath.Join(config.OutputDir, config.VMName+".vmx") if err := WriteVMX(vmxPath, vmxData); err != nil { err := fmt.Errorf("Error creating VMX file: %s", err) @@ -137,7 +140,6 @@ ide1:0.fileName = "{{ .ISOPath }}" ide1:0.deviceType = "cdrom-image" isolation.tools.hgfs.disable = "FALSE" memsize = "512" -msg.autoAnswer = "true" nvram = "{{ .Name }}.nvram" pciBridge0.pciSlotNumber = "17" pciBridge0.present = "TRUE" From b2ddb24c63624d05e84f824aa13a5b4c8a53eebe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Sep 2013 17:12:44 -0700 Subject: [PATCH 029/173] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06522a93f..74f3488ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ FEATURES: * New globally available template function: `uuid`. Generates a new random UUID. +* New globally available template function: `isotime`. Generates the + current time in ISO standard format. IMPROVEMENTS: From d0c3cedee2450ddd18938cec51f60d3bc33e4aef Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Sep 2013 17:14:50 -0700 Subject: [PATCH 030/173] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74f3488ff..523e0c544 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ IMPROVEMENTS: BUG FIXES: +* builder/amazon/all: When copying AMI to multiple regions, copy + the metadata (tags and attributes) as well. [GH-388] * builder/vmware: Autoanswer VMware dialogs. [GH-393] * command/inspect: Fix weird output for default values for optional vars. From 0e2511767eebbd24c4f2620e1d3232060a1b9b05 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Sep 2013 17:19:23 -0700 Subject: [PATCH 031/173] builder/amazon/common: if instance query returns none, ignore --- CHANGELOG.md | 2 ++ builder/amazon/common/instance.go | 44 +++++++++++++++++++------------ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 523e0c544..8a7ec358e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ BUG FIXES: * builder/amazon/all: When copying AMI to multiple regions, copy the metadata (tags and attributes) as well. [GH-388] +* builder/amazon/all: Fix panic case where eventually consistent + instance state caused an index out of bounds. * builder/vmware: Autoanswer VMware dialogs. [GH-393] * command/inspect: Fix weird output for default values for optional vars. diff --git a/builder/amazon/common/instance.go b/builder/amazon/common/instance.go index 6f8f78cd4..462bb58e7 100644 --- a/builder/amazon/common/instance.go +++ b/builder/amazon/common/instance.go @@ -40,6 +40,12 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc { return nil, "", err } + if len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 { + // Sometimes AWS just has consistency issues and doesn't see + // our instance yet. Return an empty state. + return nil, "", nil + } + i = &resp.Reservations[0].Instances[0] return i, i.State.Name, nil } @@ -57,27 +63,31 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) { return } - if currentState == conf.Target { - return - } - - if conf.StepState != nil { - if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok { - return nil, errors.New("interrupted") + // Check states only if we were able to refresh to an instance + // that exists. + if i != nil { + if currentState == conf.Target { + return } - } - found := false - for _, allowed := range conf.Pending { - if currentState == allowed { - found = true - break + if conf.StepState != nil { + if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok { + return nil, errors.New("interrupted") + } } - } - if !found { - fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target) - return + found := false + for _, allowed := range conf.Pending { + if currentState == allowed { + found = true + break + } + } + + if !found { + fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target) + return + } } time.Sleep(2 * time.Second) From 9a6309738a5ba818f782c0dab4451bf974ac9465 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Sep 2013 17:22:37 -0700 Subject: [PATCH 032/173] communicator/ssh: prettify log output --- communicator/ssh/communicator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/communicator/ssh/communicator.go b/communicator/ssh/communicator.go index ffb741851..9279ee132 100644 --- a/communicator/ssh/communicator.go +++ b/communicator/ssh/communicator.go @@ -285,7 +285,7 @@ func (c *comm) scpSession(scpCommand string, f func(io.Writer, *bufio.Reader) er // Start the sink mode on the other side // TODO(mitchellh): There are probably issues with shell escaping the path - log.Println("Starting remote scp process: %s", scpCommand) + log.Println("Starting remote scp process: ", scpCommand) if err := session.Start(scpCommand); err != nil { return err } From a9bd78a08fa071f8d0a223ea2d176fc3a1679d0d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Sep 2013 19:55:09 -0700 Subject: [PATCH 033/173] communicator/ssh: detect if SCP exited with failure [GH-386] --- CHANGELOG.md | 1 + communicator/ssh/communicator.go | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a7ec358e..f56ea00fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ IMPROVEMENTS: BUG FIXES: +* core: Detect if SCP is not enabled on the other side. [GH-386] * builder/amazon/all: When copying AMI to multiple regions, copy the metadata (tags and attributes) as well. [GH-388] * builder/amazon/all: Fix panic case where eventually consistent diff --git a/communicator/ssh/communicator.go b/communicator/ssh/communicator.go index 9279ee132..96de20de0 100644 --- a/communicator/ssh/communicator.go +++ b/communicator/ssh/communicator.go @@ -290,9 +290,11 @@ func (c *comm) scpSession(scpCommand string, f func(io.Writer, *bufio.Reader) er return err } - // Call our callback that executes in the context of SCP + // Call our callback that executes in the context of SCP. We ignore + // EOF errors if they occur because it usually means that SCP prematurely + // ended on the other side. log.Println("Started SCP session, beginning transfers...") - if err := f(stdinW, stdoutR); err != nil { + if err := f(stdinW, stdoutR); err != nil && err != io.EOF { return err } From 5b7865f085d0efc21a49fc49af659e6bccab04a9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Sep 2013 11:34:01 -0700 Subject: [PATCH 034/173] scripts: build.sh appends .exe on Windows [GH-397] --- scripts/build.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/build.sh b/scripts/build.sh index cf9c3d3cf..95ba90abf 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -20,6 +20,12 @@ cd $DIR GIT_COMMIT=$(git rev-parse HEAD) GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true) +# If we're building on Windows, specify an extension +EXTENSION="" +if [ "$(go env GOOS)" = "windows" ]; then + EXTENSION=".exe" +fi + # If we're building a race-enabled build, then set that up. if [ ! -z $PACKER_RACE ]; then echo -e "${OK_COLOR}--> Building with race detection enabled${NO_COLOR}" @@ -35,7 +41,7 @@ go build \ ${PACKER_RACE} \ -ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \ -v \ - -o bin/packer . + -o bin/packer${EXTENSION} . # Go over each plugin and build it for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do @@ -45,5 +51,5 @@ for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do ${PACKER_RACE} \ -ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \ -v \ - -o bin/packer-${PLUGIN_NAME} ${PLUGIN} + -o bin/packer-${PLUGIN_NAME}${EXTENSION} ${PLUGIN} done From e9d6e83436e761b34e681f6e7a52f3ebd1f6fb01 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Sep 2013 11:44:57 -0700 Subject: [PATCH 035/173] builder/amazon/common: support NoDevice [GH-396] --- CHANGELOG.md | 1 + builder/amazon/common/block_device.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f56ea00fa..0a73d24f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ IMPROVEMENTS: * builder/amazon/all: Ability to specify the format of the temporary keypair created. [GH-389] +* builder/amazon/all: Support the NoDevice flag for block mappings. [GH-396] * builder/digitalocean: Retry on any pending event errors. * builder/openstack: Can now specify a project. [GH-382] * builder/virtualbox: Can now attach hard drive over SATA. [GH-391] diff --git a/builder/amazon/common/block_device.go b/builder/amazon/common/block_device.go index 74714f016..995d8f6bf 100644 --- a/builder/amazon/common/block_device.go +++ b/builder/amazon/common/block_device.go @@ -13,6 +13,7 @@ type BlockDevice struct { VolumeSize int64 `mapstructure:"volume_size"` DeleteOnTermination bool `mapstructure:"delete_on_termination"` IOPS int64 `mapstructure:"iops"` + NoDevice bool `mapstructure:"no_device"` } type BlockDevices struct { @@ -32,6 +33,7 @@ func buildBlockDevices(b []BlockDevice) []ec2.BlockDeviceMapping { VolumeSize: blockDevice.VolumeSize, DeleteOnTermination: blockDevice.DeleteOnTermination, IOPS: blockDevice.IOPS, + NoDevice: blockDevice.NoDevice, }) } return blockDevices From 48a53446bf1acfcb30cdf0d58915a35658d9d20f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Sep 2013 11:48:38 -0700 Subject: [PATCH 036/173] builder/amazon/common: don't need squash here --- builder/amazon/common/block_device.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/amazon/common/block_device.go b/builder/amazon/common/block_device.go index 995d8f6bf..08f07e269 100644 --- a/builder/amazon/common/block_device.go +++ b/builder/amazon/common/block_device.go @@ -17,8 +17,8 @@ type BlockDevice struct { } type BlockDevices struct { - AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings,squash"` - LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings,squash"` + AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings"` + LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings"` } func buildBlockDevices(b []BlockDevice) []ec2.BlockDeviceMapping { From 2a04f49bcae7ddaa2950bfe658e13e6531e22087 Mon Sep 17 00:00:00 2001 From: Sean Mackrory Date: Fri, 6 Sep 2013 19:08:06 -0700 Subject: [PATCH 037/173] Using the vm_name parameter to name OVA/OVF files and their attachments --- builder/virtualbox/step_export.go | 2 +- website/source/docs/builders/virtualbox.html.markdown | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/virtualbox/step_export.go b/builder/virtualbox/step_export.go index 001aac89c..fd7282c67 100644 --- a/builder/virtualbox/step_export.go +++ b/builder/virtualbox/step_export.go @@ -50,7 +50,7 @@ func (s *stepExport) Run(state multistep.StateBag) multistep.StepAction { } // Export the VM to an OVF - outputPath := filepath.Join(config.OutputDir, "packer."+config.Format) + outputPath := filepath.Join(config.OutputDir, vmName+"."+config.Format) command = []string{ "export", diff --git a/website/source/docs/builders/virtualbox.html.markdown b/website/source/docs/builders/virtualbox.html.markdown index c4cbe6a39..75e6b1943 100644 --- a/website/source/docs/builders/virtualbox.html.markdown +++ b/website/source/docs/builders/virtualbox.html.markdown @@ -191,7 +191,7 @@ Optional: By default this is ".vbox_version", which will generally upload it into the home directory. -* `vm_name` (string) - This is the name of the VMX file for the new virtual +* `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. From a39e01b8d82069cd791b606a6f16815d40c8e7e7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Sep 2013 18:14:17 -0700 Subject: [PATCH 038/173] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a73d24f0..642b0b87c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ BUG FIXES: the metadata (tags and attributes) as well. [GH-388] * builder/amazon/all: Fix panic case where eventually consistent instance state caused an index out of bounds. +* builder/virtualbox: The `vm_name` setting now properly sets the OVF + name of the output. [GH-401] * builder/vmware: Autoanswer VMware dialogs. [GH-393] * command/inspect: Fix weird output for default values for optional vars. From f941718c3e0496a3772667a6ccac4fc47df5f137 Mon Sep 17 00:00:00 2001 From: Mark Peek Date: Sat, 7 Sep 2013 18:42:50 -0700 Subject: [PATCH 039/173] builder/amazon: add clean_ami_name template function Add a clean_ami_name template function which will translate illegal characters in an AMI name to '-'. Example usage would be: "ami_name": "Ubuntu 12.04 {{isotime | clean_ami_name}}" --- builder/amazon/chroot/builder.go | 1 + builder/amazon/common/template_funcs.go | 38 ++++++++++++++++++++ builder/amazon/common/template_funcs_test.go | 16 +++++++++ builder/amazon/ebs/builder.go | 1 + builder/amazon/instance/builder.go | 1 + packer/config_template.go | 5 +++ 6 files changed, 62 insertions(+) create mode 100644 builder/amazon/common/template_funcs.go create mode 100644 builder/amazon/common/template_funcs_test.go diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index f2f3a10e8..c940a7bfb 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -53,6 +53,7 @@ func (b *Builder) Prepare(raws ...interface{}) error { return err } b.config.tpl.UserVars = b.config.PackerUserVars + b.config.tpl.Funcs(awscommon.TemplateFuncs) // Defaults if b.config.ChrootMounts == nil { diff --git a/builder/amazon/common/template_funcs.go b/builder/amazon/common/template_funcs.go new file mode 100644 index 000000000..45f68af92 --- /dev/null +++ b/builder/amazon/common/template_funcs.go @@ -0,0 +1,38 @@ +package common + +import ( + "bytes" + "text/template" +) + +func isalphanumeric(b byte) bool { + if '0' <= b && b <= '9' { + return true + } + if 'a' <= b && b <= 'z' { + return true + } + if 'A' <= b && b <= 'Z' { + return true + } + return false +} + +// Clean up AMI name by replacing invalid characters with "-" +func templateCleanAMIName(s string) string { + allowed := []byte{'(', ')', ',', '/', '-', '_'} + b := []byte(s) + newb := make([]byte, len(b)) + for i, c := range b { + if isalphanumeric(c) || bytes.IndexByte(allowed, c) != -1 { + newb[i] = c + } else { + newb[i] = '-' + } + } + return string(newb[:]) +} + +var TemplateFuncs = template.FuncMap{ + "clean_ami_name": templateCleanAMIName, +} diff --git a/builder/amazon/common/template_funcs_test.go b/builder/amazon/common/template_funcs_test.go new file mode 100644 index 000000000..0e8c568ef --- /dev/null +++ b/builder/amazon/common/template_funcs_test.go @@ -0,0 +1,16 @@ +package common + +import ( + "testing" +) + +func TestAMITemplatePrepare_clean(t *testing.T) { + origName := "AMZamz09(),/-_:&^$%" + expected := "AMZamz09(),/-_-----" + + name := templateCleanAMIName(origName) + + if name != expected { + t.Fatalf("template names do not match: expected %s got %s\n", expected, name) + } +} diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index 453102e3d..194e10bc9 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -44,6 +44,7 @@ func (b *Builder) Prepare(raws ...interface{}) error { return err } b.config.tpl.UserVars = b.config.PackerUserVars + b.config.tpl.Funcs(awscommon.TemplateFuncs) // Accumulate any errors errs := common.CheckUnusedConfig(md) diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index 9b9c6d953..071e0e24e 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -56,6 +56,7 @@ func (b *Builder) Prepare(raws ...interface{}) error { return err } b.config.tpl.UserVars = b.config.PackerUserVars + b.config.tpl.Funcs(awscommon.TemplateFuncs) if b.config.BundleDestination == "" { b.config.BundleDestination = "/tmp" diff --git a/packer/config_template.go b/packer/config_template.go index 1f46cf92f..895e11682 100644 --- a/packer/config_template.go +++ b/packer/config_template.go @@ -63,6 +63,11 @@ func (t *ConfigTemplate) Validate(s string) error { return err } +// Add additional functions to the template +func (t *ConfigTemplate) Funcs(funcs template.FuncMap) { + t.root.Funcs(funcs) +} + func (t *ConfigTemplate) nextTemplateName() string { name := fmt.Sprintf("tpl%d", t.i) t.i++ From 521e0163eed5edd5bc36f0622f2cf9f35d82d1d4 Mon Sep 17 00:00:00 2001 From: Mark Peek Date: Sat, 7 Sep 2013 18:50:01 -0700 Subject: [PATCH 040/173] website: document isotime and clean_ami_name --- .../docs/templates/configuration-templates.html.markdown | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/website/source/docs/templates/configuration-templates.html.markdown b/website/source/docs/templates/configuration-templates.html.markdown index 36d232d05..88d13f159 100644 --- a/website/source/docs/templates/configuration-templates.html.markdown +++ b/website/source/docs/templates/configuration-templates.html.markdown @@ -53,4 +53,13 @@ While some configuration settings have local variables specific to only that configuration, a set of functions are available globally for use in _any string_ in Packer templates. These are listed below for reference. +* ``isotime`` - UTC time in RFC-3339 format. * ``timestamp`` - The current Unix timestamp in UTC. + +## Amazon Specific Functions + +Specific to Amazon builders: + +* ``clean_ami_name`` - AMI names can only contain certain characters. This + function will replace illegal characters with a '-" character. Example usage + since ":" is not a legal AMI name is: `{{isotime | clean_ami_name}}`. From 7b9ded8a369ec252428c5f175ea486fb14a7e76d Mon Sep 17 00:00:00 2001 From: Mark Peek Date: Sat, 7 Sep 2013 18:53:34 -0700 Subject: [PATCH 041/173] website: add no_device to amazon block device doc --- website/source/docs/builders/amazon-ebs.html.markdown | 2 +- website/source/docs/builders/amazon-instance.html.markdown | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/source/docs/builders/amazon-ebs.html.markdown b/website/source/docs/builders/amazon-ebs.html.markdown index 30b448ca3..7248a2e2d 100644 --- a/website/source/docs/builders/amazon-ebs.html.markdown +++ b/website/source/docs/builders/amazon-ebs.html.markdown @@ -62,7 +62,7 @@ Optional: 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), and "iops" (int). + (bool), "no\_device" (bool), and "iops" (int). * `ami_description` (string) - The description to set for the resulting AMI(s). By default this description is empty. diff --git a/website/source/docs/builders/amazon-instance.html.markdown b/website/source/docs/builders/amazon-instance.html.markdown index 01e2a70e1..a2b3b4d1a 100644 --- a/website/source/docs/builders/amazon-instance.html.markdown +++ b/website/source/docs/builders/amazon-instance.html.markdown @@ -77,8 +77,8 @@ Optional: 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), and "iops" (int). See [amazon-ebs](/docs/builders/amazon-ebs.html) - for an example template. + (bool), "no\_device" (bool), and "iops" (int). + See [amazon-ebs](/docs/builders/amazon-ebs.html) for an example template. * `ami_description` (string) - The description to set for the resulting AMI(s). By default this description is empty. From 312cb4a2dba7d4c7e0cbdaa5933a6a344535fff5 Mon Sep 17 00:00:00 2001 From: Mark Peek Date: Sat, 7 Sep 2013 18:57:00 -0700 Subject: [PATCH 042/173] Update CHANGELOG to include clean_ami_name --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 642b0b87c..fb35d80a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ FEATURES: UUID. * New globally available template function: `isotime`. Generates the current time in ISO standard format. +* New Amazon template function: `clean_ami_name`. Substitutes '-' for + characters that are illegal to use in an AMI name. IMPROVEMENTS: From 5079fd4f0b45443ecd318cbea3134597ca64e807 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Sep 2013 22:27:25 -0700 Subject: [PATCH 043/173] Rename puppet provisioner to puppet-masterless --- config.go | 4 ++-- plugin/provisioner-puppet-masterless/main.go | 10 ++++++++++ plugin/provisioner-puppet-masterless/main_test.go | 1 + plugin/provisioner-puppet/main.go | 10 ---------- .../{puppet => puppet-masterless}/provisioner.go | 2 +- .../{puppet => puppet-masterless}/provisioner_test.go | 2 +- 6 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 plugin/provisioner-puppet-masterless/main.go create mode 100644 plugin/provisioner-puppet-masterless/main_test.go delete mode 100644 plugin/provisioner-puppet/main.go rename provisioner/{puppet => puppet-masterless}/provisioner.go (99%) rename provisioner/{puppet => puppet-masterless}/provisioner_test.go (93%) diff --git a/config.go b/config.go index 207662e4d..671f75b1f 100644 --- a/config.go +++ b/config.go @@ -42,9 +42,9 @@ const defaultConfig = ` "provisioners": { "chef-solo": "packer-provisioner-chef-solo", "file": "packer-provisioner-file", + "puppet-masterless": "packer-provisioner-puppet-masterless", "shell": "packer-provisioner-shell", - "salt-masterless": "packer-provisioner-salt-masterless", - "puppet": "packer-provisioner-puppet" + "salt-masterless": "packer-provisioner-salt-masterless" } } ` diff --git a/plugin/provisioner-puppet-masterless/main.go b/plugin/provisioner-puppet-masterless/main.go new file mode 100644 index 000000000..c510cbb78 --- /dev/null +++ b/plugin/provisioner-puppet-masterless/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/mitchellh/packer/packer/plugin" + "github.com/mitchellh/packer/provisioner/puppet-masterless" +) + +func main() { + plugin.ServeProvisioner(new(puppetmasterless.Provisioner)) +} diff --git a/plugin/provisioner-puppet-masterless/main_test.go b/plugin/provisioner-puppet-masterless/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/plugin/provisioner-puppet-masterless/main_test.go @@ -0,0 +1 @@ +package main diff --git a/plugin/provisioner-puppet/main.go b/plugin/provisioner-puppet/main.go deleted file mode 100644 index 09f330bce..000000000 --- a/plugin/provisioner-puppet/main.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import ( - "github.com/mitchellh/packer/packer/plugin" - "github.com/mitchellh/packer/provisioner/puppet" -) - -func main() { - plugin.ServeProvisioner(new(puppet.Provisioner)) -} diff --git a/provisioner/puppet/provisioner.go b/provisioner/puppet-masterless/provisioner.go similarity index 99% rename from provisioner/puppet/provisioner.go rename to provisioner/puppet-masterless/provisioner.go index 5eb6d7320..868e771cf 100644 --- a/provisioner/puppet/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -1,6 +1,6 @@ // This package implements a provisioner for Packer that executes // Puppet within the remote machine -package puppet +package puppetmasterless import ( "bytes" diff --git a/provisioner/puppet/provisioner_test.go b/provisioner/puppet-masterless/provisioner_test.go similarity index 93% rename from provisioner/puppet/provisioner_test.go rename to provisioner/puppet-masterless/provisioner_test.go index 8e62e4649..e2e4b3c96 100644 --- a/provisioner/puppet/provisioner_test.go +++ b/provisioner/puppet-masterless/provisioner_test.go @@ -1,4 +1,4 @@ -package puppet +package puppetmasterless import ( "github.com/mitchellh/packer/packer" From 7684ee942b7e305feee7f7aa1facaa0014800dd3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Sep 2013 22:28:06 -0700 Subject: [PATCH 044/173] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb35d80a6..2274984ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ BACKWARDS INCOMPATIBILITIES: FEATURES: +* **NEW PROVISIONER:** `puppet-masterless`. You can now provision with + a masterless Puppet setup. [GH-234] * New globally available template function: `uuid`. Generates a new random UUID. * New globally available template function: `isotime`. Generates the From d854b08642cfe9bf206b2d5e8b2c4d0af1a15ed7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Sep 2013 23:08:56 -0700 Subject: [PATCH 045/173] provisioner/puppet-masterless: rework internals, use SCP --- provisioner/puppet-masterless/provisioner.go | 348 +++++++++---------- 1 file changed, 164 insertions(+), 184 deletions(-) diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go index 868e771cf..5a0288cba 100644 --- a/provisioner/puppet-masterless/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -1,141 +1,180 @@ // This package implements a provisioner for Packer that executes -// Puppet within the remote machine +// Puppet on the remote machine, configured to apply a local manifest +// versus connecting to a Puppet master. package puppetmasterless import ( - "bytes" "fmt" - "github.com/mitchellh/iochan" - "github.com/mitchellh/mapstructure" + "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" - "io" - "log" "os" "path/filepath" "strings" - "text/template" ) -const ( - RemoteStagingPath = "/tmp/provision/puppet" - DefaultModulePath = "modules" - DefaultManifestPath = "manifests" - DefaultManifestFile = "site.pp" -) +type Config struct { + common.PackerConfig `mapstructure:",squash"` + tpl *packer.ConfigTemplate -var Ui packer.Ui + // The command used to execute Puppet. + ExecuteCommand string `mapstructure:"execute_command"` -type config struct { // An array of local paths of modules to upload. - ModulePath string `mapstructure:"module_path"` + ModulePaths []string `mapstructure:"module_paths"` - // Path to the manifests - ManifestPath string `mapstructure:"manifest_path"` - - // Manifest file + // The main manifest file to apply to kick off the entire thing. ManifestFile string `mapstructure:"manifest_file"` - // Option to avoid sudo use when executing commands. Defaults to false. + // If true, `sudo` will NOT be used to execute Puppet. PreventSudo bool `mapstructure:"prevent_sudo"` + + // The directory where files will be uploaded. Packer requires write + // permissions in this directory. + StagingDir string `mapstructure:"staging_directory"` } type Provisioner struct { - config config + config Config } -type ExecuteManifestTemplate struct { - Sudo bool - Modulepath string - Manifest string +type ExecuteTemplate struct { + ModulePath string + ManifestFile string + Sudo bool } func (p *Provisioner) Prepare(raws ...interface{}) error { - errs := make([]error, 0) - for _, raw := range raws { - if err := mapstructure.Decode(raw, &p.config); err != nil { - return err + md, 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 := common.CheckUnusedConfig(md) + + // Set some defaults + if p.config.ExecuteCommand == "" { + p.config.ExecuteCommand = "{{if .Sudo}}sudo {{end}}puppet apply --verbose --modulepath='{{.ModulePath}}' {{.ManifestFile}}" + } + + if p.config.StagingDir == "" { + p.config.StagingDir = "/tmp/packer-puppet-masterless" + } + + // Templates + templates := map[string]*string{ + "staging_dir": &p.config.StagingDir, + } + + 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.ModulePath == "" { - p.config.ModulePath = DefaultModulePath + sliceTemplates := map[string][]string{ + "module_paths": p.config.ModulePaths, } - if p.config.ManifestPath == "" { - p.config.ManifestPath = DefaultManifestPath + for n, slice := range sliceTemplates { + for i, elem := range slice { + var err error + slice[i], err = p.config.tpl.Process(elem, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err)) + } + } } + validates := map[string]*string{ + "execute_command": &p.config.ExecuteCommand, + } + + for n, ptr := range validates { + if err := p.config.tpl.Validate(*ptr); err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error parsing %s: %s", n, err)) + } + } + + // Validation if p.config.ManifestFile == "" { - p.config.ManifestFile = DefaultManifestFile - } - - if p.config.ModulePath != "" { - pFileInfo, err := os.Stat(p.config.ModulePath) - - if err != nil || !pFileInfo.IsDir() { - errs = append(errs, fmt.Errorf("Bad module path '%s': %s", p.config.ModulePath, err)) + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("A manifest_file must be specified.")) + } else { + info, err := os.Stat(p.config.ManifestFile) + if err != nil { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("manifest_file is invalid: %s", err)) + } else if info.IsDir() { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("manifest_file must point to a file")) } } - if p.config.ManifestPath != "" { - pFileInfo, err := os.Stat(p.config.ManifestPath) - - if err != nil || !pFileInfo.IsDir() { - errs = append(errs, fmt.Errorf("Bad manifest path '%s': %s", p.config.ManifestPath, err)) - } - } - - if p.config.ManifestFile != "" { - path := filepath.Join(p.config.ManifestPath, p.config.ManifestFile) - if _, err := os.Stat(path); os.IsNotExist(err) { - errs = append(errs, fmt.Errorf("No manifest file '%s': %s", path, err)) - } - } - - if len(errs) > 0 { - return &packer.MultiError{errs} + if errs != nil && len(errs.Errors) > 0 { + return errs } return nil } func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { - var err error - Ui = ui - - err = CreateRemoteDirectory(RemoteStagingPath, comm) - if err != nil { - return fmt.Errorf("Error creating remote staging directory: %s", err) + ui.Message("Creating Puppet staging directory...") + if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { + return fmt.Errorf("Error creating staging directory: %s", err) } // Upload all modules - ui.Say(fmt.Sprintf("Copying module path: %s", p.config.ModulePath)) - err = UploadLocalDirectory(p.config.ModulePath, comm) - if err != nil { - return fmt.Errorf("Error uploading modules: %s", err) + modulePaths := make([]string, 0, len(p.config.ModulePaths)) + for i, path := range p.config.ModulePaths { + ui.Message(fmt.Sprintf("Upload local modules from: %s", path)) + targetPath := fmt.Sprintf("%s/module-%d", p.config.StagingDir, i) + if err := p.uploadDirectory(ui, comm, targetPath, path); err != nil { + return fmt.Errorf("Error uploading modules: %s", err) + } + + modulePaths = append(modulePaths, targetPath) } // Upload manifests - ui.Say(fmt.Sprintf("Copying manifests: %s", p.config.ManifestPath)) - err = UploadLocalDirectory(p.config.ManifestPath, comm) + ui.Message("Uploading manifests...") + remoteManifestFile, err := p.uploadManifests(ui, comm) if err != nil { return fmt.Errorf("Error uploading manifests: %s", err) } // Execute Puppet - ui.Say("Beginning Puppet run") - - // Compile the command - var command bytes.Buffer - mpath := filepath.Join(RemoteStagingPath, p.config.ManifestPath) - manifest := filepath.Join(mpath, p.config.ManifestFile) - modulepath := filepath.Join(RemoteStagingPath, p.config.ModulePath) - t := template.Must(template.New("puppet-run").Parse("{{if .Sudo}}sudo {{end}}puppet apply --verbose --modulepath={{.Modulepath}} {{.Manifest}}")) - t.Execute(&command, &ExecuteManifestTemplate{!p.config.PreventSudo, modulepath, manifest}) - - err = executeCommand(command.String(), comm) + command, err := p.config.tpl.Process(p.config.ExecuteCommand, &ExecuteTemplate{ + ManifestFile: remoteManifestFile, + ModulePath: strings.Join(modulePaths, ":"), + Sudo: !p.config.PreventSudo, + }) if err != nil { - return fmt.Errorf("Error running Puppet: %s", err) + return err + } + + cmd := &packer.RemoteCmd{ + Command: command, + } + + ui.Message("Running Puppet...") + if err := cmd.StartWithUi(comm, ui); err != nil { + return err + } + + if cmd.ExitStatus != 0 { + return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus) } return nil @@ -147,116 +186,57 @@ func (p *Provisioner) Cancel() { os.Exit(0) } -func UploadLocalDirectory(localDir string, comm packer.Communicator) (err error) { - visitPath := func(path string, f os.FileInfo, err error) (err2 error) { - var remotePath = RemoteStagingPath + "/" + path - if f.IsDir() { - // Make remote directory - err = CreateRemoteDirectory(remotePath, comm) - if err != nil { - return err - } - } else { - // Upload file to existing directory - file, err := os.Open(path) - if err != nil { - return fmt.Errorf("Error opening file: %s", err) - } - - err = comm.Upload(remotePath, file) - if err != nil { - return fmt.Errorf("Error uploading file: %s", err) - } - } - return +func (p *Provisioner) uploadManifests(ui packer.Ui, comm packer.Communicator) (string, error) { + // Create the remote manifests directory... + ui.Message("Uploading manifests...") + remoteManifestsPath := fmt.Sprintf("%s/manifests", p.config.StagingDir) + if err := p.createDir(ui, comm, remoteManifestsPath); err != nil { + return "", fmt.Errorf("Error creating manifests directory: %s", err) } - log.Printf("Uploading directory %s", localDir) - err = filepath.Walk(localDir, visitPath) + // Upload the main manifest + f, err := os.Open(p.config.ManifestFile) if err != nil { - return fmt.Errorf("Error uploading modules %s: %s", localDir, err) + return "", err + } + defer f.Close() + + manifestFilename := filepath.Base(p.config.ManifestFile) + remoteManifestFile := fmt.Sprintf("%s/%s", remoteManifestsPath, manifestFilename) + if err := comm.Upload(remoteManifestFile, f); err != nil { + return "", err + } + + return remoteManifestFile, nil +} + +func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error { + ui.Message(fmt.Sprintf("Creating directory: %s", dir)) + cmd := &packer.RemoteCmd{ + Command: fmt.Sprintf("mkdir -p '%s'", dir), + } + + if err := cmd.StartWithUi(comm, ui); err != nil { + return err + } + + if cmd.ExitStatus != 0 { + return fmt.Errorf("Non-zero exit status.") } return nil } -func CreateRemoteDirectory(path string, comm packer.Communicator) (err error) { - log.Printf("Creating remote directory: %s ", path) - - var copyCommand = []string{"mkdir -p", path} - - var cmd packer.RemoteCmd - cmd.Command = strings.Join(copyCommand, " ") - - var stdout bytes.Buffer - cmd.Stdout = &stdout - - // Start the command - if err := comm.Start(&cmd); err != nil { - return fmt.Errorf("Unable to create remote directory %s: %d", path, err) +func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error { + if err := p.createDir(ui, comm, dst); err != nil { + return err } - // Wait for it to complete - cmd.Wait() + // Make sure there is a trailing "/" so that the directory isn't + // created on the other side. + if src[len(src)-1] != '/' { + src = src + "/" + } - return -} - -func executeCommand(command string, comm packer.Communicator) (err error) { - // Setup the remote command - stdout_r, stdout_w := io.Pipe() - stderr_r, stderr_w := io.Pipe() - - var cmd packer.RemoteCmd - cmd.Command = command - cmd.Stdout = stdout_w - cmd.Stderr = stderr_w - - log.Printf("Executing command: %s", cmd.Command) - err = comm.Start(&cmd) - if err != nil { - return fmt.Errorf("Failed executing command: %s", err) - } - - exitChan := make(chan int, 1) - stdoutChan := iochan.DelimReader(stdout_r, '\n') - stderrChan := iochan.DelimReader(stderr_r, '\n') - - go func() { - defer stdout_w.Close() - defer stderr_w.Close() - - cmd.Wait() - exitChan <- cmd.ExitStatus - }() - -OutputLoop: - for { - select { - case output := <-stderrChan: - Ui.Message(strings.TrimSpace(output)) - case output := <-stdoutChan: - Ui.Message(strings.TrimSpace(output)) - case exitStatus := <-exitChan: - log.Printf("Puppet provisioner exited with status %d", exitStatus) - - if exitStatus != 0 { - return fmt.Errorf("Command exited with non-zero exit status: %d", exitStatus) - } - - break OutputLoop - } - } - - // Make sure we finish off stdout/stderr because we may have gotten - // a message from the exit channel first. - for output := range stdoutChan { - Ui.Message(output) - } - - for output := range stderrChan { - Ui.Message(output) - } - - return nil + return comm.UploadDir(dst, src, nil) } From 023678fe7b28edf56c2e1c13b63cd79443b8a2cd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Sep 2013 23:31:28 -0700 Subject: [PATCH 046/173] provisioner/puppet-masterless: more validation and tests --- provisioner/puppet-masterless/provisioner.go | 11 +++ .../puppet-masterless/provisioner_test.go | 67 ++++++++++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go index 5a0288cba..682aa584e 100644 --- a/provisioner/puppet-masterless/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -122,6 +122,17 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } } + for i, path := range p.config.ModulePaths { + info, err := os.Stat(path) + if err != nil { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("module_path[%d] is invalid: %s", i, err)) + } else if !info.IsDir() { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("module_path[%d] must point to a directory")) + } + } + if errs != nil && len(errs.Errors) > 0 { return errs } diff --git a/provisioner/puppet-masterless/provisioner_test.go b/provisioner/puppet-masterless/provisioner_test.go index e2e4b3c96..88dea6029 100644 --- a/provisioner/puppet-masterless/provisioner_test.go +++ b/provisioner/puppet-masterless/provisioner_test.go @@ -2,12 +2,19 @@ package puppetmasterless import ( "github.com/mitchellh/packer/packer" + "io/ioutil" + "os" "testing" ) func testConfig() map[string]interface{} { + tf, err := ioutil.TempFile("", "packer") + if err != nil { + panic(err) + } + return map[string]interface{}{ - // "inline": []interface{}{"foo", "bar"}, + "manifest_file": tf.Name(), } } @@ -18,3 +25,61 @@ func TestProvisioner_Impl(t *testing.T) { t.Fatalf("must be a Provisioner") } } + +func TestProvisionerPrepare_manifestFile(t *testing.T) { + config := testConfig() + + delete(config, "manifest_file") + p := new(Provisioner) + err := p.Prepare(config) + if err == nil { + t.Fatal("should be an error") + } + + // Test with a good one + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("error tempfile: %s", err) + } + defer os.Remove(tf.Name()) + + config["manifest_file"] = tf.Name() + p = new(Provisioner) + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestProvisionerPrepare_modulePaths(t *testing.T) { + config := testConfig() + + delete(config, "module_paths") + p := new(Provisioner) + err := p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Test with bad paths + config["module_paths"] = []string{"i-should-not-exist"} + p = new(Provisioner) + err = p.Prepare(config) + if err == nil { + t.Fatal("should be an error") + } + + // Test with a good one + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("error: %s", err) + } + defer os.RemoveAll(td) + + config["module_paths"] = []string{td} + p = new(Provisioner) + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } +} From b92aed55770ec8abbe66070a31bd79bea677defe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Sep 2013 23:43:06 -0700 Subject: [PATCH 047/173] provisioner/puppet-masterless: more sane messaging --- provisioner/puppet-masterless/provisioner.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go index 682aa584e..115198c53 100644 --- a/provisioner/puppet-masterless/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -149,7 +149,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { // Upload all modules modulePaths := make([]string, 0, len(p.config.ModulePaths)) for i, path := range p.config.ModulePaths { - ui.Message(fmt.Sprintf("Upload local modules from: %s", path)) + ui.Message(fmt.Sprintf("Uploading local modules from: %s", path)) targetPath := fmt.Sprintf("%s/module-%d", p.config.StagingDir, i) if err := p.uploadDirectory(ui, comm, targetPath, path); err != nil { return fmt.Errorf("Error uploading modules: %s", err) @@ -179,7 +179,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { Command: command, } - ui.Message("Running Puppet...") + ui.Message(fmt.Sprintf("Running Puppet: %s", command)) if err := cmd.StartWithUi(comm, ui); err != nil { return err } @@ -222,7 +222,6 @@ func (p *Provisioner) uploadManifests(ui packer.Ui, comm packer.Communicator) (s } func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error { - ui.Message(fmt.Sprintf("Creating directory: %s", dir)) cmd := &packer.RemoteCmd{ Command: fmt.Sprintf("mkdir -p '%s'", dir), } From 107309ab678c9193e4b7a3605bd84e92f2c53597 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Sep 2013 23:56:00 -0700 Subject: [PATCH 048/173] website: document the Puppet provisioner --- .../puppet-masterless.html.markdown | 89 +++++++++++++++++++ website/source/layouts/docs.erb | 1 + 2 files changed, 90 insertions(+) create mode 100644 website/source/docs/provisioners/puppet-masterless.html.markdown diff --git a/website/source/docs/provisioners/puppet-masterless.html.markdown b/website/source/docs/provisioners/puppet-masterless.html.markdown new file mode 100644 index 000000000..0535dc538 --- /dev/null +++ b/website/source/docs/provisioners/puppet-masterless.html.markdown @@ -0,0 +1,89 @@ +--- +layout: "docs" +page_title: "Puppet (Masterless) Provisioner" +--- + +# Puppet (Masterless) Provisioner + +Type: `puppet-masterless` + +The masterless Puppet provisioner configures Puppet to run on the machines +by Packer from local modules and manifest files. Modules and manifests +can be uploaded from your local machine to the remote machine or can simply +use remote paths (perhaps obtained using something like the shell provisioner). +Puppet is run in masterless mode, meaning it never communicates to a Puppet +master. + +
+Note that Puppet will not be installed automatically +by this provisioner. This provisioner expects that Puppet is already +installed on the machine. It is common practice to use the +shell provisioner before the +Puppet provisioner to do this. +
+ +## Basic Example + +The example below is fully functional and expects the configured manifest +file to exist relative to your working directory: + +
+{
+  "type": "puppet-masterless",
+  "manifest_file": "site.pp"
+}
+
+ +## Configuration Reference + +The reference of available configuration options is listed below. + +Required parameters: + +* `manifest_file` (string) - The manifest file for Puppet to use in order + to compile and run a catalog. This file must exist on your local system + and will be uploaded to the remote machine. + +Optional parameters: + +* `execute_command` (string) - The command used to execute Puppet. This has + various [configuration template variables](/docs/templates/configuration-templates.html) + available. See below for more information. + +* `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 + machine. By default, this is empty. + +* `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-masterless". + This directory doesn't need to exist but must have proper permissions so that + the SSH user that Packer uses is able to create directories and write into + this folder. If the permissions are not correct, use a shell provisioner + prior to this to configure it properly. + +## Execute Command + +By default, Packer uses the following command (broken across multiple lines +for readability) to execute Puppet: + +``` +{{if .Sudo}sudo {{end}}puppet apply \ + --verbose \ + --modulepath='{{.ModulePath}}' \ + {{.ManifestFile}} +``` + +This command can be customized using the `execute_command` configuration. +As you can see from the default value above, the value of this configuration +can contain various template variables, defined below: + +* `ManifestFile` - The path on the remote machine to the manifest file + for Puppet to use. +* `ModulePath` - The paths to the module directories. +* `Sudo` - A boolean of whether to `sudo` the command or not, depending on + the value of the `prevent_sudo` configuration. + diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 9de70e6ae..a241c3251 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -43,6 +43,7 @@
  • Shell Scripts
  • File Uploads
  • Chef Solo
  • +
  • Puppet
  • Salt
  • Custom
  • From eccffbd732781916198f34320cd3c508ba3915ec Mon Sep 17 00:00:00 2001 From: Andrew Schleifer Date: Sun, 8 Sep 2013 22:27:14 -0500 Subject: [PATCH 049/173] accidently a word specifically, "builders" --- website/source/docs/extend/builder.html.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/source/docs/extend/builder.html.markdown b/website/source/docs/extend/builder.html.markdown index b4ecec429..f9fff68ee 100644 --- a/website/source/docs/extend/builder.html.markdown +++ b/website/source/docs/extend/builder.html.markdown @@ -161,9 +161,9 @@ on certain cache keys, and is given exclusive access to that key for the duration of the lock. This locking mechanism allows multiple builders to share cache data even though they're running in parallel. -For example, both the VMware and VirtualBox support downloading an operating -system ISO from the internet. Most of the time, this ISO is identical. The -locking mechanisms of the cache allow one of the builders to download it +For example, both the VMware and VirtualBox builders support downloading an +operating system ISO from the internet. Most of the time, this ISO is identical. +The locking mechanisms of the cache allow one of the builders to download it only once, but allow both builders to share the downloaded file. The [documentation for packer.Cache](#) is From 41b70aae9914480433bff57acb639c7eac424bcc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 8 Sep 2013 22:59:43 -0700 Subject: [PATCH 050/173] provisioner/puppet-masterless: support custom facts --- provisioner/puppet-masterless/provisioner.go | 34 ++++++++++++++++++- .../puppet-masterless.html.markdown | 8 ++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go index 115198c53..c6a475a3d 100644 --- a/provisioner/puppet-masterless/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -19,6 +19,9 @@ type Config struct { // The command used to execute Puppet. ExecuteCommand string `mapstructure:"execute_command"` + // Additional facts to set when executing Puppet + Facter map[string]string + // An array of local paths of modules to upload. ModulePaths []string `mapstructure:"module_paths"` @@ -38,6 +41,7 @@ type Provisioner struct { } type ExecuteTemplate struct { + FacterVars string ModulePath string ManifestFile string Sudo bool @@ -60,7 +64,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { // Set some defaults if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = "{{if .Sudo}}sudo {{end}}puppet apply --verbose --modulepath='{{.ModulePath}}' {{.ManifestFile}}" + p.config.ExecuteCommand = "{{.FacterVars}}{{if .Sudo}} sudo -E {{end}}puppet apply --verbose --modulepath='{{.ModulePath}}' {{.ManifestFile}}" } if p.config.StagingDir == "" { @@ -107,6 +111,27 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } } + newFacts := make(map[string]string) + for k, v := range p.config.Facter { + k, err := p.config.tpl.Process(k, nil) + if err != nil { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("Error processing facter key %s: %s", k, err)) + continue + } + + v, err := p.config.tpl.Process(v, nil) + if err != nil { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("Error processing facter value '%s': %s", v, err)) + continue + } + + newFacts[k] = v + } + + p.config.Facter = newFacts + // Validation if p.config.ManifestFile == "" { errs = packer.MultiErrorAppend(errs, @@ -165,8 +190,15 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return fmt.Errorf("Error uploading manifests: %s", err) } + // Compile the facter variables + facterVars := make([]string, 0, len(p.config.Facter)) + for k, v := range p.config.Facter { + facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v)) + } + // Execute Puppet command, err := p.config.tpl.Process(p.config.ExecuteCommand, &ExecuteTemplate{ + FacterVars: strings.Join(facterVars, " "), ManifestFile: remoteManifestFile, ModulePath: strings.Join(modulePaths, ":"), Sudo: !p.config.PreventSudo, diff --git a/website/source/docs/provisioners/puppet-masterless.html.markdown b/website/source/docs/provisioners/puppet-masterless.html.markdown index 0535dc538..ee284c52e 100644 --- a/website/source/docs/provisioners/puppet-masterless.html.markdown +++ b/website/source/docs/provisioners/puppet-masterless.html.markdown @@ -50,6 +50,10 @@ Optional parameters: various [configuration template variables](/docs/templates/configuration-templates.html) available. See below for more information. +* `facter` (object, string keys and values) - Additonal + [facts](http://puppetlabs.com/puppet/related-projects/facter) to make + available when Puppet is running. + * `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 machine. By default, this is empty. @@ -71,7 +75,7 @@ By default, Packer uses the following command (broken across multiple lines for readability) to execute Puppet: ``` -{{if .Sudo}sudo {{end}}puppet apply \ +{{.FacterVars}}{{if .Sudo} sudo -E {{end}}puppet apply \ --verbose \ --modulepath='{{.ModulePath}}' \ {{.ManifestFile}} @@ -81,6 +85,8 @@ This command can be customized using the `execute_command` configuration. As you can see from the default value above, the value of this configuration can contain various template variables, defined below: +* `FacterVars` - Shell-friendly string of environmental variables used + to set custom facts configured for this provisioner. * `ManifestFile` - The path on the remote machine to the manifest file for Puppet to use. * `ModulePath` - The paths to the module directories. From fd0df9ff587247dacfe2c5491e8f8b32b2af9fd7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 9 Sep 2013 13:24:17 -0700 Subject: [PATCH 051/173] provisioners/puppet-masterless: support hiera configs --- provisioner/puppet-masterless/provisioner.go | 65 ++++++++++++++++--- .../puppet-masterless/provisioner_test.go | 25 +++++++ .../puppet-masterless.html.markdown | 7 ++ 3 files changed, 88 insertions(+), 9 deletions(-) diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go index c6a475a3d..c9ce901a4 100644 --- a/provisioner/puppet-masterless/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -22,6 +22,9 @@ type Config struct { // Additional facts to set when executing Puppet Facter map[string]string + // Path to a hiera configuration file to upload and use. + HieraConfigPath string `mapstructure:"hiera_config_path"` + // An array of local paths of modules to upload. ModulePaths []string `mapstructure:"module_paths"` @@ -41,10 +44,12 @@ type Provisioner struct { } type ExecuteTemplate struct { - FacterVars string - ModulePath string - ManifestFile string - Sudo bool + FacterVars string + HasHieraConfigPath bool + HieraConfigPath string + ModulePath string + ManifestFile string + Sudo bool } func (p *Provisioner) Prepare(raws ...interface{}) error { @@ -64,7 +69,10 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { // Set some defaults if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = "{{.FacterVars}}{{if .Sudo}} sudo -E {{end}}puppet apply --verbose --modulepath='{{.ModulePath}}' {{.ManifestFile}}" + p.config.ExecuteCommand = "{{.FacterVars}}{{if .Sudo}} sudo -E {{end}}" + + "puppet apply --verbose --modulepath='{{.ModulePath}}' " + + "{{if .HasHieraConfigPath}}--hiera_config='{{.HieraConfigPath}}' {{end}}" + + "{{.ManifestFile}}" } if p.config.StagingDir == "" { @@ -133,6 +141,17 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { p.config.Facter = newFacts // Validation + if p.config.HieraConfigPath != "" { + info, err := os.Stat(p.config.ManifestFile) + if err != nil { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("hiera_config_path is invalid: %s", err)) + } else if info.IsDir() { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("hiera_config_path must point to a file")) + } + } + if p.config.ManifestFile == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A manifest_file must be specified.")) @@ -171,6 +190,16 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return fmt.Errorf("Error creating staging directory: %s", err) } + // Upload hiera config if set + remoteHieraConfigPath := "" + if p.config.HieraConfigPath != "" { + var err error + remoteHieraConfigPath, err = p.uploadHieraConfig(ui, comm) + if err != nil { + return fmt.Errorf("Error uploading hiera config: %s", err) + } + } + // Upload all modules modulePaths := make([]string, 0, len(p.config.ModulePaths)) for i, path := range p.config.ModulePaths { @@ -198,10 +227,12 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { // Execute Puppet command, err := p.config.tpl.Process(p.config.ExecuteCommand, &ExecuteTemplate{ - FacterVars: strings.Join(facterVars, " "), - ManifestFile: remoteManifestFile, - ModulePath: strings.Join(modulePaths, ":"), - Sudo: !p.config.PreventSudo, + FacterVars: strings.Join(facterVars, " "), + HasHieraConfigPath: remoteHieraConfigPath != "", + HieraConfigPath: remoteHieraConfigPath, + ManifestFile: remoteManifestFile, + ModulePath: strings.Join(modulePaths, ":"), + Sudo: !p.config.PreventSudo, }) if err != nil { return err @@ -229,6 +260,22 @@ func (p *Provisioner) Cancel() { os.Exit(0) } +func (p *Provisioner) uploadHieraConfig(ui packer.Ui, comm packer.Communicator) (string, error) { + ui.Message("Uploading hiera configuration...") + f, err := os.Open(p.config.HieraConfigPath) + if err != nil { + return "", err + } + defer f.Close() + + path := fmt.Sprintf("%s/hiera.yaml", p.config.StagingDir) + if err := comm.Upload(path, f); err != nil { + return "", err + } + + return path, nil +} + func (p *Provisioner) uploadManifests(ui packer.Ui, comm packer.Communicator) (string, error) { // Create the remote manifests directory... ui.Message("Uploading manifests...") diff --git a/provisioner/puppet-masterless/provisioner_test.go b/provisioner/puppet-masterless/provisioner_test.go index 88dea6029..b19757f23 100644 --- a/provisioner/puppet-masterless/provisioner_test.go +++ b/provisioner/puppet-masterless/provisioner_test.go @@ -26,6 +26,31 @@ func TestProvisioner_Impl(t *testing.T) { } } +func TestProvisionerPrepare_hieraConfigPath(t *testing.T) { + config := testConfig() + + delete(config, "hiera_config_path") + p := new(Provisioner) + err := p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Test with a good one + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("error tempfile: %s", err) + } + defer os.Remove(tf.Name()) + + config["hiera_config_path"] = tf.Name() + p = new(Provisioner) + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } +} + func TestProvisionerPrepare_manifestFile(t *testing.T) { config := testConfig() diff --git a/website/source/docs/provisioners/puppet-masterless.html.markdown b/website/source/docs/provisioners/puppet-masterless.html.markdown index ee284c52e..11dbc6a85 100644 --- a/website/source/docs/provisioners/puppet-masterless.html.markdown +++ b/website/source/docs/provisioners/puppet-masterless.html.markdown @@ -54,6 +54,10 @@ Optional parameters: [facts](http://puppetlabs.com/puppet/related-projects/facter) to make available when Puppet is running. +* `hiera_config_path` (string) - The path to a local file with hiera + configuration to be uploaded to the remote machine. Hiera data directories + must be uploaded using the file provisioner separately. + * `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 machine. By default, this is empty. @@ -78,6 +82,7 @@ for readability) to execute Puppet: {{.FacterVars}}{{if .Sudo} sudo -E {{end}}puppet apply \ --verbose \ --modulepath='{{.ModulePath}}' \ + {{if .HasHieraConfigPath}}--hiera_config='{{.HieraConfigPath}}' {{end}} \ {{.ManifestFile}} ``` @@ -87,6 +92,8 @@ can contain various template variables, defined below: * `FacterVars` - Shell-friendly string of environmental variables used to set custom facts configured for this provisioner. +* `HasHieraConfigPath` - Boolean true if there is a hiera config path set. +* `HieraConfigPath` - The path to a hiera configuration file. * `ManifestFile` - The path on the remote machine to the manifest file for Puppet to use. * `ModulePath` - The paths to the module directories. From adbb490189587aa5d8729e1cad2ffdc89ec7be6a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 9 Sep 2013 13:58:23 -0700 Subject: [PATCH 052/173] provisioner/file: support uploading directories [GH-251] --- CHANGELOG.md | 1 + packer/communicator.go | 2 +- provisioner/file/provisioner.go | 11 ++++++ .../docs/provisioners/file.html.markdown | 36 +++++++++++++++++-- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2274984ce..6cde7c8d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ IMPROVEMENTS: * builder/digitalocean: Retry on any pending event errors. * builder/openstack: Can now specify a project. [GH-382] * builder/virtualbox: Can now attach hard drive over SATA. [GH-391] +* provisioner/file: Can now upload directories. [GH-251] BUG FIXES: diff --git a/packer/communicator.go b/packer/communicator.go index 236d34633..159e5e380 100644 --- a/packer/communicator.go +++ b/packer/communicator.go @@ -67,7 +67,7 @@ type Communicator interface { // is a trailing slash on the source "/". For example: "/tmp/src" as // the source will create a "src" directory in the destination unless // a trailing slash is added. This is identical behavior to rsync(1). - UploadDir(string, string, []string) error + UploadDir(dst string, src string, exclude []string) error // Download downloads a file from the machine from the given remote path // with the contents writing to the given writer. This method will diff --git a/provisioner/file/provisioner.go b/provisioner/file/provisioner.go index 096b10437..c20cbd0f2 100644 --- a/provisioner/file/provisioner.go +++ b/provisioner/file/provisioner.go @@ -72,6 +72,17 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { ui.Say(fmt.Sprintf("Uploading %s => %s", p.config.Source, p.config.Destination)) + info, err := os.Stat(p.config.Source) + if err != nil { + return err + } + + // If we're uploading a directory, short circuit and do that + if info.IsDir() { + return comm.UploadDir(p.config.Destination, p.config.Source, nil) + } + + // We're uploading a file... f, err := os.Open(p.config.Source) if err != nil { return err diff --git a/website/source/docs/provisioners/file.html.markdown b/website/source/docs/provisioners/file.html.markdown index cf2a6081f..4dea52ad8 100644 --- a/website/source/docs/provisioners/file.html.markdown +++ b/website/source/docs/provisioners/file.html.markdown @@ -12,6 +12,8 @@ recommended usage of the file provisioner is to use it to upload files, and then use [shell provisioner](/docs/provisioners/shell.html) to move them to the proper place, set permissions, etc. +The file provisioner can upload both single files and complete directories. + ## Basic Example
    @@ -26,10 +28,38 @@ them to the proper place, set permissions, etc.
     
     The available configuration options are listed below. All elements are required.
     
    -* `source` (string) - The path to a local file to upload to the machine. The
    -  path can be absolute or relative. If it is relative, it is relative to the
    -  working directory when Packer is executed.
    +* `source` (string) - The path to a local file or directory to upload to the
    +  machine. The path can be absolute or relative. If it is relative, it is
    +  relative to the working directory when Packer is executed. If this is a
    +  directory, the existence of a trailing slash is important. Read below on
    +  uploading directories.
     
     * `destination` (string) - The path where the file will be uploaded to in the
       machine. This value must be a writable location and any parent directories
       must already exist.
    +
    +## Directory Uploads
    +
    +The file provisioner is also able to upload a complete directory to the
    +remote machine. When uploading a directory, there are a few important things
    +you should know.
    +
    +First, the destination directory must already exist. If you need to
    +create it, use a shell provisioner just prior to the file provisioner
    +in order to create the directory.
    +
    +Next, the existence of a trailing slash on the source path will determine
    +whether the directory name will be embedded within the destination, or
    +whether the destination will be created. An example explains this best:
    +
    +If the source is `/foo` (no trailing slash), and the destination is
    +`/tmp`, then the contents of `/foo` on the local machine will be uploaded
    +to `/tmp/foo` on the remote machine. The `foo` directory on the remote
    +machine will be created by Packer.
    +
    +If the source, however, is `/foo/` (a trailing slash is present), and
    +the destination is `/tmp`, then the contents of `/foo` will be uploaded
    +directly into `/tmp` directly.
    +
    +This behavior was adopted from the standard behavior of rsync. Note that
    +under the covers, rsync may or may not be used.
    
    From c83fbc0b426360521c43031cb6586a9992a07c94 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Mon, 9 Sep 2013 14:03:40 -0700
    Subject: [PATCH 053/173] provisioner/puppet-masterless: better messaging
    
    ---
     provisioner/puppet-masterless/provisioner.go | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go
    index c9ce901a4..5c0b9218d 100644
    --- a/provisioner/puppet-masterless/provisioner.go
    +++ b/provisioner/puppet-masterless/provisioner.go
    @@ -185,6 +185,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
     }
     
     func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
    +	ui.Say("Provisioning with Puppet...")
     	ui.Message("Creating Puppet staging directory...")
     	if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
     		return fmt.Errorf("Error creating staging directory: %s", err)
    
    From a14a3fe3f1495e15550fd5a559b6786af170fcfd Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Mon, 9 Sep 2013 14:46:25 -0700
    Subject: [PATCH 054/173] provisioner/puppet-masterless: detailed exit codes
    
    ---
     provisioner/puppet-masterless/provisioner.go | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go
    index 5c0b9218d..9318679ef 100644
    --- a/provisioner/puppet-masterless/provisioner.go
    +++ b/provisioner/puppet-masterless/provisioner.go
    @@ -72,6 +72,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
     		p.config.ExecuteCommand = "{{.FacterVars}}{{if .Sudo}} sudo -E {{end}}" +
     			"puppet apply --verbose --modulepath='{{.ModulePath}}' " +
     			"{{if .HasHieraConfigPath}}--hiera_config='{{.HieraConfigPath}}' {{end}}" +
    +			"--detailed-exitcodes " +
     			"{{.ManifestFile}}"
     	}
     
    
    From 8e44971b3da6eeeca75875340bacb5f5f0c22ea9 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Mon, 9 Sep 2013 15:02:47 -0700
    Subject: [PATCH 055/173] provisioner/puppet-masterless: proper exit code check
    
    ---
     provisioner/puppet-masterless/provisioner.go | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go
    index 9318679ef..9341a1398 100644
    --- a/provisioner/puppet-masterless/provisioner.go
    +++ b/provisioner/puppet-masterless/provisioner.go
    @@ -249,7 +249,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
     		return err
     	}
     
    -	if cmd.ExitStatus != 0 {
    +	if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 {
     		return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus)
     	}
     
    
    From a049ff9c31b4fbe8bb664d058e9db6669e22debd Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Mon, 9 Sep 2013 14:48:42 -0700
    Subject: [PATCH 056/173] v0.3.7
    
    ---
     CHANGELOG.md      | 2 +-
     packer/version.go | 2 +-
     2 files changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index 6cde7c8d6..5f12a71ac 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -1,4 +1,4 @@
    -## 0.3.7 (unreleased)
    +## 0.3.7 (September 9, 2013)
     
     BACKWARDS INCOMPATIBILITIES:
     
    diff --git a/packer/version.go b/packer/version.go
    index 37bbac2f4..017ee4007 100644
    --- a/packer/version.go
    +++ b/packer/version.go
    @@ -15,7 +15,7 @@ const Version = "0.3.7"
     // 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 3163d785c8fecc2c060d98eaa9aee62f319304de Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Mon, 9 Sep 2013 15:19:59 -0700
    Subject: [PATCH 057/173] Update 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 5f12a71ac..c90c4b0da 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -1,3 +1,7 @@
    +## 0.3.8 (unreleased)
    +
    +
    +
     ## 0.3.7 (September 9, 2013)
     
     BACKWARDS INCOMPATIBILITIES:
    diff --git a/packer/version.go b/packer/version.go
    index 017ee4007..8f95a99ff 100644
    --- a/packer/version.go
    +++ b/packer/version.go
    @@ -10,12 +10,12 @@ import (
     var GitCommit string
     
     // The version of packer.
    -const Version = "0.3.7"
    +const Version = "0.3.8"
     
     // 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 fa2f277c67f545b745c027211066c7eda4d805bf Mon Sep 17 00:00:00 2001
    From: Jerry Clinesmith 
    Date: Tue, 10 Sep 2013 22:00:29 -0500
    Subject: [PATCH 058/173] #348: chef-solo provisioner: add support for
     data_bags and roles
    
    ---
     provisioner/chef-solo/provisioner.go      | 64 +++++++++++++++++++++--
     provisioner/chef-solo/provisioner_test.go | 22 ++++++++
     2 files changed, 82 insertions(+), 4 deletions(-)
    
    diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go
    index fe96ad467..7aa4cfc59 100644
    --- a/provisioner/chef-solo/provisioner.go
    +++ b/provisioner/chef-solo/provisioner.go
    @@ -18,6 +18,8 @@ type Config struct {
     	common.PackerConfig `mapstructure:",squash"`
     
     	CookbookPaths       []string `mapstructure:"cookbook_paths"`
    +	RolesPath      		string   `mapstructure:"roles_path"`
    +	DataBagsPath     	string   `mapstructure:"data_bags_path"`
     	ExecuteCommand      string   `mapstructure:"execute_command"`
     	InstallCommand      string   `mapstructure:"install_command"`
     	RemoteCookbookPaths []string `mapstructure:"remote_cookbook_paths"`
    @@ -35,7 +37,9 @@ type Provisioner struct {
     }
     
     type ConfigTemplate struct {
    -	CookbookPaths string
    +	CookbookPaths 	string
    +	RolesPath 		string
    +	DataBagsPath 	string
     }
     
     type ExecuteTemplate struct {
    @@ -129,6 +133,24 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
     				errs, fmt.Errorf("Bad cookbook path '%s': %s", path, err))
     		}
     	}
    +	
    +	if p.config.RolesPath != "" {
    +		pFileInfo, err := os.Stat(p.config.RolesPath)
    +		
    +		if err != nil || !pFileInfo.IsDir() {
    +			errs = packer.MultiErrorAppend(
    +				errs, fmt.Errorf("Bad roles path '%s': %s", p.config.RolesPath, err))
    +		}
    +	}
    +
    +	if p.config.DataBagsPath != "" {
    +		pFileInfo, err := os.Stat(p.config.DataBagsPath)
    +		
    +		if err != nil || !pFileInfo.IsDir() {
    +			errs = packer.MultiErrorAppend(
    +				errs, fmt.Errorf("Bad data bags path '%s': %s", p.config.DataBagsPath, err))
    +		}
    +	}
     
     	// Process the user variables within the JSON and set the JSON.
     	// Do this early so that we can validate and show errors.
    @@ -165,8 +187,24 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
     
     		cookbookPaths = append(cookbookPaths, targetPath)
     	}
    +	
    +	rolesPath := ""
    +	if p.config.RolesPath != "" {
    +		rolesPath := fmt.Sprintf("%s/roles", p.config.StagingDir)
    +		if err := p.uploadDirectory(ui, comm, rolesPath, p.config.RolesPath); err != nil {
    +			return fmt.Errorf("Error uploading roles: %s", err)
    +		}
    +	}
     
    -	configPath, err := p.createConfig(ui, comm, cookbookPaths)
    +	dataBagsPath := ""
    +	if p.config.DataBagsPath != "" {
    +		dataBagsPath := fmt.Sprintf("%s/data_bags", p.config.StagingDir)
    +		if err := p.uploadDirectory(ui, comm, dataBagsPath, p.config.DataBagsPath); err != nil {
    +			return fmt.Errorf("Error uploading data bags: %s", err)
    +		}
    +	}
    +
    +	configPath, err := p.createConfig(ui, comm, cookbookPaths, rolesPath, dataBagsPath)
     	if err != nil {
     		return fmt.Errorf("Error creating Chef config file: %s", err)
     	}
    @@ -203,7 +241,7 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds
     	return comm.UploadDir(dst, src, nil)
     }
     
    -func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, localCookbooks []string) (string, error) {
    +func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, localCookbooks []string, rolesPath string, dataBagsPath string) (string, error) {
     	ui.Message("Creating configuration file 'solo.rb'")
     
     	cookbook_paths := make([]string, len(p.config.RemoteCookbookPaths)+len(localCookbooks))
    @@ -215,9 +253,21 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local
     		i = len(p.config.RemoteCookbookPaths) + i
     		cookbook_paths[i] = fmt.Sprintf(`"%s"`, path)
     	}
    +	
    +	roles_path := ""
    +	if rolesPath != "" {
    +		roles_path = fmt.Sprintf(`"%s"`, rolesPath)
    +	}
    +
    +	data_bags_path := ""
    +	if dataBagsPath != "" {
    +		data_bags_path = fmt.Sprintf(`"%s"`, dataBagsPath)
    +	}
     
     	configString, err := p.config.tpl.Process(DefaultConfigTemplate, &ConfigTemplate{
     		CookbookPaths: strings.Join(cookbook_paths, ","),
    +		RolesPath: roles_path,
    +		DataBagsPath: data_bags_path,
     	})
     	if err != nil {
     		return "", err
    @@ -368,5 +418,11 @@ func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) {
     }
     
     var DefaultConfigTemplate = `
    -cookbook_path [{{.CookbookPaths}}]
    +cookbook_path 	[{{.CookbookPaths}}]
    +{{if .RolesPath != ""}}
    +role_path		{{.RolesPath}}
    +{{end}}
    +{{if .DataBagsPath != ""}}
    +data_bag_path	{{.DataBagsPath}}
    +{{end}}
     `
    diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go
    index 332718d01..73d882e95 100644
    --- a/provisioner/chef-solo/provisioner_test.go
    +++ b/provisioner/chef-solo/provisioner_test.go
    @@ -32,11 +32,25 @@ func TestProvisionerPrepare_cookbookPaths(t *testing.T) {
     		t.Fatalf("err: %s", err)
     	}
     
    +	rolesPath, err := ioutil.TempDir("", "roles")
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	dataBagsPath, err := ioutil.TempDir("", "data_bags")
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
     	defer os.Remove(path1)
     	defer os.Remove(path2)
    +	defer os.Remove(rolesPath)
    +	defer os.Remove(dataBagsPath)
     
     	config := testConfig()
     	config["cookbook_paths"] = []string{path1, path2}
    +	config["roles_path"] = rolesPath
    +	config["data_bags_path"] = dataBagsPath
     
     	err = p.Prepare(config)
     	if err != nil {
    @@ -50,6 +64,14 @@ func TestProvisionerPrepare_cookbookPaths(t *testing.T) {
     	if p.config.CookbookPaths[0] != path1 || p.config.CookbookPaths[1] != path2 {
     		t.Fatalf("unexpected: %#v", p.config.CookbookPaths)
     	}
    +	
    +	if p.config.RolesPath != rolesPath {
    +		t.Fatalf("unexpected: %#v", p.config.RolesPath)
    +	}
    +
    +	if p.config.DataBagsPath != dataBagsPath {
    +		t.Fatalf("unexpected: %#v", p.config.DataBagsPath)
    +	}
     }
     
     func TestProvisionerPrepare_json(t *testing.T) {
    
    From bcf6d537dcd486c37e9f9509b70b7055f6b8edf8 Mon Sep 17 00:00:00 2001
    From: Jerry Clinesmith 
    Date: Tue, 10 Sep 2013 22:00:52 -0500
    Subject: [PATCH 059/173] #348: chef-solo provisioner: update docs
    
    ---
     website/source/docs/provisioners/chef-solo.html.markdown | 8 ++++++++
     1 file changed, 8 insertions(+)
    
    diff --git a/website/source/docs/provisioners/chef-solo.html.markdown b/website/source/docs/provisioners/chef-solo.html.markdown
    index df6101b90..d5b6f4b01 100644
    --- a/website/source/docs/provisioners/chef-solo.html.markdown
    +++ b/website/source/docs/provisioners/chef-solo.html.markdown
    @@ -37,6 +37,14 @@ configuration is actually required, but at least `run_list` is recommended.
       to the remote machine in the directory specified by the `staging_directory`.
       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.
    +
    +* `data_bags_path` (string) - The path to the "data_bags" 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.
    +
     * `execute_command` (string) - The command used to execute Chef. This has
       various [configuration template variables](/docs/templates/configuration-templates.html)
       available. See below for more information.
    
    From cd2280f3bf9740480b924ef1fc53bd5c03f78bd6 Mon Sep 17 00:00:00 2001
    From: Justin Clayton 
    Date: Thu, 12 Sep 2013 16:52:25 -0700
    Subject: [PATCH 060/173] facter vars no longer smush up against puppet command
     when prevent_sudo is true
    
    ---
     provisioner/puppet-masterless/provisioner.go | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go
    index 9341a1398..9fe1c0fb8 100644
    --- a/provisioner/puppet-masterless/provisioner.go
    +++ b/provisioner/puppet-masterless/provisioner.go
    @@ -69,7 +69,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
     
     	// Set some defaults
     	if p.config.ExecuteCommand == "" {
    -		p.config.ExecuteCommand = "{{.FacterVars}}{{if .Sudo}} sudo -E {{end}}" +
    +		p.config.ExecuteCommand = "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" +
     			"puppet apply --verbose --modulepath='{{.ModulePath}}' " +
     			"{{if .HasHieraConfigPath}}--hiera_config='{{.HieraConfigPath}}' {{end}}" +
     			"--detailed-exitcodes " +
    
    From a6139af9ea193aeb45f4b49e2bc03b4530a511da Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Thu, 12 Sep 2013 17:05:57 -0700
    Subject: [PATCH 061/173] Update CHANGELOG
    
    ---
     CHANGELOG.md | 3 +++
     1 file changed, 3 insertions(+)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index c90c4b0da..4966cc39c 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -1,6 +1,9 @@
     ## 0.3.8 (unreleased)
     
    +BUG FIXES:
     
    +* provisioner/puppet-masterless: Fix failure case when both facter vars
    +  are used and prevent_sudo. [GH-415]
     
     ## 0.3.7 (September 9, 2013)
     
    
    From 27608a7b0f802a15b032c820cecfb01d0d66b6b0 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Thu, 12 Sep 2013 20:33:32 -0700
    Subject: [PATCH 062/173] builder/amazon/*: use WaitForState for AMIs
    
    ---
     CHANGELOG.md                                  |  5 ++++
     builder/amazon/chroot/step_register_ami.go    | 10 ++++++-
     builder/amazon/common/ami.go                  | 30 -------------------
     builder/amazon/common/instance.go             | 26 ++++++++++++++++
     builder/amazon/common/step_ami_region_copy.go | 13 ++++++--
     builder/amazon/ebs/step_create_ami.go         | 10 ++++++-
     builder/amazon/instance/step_register_ami.go  | 10 ++++++-
     7 files changed, 69 insertions(+), 35 deletions(-)
     delete mode 100644 builder/amazon/common/ami.go
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index 4966cc39c..6dfb440ad 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -1,7 +1,12 @@
     ## 0.3.8 (unreleased)
     
    +IMPROVEMENTS:
    +
    +* builder/amazon/*: Interrupts work while waiting for AMI to be ready.
    +
     BUG FIXES:
     
    +* builder/amazon/*: While waiting for AMI, will detect "failed" state.
     * provisioner/puppet-masterless: Fix failure case when both facter vars
       are used and prevent_sudo. [GH-415]
     
    diff --git a/builder/amazon/chroot/step_register_ami.go b/builder/amazon/chroot/step_register_ami.go
    index 4c75d0c27..cb52b646c 100644
    --- a/builder/amazon/chroot/step_register_ami.go
    +++ b/builder/amazon/chroot/step_register_ami.go
    @@ -52,8 +52,16 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
     	state.Put("amis", amis)
     
     	// Wait for the image to become ready
    +	stateChange := awscommon.StateChangeConf{
    +		Conn:      ec2conn,
    +		Pending:   []string{"pending"},
    +		Target:    "available",
    +		Refresh:   awscommon.AMIStateRefreshFunc(ec2conn, registerResp.ImageId),
    +		StepState: state,
    +	}
    +
     	ui.Say("Waiting for AMI to become ready...")
    -	if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil {
    +	if _, err := awscommon.WaitForState(&stateChange); err != nil {
     		err := fmt.Errorf("Error waiting for AMI: %s", err)
     		state.Put("error", err)
     		ui.Error(err.Error())
    diff --git a/builder/amazon/common/ami.go b/builder/amazon/common/ami.go
    deleted file mode 100644
    index 2fec62871..000000000
    --- a/builder/amazon/common/ami.go
    +++ /dev/null
    @@ -1,30 +0,0 @@
    -package common
    -
    -import (
    -	"github.com/mitchellh/goamz/ec2"
    -	"log"
    -	"time"
    -)
    -
    -// WaitForAMI waits for the given AMI ID to become ready.
    -func WaitForAMI(c *ec2.EC2, imageId string) error {
    -	for {
    -		imageResp, err := c.Images([]string{imageId}, ec2.NewFilter())
    -		if err != nil {
    -			if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" {
    -				log.Println("AMI not found, probably state issues on AWS side. Trying again.")
    -				continue
    -			}
    -
    -			return err
    -		}
    -
    -		if imageResp.Images[0].State == "available" {
    -			return nil
    -		}
    -
    -		log.Printf("Image in state %s, sleeping 2s before checking again",
    -			imageResp.Images[0].State)
    -		time.Sleep(2 * time.Second)
    -	}
    -}
    diff --git a/builder/amazon/common/instance.go b/builder/amazon/common/instance.go
    index 462bb58e7..2dedfb3a0 100644
    --- a/builder/amazon/common/instance.go
    +++ b/builder/amazon/common/instance.go
    @@ -30,6 +30,32 @@ type StateChangeConf struct {
     	Target    string
     }
     
    +// AMIStateRefreshFunc returns a StateRefreshFunc that is used to watch
    +// an AMI for state changes.
    +func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc {
    +	return func() (interface{}, string, error) {
    +		resp, err := conn.Images([]string{imageId}, ec2.NewFilter())
    +		if err != nil {
    +			if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" {
    +				// Set this to nil as if we didn't find anything.
    +				resp = nil
    +			} else {
    +				log.Printf("Error on AMIStateRefresh: %s", err)
    +				return nil, "", err
    +			}
    +		}
    +
    +		if resp == nil || len(resp.Images) == 0 {
    +			// Sometimes AWS has consistency issues and doesn't see the
    +			// AMI. Return an empty state.
    +			return nil, "", nil
    +		}
    +
    +		i := resp.Images[0]
    +		return i, i.State, nil
    +	}
    +}
    +
     // InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch
     // an EC2 instance.
     func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
    diff --git a/builder/amazon/common/step_ami_region_copy.go b/builder/amazon/common/step_ami_region_copy.go
    index 8d50f5dfc..5ee486522 100644
    --- a/builder/amazon/common/step_ami_region_copy.go
    +++ b/builder/amazon/common/step_ami_region_copy.go
    @@ -40,8 +40,17 @@ func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction {
     			return multistep.ActionHalt
     		}
     
    -		ui.Say(fmt.Sprintf("Waiting for AMI (%s) in region (%s) to become ready...", resp.ImageId, region))
    -		if err := WaitForAMI(regionconn, resp.ImageId); err != nil {
    +		stateChange := StateChangeConf{
    +			Conn:      regionconn,
    +			Pending:   []string{"pending"},
    +			Target:    "available",
    +			Refresh:   AMIStateRefreshFunc(regionconn, resp.ImageId),
    +			StepState: state,
    +		}
    +
    +		ui.Say(fmt.Sprintf("Waiting for AMI (%s) in region (%s) to become ready...",
    +			resp.ImageId, region))
    +		if _, err := WaitForState(&stateChange); err != nil {
     			err := fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s", resp.ImageId, region, err)
     			state.Put("error", err)
     			ui.Error(err.Error())
    diff --git a/builder/amazon/ebs/step_create_ami.go b/builder/amazon/ebs/step_create_ami.go
    index ec89827a0..9dea11317 100644
    --- a/builder/amazon/ebs/step_create_ami.go
    +++ b/builder/amazon/ebs/step_create_ami.go
    @@ -39,8 +39,16 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction {
     	state.Put("amis", amis)
     
     	// Wait for the image to become ready
    +	stateChange := awscommon.StateChangeConf{
    +		Conn:      ec2conn,
    +		Pending:   []string{"pending"},
    +		Target:    "available",
    +		Refresh:   awscommon.AMIStateRefreshFunc(ec2conn, createResp.ImageId),
    +		StepState: state,
    +	}
    +
     	ui.Say("Waiting for AMI to become ready...")
    -	if err := awscommon.WaitForAMI(ec2conn, createResp.ImageId); err != nil {
    +	if _, err := awscommon.WaitForState(&stateChange); err != nil {
     		err := fmt.Errorf("Error waiting for AMI: %s", err)
     		state.Put("error", err)
     		ui.Error(err.Error())
    diff --git a/builder/amazon/instance/step_register_ami.go b/builder/amazon/instance/step_register_ami.go
    index 729c020d3..35f31882d 100644
    --- a/builder/amazon/instance/step_register_ami.go
    +++ b/builder/amazon/instance/step_register_ami.go
    @@ -37,8 +37,16 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
     	state.Put("amis", amis)
     
     	// Wait for the image to become ready
    +	stateChange := awscommon.StateChangeConf{
    +		Conn:      ec2conn,
    +		Pending:   []string{"pending"},
    +		Target:    "available",
    +		Refresh:   awscommon.AMIStateRefreshFunc(ec2conn, registerResp.ImageId),
    +		StepState: state,
    +	}
    +
     	ui.Say("Waiting for AMI to become ready...")
    -	if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil {
    +	if _, err := awscommon.WaitForState(&stateChange); err != nil {
     		err := fmt.Errorf("Error waiting for AMI: %s", err)
     		state.Put("error", err)
     		ui.Error(err.Error())
    
    From 1bf91e284588a0eebe9a074aafb2288266cd6fb8 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Thu, 12 Sep 2013 20:33:58 -0700
    Subject: [PATCH 063/173] builder/amazon/common: rename instance.go to
     state.go, makes more sense
    
    ---
     builder/amazon/common/{instance.go => state.go} | 0
     1 file changed, 0 insertions(+), 0 deletions(-)
     rename builder/amazon/common/{instance.go => state.go} (100%)
    
    diff --git a/builder/amazon/common/instance.go b/builder/amazon/common/state.go
    similarity index 100%
    rename from builder/amazon/common/instance.go
    rename to builder/amazon/common/state.go
    
    From 61320b20a9f41b0e85a1347ca2a80dc215bacae0 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Thu, 12 Sep 2013 20:37:14 -0700
    Subject: [PATCH 064/173] builder/amazon/common: error if WaitForState can't
     find resource repeatably
    
    ---
     CHANGELOG.md                   |  2 ++
     builder/amazon/common/state.go | 16 +++++++++++++---
     2 files changed, 15 insertions(+), 3 deletions(-)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index 6dfb440ad..8a0b4700b 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -7,6 +7,8 @@ IMPROVEMENTS:
     BUG FIXES:
     
     * builder/amazon/*: While waiting for AMI, will detect "failed" state.
    +* builder/amazon/*: Waiting for state will detect if the resource (AMI,
    +  instance, etc.) disappears from under it.
     * provisioner/puppet-masterless: Fix failure case when both facter vars
       are used and prevent_sudo. [GH-415]
     
    diff --git a/builder/amazon/common/state.go b/builder/amazon/common/state.go
    index 2dedfb3a0..1e9dea36b 100644
    --- a/builder/amazon/common/state.go
    +++ b/builder/amazon/common/state.go
    @@ -82,6 +82,8 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
     func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
     	log.Printf("Waiting for state to become: %s", conf.Target)
     
    +	notfoundTick := 0
    +
     	for {
     		var currentState string
     		i, currentState, err = conf.Refresh()
    @@ -89,9 +91,17 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
     			return
     		}
     
    -		// Check states only if we were able to refresh to an instance
    -		// that exists.
    -		if i != nil {
    +		if i == nil {
    +			// If we didn't find the resource, check if we have been
    +			// not finding it for awhile, and if so, report an error.
    +			notfoundTick += 1
    +			if notfoundTick > 20 {
    +				return nil, errors.New("couldn't find resource")
    +			}
    +		} else {
    +			// Reset the counter for when a resource isn't found
    +			notfoundTick = 0
    +
     			if currentState == conf.Target {
     				return
     			}
    
    From 8f5a18581825b933711bc9d3c39bd629731d9b90 Mon Sep 17 00:00:00 2001
    From: Jack Pearkes 
    Date: Fri, 13 Sep 2013 14:54:28 +0200
    Subject: [PATCH 065/173] provisioner/puppet-masterless: remove duplicate
     manifest upload message
    
    ---
     provisioner/puppet-masterless/provisioner.go | 1 -
     1 file changed, 1 deletion(-)
    
    diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go
    index 9fe1c0fb8..ff8b2112b 100644
    --- a/provisioner/puppet-masterless/provisioner.go
    +++ b/provisioner/puppet-masterless/provisioner.go
    @@ -215,7 +215,6 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
     	}
     
     	// Upload manifests
    -	ui.Message("Uploading manifests...")
     	remoteManifestFile, err := p.uploadManifests(ui, comm)
     	if err != nil {
     		return fmt.Errorf("Error uploading manifests: %s", err)
    
    From b67580d3f24dd1896968945772e84c12f7d5ac74 Mon Sep 17 00:00:00 2001
    From: dlovell 
    Date: Sat, 14 Sep 2013 15:02:40 -0400
    Subject: [PATCH 066/173] fix iso_url: use old-releases.ubuntu.com/...
    
    ---
     website/source/docs/builders/vmware.html.markdown | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/website/source/docs/builders/vmware.html.markdown b/website/source/docs/builders/vmware.html.markdown
    index baadb0d93..4850337f1 100644
    --- a/website/source/docs/builders/vmware.html.markdown
    +++ b/website/source/docs/builders/vmware.html.markdown
    @@ -27,7 +27,7 @@ Ubuntu to self-install. Still, the example serves to show the basic configuratio
     
     {
       "type": "vmware",
    -  "iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.2-server-amd64.iso",
    +  "iso_url": "http://old-releases.ubuntu.com/releases/precise/ubuntu-12.04.2-server-amd64.iso",
       "iso_checksum": "af5f788aee1b32c4b2634734309cc9e9",
       "iso_checksum_type": "md5",
       "ssh_username": "packer",
    
    From eaba28a3704b71f98ea1ff676233c64f216aa92e Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Sun, 15 Sep 2013 12:21:21 -0700
    Subject: [PATCH 067/173] communicator/ssh, builder/digitalocean: fix new SSH
     API from upstream
    
    ---
     builder/digitalocean/step_create_ssh_key.go | 4 ++--
     communicator/ssh/keychain.go                | 7 ++++---
     2 files changed, 6 insertions(+), 5 deletions(-)
    
    diff --git a/builder/digitalocean/step_create_ssh_key.go b/builder/digitalocean/step_create_ssh_key.go
    index f33a368e8..6a9f0426c 100644
    --- a/builder/digitalocean/step_create_ssh_key.go
    +++ b/builder/digitalocean/step_create_ssh_key.go
    @@ -38,8 +38,8 @@ func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
     	state.Put("privateKey", string(pem.EncodeToMemory(&priv_blk)))
     
     	// Marshal the public key into SSH compatible format
    -	pub := priv.PublicKey
    -	pub_sshformat := string(ssh.MarshalAuthorizedKey(&pub))
    +	pub := ssh.NewRSAPublicKey(&priv.PublicKey)
    +	pub_sshformat := string(ssh.MarshalAuthorizedKey(pub))
     
     	// The name of the public key on DO
     	name := fmt.Sprintf("packer-%s", hex.EncodeToString(identifier.NewUUID().Raw()))
    diff --git a/communicator/ssh/keychain.go b/communicator/ssh/keychain.go
    index 686a6b1cb..39ab8cda0 100644
    --- a/communicator/ssh/keychain.go
    +++ b/communicator/ssh/keychain.go
    @@ -7,6 +7,7 @@ import (
     	"crypto/x509"
     	"encoding/pem"
     	"errors"
    +	"code.google.com/p/go.crypto/ssh"
     	"io"
     )
     
    @@ -53,15 +54,15 @@ func (k *SimpleKeychain) AddPEMKeyPassword(key string, password string) (err err
     }
     
     // Key method for ssh.ClientKeyring interface
    -func (k *SimpleKeychain) Key(i int) (interface{}, error) {
    +func (k *SimpleKeychain) Key(i int) (ssh.PublicKey, error) {
     	if i < 0 || i >= len(k.keys) {
     		return nil, nil
     	}
     	switch key := k.keys[i].(type) {
     	case *rsa.PrivateKey:
    -		return &key.PublicKey, nil
    +		return ssh.NewRSAPublicKey(&key.PublicKey), nil
     	case *dsa.PrivateKey:
    -		return &key.PublicKey, nil
    +		return ssh.NewDSAPublicKey(&key.PublicKey), nil
     	}
     	panic("unknown key type")
     }
    
    From 4952737ddbcc7d8da5c9c1373e8ab362ab8c14f6 Mon Sep 17 00:00:00 2001
    From: Charlie Sharpsteen 
    Date: Sun, 15 Sep 2013 21:04:25 -0700
    Subject: [PATCH 068/173] Fix VirtualBox scancodes
    
    Support for the backspace, delete and F1-F12 keys was added in commit 6028a3c.
    However, that commit seems to have copied character codes from the vmware
    builder into the VirtualBox builder. Character codes are appropriate for VMware
    which communicates through a VNC. However, VirtualBox communicates through
    simulating raw keyboard input and therefore needs scancodes which are key
    press/key release sequences.
    
    This patch converts backspace, delete and F1-F10 to scancodes. F11 and F12 are
    not listed in the [scancode reference][scancodes] so they have been omitted.
    
    [scancodes]: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
    ---
     builder/virtualbox/step_type_boot_command.go | 35 +++++++++++---------
     1 file changed, 20 insertions(+), 15 deletions(-)
    
    diff --git a/builder/virtualbox/step_type_boot_command.go b/builder/virtualbox/step_type_boot_command.go
    index 60c897ea9..e5fb442ea 100644
    --- a/builder/virtualbox/step_type_boot_command.go
    +++ b/builder/virtualbox/step_type_boot_command.go
    @@ -92,29 +92,34 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
     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
    +  // VBoxManage controlvm keyboardputscancode program.
    +  //
    +  // 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 0x81.
     	special := make(map[string][]string)
    -	special[""] = []string{"ff", "08"}
    -	special[""] = []string{"ff", "ff"}
    +	special[""] = []string{"0e", "8e"}
    +	special[""] = []string{"53", "d3"}
     	special[""] = []string{"1c", "9c"}
     	special[""] = []string{"01", "81"}
    -	special[""] = []string{"ff", "be"}
    -	special[""] = []string{"ff", "bf"}
    -	special[""] = []string{"ff", "c0"}
    -	special[""] = []string{"ff", "c1"}
    -	special[""] = []string{"ff", "c2"}
    -	special[""] = []string{"ff", "c3"}
    -	special[""] = []string{"ff", "c4"}
    -	special[""] = []string{"ff", "c5"}
    -	special[""] = []string{"ff", "c6"}
    -	special[""] = []string{"ff", "c7"}
    -	special[""] = []string{"ff", "c8"}
    -	special[""] = []string{"ff", "c9"}
    +	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 := "~!@#$%^&*()_+{}|:\"<>?"
     
    -	// Scancodes reference: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
     	scancodeIndex := make(map[string]uint)
     	scancodeIndex["1234567890-="] = 0x02
     	scancodeIndex["!@#$%^&*()_+"] = 0x02
    
    From a248ed886eaeebd8629168d4cc7c269da796f280 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Sun, 15 Sep 2013 22:34:46 -0700
    Subject: [PATCH 069/173] update CHANGELOG
    
    ---
     CHANGELOG.md | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index 8a0b4700b..7598f3970 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -9,6 +9,7 @@ BUG FIXES:
     * builder/amazon/*: While waiting for AMI, will detect "failed" state.
     * builder/amazon/*: Waiting for state will detect if the resource (AMI,
       instance, etc.) disappears from under it.
    +* builder/virtualbox: F1-F12 and delete scancodes now work. [GH-425]
     * provisioner/puppet-masterless: Fix failure case when both facter vars
       are used and prevent_sudo. [GH-415]
     
    
    From c653bfca3948e0fd6dff3d4292ebfd20c17e3f3d Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Sun, 15 Sep 2013 22:36:09 -0700
    Subject: [PATCH 070/173] Specify the Go version in CONTRIBUTING [GH-424]
    
    ---
     CONTRIBUTING.md | 4 +++-
     1 file changed, 3 insertions(+), 1 deletion(-)
    
    diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
    index 7956147f8..a66d3999d 100644
    --- a/CONTRIBUTING.md
    +++ b/CONTRIBUTING.md
    @@ -47,7 +47,9 @@ it raises the chances we can quickly merge or address your contributions.
     If you have never worked with Go before, you will have to complete the
     following steps in order to be able to compile and test Packer.
     
    -1. Install Go. On a Mac, you can `brew install go`.
    +1. Install Go. On a Mac, you can `brew install go`. Make sure the Go
    +   version is at least Go 1.1. Packer will not work with anything less than
    +   Go 1.1.
     
     2. Set and export the `GOPATH` environment variable. For example, you can
        add `export GOPATH=$HOME/Documents/golang` to your `.bash_profile`.
    
    From efa401e4d2001bb542e7fd260615546cb70b7cae Mon Sep 17 00:00:00 2001
    From: Charlie Sharpsteen 
    Date: Sun, 15 Sep 2013 23:18:55 -0700
    Subject: [PATCH 071/173] Fix scancode comment concerning key release
    
    According to the scancode reference, the release key code is generated by
    adding `0x80` to the key press code, not `0x81`.
    ---
     builder/virtualbox/step_type_boot_command.go | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/builder/virtualbox/step_type_boot_command.go b/builder/virtualbox/step_type_boot_command.go
    index e5fb442ea..c869cedad 100644
    --- a/builder/virtualbox/step_type_boot_command.go
    +++ b/builder/virtualbox/step_type_boot_command.go
    @@ -99,7 +99,7 @@ func scancodes(message string) []string {
       //
       // 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 0x81.
    +  // derived from the first by the addition of 0x80.
     	special := make(map[string][]string)
     	special[""] = []string{"0e", "8e"}
     	special[""] = []string{"53", "d3"}
    
    From 9f52b786023e9b65173e42bd24aafc18ef8602c5 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Sun, 15 Sep 2013 23:13:05 -0700
    Subject: [PATCH 072/173] fmt
    
    ---
     builder/virtualbox/step_type_boot_command.go | 14 +++++++-------
     communicator/ssh/keychain.go                 |  2 +-
     2 files changed, 8 insertions(+), 8 deletions(-)
    
    diff --git a/builder/virtualbox/step_type_boot_command.go b/builder/virtualbox/step_type_boot_command.go
    index c869cedad..66c05dfe9 100644
    --- a/builder/virtualbox/step_type_boot_command.go
    +++ b/builder/virtualbox/step_type_boot_command.go
    @@ -93,13 +93,13 @@ 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
    -  // VBoxManage controlvm keyboardputscancode program.
    -  //
    -  // 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.
    +	//
    +	// Scancodes represent raw keyboard output and are fed to the VM by the
    +	// VBoxManage controlvm keyboardputscancode program.
    +	//
    +	// 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 0x81.
     	special := make(map[string][]string)
     	special[""] = []string{"0e", "8e"}
     	special[""] = []string{"53", "d3"}
    diff --git a/communicator/ssh/keychain.go b/communicator/ssh/keychain.go
    index 39ab8cda0..d7934f8a8 100644
    --- a/communicator/ssh/keychain.go
    +++ b/communicator/ssh/keychain.go
    @@ -1,13 +1,13 @@
     package ssh
     
     import (
    +	"code.google.com/p/go.crypto/ssh"
     	"crypto"
     	"crypto/dsa"
     	"crypto/rsa"
     	"crypto/x509"
     	"encoding/pem"
     	"errors"
    -	"code.google.com/p/go.crypto/ssh"
     	"io"
     )
     
    
    From dc0232975d99bf984a90e8ad561c53ca9cac0839 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Sun, 15 Sep 2013 23:28:41 -0700
    Subject: [PATCH 073/173] provisioner/chef-solo: ability to specify custom chef
     template
    
    ---
     CHANGELOG.md                                  |  5 +++
     provisioner/chef-solo/provisioner.go          | 35 ++++++++++++++-
     provisioner/chef-solo/provisioner_test.go     | 43 +++++++++++++++++++
     .../docs/provisioners/chef-solo.html.markdown | 26 +++++++++++
     4 files changed, 107 insertions(+), 2 deletions(-)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index 7598f3970..cf2ee3d54 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -1,5 +1,10 @@
     ## 0.3.8 (unreleased)
     
    +FEATURES:
    +
    +* provisioner/chef-solo: Ability to specify a custom Chef configuration
    +  template.
    +
     IMPROVEMENTS:
     
     * builder/amazon/*: Interrupts work while waiting for AMI to be ready.
    diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go
    index fe96ad467..04bb472b8 100644
    --- a/provisioner/chef-solo/provisioner.go
    +++ b/provisioner/chef-solo/provisioner.go
    @@ -9,6 +9,7 @@ import (
     	"fmt"
     	"github.com/mitchellh/packer/common"
     	"github.com/mitchellh/packer/packer"
    +	"io/ioutil"
     	"os"
     	"path/filepath"
     	"strings"
    @@ -17,6 +18,7 @@ import (
     type Config struct {
     	common.PackerConfig `mapstructure:",squash"`
     
    +	ConfigTemplate      string   `mapstructure:"config_template"`
     	CookbookPaths       []string `mapstructure:"cookbook_paths"`
     	ExecuteCommand      string   `mapstructure:"execute_command"`
     	InstallCommand      string   `mapstructure:"install_command"`
    @@ -80,7 +82,8 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
     	errs := common.CheckUnusedConfig(md)
     
     	templates := map[string]*string{
    -		"staging_dir": &p.config.StagingDir,
    +		"config_template": &p.config.ConfigTemplate,
    +		"staging_dir":     &p.config.StagingDir,
     	}
     
     	for n, ptr := range templates {
    @@ -121,6 +124,17 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
     		}
     	}
     
    +	if p.config.ConfigTemplate != "" {
    +		fi, err := os.Stat(p.config.ConfigTemplate)
    +		if err != nil {
    +			errs = packer.MultiErrorAppend(
    +				errs, fmt.Errorf("Bad config template path: %s", err))
    +		} else if fi.IsDir() {
    +			errs = packer.MultiErrorAppend(
    +				errs, fmt.Errorf("Config template path must be a file: %s", err))
    +		}
    +	}
    +
     	for _, path := range p.config.CookbookPaths {
     		pFileInfo, err := os.Stat(path)
     
    @@ -216,7 +230,24 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local
     		cookbook_paths[i] = fmt.Sprintf(`"%s"`, path)
     	}
     
    -	configString, err := p.config.tpl.Process(DefaultConfigTemplate, &ConfigTemplate{
    +	// Read the template
    +	tpl := DefaultConfigTemplate
    +	if p.config.ConfigTemplate != "" {
    +		f, err := os.Open(p.config.ConfigTemplate)
    +		if err != nil {
    +			return "", err
    +		}
    +		defer f.Close()
    +
    +		tplBytes, err := ioutil.ReadAll(f)
    +		if err != nil {
    +			return "", err
    +		}
    +
    +		tpl = string(tplBytes)
    +	}
    +
    +	configString, err := p.config.tpl.Process(tpl, &ConfigTemplate{
     		CookbookPaths: strings.Join(cookbook_paths, ","),
     	})
     	if err != nil {
    diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go
    index 332718d01..dc8bea1e7 100644
    --- a/provisioner/chef-solo/provisioner_test.go
    +++ b/provisioner/chef-solo/provisioner_test.go
    @@ -19,6 +19,49 @@ func TestProvisioner_Impl(t *testing.T) {
     	}
     }
     
    +func TestProvisionerPrepare_configTemplate(t *testing.T) {
    +	var err error
    +	var p Provisioner
    +
    +	// Test no config template
    +	config := testConfig()
    +	delete(config, "config_template")
    +	err = p.Prepare(config)
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	// Test with a file
    +	tf, err := ioutil.TempFile("", "packer")
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +	defer os.Remove(tf.Name())
    +
    +	config = testConfig()
    +	config["config_template"] = tf.Name()
    +	p = Provisioner{}
    +	err = p.Prepare(config)
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	// Test with a directory
    +	td, err := ioutil.TempDir("", "packer")
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +	defer os.RemoveAll(td)
    +
    +	config = testConfig()
    +	config["config_template"] = td
    +	p = Provisioner{}
    +	err = p.Prepare(config)
    +	if err == nil {
    +		t.Fatal("should have err")
    +	}
    +}
    +
     func TestProvisionerPrepare_cookbookPaths(t *testing.T) {
     	var p Provisioner
     
    diff --git a/website/source/docs/provisioners/chef-solo.html.markdown b/website/source/docs/provisioners/chef-solo.html.markdown
    index df6101b90..12f3c5724 100644
    --- a/website/source/docs/provisioners/chef-solo.html.markdown
    +++ b/website/source/docs/provisioners/chef-solo.html.markdown
    @@ -32,6 +32,13 @@ The example below is fully functional and expects cookbooks in the
     The reference of available configuration options is listed below. No
     configuration is actually required, but at least `run_list` is recommended.
     
    +* `config_template` (string) - Path to a template that will be used for
    +  the Chef configuration file. By default Packer only sets configuration
    +  it needs to match the settings set in the provisioner configuration. If
    +  you need to set configurations that the Packer provisioner doesn't support,
    +  then you should use a custom configuration template. See the dedicated
    +  "Chef Configuration" section below for more details.
    +
     * `cookbook_paths` (array of strings) - This is an array of paths to
       "cookbooks" directories on your local filesystem. These will be uploaded
       to the remote machine in the directory specified by the `staging_directory`.
    @@ -70,6 +77,25 @@ configuration is actually required, but at least `run_list` is recommended.
       this folder. If the permissions are not correct, use a shell provisioner
       prior to this to configure it properly.
     
    +## Chef Configuration
    +
    +By default, Packer uses a simple Chef configuration file in order to set
    +the options specified for the provisioner. But Chef is a complex tool that
    +supports many configuration options. Packer allows you to specify a custom
    +configuration template if you'd like to set custom configurations.
    +
    +The default value for the configuration template is:
    +
    +```
    +cookbook_path [{{.CookbookPaths}}]
    +```
    +
    +This template is a [configuration template](/docs/templates/configuration-templates.html)
    +and has a set of variables available to use:
    +
    +* `CookbookPaths` is the set of cookbook paths ready to embedded directly
    +  into a Ruby array to configure Chef.
    +
     ## Execute Command
     
     By default, Packer uses the following command (broken across multiple lines
    
    From 692278965f742564459f86f18385714d3558d308 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Sun, 15 Sep 2013 23:41:08 -0700
    Subject: [PATCH 074/173] builder/virtualbox: fix scan codes
    
    ---
     builder/virtualbox/step_type_boot_command.go | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/builder/virtualbox/step_type_boot_command.go b/builder/virtualbox/step_type_boot_command.go
    index 66c05dfe9..ec3df82a6 100644
    --- a/builder/virtualbox/step_type_boot_command.go
    +++ b/builder/virtualbox/step_type_boot_command.go
    @@ -99,7 +99,7 @@ func scancodes(message string) []string {
     	//
     	// 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 0x81.
    +	// derived from the first by the addition of 0x80.
     	special := make(map[string][]string)
     	special[""] = []string{"0e", "8e"}
     	special[""] = []string{"53", "d3"}
    
    From dcb1bb7624f15b24984cde558feeb0683a685318 Mon Sep 17 00:00:00 2001
    From: Matthew Hooker 
    Date: Tue, 17 Sep 2013 04:35:07 -0700
    Subject: [PATCH 075/173] updatedeps target.
    
    ---
     Makefile | 7 ++++++-
     1 file changed, 6 insertions(+), 1 deletion(-)
    
    diff --git a/Makefile b/Makefile
    index 776d27dca..934a12852 100644
    --- a/Makefile
    +++ b/Makefile
    @@ -2,6 +2,7 @@ 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}}' ./...)
     
     all: deps
     	@mkdir -p bin/
    @@ -11,7 +12,11 @@ all: deps
     deps:
     	@echo "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)"
     	@go get -d -v ./...
    -	@go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs -n1 go get -d
    +	@echo $(DEPS) | xargs -n1 go get -d
    +
    +updatedeps:
    +	@echo "$(OK_COLOR)==> Updating all dependencies$(NO_COLOR)"
    +	@echo $(DEPS) | xargs -n1 go get -d -u
     
     clean:
     	@rm -rf bin/ local/ pkg/ src/ website/.sass-cache website/build
    
    From 1befba24edfaf0635fd4c37c1423b4b0a5c9cdc3 Mon Sep 17 00:00:00 2001
    From: Matthew Hooker 
    Date: Tue, 17 Sep 2013 05:58:16 -0700
    Subject: [PATCH 076/173] Allow user to override chroot command.
    
    ---
     builder/amazon/chroot/builder.go               | 6 ++++++
     builder/amazon/chroot/communicator.go          | 7 ++-----
     builder/amazon/chroot/step_chroot_provision.go | 2 ++
     3 files changed, 10 insertions(+), 5 deletions(-)
    
    diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go
    index c940a7bfb..6647b769e 100644
    --- a/builder/amazon/chroot/builder.go
    +++ b/builder/amazon/chroot/builder.go
    @@ -30,6 +30,7 @@ type Config struct {
     	CopyFiles      []string   `mapstructure:"copy_files"`
     	DevicePath     string     `mapstructure:"device_path"`
     	MountCommand   string     `mapstructure:"mount_command"`
    +	ChrootCommand  string     `mapstructure:"chroot_command"`
     	MountPath      string     `mapstructure:"mount_path"`
     	SourceAmi      string     `mapstructure:"source_ami"`
     	UnmountCommand string     `mapstructure:"unmount_command"`
    @@ -82,6 +83,10 @@ func (b *Builder) Prepare(raws ...interface{}) error {
     		b.config.MountCommand = "mount"
     	}
     
    +	if b.config.ChrootCommand == "" {
    +		b.config.ChrootCommand = "chroot"
    +	}
    +
     	if b.config.MountPath == "" {
     		b.config.MountPath = "packer-amazon-chroot-volumes/{{.Device}}"
     	}
    @@ -129,6 +134,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
     	templates := map[string]*string{
     		"device_path":     &b.config.DevicePath,
     		"mount_command":   &b.config.MountCommand,
    +		"chroot_command":  &b.config.ChrootCommand,
     		"source_ami":      &b.config.SourceAmi,
     		"unmount_command": &b.config.UnmountCommand,
     	}
    diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go
    index 111adb42e..3e1e8deda 100644
    --- a/builder/amazon/chroot/communicator.go
    +++ b/builder/amazon/chroot/communicator.go
    @@ -14,15 +14,12 @@ import (
     // commands locally but within a chroot.
     type Communicator struct {
     	Chroot string
    +    ChrootCommand string
     }
     
     func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
    -	chrootCmdPath, err := exec.LookPath("chroot")
    -	if err != nil {
    -		return err
    -	}
     
    -	localCmd := exec.Command(chrootCmdPath, c.Chroot, "/bin/sh", "-c", cmd.Command)
    +	localCmd := exec.Command(c.ChrootCommand, c.Chroot, "/bin/sh", "-c", cmd.Command)
     	localCmd.Stdin = cmd.Stdin
     	localCmd.Stdout = cmd.Stdout
     	localCmd.Stderr = cmd.Stderr
    diff --git a/builder/amazon/chroot/step_chroot_provision.go b/builder/amazon/chroot/step_chroot_provision.go
    index 5ad87836d..b1a156216 100644
    --- a/builder/amazon/chroot/step_chroot_provision.go
    +++ b/builder/amazon/chroot/step_chroot_provision.go
    @@ -14,11 +14,13 @@ type StepChrootProvision struct {
     func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction {
     	hook := state.Get("hook").(packer.Hook)
     	mountPath := state.Get("mount_path").(string)
    +	chrootCommand := state.Get("chroot_command").(string)
     	ui := state.Get("ui").(packer.Ui)
     
     	// Create our communicator
     	comm := &Communicator{
     		Chroot: mountPath,
    +        ChrootCommand: chrootCommand,
     	}
     
     	// Provision
    
    From 4f6fc963971785d7a36d4f1ecb46a237ae2b7988 Mon Sep 17 00:00:00 2001
    From: Matthew Hooker 
    Date: Tue, 17 Sep 2013 06:01:23 -0700
    Subject: [PATCH 077/173] some documentation.
    
    ---
     website/source/docs/builders/amazon-chroot.html.markdown | 4 ++++
     1 file changed, 4 insertions(+)
    
    diff --git a/website/source/docs/builders/amazon-chroot.html.markdown b/website/source/docs/builders/amazon-chroot.html.markdown
    index 593ac97fe..5d0f23b6e 100644
    --- a/website/source/docs/builders/amazon-chroot.html.markdown
    +++ b/website/source/docs/builders/amazon-chroot.html.markdown
    @@ -126,6 +126,10 @@ Optional:
     * `unmount_command` (string) - Just like `mount_command`, except this is
       the command to unmount devices.
     
    +* `chroot_command` (string) - The command to use to create the chroot.
    +  This defaults to "chroot", but like `mount_command`, it may be useful
    +  to use `sudo` or variables.
    +
     ## Basic Example
     
     Here is a basic example. It is completely valid except for the access keys:
    
    From bd0eac81041086915fddb736b4d3b70bc168dc5b Mon Sep 17 00:00:00 2001
    From: Ali Abbas 
    Date: Tue, 17 Sep 2013 21:12:50 +0200
    Subject: [PATCH 078/173] Enforce go build version dependency
    
    ---
     go_version.go | 3 +++
     1 file changed, 3 insertions(+)
     create mode 100644 go_version.go
    
    diff --git a/go_version.go b/go_version.go
    new file mode 100644
    index 000000000..4a557c621
    --- /dev/null
    +++ b/go_version.go
    @@ -0,0 +1,3 @@
    +// +build !go1.1
    +
    +"packer requires go version 1.1 or greated to build"
    
    From c7e16811630d76bdd9f5e7ebaac84362bce6523c Mon Sep 17 00:00:00 2001
    From: Jesse Nelson 
    Date: Wed, 18 Sep 2013 13:42:15 -0700
    Subject: [PATCH 079/173] exclude everything in /tmp, but keep /tmp itself
    
    There is IMO a bug with image builder that it removes /tmp, in the
    current setup. This patch makes the image bundle ignore everyting in
    /tmp, but keeps /tmp on the box.
    ---
     builder/amazon/instance/builder.go | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go
    index 071e0e24e..44691ecee 100644
    --- a/builder/amazon/instance/builder.go
    +++ b/builder/amazon/instance/builder.go
    @@ -83,7 +83,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
     			"-u {{.AccountId}} " +
     			"-c {{.CertPath}} " +
     			"-r {{.Architecture}} " +
    -			"-e {{.PrivatePath}} " +
    +			"-e {{.PrivatePath}}/* " +
     			"-d {{.Destination}} " +
     			"-p {{.Prefix}} " +
     			"--batch"
    
    From b3559b90fd4ab5ff05c214dec3685a3fd978590f Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 13:50:01 -0700
    Subject: [PATCH 080/173] Update CHANGELOG
    
    ---
     CHANGELOG.md | 8 +++++---
     1 file changed, 5 insertions(+), 3 deletions(-)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index cf2ee3d54..dec40d02c 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -7,13 +7,15 @@ FEATURES:
     
     IMPROVEMENTS:
     
    -* builder/amazon/*: Interrupts work while waiting for AMI to be ready.
    +* builder/amazon/all: Interrupts work while waiting for AMI to be ready.
     
     BUG FIXES:
     
    -* builder/amazon/*: While waiting for AMI, will detect "failed" state.
    -* builder/amazon/*: Waiting for state will detect if the resource (AMI,
    +* builder/amazon/all: While waiting for AMI, will detect "failed" state.
    +* builder/amazon/all: Waiting for state will detect if the resource (AMI,
       instance, etc.) disappears from under it.
    +* builder/amazon/instance: Exclude only contents of /tmp, not /tmp
    +  itself. [GH-437]
     * builder/virtualbox: F1-F12 and delete scancodes now work. [GH-425]
     * provisioner/puppet-masterless: Fix failure case when both facter vars
       are used and prevent_sudo. [GH-415]
    
    From cf10fff7fa23b93e37ed3881ac4da9abea66744b Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 13:59:23 -0700
    Subject: [PATCH 081/173] builder/amazon/common: save access/secret key from
     env [GH-434]
    
    ---
     CHANGELOG.md                           | 2 ++
     builder/amazon/common/access_config.go | 9 ++++++++-
     2 files changed, 10 insertions(+), 1 deletion(-)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index dec40d02c..ee5aad398 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -16,6 +16,8 @@ BUG FIXES:
       instance, etc.) disappears from under it.
     * builder/amazon/instance: Exclude only contents of /tmp, not /tmp
       itself. [GH-437]
    +* builder/amazon/instance: Make AccessKey/SecretKey available to bundle
    +  command even when they come from the environment. [GH-434]
     * builder/virtualbox: F1-F12 and delete scancodes now work. [GH-425]
     * provisioner/puppet-masterless: Fix failure case when both facter vars
       are used and prevent_sudo. [GH-415]
    diff --git a/builder/amazon/common/access_config.go b/builder/amazon/common/access_config.go
    index 10ce2eba2..1690869d0 100644
    --- a/builder/amazon/common/access_config.go
    +++ b/builder/amazon/common/access_config.go
    @@ -18,7 +18,14 @@ type AccessConfig struct {
     // Auth returns a valid aws.Auth object for access to AWS services, or
     // an error if the authentication couldn't be resolved.
     func (c *AccessConfig) Auth() (aws.Auth, error) {
    -	return aws.GetAuth(c.AccessKey, c.SecretKey)
    +	auth, err := aws.GetAuth(c.AccessKey, c.SecretKey)
    +	if err == nil {
    +		// Store the accesskey and secret that we got...
    +		c.AccessKey = auth.AccessKey
    +		c.SecretKey = auth.SecretKey
    +	}
    +
    +	return auth, err
     }
     
     // Region returns the aws.Region object for access to AWS services, requesting
    
    From 76bb457e0fb811731073b55baacb4503e5f99b2a Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 14:13:04 -0700
    Subject: [PATCH 082/173] Revert "Enforce go build version dependency"
    
    This reverts commit 0a1d696457ffe859ef8581e249230f39c1723958.
    
    Unfortunately, this was causing `go fmt ./...` to fail.
    ---
     go_version.go | 3 ---
     1 file changed, 3 deletions(-)
     delete mode 100644 go_version.go
    
    diff --git a/go_version.go b/go_version.go
    deleted file mode 100644
    index 4a557c621..000000000
    --- a/go_version.go
    +++ /dev/null
    @@ -1,3 +0,0 @@
    -// +build !go1.1
    -
    -"packer requires go version 1.1 or greated to build"
    
    From b2d5b15155cb489fce874c635da8daf3e1935d25 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 14:14:18 -0700
    Subject: [PATCH 083/173] fmt
    
    ---
     provisioner/chef-solo/provisioner.go      | 24 +++++++++++------------
     provisioner/chef-solo/provisioner_test.go |  2 +-
     2 files changed, 13 insertions(+), 13 deletions(-)
    
    diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go
    index d06c3785f..1262ab6d2 100644
    --- a/provisioner/chef-solo/provisioner.go
    +++ b/provisioner/chef-solo/provisioner.go
    @@ -20,8 +20,8 @@ type Config struct {
     
     	ConfigTemplate      string   `mapstructure:"config_template"`
     	CookbookPaths       []string `mapstructure:"cookbook_paths"`
    -	RolesPath      		string   `mapstructure:"roles_path"`
    -	DataBagsPath     	string   `mapstructure:"data_bags_path"`
    +	RolesPath           string   `mapstructure:"roles_path"`
    +	DataBagsPath        string   `mapstructure:"data_bags_path"`
     	ExecuteCommand      string   `mapstructure:"execute_command"`
     	InstallCommand      string   `mapstructure:"install_command"`
     	RemoteCookbookPaths []string `mapstructure:"remote_cookbook_paths"`
    @@ -39,9 +39,9 @@ type Provisioner struct {
     }
     
     type ConfigTemplate struct {
    -	CookbookPaths 	string
    -	RolesPath 		string
    -	DataBagsPath 	string
    +	CookbookPaths string
    +	RolesPath     string
    +	DataBagsPath  string
     }
     
     type ExecuteTemplate struct {
    @@ -147,10 +147,10 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
     				errs, fmt.Errorf("Bad cookbook path '%s': %s", path, err))
     		}
     	}
    -	
    +
     	if p.config.RolesPath != "" {
     		pFileInfo, err := os.Stat(p.config.RolesPath)
    -		
    +
     		if err != nil || !pFileInfo.IsDir() {
     			errs = packer.MultiErrorAppend(
     				errs, fmt.Errorf("Bad roles path '%s': %s", p.config.RolesPath, err))
    @@ -159,7 +159,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
     
     	if p.config.DataBagsPath != "" {
     		pFileInfo, err := os.Stat(p.config.DataBagsPath)
    -		
    +
     		if err != nil || !pFileInfo.IsDir() {
     			errs = packer.MultiErrorAppend(
     				errs, fmt.Errorf("Bad data bags path '%s': %s", p.config.DataBagsPath, err))
    @@ -201,7 +201,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
     
     		cookbookPaths = append(cookbookPaths, targetPath)
     	}
    -	
    +
     	rolesPath := ""
     	if p.config.RolesPath != "" {
     		rolesPath := fmt.Sprintf("%s/roles", p.config.StagingDir)
    @@ -267,7 +267,7 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local
     		i = len(p.config.RemoteCookbookPaths) + i
     		cookbook_paths[i] = fmt.Sprintf(`"%s"`, path)
     	}
    -	
    +
     	roles_path := ""
     	if rolesPath != "" {
     		roles_path = fmt.Sprintf(`"%s"`, rolesPath)
    @@ -297,8 +297,8 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local
     
     	configString, err := p.config.tpl.Process(tpl, &ConfigTemplate{
     		CookbookPaths: strings.Join(cookbook_paths, ","),
    -		RolesPath: roles_path,
    -		DataBagsPath: data_bags_path,
    +		RolesPath:     roles_path,
    +		DataBagsPath:  data_bags_path,
     	})
     	if err != nil {
     		return "", err
    diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go
    index 10a342dad..9a84a2a96 100644
    --- a/provisioner/chef-solo/provisioner_test.go
    +++ b/provisioner/chef-solo/provisioner_test.go
    @@ -107,7 +107,7 @@ func TestProvisionerPrepare_cookbookPaths(t *testing.T) {
     	if p.config.CookbookPaths[0] != path1 || p.config.CookbookPaths[1] != path2 {
     		t.Fatalf("unexpected: %#v", p.config.CookbookPaths)
     	}
    -	
    +
     	if p.config.RolesPath != rolesPath {
     		t.Fatalf("unexpected: %#v", p.config.RolesPath)
     	}
    
    From c5b46fb4172be616aaa41efdea96823a2f9bdda7 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 14:14:40 -0700
    Subject: [PATCH 084/173] Update CHANGELOG
    
    ---
     CHANGELOG.md | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index ee5aad398..7ad2b1fae 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -4,6 +4,7 @@ FEATURES:
     
     * provisioner/chef-solo: Ability to specify a custom Chef configuration
       template.
    +* provisioner/chef-solo: Roles and data bags support. [GH-348]
     
     IMPROVEMENTS:
     
    
    From 7034f69f2c08f776fa0e556ebf6736c6675b2cef Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 14:17:07 -0700
    Subject: [PATCH 085/173] provisioner/chef-solo: move quoting to template
    
    /cc @jerryclinesmith - I want the quoting to go into the template. I
    realize the cookbooks path doesn't do this and you were following that.
    That one is just weird because it is an array and Go templates kind of
    suck.
    ---
     provisioner/chef-solo/provisioner.go | 20 +++++---------------
     1 file changed, 5 insertions(+), 15 deletions(-)
    
    diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go
    index 1262ab6d2..dd69d1410 100644
    --- a/provisioner/chef-solo/provisioner.go
    +++ b/provisioner/chef-solo/provisioner.go
    @@ -40,8 +40,8 @@ type Provisioner struct {
     
     type ConfigTemplate struct {
     	CookbookPaths string
    -	RolesPath     string
     	DataBagsPath  string
    +	RolesPath     string
     }
     
     type ExecuteTemplate struct {
    @@ -268,16 +268,6 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local
     		cookbook_paths[i] = fmt.Sprintf(`"%s"`, path)
     	}
     
    -	roles_path := ""
    -	if rolesPath != "" {
    -		roles_path = fmt.Sprintf(`"%s"`, rolesPath)
    -	}
    -
    -	data_bags_path := ""
    -	if dataBagsPath != "" {
    -		data_bags_path = fmt.Sprintf(`"%s"`, dataBagsPath)
    -	}
    -
     	// Read the template
     	tpl := DefaultConfigTemplate
     	if p.config.ConfigTemplate != "" {
    @@ -297,8 +287,8 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local
     
     	configString, err := p.config.tpl.Process(tpl, &ConfigTemplate{
     		CookbookPaths: strings.Join(cookbook_paths, ","),
    -		RolesPath:     roles_path,
    -		DataBagsPath:  data_bags_path,
    +		RolesPath:     rolesPath,
    +		DataBagsPath:  dataBagsPath,
     	})
     	if err != nil {
     		return "", err
    @@ -451,9 +441,9 @@ func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) {
     var DefaultConfigTemplate = `
     cookbook_path 	[{{.CookbookPaths}}]
     {{if .RolesPath != ""}}
    -role_path		{{.RolesPath}}
    +role_path		"{{.RolesPath}}"
     {{end}}
     {{if .DataBagsPath != ""}}
    -data_bag_path	{{.DataBagsPath}}
    +data_bag_path	"{{.DataBagsPath}}"
     {{end}}
     `
    
    From ba9d85c744ff3b439631c4c91aded64b05f80271 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 14:17:54 -0700
    Subject: [PATCH 086/173] provisioner/chef-solo: template process roles/data
     bags path
    
    /cc @jerryclinesmith
    ---
     provisioner/chef-solo/provisioner.go | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go
    index dd69d1410..0f4fe10ab 100644
    --- a/provisioner/chef-solo/provisioner.go
    +++ b/provisioner/chef-solo/provisioner.go
    @@ -87,6 +87,8 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
     
     	templates := map[string]*string{
     		"config_template": &p.config.ConfigTemplate,
    +		"data_bags_path":  &p.config.DataBagsPath,
    +		"roles_path":      &p.config.RolesPath,
     		"staging_dir":     &p.config.StagingDir,
     	}
     
    
    From 1b1b535ee52c9b78887cd7c9926ab55d914f9dcc Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 14:19:50 -0700
    Subject: [PATCH 087/173] provisioner/chef-solo: one test per test
    
    /cc @jerryclinesmith
    ---
     provisioner/chef-solo/provisioner_test.go | 44 +++++++++++++++++++++++
     1 file changed, 44 insertions(+)
    
    diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go
    index 9a84a2a96..c18df246a 100644
    --- a/provisioner/chef-solo/provisioner_test.go
    +++ b/provisioner/chef-solo/provisioner_test.go
    @@ -117,6 +117,50 @@ func TestProvisionerPrepare_cookbookPaths(t *testing.T) {
     	}
     }
     
    +func TestProvisionerPrepare_dataBagsPath(t *testing.T) {
    +	var p Provisioner
    +
    +	dataBagsPath, err := ioutil.TempDir("", "data_bags")
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +	defer os.Remove(dataBagsPath)
    +
    +	config := testConfig()
    +	config["data_bags_path"] = dataBagsPath
    +
    +	err = p.Prepare(config)
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	if p.config.DataBagsPath != dataBagsPath {
    +		t.Fatalf("unexpected: %#v", p.config.DataBagsPath)
    +	}
    +}
    +
    +func TestProvisionerPrepare_rolesPath(t *testing.T) {
    +	var p Provisioner
    +
    +	rolesPath, err := ioutil.TempDir("", "roles")
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +	defer os.Remove(rolesPath)
    +
    +	config := testConfig()
    +	config["roles_path"] = rolesPath
    +
    +	err = p.Prepare(config)
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	if p.config.RolesPath != rolesPath {
    +		t.Fatalf("unexpected: %#v", p.config.RolesPath)
    +	}
    +}
    +
     func TestProvisionerPrepare_json(t *testing.T) {
     	config := testConfig()
     	config["json"] = map[string]interface{}{
    
    From 2e9dbb72b01005b6c94c0cb3a3e48f04335d3e9a Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 14:43:42 -0700
    Subject: [PATCH 088/173] post-processor/vagrant: fix slice copy to actually
     work for override
    
    [GH-426]
    ---
     CHANGELOG.md                             | 1 +
     post-processor/vagrant/post-processor.go | 2 +-
     2 files changed, 2 insertions(+), 1 deletion(-)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index 7ad2b1fae..ddf709bba 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -20,6 +20,7 @@ BUG FIXES:
     * builder/amazon/instance: Make AccessKey/SecretKey available to bundle
       command even when they come from the environment. [GH-434]
     * builder/virtualbox: F1-F12 and delete scancodes now work. [GH-425]
    +* post-processor/vagrant: Override configurations properly work. [GH-426]
     * provisioner/puppet-masterless: Fix failure case when both facter vars
       are used and prevent_sudo. [GH-415]
     
    diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go
    index 13e39a763..ecfae80de 100644
    --- a/post-processor/vagrant/post-processor.go
    +++ b/post-processor/vagrant/post-processor.go
    @@ -77,7 +77,7 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
     		}
     
     		// Create the proper list of configurations
    -		ppConfigs := make([]interface{}, 0, len(p.rawConfigs)+1)
    +		ppConfigs := make([]interface{}, len(p.rawConfigs), len(p.rawConfigs)+1)
     		copy(ppConfigs, p.rawConfigs)
     		ppConfigs = append(ppConfigs, raw)
     
    
    From 6fc89e95841c6318afc712ce8461a10671c98b53 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 14:51:51 -0700
    Subject: [PATCH 089/173] post-processor/vagrant: set output path always in a
     new raw
    
    ---
     post-processor/vagrant/post-processor.go | 10 +++++++---
     1 file changed, 7 insertions(+), 3 deletions(-)
    
    diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go
    index ecfae80de..e6f629068 100644
    --- a/post-processor/vagrant/post-processor.go
    +++ b/post-processor/vagrant/post-processor.go
    @@ -45,10 +45,8 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
     	tpl.UserVars = p.config.PackerUserVars
     
     	// Defaults
    -	ppExtraConfig := make(map[string]interface{})
     	if p.config.OutputPath == "" {
     		p.config.OutputPath = "packer_{{ .BuildName }}_{{.Provider}}.box"
    -		ppExtraConfig["output"] = p.config.OutputPath
     	}
     
     	// Accumulate any errors
    @@ -58,10 +56,16 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
     			errs, fmt.Errorf("Error parsing output template: %s", err))
     	}
     
    +	// Store extra configuration we'll send to each post-processor type
    +	ppExtraConfig := make(map[string]interface{})
    +	ppExtraConfig["output"] = p.config.OutputPath
    +
     	// Store the extra configuration for post-processors
     	p.rawConfigs = append(p.rawConfigs, ppExtraConfig)
     
    -	// TODO(mitchellh): Properly handle multiple raw configs
    +	// TODO(mitchellh): Properly handle multiple raw configs. This isn't
    +	// very pressing at the moment because at the time of this comment
    +	// only the first member of raws can contain the actual type-overrides.
     	var mapConfig map[string]interface{}
     	if err := mapstructure.Decode(raws[0], &mapConfig); err != nil {
     		errs = packer.MultiErrorAppend(errs,
    
    From aa694072d75b9b46d0b2f906a249c1e9b16e6f7c Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 15:01:06 -0700
    Subject: [PATCH 090/173] post-processor/vagrant: simplify logic, only send
     overrides to PP
    
    [GH-413] /cc @jasonberanek
    ---
     post-processor/vagrant/post-processor.go | 57 +++++++++++++-----------
     1 file changed, 32 insertions(+), 25 deletions(-)
    
    diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go
    index e6f629068..952f574ad 100644
    --- a/post-processor/vagrant/post-processor.go
    +++ b/post-processor/vagrant/post-processor.go
    @@ -24,15 +24,12 @@ type Config struct {
     }
     
     type PostProcessor struct {
    -	config     Config
    -	premade    map[string]packer.PostProcessor
    -	rawConfigs []interface{}
    +	config      Config
    +	premade     map[string]packer.PostProcessor
    +	extraConfig map[string]interface{}
     }
     
     func (p *PostProcessor) Configure(raws ...interface{}) error {
    -	// Store the raw configs for usage later
    -	p.rawConfigs = raws
    -
     	_, err := common.DecodeConfig(&p.config, raws...)
     	if err != nil {
     		return err
    @@ -57,11 +54,8 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
     	}
     
     	// Store extra configuration we'll send to each post-processor type
    -	ppExtraConfig := make(map[string]interface{})
    -	ppExtraConfig["output"] = p.config.OutputPath
    -
    -	// Store the extra configuration for post-processors
    -	p.rawConfigs = append(p.rawConfigs, ppExtraConfig)
    +	p.extraConfig = make(map[string]interface{})
    +	p.extraConfig["output"] = p.config.OutputPath
     
     	// TODO(mitchellh): Properly handle multiple raw configs. This isn't
     	// very pressing at the moment because at the time of this comment
    @@ -75,18 +69,14 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
     
     	p.premade = make(map[string]packer.PostProcessor)
     	for k, raw := range mapConfig {
    -		pp := keyToPostProcessor(k)
    -		if pp == nil {
    +		pp, err := p.subPostProcessor(k, raw, p.extraConfig)
    +		if err != nil {
    +			errs = packer.MultiErrorAppend(errs, err)
     			continue
     		}
     
    -		// Create the proper list of configurations
    -		ppConfigs := make([]interface{}, len(p.rawConfigs), len(p.rawConfigs)+1)
    -		copy(ppConfigs, p.rawConfigs)
    -		ppConfigs = append(ppConfigs, raw)
    -
    -		if err := pp.Configure(ppConfigs...); err != nil {
    -			errs = packer.MultiErrorAppend(errs, err)
    +		if pp == nil {
    +			continue
     		}
     
     		p.premade[k] = pp
    @@ -110,13 +100,15 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
     	pp, ok := p.premade[ppName]
     	if !ok {
     		log.Printf("Premade post-processor for '%s' not found. Creating.", ppName)
    -		pp = keyToPostProcessor(ppName)
    -		if pp == nil {
    -			return nil, false, fmt.Errorf("Vagrant box post-processor not found: %s", ppName)
    +
    +		var err error
    +		pp, err = p.subPostProcessor(ppName, nil, p.extraConfig)
    +		if err != nil {
    +			return nil, false, err
     		}
     
    -		if err := pp.Configure(p.rawConfigs...); err != nil {
    -			return nil, false, err
    +		if pp == nil {
    +			return nil, false, fmt.Errorf("Vagrant box post-processor not found: %s", ppName)
     		}
     	}
     
    @@ -124,6 +116,21 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
     	return pp.PostProcess(ui, artifact)
     }
     
    +func (p *PostProcessor) subPostProcessor(key string, specific interface{}, extra map[string]interface{}) (packer.PostProcessor, error) {
    +	pp := keyToPostProcessor(key)
    +	if pp == nil {
    +		return nil, nil
    +	}
    +
    +	if err := pp.Configure(extra, specific); err != nil {
    +		return nil, err
    +	}
    +
    +	return pp, nil
    +}
    +
    +// keyToPostProcessor maps a configuration key to the actual post-processor
    +// it will be configuring. This returns a new instance of that post-processor.
     func keyToPostProcessor(key string) packer.PostProcessor {
     	switch key {
     	case "aws":
    
    From 877dfb81fe7124a28401252164365dfcab969390 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 16:18:39 -0700
    Subject: [PATCH 091/173] common: Allow user variables to be used for
     ints/bools/etc. [GH-418]
    
    ---
     CHANGELOG.md                                               | 2 ++
     common/config.go                                           | 5 +++--
     website/source/docs/templates/user-variables.html.markdown | 2 +-
     3 files changed, 6 insertions(+), 3 deletions(-)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index ddf709bba..1c167e1d4 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -8,6 +8,8 @@ FEATURES:
     
     IMPROVEMENTS:
     
    +* core: User variables can now be used for integer, boolean, etc.
    +  values. [GH-418]
     * builder/amazon/all: Interrupts work while waiting for AMI to be ready.
     
     BUG FIXES:
    diff --git a/common/config.go b/common/config.go
    index 2a2c8b21f..b579562d5 100644
    --- a/common/config.go
    +++ b/common/config.go
    @@ -41,8 +41,9 @@ func CheckUnusedConfig(md *mapstructure.Metadata) *packer.MultiError {
     func DecodeConfig(target interface{}, raws ...interface{}) (*mapstructure.Metadata, error) {
     	var md mapstructure.Metadata
     	decoderConfig := &mapstructure.DecoderConfig{
    -		Metadata: &md,
    -		Result:   target,
    +		Metadata:         &md,
    +		Result:           target,
    +		WeaklyTypedInput: true,
     	}
     
     	decoder, err := mapstructure.NewDecoder(decoderConfig)
    diff --git a/website/source/docs/templates/user-variables.html.markdown b/website/source/docs/templates/user-variables.html.markdown
    index 8f19aadea..edce239ad 100644
    --- a/website/source/docs/templates/user-variables.html.markdown
    +++ b/website/source/docs/templates/user-variables.html.markdown
    @@ -54,7 +54,7 @@ validation will fail.
     
     Using the variables is extremely easy. Variables are used by calling
     the user function in the form of {{user `variable`}}.
    -This function can be used in _any string_ within the template, in
    +This function can be used in _any value_ within the template, in
     builders, provisioners, _anything_. The user variable is available globally
     within the template.
     
    
    From f85c9e435479ae16972ecac02a647c07ab523834 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 17:15:48 -0700
    Subject: [PATCH 092/173] packer/rpc: set keep-alive on all RPC connections
     [GH-416]
    
    ---
     CHANGELOG.md                 |  2 ++
     packer/rpc/build.go          |  4 ++--
     packer/rpc/builder.go        |  7 +++----
     packer/rpc/command.go        |  2 +-
     packer/rpc/communicator.go   | 12 ++++++------
     packer/rpc/dial.go           | 33 +++++++++++++++++++++++++++++++++
     packer/rpc/environment.go    | 12 ++++++------
     packer/rpc/hook.go           |  2 +-
     packer/rpc/post_processor.go |  4 ++--
     packer/rpc/provisioner.go    |  2 +-
     10 files changed, 57 insertions(+), 23 deletions(-)
     create mode 100644 packer/rpc/dial.go
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index 1c167e1d4..8a85c2ce1 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -14,6 +14,8 @@ IMPROVEMENTS:
     
     BUG FIXES:
     
    +* core: Set TCP KeepAlives on internally created RPC connections so that
    +  they don't die. [GH-416]
     * builder/amazon/all: While waiting for AMI, will detect "failed" state.
     * builder/amazon/all: Waiting for state will detect if the resource (AMI,
       instance, etc.) disappears from under it.
    diff --git a/packer/rpc/build.go b/packer/rpc/build.go
    index 6d81ce870..8f21b4ce9 100644
    --- a/packer/rpc/build.go
    +++ b/packer/rpc/build.go
    @@ -52,7 +52,7 @@ func (b *build) Run(ui packer.Ui, cache packer.Cache) ([]packer.Artifact, error)
     
     	artifacts := make([]packer.Artifact, len(result))
     	for i, addr := range result {
    -		client, err := rpc.Dial("tcp", addr)
    +		client, err := rpcDial(addr)
     		if err != nil {
     			return nil, err
     		}
    @@ -92,7 +92,7 @@ func (b *BuildServer) Prepare(v map[string]string, reply *error) error {
     }
     
     func (b *BuildServer) Run(args *BuildRunArgs, reply *[]string) error {
    -	client, err := rpc.Dial("tcp", args.UiRPCAddress)
    +	client, err := rpcDial(args.UiRPCAddress)
     	if err != nil {
     		return err
     	}
    diff --git a/packer/rpc/builder.go b/packer/rpc/builder.go
    index 8c56e4959..5e2c20d77 100644
    --- a/packer/rpc/builder.go
    +++ b/packer/rpc/builder.go
    @@ -5,7 +5,6 @@ import (
     	"fmt"
     	"github.com/mitchellh/packer/packer"
     	"log"
    -	"net"
     	"net/rpc"
     )
     
    @@ -95,7 +94,7 @@ func (b *builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
     		return nil, nil
     	}
     
    -	client, err := rpc.Dial("tcp", response.RPCAddress)
    +	client, err := rpcDial(response.RPCAddress)
     	if err != nil {
     		return nil, err
     	}
    @@ -119,12 +118,12 @@ func (b *BuilderServer) Prepare(args *BuilderPrepareArgs, reply *error) error {
     }
     
     func (b *BuilderServer) Run(args *BuilderRunArgs, reply *interface{}) error {
    -	client, err := rpc.Dial("tcp", args.RPCAddress)
    +	client, err := rpcDial(args.RPCAddress)
     	if err != nil {
     		return err
     	}
     
    -	responseC, err := net.Dial("tcp", args.ResponseAddress)
    +	responseC, err := tcpDial(args.ResponseAddress)
     	if err != nil {
     		return err
     	}
    diff --git a/packer/rpc/command.go b/packer/rpc/command.go
    index 18cd5667e..3e2b48b2f 100644
    --- a/packer/rpc/command.go
    +++ b/packer/rpc/command.go
    @@ -66,7 +66,7 @@ func (c *CommandServer) Help(args *interface{}, reply *string) error {
     }
     
     func (c *CommandServer) Run(args *CommandRunArgs, reply *int) error {
    -	client, err := rpc.Dial("tcp", args.RPCAddress)
    +	client, err := rpcDial(args.RPCAddress)
     	if err != nil {
     		return err
     	}
    diff --git a/packer/rpc/communicator.go b/packer/rpc/communicator.go
    index 21b507f0b..77e321153 100644
    --- a/packer/rpc/communicator.go
    +++ b/packer/rpc/communicator.go
    @@ -177,7 +177,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
     
     	toClose := make([]net.Conn, 0)
     	if args.StdinAddress != "" {
    -		stdinC, err := net.Dial("tcp", args.StdinAddress)
    +		stdinC, err := tcpDial(args.StdinAddress)
     		if err != nil {
     			return err
     		}
    @@ -187,7 +187,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
     	}
     
     	if args.StdoutAddress != "" {
    -		stdoutC, err := net.Dial("tcp", args.StdoutAddress)
    +		stdoutC, err := tcpDial(args.StdoutAddress)
     		if err != nil {
     			return err
     		}
    @@ -197,7 +197,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
     	}
     
     	if args.StderrAddress != "" {
    -		stderrC, err := net.Dial("tcp", args.StderrAddress)
    +		stderrC, err := tcpDial(args.StderrAddress)
     		if err != nil {
     			return err
     		}
    @@ -208,7 +208,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
     
     	// Connect to the response address so we can write our result to it
     	// when ready.
    -	responseC, err := net.Dial("tcp", args.ResponseAddress)
    +	responseC, err := tcpDial(args.ResponseAddress)
     	if err != nil {
     		return err
     	}
    @@ -234,7 +234,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
     }
     
     func (c *CommunicatorServer) Upload(args *CommunicatorUploadArgs, reply *interface{}) (err error) {
    -	readerC, err := net.Dial("tcp", args.ReaderAddress)
    +	readerC, err := tcpDial(args.ReaderAddress)
     	if err != nil {
     		return
     	}
    @@ -250,7 +250,7 @@ func (c *CommunicatorServer) UploadDir(args *CommunicatorUploadDirArgs, reply *e
     }
     
     func (c *CommunicatorServer) Download(args *CommunicatorDownloadArgs, reply *interface{}) (err error) {
    -	writerC, err := net.Dial("tcp", args.WriterAddress)
    +	writerC, err := tcpDial(args.WriterAddress)
     	if err != nil {
     		return
     	}
    diff --git a/packer/rpc/dial.go b/packer/rpc/dial.go
    new file mode 100644
    index 000000000..10e2cad14
    --- /dev/null
    +++ b/packer/rpc/dial.go
    @@ -0,0 +1,33 @@
    +package rpc
    +
    +import (
    +	"net"
    +	"net/rpc"
    +)
    +
    +// rpcDial makes a TCP connection to a remote RPC server and returns
    +// the client. This will set the connection up properly so that keep-alives
    +// are set and so on and should be used to make all RPC connections within
    +// this package.
    +func rpcDial(address string) (*rpc.Client, error) {
    +	tcpConn, err := tcpDial(address)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	// Create an RPC client around our connection
    +	return rpc.NewClient(tcpConn), nil
    +}
    +
    +// tcpDial connects via TCP to the designated address.
    +func tcpDial(address string) (*net.TCPConn, error) {
    +	conn, err := net.Dial("tcp", address)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	// Set a keep-alive so that the connection stays alive even when idle
    +	tcpConn := conn.(*net.TCPConn)
    +	tcpConn.SetKeepAlive(true)
    +	return tcpConn, nil
    +}
    diff --git a/packer/rpc/environment.go b/packer/rpc/environment.go
    index 8ebf709c0..36db72c56 100644
    --- a/packer/rpc/environment.go
    +++ b/packer/rpc/environment.go
    @@ -28,7 +28,7 @@ func (e *Environment) Builder(name string) (b packer.Builder, err error) {
     		return
     	}
     
    -	client, err := rpc.Dial("tcp", reply)
    +	client, err := rpcDial(reply)
     	if err != nil {
     		return
     	}
    @@ -43,7 +43,7 @@ func (e *Environment) Cache() packer.Cache {
     		panic(err)
     	}
     
    -	client, err := rpc.Dial("tcp", reply)
    +	client, err := rpcDial(reply)
     	if err != nil {
     		panic(err)
     	}
    @@ -64,7 +64,7 @@ func (e *Environment) Hook(name string) (h packer.Hook, err error) {
     		return
     	}
     
    -	client, err := rpc.Dial("tcp", reply)
    +	client, err := rpcDial(reply)
     	if err != nil {
     		return
     	}
    @@ -80,7 +80,7 @@ func (e *Environment) PostProcessor(name string) (p packer.PostProcessor, err er
     		return
     	}
     
    -	client, err := rpc.Dial("tcp", reply)
    +	client, err := rpcDial(reply)
     	if err != nil {
     		return
     	}
    @@ -96,7 +96,7 @@ func (e *Environment) Provisioner(name string) (p packer.Provisioner, err error)
     		return
     	}
     
    -	client, err := rpc.Dial("tcp", reply)
    +	client, err := rpcDial(reply)
     	if err != nil {
     		return
     	}
    @@ -109,7 +109,7 @@ func (e *Environment) Ui() packer.Ui {
     	var reply string
     	e.client.Call("Environment.Ui", new(interface{}), &reply)
     
    -	client, err := rpc.Dial("tcp", reply)
    +	client, err := rpcDial(reply)
     	if err != nil {
     		panic(err)
     	}
    diff --git a/packer/rpc/hook.go b/packer/rpc/hook.go
    index 687d991a7..223b96df2 100644
    --- a/packer/rpc/hook.go
    +++ b/packer/rpc/hook.go
    @@ -46,7 +46,7 @@ func (h *hook) Cancel() {
     }
     
     func (h *HookServer) Run(args *HookRunArgs, reply *interface{}) error {
    -	client, err := rpc.Dial("tcp", args.RPCAddress)
    +	client, err := rpcDial(args.RPCAddress)
     	if err != nil {
     		return err
     	}
    diff --git a/packer/rpc/post_processor.go b/packer/rpc/post_processor.go
    index fb43cb7d9..0a5eaefd3 100644
    --- a/packer/rpc/post_processor.go
    +++ b/packer/rpc/post_processor.go
    @@ -57,7 +57,7 @@ func (p *postProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Art
     		return nil, false, nil
     	}
     
    -	client, err := rpc.Dial("tcp", response.RPCAddress)
    +	client, err := rpcDial(response.RPCAddress)
     	if err != nil {
     		return nil, false, err
     	}
    @@ -75,7 +75,7 @@ func (p *PostProcessorServer) Configure(args *PostProcessorConfigureArgs, reply
     }
     
     func (p *PostProcessorServer) PostProcess(address string, reply *PostProcessorProcessResponse) error {
    -	client, err := rpc.Dial("tcp", address)
    +	client, err := rpcDial(address)
     	if err != nil {
     		return err
     	}
    diff --git a/packer/rpc/provisioner.go b/packer/rpc/provisioner.go
    index 7d3ed1617..4cd329d6b 100644
    --- a/packer/rpc/provisioner.go
    +++ b/packer/rpc/provisioner.go
    @@ -65,7 +65,7 @@ func (p *ProvisionerServer) Prepare(args *ProvisionerPrepareArgs, reply *error)
     }
     
     func (p *ProvisionerServer) Provision(args *ProvisionerProvisionArgs, reply *interface{}) error {
    -	client, err := rpc.Dial("tcp", args.RPCAddress)
    +	client, err := rpcDial(args.RPCAddress)
     	if err != nil {
     		return err
     	}
    
    From 865f19750324f0909ad94a21ec4c2fe3b05b3e2a Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Wed, 18 Sep 2013 18:15:46 -0700
    Subject: [PATCH 093/173] builder/virtualbox: support attaching guest additions
     [GH-405]
    
    ---
     CHANGELOG.md                                  |  2 +
     builder/virtualbox/builder.go                 |  2 +
     .../virtualbox/step_attach_guest_additions.go | 81 +++++++++++++++++++
     .../virtualbox/step_upload_guest_additions.go |  7 ++
     .../docs/builders/virtualbox.html.markdown    |  4 +
     5 files changed, 96 insertions(+)
     create mode 100644 builder/virtualbox/step_attach_guest_additions.go
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index 8a85c2ce1..c6a02f949 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -2,6 +2,8 @@
     
     FEATURES:
     
    +* builders/virtualbox: Guest additions can be attached rather than uploaded,
    +  easier to handle for Windows guests. [GH-405]
     * provisioner/chef-solo: Ability to specify a custom Chef configuration
       template.
     * provisioner/chef-solo: Roles and data bags support. [GH-348]
    diff --git a/builder/virtualbox/builder.go b/builder/virtualbox/builder.go
    index 18b16867a..452a7de49 100644
    --- a/builder/virtualbox/builder.go
    +++ b/builder/virtualbox/builder.go
    @@ -28,6 +28,7 @@ type config struct {
     	DiskSize             uint       `mapstructure:"disk_size"`
     	FloppyFiles          []string   `mapstructure:"floppy_files"`
     	Format               string     `mapstructure:"format"`
    +	GuestAdditionsAttach bool       `mapstructure:"guest_additions_attach"`
     	GuestAdditionsPath   string     `mapstructure:"guest_additions_path"`
     	GuestAdditionsURL    string     `mapstructure:"guest_additions_url"`
     	GuestAdditionsSHA256 string     `mapstructure:"guest_additions_sha256"`
    @@ -361,6 +362,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
     		new(stepCreateVM),
     		new(stepCreateDisk),
     		new(stepAttachISO),
    +		new(stepAttachGuestAdditions),
     		new(stepAttachFloppy),
     		new(stepForwardSSH),
     		new(stepVBoxManage),
    diff --git a/builder/virtualbox/step_attach_guest_additions.go b/builder/virtualbox/step_attach_guest_additions.go
    new file mode 100644
    index 000000000..9bbeb949a
    --- /dev/null
    +++ b/builder/virtualbox/step_attach_guest_additions.go
    @@ -0,0 +1,81 @@
    +package virtualbox
    +
    +import (
    +	"fmt"
    +	"github.com/mitchellh/multistep"
    +	"github.com/mitchellh/packer/packer"
    +	"log"
    +)
    +
    +// This step attaches the VirtualBox guest additions as a inserted CD onto
    +// the virtual machine.
    +//
    +// Uses:
    +//   config *config
    +//   driver Driver
    +//   guest_additions_path string
    +//   ui packer.Ui
    +//   vmName string
    +//
    +// Produces:
    +type stepAttachGuestAdditions struct {
    +	attachedPath string
    +}
    +
    +func (s *stepAttachGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
    +	config := state.Get("config").(*config)
    +	driver := state.Get("driver").(Driver)
    +	guestAdditionsPath := state.Get("guest_additions_path").(string)
    +	ui := state.Get("ui").(packer.Ui)
    +	vmName := state.Get("vmName").(string)
    +
    +	// If we're not attaching the guest additions then just return
    +	if !config.GuestAdditionsAttach {
    +		log.Println("Not attaching guest additions since we're uploading.")
    +		return multistep.ActionContinue
    +	}
    +
    +	// Attach the guest additions to the computer
    +	log.Println("Attaching guest additions ISO onto IDE controller...")
    +	command := []string{
    +		"storageattach", vmName,
    +		"--storagectl", "IDE Controller",
    +		"--port", "1",
    +		"--device", "0",
    +		"--type", "dvddrive",
    +		"--medium", guestAdditionsPath,
    +	}
    +	if err := driver.VBoxManage(command...); err != nil {
    +		err := fmt.Errorf("Error attaching guest additions: %s", err)
    +		state.Put("error", err)
    +		ui.Error(err.Error())
    +		return multistep.ActionHalt
    +	}
    +
    +	// Track the path so that we can unregister it from VirtualBox later
    +	s.attachedPath = guestAdditionsPath
    +
    +	return multistep.ActionContinue
    +}
    +
    +func (s *stepAttachGuestAdditions) Cleanup(state multistep.StateBag) {
    +	if s.attachedPath == "" {
    +		return
    +	}
    +
    +	driver := state.Get("driver").(Driver)
    +	ui := state.Get("ui").(packer.Ui)
    +	vmName := state.Get("vmName").(string)
    +
    +	command := []string{
    +		"storageattach", vmName,
    +		"--storagectl", "IDE Controller",
    +		"--port", "1",
    +		"--device", "0",
    +		"--medium", "none",
    +	}
    +
    +	if err := driver.VBoxManage(command...); err != nil {
    +		ui.Error(fmt.Sprintf("Error unregistering guest additions: %s", err))
    +	}
    +}
    diff --git a/builder/virtualbox/step_upload_guest_additions.go b/builder/virtualbox/step_upload_guest_additions.go
    index 86b83b3c6..ff2252c86 100644
    --- a/builder/virtualbox/step_upload_guest_additions.go
    +++ b/builder/virtualbox/step_upload_guest_additions.go
    @@ -4,6 +4,7 @@ import (
     	"fmt"
     	"github.com/mitchellh/multistep"
     	"github.com/mitchellh/packer/packer"
    +	"log"
     	"os"
     )
     
    @@ -21,6 +22,12 @@ func (s *stepUploadGuestAdditions) Run(state multistep.StateBag) multistep.StepA
     	guestAdditionsPath := state.Get("guest_additions_path").(string)
     	ui := state.Get("ui").(packer.Ui)
     
    +	// If we're attaching then don't do this, since we attached.
    +	if config.GuestAdditionsAttach {
    +		log.Println("Not uploading guest additions since we're attaching.")
    +		return multistep.ActionContinue
    +	}
    +
     	version, err := driver.Version()
     	if err != nil {
     		state.Put("error", fmt.Errorf("Error reading version for guest additions upload: %s", err))
    diff --git a/website/source/docs/builders/virtualbox.html.markdown b/website/source/docs/builders/virtualbox.html.markdown
    index 75e6b1943..b10cd8cb0 100644
    --- a/website/source/docs/builders/virtualbox.html.markdown
    +++ b/website/source/docs/builders/virtualbox.html.markdown
    @@ -85,6 +85,10 @@ Optional:
     * `format` (string) - Either "ovf" or "ova", this specifies the output
       format of the exported virtual machine. This defaults to "ovf".
     
    +* `guest_additions_attach` (bool) - If this is true (defaults to "false"),
    +  the guest additions ISO will be attached to the virtual machine as a CD
    +  rather than uploaded as a raw ISO.
    +
     * `guest_additions_path` (string) - The path on the guest virtual machine
       where the VirtualBox guest additions ISO will be uploaded. By default this
       is "VBoxGuestAdditions.iso" which should upload into the login directory
    
    From 229d790ebae55c2eed2152ca60d50399c56970d8 Mon Sep 17 00:00:00 2001
    From: "Jason A. Beranek" 
    Date: Wed, 18 Sep 2013 22:56:00 -0500
    Subject: [PATCH 094/173] post-processor/vagrant: pass PackerConfig to sub
     post-processors
    
    ---
     post-processor/vagrant/post-processor.go | 5 +++++
     1 file changed, 5 insertions(+)
    
    diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go
    index 952f574ad..91969e558 100644
    --- a/post-processor/vagrant/post-processor.go
    +++ b/post-processor/vagrant/post-processor.go
    @@ -56,6 +56,11 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
     	// Store extra configuration we'll send to each post-processor type
     	p.extraConfig = make(map[string]interface{})
     	p.extraConfig["output"] = p.config.OutputPath
    +	p.extraConfig["packer_build_name"] = p.config.PackerBuildName
    +	p.extraConfig["packer_builder_type"] = p.config.PackerBuilderType
    +	p.extraConfig["packer_debug"] = p.config.PackerDebug
    +	p.extraConfig["packer_force"] = p.config.PackerForce
    +	p.extraConfig["packer_user_variables"] = p.config.PackerUserVars
     
     	// TODO(mitchellh): Properly handle multiple raw configs. This isn't
     	// very pressing at the moment because at the time of this comment
    
    From 677f2989b9d12af3654d8e9afbef8ea7ca617331 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Thu, 19 Sep 2013 11:54:24 -0700
    Subject: [PATCH 095/173] provisioner/chef-solo: template doesn't support comp
     [GH-442]
    
    ---
     provisioner/chef-solo/provisioner.go | 18 +++++++++++++-----
     1 file changed, 13 insertions(+), 5 deletions(-)
    
    diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go
    index 0f4fe10ab..ddfc605fb 100644
    --- a/provisioner/chef-solo/provisioner.go
    +++ b/provisioner/chef-solo/provisioner.go
    @@ -42,6 +42,12 @@ type ConfigTemplate struct {
     	CookbookPaths string
     	DataBagsPath  string
     	RolesPath     string
    +
    +	// Templates don't support boolean statements until Go 1.2. In the
    +	// mean time, we do this.
    +	// TODO(mitchellh): Remove when Go 1.2 is released
    +	HasDataBagsPath bool
    +	HasRolesPath    bool
     }
     
     type ExecuteTemplate struct {
    @@ -288,9 +294,11 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local
     	}
     
     	configString, err := p.config.tpl.Process(tpl, &ConfigTemplate{
    -		CookbookPaths: strings.Join(cookbook_paths, ","),
    -		RolesPath:     rolesPath,
    -		DataBagsPath:  dataBagsPath,
    +		CookbookPaths:   strings.Join(cookbook_paths, ","),
    +		RolesPath:       rolesPath,
    +		DataBagsPath:    dataBagsPath,
    +		HasRolesPath:    rolesPath != "",
    +		HasDataBagsPath: dataBagsPath != "",
     	})
     	if err != nil {
     		return "", err
    @@ -442,10 +450,10 @@ func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) {
     
     var DefaultConfigTemplate = `
     cookbook_path 	[{{.CookbookPaths}}]
    -{{if .RolesPath != ""}}
    +{{if .HasRolesPath}}
     role_path		"{{.RolesPath}}"
     {{end}}
    -{{if .DataBagsPath != ""}}
    +{{if .HasDataBagsPath}}
     data_bag_path	"{{.DataBagsPath}}"
     {{end}}
     `
    
    From 71358222f05918644c1606732a371709654b5a44 Mon Sep 17 00:00:00 2001
    From: "Jason A. Beranek" 
    Date: Thu, 19 Sep 2013 23:30:22 -0500
    Subject: [PATCH 096/173] communicator/ssh, builder/digitalocean: fix new SSH
     API from upstream
    
    ---
     builder/digitalocean/step_create_ssh_key.go | 3 ++-
     communicator/ssh/keychain.go                | 4 ++--
     2 files changed, 4 insertions(+), 3 deletions(-)
    
    diff --git a/builder/digitalocean/step_create_ssh_key.go b/builder/digitalocean/step_create_ssh_key.go
    index 6a9f0426c..04699ec66 100644
    --- a/builder/digitalocean/step_create_ssh_key.go
    +++ b/builder/digitalocean/step_create_ssh_key.go
    @@ -38,7 +38,8 @@ func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
     	state.Put("privateKey", string(pem.EncodeToMemory(&priv_blk)))
     
     	// Marshal the public key into SSH compatible format
    -	pub := ssh.NewRSAPublicKey(&priv.PublicKey)
    +	// TODO properly handle the public key error
    +	pub, _ := ssh.NewPublicKey(&priv.PublicKey)
     	pub_sshformat := string(ssh.MarshalAuthorizedKey(pub))
     
     	// The name of the public key on DO
    diff --git a/communicator/ssh/keychain.go b/communicator/ssh/keychain.go
    index d7934f8a8..f9965c0da 100644
    --- a/communicator/ssh/keychain.go
    +++ b/communicator/ssh/keychain.go
    @@ -60,9 +60,9 @@ func (k *SimpleKeychain) Key(i int) (ssh.PublicKey, error) {
     	}
     	switch key := k.keys[i].(type) {
     	case *rsa.PrivateKey:
    -		return ssh.NewRSAPublicKey(&key.PublicKey), nil
    +		return ssh.NewPublicKey(&key.PublicKey)
     	case *dsa.PrivateKey:
    -		return ssh.NewDSAPublicKey(&key.PublicKey), nil
    +		return ssh.NewPublicKey(&key.PublicKey)
     	}
     	panic("unknown key type")
     }
    
    From 9c519eda4197c0e9fd2dec038bac790f499d6412 Mon Sep 17 00:00:00 2001
    From: Tim Mower 
    Date: Fri, 20 Sep 2013 11:36:37 +0100
    Subject: [PATCH 097/173] Fix syntax error in puppet template
    
    ---
     .../source/docs/provisioners/puppet-masterless.html.markdown    | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/website/source/docs/provisioners/puppet-masterless.html.markdown b/website/source/docs/provisioners/puppet-masterless.html.markdown
    index 11dbc6a85..792718570 100644
    --- a/website/source/docs/provisioners/puppet-masterless.html.markdown
    +++ b/website/source/docs/provisioners/puppet-masterless.html.markdown
    @@ -79,7 +79,7 @@ By default, Packer uses the following command (broken across multiple lines
     for readability) to execute Puppet:
     
     ```
    -{{.FacterVars}}{{if .Sudo} sudo -E {{end}}puppet apply \
    +{{.FacterVars}}{{if .Sudo}} sudo -E {{end}}puppet apply \
       --verbose \
       --modulepath='{{.ModulePath}}' \
       {{if .HasHieraConfigPath}}--hiera_config='{{.HieraConfigPath}}' {{end}} \
    
    From abbac36796eebbad94f36358003bae82b86e4357 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Fri, 20 Sep 2013 10:21:59 -0700
    Subject: [PATCH 098/173] provisioner/shell: convert windows line endings to
     Unix [GH-277]
    
    ---
     CHANGELOG.md                                  |  3 +
     provisioner/shell/provisioner.go              | 10 +++
     provisioner/shell/unix_reader.go              | 88 +++++++++++++++++++
     provisioner/shell/unix_reader_test.go         | 33 +++++++
     .../docs/provisioners/shell.html.markdown     |  4 +
     5 files changed, 138 insertions(+)
     create mode 100644 provisioner/shell/unix_reader.go
     create mode 100644 provisioner/shell/unix_reader_test.go
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index c6a02f949..0c5cb3d60 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -13,6 +13,9 @@ IMPROVEMENTS:
     * core: User variables can now be used for integer, boolean, etc.
       values. [GH-418]
     * builder/amazon/all: Interrupts work while waiting for AMI to be ready.
    +* provisioner/shell: Script line-endings are automatically converted to
    +  Unix-style line-endings. Can be disabled by setting "binary" to "true".
    +  [GH-277]
     
     BUG FIXES:
     
    diff --git a/provisioner/shell/provisioner.go b/provisioner/shell/provisioner.go
    index e8a5f61a5..3cb75187d 100644
    --- a/provisioner/shell/provisioner.go
    +++ b/provisioner/shell/provisioner.go
    @@ -8,6 +8,7 @@ import (
     	"fmt"
     	"github.com/mitchellh/packer/common"
     	"github.com/mitchellh/packer/packer"
    +	"io"
     	"io/ioutil"
     	"log"
     	"os"
    @@ -20,6 +21,10 @@ const DefaultRemotePath = "/tmp/script.sh"
     type config struct {
     	common.PackerConfig `mapstructure:",squash"`
     
    +	// If true, the script contains binary and line endings will not be
    +	// converted from Windows to Unix-style.
    +	Binary bool
    +
     	// An inline script to execute. Multiple strings are all executed
     	// in the context of a single shell.
     	Inline []string
    @@ -259,6 +264,11 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
     				return err
     			}
     
    +			var r io.Reader = f
    +			if !p.config.Binary {
    +				r = &UnixReader{Reader: r}
    +			}
    +
     			if err := comm.Upload(p.config.RemotePath, f); err != nil {
     				return fmt.Errorf("Error uploading script: %s", err)
     			}
    diff --git a/provisioner/shell/unix_reader.go b/provisioner/shell/unix_reader.go
    new file mode 100644
    index 000000000..5745dd291
    --- /dev/null
    +++ b/provisioner/shell/unix_reader.go
    @@ -0,0 +1,88 @@
    +package shell
    +
    +import (
    +	"bufio"
    +	"bytes"
    +	"io"
    +	"sync"
    +)
    +
    +// UnixReader is a Reader implementation that automatically converts
    +// Windows line endings to Unix line endings.
    +type UnixReader struct {
    +	Reader io.Reader
    +
    +	buf     []byte
    +	once    sync.Once
    +	scanner *bufio.Scanner
    +}
    +
    +func (r *UnixReader) Read(p []byte) (n int, err error) {
    +	// Create the buffered reader once
    +	r.once.Do(func() {
    +		r.scanner = bufio.NewScanner(r.Reader)
    +		r.scanner.Split(scanUnixLine)
    +	})
    +
    +	// If we have no data in our buffer, scan to the next token
    +	if len(r.buf) == 0 {
    +		if !r.scanner.Scan() {
    +			err = r.scanner.Err()
    +			if err == nil {
    +				err = io.EOF
    +			}
    +
    +			return 0, err
    +		}
    +
    +		r.buf = r.scanner.Bytes()
    +	}
    +
    +	// Write out as much data as we can to the buffer, storing the rest
    +	// for the next read.
    +	n = len(p)
    +	if n > len(r.buf) {
    +		n = len(r.buf)
    +	}
    +	copy(p, r.buf)
    +	r.buf = r.buf[n:]
    +
    +	return
    +}
    +
    +// scanUnixLine is a bufio.Scanner SplitFunc. It tokenizes on lines, but
    +// only returns unix-style lines. So even if the line is "one\r\n", the
    +// token returned will be "one\n".
    +func scanUnixLine(data []byte, atEOF bool) (advance int, token []byte, err error) {
    +	if atEOF && len(data) == 0 {
    +		return 0, nil, nil
    +	}
    +
    +	if i := bytes.IndexByte(data, '\n'); i >= 0 {
    +		// We have a new-line terminated line. Return the line with the newline
    +		return i + 1, dropCR(data[0 : i+1]), nil
    +	}
    +
    +	if atEOF {
    +		// We have a final, non-terminated line
    +		return len(data), dropCR(data), nil
    +	}
    +
    +	if data[len(data)-1] != '\r' {
    +		// We have a normal line, just let it tokenize
    +		return len(data), data, nil
    +	}
    +
    +	// We need more data
    +	return 0, nil, nil
    +}
    +
    +func dropCR(data []byte) []byte {
    +	if len(data) > 0 && data[len(data)-2] == '\r' {
    +		// Trim off the last byte and replace it with a '\n'
    +		data = data[0 : len(data)-1]
    +		data[len(data)-1] = '\n'
    +	}
    +
    +	return data
    +}
    diff --git a/provisioner/shell/unix_reader_test.go b/provisioner/shell/unix_reader_test.go
    new file mode 100644
    index 000000000..8dedf6300
    --- /dev/null
    +++ b/provisioner/shell/unix_reader_test.go
    @@ -0,0 +1,33 @@
    +package shell
    +
    +import (
    +	"bytes"
    +	"io"
    +	"testing"
    +)
    +
    +func TestUnixReader_impl(t *testing.T) {
    +	var raw interface{}
    +	raw = new(UnixReader)
    +	if _, ok := raw.(io.Reader); !ok {
    +		t.Fatal("should be reader")
    +	}
    +}
    +
    +func TestUnixReader(t *testing.T) {
    +	input := "one\r\ntwo\nthree\r\n"
    +	expected := "one\ntwo\nthree\n"
    +
    +	r := &UnixReader{
    +		Reader: bytes.NewReader([]byte(input)),
    +	}
    +
    +	result := new(bytes.Buffer)
    +	if _, err := io.Copy(result, r); err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	if result.String() != expected {
    +		t.Fatalf("bad: %#v", result.String())
    +	}
    +}
    diff --git a/website/source/docs/provisioners/shell.html.markdown b/website/source/docs/provisioners/shell.html.markdown
    index d3a895b3a..4c5a19992 100644
    --- a/website/source/docs/provisioners/shell.html.markdown
    +++ b/website/source/docs/provisioners/shell.html.markdown
    @@ -47,6 +47,10 @@ Exactly _one_ of the following is required:
     
     Optional parameters:
     
    +* `binary` (boolean) - If true, specifies that the script(s) are binary
    +   files, and Packer should therefore not convert Windows line endings to
    +   Unix line endings (if there are any). By default this is false.
    +
     * `environment_vars` (array of strings) - An array of key/value pairs
       to inject prior to the execute_command. The format should be
       `key=value`. Packer injects some environmental variables by default
    
    From 118f4fdccea36f61738fb0f288d729d6064d3b35 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Fri, 20 Sep 2013 10:49:35 -0700
    Subject: [PATCH 099/173] packer: `only` metaparameter for provisioners
     [GH-438]
    
    ---
     packer/template.go      |  32 +++++++++++--
     packer/template_test.go | 100 ++++++++++++++++++++++++++++++++++++++++
     2 files changed, 129 insertions(+), 3 deletions(-)
    
    diff --git a/packer/template.go b/packer/template.go
    index 7c3dd0529..12d47e5a8 100644
    --- a/packer/template.go
    +++ b/packer/template.go
    @@ -59,6 +59,7 @@ type RawPostProcessorConfig struct {
     type RawProvisionerConfig struct {
     	Type     string
     	Override map[string]interface{}
    +	Only     []string
     
     	RawConfig interface{}
     }
    @@ -237,9 +238,8 @@ func ParseTemplate(data []byte) (t *Template, err error) {
     			continue
     		}
     
    -		// The provisioners not only don't need or want the override settings
    -		// (as they are processed as part of the preparation below), but will
    -		// actively reject them as invalid configuration.
    +		// Delete the keys that we used
    +		delete(v, "only")
     		delete(v, "override")
     
     		// Verify that the override keys exist...
    @@ -250,6 +250,17 @@ func ParseTemplate(data []byte) (t *Template, err error) {
     			}
     		}
     
    +		// Verify that the only settings are good
    +		if len(raw.Only) > 0 {
    +			for _, n := range raw.Only {
    +				if _, ok := t.Builders[n]; !ok {
    +					errors = append(errors,
    +						fmt.Errorf("provisioner %d: 'only' specified builder '%s' not found",
    +							i+1, n))
    +				}
    +			}
    +		}
    +
     		raw.RawConfig = v
     	}
     
    @@ -425,6 +436,21 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
     	// Prepare the provisioners
     	provisioners := make([]coreBuildProvisioner, 0, len(t.Provisioners))
     	for _, rawProvisioner := range t.Provisioners {
    +		if len(rawProvisioner.Only) > 0 {
    +			onlyFound := false
    +			for _, n := range rawProvisioner.Only {
    +				if n == name {
    +					onlyFound = true
    +					break
    +				}
    +			}
    +
    +			if !onlyFound {
    +				// Skip this provisioner
    +				continue
    +			}
    +		}
    +
     		var provisioner Provisioner
     		provisioner, err = components.Provisioner(rawProvisioner.Type)
     		if err != nil {
    diff --git a/packer/template_test.go b/packer/template_test.go
    index 883933367..c8fb6b4ae 100644
    --- a/packer/template_test.go
    +++ b/packer/template_test.go
    @@ -9,6 +9,26 @@ import (
     	"testing"
     )
     
    +func testTemplateComponentFinder() *ComponentFinder {
    +	builder := testBuilder()
    +	provisioner := &MockProvisioner{}
    +
    +	builderMap := map[string]Builder{
    +		"test-builder": builder,
    +	}
    +
    +	provisionerMap := map[string]Provisioner{
    +		"test-prov": provisioner,
    +	}
    +
    +	builderFactory := func(n string) (Builder, error) { return builderMap[n], nil }
    +	provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil }
    +	return &ComponentFinder{
    +		Builder:     builderFactory,
    +		Provisioner: provFactory,
    +	}
    +}
    +
     func TestParseTemplateFile_basic(t *testing.T) {
     	data := `
     	{
    @@ -663,6 +683,86 @@ func TestTemplate_Build(t *testing.T) {
     	}
     }
     
    +func TestTemplateBuild_onlyProvInvalid(t *testing.T) {
    +	data := `
    +	{
    +		"builders": [
    +			{
    +				"name": "test1",
    +				"type": "test-builder"
    +			},
    +			{
    +				"name": "test2",
    +				"type": "test-builder"
    +			}
    +		],
    +
    +		"provisioners": [
    +			{
    +				"type": "test-prov",
    +				"only": "test5"
    +			}
    +		]
    +	}
    +	`
    +
    +	_, err := ParseTemplate([]byte(data))
    +	if err == nil {
    +		t.Fatal("should have error")
    +	}
    +}
    +
    +func TestTemplateBuild_onlyProv(t *testing.T) {
    +	data := `
    +	{
    +		"builders": [
    +			{
    +				"name": "test1",
    +				"type": "test-builder"
    +			},
    +			{
    +				"name": "test2",
    +				"type": "test-builder"
    +			}
    +		],
    +
    +		"provisioners": [
    +			{
    +				"type": "test-prov",
    +				"only": ["test2"]
    +			}
    +		]
    +	}
    +	`
    +
    +	template, err := ParseTemplate([]byte(data))
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	// Verify test1 has no provisioners
    +	build, err := template.Build("test1", testTemplateComponentFinder())
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	cbuild := build.(*coreBuild)
    +	if len(cbuild.provisioners) > 0 {
    +		t.Fatal("should have no provisioners")
    +	}
    +
    +	// Verify test2 has no provisioners
    +	build, err = template.Build("test2", testTemplateComponentFinder())
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	cbuild = build.(*coreBuild)
    +	if len(cbuild.provisioners) != 1 {
    +		t.Fatalf("invalid: %d", len(cbuild.provisioners))
    +	}
    +}
    +
     func TestTemplate_Build_ProvisionerOverride(t *testing.T) {
     	assert := asserts.NewTestingAsserts(t, true)
     
    
    From 45cd21a076bc887db10e52c519112e1fd457bfc2 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Fri, 20 Sep 2013 11:13:43 -0700
    Subject: [PATCH 100/173] packer: `only` metaparameter for post-processors
     [GH-438]
    
    ---
     packer/template.go      | 129 +++++++++++++++++++++++++++++-----------
     packer/template_test.go |  91 +++++++++++++++++++++++++++-
     2 files changed, 184 insertions(+), 36 deletions(-)
    
    diff --git a/packer/template.go b/packer/template.go
    index 12d47e5a8..7e27bf93d 100644
    --- a/packer/template.go
    +++ b/packer/template.go
    @@ -48,6 +48,8 @@ type RawBuilderConfig struct {
     // configuration. It contains the type of the post processor as well as the
     // raw configuration that is handed to the post-processor for it to process.
     type RawPostProcessorConfig struct {
    +	TemplateOnlyExcept `mapstructure:",squash"`
    +
     	Type              string
     	KeepInputArtifact bool `mapstructure:"keep_input_artifact"`
     	RawConfig         map[string]interface{}
    @@ -57,9 +59,10 @@ type RawPostProcessorConfig struct {
     // It contains the type of the provisioner as well as the raw configuration
     // that is handed to the provisioner for it to process.
     type RawProvisionerConfig struct {
    +	TemplateOnlyExcept `mapstructure:",squash"`
    +
     	Type     string
     	Override map[string]interface{}
    -	Only     []string
     
     	RawConfig interface{}
     }
    @@ -190,32 +193,50 @@ func ParseTemplate(data []byte) (t *Template, err error) {
     			continue
     		}
     
    -		t.PostProcessors[i] = make([]RawPostProcessorConfig, len(rawPP))
    -		configs := t.PostProcessors[i]
    +		configs := make([]RawPostProcessorConfig, 0, len(rawPP))
     		for j, pp := range rawPP {
    -			config := &configs[j]
    -			if err := mapstructure.Decode(pp, config); err != nil {
    +			var config RawPostProcessorConfig
    +			if err := mapstructure.Decode(pp, &config); err != nil {
     				if merr, ok := err.(*mapstructure.Error); ok {
     					for _, err := range merr.Errors {
    -						errors = append(errors, fmt.Errorf("Post-processor #%d.%d: %s", i+1, j+1, err))
    +						errors = append(errors,
    +							fmt.Errorf("Post-processor #%d.%d: %s", i+1, j+1, err))
     					}
     				} else {
    -					errors = append(errors, fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err))
    +					errors = append(errors,
    +						fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err))
     				}
     
     				continue
     			}
     
     			if config.Type == "" {
    -				errors = append(errors, fmt.Errorf("Post-processor %d.%d: missing 'type'", i+1, j+1))
    +				errors = append(errors,
    +					fmt.Errorf("Post-processor %d.%d: missing 'type'", i+1, j+1))
     				continue
     			}
     
     			// Remove the input keep_input_artifact option
    +			config.TemplateOnlyExcept.Prune(pp)
     			delete(pp, "keep_input_artifact")
     
    +			// Verify that the only settings are good
    +			if errs := config.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 {
    +				for _, err := range errs {
    +					errors = append(errors,
    +						fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err))
    +				}
    +
    +				continue
    +			}
    +
     			config.RawConfig = pp
    +
    +			// Add it to the list of configs
    +			configs = append(configs, config)
     		}
    +
    +		t.PostProcessors[i] = configs
     	}
     
     	// Gather all the provisioners
    @@ -239,7 +260,7 @@ func ParseTemplate(data []byte) (t *Template, err error) {
     		}
     
     		// Delete the keys that we used
    -		delete(v, "only")
    +		raw.TemplateOnlyExcept.Prune(v)
     		delete(v, "override")
     
     		// Verify that the override keys exist...
    @@ -251,14 +272,8 @@ func ParseTemplate(data []byte) (t *Template, err error) {
     		}
     
     		// Verify that the only settings are good
    -		if len(raw.Only) > 0 {
    -			for _, n := range raw.Only {
    -				if _, ok := t.Builders[n]; !ok {
    -					errors = append(errors,
    -						fmt.Errorf("provisioner %d: 'only' specified builder '%s' not found",
    -							i+1, n))
    -				}
    -			}
    +		if errs := raw.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 {
    +			errors = append(errors, errs...)
     		}
     
     		raw.RawConfig = v
    @@ -411,8 +426,12 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
     	// Prepare the post-processors
     	postProcessors := make([][]coreBuildPostProcessor, 0, len(t.PostProcessors))
     	for _, rawPPs := range t.PostProcessors {
    -		current := make([]coreBuildPostProcessor, len(rawPPs))
    -		for i, rawPP := range rawPPs {
    +		current := make([]coreBuildPostProcessor, 0, len(rawPPs))
    +		for _, rawPP := range rawPPs {
    +			if rawPP.TemplateOnlyExcept.Skip(name) {
    +				continue
    +			}
    +
     			pp, err := components.PostProcessor(rawPP.Type)
     			if err != nil {
     				return nil, err
    @@ -422,12 +441,18 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
     				return nil, fmt.Errorf("PostProcessor type not found: %s", rawPP.Type)
     			}
     
    -			current[i] = coreBuildPostProcessor{
    +			current = append(current, coreBuildPostProcessor{
     				processor:         pp,
     				processorType:     rawPP.Type,
     				config:            rawPP.RawConfig,
     				keepInputArtifact: rawPP.KeepInputArtifact,
    -			}
    +			})
    +		}
    +
    +		// If we have no post-processors in this chain, just continue.
    +		// This can happen if the post-processors skip certain builds.
    +		if len(current) == 0 {
    +			continue
     		}
     
     		postProcessors = append(postProcessors, current)
    @@ -436,19 +461,8 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
     	// Prepare the provisioners
     	provisioners := make([]coreBuildProvisioner, 0, len(t.Provisioners))
     	for _, rawProvisioner := range t.Provisioners {
    -		if len(rawProvisioner.Only) > 0 {
    -			onlyFound := false
    -			for _, n := range rawProvisioner.Only {
    -				if n == name {
    -					onlyFound = true
    -					break
    -				}
    -			}
    -
    -			if !onlyFound {
    -				// Skip this provisioner
    -				continue
    -			}
    +		if rawProvisioner.TemplateOnlyExcept.Skip(name) {
    +			continue
     		}
     
     		var provisioner Provisioner
    @@ -497,3 +511,50 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
     
     	return
     }
    +
    +// TemplateOnlyExcept contains the logic required for "only" and "except"
    +// meta-parameters.
    +type TemplateOnlyExcept struct {
    +	Only   []string
    +	Except []string
    +}
    +
    +// Prune will prune out the used values from the raw map.
    +func (t *TemplateOnlyExcept) Prune(raw map[string]interface{}) {
    +	delete(raw, "except")
    +	delete(raw, "only")
    +}
    +
    +// Skip tests if we should skip putting this item onto a build.
    +func (t *TemplateOnlyExcept) Skip(name string) bool {
    +	if len(t.Only) > 0 {
    +		onlyFound := false
    +		for _, n := range t.Only {
    +			if n == name {
    +				onlyFound = true
    +				break
    +			}
    +		}
    +
    +		if !onlyFound {
    +			// Skip this provisioner
    +			return true
    +		}
    +	}
    +
    +	return false
    +}
    +
    +// Validates the only/except parameters.
    +func (t *TemplateOnlyExcept) Validate(b map[string]RawBuilderConfig) (e []error) {
    +	if len(t.Only) > 0 {
    +		for _, n := range t.Only {
    +			if _, ok := b[n]; !ok {
    +				e = append(e,
    +					fmt.Errorf("'only' specified builder '%s' not found", n))
    +			}
    +		}
    +	}
    +
    +	return
    +}
    diff --git a/packer/template_test.go b/packer/template_test.go
    index c8fb6b4ae..c5c0cf146 100644
    --- a/packer/template_test.go
    +++ b/packer/template_test.go
    @@ -11,21 +11,28 @@ import (
     
     func testTemplateComponentFinder() *ComponentFinder {
     	builder := testBuilder()
    +	pp := new(TestPostProcessor)
     	provisioner := &MockProvisioner{}
     
     	builderMap := map[string]Builder{
     		"test-builder": builder,
     	}
     
    +	ppMap := map[string]PostProcessor{
    +		"test-pp": pp,
    +	}
    +
     	provisionerMap := map[string]Provisioner{
     		"test-prov": provisioner,
     	}
     
     	builderFactory := func(n string) (Builder, error) { return builderMap[n], nil }
    +	ppFactory := func(n string) (PostProcessor, error) { return ppMap[n], nil }
     	provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil }
     	return &ComponentFinder{
    -		Builder:     builderFactory,
    -		Provisioner: provFactory,
    +		Builder:       builderFactory,
    +		PostProcessor: ppFactory,
    +		Provisioner:   provFactory,
     	}
     }
     
    @@ -683,6 +690,86 @@ func TestTemplate_Build(t *testing.T) {
     	}
     }
     
    +func TestTemplateBuild_onlyPPInvalid(t *testing.T) {
    +	data := `
    +	{
    +		"builders": [
    +			{
    +				"name": "test1",
    +				"type": "test-builder"
    +			},
    +			{
    +				"name": "test2",
    +				"type": "test-builder"
    +			}
    +		],
    +
    +		"post-processors": [
    +			{
    +				"type": "test-pp",
    +				"only": "test5"
    +			}
    +		]
    +	}
    +	`
    +
    +	_, err := ParseTemplate([]byte(data))
    +	if err == nil {
    +		t.Fatal("should have error")
    +	}
    +}
    +
    +func TestTemplateBuild_onlyPP(t *testing.T) {
    +	data := `
    +	{
    +		"builders": [
    +			{
    +				"name": "test1",
    +				"type": "test-builder"
    +			},
    +			{
    +				"name": "test2",
    +				"type": "test-builder"
    +			}
    +		],
    +
    +		"post-processors": [
    +			{
    +				"type": "test-pp",
    +				"only": ["test2"]
    +			}
    +		]
    +	}
    +	`
    +
    +	template, err := ParseTemplate([]byte(data))
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	// Verify test1 has no post-processors
    +	build, err := template.Build("test1", testTemplateComponentFinder())
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	cbuild := build.(*coreBuild)
    +	if len(cbuild.postProcessors) > 0 {
    +		t.Fatal("should have no postProcessors")
    +	}
    +
    +	// Verify test2 has no post-processors
    +	build, err = template.Build("test2", testTemplateComponentFinder())
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	cbuild = build.(*coreBuild)
    +	if len(cbuild.postProcessors) != 1 {
    +		t.Fatalf("invalid: %d", len(cbuild.postProcessors))
    +	}
    +}
    +
     func TestTemplateBuild_onlyProvInvalid(t *testing.T) {
     	data := `
     	{
    
    From a31a4207dfc190adfcda207755ee6a61ca02a543 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Fri, 20 Sep 2013 11:16:33 -0700
    Subject: [PATCH 101/173] packer: `except` meta-parameter for both prov and PP
     [GH-438]
    
    ---
     packer/template.go      |   7 ++
     packer/template_test.go | 160 ++++++++++++++++++++++++++++++++++++++++
     2 files changed, 167 insertions(+)
    
    diff --git a/packer/template.go b/packer/template.go
    index 7e27bf93d..32513d33f 100644
    --- a/packer/template.go
    +++ b/packer/template.go
    @@ -542,6 +542,13 @@ func (t *TemplateOnlyExcept) Skip(name string) bool {
     		}
     	}
     
    +	// If the name is in the except list, then skip that
    +	for _, n := range t.Except {
    +		if n == name {
    +			return true
    +		}
    +	}
    +
     	return false
     }
     
    diff --git a/packer/template_test.go b/packer/template_test.go
    index c5c0cf146..3b14db021 100644
    --- a/packer/template_test.go
    +++ b/packer/template_test.go
    @@ -690,6 +690,166 @@ func TestTemplate_Build(t *testing.T) {
     	}
     }
     
    +func TestTemplateBuild_exeptPPInvalid(t *testing.T) {
    +	data := `
    +	{
    +		"builders": [
    +			{
    +				"name": "test1",
    +				"type": "test-builder"
    +			},
    +			{
    +				"name": "test2",
    +				"type": "test-builder"
    +			}
    +		],
    +
    +		"post-processors": [
    +			{
    +				"type": "test-pp",
    +				"except": "test5"
    +			}
    +		]
    +	}
    +	`
    +
    +	_, err := ParseTemplate([]byte(data))
    +	if err == nil {
    +		t.Fatal("should have error")
    +	}
    +}
    +
    +func TestTemplateBuild_exceptPP(t *testing.T) {
    +	data := `
    +	{
    +		"builders": [
    +			{
    +				"name": "test1",
    +				"type": "test-builder"
    +			},
    +			{
    +				"name": "test2",
    +				"type": "test-builder"
    +			}
    +		],
    +
    +		"post-processors": [
    +			{
    +				"type": "test-pp",
    +				"except": ["test1"]
    +			}
    +		]
    +	}
    +	`
    +
    +	template, err := ParseTemplate([]byte(data))
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	// Verify test1 has no post-processors
    +	build, err := template.Build("test1", testTemplateComponentFinder())
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	cbuild := build.(*coreBuild)
    +	if len(cbuild.postProcessors) > 0 {
    +		t.Fatal("should have no postProcessors")
    +	}
    +
    +	// Verify test2 has no post-processors
    +	build, err = template.Build("test2", testTemplateComponentFinder())
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	cbuild = build.(*coreBuild)
    +	if len(cbuild.postProcessors) != 1 {
    +		t.Fatalf("invalid: %d", len(cbuild.postProcessors))
    +	}
    +}
    +
    +func TestTemplateBuild_exceptProvInvalid(t *testing.T) {
    +	data := `
    +	{
    +		"builders": [
    +			{
    +				"name": "test1",
    +				"type": "test-builder"
    +			},
    +			{
    +				"name": "test2",
    +				"type": "test-builder"
    +			}
    +		],
    +
    +		"provisioners": [
    +			{
    +				"type": "test-prov",
    +				"except": "test5"
    +			}
    +		]
    +	}
    +	`
    +
    +	_, err := ParseTemplate([]byte(data))
    +	if err == nil {
    +		t.Fatal("should have error")
    +	}
    +}
    +
    +func TestTemplateBuild_exceptProv(t *testing.T) {
    +	data := `
    +	{
    +		"builders": [
    +			{
    +				"name": "test1",
    +				"type": "test-builder"
    +			},
    +			{
    +				"name": "test2",
    +				"type": "test-builder"
    +			}
    +		],
    +
    +		"provisioners": [
    +			{
    +				"type": "test-prov",
    +				"except": ["test1"]
    +			}
    +		]
    +	}
    +	`
    +
    +	template, err := ParseTemplate([]byte(data))
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	// Verify test1 has no provisioners
    +	build, err := template.Build("test1", testTemplateComponentFinder())
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	cbuild := build.(*coreBuild)
    +	if len(cbuild.provisioners) > 0 {
    +		t.Fatal("should have no provisioners")
    +	}
    +
    +	// Verify test2 has no provisioners
    +	build, err = template.Build("test2", testTemplateComponentFinder())
    +	if err != nil {
    +		t.Fatalf("err: %s", err)
    +	}
    +
    +	cbuild = build.(*coreBuild)
    +	if len(cbuild.provisioners) != 1 {
    +		t.Fatalf("invalid: %d", len(cbuild.provisioners))
    +	}
    +}
    +
     func TestTemplateBuild_onlyPPInvalid(t *testing.T) {
     	data := `
     	{
    
    From 12ad2cf92ea90955021afea9fe74c5fa3aaed262 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Fri, 20 Sep 2013 11:18:00 -0700
    Subject: [PATCH 102/173] packer: verify `except` has valid builders [GH-438]
    
    ---
     packer/template.go      | 7 +++++++
     packer/template_test.go | 8 ++++----
     2 files changed, 11 insertions(+), 4 deletions(-)
    
    diff --git a/packer/template.go b/packer/template.go
    index 32513d33f..cada49fd3 100644
    --- a/packer/template.go
    +++ b/packer/template.go
    @@ -563,5 +563,12 @@ func (t *TemplateOnlyExcept) Validate(b map[string]RawBuilderConfig) (e []error)
     		}
     	}
     
    +	for _, n := range t.Except {
    +		if _, ok := b[n]; !ok {
    +			e = append(e,
    +				fmt.Errorf("'except' specified builder '%s' not found", n))
    +		}
    +	}
    +
     	return
     }
    diff --git a/packer/template_test.go b/packer/template_test.go
    index 3b14db021..62b1f54fa 100644
    --- a/packer/template_test.go
    +++ b/packer/template_test.go
    @@ -707,7 +707,7 @@ func TestTemplateBuild_exeptPPInvalid(t *testing.T) {
     		"post-processors": [
     			{
     				"type": "test-pp",
    -				"except": "test5"
    +				"except": ["test5"]
     			}
     		]
     	}
    @@ -787,7 +787,7 @@ func TestTemplateBuild_exceptProvInvalid(t *testing.T) {
     		"provisioners": [
     			{
     				"type": "test-prov",
    -				"except": "test5"
    +				"except": ["test5"]
     			}
     		]
     	}
    @@ -867,7 +867,7 @@ func TestTemplateBuild_onlyPPInvalid(t *testing.T) {
     		"post-processors": [
     			{
     				"type": "test-pp",
    -				"only": "test5"
    +				"only": ["test5"]
     			}
     		]
     	}
    @@ -947,7 +947,7 @@ func TestTemplateBuild_onlyProvInvalid(t *testing.T) {
     		"provisioners": [
     			{
     				"type": "test-prov",
    -				"only": "test5"
    +				"only": ["test5"]
     			}
     		]
     	}
    
    From 5371f66599cda4600992dac3c4da969fb143b53d Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Fri, 20 Sep 2013 11:20:05 -0700
    Subject: [PATCH 103/173] packer: verify only one of 'only' or 'except'
     specified [GH-438]
    
    ---
     packer/template.go      |  5 ++++
     packer/template_test.go | 62 ++++++++++++++++++++++++++++++++++++++++-
     2 files changed, 66 insertions(+), 1 deletion(-)
    
    diff --git a/packer/template.go b/packer/template.go
    index cada49fd3..b398b54cc 100644
    --- a/packer/template.go
    +++ b/packer/template.go
    @@ -554,6 +554,11 @@ func (t *TemplateOnlyExcept) Skip(name string) bool {
     
     // Validates the only/except parameters.
     func (t *TemplateOnlyExcept) Validate(b map[string]RawBuilderConfig) (e []error) {
    +	if len(t.Only) > 0 && len(t.Except) > 0 {
    +		e = append(e,
    +			fmt.Errorf("Only one of 'only' or 'except' may be specified."))
    +	}
    +
     	if len(t.Only) > 0 {
     		for _, n := range t.Only {
     			if _, ok := b[n]; !ok {
    diff --git a/packer/template_test.go b/packer/template_test.go
    index 62b1f54fa..aa531ad26 100644
    --- a/packer/template_test.go
    +++ b/packer/template_test.go
    @@ -690,7 +690,67 @@ func TestTemplate_Build(t *testing.T) {
     	}
     }
     
    -func TestTemplateBuild_exeptPPInvalid(t *testing.T) {
    +func TestTemplateBuild_exceptOnlyPP(t *testing.T) {
    +	data := `
    +	{
    +		"builders": [
    +			{
    +				"name": "test1",
    +				"type": "test-builder"
    +			},
    +			{
    +				"name": "test2",
    +				"type": "test-builder"
    +			}
    +		],
    +
    +		"post-processors": [
    +			{
    +				"type": "test-pp",
    +				"except": ["test1"],
    +				"only": ["test1"]
    +			}
    +		]
    +	}
    +	`
    +
    +	_, err := ParseTemplate([]byte(data))
    +	if err == nil {
    +		t.Fatal("should have error")
    +	}
    +}
    +
    +func TestTemplateBuild_exceptOnlyProv(t *testing.T) {
    +	data := `
    +	{
    +		"builders": [
    +			{
    +				"name": "test1",
    +				"type": "test-builder"
    +			},
    +			{
    +				"name": "test2",
    +				"type": "test-builder"
    +			}
    +		],
    +
    +		"provisioners": [
    +			{
    +				"type": "test-prov",
    +				"except": ["test1"],
    +				"only": ["test1"]
    +			}
    +		]
    +	}
    +	`
    +
    +	_, err := ParseTemplate([]byte(data))
    +	if err == nil {
    +		t.Fatal("should have error")
    +	}
    +}
    +
    +func TestTemplateBuild_exceptPPInvalid(t *testing.T) {
     	data := `
     	{
     		"builders": [
    
    From 15b48bb71e42cbeaa2ac4af7784c29ca14a27f29 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Fri, 20 Sep 2013 11:21:03 -0700
    Subject: [PATCH 104/173] Update CHANGELOG
    
    ---
     CHANGELOG.md | 3 +++
     1 file changed, 3 insertions(+)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index 0c5cb3d60..1835745ec 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -2,6 +2,9 @@
     
     FEATURES:
     
    +* core: You can now specify `only` and `except` configurations on any
    +  provisioner or post-processor to specify a list of builds that they
    +  are valid for. [GH-438]
     * builders/virtualbox: Guest additions can be attached rather than uploaded,
       easier to handle for Windows guests. [GH-405]
     * provisioner/chef-solo: Ability to specify a custom Chef configuration
    
    From 5c02bd3d26dfed96d8afe746d0af2adbdc593f44 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Fri, 20 Sep 2013 11:26:56 -0700
    Subject: [PATCH 105/173] packer: better error message for bad provisioner
     only/except
    
    ---
     packer/template.go | 5 ++++-
     1 file changed, 4 insertions(+), 1 deletion(-)
    
    diff --git a/packer/template.go b/packer/template.go
    index b398b54cc..547348d91 100644
    --- a/packer/template.go
    +++ b/packer/template.go
    @@ -273,7 +273,10 @@ func ParseTemplate(data []byte) (t *Template, err error) {
     
     		// Verify that the only settings are good
     		if errs := raw.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 {
    -			errors = append(errors, errs...)
    +			for _, err := range errs {
    +				errors = append(errors,
    +					fmt.Errorf("provisioner %d: %s", i+1, err))
    +			}
     		}
     
     		raw.RawConfig = v
    
    From 44d1f3d9ccf176f8f15deb2e23a4a11b3a0bdf04 Mon Sep 17 00:00:00 2001
    From: Mitchell Hashimoto 
    Date: Fri, 20 Sep 2013 11:42:25 -0700
    Subject: [PATCH 106/173] website: document `only` and `except`
    
    ---
     website/Gemfile                               |   8 +-
     website/Gemfile.lock                          | 159 ++++++++----------
     .../templates/post-processors.html.markdown   |  25 +++
     .../docs/templates/provisioners.html.markdown |  24 +++
     4 files changed, 120 insertions(+), 96 deletions(-)
    
    diff --git a/website/Gemfile b/website/Gemfile
    index d270a5ae4..2b3a472d5 100644
    --- a/website/Gemfile
    +++ b/website/Gemfile
    @@ -2,11 +2,11 @@ source 'https://rubygems.org'
     
     ruby '1.9.3'
     
    -gem "middleman", "~> 3.0.6"
    -gem "middleman-minify-html", "~> 3.0.0"
    +gem "middleman", "~> 3.1.5"
    +gem "middleman-minify-html", "~> 3.1.1"
     gem "rack-contrib", "~> 1.1.0"
    -gem "redcarpet", "~> 2.2.2"
    -gem "therubyracer", "~> 0.10.2"
    +gem "redcarpet", "~> 3.0.0"
    +gem "therubyracer", "~> 0.12.0"
     gem "thin", "~> 1.5.0"
     
     group :development do
    diff --git a/website/Gemfile.lock b/website/Gemfile.lock
    index d79220dde..a25bfa939 100644
    --- a/website/Gemfile.lock
    +++ b/website/Gemfile.lock
    @@ -1,134 +1,109 @@
     GEM
       remote: https://rubygems.org/
       specs:
    -    POpen4 (0.1.4)
    -      Platform (>= 0.4.0)
    -      open4
    -    Platform (0.4.0)
    -    activesupport (3.2.9)
    -      i18n (~> 0.6)
    +    activesupport (3.2.14)
    +      i18n (~> 0.6, >= 0.6.4)
           multi_json (~> 1.0)
    -    chunky_png (1.2.6)
    +    chunky_png (1.2.8)
         coffee-script (2.2.0)
           coffee-script-source
           execjs
    -    coffee-script-source (1.3.3)
    +    coffee-script-source (1.6.3)
         compass (0.12.2)
           chunky_png (~> 1.2)
           fssm (>= 0.2.7)
           sass (~> 3.1)
         daemons (1.1.9)
    -    eventmachine (1.0.0)
    +    eventmachine (1.0.3)
         execjs (1.4.0)
           multi_json (~> 1.0)
    -    ffi (1.2.0)
    -    fssm (0.2.9)
    -    haml (3.1.7)
    -    highline (1.6.15)
    -    hike (1.2.1)
    -    htmlcompressor (0.0.3)
    -      yui-compressor (~> 0.9.6)
    -    http_router (0.10.2)
    -      rack (>= 1.0.0)
    -      url_mount (~> 0.2.1)
    -    i18n (0.6.1)
    -    libv8 (3.3.10.4)
    -    listen (0.5.3)
    -    maruku (0.6.1)
    -      syntax (>= 1.0.0)
    -    middleman (3.0.6)
    -      middleman-core (= 3.0.6)
    -      middleman-more (= 3.0.6)
    -      middleman-sprockets (~> 3.0.2)
    -    middleman-core (3.0.6)
    -      activesupport (~> 3.2.6)
    -      bundler (~> 1.1)
    -      listen (~> 0.5.2)
    -      rack (~> 1.4.1)
    -      rack-test (~> 0.6.1)
    -      rb-fsevent (~> 0.9.1)
    -      rb-inotify (~> 0.8.8)
    -      thor (~> 0.15.4)
    -      tilt (~> 1.3.1)
    -    middleman-minify-html (3.0.0)
    -      htmlcompressor
    -      middleman-core (~> 3.0.0)
    -    middleman-more (3.0.6)
    +    ffi (1.9.0)
    +    fssm (0.2.10)
    +    haml (4.0.3)
    +      tilt
    +    highline (1.6.19)
    +    hike (1.2.3)
    +    i18n (0.6.5)
    +    kramdown (1.1.0)
    +    libv8 (3.16.14.3)
    +    listen (1.2.3)
    +      rb-fsevent (>= 0.9.3)
    +      rb-inotify (>= 0.9)
    +      rb-kqueue (>= 0.2)
    +    middleman (3.1.5)
           coffee-script (~> 2.2.0)
    -      coffee-script-source (~> 1.3.3)
           compass (>= 0.12.2)
           execjs (~> 1.4.0)
           haml (>= 3.1.6)
    -      i18n (~> 0.6.0)
    -      maruku (~> 0.6.0)
    -      middleman-core (= 3.0.6)
    -      padrino-helpers (= 0.10.7)
    +      kramdown (~> 1.1.0)
    +      middleman-core (= 3.1.5)
    +      middleman-more (= 3.1.5)
    +      middleman-sprockets (>= 3.1.2)
           sass (>= 3.1.20)
    -      uglifier (~> 1.2.6)
    -    middleman-sprockets (3.0.4)
    -      middleman-more (~> 3.0.1)
    -      sprockets (~> 2.1, < 2.5)
    -      sprockets-sass (~> 0.8.0)
    -    multi_json (1.4.0)
    -    open4 (1.3.0)
    -    padrino-core (0.10.7)
    -      activesupport (~> 3.2.0)
    -      http_router (~> 0.10.2)
    -      sinatra (~> 1.3.1)
    -      thor (~> 0.15.2)
    -      tilt (~> 1.3.0)
    -    padrino-helpers (0.10.7)
    -      i18n (~> 0.6)
    -      padrino-core (= 0.10.7)
    -    rack (1.4.1)
    +      uglifier (~> 2.1.0)
    +    middleman-core (3.1.5)
    +      activesupport (~> 3.2.6)
    +      bundler (~> 1.1)
    +      i18n (~> 0.6.1)
    +      listen (~> 1.2.2)
    +      rack (>= 1.4.5)
    +      rack-test (~> 0.6.1)
    +      thor (>= 0.15.2, < 2.0)
    +      tilt (~> 1.3.6)
    +    middleman-minify-html (3.1.1)
    +      middleman-core (~> 3.0)
    +    middleman-more (3.1.5)
    +    middleman-sprockets (3.1.4)
    +      middleman-core (>= 3.0.14)
    +      middleman-more (>= 3.0.14)
    +      sprockets (~> 2.1)
    +      sprockets-helpers (~> 1.0.0)
    +      sprockets-sass (~> 1.0.0)
    +    multi_json (1.8.0)
    +    rack (1.5.2)
         rack-contrib (1.1.0)
           rack (>= 0.9.1)
    -    rack-protection (1.2.0)
    -      rack
         rack-test (0.6.2)
           rack (>= 1.0)
    -    rb-fsevent (0.9.2)
    -    rb-inotify (0.8.8)
    +    rb-fsevent (0.9.3)
    +    rb-inotify (0.9.2)
           ffi (>= 0.5.0)
    -    redcarpet (2.2.2)
    -    sass (3.2.3)
    -    sinatra (1.3.3)
    -      rack (~> 1.3, >= 1.3.6)
    -      rack-protection (~> 1.2)
    -      tilt (~> 1.3, >= 1.3.3)
    -    sprockets (2.4.5)
    +    rb-kqueue (0.2.0)
    +      ffi (>= 0.5.0)
    +    redcarpet (3.0.0)
    +    ref (1.0.5)
    +    sass (3.2.10)
    +    sprockets (2.10.0)
           hike (~> 1.2)
           multi_json (~> 1.0)
           rack (~> 1.0)
           tilt (~> 1.1, != 1.3.0)
    -    sprockets-sass (0.8.0)
    +    sprockets-helpers (1.0.1)
    +      sprockets (~> 2.0)
    +    sprockets-sass (1.0.1)
           sprockets (~> 2.0)
           tilt (~> 1.1)
    -    syntax (1.0.0)
    -    therubyracer (0.10.2)
    -      libv8 (~> 3.3.10)
    -    thin (1.5.0)
    +    therubyracer (0.12.0)
    +      libv8 (~> 3.16.14.0)
    +      ref
    +    thin (1.5.1)
           daemons (>= 1.0.9)
           eventmachine (>= 0.12.6)
           rack (>= 1.0.0)
    -    thor (0.15.4)
    -    tilt (1.3.3)
    -    uglifier (1.2.7)
    +    thor (0.18.1)
    +    tilt (1.3.7)
    +    uglifier (2.1.2)
           execjs (>= 0.3.0)
    -      multi_json (~> 1.3)
    -    url_mount (0.2.1)
    -      rack
    -    yui-compressor (0.9.6)
    -      POpen4 (>= 0.1.4)
    +      multi_json (~> 1.0, >= 1.0.2)
     
     PLATFORMS
       ruby
     
     DEPENDENCIES
       highline (~> 1.6.15)
    -  middleman (~> 3.0.6)
    -  middleman-minify-html (~> 3.0.0)
    +  middleman (~> 3.1.5)
    +  middleman-minify-html (~> 3.1.1)
       rack-contrib (~> 1.1.0)
    -  redcarpet (~> 2.2.2)
    -  therubyracer (~> 0.10.2)
    +  redcarpet (~> 3.0.0)
    +  therubyracer (~> 0.12.0)
       thin (~> 1.5.0)
    diff --git a/website/source/docs/templates/post-processors.html.markdown b/website/source/docs/templates/post-processors.html.markdown
    index e4c1bfa8c..cda12e2a2 100644
    --- a/website/source/docs/templates/post-processors.html.markdown
    +++ b/website/source/docs/templates/post-processors.html.markdown
    @@ -125,3 +125,28 @@ The answer is no, of course not. Packer is smart enough to figure out
     that at least one post-processor requested that the input be kept, so it will keep
     it around.
     
    +
    +## Run on Specific Builds
    +
    +You can use the `only` or `except` configurations to run a post-processor
    +only with specific builds. These two configurations do what you expect:
    +`only` will only run the post-processor on the specified builds and
    +`except` will run the post-processor on anything other than the specified
    +builds.
    +
    +An example of `only` being used is shown below, but the usage of `except`
    +is effectively the same. `only` and `except` can only be specified on "detailed"
    +configurations. If you have a sequence of post-processors to run, `only`
    +and `except` will only affect that single post-processor in the sequence.
    +
    +
    +{
    +  "type": "vagrant",
    +  "only": ["virtualbox"]
    +}
    +
    + +The values within `only` or `except` are _build names_, not builder +types. If you recall, build names by default are just their builder type, +but if you specify a custom `name` parameter, then you should use that +as the value instead of the type. diff --git a/website/source/docs/templates/provisioners.html.markdown b/website/source/docs/templates/provisioners.html.markdown index f4263fb9b..eb49788e1 100644 --- a/website/source/docs/templates/provisioners.html.markdown +++ b/website/source/docs/templates/provisioners.html.markdown @@ -53,6 +53,30 @@ provisioner to run a local script within the machines: }
    +## Run on Specific Builds + +You can use the `only` or `except` configurations to run a provisioner +only with specific builds. These two configurations do what you expect: +`only` will only run the provisioner on the specified builds and +`except` will run the provisioner on anything other than the specified +builds. + +An example of `only` being used is shown below, but the usage of `except` +is effectively the same: + +
    +{
    +  "type": "shell",
    +  "script": "script.sh",
    +  "only": ["virtualbox"]
    +}
    +
    + +The values within `only` or `except` are _build names_, not builder +types. If you recall, build names by default are just their builder type, +but if you specify a custom `name` parameter, then you should use that +as the value instead of the type. + ## Build-Specific Overrides While the goal of Packer is to produce identical machine images, it From 25924a2d2b3917f530fe193818dbed5d604ec56a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 21 Sep 2013 18:28:14 -0700 Subject: [PATCH 107/173] provisioner/puppet-masterless: user variables in puppet vars [GH-448] --- CHANGELOG.md | 2 ++ provisioner/puppet-masterless/provisioner.go | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1835745ec..f2e696741 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ BUG FIXES: * post-processor/vagrant: Override configurations properly work. [GH-426] * provisioner/puppet-masterless: Fix failure case when both facter vars are used and prevent_sudo. [GH-415] +* provisioner/puppet-masterless: User variables now work properly in + manifest file and hiera path. [GH-448] ## 0.3.7 (September 9, 2013) diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go index ff8b2112b..4552aef9a 100644 --- a/provisioner/puppet-masterless/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -82,7 +82,9 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { // Templates templates := map[string]*string{ - "staging_dir": &p.config.StagingDir, + "hiera_config_path": &p.config.HieraConfigPath, + "manifest_file": &p.config.ManifestFile, + "staging_dir": &p.config.StagingDir, } for n, ptr := range templates { From 6965af291bc766229fdc9feb09ae69b528a5d968 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Sep 2013 09:39:27 -0700 Subject: [PATCH 108/173] packer/plugin: log git commit plugin was built against --- packer/plugin/plugin.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packer/plugin/plugin.go b/packer/plugin/plugin.go index f9fca8cdd..9afba0285 100644 --- a/packer/plugin/plugin.go +++ b/packer/plugin/plugin.go @@ -33,6 +33,8 @@ const MagicCookieValue = "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d69 // This serves a single RPC connection on the given RPC server on // a random port. func serve(server *rpc.Server) (err error) { + log.Printf("Plugin build against Packer '%s'", packer.GitCommit) + if os.Getenv(MagicCookieKey) != MagicCookieValue { return errors.New("Please do not execute plugins directly. Packer will execute these for you.") } From c7b10cb2cf84cd67023bf88ce56f1ac567649649 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Sep 2013 09:51:14 -0700 Subject: [PATCH 109/173] packer/plugin: detect invalid versions --- CHANGELOG.md | 1 + packer/plugin/client.go | 22 ++++++++++++++++++---- packer/plugin/client_test.go | 15 +++++++++++++++ packer/plugin/plugin.go | 7 ++++++- packer/plugin/plugin_test.go | 9 ++++++--- 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2e696741..2bd9fc504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ IMPROVEMENTS: * core: User variables can now be used for integer, boolean, etc. values. [GH-418] +* core: Plugins made with incompatible versions will no longer load. * builder/amazon/all: Interrupts work while waiting for AMI to be ready. * provisioner/shell: Script line-endings are automatically converted to Unix-style line-endings. Can be disabled by setting "binary" to "true". diff --git a/packer/plugin/client.go b/packer/plugin/client.go index d3a302977..792f9d99d 100644 --- a/packer/plugin/client.go +++ b/packer/plugin/client.go @@ -317,10 +317,24 @@ func (c *Client) Start() (address string, err error) { err = errors.New("timeout while waiting for plugin to start") case <-exitCh: err = errors.New("plugin exited before we could connect") - case line := <-linesCh: - // Trim the address and reset the err since we were able - // to read some sort of address. - c.address = strings.TrimSpace(string(line)) + case lineBytes := <-linesCh: + // Trim the line and split by "|" in order to get the parts of + // the output. + line := strings.TrimSpace(string(lineBytes)) + parts := strings.SplitN(line, "|", 2) + if len(parts) < 2 { + err = fmt.Errorf("Unrecognized remote plugin message: %s", line) + return + } + + // Test the API version + if parts[0] != APIVersion { + err = fmt.Errorf("Incompatible API version with plugin. "+ + "Plugin version: %s, Ours: %s", parts[0], APIVersion) + return + } + + c.address = parts[1] address = c.address } diff --git a/packer/plugin/client_test.go b/packer/plugin/client_test.go index ae71c3362..f9257034e 100644 --- a/packer/plugin/client_test.go +++ b/packer/plugin/client_test.go @@ -37,6 +37,21 @@ func TestClient(t *testing.T) { } } +func TestClientStart_badVersion(t *testing.T) { + config := &ClientConfig{ + Cmd: helperProcess("bad-version"), + StartTimeout: 50 * time.Millisecond, + } + + c := NewClient(config) + defer c.Kill() + + _, err := c.Start() + if err == nil { + t.Fatal("err should not be nil") + } +} + func TestClient_Start_Timeout(t *testing.T) { config := &ClientConfig{ Cmd: helperProcess("start-timeout"), diff --git a/packer/plugin/plugin.go b/packer/plugin/plugin.go index 9afba0285..a91fcc3ce 100644 --- a/packer/plugin/plugin.go +++ b/packer/plugin/plugin.go @@ -30,6 +30,11 @@ var Interrupts int32 = 0 const MagicCookieKey = "PACKER_PLUGIN_MAGIC_COOKIE" const MagicCookieValue = "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d6991ca9872b2" +// 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 = "1" + // This serves a single RPC connection on the given RPC server on // a random port. func serve(server *rpc.Server) (err error) { @@ -77,7 +82,7 @@ func serve(server *rpc.Server) (err error) { // Output the address to stdout log.Printf("Plugin address: %s\n", address) - fmt.Println(address) + fmt.Printf("%s|%s\n", APIVersion, address) os.Stdout.Sync() // Accept a connection diff --git a/packer/plugin/plugin_test.go b/packer/plugin/plugin_test.go index 17018f82d..10c3f9d5c 100644 --- a/packer/plugin/plugin_test.go +++ b/packer/plugin/plugin_test.go @@ -50,6 +50,9 @@ func TestHelperProcess(*testing.T) { cmd, args := args[0], args[1:] switch cmd { + case "bad-version": + fmt.Printf("%s1|:1234\n", APIVersion) + <-make(chan int) case "builder": ServeBuilder(new(helperBuilder)) case "command": @@ -59,7 +62,7 @@ func TestHelperProcess(*testing.T) { case "invalid-rpc-address": fmt.Println("lolinvalid") case "mock": - fmt.Println(":1234") + fmt.Printf("%s|:1234\n", APIVersion) <-make(chan int) case "post-processor": ServePostProcessor(new(helperPostProcessor)) @@ -69,11 +72,11 @@ func TestHelperProcess(*testing.T) { time.Sleep(1 * time.Minute) os.Exit(1) case "stderr": - fmt.Println(":1234") + fmt.Printf("%s|:1234\n", APIVersion) log.Println("HELLO") log.Println("WORLD") case "stdin": - fmt.Println(":1234") + fmt.Printf("%s|:1234\n", APIVersion) data := make([]byte, 5) if _, err := os.Stdin.Read(data); err != nil { log.Printf("stdin read error: %s", err) From a2e6e8b3986bed88ec71e9a372104db11102cc90 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Sep 2013 09:53:53 -0700 Subject: [PATCH 110/173] Add updatedeps to .PHONY list in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 934a12852..aa7bbae2e 100644 --- a/Makefile +++ b/Makefile @@ -28,4 +28,4 @@ test: deps @echo "$(OK_COLOR)==> Testing Packer...$(NO_COLOR)" go test ./... -.PHONY: all deps format test +.PHONY: all deps format test updatedeps From 3f4c9a500eb8d920839681da1f4c50a3dbe16fd8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Sep 2013 09:54:21 -0700 Subject: [PATCH 111/173] Add clean to .PHONY list in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index aa7bbae2e..e3d7e9fcd 100644 --- a/Makefile +++ b/Makefile @@ -28,4 +28,4 @@ test: deps @echo "$(OK_COLOR)==> Testing Packer...$(NO_COLOR)" go test ./... -.PHONY: all deps format test updatedeps +.PHONY: all clean deps format test updatedeps From 3eb744249f6bbbe88018dc7d06806c6e7c6a5874 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Sep 2013 09:55:01 -0700 Subject: [PATCH 112/173] make updatedeps updates the core deps too --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index e3d7e9fcd..a74b30740 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,7 @@ deps: updatedeps: @echo "$(OK_COLOR)==> Updating all dependencies$(NO_COLOR)" + @go get -d -v -u ./... @echo $(DEPS) | xargs -n1 go get -d -u clean: From 6511b1892c0f92e3b71fe77956d515753a774df7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Sep 2013 09:58:34 -0700 Subject: [PATCH 113/173] scripts: build.sh compiles all Packer components in parallel --- scripts/build.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/scripts/build.sh b/scripts/build.sh index 95ba90abf..8d0976b6b 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -35,21 +35,44 @@ fi echo -e "${OK_COLOR}--> Installing dependencies to speed up builds...${NO_COLOR}" go get ./... +# 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 +} + +echo -e "${OK_COLOR}--> NOTE: Compilation of components will be done in parallel.${NO_COLOR}" + # Compile the main Packer app echo -e "${OK_COLOR}--> Compiling Packer${NO_COLOR}" +( go build \ ${PACKER_RACE} \ -ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \ -v \ -o bin/packer${EXTENSION} . +) & # Go over each plugin and build it for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do PLUGIN_NAME=$(basename ${PLUGIN}) echo -e "${OK_COLOR}--> Compiling Plugin: ${PLUGIN_NAME}${NO_COLOR}" + ( go build \ ${PACKER_RACE} \ -ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \ -v \ -o bin/packer-${PLUGIN_NAME}${EXTENSION} ${PLUGIN} + ) & done + +waitAll From 1e309b660925ea615025cec119c0ab3b7432771f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Sep 2013 10:03:04 -0700 Subject: [PATCH 114/173] scripts: PACKER_NO_BUILD_PARALLEL to build in sequence --- scripts/build.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/build.sh b/scripts/build.sh index 8d0976b6b..8ba8d3d4f 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -26,6 +26,9 @@ if [ "$(go env GOOS)" = "windows" ]; then EXTENSION=".exe" fi +# Make sure that if we're killed, we kill all our subprocseses +trap "kill 0" SIGINT SIGTERM EXIT + # If we're building a race-enabled build, then set that up. if [ ! -z $PACKER_RACE ]; then echo -e "${OK_COLOR}--> Building with race detection enabled${NO_COLOR}" @@ -50,7 +53,16 @@ waitAll() { fi } -echo -e "${OK_COLOR}--> NOTE: Compilation of components will be done in parallel.${NO_COLOR}" +waitSingle() { + if [ ! -z $PACKER_NO_BUILD_PARALLEL ]; then + waitAll + fi +} + +if [ -z $PACKER_NO_BUILD_PARALLEL ]; then + echo -e "${OK_COLOR}--> NOTE: Compilation of components " \ + "will be done in parallel.${NO_COLOR}" +fi # Compile the main Packer app echo -e "${OK_COLOR}--> Compiling Packer${NO_COLOR}" @@ -62,6 +74,8 @@ go build \ -o bin/packer${EXTENSION} . ) & +waitSingle + # Go over each plugin and build it for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do PLUGIN_NAME=$(basename ${PLUGIN}) @@ -73,6 +87,8 @@ for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do -v \ -o bin/packer-${PLUGIN_NAME}${EXTENSION} ${PLUGIN} ) & + + waitSingle done waitAll From dca427ce303b03e3182f83be33e615886643d2f3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Sep 2013 15:56:11 -0500 Subject: [PATCH 115/173] v0.3.8 --- CHANGELOG.md | 2 +- packer/version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bd9fc504..da3fe4660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.3.8 (unreleased) +## 0.3.8 (September 22, 2013) FEATURES: diff --git a/packer/version.go b/packer/version.go index 8f95a99ff..66129d8f8 100644 --- a/packer/version.go +++ b/packer/version.go @@ -15,7 +15,7 @@ const Version = "0.3.8" // 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 8a4a08b1430bd579077e1582458b8e21953a5c29 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Sep 2013 16:15:55 -0500 Subject: [PATCH 116/173] Update 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 da3fe4660..6ba16b249 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.9 (unreleased) + + + ## 0.3.8 (September 22, 2013) FEATURES: diff --git a/packer/version.go b/packer/version.go index 66129d8f8..758985d14 100644 --- a/packer/version.go +++ b/packer/version.go @@ -10,12 +10,12 @@ import ( var GitCommit string // The version of packer. -const Version = "0.3.8" +const Version = "0.3.9" // 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 c47f740946d682272ad69d256199befc8431d8dc Mon Sep 17 00:00:00 2001 From: Nathan Sullivan Date: Tue, 24 Sep 2013 16:14:34 +1000 Subject: [PATCH 117/173] fix variable name --- website/source/docs/provisioners/shell.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/provisioners/shell.html.markdown b/website/source/docs/provisioners/shell.html.markdown index 4c5a19992..d47b59733 100644 --- a/website/source/docs/provisioners/shell.html.markdown +++ b/website/source/docs/provisioners/shell.html.markdown @@ -102,7 +102,7 @@ root privileges without worrying about password prompts. ## Default Environmental Variables In addition to being able to specify custom environmental variables using -the `environmental_vars` configuration, the provisioner automatically +the `environment_vars` configuration, the provisioner automatically defines certain commonly useful environmental variables: * `PACKER_BUILD_NAME` is set to the name of the build that Packer is running. From d3c2b3492c572c445dc6eb33a2968872788fdf95 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Tue, 24 Sep 2013 01:40:42 -0700 Subject: [PATCH 118/173] use interactive shell to run build script. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a74b30740..77e9b5a23 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...) all: deps @mkdir -p bin/ @echo "$(OK_COLOR)==> Building$(NO_COLOR)" - @./scripts/build.sh + @bash --norc -i ./scripts/build.sh deps: @echo "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)" From f0d0621855f5b1d0dd3e956030f7d832db30bc91 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Sep 2013 22:59:44 +0200 Subject: [PATCH 119/173] packer: default user var values needn't be strings [GH-456] --- CHANGELOG.md | 2 ++ packer/template.go | 24 +++++++++++++++--------- packer/template_test.go | 13 +++++++++++-- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ba16b249..562fe7b09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## 0.3.9 (unreleased) +BUG FIXES: +* core: default user variable values don't need to be strings. [GH-456] ## 0.3.8 (September 22, 2013) diff --git a/packer/template.go b/packer/template.go index 547348d91..063508ba4 100644 --- a/packer/template.go +++ b/packer/template.go @@ -124,18 +124,24 @@ func ParseTemplate(data []byte) (t *Template, err error) { // Gather all the variables for k, v := range rawTpl.Variables { var variable RawVariable - variable.Default = "" variable.Required = v == nil - if v != nil { - def, ok := v.(string) - if !ok { - errors = append(errors, - fmt.Errorf("variable '%s': default value must be string or null", k)) - continue - } + // Create a new mapstructure decoder in order to decode the default + // value since this is the only value in the regular template that + // can be weakly typed. + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: &variable.Default, + WeaklyTypedInput: true, + }) + if err != nil { + // This should never happen. + panic(err) + } - variable.Default = def + err = decoder.Decode(v) + if err != nil { + errors = append(errors, + fmt.Errorf("Error decoding default value for user var '%s': %s", k, err)) } t.Variables[k] = variable diff --git a/packer/template_test.go b/packer/template_test.go index aa531ad26..d457e7932 100644 --- a/packer/template_test.go +++ b/packer/template_test.go @@ -391,7 +391,8 @@ func TestParseTemplate_Variables(t *testing.T) { { "variables": { "foo": "bar", - "bar": null + "bar": null, + "baz": 27 }, "builders": [{"type": "something"}] @@ -403,7 +404,7 @@ func TestParseTemplate_Variables(t *testing.T) { t.Fatalf("err: %s", err) } - if result.Variables == nil || len(result.Variables) != 2 { + if result.Variables == nil || len(result.Variables) != 3 { t.Fatalf("bad vars: %#v", result.Variables) } @@ -422,6 +423,14 @@ func TestParseTemplate_Variables(t *testing.T) { if !result.Variables["bar"].Required { t.Fatal("bar should be required") } + + if result.Variables["baz"].Default != "27" { + t.Fatal("default should be empty") + } + + if result.Variables["baz"].Required { + t.Fatal("baz should not be required") + } } func TestParseTemplate_variablesBadDefault(t *testing.T) { From 6face65ecc9fae78ddbc12c81b0ee25f6b32da76 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Sep 2013 23:01:16 +0200 Subject: [PATCH 120/173] packer: skip the user var if there was an error --- packer/template.go | 1 + 1 file changed, 1 insertion(+) diff --git a/packer/template.go b/packer/template.go index 063508ba4..180b02c0f 100644 --- a/packer/template.go +++ b/packer/template.go @@ -142,6 +142,7 @@ func ParseTemplate(data []byte) (t *Template, err error) { if err != nil { errors = append(errors, fmt.Errorf("Error decoding default value for user var '%s': %s", k, err)) + continue } t.Variables[k] = variable From af477a59df23d8fe0f7dce52bcdd1e73aa31d275 Mon Sep 17 00:00:00 2001 From: Bob Potter Date: Tue, 24 Sep 2013 23:44:03 -0500 Subject: [PATCH 121/173] Update builder-amazon-chroot to work with WaitForState changes in 7c56148f --- builder/amazon/chroot/step_attach_volume.go | 13 +++++++------ builder/amazon/chroot/step_create_volume.go | 3 ++- builder/amazon/chroot/step_snapshot.go | 3 ++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/builder/amazon/chroot/step_attach_volume.go b/builder/amazon/chroot/step_attach_volume.go index 1b1bce0f2..ab2ddb708 100644 --- a/builder/amazon/chroot/step_attach_volume.go +++ b/builder/amazon/chroot/step_attach_volume.go @@ -60,7 +60,8 @@ func (s *StepAttachVolume) Run(state multistep.StateBag) multistep.StepAction { return nil, "", errors.New("No attachments on volume.") } - return nil, resp.Volumes[0].Attachments[0].Status, nil + a := resp.Volumes[0].Attachments[0] + return a, a.Status, nil }, } @@ -111,12 +112,12 @@ func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error { return nil, "", err } - state := "detached" - if len(resp.Volumes[0].Attachments) > 0 { - state = resp.Volumes[0].Attachments[0].Status + v := resp.Volumes[0] + if len(v.Attachments) > 0 { + return v, v.Attachments[0].Status, nil + } else { + return v, "detached", nil } - - return nil, state, nil }, } diff --git a/builder/amazon/chroot/step_create_volume.go b/builder/amazon/chroot/step_create_volume.go index 5346bf1ca..0b89cf642 100644 --- a/builder/amazon/chroot/step_create_volume.go +++ b/builder/amazon/chroot/step_create_volume.go @@ -75,7 +75,8 @@ func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction { return nil, "", err } - return nil, resp.Volumes[0].Status, nil + v := resp.Volumes[0] + return v, v.Status, nil }, } diff --git a/builder/amazon/chroot/step_snapshot.go b/builder/amazon/chroot/step_snapshot.go index 22cf4d17f..e065da2df 100644 --- a/builder/amazon/chroot/step_snapshot.go +++ b/builder/amazon/chroot/step_snapshot.go @@ -51,7 +51,8 @@ func (s *StepSnapshot) Run(state multistep.StateBag) multistep.StepAction { return nil, "", errors.New("No snapshots found.") } - return nil, resp.Snapshots[0].Status, nil + s := resp.Snapshots[0] + return s, s.Status, nil }, } From 07c93fdfdd82a101e98e1e988f6d88696c086d69 Mon Sep 17 00:00:00 2001 From: Bob Potter Date: Wed, 25 Sep 2013 00:09:10 -0500 Subject: [PATCH 122/173] Document permissions required for amazon-chroot build to function. --- .../docs/builders/amazon-chroot.html.markdown | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/website/source/docs/builders/amazon-chroot.html.markdown b/website/source/docs/builders/amazon-chroot.html.markdown index 593ac97fe..b23607470 100644 --- a/website/source/docs/builders/amazon-chroot.html.markdown +++ b/website/source/docs/builders/amazon-chroot.html.markdown @@ -184,3 +184,37 @@ out of your AMI builds. Packer properly obtains a process lock for the parallelism-sensitive parts of its internals such as finding an available device. + +## Using an IAM Instance Profile + +If AWS keys are not specified in the template or through environment variables +Packer will use credentials provided by the instance's IAM profile, if it has one. + +The following policy document provides the minimal set permissions necessary for Packer to work: + +
    +{
    +  "Statement": [{
    +      "Effect": "Allow",
    +      "Action" : [
    +        "ec2:AttachVolume",
    +        "ec2:CreateVolume",
    +        "ec2:DeleteVolume",
    +        "ec2:DescribeVolumes",
    +        "ec2:DetachVolume",
    +
    +        "ec2:DescribeInstances",
    +
    +        "ec2:CreateSnapshot",
    +        "ec2:DeleteSnapshot",
    +        "ec2:DescribeSnapshots",
    +
    +        "ec2:DescribeImages",
    +        "ec2:RegisterImage",
    +
    +        "ec2:CreateTags"
    +      ],
    +      "Resource" : "*"
    +  }]
    +}
    +
    From 3e82c6cf1229304ade6c028cddf87b42060a6490 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Sep 2013 09:29:01 +0200 Subject: [PATCH 123/173] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 562fe7b09..589492ae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ BUG FIXES: * core: default user variable values don't need to be strings. [GH-456] +* builder/amazon-chroot: Fix errors with waitin for state change. [GH-459] ## 0.3.8 (September 22, 2013) From 8cdb92e327a86ec4e9ccfdea04ee3d36819c4d40 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Sep 2013 10:42:49 +0200 Subject: [PATCH 124/173] communicator/ssh: dir upload works when dir contains symlinks [Gh-449] --- CHANGELOG.md | 2 ++ communicator/ssh/communicator.go | 23 +++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 589492ae7..60b1780d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ BUG FIXES: * core: default user variable values don't need to be strings. [GH-456] * builder/amazon-chroot: Fix errors with waitin for state change. [GH-459] +* communicator/ssh: SCP uploads now work properly when directories + contain symlinks. [GH-449] ## 0.3.8 (September 22, 2013) diff --git a/communicator/ssh/communicator.go b/communicator/ssh/communicator.go index 96de20de0..e2eea4b21 100644 --- a/communicator/ssh/communicator.go +++ b/communicator/ssh/communicator.go @@ -408,8 +408,27 @@ func scpUploadDir(root string, fs []os.FileInfo, w io.Writer, r *bufio.Reader) e for _, fi := range fs { realPath := filepath.Join(root, fi.Name()) - if !fi.IsDir() { - // It is a regular file, just upload it + // Track if this is actually a symlink to a directory. If it is + // a symlink to a file we don't do any special behavior because uploading + // a file just works. If it is a directory, we need to know so we + // treat it as such. + isSymlinkToDir := false + if fi.Mode() & os.ModeSymlink == os.ModeSymlink { + symPath, err := filepath.EvalSymlinks(realPath) + if err != nil { + return err + } + + symFi, err := os.Lstat(symPath) + if err != nil { + return err + } + + isSymlinkToDir = symFi.IsDir() + } + + if !fi.IsDir() && !isSymlinkToDir { + // It is a regular file (or symlink to a file), just upload it f, err := os.Open(realPath) if err != nil { return err From 5086ff1a4eb30073147067d8aca12f76180d8c1b Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 25 Sep 2013 03:20:22 -0700 Subject: [PATCH 125/173] pass chroot command to /bin/sh this allows us to specify an arbitrary chroot command, i.e. for sudo --- builder/amazon/chroot/communicator.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 3e1e8deda..d434da8f1 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -13,13 +13,14 @@ import ( // Communicator is a special communicator that works by executing // commands locally but within a chroot. type Communicator struct { - Chroot string - ChrootCommand string + Chroot string + ChrootCommand string } func (c *Communicator) Start(cmd *packer.RemoteCmd) error { - localCmd := exec.Command(c.ChrootCommand, c.Chroot, "/bin/sh", "-c", cmd.Command) + chrootCommand := fmt.Sprintf("%s %s %s", c.ChrootCommand, c.Chroot, cmd.Command) + localcmd := exec.Command("/bin/sh", "-c", chrootCommand) localCmd.Stdin = cmd.Stdin localCmd.Stdout = cmd.Stdout localCmd.Stderr = cmd.Stderr From dd356d33d8e912eedaa724c6ff2b443bc1bcace9 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 26 Sep 2013 00:31:07 -0700 Subject: [PATCH 126/173] notes/reorg. --- builder/amazon/chroot/communicator.go | 32 ++++++++++++------------ builder/amazon/chroot/step_copy_files.go | 6 +++++ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index d434da8f1..b81300563 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -52,22 +52,6 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error { return nil } -func (c *Communicator) Upload(dst string, r io.Reader) error { - dst = filepath.Join(c.Chroot, dst) - log.Printf("Uploading to chroot dir: %s", dst) - f, err := os.Create(dst) - if err != nil { - return err - } - defer f.Close() - - if _, err := io.Copy(f, r); err != nil { - return err - } - - return nil -} - func (c *Communicator) UploadDir(dst string, src string, exclude []string) error { walkFn := func(fullPath string, info os.FileInfo, err error) error { if err != nil { @@ -100,6 +84,22 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error return filepath.Walk(src, walkFn) } +func (c *Communicator) Upload(dst string, r io.Reader) error { + dst = filepath.Join(c.Chroot, dst) + log.Printf("Uploading to chroot dir: %s", dst) + f, err := os.Create(dst) + if err != nil { + return err + } + defer f.Close() + + if _, err := io.Copy(f, r); err != nil { + return err + } + + return nil +} + func (c *Communicator) Download(src string, w io.Writer) error { src = filepath.Join(c.Chroot, src) log.Printf("Downloading from chroot dir: %s", src) diff --git a/builder/amazon/chroot/step_copy_files.go b/builder/amazon/chroot/step_copy_files.go index d729ee234..d383103a4 100644 --- a/builder/amazon/chroot/step_copy_files.go +++ b/builder/amazon/chroot/step_copy_files.go @@ -68,6 +68,12 @@ func (s *StepCopyFiles) CleanupFunc(multistep.StateBag) error { return nil } +/* TODO: move to util file. + * change prototype to + func copySingle(dst string, src string, copyCommand string) error + * I think we should switch to cp for copying files, then allow specifying a copy_files_command or something. +Maybe we should just do a execute_wrapper that allows you to wrap every command... +*/ func (s *StepCopyFiles) copySingle(dst, src string) error { // Stat the src file so we can copy the mode later srcInfo, err := os.Stat(src) From 7fa238503b6499e4bc1b4f43f109e2eb0ed710b1 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 26 Sep 2013 00:58:25 -0700 Subject: [PATCH 127/173] wip --- builder/amazon/chroot/builder.go | 6 ++++++ builder/amazon/chroot/communicator.go | 12 +++++------ builder/amazon/chroot/copy_files.go | 21 +++++++++++++++++++ .../amazon/chroot/step_chroot_provision.go | 6 ++++-- builder/amazon/chroot/step_copy_files.go | 3 ++- 5 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 builder/amazon/chroot/copy_files.go diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index 6647b769e..93685de72 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -31,6 +31,7 @@ type Config struct { DevicePath string `mapstructure:"device_path"` MountCommand string `mapstructure:"mount_command"` ChrootCommand string `mapstructure:"chroot_command"` + CopyCommand string `mapstructure:"copy_command"` MountPath string `mapstructure:"mount_path"` SourceAmi string `mapstructure:"source_ami"` UnmountCommand string `mapstructure:"unmount_command"` @@ -87,6 +88,10 @@ func (b *Builder) Prepare(raws ...interface{}) error { b.config.ChrootCommand = "chroot" } + if b.config.CopyCommand == "" { + b.config.CopyCommand = "cp" + } + if b.config.MountPath == "" { b.config.MountPath = "packer-amazon-chroot-volumes/{{.Device}}" } @@ -135,6 +140,7 @@ func (b *Builder) Prepare(raws ...interface{}) error { "device_path": &b.config.DevicePath, "mount_command": &b.config.MountCommand, "chroot_command": &b.config.ChrootCommand, + "copy_command": &b.config.CopyCommand, "source_ami": &b.config.SourceAmi, "unmount_command": &b.config.UnmountCommand, } diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index b81300563..2af07833c 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -15,6 +15,7 @@ import ( type Communicator struct { Chroot string ChrootCommand string + CopyCommand string } func (c *Communicator) Start(cmd *packer.RemoteCmd) error { @@ -71,13 +72,10 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error } dstPath := filepath.Join(dst, path) - f, err := os.Open(fullPath) - if err != nil { - return err - } - defer f.Close() - - return c.Upload(dstPath, f) + dst = filepath.Join(c.Chroot, dst) + log.Printf("Uploading to chroot dir: %s", dst) + return copySingle(dst, "", c.CopyCommand) + //return c.Upload(dstPath, f) } log.Printf("Uploading directory '%s' to '%s'", src, dst) diff --git a/builder/amazon/chroot/copy_files.go b/builder/amazon/chroot/copy_files.go new file mode 100644 index 000000000..8d75cb65d --- /dev/null +++ b/builder/amazon/chroot/copy_files.go @@ -0,0 +1,21 @@ +package chroot + +import ( + "log" + "os" + "os/exec" + "path/filepath" + "syscall" +) + +func copySingle(dst string, src string, copyCommand string) error { + cpCommand := fmt.Sprintf("sudo cp -fn %s %s", src, dest) + localcmd := exec.Command("/bin/sh", "-c", cpCommand) + log.Println(localcmd.Args) + out, err := localcmd.CombinedOutput() + if err != nil { + log.Println(err) + } + log.Printf("output: %s", out) + return nil +} diff --git a/builder/amazon/chroot/step_chroot_provision.go b/builder/amazon/chroot/step_chroot_provision.go index b1a156216..bb53e0603 100644 --- a/builder/amazon/chroot/step_chroot_provision.go +++ b/builder/amazon/chroot/step_chroot_provision.go @@ -15,12 +15,14 @@ func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction hook := state.Get("hook").(packer.Hook) mountPath := state.Get("mount_path").(string) chrootCommand := state.Get("chroot_command").(string) + copyCommand := state.Get("copy_command").(string) ui := state.Get("ui").(packer.Ui) // Create our communicator comm := &Communicator{ - Chroot: mountPath, - ChrootCommand: chrootCommand, + Chroot: mountPath, + ChrootCommand: chrootCommand, + CopyCommand: copyCommand, } // Provision diff --git a/builder/amazon/chroot/step_copy_files.go b/builder/amazon/chroot/step_copy_files.go index d383103a4..efe411354 100644 --- a/builder/amazon/chroot/step_copy_files.go +++ b/builder/amazon/chroot/step_copy_files.go @@ -22,6 +22,7 @@ type StepCopyFiles struct { func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) mountPath := state.Get("mount_path").(string) + copyCmd := state.Get("copy_command").(string) ui := state.Get("ui").(packer.Ui) s.files = make([]string, 0, len(config.CopyFiles)) @@ -32,7 +33,7 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction { chrootPath := filepath.Join(mountPath, path) log.Printf("Copying '%s' to '%s'", path, chrootPath) - if err := s.copySingle(chrootPath, path); err != nil { + if err := copySingle(chrootPath, path, copyCmd); err != nil { err := fmt.Errorf("Error copying file: %s", err) state.Put("error", err) ui.Error(err.Error()) From ce3725efec2019d412a427e313ddc6e2d0276f3e Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 26 Sep 2013 01:07:01 -0700 Subject: [PATCH 128/173] wip --- builder/amazon/chroot/communicator.go | 5 ++- builder/amazon/chroot/copy_files.go | 3 -- builder/amazon/chroot/step_copy_files.go | 45 ------------------------ 3 files changed, 2 insertions(+), 51 deletions(-) diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 2af07833c..ba71d8605 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -72,10 +72,9 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error } dstPath := filepath.Join(dst, path) - dst = filepath.Join(c.Chroot, dst) + dst := filepath.Join(c.Chroot, dst) log.Printf("Uploading to chroot dir: %s", dst) - return copySingle(dst, "", c.CopyCommand) - //return c.Upload(dstPath, f) + return copySingle(dst, fullPath, c.CopyCommand) } log.Printf("Uploading directory '%s' to '%s'", src, dst) diff --git a/builder/amazon/chroot/copy_files.go b/builder/amazon/chroot/copy_files.go index 8d75cb65d..0c4ef2dbc 100644 --- a/builder/amazon/chroot/copy_files.go +++ b/builder/amazon/chroot/copy_files.go @@ -2,10 +2,7 @@ package chroot import ( "log" - "os" "os/exec" - "path/filepath" - "syscall" ) func copySingle(dst string, src string, copyCommand string) error { diff --git a/builder/amazon/chroot/step_copy_files.go b/builder/amazon/chroot/step_copy_files.go index efe411354..5e779efa8 100644 --- a/builder/amazon/chroot/step_copy_files.go +++ b/builder/amazon/chroot/step_copy_files.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "io" "log" "os" "path/filepath" @@ -68,47 +67,3 @@ func (s *StepCopyFiles) CleanupFunc(multistep.StateBag) error { s.files = nil return nil } - -/* TODO: move to util file. - * change prototype to - func copySingle(dst string, src string, copyCommand string) error - * I think we should switch to cp for copying files, then allow specifying a copy_files_command or something. -Maybe we should just do a execute_wrapper that allows you to wrap every command... -*/ -func (s *StepCopyFiles) copySingle(dst, src string) error { - // Stat the src file so we can copy the mode later - srcInfo, err := os.Stat(src) - if err != nil { - return err - } - - // Remove any existing destination file - if err := os.Remove(dst); err != nil { - return err - } - - // Copy the files - srcF, err := os.Open(src) - if err != nil { - return err - } - defer srcF.Close() - - dstF, err := os.Create(dst) - if err != nil { - return err - } - defer dstF.Close() - - if _, err := io.Copy(dstF, srcF); err != nil { - return err - } - dstF.Close() - - // Match the mode - if err := os.Chmod(dst, srcInfo.Mode()); err != nil { - return err - } - - return nil -} From 5e9ff92ff3e0115603dbb6956937f1b26eb68f16 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 26 Sep 2013 01:16:51 -0700 Subject: [PATCH 129/173] Revert "notes/reorg." This reverts commit 73c5aec24d0a504ecfbae038b4b7effb54adb929. Conflicts: builder/amazon/chroot/step_copy_files.go --- builder/amazon/chroot/communicator.go | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index ba71d8605..741de2d83 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -53,6 +53,22 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error { return nil } +func (c *Communicator) Upload(dst string, r io.Reader) error { + dst = filepath.Join(c.Chroot, dst) + log.Printf("Uploading to chroot dir: %s", dst) + f, err := os.Create(dst) + if err != nil { + return err + } + defer f.Close() + + if _, err := io.Copy(f, r); err != nil { + return err + } + + return nil +} + func (c *Communicator) UploadDir(dst string, src string, exclude []string) error { walkFn := func(fullPath string, info os.FileInfo, err error) error { if err != nil { @@ -81,22 +97,6 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error return filepath.Walk(src, walkFn) } -func (c *Communicator) Upload(dst string, r io.Reader) error { - dst = filepath.Join(c.Chroot, dst) - log.Printf("Uploading to chroot dir: %s", dst) - f, err := os.Create(dst) - if err != nil { - return err - } - defer f.Close() - - if _, err := io.Copy(f, r); err != nil { - return err - } - - return nil -} - func (c *Communicator) Download(src string, w io.Writer) error { src = filepath.Join(c.Chroot, src) log.Printf("Downloading from chroot dir: %s", src) From 7740c8fed527860e72ab4ea70bb2533cebc6df46 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 26 Sep 2013 01:32:53 -0700 Subject: [PATCH 130/173] using blocking cp method. --- builder/amazon/chroot/communicator.go | 3 ++- builder/amazon/chroot/copy_files.go | 13 +++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 741de2d83..9d4e4ce09 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -1,6 +1,7 @@ package chroot import ( + "fmt" "github.com/mitchellh/packer/packer" "io" "log" @@ -21,7 +22,7 @@ type Communicator struct { func (c *Communicator) Start(cmd *packer.RemoteCmd) error { chrootCommand := fmt.Sprintf("%s %s %s", c.ChrootCommand, c.Chroot, cmd.Command) - localcmd := exec.Command("/bin/sh", "-c", chrootCommand) + localCmd := exec.Command("/bin/sh", "-c", chrootCommand) localCmd.Stdin = cmd.Stdin localCmd.Stdout = cmd.Stdout localCmd.Stderr = cmd.Stderr diff --git a/builder/amazon/chroot/copy_files.go b/builder/amazon/chroot/copy_files.go index 0c4ef2dbc..ef5cd70b5 100644 --- a/builder/amazon/chroot/copy_files.go +++ b/builder/amazon/chroot/copy_files.go @@ -1,18 +1,15 @@ package chroot import ( - "log" + "fmt" "os/exec" ) -func copySingle(dst string, src string, copyCommand string) error { +func copySingle(dest string, src string, copyCommand string) error { cpCommand := fmt.Sprintf("sudo cp -fn %s %s", src, dest) - localcmd := exec.Command("/bin/sh", "-c", cpCommand) - log.Println(localcmd.Args) - out, err := localcmd.CombinedOutput() - if err != nil { - log.Println(err) + localCmd := exec.Command("/bin/sh", "-c", cpCommand) + if err := localCmd.Run(); err != nil { + return err } - log.Printf("output: %s", out) return nil } From 23a331fc009dbed7d38133a94cb3941db2852a65 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 26 Sep 2013 01:35:29 -0700 Subject: [PATCH 131/173] bugfix. --- builder/amazon/chroot/communicator.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 9d4e4ce09..2b2e1cada 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -88,10 +88,9 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error } } - dstPath := filepath.Join(dst, path) - dst := filepath.Join(c.Chroot, dst) + chrootDest := filepath.Join(c.Chroot, dst, path) log.Printf("Uploading to chroot dir: %s", dst) - return copySingle(dst, fullPath, c.CopyCommand) + return copySingle(chrootDest, fullPath, c.CopyCommand) } log.Printf("Uploading directory '%s' to '%s'", src, dst) From e1dadfc57a8e326f54ed138bf9f5f1d5275096fc Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 26 Sep 2013 02:25:57 -0700 Subject: [PATCH 132/173] Unit tests. --- builder/amazon/chroot/copy_files.go | 2 +- builder/amazon/chroot/copy_files_test.go | 42 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 builder/amazon/chroot/copy_files_test.go diff --git a/builder/amazon/chroot/copy_files.go b/builder/amazon/chroot/copy_files.go index ef5cd70b5..81be8a9c8 100644 --- a/builder/amazon/chroot/copy_files.go +++ b/builder/amazon/chroot/copy_files.go @@ -6,7 +6,7 @@ import ( ) func copySingle(dest string, src string, copyCommand string) error { - cpCommand := fmt.Sprintf("sudo cp -fn %s %s", src, dest) + cpCommand := fmt.Sprintf("%s %s %s", copyCommand, src, dest) localCmd := exec.Command("/bin/sh", "-c", cpCommand) if err := localCmd.Run(); err != nil { return err diff --git a/builder/amazon/chroot/copy_files_test.go b/builder/amazon/chroot/copy_files_test.go new file mode 100644 index 000000000..9a0bb5a4a --- /dev/null +++ b/builder/amazon/chroot/copy_files_test.go @@ -0,0 +1,42 @@ +package chroot + +import ( + "io/ioutil" + "log" + "os" + "testing" +) + +func TestCopyFile(t *testing.T) { + first, err := ioutil.TempFile("", "copy_files_test") + if err != nil { + t.Fatalf("couldn't create temp file.") + } + defer os.Remove(first.Name()) + newName := first.Name() + "-new" + + payload := "copy_files_test.go payload" + if _, err = first.WriteString(payload); err != nil { + t.Fatalf("Couldn't write payload to first file.") + } + + if err := copySingle(newName, first.Name(), "cp"); err != nil { + t.Fatalf("Couldn't copy file") + } + defer os.Remove(newName) + + second, err := os.Open(newName) + if err != nil { + t.Fatalf("Couldn't open copied file.") + } + defer second.Close() + + var copiedPayload = make([]byte, len(payload)) + if _, err := second.Read(copiedPayload); err != nil { + t.Fatalf("Couldn't open copied file for reading.") + } + + if string(copiedPayload) != payload { + t.Fatalf("payload not copied.") + } +} From ee60ed319b08f2dc21964df9fec8ce4983a588a3 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 26 Sep 2013 02:31:10 -0700 Subject: [PATCH 133/173] We're not using this log package. --- builder/amazon/chroot/copy_files_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/builder/amazon/chroot/copy_files_test.go b/builder/amazon/chroot/copy_files_test.go index 9a0bb5a4a..a9da8d742 100644 --- a/builder/amazon/chroot/copy_files_test.go +++ b/builder/amazon/chroot/copy_files_test.go @@ -2,7 +2,6 @@ package chroot import ( "io/ioutil" - "log" "os" "testing" ) From 1104ad3e17ed90875af3279643c62273ca5a855e Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 26 Sep 2013 16:39:37 -0700 Subject: [PATCH 134/173] get chroot command from proper place. --- builder/amazon/chroot/step_copy_files.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builder/amazon/chroot/step_copy_files.go b/builder/amazon/chroot/step_copy_files.go index 5e779efa8..4b8298928 100644 --- a/builder/amazon/chroot/step_copy_files.go +++ b/builder/amazon/chroot/step_copy_files.go @@ -21,7 +21,6 @@ type StepCopyFiles struct { func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) mountPath := state.Get("mount_path").(string) - copyCmd := state.Get("copy_command").(string) ui := state.Get("ui").(packer.Ui) s.files = make([]string, 0, len(config.CopyFiles)) @@ -32,7 +31,7 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction { chrootPath := filepath.Join(mountPath, path) log.Printf("Copying '%s' to '%s'", path, chrootPath) - if err := copySingle(chrootPath, path, copyCmd); err != nil { + if err := copySingle(chrootPath, path, config.ChrootCommand); err != nil { err := fmt.Errorf("Error copying file: %s", err) state.Put("error", err) ui.Error(err.Error()) From 96839f6a050f93dd8baf2c5ef6bfb5c6cbe768c8 Mon Sep 17 00:00:00 2001 From: Ben Podoll Date: Thu, 26 Sep 2013 20:24:18 -0500 Subject: [PATCH 135/173] removing the docs for digital ocean's optional param 'event_delay' as it is no longer used --- website/source/docs/builders/digitalocean.html.markdown | 5 ----- 1 file changed, 5 deletions(-) diff --git a/website/source/docs/builders/digitalocean.html.markdown b/website/source/docs/builders/digitalocean.html.markdown index 1c11d2d3b..b4793a8dd 100644 --- a/website/source/docs/builders/digitalocean.html.markdown +++ b/website/source/docs/builders/digitalocean.html.markdown @@ -35,11 +35,6 @@ Required: Optional: -* `event_delay` (string) - The delay, as a duration string, before checking - the status of an event. DigitalOcean's current API has consistency issues - where events take time to appear after being created. This defaults to "5s" - and generally shouldn't have to be changed. - * `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 "284203", which happens to be "Ubuntu 12.04 x64 Server." From c15bb284913b76d393951c48c2a6d65240b8a66e Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Fri, 27 Sep 2013 01:28:06 +0000 Subject: [PATCH 136/173] bugfixes, wip --- builder/amazon/chroot/copy_files.go | 2 ++ builder/amazon/chroot/step_chroot_provision.go | 7 +++---- builder/amazon/chroot/step_copy_files.go | 5 ++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/builder/amazon/chroot/copy_files.go b/builder/amazon/chroot/copy_files.go index 81be8a9c8..f44c7da96 100644 --- a/builder/amazon/chroot/copy_files.go +++ b/builder/amazon/chroot/copy_files.go @@ -3,11 +3,13 @@ package chroot import ( "fmt" "os/exec" + "log" ) func copySingle(dest string, src string, copyCommand string) error { cpCommand := fmt.Sprintf("%s %s %s", copyCommand, src, dest) localCmd := exec.Command("/bin/sh", "-c", cpCommand) + log.Println(localCmd.Args) if err := localCmd.Run(); err != nil { return err } diff --git a/builder/amazon/chroot/step_chroot_provision.go b/builder/amazon/chroot/step_chroot_provision.go index bb53e0603..6b76d9541 100644 --- a/builder/amazon/chroot/step_chroot_provision.go +++ b/builder/amazon/chroot/step_chroot_provision.go @@ -14,15 +14,14 @@ type StepChrootProvision struct { func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction { hook := state.Get("hook").(packer.Hook) mountPath := state.Get("mount_path").(string) - chrootCommand := state.Get("chroot_command").(string) - copyCommand := state.Get("copy_command").(string) + config := state.Get("config").(*Config) ui := state.Get("ui").(packer.Ui) // Create our communicator comm := &Communicator{ Chroot: mountPath, - ChrootCommand: chrootCommand, - CopyCommand: copyCommand, + ChrootCommand: config.ChrootCommand, + CopyCommand: config.CopyCommand, } // Provision diff --git a/builder/amazon/chroot/step_copy_files.go b/builder/amazon/chroot/step_copy_files.go index 4b8298928..102967423 100644 --- a/builder/amazon/chroot/step_copy_files.go +++ b/builder/amazon/chroot/step_copy_files.go @@ -5,7 +5,6 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" - "os" "path/filepath" ) @@ -31,7 +30,7 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction { chrootPath := filepath.Join(mountPath, path) log.Printf("Copying '%s' to '%s'", path, chrootPath) - if err := copySingle(chrootPath, path, config.ChrootCommand); err != nil { + if err := copySingle(chrootPath, path, config.CopyCommand); err != nil { err := fmt.Errorf("Error copying file: %s", err) state.Put("error", err) ui.Error(err.Error()) @@ -57,7 +56,7 @@ func (s *StepCopyFiles) CleanupFunc(multistep.StateBag) error { if s.files != nil { for _, file := range s.files { log.Printf("Removing: %s", file) - if err := os.Remove(file); err != nil { + if err := copySingle(file, "", "rm"); err != nil { return err } } From de83755c00000e5a848ec05ebc74ed220e3e1efd Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 26 Sep 2013 18:34:01 -0700 Subject: [PATCH 137/173] wip --- builder/amazon/chroot/step_copy_files.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/builder/amazon/chroot/step_copy_files.go b/builder/amazon/chroot/step_copy_files.go index 102967423..7ca6ef2a0 100644 --- a/builder/amazon/chroot/step_copy_files.go +++ b/builder/amazon/chroot/step_copy_files.go @@ -5,6 +5,7 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" + "os/exec" "path/filepath" ) @@ -56,7 +57,9 @@ func (s *StepCopyFiles) CleanupFunc(multistep.StateBag) error { if s.files != nil { for _, file := range s.files { log.Printf("Removing: %s", file) - if err := copySingle(file, "", "rm"); err != nil { + chrootCommand := fmt.Sprintf("rm %s", file) + localCmd := exec.Command("/bin/sh", "-c", chrootCommand) + if err := localCmd.Run(); err != nil { return err } } From cebbcc51a64e2b201a54a232187c54cd9558ba9d Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Fri, 27 Sep 2013 02:11:28 +0000 Subject: [PATCH 138/173] fix chroot Upload command. --- builder/amazon/chroot/communicator.go | 15 ++++++--------- builder/amazon/chroot/copy_files.go | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 2b2e1cada..879d5e5a5 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/mitchellh/packer/packer" "io" + "io/ioutil" "log" "os" "os/exec" @@ -57,17 +58,13 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error { func (c *Communicator) Upload(dst string, r io.Reader) error { dst = filepath.Join(c.Chroot, dst) log.Printf("Uploading to chroot dir: %s", dst) - f, err := os.Create(dst) + tf, err := ioutil.TempFile("", "packer-amazon-chroot") if err != nil { - return err + return fmt.Errorf("Error preparing shell script: %s", err) } - defer f.Close() - - if _, err := io.Copy(f, r); err != nil { - return err - } - - return nil + defer os.Remove(tf.Name()) + io.Copy(tf, r) + return copySingle(dst, tf.Name(), c.CopyCommand) } func (c *Communicator) UploadDir(dst string, src string, exclude []string) error { diff --git a/builder/amazon/chroot/copy_files.go b/builder/amazon/chroot/copy_files.go index f44c7da96..16d0a82f6 100644 --- a/builder/amazon/chroot/copy_files.go +++ b/builder/amazon/chroot/copy_files.go @@ -9,7 +9,7 @@ import ( func copySingle(dest string, src string, copyCommand string) error { cpCommand := fmt.Sprintf("%s %s %s", copyCommand, src, dest) localCmd := exec.Command("/bin/sh", "-c", cpCommand) - log.Println(localCmd.Args) + log.Printf("Executing copy: %s %#v", localCmd.Path, localCmd.Args) if err := localCmd.Run(); err != nil { return err } From 678eb38e184f9b4506d7941f5b2225c59ef2a8d7 Mon Sep 17 00:00:00 2001 From: Kirill Kazakov Date: Fri, 27 Sep 2013 14:27:47 +0400 Subject: [PATCH 139/173] scripts: build.sh exits without being killed by SIGTERM --- scripts/build.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/build.sh b/scripts/build.sh index 8ba8d3d4f..d8243eac7 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -92,3 +92,6 @@ for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do done waitAll + +# Reset signal trapping to avoid "Terminated: 15" at the end +trap - SIGINT SIGTERM EXIT From ac496a63dcc75b4e65988506931cecfdbeec6ed4 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Fri, 27 Sep 2013 10:54:53 +0000 Subject: [PATCH 140/173] replace command config with wrapper command. --- builder/amazon/chroot/builder.go | 29 ++++--------------- builder/amazon/chroot/communicator.go | 20 ++++++++----- builder/amazon/chroot/copy_files.go | 19 ++++++------ .../amazon/chroot/step_chroot_provision.go | 26 +++++++++++++++-- builder/amazon/chroot/step_copy_files.go | 12 ++++---- builder/amazon/chroot/step_mount_device.go | 12 ++++---- builder/amazon/chroot/step_mount_extra.go | 13 ++++----- .../docs/builders/amazon-chroot.html.markdown | 15 ++++------ 8 files changed, 74 insertions(+), 72 deletions(-) diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index 93685de72..165e29078 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -29,12 +29,9 @@ type Config struct { ChrootMounts [][]string `mapstructure:"chroot_mounts"` CopyFiles []string `mapstructure:"copy_files"` DevicePath string `mapstructure:"device_path"` - MountCommand string `mapstructure:"mount_command"` - ChrootCommand string `mapstructure:"chroot_command"` - CopyCommand string `mapstructure:"copy_command"` + CommandWrapper string `mapstructure:"command_wrapper"` MountPath string `mapstructure:"mount_path"` SourceAmi string `mapstructure:"source_ami"` - UnmountCommand string `mapstructure:"unmount_command"` tpl *packer.ConfigTemplate } @@ -80,26 +77,14 @@ func (b *Builder) Prepare(raws ...interface{}) error { b.config.CopyFiles = []string{"/etc/resolv.conf"} } - if b.config.MountCommand == "" { - b.config.MountCommand = "mount" - } - - if b.config.ChrootCommand == "" { - b.config.ChrootCommand = "chroot" - } - - if b.config.CopyCommand == "" { - b.config.CopyCommand = "cp" + if b.config.CommandWrapper == "" { + b.config.CommandWrapper = "{{.Command}}" } if b.config.MountPath == "" { b.config.MountPath = "packer-amazon-chroot-volumes/{{.Device}}" } - if b.config.UnmountCommand == "" { - b.config.UnmountCommand = "umount" - } - // Accumulate any errors errs := common.CheckUnusedConfig(md) errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.tpl)...) @@ -137,12 +122,8 @@ func (b *Builder) Prepare(raws ...interface{}) error { } templates := map[string]*string{ - "device_path": &b.config.DevicePath, - "mount_command": &b.config.MountCommand, - "chroot_command": &b.config.ChrootCommand, - "copy_command": &b.config.CopyCommand, - "source_ami": &b.config.SourceAmi, - "unmount_command": &b.config.UnmountCommand, + "device_path": &b.config.DevicePath, + "source_ami": &b.config.SourceAmi, } for n, ptr := range templates { diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 879d5e5a5..2902e5708 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -1,5 +1,7 @@ package chroot +// pf := func () { somefunc("a str", 1) } + import ( "fmt" "github.com/mitchellh/packer/packer" @@ -12,18 +14,18 @@ import ( "syscall" ) +type Command func(string) *exec.Cmd + // Communicator is a special communicator that works by executing // commands locally but within a chroot. type Communicator struct { - Chroot string - ChrootCommand string - CopyCommand string + Chroot string + ChrootCmd Command + wrappedCommand Command } func (c *Communicator) Start(cmd *packer.RemoteCmd) error { - - chrootCommand := fmt.Sprintf("%s %s %s", c.ChrootCommand, c.Chroot, cmd.Command) - localCmd := exec.Command("/bin/sh", "-c", chrootCommand) + localCmd := c.ChrootCmd(cmd.Command) localCmd.Stdin = cmd.Stdin localCmd.Stdout = cmd.Stdout localCmd.Stderr = cmd.Stderr @@ -64,7 +66,8 @@ func (c *Communicator) Upload(dst string, r io.Reader) error { } defer os.Remove(tf.Name()) io.Copy(tf, r) - return copySingle(dst, tf.Name(), c.CopyCommand) + cpCmd := fmt.Sprintf("cp %s %s", dst, tf.Name()) + return (*c.ChrootCmd(cpCmd)).Run() } func (c *Communicator) UploadDir(dst string, src string, exclude []string) error { @@ -87,7 +90,8 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error chrootDest := filepath.Join(c.Chroot, dst, path) log.Printf("Uploading to chroot dir: %s", dst) - return copySingle(chrootDest, fullPath, c.CopyCommand) + cpCmd := fmt.Sprintf("cp %s %s", chrootDest, fullPath) + return c.ChrootCmd(cpCmd).Run() } log.Printf("Uploading directory '%s' to '%s'", src, dst) diff --git a/builder/amazon/chroot/copy_files.go b/builder/amazon/chroot/copy_files.go index 16d0a82f6..055a7d403 100644 --- a/builder/amazon/chroot/copy_files.go +++ b/builder/amazon/chroot/copy_files.go @@ -2,16 +2,17 @@ package chroot import ( "fmt" - "os/exec" "log" + "os/exec" ) -func copySingle(dest string, src string, copyCommand string) error { - cpCommand := fmt.Sprintf("%s %s %s", copyCommand, src, dest) - localCmd := exec.Command("/bin/sh", "-c", cpCommand) - log.Printf("Executing copy: %s %#v", localCmd.Path, localCmd.Args) - if err := localCmd.Run(); err != nil { - return err - } - return nil +func ChrootCommand(chroot string, command string) *exec.Cmd { + chrootCommand := fmt.Sprintf("chroot %s %s", chroot, command) + return ShellCommand(chrootCommand) +} + +func ShellCommand(command string) *exec.Cmd { + cmd := exec.Command("/bin/sh", "-c", command) + log.Printf("WrappedCommand(%s) -> #%v", command, cmd.Args) + return cmd } diff --git a/builder/amazon/chroot/step_chroot_provision.go b/builder/amazon/chroot/step_chroot_provision.go index 6b76d9541..7375ecad2 100644 --- a/builder/amazon/chroot/step_chroot_provision.go +++ b/builder/amazon/chroot/step_chroot_provision.go @@ -4,6 +4,7 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" + "os/exec" ) // StepChrootProvision provisions the instance within a chroot. @@ -11,17 +12,36 @@ type StepChrootProvision struct { mounts []string } +type WrappedCommandTemplate struct { + command string +} + func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction { hook := state.Get("hook").(packer.Hook) mountPath := state.Get("mount_path").(string) config := state.Get("config").(*Config) ui := state.Get("ui").(packer.Ui) + chrootCmd := func(command string) *exec.Cmd { + return ChrootCommand(mountPath, command) + } + wrappedCommand := func(command string) *exec.Cmd { + wrapped, err := config.tpl.Process(config.CommandWrapper, &WrappedCommandTemplate{ + command: command, + }) + if err != nil { + ui.Error(err.Error()) + } + return ShellCommand(wrapped) + } + + state.Put("chrootCmd", chrootCmd) + state.Put("wrappedCommand", wrappedCommand) // Create our communicator comm := &Communicator{ - Chroot: mountPath, - ChrootCommand: config.ChrootCommand, - CopyCommand: config.CopyCommand, + Chroot: mountPath, + ChrootCmd: chrootCmd, + wrappedCommand: wrappedCommand, } // Provision diff --git a/builder/amazon/chroot/step_copy_files.go b/builder/amazon/chroot/step_copy_files.go index 7ca6ef2a0..80a7b135e 100644 --- a/builder/amazon/chroot/step_copy_files.go +++ b/builder/amazon/chroot/step_copy_files.go @@ -5,7 +5,6 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" - "os/exec" "path/filepath" ) @@ -22,6 +21,7 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) mountPath := state.Get("mount_path").(string) ui := state.Get("ui").(packer.Ui) + wrappedCommand := state.Get("wrappedCommand").(Command) s.files = make([]string, 0, len(config.CopyFiles)) if len(config.CopyFiles) > 0 { @@ -31,7 +31,8 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction { chrootPath := filepath.Join(mountPath, path) log.Printf("Copying '%s' to '%s'", path, chrootPath) - if err := copySingle(chrootPath, path, config.CopyCommand); err != nil { + cmd := fmt.Sprintf("cp %s %s", chrootPath, path) + if err := wrappedCommand(cmd); err != nil { err := fmt.Errorf("Error copying file: %s", err) state.Put("error", err) ui.Error(err.Error()) @@ -53,12 +54,13 @@ func (s *StepCopyFiles) Cleanup(state multistep.StateBag) { } } -func (s *StepCopyFiles) CleanupFunc(multistep.StateBag) error { +func (s *StepCopyFiles) CleanupFunc(state multistep.StateBag) error { + wrappedCommand := state.Get("wrappedCommand").(Command) if s.files != nil { for _, file := range s.files { log.Printf("Removing: %s", file) - chrootCommand := fmt.Sprintf("rm %s", file) - localCmd := exec.Command("/bin/sh", "-c", chrootCommand) + localCmd := wrappedCommand(fmt.Sprintf("rm -f %s", file)) + log.Println(localCmd.Args) if err := localCmd.Run(); err != nil { return err } diff --git a/builder/amazon/chroot/step_mount_device.go b/builder/amazon/chroot/step_mount_device.go index 6079a0293..f9a823623 100644 --- a/builder/amazon/chroot/step_mount_device.go +++ b/builder/amazon/chroot/step_mount_device.go @@ -7,7 +7,6 @@ import ( "github.com/mitchellh/packer/packer" "log" "os" - "os/exec" "path/filepath" ) @@ -59,8 +58,9 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Mounting the root device...") stderr := new(bytes.Buffer) - mountCommand := fmt.Sprintf("%s %s %s", config.MountCommand, device, mountPath) - cmd := exec.Command("/bin/sh", "-c", mountCommand) + mountCommand := fmt.Sprintf("mount %s %s", device, mountPath) + wrappedCommand := state.Get("wrappedCommand").(Command) + cmd := wrappedCommand(mountCommand) cmd.Stderr = stderr if err := cmd.Run(); err != nil { err := fmt.Errorf( @@ -90,12 +90,12 @@ func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error { return nil } - config := state.Get("config").(*Config) ui := state.Get("ui").(packer.Ui) ui.Say("Unmounting the root device...") - unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, s.mountPath) - cmd := exec.Command("/bin/sh", "-c", unmountCommand) + unmountCommand := fmt.Sprintf("umount %s", s.mountPath) + wrappedCommand := state.Get("wrappedCommand").(Command) + cmd := wrappedCommand(unmountCommand) if err := cmd.Run(); err != nil { return fmt.Errorf("Error unmounting root device: %s", err) } diff --git a/builder/amazon/chroot/step_mount_extra.go b/builder/amazon/chroot/step_mount_extra.go index 3fc35f4ac..bbe958ec8 100644 --- a/builder/amazon/chroot/step_mount_extra.go +++ b/builder/amazon/chroot/step_mount_extra.go @@ -6,7 +6,6 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "os" - "os/exec" ) // StepMountExtra mounts the attached device. @@ -21,6 +20,7 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) mountPath := state.Get("mount_path").(string) ui := state.Get("ui").(packer.Ui) + wrappedCommand := state.Get("wrappedCommand").(Command) s.mounts = make([]string, 0, len(config.ChrootMounts)) @@ -43,12 +43,11 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction { ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2])) stderr := new(bytes.Buffer) mountCommand := fmt.Sprintf( - "%s %s %s %s", - config.MountCommand, + "mount %s %s %s", flags, mountInfo[1], innerPath) - cmd := exec.Command("/bin/sh", "-c", mountCommand) + cmd := wrappedCommand(mountCommand) cmd.Stderr = stderr if err := cmd.Run(); err != nil { err := fmt.Errorf( @@ -79,15 +78,15 @@ func (s *StepMountExtra) CleanupFunc(state multistep.StateBag) error { return nil } - config := state.Get("config").(*Config) + wrappedCommand := state.Get("wrappedCommand").(Command) for len(s.mounts) > 0 { var path string lastIndex := len(s.mounts) - 1 path, s.mounts = s.mounts[lastIndex], s.mounts[:lastIndex] - unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, path) + unmountCommand := fmt.Sprintf("unmount %s", path) stderr := new(bytes.Buffer) - cmd := exec.Command("/bin/sh", "-c", unmountCommand) + cmd := wrappedCommand(unmountCommand) cmd.Stderr = stderr if err := cmd.Run(); err != nil { return fmt.Errorf( diff --git a/website/source/docs/builders/amazon-chroot.html.markdown b/website/source/docs/builders/amazon-chroot.html.markdown index 8b593c3e5..2ea9e875d 100644 --- a/website/source/docs/builders/amazon-chroot.html.markdown +++ b/website/source/docs/builders/amazon-chroot.html.markdown @@ -111,9 +111,11 @@ Optional: of the source AMI will be attached. This defaults to "" (empty string), which forces Packer to find an open device automatically. -* `mount_command` (string) - The command to use to mount devices. This - defaults to "mount". This may be useful to set if you want to set - environmental variables or perhaps run it with `sudo` or so on. +* `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 @@ -123,13 +125,6 @@ Optional: * `tags` (object of key/value strings) - Tags applied to the AMI. -* `unmount_command` (string) - Just like `mount_command`, except this is - the command to unmount devices. - -* `chroot_command` (string) - The command to use to create the chroot. - This defaults to "chroot", but like `mount_command`, it may be useful - to use `sudo` or variables. - ## Basic Example Here is a basic example. It is completely valid except for the access keys: From d2f9ba0d118886b2e7ae728ace434f392c4211bb Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Fri, 27 Sep 2013 11:55:19 +0000 Subject: [PATCH 141/173] fixing up types. --- builder/amazon/chroot/builder_test.go | 11 +++++++++++ builder/amazon/chroot/copy_files_test.go | 4 +++- builder/amazon/chroot/step_chroot_provision.go | 4 ++-- builder/amazon/chroot/step_copy_files.go | 1 - builder/amazon/chroot/step_mount_device.go | 2 +- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/builder/amazon/chroot/builder_test.go b/builder/amazon/chroot/builder_test.go index 2f13bae54..6bdf7dc28 100644 --- a/builder/amazon/chroot/builder_test.go +++ b/builder/amazon/chroot/builder_test.go @@ -82,3 +82,14 @@ func TestBuilderPrepare_SourceAmi(t *testing.T) { t.Errorf("err: %s", err) } } + +func TestBuilderPrepare_CommandWrapper(t *testing.T) { + b := &Builder{} + config := testConfig() + + config["command_wrapper"] = "echo hi; {{.Command}}" + err := b.Prepare(config) + if err != nil { + t.Errorf("err: %s", err) + } +} diff --git a/builder/amazon/chroot/copy_files_test.go b/builder/amazon/chroot/copy_files_test.go index a9da8d742..2dccbf0bc 100644 --- a/builder/amazon/chroot/copy_files_test.go +++ b/builder/amazon/chroot/copy_files_test.go @@ -18,8 +18,10 @@ func TestCopyFile(t *testing.T) { if _, err = first.WriteString(payload); err != nil { t.Fatalf("Couldn't write payload to first file.") } + first.Sync() - if err := copySingle(newName, first.Name(), "cp"); err != nil { + cmd := ShellCommand(fmt.Sprintf("cp %s %s", first.Name(), newName)) + if err := cmd.Run(); err != nil { t.Fatalf("Couldn't copy file") } defer os.Remove(newName) diff --git a/builder/amazon/chroot/step_chroot_provision.go b/builder/amazon/chroot/step_chroot_provision.go index 7375ecad2..59a95f45c 100644 --- a/builder/amazon/chroot/step_chroot_provision.go +++ b/builder/amazon/chroot/step_chroot_provision.go @@ -13,7 +13,7 @@ type StepChrootProvision struct { } type WrappedCommandTemplate struct { - command string + Command string } func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction { @@ -26,7 +26,7 @@ func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction } wrappedCommand := func(command string) *exec.Cmd { wrapped, err := config.tpl.Process(config.CommandWrapper, &WrappedCommandTemplate{ - command: command, + Command: command, }) if err != nil { ui.Error(err.Error()) diff --git a/builder/amazon/chroot/step_copy_files.go b/builder/amazon/chroot/step_copy_files.go index 80a7b135e..e22301369 100644 --- a/builder/amazon/chroot/step_copy_files.go +++ b/builder/amazon/chroot/step_copy_files.go @@ -60,7 +60,6 @@ func (s *StepCopyFiles) CleanupFunc(state multistep.StateBag) error { for _, file := range s.files { log.Printf("Removing: %s", file) localCmd := wrappedCommand(fmt.Sprintf("rm -f %s", file)) - log.Println(localCmd.Args) if err := localCmd.Run(); err != nil { return err } diff --git a/builder/amazon/chroot/step_mount_device.go b/builder/amazon/chroot/step_mount_device.go index f9a823623..4cce60f10 100644 --- a/builder/amazon/chroot/step_mount_device.go +++ b/builder/amazon/chroot/step_mount_device.go @@ -59,7 +59,7 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Mounting the root device...") stderr := new(bytes.Buffer) mountCommand := fmt.Sprintf("mount %s %s", device, mountPath) - wrappedCommand := state.Get("wrappedCommand").(Command) + wrappedCommand := state.Get("wrappedCommand").(*Command) cmd := wrappedCommand(mountCommand) cmd.Stderr = stderr if err := cmd.Run(); err != nil { From be80edb28e8d0fe1d531517d7cb24b93b951691d Mon Sep 17 00:00:00 2001 From: Kirill Kazakov Date: Fri, 27 Sep 2013 19:13:55 +0400 Subject: [PATCH 142/173] provisioner/chef-solo: fix .RolesPath and .DataBagsPath in the config template --- provisioner/chef-solo/provisioner.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index ddfc605fb..a22b18e30 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -212,7 +212,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { rolesPath := "" if p.config.RolesPath != "" { - rolesPath := fmt.Sprintf("%s/roles", p.config.StagingDir) + rolesPath = fmt.Sprintf("%s/roles", p.config.StagingDir) if err := p.uploadDirectory(ui, comm, rolesPath, p.config.RolesPath); err != nil { return fmt.Errorf("Error uploading roles: %s", err) } @@ -220,7 +220,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { dataBagsPath := "" if p.config.DataBagsPath != "" { - dataBagsPath := fmt.Sprintf("%s/data_bags", p.config.StagingDir) + dataBagsPath = fmt.Sprintf("%s/data_bags", p.config.StagingDir) if err := p.uploadDirectory(ui, comm, dataBagsPath, p.config.DataBagsPath); err != nil { return fmt.Errorf("Error uploading data bags: %s", err) } From 39c3051a95c44a4ec6205040ec4995b0388e21fd Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Fri, 27 Sep 2013 20:47:44 +0000 Subject: [PATCH 143/173] building but there's an exec error. --- builder/amazon/chroot/builder.go | 12 +++++++++++ builder/amazon/chroot/communicator.go | 2 +- builder/amazon/chroot/copy_files.go | 2 +- builder/amazon/chroot/copy_files_test.go | 1 + .../amazon/chroot/step_chroot_provision.go | 21 +++---------------- builder/amazon/chroot/step_mount_device.go | 8 ++++++- builder/amazon/chroot/step_mount_extra.go | 2 +- 7 files changed, 26 insertions(+), 22 deletions(-) diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index 165e29078..a7f46a5f4 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -13,6 +13,7 @@ import ( "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" "log" + "os/exec" "runtime" ) @@ -160,12 +161,23 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ec2conn := ec2.New(auth, region) + wrappedCommand := func(command string) *exec.Cmd { + wrapped, err := b.config.tpl.Process(b.config.CommandWrapper, &WrappedCommandTemplate{ + Command: command, + }) + if err != nil { + ui.Error(err.Error()) + } + return ShellCommand(wrapped) + } + // Setup the state bag and initial state for the steps state := new(multistep.BasicStateBag) state.Put("config", &b.config) state.Put("ec2", ec2conn) state.Put("hook", hook) state.Put("ui", ui) + state.Put("wrappedCommand", Command(wrappedCommand)) // Build the steps steps := []multistep.Step{ diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 2902e5708..7f52d9fdb 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -21,7 +21,7 @@ type Command func(string) *exec.Cmd type Communicator struct { Chroot string ChrootCmd Command - wrappedCommand Command + WrappedCommand Command } func (c *Communicator) Start(cmd *packer.RemoteCmd) error { diff --git a/builder/amazon/chroot/copy_files.go b/builder/amazon/chroot/copy_files.go index 055a7d403..5af58baa5 100644 --- a/builder/amazon/chroot/copy_files.go +++ b/builder/amazon/chroot/copy_files.go @@ -13,6 +13,6 @@ func ChrootCommand(chroot string, command string) *exec.Cmd { func ShellCommand(command string) *exec.Cmd { cmd := exec.Command("/bin/sh", "-c", command) - log.Printf("WrappedCommand(%s) -> #%v", command, cmd.Args) + log.Printf("ShellCommand(%s) -> #%v", command, cmd.Args) return cmd } diff --git a/builder/amazon/chroot/copy_files_test.go b/builder/amazon/chroot/copy_files_test.go index 2dccbf0bc..c7e00018e 100644 --- a/builder/amazon/chroot/copy_files_test.go +++ b/builder/amazon/chroot/copy_files_test.go @@ -1,6 +1,7 @@ package chroot import ( + "fmt" "io/ioutil" "os" "testing" diff --git a/builder/amazon/chroot/step_chroot_provision.go b/builder/amazon/chroot/step_chroot_provision.go index 59a95f45c..481f58671 100644 --- a/builder/amazon/chroot/step_chroot_provision.go +++ b/builder/amazon/chroot/step_chroot_provision.go @@ -4,7 +4,6 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" - "os/exec" ) // StepChrootProvision provisions the instance within a chroot. @@ -19,29 +18,15 @@ type WrappedCommandTemplate struct { func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction { hook := state.Get("hook").(packer.Hook) mountPath := state.Get("mount_path").(string) - config := state.Get("config").(*Config) ui := state.Get("ui").(packer.Ui) - chrootCmd := func(command string) *exec.Cmd { - return ChrootCommand(mountPath, command) - } - wrappedCommand := func(command string) *exec.Cmd { - wrapped, err := config.tpl.Process(config.CommandWrapper, &WrappedCommandTemplate{ - Command: command, - }) - if err != nil { - ui.Error(err.Error()) - } - return ShellCommand(wrapped) - } - - state.Put("chrootCmd", chrootCmd) - state.Put("wrappedCommand", wrappedCommand) + wrappedCommand := state.Get("wrappedCommand").(Command) + chrootCmd := state.Get("chrootCmd").(Command) // Create our communicator comm := &Communicator{ Chroot: mountPath, ChrootCmd: chrootCmd, - wrappedCommand: wrappedCommand, + WrappedCommand: wrappedCommand, } // Provision diff --git a/builder/amazon/chroot/step_mount_device.go b/builder/amazon/chroot/step_mount_device.go index 4cce60f10..e8a987d1d 100644 --- a/builder/amazon/chroot/step_mount_device.go +++ b/builder/amazon/chroot/step_mount_device.go @@ -7,6 +7,7 @@ import ( "github.com/mitchellh/packer/packer" "log" "os" + "os/exec" "path/filepath" ) @@ -56,10 +57,15 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + chrootCmd := func(command string) *exec.Cmd { + return ChrootCommand(mountPath, command) + } + state.Put("chrootCmd", Command(chrootCmd)) + ui.Say("Mounting the root device...") stderr := new(bytes.Buffer) mountCommand := fmt.Sprintf("mount %s %s", device, mountPath) - wrappedCommand := state.Get("wrappedCommand").(*Command) + wrappedCommand := state.Get("wrappedCommand").(Command) cmd := wrappedCommand(mountCommand) cmd.Stderr = stderr if err := cmd.Run(); err != nil { diff --git a/builder/amazon/chroot/step_mount_extra.go b/builder/amazon/chroot/step_mount_extra.go index bbe958ec8..a5216e055 100644 --- a/builder/amazon/chroot/step_mount_extra.go +++ b/builder/amazon/chroot/step_mount_extra.go @@ -83,7 +83,7 @@ func (s *StepMountExtra) CleanupFunc(state multistep.StateBag) error { var path string lastIndex := len(s.mounts) - 1 path, s.mounts = s.mounts[lastIndex], s.mounts[:lastIndex] - unmountCommand := fmt.Sprintf("unmount %s", path) + unmountCommand := fmt.Sprintf("umount %s", path) stderr := new(bytes.Buffer) cmd := wrappedCommand(unmountCommand) From 831d5caa50653afb6db985dac50bdc37f326b430 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Fri, 27 Sep 2013 22:08:15 +0000 Subject: [PATCH 144/173] move wrapper definitions around. --- builder/amazon/chroot/builder.go | 6 +++++- builder/amazon/chroot/communicator.go | 4 ++-- builder/amazon/chroot/step_chroot_provision.go | 9 ++++----- builder/amazon/chroot/step_copy_files.go | 2 +- builder/amazon/chroot/step_mount_device.go | 6 ------ 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index a7f46a5f4..642f4925d 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -37,6 +37,10 @@ type Config struct { tpl *packer.ConfigTemplate } +type wrappedCommandTemplate struct { + Command string +} + type Builder struct { config Config runner multistep.Runner @@ -162,7 +166,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ec2conn := ec2.New(auth, region) wrappedCommand := func(command string) *exec.Cmd { - wrapped, err := b.config.tpl.Process(b.config.CommandWrapper, &WrappedCommandTemplate{ + wrapped, err := b.config.tpl.Process(b.config.CommandWrapper, &wrappedCommandTemplate{ Command: command, }) if err != nil { diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 7f52d9fdb..92ed0d25a 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -66,7 +66,7 @@ func (c *Communicator) Upload(dst string, r io.Reader) error { } defer os.Remove(tf.Name()) io.Copy(tf, r) - cpCmd := fmt.Sprintf("cp %s %s", dst, tf.Name()) + cpCmd := fmt.Sprintf("cp %s %s", tf.Name(), dst) return (*c.ChrootCmd(cpCmd)).Run() } @@ -90,7 +90,7 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error chrootDest := filepath.Join(c.Chroot, dst, path) log.Printf("Uploading to chroot dir: %s", dst) - cpCmd := fmt.Sprintf("cp %s %s", chrootDest, fullPath) + cpCmd := fmt.Sprintf("cp %s %s", fullPath, chrootDest) return c.ChrootCmd(cpCmd).Run() } diff --git a/builder/amazon/chroot/step_chroot_provision.go b/builder/amazon/chroot/step_chroot_provision.go index 481f58671..f4612c807 100644 --- a/builder/amazon/chroot/step_chroot_provision.go +++ b/builder/amazon/chroot/step_chroot_provision.go @@ -4,6 +4,7 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" + "os/exec" ) // StepChrootProvision provisions the instance within a chroot. @@ -11,16 +12,14 @@ type StepChrootProvision struct { mounts []string } -type WrappedCommandTemplate struct { - Command string -} - func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction { hook := state.Get("hook").(packer.Hook) mountPath := state.Get("mount_path").(string) ui := state.Get("ui").(packer.Ui) wrappedCommand := state.Get("wrappedCommand").(Command) - chrootCmd := state.Get("chrootCmd").(Command) + chrootCmd := func(command string) *exec.Cmd { + return ChrootCommand(mountPath, command) + } // Create our communicator comm := &Communicator{ diff --git a/builder/amazon/chroot/step_copy_files.go b/builder/amazon/chroot/step_copy_files.go index e22301369..c7be3bb66 100644 --- a/builder/amazon/chroot/step_copy_files.go +++ b/builder/amazon/chroot/step_copy_files.go @@ -31,7 +31,7 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction { chrootPath := filepath.Join(mountPath, path) log.Printf("Copying '%s' to '%s'", path, chrootPath) - cmd := fmt.Sprintf("cp %s %s", chrootPath, path) + cmd := fmt.Sprintf("cp %s %s", path, chrootPath) if err := wrappedCommand(cmd); err != nil { err := fmt.Errorf("Error copying file: %s", err) state.Put("error", err) diff --git a/builder/amazon/chroot/step_mount_device.go b/builder/amazon/chroot/step_mount_device.go index e8a987d1d..f9a823623 100644 --- a/builder/amazon/chroot/step_mount_device.go +++ b/builder/amazon/chroot/step_mount_device.go @@ -7,7 +7,6 @@ import ( "github.com/mitchellh/packer/packer" "log" "os" - "os/exec" "path/filepath" ) @@ -57,11 +56,6 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - chrootCmd := func(command string) *exec.Cmd { - return ChrootCommand(mountPath, command) - } - state.Put("chrootCmd", Command(chrootCmd)) - ui.Say("Mounting the root device...") stderr := new(bytes.Buffer) mountCommand := fmt.Sprintf("mount %s %s", device, mountPath) From a15f629f4fe62e228de7381689bc48773b733136 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Sat, 28 Sep 2013 01:10:33 +0000 Subject: [PATCH 145/173] WIP copying files. --- builder/amazon/chroot/communicator.go | 52 +++++++++++++----------- builder/amazon/chroot/copy_files.go | 11 ++--- builder/amazon/chroot/step_copy_files.go | 11 +++-- 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 92ed0d25a..37a9bbdf8 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -49,7 +49,7 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error { } log.Printf( - "Chroot executation ended with '%d': '%s'", + "Chroot executation exited with '%d': '%s'", exitStatus, cmd.Command) cmd.SetExited(exitStatus) }() @@ -67,35 +67,39 @@ func (c *Communicator) Upload(dst string, r io.Reader) error { defer os.Remove(tf.Name()) io.Copy(tf, r) cpCmd := fmt.Sprintf("cp %s %s", tf.Name(), dst) - return (*c.ChrootCmd(cpCmd)).Run() + return (c.WrappedCommand(cpCmd)).Run() } func (c *Communicator) UploadDir(dst string, src string, exclude []string) error { - walkFn := func(fullPath string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - path, err := filepath.Rel(src, fullPath) - if err != nil { - return err - } - - for _, e := range exclude { - if e == path { - log.Printf("Skipping excluded file: %s", path) - return nil + /* + walkFn := func(fullPath string, info os.FileInfo, err error) error { + if err != nil { + return err } + + path, err := filepath.Rel(src, fullPath) + if err != nil { + return err + } + + for _, e := range exclude { + if e == path { + log.Printf("Skipping excluded file: %s", path) + return nil + } + } + + chrootDest := filepath.Join(c.Chroot, dst, path) + log.Printf("Uploading dir %s to chroot dir: %s", src, dst) + cpCmd := fmt.Sprintf("cp %s %s", fullPath, chrootDest) + return c.WrappedCommand(cpCmd).Run() } + */ - chrootDest := filepath.Join(c.Chroot, dst, path) - log.Printf("Uploading to chroot dir: %s", dst) - cpCmd := fmt.Sprintf("cp %s %s", fullPath, chrootDest) - return c.ChrootCmd(cpCmd).Run() - } - - log.Printf("Uploading directory '%s' to '%s'", src, dst) - return filepath.Walk(src, walkFn) + chrootDest := filepath.Join(c.Chroot, dst) + log.Printf("Uploading directory '%s' to '%s'", src, chrootDest) + cpCmd := fmt.Sprintf("cp -R %s* %s", src, chrootDest) + return c.WrappedCommand(cpCmd).Run() } func (c *Communicator) Download(src string, w io.Writer) error { diff --git a/builder/amazon/chroot/copy_files.go b/builder/amazon/chroot/copy_files.go index 5af58baa5..b8807d095 100644 --- a/builder/amazon/chroot/copy_files.go +++ b/builder/amazon/chroot/copy_files.go @@ -7,12 +7,13 @@ import ( ) func ChrootCommand(chroot string, command string) *exec.Cmd { - chrootCommand := fmt.Sprintf("chroot %s %s", chroot, command) - return ShellCommand(chrootCommand) + cmd := fmt.Sprintf("sudo chroot %s", chroot) + return ShellCommand(cmd, command) } -func ShellCommand(command string) *exec.Cmd { - cmd := exec.Command("/bin/sh", "-c", command) - log.Printf("ShellCommand(%s) -> #%v", command, cmd.Args) +func ShellCommand(commands ...string) *exec.Cmd { + cmds := append([]string{"-c"}, commands...) + cmd := exec.Command("/bin/sh", cmds...) + log.Printf("ShellCommand: %s %v", cmd.Path, cmd.Args[1:]) return cmd } diff --git a/builder/amazon/chroot/step_copy_files.go b/builder/amazon/chroot/step_copy_files.go index c7be3bb66..3cb096d10 100644 --- a/builder/amazon/chroot/step_copy_files.go +++ b/builder/amazon/chroot/step_copy_files.go @@ -1,6 +1,7 @@ package chroot import ( + "bytes" "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" @@ -22,6 +23,7 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction { mountPath := state.Get("mount_path").(string) ui := state.Get("ui").(packer.Ui) wrappedCommand := state.Get("wrappedCommand").(Command) + stderr := new(bytes.Buffer) s.files = make([]string, 0, len(config.CopyFiles)) if len(config.CopyFiles) > 0 { @@ -31,9 +33,12 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction { chrootPath := filepath.Join(mountPath, path) log.Printf("Copying '%s' to '%s'", path, chrootPath) - cmd := fmt.Sprintf("cp %s %s", path, chrootPath) - if err := wrappedCommand(cmd); err != nil { - err := fmt.Errorf("Error copying file: %s", err) + cmd := wrappedCommand(fmt.Sprintf("cp %s %s", path, chrootPath)) + stderr.Reset() + cmd.Stderr = stderr + if err := cmd.Run(); err != nil { + err := fmt.Errorf( + "Error copying file: %s\nnStderr: %s", err, stderr.String()) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt From d6be5d851edccec1ef70f485adb8b07b392f2ee2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 28 Sep 2013 09:39:34 +0200 Subject: [PATCH 146/173] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60b1780d7..ee45c62d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ BUG FIXES: * builder/amazon-chroot: Fix errors with waitin for state change. [GH-459] * communicator/ssh: SCP uploads now work properly when directories contain symlinks. [GH-449] +* provisioner/chef-solo: Data bags and roles path are now properly + populated when set. [GH-470] ## 0.3.8 (September 22, 2013) From 5f19648268d80760a8fac6afde962a839f9c43e4 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Sun, 29 Sep 2013 08:04:57 +0000 Subject: [PATCH 147/173] comment, formatting. --- builder/amazon/chroot/builder.go | 7 ++++--- builder/amazon/chroot/communicator.go | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index 642f4925d..de3a840b1 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -166,9 +166,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ec2conn := ec2.New(auth, region) wrappedCommand := func(command string) *exec.Cmd { - wrapped, err := b.config.tpl.Process(b.config.CommandWrapper, &wrappedCommandTemplate{ - Command: command, - }) + wrapped, err := b.config.tpl.Process( + b.config.CommandWrapper, &wrappedCommandTemplate{ + Command: command, + }) if err != nil { ui.Error(err.Error()) } diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 37a9bbdf8..8e6bc90bf 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -96,6 +96,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 := fmt.Sprintf("cp -R %s* %s", src, chrootDest) From 26278a7c72cbde8b1e41b96ab312f44a6f38b4e2 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Sun, 29 Sep 2013 11:01:30 +0200 Subject: [PATCH 148/173] builder/digitalocean: error message key is "message" not "error_message" --- builder/digitalocean/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/digitalocean/api.go b/builder/digitalocean/api.go index 5abc12faa..7e09595cc 100644 --- a/builder/digitalocean/api.go +++ b/builder/digitalocean/api.go @@ -227,7 +227,7 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin } if status == "ERROR" { - status = decodedResponse["error_message"].(string) + status = decodedResponse["message"].(string) } lastErr = errors.New(fmt.Sprintf("Received error from DigitalOcean (%d): %s", From b00e395516cdd7a50cf09eaab10bb63d6363a296 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 29 Sep 2013 15:11:32 -0400 Subject: [PATCH 149/173] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee45c62d9..bd8d067cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ BUG FIXES: * core: default user variable values don't need to be strings. [GH-456] * builder/amazon-chroot: Fix errors with waitin for state change. [GH-459] +* builder/digitalocean: Use proper error message JSON key (DO API change). * communicator/ssh: SCP uploads now work properly when directories contain symlinks. [GH-449] * provisioner/chef-solo: Data bags and roles path are now properly From dfe2adf1ed06eecfefbc6d5f897d382a867bd560 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 30 Sep 2013 09:02:18 -0700 Subject: [PATCH 150/173] Update CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd8d067cf..11ba3ff50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## 0.3.9 (unreleased) +FEATURES: + +* The Amazon chroot builder is now able to run without any `sudo` privileges + by using the "command_wrapper" configuration. [GH-430] + BUG FIXES: * core: default user variable values don't need to be strings. [GH-456] From 535888d9d87edc43758df78dc6ba3ffdbc1ccbb5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 30 Sep 2013 09:32:20 -0700 Subject: [PATCH 151/173] builder/amazon/chroot: alphebatize the configs /cc @mwhooker --- builder/amazon/chroot/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index de3a840b1..2abd6edfe 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -28,9 +28,9 @@ type Config struct { awscommon.AMIConfig `mapstructure:",squash"` ChrootMounts [][]string `mapstructure:"chroot_mounts"` + CommandWrapper string `mapstructure:"command_wrapper"` CopyFiles []string `mapstructure:"copy_files"` DevicePath string `mapstructure:"device_path"` - CommandWrapper string `mapstructure:"command_wrapper"` MountPath string `mapstructure:"mount_path"` SourceAmi string `mapstructure:"source_ami"` From b554a0dd8686a645e08b3cd0308be83e95a29ebe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 30 Sep 2013 09:33:57 -0700 Subject: [PATCH 152/173] builder/amazon/chroot: CommandWrapper /cc @mwhooker - I changed the interface up a bit to return an error, since things should return errors in Go (the ui.Error bit was kind of ghetto because it had no way to bubble that error up except through the UI). Using this, I made it so that the communicator uses both a CommandWrapper and ShellCommand with chroot so that the chroot commannd is also wrapped (it wasn't before). I think the functionality of all this is the same but I'd love if you could look it over and make sure. --- builder/amazon/chroot/builder.go | 11 +--- builder/amazon/chroot/command.go | 15 +++++ builder/amazon/chroot/communicator.go | 59 ++++++++----------- builder/amazon/chroot/copy_files.go | 18 ------ .../amazon/chroot/step_chroot_provision.go | 11 +--- builder/amazon/chroot/step_copy_files.go | 21 +++++-- builder/amazon/chroot/step_mount_device.go | 26 +++++--- builder/amazon/chroot/step_mount_extra.go | 24 +++++--- 8 files changed, 97 insertions(+), 88 deletions(-) create mode 100644 builder/amazon/chroot/command.go diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index 2abd6edfe..00119b487 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -13,7 +13,6 @@ import ( "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" "log" - "os/exec" "runtime" ) @@ -165,15 +164,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ec2conn := ec2.New(auth, region) - wrappedCommand := func(command string) *exec.Cmd { - wrapped, err := b.config.tpl.Process( + wrappedCommand := func(command string) (string, error) { + return b.config.tpl.Process( b.config.CommandWrapper, &wrappedCommandTemplate{ Command: command, }) - if err != nil { - ui.Error(err.Error()) - } - return ShellCommand(wrapped) } // Setup the state bag and initial state for the steps @@ -182,7 +177,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe state.Put("ec2", ec2conn) state.Put("hook", hook) state.Put("ui", ui) - state.Put("wrappedCommand", Command(wrappedCommand)) + state.Put("wrappedCommand", CommandWrapper(wrappedCommand)) // Build the steps steps := []multistep.Step{ diff --git a/builder/amazon/chroot/command.go b/builder/amazon/chroot/command.go new file mode 100644 index 000000000..0ca55be67 --- /dev/null +++ b/builder/amazon/chroot/command.go @@ -0,0 +1,15 @@ +package chroot + +import ( + "os/exec" +) + +// CommandWrapper is a type that given a command, will possibly modify that +// command in-flight. This might return an error. +type CommandWrapper func(string) (string, error) + +// ShellCommand takes a command string and returns an *exec.Cmd to execute +// it within the context of a shell (/bin/sh). +func ShellCommand(command string) *exec.Cmd { + return exec.Command("/bin/sh", "-c", command) +} diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 8e6bc90bf..2540605b7 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -1,6 +1,6 @@ package chroot -// pf := func () { somefunc("a str", 1) } +// pf := func () { somefunc("a str", 1) } import ( "fmt" @@ -14,18 +14,21 @@ import ( "syscall" ) -type Command func(string) *exec.Cmd - // Communicator is a special communicator that works by executing // commands locally but within a chroot. type Communicator struct { - Chroot string - ChrootCmd Command - WrappedCommand Command + Chroot string + CmdWrapper CommandWrapper } func (c *Communicator) Start(cmd *packer.RemoteCmd) error { - localCmd := c.ChrootCmd(cmd.Command) + command, err := c.CmdWrapper( + fmt.Sprintf("sudo chroot %s '%s'", c.Chroot, cmd.Command)) + if err != nil { + return err + } + + localCmd := ShellCommand(command) localCmd.Stdin = cmd.Stdin localCmd.Stdout = cmd.Stdout localCmd.Stderr = cmd.Stderr @@ -66,41 +69,25 @@ func (c *Communicator) Upload(dst string, r io.Reader) error { } defer os.Remove(tf.Name()) io.Copy(tf, r) - cpCmd := fmt.Sprintf("cp %s %s", tf.Name(), dst) - return (c.WrappedCommand(cpCmd)).Run() + + cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp %s %s", tf.Name(), dst)) + if err != nil { + return err + } + + return ShellCommand(cpCmd).Run() } func (c *Communicator) UploadDir(dst string, src string, exclude []string) error { - /* - walkFn := func(fullPath string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - path, err := filepath.Rel(src, fullPath) - if err != nil { - return err - } - - for _, e := range exclude { - if e == path { - log.Printf("Skipping excluded file: %s", path) - return nil - } - } - - chrootDest := filepath.Join(c.Chroot, dst, path) - log.Printf("Uploading dir %s to chroot dir: %s", src, dst) - cpCmd := fmt.Sprintf("cp %s %s", fullPath, chrootDest) - return c.WrappedCommand(cpCmd).Run() - } - */ - // 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 := fmt.Sprintf("cp -R %s* %s", src, chrootDest) - return c.WrappedCommand(cpCmd).Run() + cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp -R %s* %s", src, chrootDest)) + if err != nil { + return err + } + + return ShellCommand(cpCmd).Run() } func (c *Communicator) Download(src string, w io.Writer) error { diff --git a/builder/amazon/chroot/copy_files.go b/builder/amazon/chroot/copy_files.go index b8807d095..f7358556a 100644 --- a/builder/amazon/chroot/copy_files.go +++ b/builder/amazon/chroot/copy_files.go @@ -1,19 +1 @@ package chroot - -import ( - "fmt" - "log" - "os/exec" -) - -func ChrootCommand(chroot string, command string) *exec.Cmd { - cmd := fmt.Sprintf("sudo chroot %s", chroot) - return ShellCommand(cmd, command) -} - -func ShellCommand(commands ...string) *exec.Cmd { - cmds := append([]string{"-c"}, commands...) - cmd := exec.Command("/bin/sh", cmds...) - log.Printf("ShellCommand: %s %v", cmd.Path, cmd.Args[1:]) - return cmd -} diff --git a/builder/amazon/chroot/step_chroot_provision.go b/builder/amazon/chroot/step_chroot_provision.go index f4612c807..09ad9b8c9 100644 --- a/builder/amazon/chroot/step_chroot_provision.go +++ b/builder/amazon/chroot/step_chroot_provision.go @@ -4,7 +4,6 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" - "os/exec" ) // StepChrootProvision provisions the instance within a chroot. @@ -16,16 +15,12 @@ func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction hook := state.Get("hook").(packer.Hook) mountPath := state.Get("mount_path").(string) ui := state.Get("ui").(packer.Ui) - wrappedCommand := state.Get("wrappedCommand").(Command) - chrootCmd := func(command string) *exec.Cmd { - return ChrootCommand(mountPath, command) - } + wrappedCommand := state.Get("wrappedCommand").(CommandWrapper) // Create our communicator comm := &Communicator{ - Chroot: mountPath, - ChrootCmd: chrootCmd, - WrappedCommand: wrappedCommand, + Chroot: mountPath, + CmdWrapper: wrappedCommand, } // Provision diff --git a/builder/amazon/chroot/step_copy_files.go b/builder/amazon/chroot/step_copy_files.go index 3cb096d10..70af624c8 100644 --- a/builder/amazon/chroot/step_copy_files.go +++ b/builder/amazon/chroot/step_copy_files.go @@ -22,7 +22,7 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) mountPath := state.Get("mount_path").(string) ui := state.Get("ui").(packer.Ui) - wrappedCommand := state.Get("wrappedCommand").(Command) + wrappedCommand := state.Get("wrappedCommand").(CommandWrapper) stderr := new(bytes.Buffer) s.files = make([]string, 0, len(config.CopyFiles)) @@ -33,8 +33,16 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction { chrootPath := filepath.Join(mountPath, path) log.Printf("Copying '%s' to '%s'", path, chrootPath) - cmd := wrappedCommand(fmt.Sprintf("cp %s %s", path, chrootPath)) + cmdText, err := wrappedCommand(fmt.Sprintf("cp %s %s", path, chrootPath)) + if err != nil { + err := fmt.Errorf("Error building copy command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + stderr.Reset() + cmd := ShellCommand(cmdText) cmd.Stderr = stderr if err := cmd.Run(); err != nil { err := fmt.Errorf( @@ -60,11 +68,16 @@ func (s *StepCopyFiles) Cleanup(state multistep.StateBag) { } func (s *StepCopyFiles) CleanupFunc(state multistep.StateBag) error { - wrappedCommand := state.Get("wrappedCommand").(Command) + wrappedCommand := state.Get("wrappedCommand").(CommandWrapper) if s.files != nil { for _, file := range s.files { log.Printf("Removing: %s", file) - localCmd := wrappedCommand(fmt.Sprintf("rm -f %s", file)) + localCmdText, err := wrappedCommand(fmt.Sprintf("rm -f %s", file)) + if err != nil { + return err + } + + localCmd := ShellCommand(localCmdText) if err := localCmd.Run(); err != nil { return err } diff --git a/builder/amazon/chroot/step_mount_device.go b/builder/amazon/chroot/step_mount_device.go index f9a823623..a6419774f 100644 --- a/builder/amazon/chroot/step_mount_device.go +++ b/builder/amazon/chroot/step_mount_device.go @@ -27,6 +27,7 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) ui := state.Get("ui").(packer.Ui) device := state.Get("device").(string) + wrappedCommand := state.Get("wrappedCommand").(CommandWrapper) mountPath, err := config.tpl.Process(config.MountPath, &mountPathData{ Device: filepath.Base(device), @@ -58,9 +59,16 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Mounting the root device...") stderr := new(bytes.Buffer) - mountCommand := fmt.Sprintf("mount %s %s", device, mountPath) - wrappedCommand := state.Get("wrappedCommand").(Command) - cmd := wrappedCommand(mountCommand) + mountCommand, err := wrappedCommand( + fmt.Sprintf("mount %s %s", device, mountPath)) + if err != nil { + err := fmt.Errorf("Error creating mount command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + cmd := ShellCommand(mountCommand) cmd.Stderr = stderr if err := cmd.Run(); err != nil { err := fmt.Errorf( @@ -91,11 +99,15 @@ func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error { } ui := state.Get("ui").(packer.Ui) - ui.Say("Unmounting the root device...") + wrappedCommand := state.Get("wrappedCommand").(CommandWrapper) - unmountCommand := fmt.Sprintf("umount %s", s.mountPath) - wrappedCommand := state.Get("wrappedCommand").(Command) - cmd := wrappedCommand(unmountCommand) + ui.Say("Unmounting the root device...") + unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", s.mountPath)) + if err != nil { + return fmt.Errorf("Error creating unmount command: %s", err) + } + + cmd := ShellCommand(unmountCommand) if err := cmd.Run(); err != nil { return fmt.Errorf("Error unmounting root device: %s", err) } diff --git a/builder/amazon/chroot/step_mount_extra.go b/builder/amazon/chroot/step_mount_extra.go index a5216e055..d589d6c74 100644 --- a/builder/amazon/chroot/step_mount_extra.go +++ b/builder/amazon/chroot/step_mount_extra.go @@ -20,7 +20,7 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) mountPath := state.Get("mount_path").(string) ui := state.Get("ui").(packer.Ui) - wrappedCommand := state.Get("wrappedCommand").(Command) + wrappedCommand := state.Get("wrappedCommand").(CommandWrapper) s.mounts = make([]string, 0, len(config.ChrootMounts)) @@ -42,12 +42,19 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction { ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2])) stderr := new(bytes.Buffer) - mountCommand := fmt.Sprintf( + mountCommand, err := wrappedCommand(fmt.Sprintf( "mount %s %s %s", flags, mountInfo[1], - innerPath) - cmd := wrappedCommand(mountCommand) + innerPath)) + if err != nil { + err := fmt.Errorf("Error creating mount command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + cmd := ShellCommand(mountCommand) cmd.Stderr = stderr if err := cmd.Run(); err != nil { err := fmt.Errorf( @@ -78,15 +85,18 @@ func (s *StepMountExtra) CleanupFunc(state multistep.StateBag) error { return nil } - wrappedCommand := state.Get("wrappedCommand").(Command) + wrappedCommand := state.Get("wrappedCommand").(CommandWrapper) for len(s.mounts) > 0 { var path string lastIndex := len(s.mounts) - 1 path, s.mounts = s.mounts[lastIndex], s.mounts[:lastIndex] - unmountCommand := fmt.Sprintf("umount %s", path) + unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", path)) + if err != nil { + return fmt.Errorf("Error creating unmount command: %s", err) + } stderr := new(bytes.Buffer) - cmd := wrappedCommand(unmountCommand) + cmd := ShellCommand(unmountCommand) cmd.Stderr = stderr if err := cmd.Run(); err != nil { return fmt.Errorf( From 38d3411f800d798c0879e735e50d607f8ca5262b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 30 Sep 2013 09:35:58 -0700 Subject: [PATCH 153/173] builder/amazon/chroot: remove unused comment --- builder/amazon/chroot/communicator.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 2540605b7..0cd636bf7 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -1,7 +1,5 @@ package chroot -// pf := func () { somefunc("a str", 1) } - import ( "fmt" "github.com/mitchellh/packer/packer" From 89d165aae7d3d56213c5f3d7678a9246896eca28 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 30 Sep 2013 11:39:24 -0700 Subject: [PATCH 154/173] provisioner/shell: add another UnixReader test for sanity --- provisioner/shell/unix_reader_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/provisioner/shell/unix_reader_test.go b/provisioner/shell/unix_reader_test.go index 8dedf6300..17e16ae39 100644 --- a/provisioner/shell/unix_reader_test.go +++ b/provisioner/shell/unix_reader_test.go @@ -31,3 +31,21 @@ func TestUnixReader(t *testing.T) { t.Fatalf("bad: %#v", result.String()) } } + +func TestUnixReader_unixOnly(t *testing.T) { + input := "one\ntwo\nthree\n" + expected := "one\ntwo\nthree\n" + + r := &UnixReader{ + Reader: bytes.NewReader([]byte(input)), + } + + result := new(bytes.Buffer) + if _, err := io.Copy(result, r); err != nil { + t.Fatalf("err: %s", err) + } + + if result.String() != expected { + t.Fatalf("bad: %#v", result.String()) + } +} From f017fd8af9459de0919c988d6dceb0ec68b9e208 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 30 Sep 2013 11:39:52 -0700 Subject: [PATCH 155/173] go fmt --- communicator/ssh/communicator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/communicator/ssh/communicator.go b/communicator/ssh/communicator.go index e2eea4b21..abdc46461 100644 --- a/communicator/ssh/communicator.go +++ b/communicator/ssh/communicator.go @@ -413,7 +413,7 @@ func scpUploadDir(root string, fs []os.FileInfo, w io.Writer, r *bufio.Reader) e // a file just works. If it is a directory, we need to know so we // treat it as such. isSymlinkToDir := false - if fi.Mode() & os.ModeSymlink == os.ModeSymlink { + if fi.Mode()&os.ModeSymlink == os.ModeSymlink { symPath, err := filepath.EvalSymlinks(realPath) if err != nil { return err From b3d76b79451547934e37dc1e347fbaa1aa1f07d9 Mon Sep 17 00:00:00 2001 From: "Jason A. Beranek" Date: Mon, 30 Sep 2013 23:54:54 -0500 Subject: [PATCH 156/173] provisioner/shell: fix provisioner to use UnixReader [GH-477] Packer was using the original file reader when uploading, instead of the UnixReader /cc @mitchellh --- provisioner/shell/provisioner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioner/shell/provisioner.go b/provisioner/shell/provisioner.go index 3cb75187d..0dcafa7a7 100644 --- a/provisioner/shell/provisioner.go +++ b/provisioner/shell/provisioner.go @@ -269,7 +269,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { r = &UnixReader{Reader: r} } - if err := comm.Upload(p.config.RemotePath, f); err != nil { + if err := comm.Upload(p.config.RemotePath, r); err != nil { return fmt.Errorf("Error uploading script: %s", err) } From 2e119b5137a6dfd4e9b25b801a9d5272e97a1442 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 30 Sep 2013 22:03:14 -0700 Subject: [PATCH 157/173] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11ba3ff50..91e8b144a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ BUG FIXES: contain symlinks. [GH-449] * provisioner/chef-solo: Data bags and roles path are now properly populated when set. [GH-470] +* provisioner/shell: Windows line endings are actually properly changed + to Unix line endings. [GH-477] ## 0.3.8 (September 22, 2013) From 0d1b936f69a164dd6356e3a323e25a50e56e6bad Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Tue, 1 Oct 2013 09:06:01 +0000 Subject: [PATCH 158/173] Fix UnixReader panic on empty lines. Added a test for the failure. Most of the scanner code looks like it's a modified version of bufio.ScanLines, so I changed it to use that but always add a line feed. --- provisioner/shell/unix_reader.go | 34 ++------------------------- provisioner/shell/unix_reader_test.go | 4 ++-- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/provisioner/shell/unix_reader.go b/provisioner/shell/unix_reader.go index 5745dd291..0a19c0692 100644 --- a/provisioner/shell/unix_reader.go +++ b/provisioner/shell/unix_reader.go @@ -2,7 +2,6 @@ package shell import ( "bufio" - "bytes" "io" "sync" ) @@ -54,35 +53,6 @@ func (r *UnixReader) Read(p []byte) (n int, err error) { // only returns unix-style lines. So even if the line is "one\r\n", the // token returned will be "one\n". func scanUnixLine(data []byte, atEOF bool) (advance int, token []byte, err error) { - if atEOF && len(data) == 0 { - return 0, nil, nil - } - - if i := bytes.IndexByte(data, '\n'); i >= 0 { - // We have a new-line terminated line. Return the line with the newline - return i + 1, dropCR(data[0 : i+1]), nil - } - - if atEOF { - // We have a final, non-terminated line - return len(data), dropCR(data), nil - } - - if data[len(data)-1] != '\r' { - // We have a normal line, just let it tokenize - return len(data), data, nil - } - - // We need more data - return 0, nil, nil -} - -func dropCR(data []byte) []byte { - if len(data) > 0 && data[len(data)-2] == '\r' { - // Trim off the last byte and replace it with a '\n' - data = data[0 : len(data)-1] - data[len(data)-1] = '\n' - } - - return data + advance, token, err = bufio.ScanLines(data, atEOF) + return advance, append(token, "\n"...), err } diff --git a/provisioner/shell/unix_reader_test.go b/provisioner/shell/unix_reader_test.go index 17e16ae39..b498489ec 100644 --- a/provisioner/shell/unix_reader_test.go +++ b/provisioner/shell/unix_reader_test.go @@ -33,8 +33,8 @@ func TestUnixReader(t *testing.T) { } func TestUnixReader_unixOnly(t *testing.T) { - input := "one\ntwo\nthree\n" - expected := "one\ntwo\nthree\n" + input := "one\n\ntwo\nthree\n" + expected := "one\n\ntwo\nthree\n" r := &UnixReader{ Reader: bytes.NewReader([]byte(input)), From 5c41eccfd548c9cf4621be4f1cb6d524473a41e4 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Mon, 30 Sep 2013 21:53:01 +0000 Subject: [PATCH 159/173] Fix the way we run commands in chroot. * Single quotes around chroot command cause it to fail. * The chroot command also gets passed through the CommandWrapper template, so having sudo would clash with however the user wants to run it. * Fix spelling mistake. --- builder/amazon/chroot/communicator.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go index 0cd636bf7..0da11996e 100644 --- a/builder/amazon/chroot/communicator.go +++ b/builder/amazon/chroot/communicator.go @@ -21,7 +21,7 @@ type Communicator struct { func (c *Communicator) Start(cmd *packer.RemoteCmd) error { command, err := c.CmdWrapper( - fmt.Sprintf("sudo chroot %s '%s'", c.Chroot, cmd.Command)) + fmt.Sprintf("chroot %s %s", c.Chroot, cmd.Command)) if err != nil { return err } @@ -50,7 +50,7 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error { } log.Printf( - "Chroot executation exited with '%d': '%s'", + "Chroot execution exited with '%d': '%s'", exitStatus, cmd.Command) cmd.SetExited(exitStatus) }() From bc8e5d2c4164fedc7aa0699b3bba423efc5e095c Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Oct 2013 11:20:36 -0400 Subject: [PATCH 160/173] add environments_path and chef_environment support to chef provisioner --- provisioner/chef-solo/provisioner.go | 64 +++++++++++++++++------ provisioner/chef-solo/provisioner_test.go | 38 ++++++++++++++ 2 files changed, 86 insertions(+), 16 deletions(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index a22b18e30..0e2cddaff 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -22,6 +22,8 @@ type Config struct { CookbookPaths []string `mapstructure:"cookbook_paths"` RolesPath string `mapstructure:"roles_path"` DataBagsPath string `mapstructure:"data_bags_path"` + EnvironmentsPath string `mapstructure:"environments_path"` + ChefEnvironment string `mapstructure:"chef_environment"` ExecuteCommand string `mapstructure:"execute_command"` InstallCommand string `mapstructure:"install_command"` RemoteCookbookPaths []string `mapstructure:"remote_cookbook_paths"` @@ -39,15 +41,19 @@ type Provisioner struct { } type ConfigTemplate struct { - CookbookPaths string - DataBagsPath string - RolesPath string + CookbookPaths string + DataBagsPath string + RolesPath string + EnvironmentsPath string + ChefEnvironment string // Templates don't support boolean statements until Go 1.2. In the // mean time, we do this. // TODO(mitchellh): Remove when Go 1.2 is released - HasDataBagsPath bool - HasRolesPath bool + HasDataBagsPath bool + HasRolesPath bool + HasEnvironmentsPath bool + HasChefEnvironment bool } type ExecuteTemplate struct { @@ -92,10 +98,12 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { errs := common.CheckUnusedConfig(md) templates := map[string]*string{ - "config_template": &p.config.ConfigTemplate, - "data_bags_path": &p.config.DataBagsPath, - "roles_path": &p.config.RolesPath, - "staging_dir": &p.config.StagingDir, + "config_template": &p.config.ConfigTemplate, + "data_bags_path": &p.config.DataBagsPath, + "roles_path": &p.config.RolesPath, + "staging_dir": &p.config.StagingDir, + "environments_path": &p.config.EnvironmentsPath, + "chef_environment": &p.config.ChefEnvironment, } for n, ptr := range templates { @@ -174,6 +182,15 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } } + if p.config.EnvironmentsPath != "" { + pFileInfo, err := os.Stat(p.config.EnvironmentsPath) + + if err != nil || !pFileInfo.IsDir() { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Bad environments path '%s': %s", p.config.EnvironmentsPath, err)) + } + } + // 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() @@ -226,7 +243,15 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { } } - configPath, err := p.createConfig(ui, comm, cookbookPaths, rolesPath, dataBagsPath) + environmentsPath := "" + if p.config.EnvironmentsPath != "" { + environmentsPath = fmt.Sprintf("%s/environments", p.config.StagingDir) + if err := p.uploadDirectory(ui, comm, environmentsPath, p.config.EnvironmentsPath); err != nil { + return fmt.Errorf("Error uploading environments: %s", err) + } + } + + configPath, err := p.createConfig(ui, comm, cookbookPaths, rolesPath, dataBagsPath, environmentsPath, p.config.ChefEnvironment) if err != nil { return fmt.Errorf("Error creating Chef config file: %s", err) } @@ -263,7 +288,7 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds return comm.UploadDir(dst, src, nil) } -func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, localCookbooks []string, rolesPath string, dataBagsPath string) (string, error) { +func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, localCookbooks []string, rolesPath string, dataBagsPath string, environmentsPath string, chefEnvironment string) (string, error) { ui.Message("Creating configuration file 'solo.rb'") cookbook_paths := make([]string, len(p.config.RemoteCookbookPaths)+len(localCookbooks)) @@ -294,11 +319,14 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local } configString, err := p.config.tpl.Process(tpl, &ConfigTemplate{ - CookbookPaths: strings.Join(cookbook_paths, ","), - RolesPath: rolesPath, - DataBagsPath: dataBagsPath, - HasRolesPath: rolesPath != "", - HasDataBagsPath: dataBagsPath != "", + CookbookPaths: strings.Join(cookbook_paths, ","), + RolesPath: rolesPath, + DataBagsPath: dataBagsPath, + EnvironmentsPath: environmentsPath, + HasRolesPath: rolesPath != "", + HasDataBagsPath: dataBagsPath != "", + HasEnvironmentsPath: environmentsPath != "", + ChefEnvironment: chefEnvironment, }) if err != nil { return "", err @@ -456,4 +484,8 @@ role_path "{{.RolesPath}}" {{if .HasDataBagsPath}} data_bag_path "{{.DataBagsPath}}" {{end}} +{{if .HasEnvironmentsPath}} +environments_path "{{.EnvironmentsPath}}" +chef_environment "{{.ChefEnvironment}}" +{{end}} ` diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go index c18df246a..4cd744c30 100644 --- a/provisioner/chef-solo/provisioner_test.go +++ b/provisioner/chef-solo/provisioner_test.go @@ -161,6 +161,44 @@ func TestProvisionerPrepare_rolesPath(t *testing.T) { } } +func TestProvisionerPrepare_environmentsPath(t *testing.T) { + var p Provisioner + + environmentsPath, err := ioutil.TempDir("", "environments") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(environmentsPath) + + config := testConfig() + config["environments_path"] = environmentsPath + + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if p.config.EnvironmentsPath != environmentsPath { + t.Fatalf("unexpected: %#v", p.config.EnvironmentsPath) + } +} + +func TestProvisionerPrepare_chefEnvironment(t *testing.T) { + var p Provisioner + + config := testConfig() + config["chef_environment"] = "some-env" + + err := p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if p.config.ChefEnvironment != "some-env" { + t.Fatalf("unexpected: %#v", p.config.ChefEnvironment) + } +} + func TestProvisionerPrepare_json(t *testing.T) { config := testConfig() config["json"] = map[string]interface{}{ From fd6415ddf5d6e11b2565c2e59ce7f0dae37b8405 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Oct 2013 11:21:16 -0400 Subject: [PATCH 161/173] formatting --- provisioner/chef-solo/provisioner.go | 44 ++++++++++++++-------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 0e2cddaff..c49c69625 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -41,19 +41,19 @@ type Provisioner struct { } type ConfigTemplate struct { - CookbookPaths string - DataBagsPath string - RolesPath string - EnvironmentsPath string - ChefEnvironment string + CookbookPaths string + DataBagsPath string + RolesPath string + EnvironmentsPath string + ChefEnvironment string // Templates don't support boolean statements until Go 1.2. In the // mean time, we do this. // TODO(mitchellh): Remove when Go 1.2 is released - HasDataBagsPath bool - HasRolesPath bool + HasDataBagsPath bool + HasRolesPath bool HasEnvironmentsPath bool - HasChefEnvironment bool + HasChefEnvironment bool } type ExecuteTemplate struct { @@ -98,12 +98,12 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { errs := common.CheckUnusedConfig(md) templates := map[string]*string{ - "config_template": &p.config.ConfigTemplate, - "data_bags_path": &p.config.DataBagsPath, - "roles_path": &p.config.RolesPath, - "staging_dir": &p.config.StagingDir, - "environments_path": &p.config.EnvironmentsPath, - "chef_environment": &p.config.ChefEnvironment, + "config_template": &p.config.ConfigTemplate, + "data_bags_path": &p.config.DataBagsPath, + "roles_path": &p.config.RolesPath, + "staging_dir": &p.config.StagingDir, + "environments_path": &p.config.EnvironmentsPath, + "chef_environment": &p.config.ChefEnvironment, } for n, ptr := range templates { @@ -319,14 +319,14 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local } configString, err := p.config.tpl.Process(tpl, &ConfigTemplate{ - CookbookPaths: strings.Join(cookbook_paths, ","), - RolesPath: rolesPath, - DataBagsPath: dataBagsPath, - EnvironmentsPath: environmentsPath, - HasRolesPath: rolesPath != "", - HasDataBagsPath: dataBagsPath != "", - HasEnvironmentsPath: environmentsPath != "", - ChefEnvironment: chefEnvironment, + CookbookPaths: strings.Join(cookbook_paths, ","), + RolesPath: rolesPath, + DataBagsPath: dataBagsPath, + EnvironmentsPath: environmentsPath, + HasRolesPath: rolesPath != "", + HasDataBagsPath: dataBagsPath != "", + HasEnvironmentsPath: environmentsPath != "", + ChefEnvironment: chefEnvironment, }) if err != nil { return "", err From d969abd730fa60bad83e77f62a6770b12390e5ee Mon Sep 17 00:00:00 2001 From: Tehmasp Chaudhri Date: Tue, 1 Oct 2013 19:45:11 -0600 Subject: [PATCH 162/173] Updated web site doc. for avail commands list --- website/source/docs/installation.html.markdown | 2 ++ website/source/intro/getting-started/setup.html.markdown | 2 ++ 2 files changed, 4 insertions(+) diff --git a/website/source/docs/installation.html.markdown b/website/source/docs/installation.html.markdown index 81474dcab..32fde782f 100644 --- a/website/source/docs/installation.html.markdown +++ b/website/source/docs/installation.html.markdown @@ -42,6 +42,8 @@ usage: packer [--version] [--help] [] Available commands are: build build image(s) from template + fix fixes templates from old versions of packer + inspect see components of a template validate check that a template is valid ``` diff --git a/website/source/intro/getting-started/setup.html.markdown b/website/source/intro/getting-started/setup.html.markdown index 6b624db81..276f1d9c8 100644 --- a/website/source/intro/getting-started/setup.html.markdown +++ b/website/source/intro/getting-started/setup.html.markdown @@ -45,6 +45,8 @@ usage: packer [--version] [--help] [] Available commands are: build build image(s) from template + fix fixes templates from old versions of packer + inspect see components of a template validate check that a template is valid ``` From 710db8ded04903f966d11b63e31d50fbddd425e0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 2 Oct 2013 08:27:38 -0700 Subject: [PATCH 163/173] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91e8b144a..cd911aa59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ FEATURES: * The Amazon chroot builder is now able to run without any `sudo` privileges by using the "command_wrapper" configuration. [GH-430] +* Chef provisioner supports environments. [GH-483] BUG FIXES: From 543a1a21e80a95f165b8afcf11642fffe18b9bb4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 2 Oct 2013 08:28:51 -0700 Subject: [PATCH 164/173] provisioner/chef-solo: alphabetize tests and such /cc @netshade --- provisioner/chef-solo/provisioner.go | 2 +- provisioner/chef-solo/provisioner_test.go | 56 +++++++++++------------ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index c49c69625..a089356e6 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -18,12 +18,12 @@ import ( type Config struct { common.PackerConfig `mapstructure:",squash"` + ChefEnvironment string `mapstructure:"chef_environment"` ConfigTemplate string `mapstructure:"config_template"` CookbookPaths []string `mapstructure:"cookbook_paths"` RolesPath string `mapstructure:"roles_path"` DataBagsPath string `mapstructure:"data_bags_path"` EnvironmentsPath string `mapstructure:"environments_path"` - ChefEnvironment string `mapstructure:"chef_environment"` ExecuteCommand string `mapstructure:"execute_command"` InstallCommand string `mapstructure:"install_command"` RemoteCookbookPaths []string `mapstructure:"remote_cookbook_paths"` diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go index 4cd744c30..46da3c465 100644 --- a/provisioner/chef-solo/provisioner_test.go +++ b/provisioner/chef-solo/provisioner_test.go @@ -19,6 +19,22 @@ func TestProvisioner_Impl(t *testing.T) { } } +func TestProvisionerPrepare_chefEnvironment(t *testing.T) { + var p Provisioner + + config := testConfig() + config["chef_environment"] = "some-env" + + err := p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if p.config.ChefEnvironment != "some-env" { + t.Fatalf("unexpected: %#v", p.config.ChefEnvironment) + } +} + func TestProvisionerPrepare_configTemplate(t *testing.T) { var err error var p Provisioner @@ -139,28 +155,6 @@ func TestProvisionerPrepare_dataBagsPath(t *testing.T) { } } -func TestProvisionerPrepare_rolesPath(t *testing.T) { - var p Provisioner - - rolesPath, err := ioutil.TempDir("", "roles") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(rolesPath) - - config := testConfig() - config["roles_path"] = rolesPath - - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - if p.config.RolesPath != rolesPath { - t.Fatalf("unexpected: %#v", p.config.RolesPath) - } -} - func TestProvisionerPrepare_environmentsPath(t *testing.T) { var p Provisioner @@ -183,19 +177,25 @@ func TestProvisionerPrepare_environmentsPath(t *testing.T) { } } -func TestProvisionerPrepare_chefEnvironment(t *testing.T) { +func TestProvisionerPrepare_rolesPath(t *testing.T) { var p Provisioner - config := testConfig() - config["chef_environment"] = "some-env" + rolesPath, err := ioutil.TempDir("", "roles") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(rolesPath) - err := p.Prepare(config) + config := testConfig() + config["roles_path"] = rolesPath + + err = p.Prepare(config) if err != nil { t.Fatalf("err: %s", err) } - if p.config.ChefEnvironment != "some-env" { - t.Fatalf("unexpected: %#v", p.config.ChefEnvironment) + if p.config.RolesPath != rolesPath { + t.Fatalf("unexpected: %#v", p.config.RolesPath) } } From 5dfe27b40f880242491f9d2968f5bb1628a93f21 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 2 Oct 2013 08:29:12 -0700 Subject: [PATCH 165/173] provisioner/chef-solo: unused var --- provisioner/chef-solo/provisioner.go | 1 - 1 file changed, 1 deletion(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index a089356e6..907678d90 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -53,7 +53,6 @@ type ConfigTemplate struct { HasDataBagsPath bool HasRolesPath bool HasEnvironmentsPath bool - HasChefEnvironment bool } type ExecuteTemplate struct { From c5fb53743a4411a1afe843647b898e1b715ee258 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 2 Oct 2013 08:34:29 -0700 Subject: [PATCH 166/173] v0.3.9 --- CHANGELOG.md | 2 +- packer/version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd911aa59..f6d144d0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.3.9 (unreleased) +## 0.3.9 (October 2, 2013) FEATURES: diff --git a/packer/version.go b/packer/version.go index 758985d14..940d4901d 100644 --- a/packer/version.go +++ b/packer/version.go @@ -15,7 +15,7 @@ const Version = "0.3.9" // 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 eb257e847eabd00d05ef7a134f8ed68d2c1f4e3f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 2 Oct 2013 10:56:04 -0700 Subject: [PATCH 167/173] 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 f6d144d0d..3dbfd4b1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.10 (unreleased) + + + ## 0.3.9 (October 2, 2013) FEATURES: diff --git a/packer/version.go b/packer/version.go index 940d4901d..55ff57d38 100644 --- a/packer/version.go +++ b/packer/version.go @@ -10,12 +10,12 @@ import ( var GitCommit string // The version of packer. -const Version = "0.3.9" +const Version = "0.3.10" // 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 7f0916b6468ced5426ecaf5dcd2041833f04ba99 Mon Sep 17 00:00:00 2001 From: Patrick Lucas Date: Wed, 2 Oct 2013 14:55:28 -0700 Subject: [PATCH 168/173] An SSH timeout should be treated as an error --- common/step_connect_ssh.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/step_connect_ssh.go b/common/step_connect_ssh.go index a8be11c17..39e32a3d8 100644 --- a/common/step_connect_ssh.go +++ b/common/step_connect_ssh.go @@ -72,7 +72,9 @@ WaitLoop: state.Put("communicator", comm) break WaitLoop case <-timeout: - ui.Error("Timeout waiting for SSH.") + err := fmt.Errorf("Timeout waiting for SSH.") + state.Put("error", err) + ui.Error(err.Error()) close(cancel) return multistep.ActionHalt case <-time.After(1 * time.Second): From a27300e4740cbbe8c3fb8eef5631a4f199d4fb37 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 2 Oct 2013 17:05:33 -0700 Subject: [PATCH 169/173] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dbfd4b1f..9f4460723 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## 0.3.10 (unreleased) +BUG FIXES: +* builder/all: timeout waiting for SSH connection is a failure. [GH-491] ## 0.3.9 (October 2, 2013) From c19fcafc01c74746cb0e74bcfc0a3d9364674b50 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 2 Oct 2013 17:11:42 -0700 Subject: [PATCH 170/173] builder/virtualbox: error if version can't be detected [GH-488] --- CHANGELOG.md | 1 + builder/virtualbox/driver.go | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f4460723..047716f20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ BUG FIXES: * builder/all: timeout waiting for SSH connection is a failure. [GH-491] +* builder/virtualbox: error if VirtualBox version cant be detected. [GH-488] ## 0.3.9 (October 2, 2013) diff --git a/builder/virtualbox/driver.go b/builder/virtualbox/driver.go index ebd775a45..86a1a3905 100644 --- a/builder/virtualbox/driver.go +++ b/builder/virtualbox/driver.go @@ -135,9 +135,10 @@ func (d *VBox42Driver) Version() (string, error) { versionOutput := strings.TrimSpace(stdout.String()) log.Printf("VBoxManage --version output: %s", versionOutput) + versionRe := regexp.MustCompile("[^.0-9]") matches := versionRe.Split(versionOutput, 2) - if len(matches) == 0 { + if len(matches) == 0 || matches[0] == "" { return "", fmt.Errorf("No version found: %s", versionOutput) } From 8821ef4defc89c2fa7a1f7fd715fda6df0bd185f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 2 Oct 2013 17:13:22 -0700 Subject: [PATCH 171/173] builder/virtualbox: detect vboxdrv issues [GH-488] --- CHANGELOG.md | 1 + builder/virtualbox/driver.go | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 047716f20..4cc95e4fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ BUG FIXES: * builder/all: timeout waiting for SSH connection is a failure. [GH-491] * builder/virtualbox: error if VirtualBox version cant be detected. [GH-488] +* builder/virtualbox: detect if vboxdrv isn't properly setup. [GH-488] ## 0.3.9 (October 2, 2013) diff --git a/builder/virtualbox/driver.go b/builder/virtualbox/driver.go index 86a1a3905..b4a5392da 100644 --- a/builder/virtualbox/driver.go +++ b/builder/virtualbox/driver.go @@ -136,6 +136,13 @@ func (d *VBox42Driver) Version() (string, error) { versionOutput := strings.TrimSpace(stdout.String()) log.Printf("VBoxManage --version output: %s", versionOutput) + // If the "--version" output contains vboxdrv, then this is indicative + // of problems with the VirtualBox setup and we shouldn't really continue, + // whether or not we can read the version. + if strings.Contains(versionOutput, "vboxdrv") { + 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] == "" { From 4067bab3d415e2417a959ff1357f8ab0717aeb2a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 4 Oct 2013 10:44:04 -0700 Subject: [PATCH 172/173] builder/digitalocean: don't panic if error contains no message [GH-492] --- CHANGELOG.md | 2 ++ builder/digitalocean/api.go | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cc95e4fd..cf62f1129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ BUG FIXES: * builder/all: timeout waiting for SSH connection is a failure. [GH-491] +* builder/digitalocean: don't panic if erroneous API response doesn't + contain error message. [GH-492] * builder/virtualbox: error if VirtualBox version cant be detected. [GH-488] * builder/virtualbox: detect if vboxdrv isn't properly setup. [GH-488] diff --git a/builder/digitalocean/api.go b/builder/digitalocean/api.go index 7e09595cc..b22c1bb4d 100644 --- a/builder/digitalocean/api.go +++ b/builder/digitalocean/api.go @@ -227,7 +227,13 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin } if status == "ERROR" { - status = decodedResponse["message"].(string) + statusRaw, ok := decodedResponse["message"] + if ok { + status = statusRaw.(string) + } else { + status = fmt.Sprintf( + "Unknown error. Full response body: %s", body) + } } lastErr = errors.New(fmt.Sprintf("Received error from DigitalOcean (%d): %s", From ff970483c472b1a38709ef6f7e23b59097364ea5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 4 Oct 2013 10:47:28 -0700 Subject: [PATCH 173/173] provisioner/shell: add more tests for UnixReader --- provisioner/shell/unix_reader_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/provisioner/shell/unix_reader_test.go b/provisioner/shell/unix_reader_test.go index b498489ec..4f39eb00f 100644 --- a/provisioner/shell/unix_reader_test.go +++ b/provisioner/shell/unix_reader_test.go @@ -15,8 +15,8 @@ func TestUnixReader_impl(t *testing.T) { } func TestUnixReader(t *testing.T) { - input := "one\r\ntwo\nthree\r\n" - expected := "one\ntwo\nthree\n" + input := "one\r\ntwo\n\r\nthree\r\n" + expected := "one\ntwo\n\nthree\n" r := &UnixReader{ Reader: bytes.NewReader([]byte(input)), @@ -33,8 +33,8 @@ func TestUnixReader(t *testing.T) { } func TestUnixReader_unixOnly(t *testing.T) { - input := "one\n\ntwo\nthree\n" - expected := "one\n\ntwo\nthree\n" + input := "\none\n\ntwo\nthree\n\n" + expected := "\none\n\ntwo\nthree\n\n" r := &UnixReader{ Reader: bytes.NewReader([]byte(input)),