github.phpd.cn/hashicorp/packer@v1.3.2/common/shell-local/run.go (about) 1 package shell_local 2 3 import ( 4 "bufio" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "os" 9 "runtime" 10 "sort" 11 "strings" 12 13 "github.com/hashicorp/packer/common" 14 commonhelper "github.com/hashicorp/packer/helper/common" 15 "github.com/hashicorp/packer/packer" 16 "github.com/hashicorp/packer/template/interpolate" 17 ) 18 19 type ExecuteCommandTemplate struct { 20 Vars string 21 Script string 22 Command string 23 WinRMPassword string 24 } 25 26 type EnvVarsTemplate struct { 27 WinRMPassword string 28 } 29 30 func Run(ui packer.Ui, config *Config) (bool, error) { 31 // Check if shell-local can even execute against this runtime OS 32 if len(config.OnlyOn) > 0 { 33 runCommand := false 34 for _, os := range config.OnlyOn { 35 if os == runtime.GOOS { 36 runCommand = true 37 break 38 } 39 } 40 if !runCommand { 41 ui.Say(fmt.Sprintf("Skipping shell-local due to runtime OS")) 42 log.Printf("[INFO] (shell-local): skipping shell-local due to missing runtime OS") 43 return true, nil 44 } 45 } 46 47 scripts := make([]string, len(config.Scripts)) 48 if len(config.Scripts) > 0 { 49 copy(scripts, config.Scripts) 50 } else if config.Inline != nil { 51 // If we have an inline script, then turn that into a temporary 52 // shell script and use that. 53 tempScriptFileName, err := createInlineScriptFile(config) 54 if err != nil { 55 return false, err 56 } 57 scripts = append(scripts, tempScriptFileName) 58 59 // figure out what extension the file should have, and rename it. 60 if config.TempfileExtension != "" { 61 os.Rename(tempScriptFileName, fmt.Sprintf("%s.%s", tempScriptFileName, config.TempfileExtension)) 62 tempScriptFileName = fmt.Sprintf("%s.%s", tempScriptFileName, config.TempfileExtension) 63 } 64 defer os.Remove(tempScriptFileName) 65 } 66 67 // Create environment variables to set before executing the command 68 flattenedEnvVars, err := createFlattenedEnvVars(config) 69 if err != nil { 70 return false, err 71 } 72 73 for _, script := range scripts { 74 interpolatedCmds, err := createInterpolatedCommands(config, script, flattenedEnvVars) 75 if err != nil { 76 return false, err 77 } 78 ui.Say(fmt.Sprintf("Running local shell script: %s", script)) 79 80 comm := &Communicator{ 81 ExecuteCommand: interpolatedCmds, 82 } 83 84 // The remoteCmd generated here isn't actually run, but it allows us to 85 // use the same interafce for the shell-local communicator as we use for 86 // the other communicators; ultimately, this command is just used for 87 // buffers and for reading the final exit status. 88 flattenedCmd := strings.Join(interpolatedCmds, " ") 89 cmd := &packer.RemoteCmd{Command: flattenedCmd} 90 log.Printf("[INFO] (shell-local): starting local command: %s", flattenedCmd) 91 if err := cmd.StartWithUi(comm, ui); err != nil { 92 return false, fmt.Errorf( 93 "Error executing script: %s\n\n"+ 94 "Please see output above for more information.", 95 script) 96 } 97 if cmd.ExitStatus != 0 { 98 return false, fmt.Errorf( 99 "Erroneous exit code %d while executing script: %s\n\n"+ 100 "Please see output above for more information.", 101 cmd.ExitStatus, 102 script) 103 } 104 } 105 106 return true, nil 107 } 108 109 func createInlineScriptFile(config *Config) (string, error) { 110 tf, err := ioutil.TempFile("", "packer-shell") 111 if err != nil { 112 return "", fmt.Errorf("Error preparing shell script: %s", err) 113 } 114 defer tf.Close() 115 // Write our contents to it 116 writer := bufio.NewWriter(tf) 117 if config.InlineShebang != "" { 118 shebang := fmt.Sprintf("#!%s\n", config.InlineShebang) 119 log.Printf("[INFO] (shell-local): Prepending inline script with %s", shebang) 120 writer.WriteString(shebang) 121 } 122 123 // generate context so you can interpolate the command 124 config.Ctx.Data = &EnvVarsTemplate{ 125 WinRMPassword: getWinRMPassword(config.PackerBuildName), 126 } 127 128 for _, command := range config.Inline { 129 // interpolate command to check for template variables. 130 command, err := interpolate.Render(command, &config.Ctx) 131 if err != nil { 132 return "", err 133 } 134 135 if _, err := writer.WriteString(command + "\n"); err != nil { 136 return "", fmt.Errorf("Error preparing shell script: %s", err) 137 } 138 } 139 140 if err := writer.Flush(); err != nil { 141 return "", fmt.Errorf("Error preparing shell script: %s", err) 142 } 143 144 err = os.Chmod(tf.Name(), 0700) 145 if err != nil { 146 log.Printf("[ERROR] (shell-local): error modifying permissions of temp script file: %s", err.Error()) 147 } 148 return tf.Name(), nil 149 } 150 151 // Generates the final command to send to the communicator, using either the 152 // user-provided ExecuteCommand or defaulting to something that makes sense for 153 // the host OS 154 func createInterpolatedCommands(config *Config, script string, flattenedEnvVars string) ([]string, error) { 155 config.Ctx.Data = &ExecuteCommandTemplate{ 156 Vars: flattenedEnvVars, 157 Script: script, 158 Command: script, 159 WinRMPassword: getWinRMPassword(config.PackerBuildName), 160 } 161 162 interpolatedCmds := make([]string, len(config.ExecuteCommand)) 163 for i, cmd := range config.ExecuteCommand { 164 interpolatedCmd, err := interpolate.Render(cmd, &config.Ctx) 165 if err != nil { 166 return nil, fmt.Errorf("Error processing command: %s", err) 167 } 168 interpolatedCmds[i] = interpolatedCmd 169 } 170 return interpolatedCmds, nil 171 } 172 173 func createFlattenedEnvVars(config *Config) (string, error) { 174 flattened := "" 175 envVars := make(map[string]string) 176 177 // Always available Packer provided env vars 178 envVars["PACKER_BUILD_NAME"] = fmt.Sprintf("%s", config.PackerBuildName) 179 envVars["PACKER_BUILDER_TYPE"] = fmt.Sprintf("%s", config.PackerBuilderType) 180 181 // expose PACKER_HTTP_ADDR 182 httpAddr := common.GetHTTPAddr() 183 if httpAddr != "" { 184 envVars["PACKER_HTTP_ADDR"] = fmt.Sprintf("%s", httpAddr) 185 } 186 187 // interpolate environment variables 188 config.Ctx.Data = &EnvVarsTemplate{ 189 WinRMPassword: getWinRMPassword(config.PackerBuildName), 190 } 191 // Split vars into key/value components 192 for _, envVar := range config.Vars { 193 envVar, err := interpolate.Render(envVar, &config.Ctx) 194 if err != nil { 195 return "", err 196 } 197 // Split vars into key/value components 198 keyValue := strings.SplitN(envVar, "=", 2) 199 // Store pair, replacing any single quotes in value so they parse 200 // correctly with required environment variable format 201 envVars[keyValue[0]] = strings.Replace(keyValue[1], "'", `'"'"'`, -1) 202 } 203 204 // Create a list of env var keys in sorted order 205 var keys []string 206 for k := range envVars { 207 keys = append(keys, k) 208 } 209 sort.Strings(keys) 210 211 for _, key := range keys { 212 flattened += fmt.Sprintf(config.EnvVarFormat, key, envVars[key]) 213 } 214 return flattened, nil 215 } 216 217 func getWinRMPassword(buildName string) string { 218 winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName) 219 packer.LogSecretFilter.Set(winRMPass) 220 return winRMPass 221 }