mirror of
https://github.com/hashicorp/packer.git
synced 2026-10-01 20:24:00 -04:00
The following commits build on work from @vtolstov to create a post processor shell-local plugin. Please see his original work over at https://github.com/vtolstov/packer-post-processor-shell I have modified it slightly to output information onto the packer ui as shown in the below screenshot which executes a script that runs env. This plugin enables users to submit environmental variables to external external shell script(s) to do some post processing e.g. (Upload to somewhere, convert to different format, and so on) Most of the work is a merge from the provisioner shell and shell-local scripts.  Signed-off-by: Ian Duffy <[email protected]>
214 lines
5.6 KiB
Go
214 lines
5.6 KiB
Go
package shell_local
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/mitchellh/packer/common"
|
|
"github.com/mitchellh/packer/helper/config"
|
|
"github.com/mitchellh/packer/packer"
|
|
"github.com/mitchellh/packer/template/interpolate"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
type ExecuteCommandTemplate struct {
|
|
Vars string
|
|
Path string
|
|
}
|
|
|
|
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...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if p.config.ExecuteCommand == "" {
|
|
p.config.ExecuteCommand = "chmod +x {{.Path}}; {{.Vars}} {{.Path}}"
|
|
}
|
|
|
|
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 idx, 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))
|
|
} else {
|
|
// Replace single quotes so they parse
|
|
vs[1] = strings.Replace(vs[1], "'", `'"'"'`, -1)
|
|
|
|
// Single quote env var values
|
|
p.config.Vars[idx] = fmt.Sprintf("%s='%s'", vs[0], vs[1])
|
|
}
|
|
}
|
|
|
|
if errs != nil && len(errs.Errors) > 0 {
|
|
return errs
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
|
|
|
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()
|
|
}
|
|
|
|
// Build our variables up by adding in the build name and builder type
|
|
envVars := make([]string, len(p.config.Vars)+2)
|
|
envVars[0] = fmt.Sprintf("PACKER_BUILD_NAME='%s'", p.config.PackerBuildName)
|
|
envVars[1] = fmt.Sprintf("PACKER_BUILDER_TYPE='%s'", p.config.PackerBuilderType)
|
|
copy(envVars[2:], p.config.Vars)
|
|
|
|
for _, path := range scripts {
|
|
ui.Say(fmt.Sprintf("Post processing with local shell script: %s", path))
|
|
|
|
// Flatten the environment variables
|
|
flattendVars := strings.Join(envVars, " ")
|
|
|
|
p.config.ctx.Data = &ExecuteCommandTemplate{
|
|
Vars: flattendVars,
|
|
Path: path,
|
|
}
|
|
|
|
command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
|
|
if err != nil {
|
|
return nil, false, fmt.Errorf("Error processing command: %s", err)
|
|
}
|
|
|
|
comm := &Communicator{}
|
|
|
|
cmd := &packer.RemoteCmd{Command: command}
|
|
|
|
ui.Say(fmt.Sprintf(
|
|
"Executing local script: %s",
|
|
path))
|
|
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.",
|
|
path)
|
|
}
|
|
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,
|
|
path)
|
|
}
|
|
|
|
}
|
|
|
|
return nil, true, nil
|
|
}
|