github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/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 "log" 9 "os" 10 "sort" 11 "strings" 12 13 "github.com/hashicorp/packer/common" 14 "github.com/hashicorp/packer/helper/config" 15 "github.com/hashicorp/packer/packer" 16 "github.com/hashicorp/packer/template/interpolate" 17 ) 18 19 type Config struct { 20 common.PackerConfig `mapstructure:",squash"` 21 22 // An inline script to execute. Multiple strings are all executed 23 // in the context of a single shell. 24 Inline []string 25 26 // The shebang value used when running inline scripts. 27 InlineShebang string `mapstructure:"inline_shebang"` 28 29 // The local path of the shell script to upload and execute. 30 Script string 31 32 // An array of multiple scripts to run. 33 Scripts []string 34 35 // An array of environment variables that will be injected before 36 // your command(s) are executed. 37 Vars []string `mapstructure:"environment_vars"` 38 39 // The command used to execute the script. The '{{ .Path }}' variable 40 // should be used to specify where the script goes, {{ .Vars }} 41 // can be used to inject the environment_vars into the environment. 42 ExecuteCommand string `mapstructure:"execute_command"` 43 44 ctx interpolate.Context 45 } 46 47 type PostProcessor struct { 48 config Config 49 } 50 51 type ExecuteCommandTemplate struct { 52 Vars string 53 Script string 54 } 55 56 func (p *PostProcessor) Configure(raws ...interface{}) error { 57 err := config.Decode(&p.config, &config.DecodeOpts{ 58 Interpolate: true, 59 InterpolateContext: &p.config.ctx, 60 InterpolateFilter: &interpolate.RenderFilter{ 61 Exclude: []string{ 62 "execute_command", 63 }, 64 }, 65 }, raws...) 66 if err != nil { 67 return err 68 } 69 70 if p.config.ExecuteCommand == "" { 71 p.config.ExecuteCommand = `chmod +x "{{.Script}}"; {{.Vars}} "{{.Script}}"` 72 } 73 74 if p.config.Inline != nil && len(p.config.Inline) == 0 { 75 p.config.Inline = nil 76 } 77 78 if p.config.InlineShebang == "" { 79 p.config.InlineShebang = "/bin/sh -e" 80 } 81 82 if p.config.Scripts == nil { 83 p.config.Scripts = make([]string, 0) 84 } 85 86 if p.config.Vars == nil { 87 p.config.Vars = make([]string, 0) 88 } 89 90 var errs *packer.MultiError 91 if p.config.Script != "" && len(p.config.Scripts) > 0 { 92 errs = packer.MultiErrorAppend(errs, 93 errors.New("Only one of script or scripts can be specified.")) 94 } 95 96 if p.config.Script != "" { 97 p.config.Scripts = []string{p.config.Script} 98 } 99 100 if len(p.config.Scripts) == 0 && p.config.Inline == nil { 101 errs = packer.MultiErrorAppend(errs, 102 errors.New("Either a script file or inline script must be specified.")) 103 } else if len(p.config.Scripts) > 0 && p.config.Inline != nil { 104 errs = packer.MultiErrorAppend(errs, 105 errors.New("Only a script file or an inline script can be specified, not both.")) 106 } 107 108 for _, path := range p.config.Scripts { 109 if _, err := os.Stat(path); err != nil { 110 errs = packer.MultiErrorAppend(errs, 111 fmt.Errorf("Bad script '%s': %s", path, err)) 112 } 113 } 114 115 // Do a check for bad environment variables, such as '=foo', 'foobar' 116 for _, kv := range p.config.Vars { 117 vs := strings.SplitN(kv, "=", 2) 118 if len(vs) != 2 || vs[0] == "" { 119 errs = packer.MultiErrorAppend(errs, 120 fmt.Errorf("Environment variable not in format 'key=value': %s", kv)) 121 } 122 } 123 124 if errs != nil && len(errs.Errors) > 0 { 125 return errs 126 } 127 128 return nil 129 } 130 131 func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { 132 133 scripts := make([]string, len(p.config.Scripts)) 134 copy(scripts, p.config.Scripts) 135 136 // If we have an inline script, then turn that into a temporary 137 // shell script and use that. 138 if p.config.Inline != nil { 139 tf, err := ioutil.TempFile("", "packer-shell") 140 if err != nil { 141 return nil, false, fmt.Errorf("Error preparing shell script: %s", err) 142 } 143 defer os.Remove(tf.Name()) 144 145 // Set the path to the temporary file 146 scripts = append(scripts, tf.Name()) 147 148 // Write our contents to it 149 writer := bufio.NewWriter(tf) 150 writer.WriteString(fmt.Sprintf("#!%s\n", p.config.InlineShebang)) 151 for _, command := range p.config.Inline { 152 if _, err := writer.WriteString(command + "\n"); err != nil { 153 return nil, false, fmt.Errorf("Error preparing shell script: %s", err) 154 } 155 } 156 157 if err := writer.Flush(); err != nil { 158 return nil, false, fmt.Errorf("Error preparing shell script: %s", err) 159 } 160 161 tf.Close() 162 } 163 164 // Create environment variables to set before executing the command 165 flattenedEnvVars := p.createFlattenedEnvVars() 166 167 for _, script := range scripts { 168 169 p.config.ctx.Data = &ExecuteCommandTemplate{ 170 Vars: flattenedEnvVars, 171 Script: script, 172 } 173 174 command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) 175 if err != nil { 176 return nil, false, fmt.Errorf("Error processing command: %s", err) 177 } 178 179 ui.Say(fmt.Sprintf("Post processing with local shell script: %s", script)) 180 181 comm := &Communicator{} 182 183 cmd := &packer.RemoteCmd{Command: command} 184 185 log.Printf("starting local command: %s", command) 186 if err := cmd.StartWithUi(comm, ui); err != nil { 187 return nil, false, fmt.Errorf( 188 "Error executing script: %s\n\n"+ 189 "Please see output above for more information.", 190 script) 191 } 192 if cmd.ExitStatus != 0 { 193 return nil, false, fmt.Errorf( 194 "Erroneous exit code %d while executing script: %s\n\n"+ 195 "Please see output above for more information.", 196 cmd.ExitStatus, 197 script) 198 } 199 } 200 ui.Say("\n" + 201 "--------------------------------------------------------------\n" + 202 "--------------------DEPRECATION WARNING-----------------------\n" + 203 "--------------------------------------------------------------\n" + 204 "The shell-local provisioner will be deprecated in version 1.2.0\n" + 205 "If you need access to packer variables in your post-processing \n" + 206 "shell scripts, please use the manifest post-processor\n" + 207 "(see https://www.packer.io/docs/post-processors/manifest.html).\n" + 208 "If you need additional information that's already in the artifact,\n" + 209 "please open a ticket so we can add it. If the manifest provisioner\n" + 210 "does not fit your use case, please comment on our deprecation ticket\n" + 211 "with your use case so we can make sure that the transition will be\n" + 212 "seamless for you: https://github.com/hashicorp/packer/issues/5330\n" + 213 "--------------------------------------------------------------\n" + 214 "--------------------DEPRECATION WARNING-----------------------\n" + 215 "--------------------------------------------------------------\n" + 216 "\n\n") 217 218 return artifact, true, nil 219 } 220 221 func (p *PostProcessor) createFlattenedEnvVars() (flattened string) { 222 flattened = "" 223 envVars := make(map[string]string) 224 225 // Always available Packer provided env vars 226 envVars["PACKER_BUILD_NAME"] = fmt.Sprintf("%s", p.config.PackerBuildName) 227 envVars["PACKER_BUILDER_TYPE"] = fmt.Sprintf("%s", p.config.PackerBuilderType) 228 229 // Split vars into key/value components 230 for _, envVar := range p.config.Vars { 231 keyValue := strings.SplitN(envVar, "=", 2) 232 // Store pair, replacing any single quotes in value so they parse 233 // correctly with required environment variable format 234 envVars[keyValue[0]] = strings.Replace(keyValue[1], "'", `'"'"'`, -1) 235 } 236 237 // Create a list of env var keys in sorted order 238 var keys []string 239 for k := range envVars { 240 keys = append(keys, k) 241 } 242 sort.Strings(keys) 243 244 // Re-assemble vars surrounding value with single quotes and flatten 245 for _, key := range keys { 246 flattened += fmt.Sprintf("%s='%s' ", key, envVars[key]) 247 } 248 return 249 }