github.com/marksheahan/packer@v0.10.2-0.20160613200515-1acb2d6645a0/post-processor/shell-local/post-processor.go (about)

     1  package shell_local
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"strings"
    10  
    11  	"github.com/mitchellh/packer/common"
    12  	"github.com/mitchellh/packer/helper/config"
    13  	"github.com/mitchellh/packer/packer"
    14  	"github.com/mitchellh/packer/template/interpolate"
    15  )
    16  
    17  type Config struct {
    18  	common.PackerConfig `mapstructure:",squash"`
    19  
    20  	// An inline script to execute. Multiple strings are all executed
    21  	// in the context of a single shell.
    22  	Inline []string
    23  
    24  	// The shebang value used when running inline scripts.
    25  	InlineShebang string `mapstructure:"inline_shebang"`
    26  
    27  	// The local path of the shell script to upload and execute.
    28  	Script string
    29  
    30  	// An array of multiple scripts to run.
    31  	Scripts []string
    32  
    33  	// An array of environment variables that will be injected before
    34  	// your command(s) are executed.
    35  	Vars []string `mapstructure:"environment_vars"`
    36  
    37  	// The command used to execute the script. The '{{ .Path }}' variable
    38  	// should be used to specify where the script goes, {{ .Vars }}
    39  	// can be used to inject the environment_vars into the environment.
    40  	ExecuteCommand string `mapstructure:"execute_command"`
    41  
    42  	ctx interpolate.Context
    43  }
    44  
    45  type PostProcessor struct {
    46  	config Config
    47  }
    48  
    49  type ExecuteCommandTemplate struct {
    50  	Vars     string
    51  	Script   string
    52  	Artifact string
    53  }
    54  
    55  func (p *PostProcessor) Configure(raws ...interface{}) error {
    56  	err := config.Decode(&p.config, &config.DecodeOpts{
    57  		Interpolate:        true,
    58  		InterpolateContext: &p.config.ctx,
    59  		InterpolateFilter: &interpolate.RenderFilter{
    60  			Exclude: []string{
    61  				"execute_command",
    62  			},
    63  		},
    64  	}, raws...)
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	if p.config.ExecuteCommand == "" {
    70  		p.config.ExecuteCommand = "chmod +x {{.Script}}; {{.Vars}} {{.Script}} {{.Artifact}}"
    71  	}
    72  
    73  	if p.config.Inline != nil && len(p.config.Inline) == 0 {
    74  		p.config.Inline = nil
    75  	}
    76  
    77  	if p.config.InlineShebang == "" {
    78  		p.config.InlineShebang = "/bin/sh -e"
    79  	}
    80  
    81  	if p.config.Scripts == nil {
    82  		p.config.Scripts = make([]string, 0)
    83  	}
    84  
    85  	if p.config.Vars == nil {
    86  		p.config.Vars = make([]string, 0)
    87  	}
    88  
    89  	var errs *packer.MultiError
    90  	if p.config.Script != "" && len(p.config.Scripts) > 0 {
    91  		errs = packer.MultiErrorAppend(errs,
    92  			errors.New("Only one of script or scripts can be specified."))
    93  	}
    94  
    95  	if p.config.Script != "" {
    96  		p.config.Scripts = []string{p.config.Script}
    97  	}
    98  
    99  	if len(p.config.Scripts) == 0 && p.config.Inline == nil {
   100  		errs = packer.MultiErrorAppend(errs,
   101  			errors.New("Either a script file or inline script must be specified."))
   102  	} else if len(p.config.Scripts) > 0 && p.config.Inline != nil {
   103  		errs = packer.MultiErrorAppend(errs,
   104  			errors.New("Only a script file or an inline script can be specified, not both."))
   105  	}
   106  
   107  	for _, path := range p.config.Scripts {
   108  		if _, err := os.Stat(path); err != nil {
   109  			errs = packer.MultiErrorAppend(errs,
   110  				fmt.Errorf("Bad script '%s': %s", path, err))
   111  		}
   112  	}
   113  
   114  	// Do a check for bad environment variables, such as '=foo', 'foobar'
   115  	for idx, kv := range p.config.Vars {
   116  		vs := strings.SplitN(kv, "=", 2)
   117  		if len(vs) != 2 || vs[0] == "" {
   118  			errs = packer.MultiErrorAppend(errs,
   119  				fmt.Errorf("Environment variable not in format 'key=value': %s", kv))
   120  		} else {
   121  			// Replace single quotes so they parse
   122  			vs[1] = strings.Replace(vs[1], "'", `'"'"'`, -1)
   123  
   124  			// Single quote env var values
   125  			p.config.Vars[idx] = fmt.Sprintf("%s='%s'", vs[0], vs[1])
   126  		}
   127  	}
   128  
   129  	if errs != nil && len(errs.Errors) > 0 {
   130  		return errs
   131  	}
   132  
   133  	return nil
   134  }
   135  
   136  func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
   137  
   138  	scripts := make([]string, len(p.config.Scripts))
   139  	copy(scripts, p.config.Scripts)
   140  
   141  	// If we have an inline script, then turn that into a temporary
   142  	// shell script and use that.
   143  	if p.config.Inline != nil {
   144  		tf, err := ioutil.TempFile("", "packer-shell")
   145  		if err != nil {
   146  			return nil, false, fmt.Errorf("Error preparing shell script: %s", err)
   147  		}
   148  		defer os.Remove(tf.Name())
   149  
   150  		// Set the path to the temporary file
   151  		scripts = append(scripts, tf.Name())
   152  
   153  		// Write our contents to it
   154  		writer := bufio.NewWriter(tf)
   155  		writer.WriteString(fmt.Sprintf("#!%s\n", p.config.InlineShebang))
   156  		for _, command := range p.config.Inline {
   157  			if _, err := writer.WriteString(command + "\n"); err != nil {
   158  				return nil, false, fmt.Errorf("Error preparing shell script: %s", err)
   159  			}
   160  		}
   161  
   162  		if err := writer.Flush(); err != nil {
   163  			return nil, false, fmt.Errorf("Error preparing shell script: %s", err)
   164  		}
   165  
   166  		tf.Close()
   167  	}
   168  
   169  	// Build our variables up by adding in the build name and builder type
   170  	envVars := make([]string, len(p.config.Vars)+2)
   171  	envVars[0] = fmt.Sprintf("PACKER_BUILD_NAME='%s'", p.config.PackerBuildName)
   172  	envVars[1] = fmt.Sprintf("PACKER_BUILDER_TYPE='%s'", p.config.PackerBuilderType)
   173  	copy(envVars[2:], p.config.Vars)
   174  
   175  	for _, file := range artifact.Files() {
   176  		for _, script := range scripts {
   177  			// Flatten the environment variables
   178  			flattendVars := strings.Join(envVars, " ")
   179  
   180  			p.config.ctx.Data = &ExecuteCommandTemplate{
   181  				Vars:     flattendVars,
   182  				Script:   script,
   183  				Artifact: file,
   184  			}
   185  
   186  			command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
   187  			if err != nil {
   188  				return nil, false, fmt.Errorf("Error processing command: %s", err)
   189  			}
   190  
   191  			ui.Say(fmt.Sprintf("Post processing with local shell script: %s", command))
   192  
   193  			comm := &Communicator{}
   194  
   195  			cmd := &packer.RemoteCmd{Command: command}
   196  
   197  			ui.Say(fmt.Sprintf(
   198  				"Executing local script: %s",
   199  				script))
   200  			if err := cmd.StartWithUi(comm, ui); err != nil {
   201  				return nil, false, fmt.Errorf(
   202  					"Error executing script: %s\n\n"+
   203  						"Please see output above for more information.",
   204  					script)
   205  			}
   206  			if cmd.ExitStatus != 0 {
   207  				return nil, false, fmt.Errorf(
   208  					"Erroneous exit code %d while executing script: %s\n\n"+
   209  						"Please see output above for more information.",
   210  					cmd.ExitStatus,
   211  					script)
   212  			}
   213  		}
   214  	}
   215  
   216  	return artifact, true, nil
   217  }