From 616b41e58f1acfd445851543634ac61bb7a6b6ae Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Fri, 23 Feb 2018 13:26:31 -0800 Subject: [PATCH 01/22] deduplicate the nearly identical communicators for the shell-local provisioner and post-processor, moving single communicator into a new common/shell-local module --- builder/amazon/chroot/run_local_commands.go | 7 ++- .../shell-local/communicator.go | 38 ++++++++--- .../shell-local/communicator_test.go | 2 +- post-processor/shell-local/communicator.go | 63 ------------------- .../shell-local/communicator_test.go | 43 ------------- post-processor/shell-local/post-processor.go | 6 +- provisioner/shell-local/provisioner.go | 25 +------- 7 files changed, 41 insertions(+), 143 deletions(-) rename {provisioner => common}/shell-local/communicator.go (71%) rename {provisioner => common}/shell-local/communicator_test.go (97%) delete mode 100644 post-processor/shell-local/communicator.go delete mode 100644 post-processor/shell-local/communicator_test.go diff --git a/builder/amazon/chroot/run_local_commands.go b/builder/amazon/chroot/run_local_commands.go index 024a208f8..154d37a4f 100644 --- a/builder/amazon/chroot/run_local_commands.go +++ b/builder/amazon/chroot/run_local_commands.go @@ -3,8 +3,8 @@ package chroot import ( "fmt" + sl "github.com/hashicorp/packer/common/shell-local" "github.com/hashicorp/packer/packer" - "github.com/hashicorp/packer/post-processor/shell-local" "github.com/hashicorp/packer/template/interpolate" ) @@ -21,7 +21,10 @@ func RunLocalCommands(commands []string, wrappedCommand CommandWrapper, ctx inte } ui.Say(fmt.Sprintf("Executing command: %s", command)) - comm := &shell_local.Communicator{} + comm := &sl.Communicator{ + Ctx: ctx, + ExecuteCommand: []string{""}, + } cmd := &packer.RemoteCmd{Command: command} if err := cmd.StartWithUi(comm, ui); err != nil { return fmt.Errorf("Error executing command: %s", err) diff --git a/provisioner/shell-local/communicator.go b/common/shell-local/communicator.go similarity index 71% rename from provisioner/shell-local/communicator.go rename to common/shell-local/communicator.go index 2afbe1028..dc84b575a 100644 --- a/provisioner/shell-local/communicator.go +++ b/common/shell-local/communicator.go @@ -1,10 +1,11 @@ -package shell +package shell_local import ( "fmt" "io" "os" "os/exec" + "runtime" "syscall" "github.com/hashicorp/packer/packer" @@ -17,17 +18,34 @@ type Communicator struct { } func (c *Communicator) Start(cmd *packer.RemoteCmd) error { - // Render the template so that we know how to execute the command - c.Ctx.Data = &ExecuteCommandTemplate{ - Command: cmd.Command, - } - for i, field := range c.ExecuteCommand { - command, err := interpolate.Render(field, &c.Ctx) - if err != nil { - return fmt.Errorf("Error processing command: %s", err) + if len(c.ExecuteCommand) == 0 { + // Get default Execute Command + if runtime.GOOS == "windows" { + c.ExecuteCommand = []string{ + "cmd", + "/C", + "{{.Command}}", + } + } else { + c.ExecuteCommand = []string{ + "/bin/sh", + "-c", + "{{.Command}}", + } } + } else { + // Render the template so that we know how to execute the command + c.Ctx.Data = &ExecuteCommandTemplate{ + Command: cmd.Command, + } + for i, field := range c.ExecuteCommand { + command, err := interpolate.Render(field, &c.Ctx) + if err != nil { + return fmt.Errorf("Error processing command: %s", err) + } - c.ExecuteCommand[i] = command + c.ExecuteCommand[i] = command + } } // Build the local command to execute diff --git a/provisioner/shell-local/communicator_test.go b/common/shell-local/communicator_test.go similarity index 97% rename from provisioner/shell-local/communicator_test.go rename to common/shell-local/communicator_test.go index 8ebd4fa60..903ab154d 100644 --- a/provisioner/shell-local/communicator_test.go +++ b/common/shell-local/communicator_test.go @@ -1,4 +1,4 @@ -package shell +package shell_local import ( "bytes" diff --git a/post-processor/shell-local/communicator.go b/post-processor/shell-local/communicator.go deleted file mode 100644 index b0bfb008f..000000000 --- a/post-processor/shell-local/communicator.go +++ /dev/null @@ -1,63 +0,0 @@ -package shell_local - -import ( - "fmt" - "io" - "os" - "os/exec" - "syscall" - - "github.com/hashicorp/packer/packer" -) - -type Communicator struct{} - -func (c *Communicator) Start(cmd *packer.RemoteCmd) error { - localCmd := exec.Command("sh", "-c", cmd.Command) - localCmd.Stdin = cmd.Stdin - localCmd.Stdout = cmd.Stdout - localCmd.Stderr = cmd.Stderr - - // Start it. If it doesn't work, then error right away. - if err := localCmd.Start(); err != nil { - return err - } - - // We've started successfully. Start a goroutine to wait for - // it to complete and track exit status. - go func() { - var exitStatus int - err := localCmd.Wait() - if err != nil { - if exitErr, ok := err.(*exec.ExitError); ok { - exitStatus = 1 - - // There is no process-independent way to get the REAL - // exit status so we just try to go deeper. - if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { - exitStatus = status.ExitStatus() - } - } - } - - cmd.SetExited(exitStatus) - }() - - return nil -} - -func (c *Communicator) Upload(string, io.Reader, *os.FileInfo) error { - return fmt.Errorf("upload not supported") -} - -func (c *Communicator) UploadDir(string, string, []string) error { - return fmt.Errorf("uploadDir not supported") -} - -func (c *Communicator) Download(string, io.Writer) error { - return fmt.Errorf("download not supported") -} - -func (c *Communicator) DownloadDir(src string, dst string, exclude []string) error { - return fmt.Errorf("downloadDir not supported") -} diff --git a/post-processor/shell-local/communicator_test.go b/post-processor/shell-local/communicator_test.go deleted file mode 100644 index 025deec54..000000000 --- a/post-processor/shell-local/communicator_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package shell_local - -import ( - "bytes" - "runtime" - "strings" - "testing" - - "github.com/hashicorp/packer/packer" -) - -func TestCommunicator_impl(t *testing.T) { - var _ packer.Communicator = new(Communicator) -} - -func TestCommunicator(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("windows not supported for this test") - return - } - - c := &Communicator{} - - var buf bytes.Buffer - cmd := &packer.RemoteCmd{ - Command: "/bin/echo foo", - Stdout: &buf, - } - - if err := c.Start(cmd); err != nil { - t.Fatalf("err: %s", err) - } - - cmd.Wait() - - if cmd.ExitStatus != 0 { - t.Fatalf("err bad exit status: %d", cmd.ExitStatus) - } - - if strings.TrimSpace(buf.String()) != "foo" { - t.Fatalf("bad: %s", buf.String()) - } -} diff --git a/post-processor/shell-local/post-processor.go b/post-processor/shell-local/post-processor.go index c2bd2d5c0..d77086177 100644 --- a/post-processor/shell-local/post-processor.go +++ b/post-processor/shell-local/post-processor.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/hashicorp/packer/common" + sl "github.com/hashicorp/packer/common/shell-local" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -178,7 +179,10 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac ui.Say(fmt.Sprintf("Post processing with local shell script: %s", script)) - comm := &Communicator{} + comm := &sl.Communicator{ + Ctx: p.config.ctx, + ExecuteCommand: []string{p.config.ExecuteCommand}, + } cmd := &packer.RemoteCmd{Command: command} diff --git a/provisioner/shell-local/provisioner.go b/provisioner/shell-local/provisioner.go index 3f8222c19..ecd59fa98 100644 --- a/provisioner/shell-local/provisioner.go +++ b/provisioner/shell-local/provisioner.go @@ -3,9 +3,9 @@ package shell import ( "errors" "fmt" - "runtime" "github.com/hashicorp/packer/common" + sl "github.com/hashicorp/packer/common/shell-local" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -41,33 +41,12 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return err } - if len(p.config.ExecuteCommand) == 0 { - if runtime.GOOS == "windows" { - p.config.ExecuteCommand = []string{ - "cmd", - "/C", - "{{.Command}}", - } - } else { - p.config.ExecuteCommand = []string{ - "/bin/sh", - "-c", - "{{.Command}}", - } - } - } - var errs *packer.MultiError if p.config.Command == "" { errs = packer.MultiErrorAppend(errs, errors.New("command must be specified")) } - if len(p.config.ExecuteCommand) == 0 { - errs = packer.MultiErrorAppend(errs, - errors.New("execute_command must not be empty")) - } - if errs != nil && len(errs.Errors) > 0 { return errs } @@ -77,7 +56,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { func (p *Provisioner) Provision(ui packer.Ui, _ packer.Communicator) error { // Make another communicator for local - comm := &Communicator{ + comm := &sl.Communicator{ Ctx: p.config.ctx, ExecuteCommand: p.config.ExecuteCommand, } From 926327bebadf68119089074ccd3d0d72033de70b Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Tue, 27 Feb 2018 12:50:42 -0800 Subject: [PATCH 02/22] deduplicate all validation and interpolation of the shell-local config, sharing options between shell-local provisioner and post-processor. Maintain backwards compatibility with shell-local provisioner. --- common/shell-local/config.go | 154 +++++++++++++++++++ post-processor/shell-local/post-processor.go | 112 +------------- provisioner/shell-local/provisioner.go | 42 +---- 3 files changed, 166 insertions(+), 142 deletions(-) create mode 100644 common/shell-local/config.go diff --git a/common/shell-local/config.go b/common/shell-local/config.go new file mode 100644 index 000000000..b54f8d713 --- /dev/null +++ b/common/shell-local/config.go @@ -0,0 +1,154 @@ +package shell_local + +import ( + "errors" + "fmt" + "os" + "runtime" + "strings" + + "github.com/hashicorp/packer/common" + configHelper "github.com/hashicorp/packer/helper/config" + "github.com/hashicorp/packer/packer" + "github.com/hashicorp/packer/template/interpolate" +) + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + // ** DEPRECATED: USE INLINE INSTEAD ** + // ** Only Present for backwards compatibiltiy ** + // Command is the command to execute + Command string + + // An inline script to execute. Multiple strings are all executed + // in the context of a single shell. + Inline []string + + // The shebang value used when running inline scripts. + InlineShebang string `mapstructure:"inline_shebang"` + + // The local path of the shell script to upload and execute. + Script string + + // An array of multiple scripts to run. + Scripts []string + + // An array of environment variables that will be injected before + // your command(s) are executed. + Vars []string `mapstructure:"environment_vars"` + // End dedupe with postprocessor + + // The command used to execute the script. The '{{ .Path }}' variable + // should be used to specify where the script goes, {{ .Vars }} + // can be used to inject the environment_vars into the environment. + ExecuteCommand []string `mapstructure:"execute_command"` + + Ctx interpolate.Context +} + +func Decode(config *Config, raws ...interface{}) error { + err := configHelper.Decode(&config, &configHelper.DecodeOpts{ + Interpolate: true, + InterpolateContext: &config.Ctx, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{ + "execute_command", + }, + }, + }, raws...) + if err != nil { + return err + } + + return Validate(config) +} + +func Validate(config *Config) error { + var errs *packer.MultiError + + if runtime.GOOS == "windows" { + if config.InlineShebang == "" { + config.InlineShebang = "" + } + if len(config.ExecuteCommand) == 0 { + config.ExecuteCommand = []string{`{{.Vars}} "{{.Script}}"`} + } + } else { + if config.InlineShebang == "" { + // TODO: verify that provisioner defaulted to this as well + config.InlineShebang = "/bin/sh -e" + } + if len(config.ExecuteCommand) == 0 { + config.ExecuteCommand = []string{`chmod +x "{{.Script}}"; {{.Vars}} "{{.Script}}"`} + } + } + + // Clean up input + if config.Inline != nil && len(config.Inline) == 0 { + config.Inline = nil + } + + if config.Scripts == nil { + config.Scripts = make([]string, 0) + } + + if config.Vars == nil { + config.Vars = make([]string, 0) + } + + // Verify that the user has given us a command to run + if config.Command != "" && len(config.Inline) == 0 && + len(config.Scripts) == 0 && config.Script == "" { + errs = packer.MultiErrorAppend(errs, + errors.New("Command, Inline, Script and Scripts options cannot all be empty.")) + } + + if config.Command != "" { + // Backwards Compatibility: Before v1.2.2, the shell-local + // provisioner only allowed a single Command, and to run + // multiple commands you needed to run several provisioners in a + // row, one for each command. In deduplicating the post-processor and + // provisioner code, we've changed this to allow an array of scripts or + // inline commands just like in the post-processor. This conditional + // grandfathers in the "Command" option, allowing the original usage to + // continue to work. + config.Inline = append(config.Inline, config.Command) + } + + if config.Script != "" && len(config.Scripts) > 0 { + errs = packer.MultiErrorAppend(errs, + errors.New("Only one of script or scripts can be specified.")) + } + + if config.Script != "" { + config.Scripts = []string{config.Script} + } + + if len(config.Scripts) > 0 && config.Inline != nil { + errs = packer.MultiErrorAppend(errs, + errors.New("You may specify either a script file(s) or an inline script(s), but not both.")) + } + + for _, path := range config.Scripts { + if _, err := os.Stat(path); err != nil { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("Bad script '%s': %s", path, err)) + } + } + + // Do a check for bad environment variables, such as '=foo', 'foobar' + for _, kv := range config.Vars { + vs := strings.SplitN(kv, "=", 2) + if len(vs) != 2 || vs[0] == "" { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("Environment variable not in format 'key=value': %s", kv)) + } + } + + if errs != nil && len(errs.Errors) > 0 { + return errs + } + + return nil +} diff --git a/post-processor/shell-local/post-processor.go b/post-processor/shell-local/post-processor.go index d77086177..818a8b44e 100644 --- a/post-processor/shell-local/post-processor.go +++ b/post-processor/shell-local/post-processor.go @@ -2,7 +2,6 @@ package shell_local import ( "bufio" - "errors" "fmt" "io/ioutil" "log" @@ -10,43 +9,13 @@ import ( "sort" "strings" - "github.com/hashicorp/packer/common" sl "github.com/hashicorp/packer/common/shell-local" - "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" ) -type Config struct { - common.PackerConfig `mapstructure:",squash"` - - // An inline script to execute. Multiple strings are all executed - // in the context of a single shell. - Inline []string - - // The shebang value used when running inline scripts. - InlineShebang string `mapstructure:"inline_shebang"` - - // The local path of the shell script to upload and execute. - Script string - - // An array of multiple scripts to run. - Scripts []string - - // An array of environment variables that will be injected before - // your command(s) are executed. - Vars []string `mapstructure:"environment_vars"` - - // The command used to execute the script. The '{{ .Path }}' variable - // should be used to specify where the script goes, {{ .Vars }} - // can be used to inject the environment_vars into the environment. - ExecuteCommand string `mapstructure:"execute_command"` - - ctx interpolate.Context -} - type PostProcessor struct { - config Config + config sl.Config } type ExecuteCommandTemplate struct { @@ -55,78 +24,12 @@ type ExecuteCommandTemplate struct { } func (p *PostProcessor) Configure(raws ...interface{}) error { - err := config.Decode(&p.config, &config.DecodeOpts{ - Interpolate: true, - InterpolateContext: &p.config.ctx, - InterpolateFilter: &interpolate.RenderFilter{ - Exclude: []string{ - "execute_command", - }, - }, - }, raws...) + err := sl.Decode(&p.config, raws) if err != nil { return err } - if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `chmod +x "{{.Script}}"; {{.Vars}} "{{.Script}}"` - } - - if p.config.Inline != nil && len(p.config.Inline) == 0 { - p.config.Inline = nil - } - - if p.config.InlineShebang == "" { - p.config.InlineShebang = "/bin/sh -e" - } - - if p.config.Scripts == nil { - p.config.Scripts = make([]string, 0) - } - - if p.config.Vars == nil { - p.config.Vars = make([]string, 0) - } - - var errs *packer.MultiError - if p.config.Script != "" && len(p.config.Scripts) > 0 { - errs = packer.MultiErrorAppend(errs, - errors.New("Only one of script or scripts can be specified.")) - } - - if p.config.Script != "" { - p.config.Scripts = []string{p.config.Script} - } - - if len(p.config.Scripts) == 0 && p.config.Inline == nil { - errs = packer.MultiErrorAppend(errs, - errors.New("Either a script file or inline script must be specified.")) - } else if len(p.config.Scripts) > 0 && p.config.Inline != nil { - errs = packer.MultiErrorAppend(errs, - errors.New("Only a script file or an inline script can be specified, not both.")) - } - - for _, path := range p.config.Scripts { - if _, err := os.Stat(path); err != nil { - errs = packer.MultiErrorAppend(errs, - fmt.Errorf("Bad script '%s': %s", path, err)) - } - } - - // Do a check for bad environment variables, such as '=foo', 'foobar' - for _, kv := range p.config.Vars { - vs := strings.SplitN(kv, "=", 2) - if len(vs) != 2 || vs[0] == "" { - errs = packer.MultiErrorAppend(errs, - fmt.Errorf("Environment variable not in format 'key=value': %s", kv)) - } - } - - if errs != nil && len(errs.Errors) > 0 { - return errs - } - - return nil + return sl.Validate(&p.config) } func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { @@ -167,12 +70,13 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac for _, script := range scripts { - p.config.ctx.Data = &ExecuteCommandTemplate{ + p.config.Ctx.Data = &ExecuteCommandTemplate{ Vars: flattenedEnvVars, Script: script, } - command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) + flattenedCmd := strings.Join(p.config.ExecuteCommand, " ") + command, err := interpolate.Render(flattenedCmd, &p.config.Ctx) if err != nil { return nil, false, fmt.Errorf("Error processing command: %s", err) } @@ -180,8 +84,8 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac ui.Say(fmt.Sprintf("Post processing with local shell script: %s", script)) comm := &sl.Communicator{ - Ctx: p.config.ctx, - ExecuteCommand: []string{p.config.ExecuteCommand}, + Ctx: p.config.Ctx, + ExecuteCommand: []string{flattenedCmd}, } cmd := &packer.RemoteCmd{Command: command} diff --git a/provisioner/shell-local/provisioner.go b/provisioner/shell-local/provisioner.go index ecd59fa98..615a7eb24 100644 --- a/provisioner/shell-local/provisioner.go +++ b/provisioner/shell-local/provisioner.go @@ -1,63 +1,29 @@ package shell import ( - "errors" "fmt" - "github.com/hashicorp/packer/common" sl "github.com/hashicorp/packer/common/shell-local" - "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" - "github.com/hashicorp/packer/template/interpolate" ) -type Config struct { - common.PackerConfig `mapstructure:",squash"` - - // Command is the command to execute - Command string - - // ExecuteCommand is the command used to execute the command. - ExecuteCommand []string `mapstructure:"execute_command"` - - ctx interpolate.Context -} - type Provisioner struct { - config Config + config sl.Config } func (p *Provisioner) Prepare(raws ...interface{}) error { - err := config.Decode(&p.config, &config.DecodeOpts{ - Interpolate: true, - InterpolateContext: &p.config.ctx, - InterpolateFilter: &interpolate.RenderFilter{ - Exclude: []string{ - "execute_command", - }, - }, - }, raws...) + err := sl.Decode(&p.config, raws) if err != nil { return err } - var errs *packer.MultiError - if p.config.Command == "" { - errs = packer.MultiErrorAppend(errs, - errors.New("command must be specified")) - } - - if errs != nil && len(errs.Errors) > 0 { - return errs - } - - return nil + return sl.Validate(&p.config) } func (p *Provisioner) Provision(ui packer.Ui, _ packer.Communicator) error { // Make another communicator for local comm := &sl.Communicator{ - Ctx: p.config.ctx, + Ctx: p.config.Ctx, ExecuteCommand: p.config.ExecuteCommand, } From c7c66bedcba477d145dae01a4e07e0aa9d6cf806 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 28 Feb 2018 09:45:29 -0800 Subject: [PATCH 03/22] set inline to an empty array, rather than nil --- common/shell-local/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/shell-local/config.go b/common/shell-local/config.go index b54f8d713..a6a0a279d 100644 --- a/common/shell-local/config.go +++ b/common/shell-local/config.go @@ -86,7 +86,7 @@ func Validate(config *Config) error { // Clean up input if config.Inline != nil && len(config.Inline) == 0 { - config.Inline = nil + config.Inline = make([]string, 0) } if config.Scripts == nil { From 6dc4b1cbdc1be33ff953d11a1b4643f366f05ae0 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 28 Feb 2018 11:53:53 -0800 Subject: [PATCH 04/22] move all of the run commands for shell-local provisioner and postprocessor into common library too --- builder/amazon/chroot/run_local_commands.go | 3 +- common/shell-local/communicator.go | 21 +-- common/shell-local/run.go | 160 +++++++++++++++++++ post-processor/shell-local/post-processor.go | 116 +------------- provisioner/shell-local/provisioner.go | 29 +--- 5 files changed, 172 insertions(+), 157 deletions(-) create mode 100644 common/shell-local/run.go diff --git a/builder/amazon/chroot/run_local_commands.go b/builder/amazon/chroot/run_local_commands.go index 154d37a4f..4d5b0f75c 100644 --- a/builder/amazon/chroot/run_local_commands.go +++ b/builder/amazon/chroot/run_local_commands.go @@ -22,8 +22,7 @@ func RunLocalCommands(commands []string, wrappedCommand CommandWrapper, ctx inte ui.Say(fmt.Sprintf("Executing command: %s", command)) comm := &sl.Communicator{ - Ctx: ctx, - ExecuteCommand: []string{""}, + ExecuteCommand: []string{command}, } cmd := &packer.RemoteCmd{Command: command} if err := cmd.StartWithUi(comm, ui); err != nil { diff --git a/common/shell-local/communicator.go b/common/shell-local/communicator.go index dc84b575a..5532143c9 100644 --- a/common/shell-local/communicator.go +++ b/common/shell-local/communicator.go @@ -9,12 +9,10 @@ import ( "syscall" "github.com/hashicorp/packer/packer" - "github.com/hashicorp/packer/template/interpolate" ) type Communicator struct { ExecuteCommand []string - Ctx interpolate.Context } func (c *Communicator) Start(cmd *packer.RemoteCmd) error { @@ -24,28 +22,17 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error { c.ExecuteCommand = []string{ "cmd", "/C", + "{{.Vars}}", "{{.Command}}", } } else { c.ExecuteCommand = []string{ "/bin/sh", "-c", + "{{.Vars}}", "{{.Command}}", } } - } else { - // Render the template so that we know how to execute the command - c.Ctx.Data = &ExecuteCommandTemplate{ - Command: cmd.Command, - } - for i, field := range c.ExecuteCommand { - command, err := interpolate.Render(field, &c.Ctx) - if err != nil { - return fmt.Errorf("Error processing command: %s", err) - } - - c.ExecuteCommand[i] = command - } } // Build the local command to execute @@ -97,7 +84,3 @@ func (c *Communicator) Download(string, io.Writer) error { func (c *Communicator) DownloadDir(string, string, []string) error { return fmt.Errorf("downloadDir not supported") } - -type ExecuteCommandTemplate struct { - Command string -} diff --git a/common/shell-local/run.go b/common/shell-local/run.go new file mode 100644 index 000000000..a42cb3216 --- /dev/null +++ b/common/shell-local/run.go @@ -0,0 +1,160 @@ +package shell_local + +import ( + "bufio" + "fmt" + "io/ioutil" + "log" + "os" + "runtime" + "sort" + "strings" + + "github.com/hashicorp/packer/packer" + "github.com/hashicorp/packer/template/interpolate" +) + +type ExecuteCommandTemplate struct { + Vars string + Script string +} + +func Run(ui packer.Ui, config *Config) (bool, error) { + scripts := make([]string, len(config.Scripts)) + copy(scripts, config.Scripts) + + // If we have an inline script, then turn that into a temporary + // shell script and use that. + if config.Inline != nil { + tf, err := ioutil.TempFile("", "packer-shell") + if err != nil { + return false, fmt.Errorf("Error preparing shell script: %s", err) + } + defer os.Remove(tf.Name()) + + // Set the path to the temporary file + scripts = append(scripts, tf.Name()) + + // Write our contents to it + writer := bufio.NewWriter(tf) + writer.WriteString(fmt.Sprintf("#!%s\n", config.InlineShebang)) + for _, command := range config.Inline { + if _, err := writer.WriteString(command + "\n"); err != nil { + return false, fmt.Errorf("Error preparing shell script: %s", err) + } + } + + if err := writer.Flush(); err != nil { + return false, fmt.Errorf("Error preparing shell script: %s", err) + } + + tf.Close() + } + + // Create environment variables to set before executing the command + flattenedEnvVars := createFlattenedEnvVars(config) + + for _, script := range scripts { + interpolatedCmds, err := createInterpolatedCommands(config, script, flattenedEnvVars) + if err != nil { + return false, err + } + ui.Say(fmt.Sprintf("Post processing with local shell script: %s", script)) + + comm := &Communicator{ + ExecuteCommand: interpolatedCmds, + } + + // The remoteCmd generated here isn't actually run, but it allows us to + // use the same interafce for the shell-local communicator as we use for + // the other communicators; ultimately, this command is just used for + // buffers and for reading the final exit status. + flattenedCmd := strings.Join(interpolatedCmds, " ") + cmd := &packer.RemoteCmd{Command: flattenedCmd} + log.Printf("starting local command: %s", flattenedCmd) + + if err := cmd.StartWithUi(comm, ui); err != nil { + return false, fmt.Errorf( + "Error executing script: %s\n\n"+ + "Please see output above for more information.", + script) + } + if cmd.ExitStatus != 0 { + return false, fmt.Errorf( + "Erroneous exit code %d while executing script: %s\n\n"+ + "Please see output above for more information.", + cmd.ExitStatus, + script) + } + } + + return true, nil +} + +// Generates the final command to send to the communicator, using either the +// user-provided ExecuteCommand or defaulting to something that makes sense for +// the host OS +func createInterpolatedCommands(config *Config, script string, flattenedEnvVars string) ([]string, error) { + config.Ctx.Data = &ExecuteCommandTemplate{ + Vars: flattenedEnvVars, + Script: script, + } + + if len(config.ExecuteCommand) == 0 { + // Get default Execute Command + if runtime.GOOS == "windows" { + config.ExecuteCommand = []string{ + "cmd", + "/C", + "{{.Vars}}", + "{{.Script}}", + } + } else { + config.ExecuteCommand = []string{ + "/bin/sh", + "-c", + "{{.Vars}}", + "{{.Script}}", + } + } + } + interpolatedCmds := make([]string, len(config.ExecuteCommand)) + for i, cmd := range config.ExecuteCommand { + interpolatedCmd, err := interpolate.Render(cmd, &config.Ctx) + if err != nil { + return nil, fmt.Errorf("Error processing command: %s", err) + } + interpolatedCmds[i] = interpolatedCmd + } + return interpolatedCmds, nil +} + +func createFlattenedEnvVars(config *Config) (flattened string) { + flattened = "" + envVars := make(map[string]string) + + // Always available Packer provided env vars + envVars["PACKER_BUILD_NAME"] = fmt.Sprintf("%s", config.PackerBuildName) + envVars["PACKER_BUILDER_TYPE"] = fmt.Sprintf("%s", config.PackerBuilderType) + + // Split vars into key/value components + for _, envVar := range config.Vars { + keyValue := strings.SplitN(envVar, "=", 2) + // Store pair, replacing any single quotes in value so they parse + // correctly with required environment variable format + envVars[keyValue[0]] = strings.Replace(keyValue[1], "'", `'"'"'`, -1) + } + + // Create a list of env var keys in sorted order + var keys []string + for k := range envVars { + keys = append(keys, k) + } + sort.Strings(keys) + + // Re-assemble vars surrounding value with single quotes and flatten + for _, key := range keys { + flattened += fmt.Sprintf("%s='%s' ", key, envVars[key]) + } + return +} diff --git a/post-processor/shell-local/post-processor.go b/post-processor/shell-local/post-processor.go index 818a8b44e..b1585a228 100644 --- a/post-processor/shell-local/post-processor.go +++ b/post-processor/shell-local/post-processor.go @@ -1,17 +1,8 @@ package shell_local import ( - "bufio" - "fmt" - "io/ioutil" - "log" - "os" - "sort" - "strings" - sl "github.com/hashicorp/packer/common/shell-local" "github.com/hashicorp/packer/packer" - "github.com/hashicorp/packer/template/interpolate" ) type PostProcessor struct { @@ -33,108 +24,13 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { } func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { + // this particular post-processor doesn't do anything with the artifact + // except to return it. - scripts := make([]string, len(p.config.Scripts)) - copy(scripts, p.config.Scripts) - - // If we have an inline script, then turn that into a temporary - // shell script and use that. - if p.config.Inline != nil { - tf, err := ioutil.TempFile("", "packer-shell") - if err != nil { - return nil, false, fmt.Errorf("Error preparing shell script: %s", err) - } - defer os.Remove(tf.Name()) - - // Set the path to the temporary file - scripts = append(scripts, tf.Name()) - - // Write our contents to it - writer := bufio.NewWriter(tf) - writer.WriteString(fmt.Sprintf("#!%s\n", p.config.InlineShebang)) - for _, command := range p.config.Inline { - if _, err := writer.WriteString(command + "\n"); err != nil { - return nil, false, fmt.Errorf("Error preparing shell script: %s", err) - } - } - - if err := writer.Flush(); err != nil { - return nil, false, fmt.Errorf("Error preparing shell script: %s", err) - } - - tf.Close() + retBool, retErr := sl.Run(ui, &p.config) + if !retBool { + return nil, retBool, retErr } - // Create environment variables to set before executing the command - flattenedEnvVars := p.createFlattenedEnvVars() - - for _, script := range scripts { - - p.config.Ctx.Data = &ExecuteCommandTemplate{ - Vars: flattenedEnvVars, - Script: script, - } - - flattenedCmd := strings.Join(p.config.ExecuteCommand, " ") - command, err := interpolate.Render(flattenedCmd, &p.config.Ctx) - if err != nil { - return nil, false, fmt.Errorf("Error processing command: %s", err) - } - - ui.Say(fmt.Sprintf("Post processing with local shell script: %s", script)) - - comm := &sl.Communicator{ - Ctx: p.config.Ctx, - ExecuteCommand: []string{flattenedCmd}, - } - - cmd := &packer.RemoteCmd{Command: command} - - log.Printf("starting local command: %s", command) - if err := cmd.StartWithUi(comm, ui); err != nil { - return nil, false, fmt.Errorf( - "Error executing script: %s\n\n"+ - "Please see output above for more information.", - script) - } - if cmd.ExitStatus != 0 { - return nil, false, fmt.Errorf( - "Erroneous exit code %d while executing script: %s\n\n"+ - "Please see output above for more information.", - cmd.ExitStatus, - script) - } - } - - return artifact, true, nil -} - -func (p *PostProcessor) createFlattenedEnvVars() (flattened string) { - flattened = "" - envVars := make(map[string]string) - - // Always available Packer provided env vars - envVars["PACKER_BUILD_NAME"] = fmt.Sprintf("%s", p.config.PackerBuildName) - envVars["PACKER_BUILDER_TYPE"] = fmt.Sprintf("%s", p.config.PackerBuilderType) - - // Split vars into key/value components - for _, envVar := range p.config.Vars { - keyValue := strings.SplitN(envVar, "=", 2) - // Store pair, replacing any single quotes in value so they parse - // correctly with required environment variable format - envVars[keyValue[0]] = strings.Replace(keyValue[1], "'", `'"'"'`, -1) - } - - // Create a list of env var keys in sorted order - var keys []string - for k := range envVars { - keys = append(keys, k) - } - sort.Strings(keys) - - // Re-assemble vars surrounding value with single quotes and flatten - for _, key := range keys { - flattened += fmt.Sprintf("%s='%s' ", key, envVars[key]) - } - return + return artifact, retBool, retErr } diff --git a/provisioner/shell-local/provisioner.go b/provisioner/shell-local/provisioner.go index 615a7eb24..a56553245 100644 --- a/provisioner/shell-local/provisioner.go +++ b/provisioner/shell-local/provisioner.go @@ -1,8 +1,6 @@ package shell import ( - "fmt" - sl "github.com/hashicorp/packer/common/shell-local" "github.com/hashicorp/packer/packer" ) @@ -21,30 +19,9 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } func (p *Provisioner) Provision(ui packer.Ui, _ packer.Communicator) error { - // Make another communicator for local - comm := &sl.Communicator{ - Ctx: p.config.Ctx, - ExecuteCommand: p.config.ExecuteCommand, - } - - // Build the remote command - cmd := &packer.RemoteCmd{Command: p.config.Command} - - ui.Say(fmt.Sprintf( - "Executing local command: %s", - p.config.Command)) - if err := cmd.StartWithUi(comm, ui); err != nil { - return fmt.Errorf( - "Error executing command: %s\n\n"+ - "Please see output above for more information.", - p.config.Command) - } - if cmd.ExitStatus != 0 { - return fmt.Errorf( - "Erroneous exit code %d while executing command: %s\n\n"+ - "Please see output above for more information.", - cmd.ExitStatus, - p.config.Command) + _, retErr := sl.Run(ui, &p.config) + if retErr != nil { + return retErr } return nil From 67739270bb32cb850caee1e086948d80fa71448d Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 28 Feb 2018 12:17:40 -0800 Subject: [PATCH 05/22] pull temp file writing into its own function for easier testing --- common/shell-local/run.go | 48 ++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/common/shell-local/run.go b/common/shell-local/run.go index a42cb3216..22366c27f 100644 --- a/common/shell-local/run.go +++ b/common/shell-local/run.go @@ -26,29 +26,12 @@ func Run(ui packer.Ui, config *Config) (bool, error) { // If we have an inline script, then turn that into a temporary // shell script and use that. if config.Inline != nil { - tf, err := ioutil.TempFile("", "packer-shell") + tempScriptFileName, err := createInlineScriptFile(config) if err != nil { - return false, fmt.Errorf("Error preparing shell script: %s", err) + return false, err } - defer os.Remove(tf.Name()) - - // Set the path to the temporary file - scripts = append(scripts, tf.Name()) - - // Write our contents to it - writer := bufio.NewWriter(tf) - writer.WriteString(fmt.Sprintf("#!%s\n", config.InlineShebang)) - for _, command := range config.Inline { - if _, err := writer.WriteString(command + "\n"); err != nil { - return false, fmt.Errorf("Error preparing shell script: %s", err) - } - } - - if err := writer.Flush(); err != nil { - return false, fmt.Errorf("Error preparing shell script: %s", err) - } - - tf.Close() + defer os.Remove(tempScriptFileName) + scripts = append(scripts, tempScriptFileName) } // Create environment variables to set before executing the command @@ -91,6 +74,29 @@ func Run(ui packer.Ui, config *Config) (bool, error) { return true, nil } +func createInlineScriptFile(config *Config) (string, error) { + tf, err := ioutil.TempFile("", "packer-shell") + if err != nil { + return "", fmt.Errorf("Error preparing shell script: %s", err) + } + + // Write our contents to it + writer := bufio.NewWriter(tf) + writer.WriteString(fmt.Sprintf("#!%s\n", config.InlineShebang)) + for _, command := range config.Inline { + if _, err := writer.WriteString(command + "\n"); err != nil { + return "", fmt.Errorf("Error preparing shell script: %s", err) + } + } + + if err := writer.Flush(); err != nil { + return "", fmt.Errorf("Error preparing shell script: %s", err) + } + + tf.Close() + return tf.Name(), nil +} + // Generates the final command to send to the communicator, using either the // user-provided ExecuteCommand or defaulting to something that makes sense for // the host OS From d30423472543a2a81f3b518963c37b495fcf719b Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 28 Feb 2018 14:35:42 -0800 Subject: [PATCH 06/22] fix tests --- common/shell-local/communicator.go | 18 +-- common/shell-local/communicator_test.go | 5 +- common/shell-local/config.go | 6 +- post-processor/shell-local/post-processor.go | 2 +- .../shell-local/post-processor_test.go | 130 +++++++----------- 5 files changed, 55 insertions(+), 106 deletions(-) diff --git a/common/shell-local/communicator.go b/common/shell-local/communicator.go index 5532143c9..7664bc896 100644 --- a/common/shell-local/communicator.go +++ b/common/shell-local/communicator.go @@ -5,7 +5,6 @@ import ( "io" "os" "os/exec" - "runtime" "syscall" "github.com/hashicorp/packer/packer" @@ -17,22 +16,7 @@ type Communicator struct { func (c *Communicator) Start(cmd *packer.RemoteCmd) error { if len(c.ExecuteCommand) == 0 { - // Get default Execute Command - if runtime.GOOS == "windows" { - c.ExecuteCommand = []string{ - "cmd", - "/C", - "{{.Vars}}", - "{{.Command}}", - } - } else { - c.ExecuteCommand = []string{ - "/bin/sh", - "-c", - "{{.Vars}}", - "{{.Command}}", - } - } + return fmt.Errorf("Error launching command via shell-local communicator: No ExecuteCommand provided") } // Build the local command to execute diff --git a/common/shell-local/communicator_test.go b/common/shell-local/communicator_test.go index 903ab154d..9a8cb9057 100644 --- a/common/shell-local/communicator_test.go +++ b/common/shell-local/communicator_test.go @@ -20,13 +20,12 @@ func TestCommunicator(t *testing.T) { } c := &Communicator{ - ExecuteCommand: []string{"/bin/sh", "-c", "{{.Command}}"}, + ExecuteCommand: []string{"/bin/sh", "-c", "echo foo"}, } var buf bytes.Buffer cmd := &packer.RemoteCmd{ - Command: "echo foo", - Stdout: &buf, + Stdout: &buf, } if err := c.Start(cmd); err != nil { diff --git a/common/shell-local/config.go b/common/shell-local/config.go index a6a0a279d..dfd3623b9 100644 --- a/common/shell-local/config.go +++ b/common/shell-local/config.go @@ -58,10 +58,10 @@ func Decode(config *Config, raws ...interface{}) error { }, }, raws...) if err != nil { - return err + return fmt.Errorf("Error decoding config: %s, config is %#v, and raws is %#v", err, config, raws) } - return Validate(config) + return nil } func Validate(config *Config) error { @@ -98,7 +98,7 @@ func Validate(config *Config) error { } // Verify that the user has given us a command to run - if config.Command != "" && len(config.Inline) == 0 && + if config.Command == "" && len(config.Inline) == 0 && len(config.Scripts) == 0 && config.Script == "" { errs = packer.MultiErrorAppend(errs, errors.New("Command, Inline, Script and Scripts options cannot all be empty.")) diff --git a/post-processor/shell-local/post-processor.go b/post-processor/shell-local/post-processor.go index b1585a228..91bc5acc9 100644 --- a/post-processor/shell-local/post-processor.go +++ b/post-processor/shell-local/post-processor.go @@ -15,7 +15,7 @@ type ExecuteCommandTemplate struct { } func (p *PostProcessor) Configure(raws ...interface{}) error { - err := sl.Decode(&p.config, raws) + err := sl.Decode(&p.config, raws...) if err != nil { return err } diff --git a/post-processor/shell-local/post-processor_test.go b/post-processor/shell-local/post-processor_test.go index 7bdef1c32..caf4f5a42 100644 --- a/post-processor/shell-local/post-processor_test.go +++ b/post-processor/shell-local/post-processor_test.go @@ -28,20 +28,20 @@ func TestPostProcessor_Impl(t *testing.T) { func TestPostProcessorPrepare_Defaults(t *testing.T) { var p PostProcessor - config := testConfig() + raws := testConfig() - err := p.Configure(config) + err := p.Configure(raws) if err != nil { t.Fatalf("err: %s", err) } } func TestPostProcessorPrepare_InlineShebang(t *testing.T) { - config := testConfig() + raws := testConfig() - delete(config, "inline_shebang") + delete(raws, "inline_shebang") p := new(PostProcessor) - err := p.Configure(config) + err := p.Configure(raws) if err != nil { t.Fatalf("should not have error: %s", err) } @@ -51,9 +51,9 @@ func TestPostProcessorPrepare_InlineShebang(t *testing.T) { } // Test with a good one - config["inline_shebang"] = "foo" + raws["inline_shebang"] = "foo" p = new(PostProcessor) - err = p.Configure(config) + err = p.Configure(raws) if err != nil { t.Fatalf("should not have error: %s", err) } @@ -65,23 +65,23 @@ func TestPostProcessorPrepare_InlineShebang(t *testing.T) { func TestPostProcessorPrepare_InvalidKey(t *testing.T) { var p PostProcessor - config := testConfig() + raws := testConfig() // Add a random key - config["i_should_not_be_valid"] = true - err := p.Configure(config) + raws["i_should_not_be_valid"] = true + err := p.Configure(raws) if err == nil { t.Fatal("should have error") } } func TestPostProcessorPrepare_Script(t *testing.T) { - config := testConfig() - delete(config, "inline") + raws := testConfig() + delete(raws, "inline") - config["script"] = "/this/should/not/exist" + raws["script"] = "/this/should/not/exist" p := new(PostProcessor) - err := p.Configure(config) + err := p.Configure(raws) if err == nil { t.Fatal("should have error") } @@ -93,9 +93,9 @@ func TestPostProcessorPrepare_Script(t *testing.T) { } defer os.Remove(tf.Name()) - config["script"] = tf.Name() + raws["script"] = tf.Name() p = new(PostProcessor) - err = p.Configure(config) + err = p.Configure(raws) if err != nil { t.Fatalf("should not have error: %s", err) } @@ -103,13 +103,16 @@ func TestPostProcessorPrepare_Script(t *testing.T) { func TestPostProcessorPrepare_ScriptAndInline(t *testing.T) { var p PostProcessor - config := testConfig() + raws := testConfig() - delete(config, "inline") - delete(config, "script") - err := p.Configure(config) + // Error if no scripts/inline commands provided + delete(raws, "inline") + delete(raws, "script") + delete(raws, "command") + delete(raws, "scripts") + err := p.Configure(raws) if err == nil { - t.Fatal("should have error") + t.Fatalf("should error when no scripts/inline commands are provided: %#v", raws) } // Test with both @@ -119,9 +122,9 @@ func TestPostProcessorPrepare_ScriptAndInline(t *testing.T) { } defer os.Remove(tf.Name()) - config["inline"] = []interface{}{"foo"} - config["script"] = tf.Name() - err = p.Configure(config) + raws["inline"] = []interface{}{"foo"} + raws["script"] = tf.Name() + err = p.Configure(raws) if err == nil { t.Fatal("should have error") } @@ -129,7 +132,7 @@ func TestPostProcessorPrepare_ScriptAndInline(t *testing.T) { func TestPostProcessorPrepare_ScriptAndScripts(t *testing.T) { var p PostProcessor - config := testConfig() + raws := testConfig() // Test with both tf, err := ioutil.TempFile("", "packer") @@ -138,21 +141,21 @@ func TestPostProcessorPrepare_ScriptAndScripts(t *testing.T) { } defer os.Remove(tf.Name()) - config["inline"] = []interface{}{"foo"} - config["scripts"] = []string{tf.Name()} - err = p.Configure(config) + raws["inline"] = []interface{}{"foo"} + raws["scripts"] = []string{tf.Name()} + err = p.Configure(raws) if err == nil { t.Fatal("should have error") } } func TestPostProcessorPrepare_Scripts(t *testing.T) { - config := testConfig() - delete(config, "inline") + raws := testConfig() + delete(raws, "inline") - config["scripts"] = []string{} + raws["scripts"] = []string{} p := new(PostProcessor) - err := p.Configure(config) + err := p.Configure(raws) if err == nil { t.Fatal("should have error") } @@ -164,92 +167,55 @@ func TestPostProcessorPrepare_Scripts(t *testing.T) { } defer os.Remove(tf.Name()) - config["scripts"] = []string{tf.Name()} + raws["scripts"] = []string{tf.Name()} p = new(PostProcessor) - err = p.Configure(config) + err = p.Configure(raws) if err != nil { t.Fatalf("should not have error: %s", err) } } func TestPostProcessorPrepare_EnvironmentVars(t *testing.T) { - config := testConfig() + raws := testConfig() // Test with a bad case - config["environment_vars"] = []string{"badvar", "good=var"} + raws["environment_vars"] = []string{"badvar", "good=var"} p := new(PostProcessor) - err := p.Configure(config) + err := p.Configure(raws) if err == nil { t.Fatal("should have error") } // Test with a trickier case - config["environment_vars"] = []string{"=bad"} + raws["environment_vars"] = []string{"=bad"} p = new(PostProcessor) - err = p.Configure(config) + err = p.Configure(raws) if err == nil { t.Fatal("should have error") } // Test with a good case // Note: baz= is a real env variable, just empty - config["environment_vars"] = []string{"FOO=bar", "baz="} + raws["environment_vars"] = []string{"FOO=bar", "baz="} p = new(PostProcessor) - err = p.Configure(config) + err = p.Configure(raws) if err != nil { t.Fatalf("should not have error: %s", err) } // Test when the env variable value contains an equals sign - config["environment_vars"] = []string{"good=withequals=true"} + raws["environment_vars"] = []string{"good=withequals=true"} p = new(PostProcessor) - err = p.Configure(config) + err = p.Configure(raws) if err != nil { t.Fatalf("should not have error: %s", err) } // Test when the env variable value starts with an equals sign - config["environment_vars"] = []string{"good==true"} + raws["environment_vars"] = []string{"good==true"} p = new(PostProcessor) - err = p.Configure(config) + err = p.Configure(raws) if err != nil { t.Fatalf("should not have error: %s", err) } } - -func TestPostProcessor_createFlattenedEnvVars(t *testing.T) { - var flattenedEnvVars string - config := testConfig() - - userEnvVarTests := [][]string{ - {}, // No user env var - {"FOO=bar"}, // Single user env var - {"FOO=bar's"}, // User env var with single quote in value - {"FOO=bar", "BAZ=qux"}, // Multiple user env vars - {"FOO=bar=baz"}, // User env var with value containing equals - {"FOO==bar"}, // User env var with value starting with equals - } - expected := []string{ - `PACKER_BUILDER_TYPE='iso' PACKER_BUILD_NAME='vmware' `, - `FOO='bar' PACKER_BUILDER_TYPE='iso' PACKER_BUILD_NAME='vmware' `, - `FOO='bar'"'"'s' PACKER_BUILDER_TYPE='iso' PACKER_BUILD_NAME='vmware' `, - `BAZ='qux' FOO='bar' PACKER_BUILDER_TYPE='iso' PACKER_BUILD_NAME='vmware' `, - `FOO='bar=baz' PACKER_BUILDER_TYPE='iso' PACKER_BUILD_NAME='vmware' `, - `FOO='=bar' PACKER_BUILDER_TYPE='iso' PACKER_BUILD_NAME='vmware' `, - } - - p := new(PostProcessor) - p.Configure(config) - - // Defaults provided by Packer - p.config.PackerBuildName = "vmware" - p.config.PackerBuilderType = "iso" - - for i, expectedValue := range expected { - p.config.Vars = userEnvVarTests[i] - flattenedEnvVars = p.createFlattenedEnvVars() - if flattenedEnvVars != expectedValue { - t.Fatalf("expected flattened env vars to be: %s, got %s.", expectedValue, flattenedEnvVars) - } - } -} From 479d36734ded8c59742a5885b11c15a97b030de8 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 28 Feb 2018 14:43:58 -0800 Subject: [PATCH 07/22] consolidate shell-local defaulting of InlineShebang and ExecuteCommand to the config validation --- common/shell-local/config.go | 18 ++++++++++++------ common/shell-local/run.go | 19 ------------------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/common/shell-local/config.go b/common/shell-local/config.go index dfd3623b9..5b086a794 100644 --- a/common/shell-local/config.go +++ b/common/shell-local/config.go @@ -68,18 +68,24 @@ func Validate(config *Config) error { var errs *packer.MultiError if runtime.GOOS == "windows" { - if config.InlineShebang == "" { - config.InlineShebang = "" - } if len(config.ExecuteCommand) == 0 { - config.ExecuteCommand = []string{`{{.Vars}} "{{.Script}}"`} - } + config.ExecuteCommand = []string{ + "cmd", + "/C", + "{{.Vars}}", + "{{.Script}}", + } } else { if config.InlineShebang == "" { - // TODO: verify that provisioner defaulted to this as well config.InlineShebang = "/bin/sh -e" } if len(config.ExecuteCommand) == 0 { + config.ExecuteCommand = []string{ + "/bin/sh", + "-c", + "{{.Vars}}", + "{{.Script}}", + } config.ExecuteCommand = []string{`chmod +x "{{.Script}}"; {{.Vars}} "{{.Script}}"`} } } diff --git a/common/shell-local/run.go b/common/shell-local/run.go index 22366c27f..04d653389 100644 --- a/common/shell-local/run.go +++ b/common/shell-local/run.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "log" "os" - "runtime" "sort" "strings" @@ -106,24 +105,6 @@ func createInterpolatedCommands(config *Config, script string, flattenedEnvVars Script: script, } - if len(config.ExecuteCommand) == 0 { - // Get default Execute Command - if runtime.GOOS == "windows" { - config.ExecuteCommand = []string{ - "cmd", - "/C", - "{{.Vars}}", - "{{.Script}}", - } - } else { - config.ExecuteCommand = []string{ - "/bin/sh", - "-c", - "{{.Vars}}", - "{{.Script}}", - } - } - } interpolatedCmds := make([]string, len(config.ExecuteCommand)) for i, cmd := range config.ExecuteCommand { interpolatedCmd, err := interpolate.Render(cmd, &config.Ctx) From f799003b66d64d859bff0229550e4123df4e8ef6 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 28 Feb 2018 15:19:28 -0800 Subject: [PATCH 08/22] tighten up shell-local config validation --- common/shell-local/config.go | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/common/shell-local/config.go b/common/shell-local/config.go index 5b086a794..76c82c793 100644 --- a/common/shell-local/config.go +++ b/common/shell-local/config.go @@ -75,6 +75,7 @@ func Validate(config *Config) error { "{{.Vars}}", "{{.Script}}", } + } } else { if config.InlineShebang == "" { config.InlineShebang = "/bin/sh -e" @@ -110,32 +111,32 @@ func Validate(config *Config) error { errors.New("Command, Inline, Script and Scripts options cannot all be empty.")) } - if config.Command != "" { - // Backwards Compatibility: Before v1.2.2, the shell-local - // provisioner only allowed a single Command, and to run - // multiple commands you needed to run several provisioners in a - // row, one for each command. In deduplicating the post-processor and - // provisioner code, we've changed this to allow an array of scripts or - // inline commands just like in the post-processor. This conditional - // grandfathers in the "Command" option, allowing the original usage to - // continue to work. - config.Inline = append(config.Inline, config.Command) - } + // Check that user hasn't given us too many commands to run + tooManyOptionsErr := errors.New("You may only specify one of the " + + "following options: Command, Inline, Script or Scripts. Please" + + " consolidate these options in your config.") - if config.Script != "" && len(config.Scripts) > 0 { - errs = packer.MultiErrorAppend(errs, - errors.New("Only one of script or scripts can be specified.")) + if config.Command != "" { + if len(config.Inline) != 0 || len(config.Scripts) != 0 || config.Script != "" { + errs = packer.MultiErrorAppend(errs, tooManyOptionsErr) + } else { + config.Inline = []string{config.Command} + } } if config.Script != "" { - config.Scripts = []string{config.Script} + if len(config.Scripts) > 0 || len(config.Inline) > 0 { + errs = packer.MultiErrorAppend(errs, tooManyOptionsErr) + } else { + config.Scripts = []string{config.Script} + } } if len(config.Scripts) > 0 && config.Inline != nil { - errs = packer.MultiErrorAppend(errs, - errors.New("You may specify either a script file(s) or an inline script(s), but not both.")) + errs = packer.MultiErrorAppend(errs, tooManyOptionsErr) } + // Check that all scripts we need to run exist locally for _, path := range config.Scripts { if _, err := os.Stat(path); err != nil { errs = packer.MultiErrorAppend(errs, From 854d6fb141ae89ccf88c8f5c75ecf0c4de588454 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 1 Mar 2018 08:48:21 -0800 Subject: [PATCH 09/22] add tests making sure post-processor has backwards compatability --- common/shell-local/config.go | 1 - post-processor/shell-local/post-processor.go | 14 +++++ .../shell-local/post-processor_test.go | 53 +++++++++++++++++-- 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/common/shell-local/config.go b/common/shell-local/config.go index 76c82c793..2d31b7f01 100644 --- a/common/shell-local/config.go +++ b/common/shell-local/config.go @@ -87,7 +87,6 @@ func Validate(config *Config) error { "{{.Vars}}", "{{.Script}}", } - config.ExecuteCommand = []string{`chmod +x "{{.Script}}"; {{.Vars}} "{{.Script}}"`} } } diff --git a/post-processor/shell-local/post-processor.go b/post-processor/shell-local/post-processor.go index 91bc5acc9..557fe7eba 100644 --- a/post-processor/shell-local/post-processor.go +++ b/post-processor/shell-local/post-processor.go @@ -1,6 +1,8 @@ package shell_local import ( + "runtime" + sl "github.com/hashicorp/packer/common/shell-local" "github.com/hashicorp/packer/packer" ) @@ -19,6 +21,18 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { if err != nil { return err } + if len(p.config.ExecuteCommand) == 0 && runtime.GOOS != "windows" { + // Backwards compatibility from before post-processor merge with + // provisioner. Don't need to default separately for windows becuase the + // post-processor never worked for windows before the merge with the + // provisioner code, so the provisioner defaults are fine. + p.config.ExecuteCommand = []string{"sh", "-c", `chmod +x "{{.Script}}"; {{.Vars}} "{{.Script}}"`} + } else if len(p.config.ExecuteCommand) == 1 { + // Backwards compatibility -- before merge, post-processor didn't have + // configurable call to shell program, meaning users may not have + // defined this in their call + p.config.ExecuteCommand = append([]string{"sh", "-c"}, p.config.ExecuteCommand...) + } return sl.Validate(&p.config) } diff --git a/post-processor/shell-local/post-processor_test.go b/post-processor/shell-local/post-processor_test.go index caf4f5a42..afec79f81 100644 --- a/post-processor/shell-local/post-processor_test.go +++ b/post-processor/shell-local/post-processor_test.go @@ -3,6 +3,8 @@ package shell_local import ( "io/ioutil" "os" + "runtime" + "strings" "testing" "github.com/hashicorp/packer/packer" @@ -45,8 +47,11 @@ func TestPostProcessorPrepare_InlineShebang(t *testing.T) { if err != nil { t.Fatalf("should not have error: %s", err) } - - if p.config.InlineShebang != "/bin/sh -e" { + expected := "" + if runtime.GOOS != "windows" { + expected = "/bin/sh -e" + } + if p.config.InlineShebang != expected { t.Fatalf("bad value: %s", p.config.InlineShebang) } @@ -101,6 +106,48 @@ func TestPostProcessorPrepare_Script(t *testing.T) { } } +func TestPostProcessorPrepare_ExecuteCommand(t *testing.T) { + // Check that passing a string will work (Backwards Compatibility) + p := new(PostProcessor) + raws := testConfig() + raws["execute_command"] = "foo bar" + err := p.Configure(raws) + expected := []string{"sh", "-c", "foo bar"} + if err != nil { + t.Fatalf("should handle backwards compatibility: %s", err) + } + if strings.Compare(strings.Join(p.config.ExecuteCommand, " "), strings.Join(expected, " ")) != 0 { + t.Fatalf("Did not get expected execute_command: expected: %#v; received %#v", expected, p.config.ExecuteCommand) + } + + // Check that passing a list will work + p = new(PostProcessor) + raws = testConfig() + raws["execute_command"] = []string{"foo", "bar"} + err = p.Configure(raws) + if err != nil { + t.Fatalf("should handle backwards compatibility: %s", err) + } + expected = []string{"foo", "bar"} + if strings.Compare(strings.Join(p.config.ExecuteCommand, " "), strings.Join(expected, " ")) != 0 { + t.Fatalf("Did not get expected execute_command: expected: %#v; received %#v", expected, p.config.ExecuteCommand) + } + + // Check that default is as expected + raws = testConfig() + delete(raws, "execute_command") + p = new(PostProcessor) + p.Configure(raws) + if runtime.GOOS != "windows" { + expected = []string{"sh", "-c", `chmod +x "{{.Script}}"; {{.Vars}} "{{.Script}}"`} + } else { + expected = []string{"cmd", "/C", "{{.Vars}}", "{{.Script}}"} + } + if strings.Compare(strings.Join(p.config.ExecuteCommand, " "), strings.Join(expected, " ")) != 0 { + t.Fatalf("Did not get expected default: expected: %#v; received %#v", expected, p.config.ExecuteCommand) + } +} + func TestPostProcessorPrepare_ScriptAndInline(t *testing.T) { var p PostProcessor raws := testConfig() @@ -112,7 +159,7 @@ func TestPostProcessorPrepare_ScriptAndInline(t *testing.T) { delete(raws, "scripts") err := p.Configure(raws) if err == nil { - t.Fatalf("should error when no scripts/inline commands are provided: %#v", raws) + t.Fatalf("should error when no scripts/inline commands are provided") } // Test with both From 5da4377f210d92e922210275aec809a7e2ebb2f9 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 1 Mar 2018 10:56:30 -0800 Subject: [PATCH 10/22] first pass at docs update --- common/shell-local/communicator.go | 2 + post-processor/shell-local/post-processor.go | 6 ++- .../docs/post-processors/shell-local.html.md | 32 ++++++++--- .../docs/provisioners/shell-local.html.md | 53 +++++++++++++++++-- 4 files changed, 83 insertions(+), 10 deletions(-) diff --git a/common/shell-local/communicator.go b/common/shell-local/communicator.go index 7664bc896..b51d309d9 100644 --- a/common/shell-local/communicator.go +++ b/common/shell-local/communicator.go @@ -3,6 +3,7 @@ package shell_local import ( "fmt" "io" + "log" "os" "os/exec" "syscall" @@ -20,6 +21,7 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error { } // Build the local command to execute + log.Printf("Executing local shell command %s", c.ExecuteCommand) localCmd := exec.Command(c.ExecuteCommand[0], c.ExecuteCommand[1:]...) localCmd.Stdin = cmd.Stdin localCmd.Stdout = cmd.Stdout diff --git a/post-processor/shell-local/post-processor.go b/post-processor/shell-local/post-processor.go index 557fe7eba..cc1f2845e 100644 --- a/post-processor/shell-local/post-processor.go +++ b/post-processor/shell-local/post-processor.go @@ -30,7 +30,11 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { } else if len(p.config.ExecuteCommand) == 1 { // Backwards compatibility -- before merge, post-processor didn't have // configurable call to shell program, meaning users may not have - // defined this in their call + // defined this in their call. If users are still using the old way of + // defining ExecuteCommand (e.g. just supplying a single string that is + // now being interpolated as a slice with one item), then assume we need + // to prepend this call still, and use the one that the post-processor + // defaulted to before. p.config.ExecuteCommand = append([]string{"sh", "-c"}, p.config.ExecuteCommand...) } diff --git a/website/source/docs/post-processors/shell-local.html.md b/website/source/docs/post-processors/shell-local.html.md index d23780731..26ef7871f 100644 --- a/website/source/docs/post-processors/shell-local.html.md +++ b/website/source/docs/post-processors/shell-local.html.md @@ -13,7 +13,7 @@ Type: `shell-local` The local shell post processor executes scripts locally during the post processing stage. Shell local provides a convenient way to automate executing -some task with the packer outputs. +some task with packer outputs and variables. ## Basic example @@ -33,6 +33,9 @@ required element is either "inline" or "script". Every other option is optional. Exactly *one* of the following is required: +- `command` (string) - This is a single command to execute. It will be written + to a temporary file and run using the `execute_command` call below. + - `inline` (array of strings) - This is an array of commands to execute. The commands are concatenated by newlines and turned into a single file, so they are all executed within the same context. This allows you to change @@ -52,15 +55,32 @@ Exactly *one* of the following is required: Optional parameters: - `environment_vars` (array of strings) - An array of key/value pairs to - inject prior to the execute\_command. The format should be `key=value`. + inject prior to the `execute_command`. The format should be `key=value`. Packer injects some environmental variables by default into the environment, as well, which are covered in the section below. -- `execute_command` (string) - The command to use to execute the script. By - default this is `chmod +x "{{.Script}}"; {{.Vars}} "{{.Script}}"`. - The value of this is treated as [template engine](/docs/templates/engine.html). +- `execute_command` (array of strings) - The command used to execute the script. By + default this is `["sh", "-c", "chmod +x \"{{.Script}}\"; {{.Vars}} \"{{.Script}}\""]` + on unix and `["cmd", "/c", "{{.Vars}}", "{{.Script}}"]` on windows. + This is treated as a [template engine](/docs/templates/engine.html). There are two available variables: `Script`, which is the path to the script - to run, `Vars`, which is the list of `environment_vars`, if configured. + to run, and `Vars`, which is the list of `environment_vars`, if configured. + If you choose to set this option, make sure that the first element in the + array is the shell program you want to use (for example, "sh" or + "/usr/local/bin/zsh" or even "powershell.exe" although anything other than + a flavor of the shell command language is not explicitly supported and may + be broken by assumptions made within Packer). + + For backwards compatibility, `execute_command` will accept a string insetad + of an array of strings. If a single string or an array of strings with only + one element is provided, Packer will replicate past behavior by appending + your `execute_command` to the array of strings `["sh", "-c"]`. For example, + if you set `"execute_command": "foo bar"`, the final `execute_command` that + Packer runs will be ["sh", "-c", "foo bar"]. If you set `"execute_command": ["foo", "bar"]`, + the final execute_command will remain `["foo", "bar"]`. + + Again, the above is only provided as a backwards compatibility fix; we + strongly recommend that you set execute_command as an array of strings. - `inline_shebang` (string) - The [shebang](http://en.wikipedia.org/wiki/Shebang_%28Unix%29) value to use when diff --git a/website/source/docs/provisioners/shell-local.html.md b/website/source/docs/provisioners/shell-local.html.md index c52bcd893..bd66e74c7 100644 --- a/website/source/docs/provisioners/shell-local.html.md +++ b/website/source/docs/provisioners/shell-local.html.md @@ -37,10 +37,26 @@ The example below is fully functional. The reference of available configuration options is listed below. The only required element is "command". -Required: +Exactly *one* of the following is required: -- `command` (string) - The command to execute. This will be executed within - the context of a shell as specified by `execute_command`. +- `command` (string) - This is a single command to execute. It will be written + to a temporary file and run using the `execute_command` call below. + +- `inline` (array of strings) - This is an array of commands to execute. The + commands are concatenated by newlines and turned into a single file, so they + are all executed within the same context. This allows you to change + directories in one command and use something in the directory in the next + and so on. Inline scripts are the easiest way to pull off simple tasks + within the machine. + +- `script` (string) - The path to a script to execute. This path can be + absolute or relative. If it is relative, it is relative to the working + directory when Packer is executed. + +- `scripts` (array of strings) - An array of scripts to execute. The scripts + will be executed in the order specified. Each script is executed in + isolation, so state such as variables from one script won't carry on to the + next. Optional parameters: @@ -50,3 +66,34 @@ Optional parameters: treated as [configuration template](/docs/templates/engine.html). The only available variable is `Command` which is the command to execute. + +- `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 into the environment, + as well, which are covered in the section below. + +- `execute_command` (array of strings) - The command used to execute the script. + By default this is `["/bin/sh", "-c", "{{.Vars}}, "{{.Script}}"]` + on unix and `["cmd", "/c", "{{.Vars}}", "{{.Script}}"]` on windows. + This is treated as a [template engine](/docs/templates/engine.html). + There are two available variables: `Script`, which is the path to the script + to run, and `Vars`, which is the list of `environment_vars`, if configured + If you choose to set this option, make sure that the first element in the + array is the shell program you want to use (for example, "sh" or + "/usr/local/bin/zsh" or even "powershell.exe" although anything other than + a flavor of the shell command language is not explicitly supported and may + be broken by assumptions made within Packer), and a later element in the + array must be `{{.Script}}`. + + For backwards compatability, {{.Command}} is also available to use in + `execute_command` but it is decoded the same way as {{.Script}}. We + recommend using {{.Script}} for the sake of clarity, as even when you set + only a single `command` to run, Packer writes it to a temporary file and + then runs it as a script. + +- `inline_shebang` (string) - The + [shebang](http://en.wikipedia.org/wiki/Shebang_%28Unix%29) value to use when + running commands specified by `inline`. By default, this is `/bin/sh -e`. If + you're not using `inline`, then this configuration has no effect. + **Important:** If you customize this, be sure to include something like the + `-e` flag, otherwise individual steps failing won't fail the provisioner. From e983a94a88251a32d6426269d321117d58e33266 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Fri, 2 Mar 2018 12:32:34 -0800 Subject: [PATCH 11/22] fix default windows bash call for shell-local provisioner and move chmod command from the execute_command array into the portion of code where we actually generate inline scripts, sparing users the need to think about this modification which Packer should really handle on its own make bash call work on windows --- common/shell-local/config.go | 8 +++- common/shell-local/run.go | 6 ++- post-processor/shell-local/post-processor.go | 26 +++++------- provisioner/shell-local/provisioner.go | 43 +++++++++++++++++++- 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/common/shell-local/config.go b/common/shell-local/config.go index 2d31b7f01..8c73d0f57 100644 --- a/common/shell-local/config.go +++ b/common/shell-local/config.go @@ -67,6 +67,11 @@ func Decode(config *Config, raws ...interface{}) error { func Validate(config *Config) error { var errs *packer.MultiError + // Do not treat these defaults as a source of truth; the shell-local + // provisioner sets these defaults before Validate is called. Eventually + // we will have to bring the provisioner and post-processor defaults in + // line with one another, but for now the following may or may not be + // applied depending on where Validate is being called from. if runtime.GOOS == "windows" { if len(config.ExecuteCommand) == 0 { config.ExecuteCommand = []string{ @@ -84,8 +89,7 @@ func Validate(config *Config) error { config.ExecuteCommand = []string{ "/bin/sh", "-c", - "{{.Vars}}", - "{{.Script}}", + "{{.Vars}} {{.Script}}", } } } diff --git a/common/shell-local/run.go b/common/shell-local/run.go index 04d653389..5545d56d7 100644 --- a/common/shell-local/run.go +++ b/common/shell-local/run.go @@ -41,7 +41,7 @@ func Run(ui packer.Ui, config *Config) (bool, error) { if err != nil { return false, err } - ui.Say(fmt.Sprintf("Post processing with local shell script: %s", script)) + ui.Say(fmt.Sprintf("Running local shell script: %s", script)) comm := &Communicator{ ExecuteCommand: interpolatedCmds, @@ -93,6 +93,10 @@ func createInlineScriptFile(config *Config) (string, error) { } tf.Close() + err = os.Chmod(tf.Name(), 0555) + if err != nil { + log.Printf("error modifying permissions of temp script file: %s", err.Error()) + } return tf.Name(), nil } diff --git a/post-processor/shell-local/post-processor.go b/post-processor/shell-local/post-processor.go index cc1f2845e..c761a19f4 100644 --- a/post-processor/shell-local/post-processor.go +++ b/post-processor/shell-local/post-processor.go @@ -1,8 +1,6 @@ package shell_local import ( - "runtime" - sl "github.com/hashicorp/packer/common/shell-local" "github.com/hashicorp/packer/packer" ) @@ -21,20 +19,16 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { if err != nil { return err } - if len(p.config.ExecuteCommand) == 0 && runtime.GOOS != "windows" { - // Backwards compatibility from before post-processor merge with - // provisioner. Don't need to default separately for windows becuase the - // post-processor never worked for windows before the merge with the - // provisioner code, so the provisioner defaults are fine. - p.config.ExecuteCommand = []string{"sh", "-c", `chmod +x "{{.Script}}"; {{.Vars}} "{{.Script}}"`} - } else if len(p.config.ExecuteCommand) == 1 { - // Backwards compatibility -- before merge, post-processor didn't have - // configurable call to shell program, meaning users may not have - // defined this in their call. If users are still using the old way of - // defining ExecuteCommand (e.g. just supplying a single string that is - // now being interpolated as a slice with one item), then assume we need - // to prepend this call still, and use the one that the post-processor - // defaulted to before. + if len(p.config.ExecuteCommand) == 1 { + // Backwards compatibility -- before we merged the shell-local + // post-processor and provisioners, the post-processor accepted + // execute_command as a string rather than a slice of strings. It didn't + // have a configurable call to shell program, automatically prepending + // the user-supplied execute_command string with "sh -c". If users are + // still using the old way of defining ExecuteCommand (by supplying a + // single string rather than a slice of strings) then we need to + // prepend this command with the call that the post-processor defaulted + // to before. p.config.ExecuteCommand = append([]string{"sh", "-c"}, p.config.ExecuteCommand...) } diff --git a/provisioner/shell-local/provisioner.go b/provisioner/shell-local/provisioner.go index a56553245..a58f0c859 100644 --- a/provisioner/shell-local/provisioner.go +++ b/provisioner/shell-local/provisioner.go @@ -1,6 +1,11 @@ package shell import ( + "fmt" + "path/filepath" + "runtime" + "strings" + sl "github.com/hashicorp/packer/common/shell-local" "github.com/hashicorp/packer/packer" ) @@ -10,12 +15,46 @@ type Provisioner struct { } func (p *Provisioner) Prepare(raws ...interface{}) error { - err := sl.Decode(&p.config, raws) + err := sl.Decode(&p.config, raws...) + if err != nil { + return err + } + convertPath := false + if len(p.config.ExecuteCommand) == 0 && runtime.GOOS == "windows" { + convertPath = true + p.config.ExecuteCommand = []string{ + "bash", + "-c", + "{{.Vars}} {{.Script}}", + } + } + + err = sl.Validate(&p.config) if err != nil { return err } - return sl.Validate(&p.config) + if convertPath { + for index, script := range p.config.Scripts { + p.config.Scripts[index], err = convertToWindowsBashPath(script) + if err != nil { + return err + } + } + } + + return nil +} + +func convertToWindowsBashPath(winPath string) (string, error) { + // get absolute path of script, and morph it into the bash path + winAbsPath, err := filepath.Abs(winPath) + if err != nil { + return "", fmt.Errorf("Error converting %s to absolute path: %s", winPath, err.Error()) + } + winAbsPath = strings.Replace(winAbsPath, "\\", "/", -1) + winBashPath := strings.Replace(winAbsPath, "C:/", "/mnt/c/", 1) + return winBashPath, nil } func (p *Provisioner) Provision(ui packer.Ui, _ packer.Communicator) error { From 51bcc7aa136ccb7331435333cf162d0c5d007feb Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 8 Mar 2018 16:42:17 -0800 Subject: [PATCH 12/22] add new feature for telling shell-local whether to use linux pathing on windows; update docs with some examples. --- common/shell-local/config.go | 33 ++++++++++++--- common/shell-local/run.go | 10 +++++ provisioner/shell-local/provisioner.go | 34 --------------- .../docs/post-processors/shell-local.html.md | 41 +++++++++++++++++-- 4 files changed, 75 insertions(+), 43 deletions(-) diff --git a/common/shell-local/config.go b/common/shell-local/config.go index 8c73d0f57..80751aee7 100644 --- a/common/shell-local/config.go +++ b/common/shell-local/config.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "runtime" "strings" @@ -44,6 +45,8 @@ type Config struct { // can be used to inject the environment_vars into the environment. ExecuteCommand []string `mapstructure:"execute_command"` + UseLinuxPathing bool `mapstructure:"use_linux_pathing"` + Ctx interpolate.Context } @@ -67,11 +70,6 @@ func Decode(config *Config, raws ...interface{}) error { func Validate(config *Config) error { var errs *packer.MultiError - // Do not treat these defaults as a source of truth; the shell-local - // provisioner sets these defaults before Validate is called. Eventually - // we will have to bring the provisioner and post-processor defaults in - // line with one another, but for now the following may or may not be - // applied depending on where Validate is being called from. if runtime.GOOS == "windows" { if len(config.ExecuteCommand) == 0 { config.ExecuteCommand = []string{ @@ -89,7 +87,8 @@ func Validate(config *Config) error { config.ExecuteCommand = []string{ "/bin/sh", "-c", - "{{.Vars}} {{.Script}}", + "{{.Vars}}", + "{{.Script}}", } } } @@ -146,6 +145,15 @@ func Validate(config *Config) error { fmt.Errorf("Bad script '%s': %s", path, err)) } } + if config.UseLinuxPathing { + for index, script := range config.Scripts { + converted, err := convertToLinuxPath(script) + if err != nil { + return err + } + config.Scripts[index] = converted + } + } // Do a check for bad environment variables, such as '=foo', 'foobar' for _, kv := range config.Vars { @@ -162,3 +170,16 @@ func Validate(config *Config) error { return nil } + +// C:/path/to/your/file becomes /mnt/c/path/to/your/file +func convertToLinuxPath(winPath string) (string, error) { + // get absolute path of script, and morph it into the bash path + winAbsPath, err := filepath.Abs(winPath) + if err != nil { + return "", fmt.Errorf("Error converting %s to absolute path: %s", winPath, err.Error()) + } + winAbsPath = strings.Replace(winAbsPath, "\\", "/", -1) + splitPath := strings.SplitN(winAbsPath, ":/", 2) + winBashPath := fmt.Sprintf("/mnt/%s/%s", strings.ToLower(splitPath[0]), splitPath[1]) + return winBashPath, nil +} diff --git a/common/shell-local/run.go b/common/shell-local/run.go index 5545d56d7..ea8043737 100644 --- a/common/shell-local/run.go +++ b/common/shell-local/run.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "log" "os" + "runtime" "sort" "strings" @@ -144,8 +145,17 @@ func createFlattenedEnvVars(config *Config) (flattened string) { sort.Strings(keys) // Re-assemble vars surrounding value with single quotes and flatten + if runtime.GOOS == "windows" { + log.Printf("MEGAN NEED TO IMPLEMENT") + // createEnvVarsSourceFileWindows() + } for _, key := range keys { flattened += fmt.Sprintf("%s='%s' ", key, envVars[key]) } return } + +// func createFlattenedEnvVarsWindows( +// // The default shell, cmd, can set vars via dot sourcing +// // set TESTXYZ=XYZ +// ) diff --git a/provisioner/shell-local/provisioner.go b/provisioner/shell-local/provisioner.go index a58f0c859..16c3806e4 100644 --- a/provisioner/shell-local/provisioner.go +++ b/provisioner/shell-local/provisioner.go @@ -1,11 +1,6 @@ package shell import ( - "fmt" - "path/filepath" - "runtime" - "strings" - sl "github.com/hashicorp/packer/common/shell-local" "github.com/hashicorp/packer/packer" ) @@ -19,44 +14,15 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { if err != nil { return err } - convertPath := false - if len(p.config.ExecuteCommand) == 0 && runtime.GOOS == "windows" { - convertPath = true - p.config.ExecuteCommand = []string{ - "bash", - "-c", - "{{.Vars}} {{.Script}}", - } - } err = sl.Validate(&p.config) if err != nil { return err } - if convertPath { - for index, script := range p.config.Scripts { - p.config.Scripts[index], err = convertToWindowsBashPath(script) - if err != nil { - return err - } - } - } - return nil } -func convertToWindowsBashPath(winPath string) (string, error) { - // get absolute path of script, and morph it into the bash path - winAbsPath, err := filepath.Abs(winPath) - if err != nil { - return "", fmt.Errorf("Error converting %s to absolute path: %s", winPath, err.Error()) - } - winAbsPath = strings.Replace(winAbsPath, "\\", "/", -1) - winBashPath := strings.Replace(winAbsPath, "C:/", "/mnt/c/", 1) - return winBashPath, nil -} - func (p *Provisioner) Provision(ui packer.Ui, _ packer.Communicator) error { _, retErr := sl.Run(ui, &p.config) if retErr != nil { diff --git a/website/source/docs/post-processors/shell-local.html.md b/website/source/docs/post-processors/shell-local.html.md index 26ef7871f..e2bc324da 100644 --- a/website/source/docs/post-processors/shell-local.html.md +++ b/website/source/docs/post-processors/shell-local.html.md @@ -60,7 +60,7 @@ Optional parameters: as well, which are covered in the section below. - `execute_command` (array of strings) - The command used to execute the script. By - default this is `["sh", "-c", "chmod +x \"{{.Script}}\"; {{.Vars}} \"{{.Script}}\""]` + default this is `["/bin/sh", "-c", "{{.Vars}}, "{{.Script}}"]` on unix and `["cmd", "/c", "{{.Vars}}", "{{.Script}}"]` on windows. This is treated as a [template engine](/docs/templates/engine.html). There are two available variables: `Script`, which is the path to the script @@ -69,7 +69,9 @@ Optional parameters: array is the shell program you want to use (for example, "sh" or "/usr/local/bin/zsh" or even "powershell.exe" although anything other than a flavor of the shell command language is not explicitly supported and may - be broken by assumptions made within Packer). + be broken by assumptions made within Packer). It's worth noting that if you + choose to try to use shell-local for Powershell or other Windows commands, + the environment variables will not be set properly for your environment. For backwards compatibility, `execute_command` will accept a string insetad of an array of strings. If a single string or an array of strings with only @@ -89,13 +91,46 @@ Optional parameters: **Important:** If you customize this, be sure to include something like the `-e` flag, otherwise individual steps failing won't fail the provisioner. -## Execute Command Example +- `use_linux_pathing` (bool) - This is only relevant to windows hosts. If you + are running Packer in a Windows environment with the Windows Subsystem for + Linux feature enabled, and would like to invoke a bash script rather than + invoking a Cmd script, you'll need to set this flag to true; it tells Packer + to use the linux subsystem path for your script rather than the Windows path. + (e.g. /mnt/c/path/to/your/file instead of C:/path/to/your/file). + +## Execute Command To many new users, the `execute_command` is puzzling. However, it provides an important function: customization of how the command is executed. The most common use case for this is dealing with **sudo password prompts**. You may also need to customize this if you use a non-POSIX shell, such as `tcsh` on FreeBSD. +### The Windows Linux Subsystem + +If you have a bash script that you'd like to run on your Windows Linux +Subsystem as part of the shell-local post-processor, you must set +`execute_command` and `use_linux_pathing`. + +The example below is a fully functional test config. + +``` +{ + "builders": [ + { + "type": "null", + "communicator": "none" + } + ], + "provisioners": [ + { + "type": "shell-local", + "environment_vars": ["PROVISIONERTEST=ProvisionerTest1"], + "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"] + "use_linux_pathing": true + "scripts": ["./scripts/.sh"] + }, +``` + ## Default Environmental Variables In addition to being able to specify custom environmental variables using the From dd183f22d9c78524884de923eaf791e5ca3c7eed Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Fri, 9 Mar 2018 15:14:52 -0800 Subject: [PATCH 13/22] update docs and add warnings around WSL limitations --- common/shell-local/config.go | 12 ++- .../docs/post-processors/shell-local.html.md | 36 +++++-- .../docs/provisioners/shell-local.html.md | 97 +++++++++++++++++++ 3 files changed, 136 insertions(+), 9 deletions(-) diff --git a/common/shell-local/config.go b/common/shell-local/config.go index 80751aee7..64dfe25c1 100644 --- a/common/shell-local/config.go +++ b/common/shell-local/config.go @@ -147,12 +147,20 @@ func Validate(config *Config) error { } if config.UseLinuxPathing { for index, script := range config.Scripts { - converted, err := convertToLinuxPath(script) + converted, err := ConvertToLinuxPath(script) if err != nil { return err } config.Scripts[index] = converted } + // Interoperability issues with WSL makes creating and running tempfiles + // via golang's os package basically impossible. + if len(config.Inline) > 0 { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("Packer is unable to use the Command and Inline "+ + "features with the Windows Linux Subsystem. Please use "+ + "the Script or Scripts options instead")) + } } // Do a check for bad environment variables, such as '=foo', 'foobar' @@ -172,7 +180,7 @@ func Validate(config *Config) error { } // C:/path/to/your/file becomes /mnt/c/path/to/your/file -func convertToLinuxPath(winPath string) (string, error) { +func ConvertToLinuxPath(winPath string) (string, error) { // get absolute path of script, and morph it into the bash path winAbsPath, err := filepath.Abs(winPath) if err != nil { diff --git a/website/source/docs/post-processors/shell-local.html.md b/website/source/docs/post-processors/shell-local.html.md index e2bc324da..ab2f4bba3 100644 --- a/website/source/docs/post-processors/shell-local.html.md +++ b/website/source/docs/post-processors/shell-local.html.md @@ -96,7 +96,10 @@ Optional parameters: Linux feature enabled, and would like to invoke a bash script rather than invoking a Cmd script, you'll need to set this flag to true; it tells Packer to use the linux subsystem path for your script rather than the Windows path. - (e.g. /mnt/c/path/to/your/file instead of C:/path/to/your/file). + (e.g. /mnt/c/path/to/your/file instead of C:/path/to/your/file). Please see + the example below for more guidance on how to use this feature. If you are + not on a Windows host, or you do not intend to use the shell-local + post-processor to run a bash script, please ignore this option. ## Execute Command @@ -107,12 +110,22 @@ need to customize this if you use a non-POSIX shell, such as `tcsh` on FreeBSD. ### The Windows Linux Subsystem -If you have a bash script that you'd like to run on your Windows Linux -Subsystem as part of the shell-local post-processor, you must set -`execute_command` and `use_linux_pathing`. +The shell-local post-processor was designed with the idea of allowing you to run +commands in your local operating system's native shell. For Windows, we've +assumed in our defaults that this is Cmd. However, it is possible to run a +bash script as part of the Windows Linux Subsystem from the shell-local +post-processor, by modifying the `execute_command` and the `use_linux_pathing` +options in the post-processor config. The example below is a fully functional test config. +One limitation of this offering is that "inline" and "command" options are not +available to you; please limit yourself to using the "script" or "scripts" +options instead. + +Please note that the WSL is a beta feature, and this tool is not guaranteed to +work as you expect it to. + ``` { "builders": [ @@ -125,10 +138,19 @@ The example below is a fully functional test config. { "type": "shell-local", "environment_vars": ["PROVISIONERTEST=ProvisionerTest1"], - "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"] - "use_linux_pathing": true - "scripts": ["./scripts/.sh"] + "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"], + "use_linux_pathing": true, + "scripts": ["C:/Users/me/scripts/example_bash.sh"] }, + { + "type": "shell-local", + "environment_vars": ["PROVISIONERTEST=ProvisionerTest2"], + "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"], + "use_linux_pathing": true, + "script": "C:/Users/me/scripts/example_bash.sh" + } + ] +} ``` ## Default Environmental Variables diff --git a/website/source/docs/provisioners/shell-local.html.md b/website/source/docs/provisioners/shell-local.html.md index bd66e74c7..f40c4e9f0 100644 --- a/website/source/docs/provisioners/shell-local.html.md +++ b/website/source/docs/provisioners/shell-local.html.md @@ -97,3 +97,100 @@ Optional parameters: you're not using `inline`, then this configuration has no effect. **Important:** If you customize this, be sure to include something like the `-e` flag, otherwise individual steps failing won't fail the provisioner. + +- `use_linux_pathing` (bool) - This is only relevant to windows hosts. If you + are running Packer in a Windows environment with the Windows Subsystem for + Linux feature enabled, and would like to invoke a bash script rather than + invoking a Cmd script, you'll need to set this flag to true; it tells Packer + to use the linux subsystem path for your script rather than the Windows path. + (e.g. /mnt/c/path/to/your/file instead of C:/path/to/your/file). Please see + the example below for more guidance on how to use this feature. If you are + not on a Windows host, or you do not intend to use the shell-local + provisioner to run a bash script, please ignore this option. + +## Execute Command + +To many new users, the `execute_command` is puzzling. However, it provides an +important function: customization of how the command is executed. The most +common use case for this is dealing with **sudo password prompts**. You may also +need to customize this if you use a non-POSIX shell, such as `tcsh` on FreeBSD. + +### The Windows Linux Subsystem + +The shell-local provisioner was designed with the idea of allowing you to run +commands in your local operating system's native shell. For Windows, we've +assumed in our defaults that this is Cmd. However, it is possible to run a +bash script as part of the Windows Linux Subsystem from the shell-local +provisioner, by modifying the `execute_command` and the `use_linux_pathing` +options in the provisioner config. + +The example below is a fully functional test config. + +One limitation of this offering is that "inline" and "command" options are not +available to you; please limit yourself to using the "script" or "scripts" +options instead. + +Please note that the WSL is a beta feature, and this tool is not guaranteed to +work as you expect it to. + +``` +{ + "builders": [ + { + "type": "null", + "communicator": "none" + } + ], + "provisioners": [ + { + "type": "shell-local", + "environment_vars": ["PROVISIONERTEST=ProvisionerTest1"], + "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"], + "use_linux_pathing": true, + "scripts": ["C:/Users/me/scripts/example_bash.sh"] + }, + { + "type": "shell-local", + "environment_vars": ["PROVISIONERTEST=ProvisionerTest2"], + "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"], + "use_linux_pathing": true, + "script": "C:/Users/me/scripts/example_bash.sh" + } + ] +} +``` + +## Default Environmental Variables + +In addition to being able to specify custom environmental variables using 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. + This is most useful when Packer is making multiple builds and you want to + distinguish them slightly from a common provisioning script. + +- `PACKER_BUILDER_TYPE` is the type of the builder that was used to create the + machine that the script is running on. This is useful if you want to run + only certain parts of the script on systems built with certain builders. + +## Safely Writing A Script + +Whether you use the `inline` option, or pass it a direct `script` or `scripts`, +it is important to understand a few things about how the shell-local +provisioner works to run it safely and easily. This understanding will save +you much time in the process. + +### Once Per Builder + +The `shell-local` script(s) you pass are run once per builder. That means that +if you have an `amazon-ebs` builder and a `docker` builder, your script will be +run twice. If you have 3 builders, it will run 3 times, once for each builder. + +### Always Exit Intentionally + +If any provisioner fails, the `packer build` stops and all interim artifacts +are cleaned up. + +For a shell script, that means the script **must** exit with a zero code. You +*must* be extra careful to `exit 0` when necessary. From 9651432378952cecf47e9b24647021809cb161bc Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Fri, 9 Mar 2018 15:36:11 -0800 Subject: [PATCH 14/22] preserver BC for people using 'command' option --- common/shell-local/run.go | 10 +++++--- .../docs/provisioners/shell-local.html.md | 25 +++++++++++-------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/common/shell-local/run.go b/common/shell-local/run.go index ea8043737..a16573e7a 100644 --- a/common/shell-local/run.go +++ b/common/shell-local/run.go @@ -15,8 +15,9 @@ import ( ) type ExecuteCommandTemplate struct { - Vars string - Script string + Vars string + Script string + Command string } func Run(ui packer.Ui, config *Config) (bool, error) { @@ -106,8 +107,9 @@ func createInlineScriptFile(config *Config) (string, error) { // the host OS func createInterpolatedCommands(config *Config, script string, flattenedEnvVars string) ([]string, error) { config.Ctx.Data = &ExecuteCommandTemplate{ - Vars: flattenedEnvVars, - Script: script, + Vars: flattenedEnvVars, + Script: script, + Command: script, } interpolatedCmds := make([]string, len(config.ExecuteCommand)) diff --git a/website/source/docs/provisioners/shell-local.html.md b/website/source/docs/provisioners/shell-local.html.md index f40c4e9f0..eae9cf2ff 100644 --- a/website/source/docs/provisioners/shell-local.html.md +++ b/website/source/docs/provisioners/shell-local.html.md @@ -78,18 +78,21 @@ Optional parameters: This is treated as a [template engine](/docs/templates/engine.html). There are two available variables: `Script`, which is the path to the script to run, and `Vars`, which is the list of `environment_vars`, if configured - If you choose to set this option, make sure that the first element in the - array is the shell program you want to use (for example, "sh" or - "/usr/local/bin/zsh" or even "powershell.exe" although anything other than - a flavor of the shell command language is not explicitly supported and may - be broken by assumptions made within Packer), and a later element in the - array must be `{{.Script}}`. - For backwards compatability, {{.Command}} is also available to use in - `execute_command` but it is decoded the same way as {{.Script}}. We - recommend using {{.Script}} for the sake of clarity, as even when you set - only a single `command` to run, Packer writes it to a temporary file and - then runs it as a script. + If you choose to set this option, make sure that the first element in the + array is the shell program you want to use (for example, "sh"), and a later + element in the array must be `{{.Script}}`. + + This option provides you a great deal of flexibility. You may choose to + provide your own shell program, for example "/usr/local/bin/zsh" or even + "powershell.exe". However, with great power comes great responsibility - + these commands are not officially supported and things like environment + variables may not work if you use a different shell than the default. + + For backwards compatability, you may also use {{.Command}}, but it is + decoded the same way as {{.Script}}. We recommend using {{.Script}} for the + sake of clarity, as even when you set only a single `command` to run, + Packer writes it to a temporary file and then runs it as a script. - `inline_shebang` (string) - The [shebang](http://en.wikipedia.org/wiki/Shebang_%28Unix%29) value to use when From fabd1a651771c099aec387f0f8b1fa9c5aec0ba1 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Mon, 12 Mar 2018 11:25:39 -0700 Subject: [PATCH 15/22] windows cmd env vars --- common/shell-local/config.go | 11 +++++++++ common/shell-local/run.go | 24 +++++++------------ .../shell-local/post-processor_test.go | 2 +- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/common/shell-local/config.go b/common/shell-local/config.go index 64dfe25c1..f4514a014 100644 --- a/common/shell-local/config.go +++ b/common/shell-local/config.go @@ -38,6 +38,8 @@ type Config struct { // An array of environment variables that will be injected before // your command(s) are executed. Vars []string `mapstructure:"environment_vars"` + + EnvVarFormat string // End dedupe with postprocessor // The command used to execute the script. The '{{ .Path }}' variable @@ -162,6 +164,15 @@ func Validate(config *Config) error { "the Script or Scripts options instead")) } } + // This is currently undocumented and not a feature users are expected to + // interact with. + if config.EnvVarFormat == "" { + if (runtime.GOOS == "windows") && !config.UseLinuxPathing { + config.EnvVarFormat = `set "%s=%s" && ` + } else { + config.EnvVarFormat = "%s='%s' " + } + } // Do a check for bad environment variables, such as '=foo', 'foobar' for _, kv := range config.Vars { diff --git a/common/shell-local/run.go b/common/shell-local/run.go index a16573e7a..9e17d2d87 100644 --- a/common/shell-local/run.go +++ b/common/shell-local/run.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "log" "os" - "runtime" "sort" "strings" @@ -36,7 +35,10 @@ func Run(ui packer.Ui, config *Config) (bool, error) { } // Create environment variables to set before executing the command - flattenedEnvVars := createFlattenedEnvVars(config) + flattenedEnvVars, err := createFlattenedEnvVars(config) + if err != nil { + return false, err + } for _, script := range scripts { interpolatedCmds, err := createInterpolatedCommands(config, script, flattenedEnvVars) @@ -123,8 +125,8 @@ func createInterpolatedCommands(config *Config, script string, flattenedEnvVars return interpolatedCmds, nil } -func createFlattenedEnvVars(config *Config) (flattened string) { - flattened = "" +func createFlattenedEnvVars(config *Config) (string, error) { + flattened := "" envVars := make(map[string]string) // Always available Packer provided env vars @@ -146,18 +148,8 @@ func createFlattenedEnvVars(config *Config) (flattened string) { } sort.Strings(keys) - // Re-assemble vars surrounding value with single quotes and flatten - if runtime.GOOS == "windows" { - log.Printf("MEGAN NEED TO IMPLEMENT") - // createEnvVarsSourceFileWindows() - } for _, key := range keys { - flattened += fmt.Sprintf("%s='%s' ", key, envVars[key]) + flattened += fmt.Sprintf(config.EnvVarFormat, key, envVars[key]) } - return + return flattened, nil } - -// func createFlattenedEnvVarsWindows( -// // The default shell, cmd, can set vars via dot sourcing -// // set TESTXYZ=XYZ -// ) diff --git a/post-processor/shell-local/post-processor_test.go b/post-processor/shell-local/post-processor_test.go index afec79f81..5fabac124 100644 --- a/post-processor/shell-local/post-processor_test.go +++ b/post-processor/shell-local/post-processor_test.go @@ -139,7 +139,7 @@ func TestPostProcessorPrepare_ExecuteCommand(t *testing.T) { p = new(PostProcessor) p.Configure(raws) if runtime.GOOS != "windows" { - expected = []string{"sh", "-c", `chmod +x "{{.Script}}"; {{.Vars}} "{{.Script}}"`} + expected = []string{"/bin/sh", "-c", "{{.Vars}}", "{{.Script}}"} } else { expected = []string{"cmd", "/C", "{{.Vars}}", "{{.Script}}"} } From 1bea658e16cb5a45ea043b78276e81f2c4ec62db Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 4 Apr 2018 11:07:10 -0700 Subject: [PATCH 16/22] fix command and inline calls on windows --- common/shell-local/config.go | 19 +++- common/shell-local/run.go | 17 ++- .../docs/post-processors/shell-local.html.md | 102 ++++++++++++++++++ .../docs/provisioners/shell-local.html.md | 101 +++++++++++++++++ 4 files changed, 231 insertions(+), 8 deletions(-) diff --git a/common/shell-local/config.go b/common/shell-local/config.go index f4514a014..846e4b4a4 100644 --- a/common/shell-local/config.go +++ b/common/shell-local/config.go @@ -29,6 +29,9 @@ type Config struct { // The shebang value used when running inline scripts. InlineShebang string `mapstructure:"inline_shebang"` + // The file extension to use for the file generated from the inline commands + TempfileExtension string `mapstructure:"tempfile_extension"` + // The local path of the shell script to upload and execute. Script string @@ -39,7 +42,7 @@ type Config struct { // your command(s) are executed. Vars []string `mapstructure:"environment_vars"` - EnvVarFormat string + EnvVarFormat string `mapstructure:"env_var_format"` // End dedupe with postprocessor // The command used to execute the script. The '{{ .Path }}' variable @@ -76,8 +79,10 @@ func Validate(config *Config) error { if len(config.ExecuteCommand) == 0 { config.ExecuteCommand = []string{ "cmd", + "/V", "/C", "{{.Vars}}", + "call", "{{.Script}}", } } @@ -89,8 +94,7 @@ func Validate(config *Config) error { config.ExecuteCommand = []string{ "/bin/sh", "-c", - "{{.Vars}}", - "{{.Script}}", + "{{.Vars}} {{.Script}}", } } } @@ -168,12 +172,19 @@ func Validate(config *Config) error { // interact with. if config.EnvVarFormat == "" { if (runtime.GOOS == "windows") && !config.UseLinuxPathing { - config.EnvVarFormat = `set "%s=%s" && ` + config.EnvVarFormat = "set %s=%s && " } else { config.EnvVarFormat = "%s='%s' " } } + // drop unnecessary "." in extension; we add this later. + if config.TempfileExtension != "" { + if strings.HasPrefix(config.TempfileExtension, ".") { + config.TempfileExtension = config.TempfileExtension[1:] + } + } + // Do a check for bad environment variables, such as '=foo', 'foobar' for _, kv := range config.Vars { vs := strings.SplitN(kv, "=", 2) diff --git a/common/shell-local/run.go b/common/shell-local/run.go index 9e17d2d87..6af406522 100644 --- a/common/shell-local/run.go +++ b/common/shell-local/run.go @@ -30,8 +30,13 @@ func Run(ui packer.Ui, config *Config) (bool, error) { if err != nil { return false, err } - defer os.Remove(tempScriptFileName) scripts = append(scripts, tempScriptFileName) + + defer os.Remove(tempScriptFileName) + // figure out what extension the file should have, and rename it. + if config.TempfileExtension != "" { + os.Rename(tempScriptFileName, fmt.Sprintf("%s.%s", tempScriptFileName, config.TempfileExtension)) + } } // Create environment variables to set before executing the command @@ -78,14 +83,18 @@ func Run(ui packer.Ui, config *Config) (bool, error) { } func createInlineScriptFile(config *Config) (string, error) { - tf, err := ioutil.TempFile("", "packer-shell") + tf, err := ioutil.TempFile(os.TempDir(), "packer-shell") if err != nil { return "", fmt.Errorf("Error preparing shell script: %s", err) } - + defer tf.Close() // Write our contents to it writer := bufio.NewWriter(tf) - writer.WriteString(fmt.Sprintf("#!%s\n", config.InlineShebang)) + if config.InlineShebang != "" { + shebang := fmt.Sprintf("#!%s\n", config.InlineShebang) + log.Printf("Prepending inline script with %s", shebang) + writer.WriteString(shebang) + } for _, command := range config.Inline { if _, err := writer.WriteString(command + "\n"); err != nil { return "", fmt.Errorf("Error preparing shell script: %s", err) diff --git a/website/source/docs/post-processors/shell-local.html.md b/website/source/docs/post-processors/shell-local.html.md index ab2f4bba3..3ace72792 100644 --- a/website/source/docs/post-processors/shell-local.html.md +++ b/website/source/docs/post-processors/shell-local.html.md @@ -227,3 +227,105 @@ are cleaned up. For a shell script, that means the script **must** exit with a zero code. You *must* be extra careful to `exit 0` when necessary. + + +## Usage Examples: + +Example of running a .cmd file on windows: + +``` + { + "type": "shell-local", + "environment_vars": ["SHELLLOCALTEST=ShellTest1"], + "scripts": ["./scripts/test_cmd.cmd"] + }, +``` + +Contents of "test_cmd.cmd": + +``` +echo %SHELLLOCALTEST% +``` + +Example of running an inline command on windows: +Required customization: tempfile_extension + +``` + { + "type": "shell-local", + "environment_vars": ["SHELLLOCALTEST=ShellTest2"], + "tempfile_extension": ".cmd", + "inline": ["echo %SHELLLOCALTEST%"] + }, +``` + +Example of running a bash command on windows using WSL: +Required customizations: use_linux_pathing and execute_command + +``` + { + "type": "shell-local", + "environment_vars": ["SHELLLOCALTEST=ShellTest3"], + "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"], + "use_linux_pathing": true, + "script": "./scripts/example_bash.sh" + } +``` + +Contents of "example_bash.sh": + +``` +#!/bin/bash +echo $SHELLLOCALTEST +``` + +Example of running a powershell script on windows: +Required customizations: env_var_format and execute_command + +``` + + { + "type": "shell-local", + "environment_vars": ["SHELLLOCALTEST=ShellTest4"], + "execute_command": ["powershell.exe", "{{.Vars}} {{.Script}}"], + "env_var_format": "$env:%s=\"%s\"; ", + } +``` + +Example of running a powershell script on windows as "inline": +Required customizations: env_var_format, tempfile_extension, and execute_command + +``` + { + "type": "shell-local", + "tempfile_extension": ".ps1", + "environment_vars": ["SHELLLOCALTEST=ShellTest5"], + "execute_command": ["powershell.exe", "{{.Vars}} {{.Script}}"], + "env_var_format": "$env:%s=\"%s\"; ", + "inline": ["write-output $env:SHELLLOCALTEST"] + } +``` + + +Example of running a bash script on linux: + +``` + { + "type": "shell-local", + "environment_vars": ["PROVISIONERTEST=ProvisionerTest1"], + "scripts": ["./scripts/dummy_bash.sh"] + } +``` + +Example of running a bash "inline" on linux: + +``` + { + "type": "shell-local", + "environment_vars": ["PROVISIONERTEST=ProvisionerTest2"], + "inline": ["echo hello", + "echo $PROVISIONERTEST"] + } +``` + + diff --git a/website/source/docs/provisioners/shell-local.html.md b/website/source/docs/provisioners/shell-local.html.md index eae9cf2ff..cadb1d6a1 100644 --- a/website/source/docs/provisioners/shell-local.html.md +++ b/website/source/docs/provisioners/shell-local.html.md @@ -197,3 +197,104 @@ are cleaned up. For a shell script, that means the script **must** exit with a zero code. You *must* be extra careful to `exit 0` when necessary. + + +## Usage Examples: + +Example of running a .cmd file on windows: + +``` + { + "type": "shell-local", + "environment_vars": ["SHELLLOCALTEST=ShellTest1"], + "scripts": ["./scripts/test_cmd.cmd"] + }, +``` + +Contents of "test_cmd.cmd": + +``` +echo %SHELLLOCALTEST% +``` + +Example of running an inline command on windows: +Required customization: tempfile_extension + +``` + { + "type": "shell-local", + "environment_vars": ["SHELLLOCALTEST=ShellTest2"], + "tempfile_extension": ".cmd", + "inline": ["echo %SHELLLOCALTEST%"] + }, +``` + +Example of running a bash command on windows using WSL: +Required customizations: use_linux_pathing and execute_command + +``` + { + "type": "shell-local", + "environment_vars": ["SHELLLOCALTEST=ShellTest3"], + "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"], + "use_linux_pathing": true, + "script": "./scripts/example_bash.sh" + } +``` + +Contents of "example_bash.sh": + +``` +#!/bin/bash +echo $SHELLLOCALTEST +``` + +Example of running a powershell script on windows: +Required customizations: env_var_format and execute_command + +``` + + { + "type": "shell-local", + "environment_vars": ["SHELLLOCALTEST=ShellTest4"], + "execute_command": ["powershell.exe", "{{.Vars}} {{.Script}}"], + "env_var_format": "$env:%s=\"%s\"; ", + } +``` + +Example of running a powershell script on windows as "inline": +Required customizations: env_var_format, tempfile_extension, and execute_command + +``` + { + "type": "shell-local", + "tempfile_extension": ".ps1", + "environment_vars": ["SHELLLOCALTEST=ShellTest5"], + "execute_command": ["powershell.exe", "{{.Vars}} {{.Script}}"], + "env_var_format": "$env:%s=\"%s\"; ", + "inline": ["write-output $env:SHELLLOCALTEST"] + } +``` + + +Example of running a bash script on linux: + +``` + { + "type": "shell-local", + "environment_vars": ["PROVISIONERTEST=ProvisionerTest1"], + "scripts": ["./scripts/dummy_bash.sh"] + } +``` + +Example of running a bash "inline" on linux: + +``` + { + "type": "shell-local", + "environment_vars": ["PROVISIONERTEST=ProvisionerTest2"], + "inline": ["echo hello", + "echo $PROVISIONERTEST"] + } +``` + From 2b2bd5715c96814caa88b631290945140bfe87e0 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 4 Apr 2018 15:29:37 -0700 Subject: [PATCH 17/22] fix docs --- .../source/docs/post-processors/shell-local.html.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/website/source/docs/post-processors/shell-local.html.md b/website/source/docs/post-processors/shell-local.html.md index 3ace72792..6812fac2b 100644 --- a/website/source/docs/post-processors/shell-local.html.md +++ b/website/source/docs/post-processors/shell-local.html.md @@ -100,6 +100,8 @@ Optional parameters: the example below for more guidance on how to use this feature. If you are not on a Windows host, or you do not intend to use the shell-local post-processor to run a bash script, please ignore this option. + If you set this flag to true, you still need to provide the standard windows + path to the script when providing a `script`. This is a beta feature. ## Execute Command @@ -123,8 +125,10 @@ One limitation of this offering is that "inline" and "command" options are not available to you; please limit yourself to using the "script" or "scripts" options instead. -Please note that the WSL is a beta feature, and this tool is not guaranteed to -work as you expect it to. +Please note that this feature is still in beta, as the underlying WSL is also +still in beta. There will be some limitations as a result. For example, it will +likely not work unless both Packer and the scripts you want to run are both on +the C drive. ``` { @@ -289,6 +293,7 @@ Required customizations: env_var_format and execute_command "environment_vars": ["SHELLLOCALTEST=ShellTest4"], "execute_command": ["powershell.exe", "{{.Vars}} {{.Script}}"], "env_var_format": "$env:%s=\"%s\"; ", + "script": "./scripts/example_ps.ps1" } ``` @@ -313,7 +318,7 @@ Example of running a bash script on linux: { "type": "shell-local", "environment_vars": ["PROVISIONERTEST=ProvisionerTest1"], - "scripts": ["./scripts/dummy_bash.sh"] + "scripts": ["./scripts/example_bash.sh"] } ``` From 58acb7f436cee34c350ff36e3c4084ee24267221 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 4 Apr 2018 15:52:39 -0700 Subject: [PATCH 18/22] fix windows test --- post-processor/shell-local/post-processor_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post-processor/shell-local/post-processor_test.go b/post-processor/shell-local/post-processor_test.go index 5fabac124..ee7e27d70 100644 --- a/post-processor/shell-local/post-processor_test.go +++ b/post-processor/shell-local/post-processor_test.go @@ -141,7 +141,7 @@ func TestPostProcessorPrepare_ExecuteCommand(t *testing.T) { if runtime.GOOS != "windows" { expected = []string{"/bin/sh", "-c", "{{.Vars}}", "{{.Script}}"} } else { - expected = []string{"cmd", "/C", "{{.Vars}}", "{{.Script}}"} + expected = []string{"cmd", "/V", "/C", "{{.Vars}}", "call", "{{.Script}}"} } if strings.Compare(strings.Join(p.config.ExecuteCommand, " "), strings.Join(expected, " ")) != 0 { t.Fatalf("Did not get expected default: expected: %#v; received %#v", expected, p.config.ExecuteCommand) From 1fdf763d0f5ddd6e2541a0b825dfce1ed13b61c2 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Mon, 21 May 2018 11:25:51 -0700 Subject: [PATCH 19/22] fancier logging --- common/shell-local/communicator.go | 2 +- common/shell-local/run.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/shell-local/communicator.go b/common/shell-local/communicator.go index b51d309d9..4055c96b5 100644 --- a/common/shell-local/communicator.go +++ b/common/shell-local/communicator.go @@ -21,7 +21,7 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error { } // Build the local command to execute - log.Printf("Executing local shell command %s", c.ExecuteCommand) + log.Printf("[INFO] (shell-local communicator): Executing local shell command %s", c.ExecuteCommand) localCmd := exec.Command(c.ExecuteCommand[0], c.ExecuteCommand[1:]...) localCmd.Stdin = cmd.Stdin localCmd.Stdout = cmd.Stdout diff --git a/common/shell-local/run.go b/common/shell-local/run.go index 6af406522..0457536fa 100644 --- a/common/shell-local/run.go +++ b/common/shell-local/run.go @@ -62,7 +62,7 @@ func Run(ui packer.Ui, config *Config) (bool, error) { // buffers and for reading the final exit status. flattenedCmd := strings.Join(interpolatedCmds, " ") cmd := &packer.RemoteCmd{Command: flattenedCmd} - log.Printf("starting local command: %s", flattenedCmd) + log.Printf("[INFO] (shell-local): starting local command: %s", flattenedCmd) if err := cmd.StartWithUi(comm, ui); err != nil { return false, fmt.Errorf( @@ -92,7 +92,7 @@ func createInlineScriptFile(config *Config) (string, error) { writer := bufio.NewWriter(tf) if config.InlineShebang != "" { shebang := fmt.Sprintf("#!%s\n", config.InlineShebang) - log.Printf("Prepending inline script with %s", shebang) + log.Printf("[INFO] (shell-local): Prepending inline script with %s", shebang) writer.WriteString(shebang) } for _, command := range config.Inline { @@ -108,7 +108,7 @@ func createInlineScriptFile(config *Config) (string, error) { tf.Close() err = os.Chmod(tf.Name(), 0555) if err != nil { - log.Printf("error modifying permissions of temp script file: %s", err.Error()) + log.Printf("[ERROR] (shell-local): error modifying permissions of temp script file: %s", err.Error()) } return tf.Name(), nil } From 969201a2d4e60e3d26e682355417c12e0484aa9f Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Mon, 21 May 2018 14:56:44 -0700 Subject: [PATCH 20/22] handle minor shell-local PR suggestions and corrections --- common/shell-local/config.go | 12 ++++++------ common/shell-local/run.go | 8 ++++---- .../source/docs/post-processors/shell-local.html.md | 2 +- website/source/docs/provisioners/shell-local.html.md | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/common/shell-local/config.go b/common/shell-local/config.go index 846e4b4a4..9eb657ff9 100644 --- a/common/shell-local/config.go +++ b/common/shell-local/config.go @@ -153,7 +153,11 @@ func Validate(config *Config) error { } if config.UseLinuxPathing { for index, script := range config.Scripts { - converted, err := ConvertToLinuxPath(script) + scriptAbsPath, err := filepath.Abs(script) + if err != nil { + return fmt.Errorf("Error converting %s to absolute path: %s", script, err.Error()) + } + converted, err := ConvertToLinuxPath(scriptAbsPath) if err != nil { return err } @@ -202,12 +206,8 @@ func Validate(config *Config) error { } // C:/path/to/your/file becomes /mnt/c/path/to/your/file -func ConvertToLinuxPath(winPath string) (string, error) { +func ConvertToLinuxPath(winAbsPath string) (string, error) { // get absolute path of script, and morph it into the bash path - winAbsPath, err := filepath.Abs(winPath) - if err != nil { - return "", fmt.Errorf("Error converting %s to absolute path: %s", winPath, err.Error()) - } winAbsPath = strings.Replace(winAbsPath, "\\", "/", -1) splitPath := strings.SplitN(winAbsPath, ":/", 2) winBashPath := fmt.Sprintf("/mnt/%s/%s", strings.ToLower(splitPath[0]), splitPath[1]) diff --git a/common/shell-local/run.go b/common/shell-local/run.go index 0457536fa..7ab93e346 100644 --- a/common/shell-local/run.go +++ b/common/shell-local/run.go @@ -32,11 +32,12 @@ func Run(ui packer.Ui, config *Config) (bool, error) { } scripts = append(scripts, tempScriptFileName) - defer os.Remove(tempScriptFileName) // figure out what extension the file should have, and rename it. if config.TempfileExtension != "" { os.Rename(tempScriptFileName, fmt.Sprintf("%s.%s", tempScriptFileName, config.TempfileExtension)) + tempScriptFileName = fmt.Sprintf("%s.%s", tempScriptFileName, config.TempfileExtension) } + defer os.Remove(tempScriptFileName) } // Create environment variables to set before executing the command @@ -83,7 +84,7 @@ func Run(ui packer.Ui, config *Config) (bool, error) { } func createInlineScriptFile(config *Config) (string, error) { - tf, err := ioutil.TempFile(os.TempDir(), "packer-shell") + tf, err := ioutil.TempFile("", "packer-shell") if err != nil { return "", fmt.Errorf("Error preparing shell script: %s", err) } @@ -105,8 +106,7 @@ func createInlineScriptFile(config *Config) (string, error) { return "", fmt.Errorf("Error preparing shell script: %s", err) } - tf.Close() - err = os.Chmod(tf.Name(), 0555) + err = os.Chmod(tf.Name(), 0700) if err != nil { log.Printf("[ERROR] (shell-local): error modifying permissions of temp script file: %s", err.Error()) } diff --git a/website/source/docs/post-processors/shell-local.html.md b/website/source/docs/post-processors/shell-local.html.md index 6812fac2b..ac8056407 100644 --- a/website/source/docs/post-processors/shell-local.html.md +++ b/website/source/docs/post-processors/shell-local.html.md @@ -73,7 +73,7 @@ Optional parameters: choose to try to use shell-local for Powershell or other Windows commands, the environment variables will not be set properly for your environment. - For backwards compatibility, `execute_command` will accept a string insetad + For backwards compatibility, `execute_command` will accept a string instead of an array of strings. If a single string or an array of strings with only one element is provided, Packer will replicate past behavior by appending your `execute_command` to the array of strings `["sh", "-c"]`. For example, diff --git a/website/source/docs/provisioners/shell-local.html.md b/website/source/docs/provisioners/shell-local.html.md index cadb1d6a1..a7400c589 100644 --- a/website/source/docs/provisioners/shell-local.html.md +++ b/website/source/docs/provisioners/shell-local.html.md @@ -89,7 +89,7 @@ Optional parameters: these commands are not officially supported and things like environment variables may not work if you use a different shell than the default. - For backwards compatability, you may also use {{.Command}}, but it is + For backwards compatibility, you may also use {{.Command}}, but it is decoded the same way as {{.Script}}. We recommend using {{.Script}} for the sake of clarity, as even when you set only a single `command` to run, Packer writes it to a temporary file and then runs it as a script. From d1e31c0f2360f8460aae16ed934ad7a35addb3db Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Mon, 21 May 2018 15:19:27 -0700 Subject: [PATCH 21/22] use if/else to clarify code --- common/shell-local/run.go | 10 ++++---- .../Azure/azure-sdk-for-go/storage/README.md | 1 + vendor/vendor.json | 24 +++++++++---------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/common/shell-local/run.go b/common/shell-local/run.go index 7ab93e346..b65196ea9 100644 --- a/common/shell-local/run.go +++ b/common/shell-local/run.go @@ -21,11 +21,11 @@ type ExecuteCommandTemplate struct { func Run(ui packer.Ui, config *Config) (bool, error) { scripts := make([]string, len(config.Scripts)) - copy(scripts, config.Scripts) - - // If we have an inline script, then turn that into a temporary - // shell script and use that. - if config.Inline != nil { + if len(config.Scripts) > 0 { + copy(scripts, config.Scripts) + } else if config.Inline != nil { + // If we have an inline script, then turn that into a temporary + // shell script and use that. tempScriptFileName, err := createInlineScriptFile(config) if err != nil { return false, err diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/README.md b/vendor/github.com/Azure/azure-sdk-for-go/storage/README.md index ed90cf8bc..49e48cdf1 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/storage/README.md +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/README.md @@ -15,3 +15,4 @@ at [github.com/Azure/azure-sdk-for-go/services/storage](https://github.com/Azure This package also supports the [Azure Storage Emulator](https://azure.microsoft.com/documentation/articles/storage-use-emulator/) (Windows only). + diff --git a/vendor/vendor.json b/vendor/vendor.json index a979c6b6f..1f08d096b 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -9,7 +9,7 @@ "revisionTime": "2016-08-11T22:04:02Z" }, { - "checksumSHA1": "XZVCJXyy79hy5KBOI6flZ6iHnHY=", + "checksumSHA1": "cJxhrzJRtddboU3S0TPyvEPBqsc=", "path": "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute", "revision": "56332fec5b308fbb6615fa1af6117394cdba186d", "revisionTime": "2018-03-26T23:29:47Z", @@ -17,7 +17,7 @@ "versionExact": "v15.0.0" }, { - "checksumSHA1": "738URn/O+S8TN9psssjK7cteZXA=", + "checksumSHA1": "VDwUBYd9RVKy09Y17al0EQ7ivYI=", "path": "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-01-01/network", "revision": "56332fec5b308fbb6615fa1af6117394cdba186d", "revisionTime": "2018-03-26T23:29:47Z", @@ -25,7 +25,7 @@ "versionExact": "v15.0.0" }, { - "checksumSHA1": "KDrlouaRfBHk+qH/yljC0JnsV4Y=", + "checksumSHA1": "woz67BK+/NdoZm4GzVYnJwzl61A=", "path": "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-06-01/subscriptions", "revision": "56332fec5b308fbb6615fa1af6117394cdba186d", "revisionTime": "2018-03-26T23:29:47Z", @@ -33,7 +33,7 @@ "versionExact": "v15.0.0" }, { - "checksumSHA1": "BMd5SfQ0KfqEUvi9zAt+QAB/JPQ=", + "checksumSHA1": "1W8UIxg6Rycuzg41FQFu35vkCEU=", "path": "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-02-01/resources", "revision": "56332fec5b308fbb6615fa1af6117394cdba186d", "revisionTime": "2018-03-26T23:29:47Z", @@ -41,7 +41,7 @@ "versionExact": "v15.0.0" }, { - "checksumSHA1": "qHMzicMTsihjgKyS/VB8oguXmmc=", + "checksumSHA1": "g9eP5AgV9yXRkY36M8h7aDW9oi8=", "path": "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2017-10-01/storage", "revision": "56332fec5b308fbb6615fa1af6117394cdba186d", "revisionTime": "2018-03-26T23:29:47Z", @@ -49,7 +49,7 @@ "versionExact": "v15.0.0" }, { - "checksumSHA1": "s/831Hsxh0h6PCHCoMOiOdh1Hwg=", + "checksumSHA1": "3N5Et8QnWsHJYN+v/0J/VSQUkJ0=", "path": "github.com/Azure/azure-sdk-for-go/storage", "revision": "56332fec5b308fbb6615fa1af6117394cdba186d", "revisionTime": "2018-03-26T23:29:47Z", @@ -57,13 +57,13 @@ "versionExact": "v15.0.0" }, { - "checksumSHA1": "kbpNrLhdZinIK0H1vsJh7eSB2JM=", + "checksumSHA1": "Fb2OanEbwZVaGHYLf9Y4FAajsOM=", "path": "github.com/Azure/azure-sdk-for-go/version", "revision": "56332fec5b308fbb6615fa1af6117394cdba186d", "revisionTime": "2018-03-26T23:29:47Z" }, { - "checksumSHA1": "LaWzRZq1p8T0iqZTD4+QL7qlJPg=", + "checksumSHA1": "+P6HOINDh/n2z4GqEkluzuGP5p0=", "comment": "v7.0.7", "path": "github.com/Azure/go-autorest/autorest", "revision": "ed4b7f5bf1ec0c9ede1fda2681d96771282f2862", @@ -72,7 +72,7 @@ "versionExact": "v10.4.0" }, { - "checksumSHA1": "HzA52MbMWnsR31CFrub5biN90/Q=", + "checksumSHA1": "4Z3yO++uYspufDkuaIydTpT787c=", "path": "github.com/Azure/go-autorest/autorest/adal", "revision": "ed4b7f5bf1ec0c9ede1fda2681d96771282f2862", "revisionTime": "2018-03-26T17:06:54Z", @@ -80,7 +80,7 @@ "versionExact": "v10.4.0" }, { - "checksumSHA1": "5698vgeScEFD2bOOCssAfMFP4Mg=", + "checksumSHA1": "bDFbLGwpCT8TRmqEKtPY/U1DAY8=", "comment": "v7.0.7", "path": "github.com/Azure/go-autorest/autorest/azure", "revision": "ed4b7f5bf1ec0c9ede1fda2681d96771282f2862", @@ -107,7 +107,7 @@ "versionExact": "v8.0.0" }, { - "checksumSHA1": "CdDkG+J8wqXQVQ0f0xal+eolB1w=", + "checksumSHA1": "5UH4IFIB/98iowPCzzVs4M4MXiQ=", "path": "github.com/Azure/go-autorest/autorest/validation", "revision": "ed4b7f5bf1ec0c9ede1fda2681d96771282f2862", "revisionTime": "2018-03-26T17:06:54Z", @@ -965,7 +965,7 @@ "revision": "2788f0dbd16903de03cb8186e5c7d97b69ad387b" }, { - "checksumSHA1": "9Ok54so+GJLC4rMpb7XqZzlfieI=", + "checksumSHA1": "T9E+5mKBQ/BX4wlNxgaPfetxdeI=", "path": "github.com/marstr/guid", "revision": "8bdf7d1a087ccc975cf37dd6507da50698fd19ca", "revisionTime": "2017-04-27T23:51:15Z" From 7e9a653da7d565a51ee11e20c83e7b6f016fbd7c Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Mon, 21 May 2018 15:26:57 -0700 Subject: [PATCH 22/22] use testify instead of homegrown string compare --- .../shell-local/post-processor_test.go | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/post-processor/shell-local/post-processor_test.go b/post-processor/shell-local/post-processor_test.go index ee7e27d70..515704f9d 100644 --- a/post-processor/shell-local/post-processor_test.go +++ b/post-processor/shell-local/post-processor_test.go @@ -4,10 +4,10 @@ import ( "io/ioutil" "os" "runtime" - "strings" "testing" "github.com/hashicorp/packer/packer" + "github.com/stretchr/testify/assert" ) func TestPostProcessor_ImplementsPostProcessor(t *testing.T) { @@ -116,9 +116,8 @@ func TestPostProcessorPrepare_ExecuteCommand(t *testing.T) { if err != nil { t.Fatalf("should handle backwards compatibility: %s", err) } - if strings.Compare(strings.Join(p.config.ExecuteCommand, " "), strings.Join(expected, " ")) != 0 { - t.Fatalf("Did not get expected execute_command: expected: %#v; received %#v", expected, p.config.ExecuteCommand) - } + assert.Equal(t, p.config.ExecuteCommand, expected, + "Did not get expected execute_command: expected: %#v; received %#v", expected, p.config.ExecuteCommand) // Check that passing a list will work p = new(PostProcessor) @@ -129,9 +128,8 @@ func TestPostProcessorPrepare_ExecuteCommand(t *testing.T) { t.Fatalf("should handle backwards compatibility: %s", err) } expected = []string{"foo", "bar"} - if strings.Compare(strings.Join(p.config.ExecuteCommand, " "), strings.Join(expected, " ")) != 0 { - t.Fatalf("Did not get expected execute_command: expected: %#v; received %#v", expected, p.config.ExecuteCommand) - } + assert.Equal(t, p.config.ExecuteCommand, expected, + "Did not get expected execute_command: expected: %#v; received %#v", expected, p.config.ExecuteCommand) // Check that default is as expected raws = testConfig() @@ -139,13 +137,12 @@ func TestPostProcessorPrepare_ExecuteCommand(t *testing.T) { p = new(PostProcessor) p.Configure(raws) if runtime.GOOS != "windows" { - expected = []string{"/bin/sh", "-c", "{{.Vars}}", "{{.Script}}"} + expected = []string{"/bin/sh", "-c", "{{.Vars}} {{.Script}}"} } else { expected = []string{"cmd", "/V", "/C", "{{.Vars}}", "call", "{{.Script}}"} } - if strings.Compare(strings.Join(p.config.ExecuteCommand, " "), strings.Join(expected, " ")) != 0 { - t.Fatalf("Did not get expected default: expected: %#v; received %#v", expected, p.config.ExecuteCommand) - } + assert.Equal(t, p.config.ExecuteCommand, expected, + "Did not get expected default: expected: %#v; received %#v", expected, p.config.ExecuteCommand) } func TestPostProcessorPrepare_ScriptAndInline(t *testing.T) {