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, }