github.phpd.cn/hashicorp/packer@v1.3.2/common/shell-local/config.go (about) 1 package shell_local 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "runtime" 9 "strings" 10 11 "github.com/hashicorp/packer/common" 12 configHelper "github.com/hashicorp/packer/helper/config" 13 "github.com/hashicorp/packer/packer" 14 "github.com/hashicorp/packer/template/interpolate" 15 ) 16 17 type Config struct { 18 common.PackerConfig `mapstructure:",squash"` 19 20 // ** DEPRECATED: USE INLINE INSTEAD ** 21 // ** Only Present for backwards compatibiltiy ** 22 // Command is the command to execute 23 Command string 24 25 // An inline script to execute. Multiple strings are all executed 26 // in the context of a single shell. 27 Inline []string 28 29 // The shebang value used when running inline scripts. 30 InlineShebang string `mapstructure:"inline_shebang"` 31 32 // An array of multiple Runtime OSs to run on. 33 OnlyOn []string `mapstructure:"only_on"` 34 35 // The file extension to use for the file generated from the inline commands 36 TempfileExtension string `mapstructure:"tempfile_extension"` 37 38 // The local path of the shell script to upload and execute. 39 Script string 40 41 // An array of multiple scripts to run. 42 Scripts []string 43 44 // An array of environment variables that will be injected before 45 // your command(s) are executed. 46 Vars []string `mapstructure:"environment_vars"` 47 48 EnvVarFormat string `mapstructure:"env_var_format"` 49 // End dedupe with postprocessor 50 51 // The command used to execute the script. The '{{ .Path }}' variable 52 // should be used to specify where the script goes, {{ .Vars }} 53 // can be used to inject the environment_vars into the environment. 54 ExecuteCommand []string `mapstructure:"execute_command"` 55 56 UseLinuxPathing bool `mapstructure:"use_linux_pathing"` 57 58 Ctx interpolate.Context 59 } 60 61 func Decode(config *Config, raws ...interface{}) error { 62 //Create passthrough for winrm password so we can fill it in once we know it 63 config.Ctx.Data = &EnvVarsTemplate{ 64 WinRMPassword: `{{.WinRMPassword}}`, 65 } 66 67 err := configHelper.Decode(&config, &configHelper.DecodeOpts{ 68 Interpolate: true, 69 InterpolateContext: &config.Ctx, 70 InterpolateFilter: &interpolate.RenderFilter{ 71 Exclude: []string{ 72 "execute_command", 73 }, 74 }, 75 }, raws...) 76 if err != nil { 77 return fmt.Errorf("Error decoding config: %s, config is %#v, and raws is %#v", err, config, raws) 78 } 79 80 return nil 81 } 82 83 func Validate(config *Config) error { 84 var errs *packer.MultiError 85 86 if runtime.GOOS == "windows" { 87 if len(config.ExecuteCommand) == 0 { 88 config.ExecuteCommand = []string{ 89 "cmd", 90 "/V", 91 "/C", 92 "{{.Vars}}", 93 "call", 94 "{{.Script}}", 95 } 96 } 97 if len(config.TempfileExtension) == 0 { 98 config.TempfileExtension = ".cmd" 99 } 100 } else { 101 if config.InlineShebang == "" { 102 config.InlineShebang = "/bin/sh -e" 103 } 104 if len(config.ExecuteCommand) == 0 { 105 config.ExecuteCommand = []string{ 106 "/bin/sh", 107 "-c", 108 "{{.Vars}} {{.Script}}", 109 } 110 } 111 } 112 113 // Clean up input 114 if config.Inline != nil && len(config.Inline) == 0 { 115 config.Inline = make([]string, 0) 116 } 117 118 if config.Scripts == nil { 119 config.Scripts = make([]string, 0) 120 } 121 122 if config.Vars == nil { 123 config.Vars = make([]string, 0) 124 } 125 126 // Verify that the user has given us a command to run 127 if config.Command == "" && len(config.Inline) == 0 && 128 len(config.Scripts) == 0 && config.Script == "" { 129 errs = packer.MultiErrorAppend(errs, 130 errors.New("Command, Inline, Script and Scripts options cannot all be empty.")) 131 } 132 133 // Check that user hasn't given us too many commands to run 134 tooManyOptionsErr := errors.New("You may only specify one of the " + 135 "following options: Command, Inline, Script or Scripts. Please" + 136 " consolidate these options in your config.") 137 138 if config.Command != "" { 139 if len(config.Inline) != 0 || len(config.Scripts) != 0 || config.Script != "" { 140 errs = packer.MultiErrorAppend(errs, tooManyOptionsErr) 141 } else { 142 config.Inline = []string{config.Command} 143 } 144 } 145 146 if config.Script != "" { 147 if len(config.Scripts) > 0 || len(config.Inline) > 0 { 148 errs = packer.MultiErrorAppend(errs, tooManyOptionsErr) 149 } else { 150 config.Scripts = []string{config.Script} 151 } 152 } 153 154 if len(config.Scripts) > 0 && config.Inline != nil { 155 errs = packer.MultiErrorAppend(errs, tooManyOptionsErr) 156 } 157 158 // Check that all scripts we need to run exist locally 159 for _, path := range config.Scripts { 160 if _, err := os.Stat(path); err != nil { 161 errs = packer.MultiErrorAppend(errs, 162 fmt.Errorf("Bad script '%s': %s", path, err)) 163 } 164 } 165 166 // Check for properly formatted go os types 167 supportedSyslist := []string{"darwin", "freebsd", "linux", "openbsd", "solaris", "windows"} 168 if len(config.OnlyOn) > 0 { 169 for _, provided_os := range config.OnlyOn { 170 supported_os := false 171 for _, go_os := range supportedSyslist { 172 if provided_os == go_os { 173 supported_os = true 174 break 175 } 176 } 177 if supported_os != true { 178 return fmt.Errorf("Invalid OS specified in only_on: '%s'\n"+ 179 "Supported OS names: %s", provided_os, strings.Join(supportedSyslist, ", ")) 180 } 181 } 182 } 183 184 if config.UseLinuxPathing { 185 for index, script := range config.Scripts { 186 scriptAbsPath, err := filepath.Abs(script) 187 if err != nil { 188 return fmt.Errorf("Error converting %s to absolute path: %s", script, err.Error()) 189 } 190 converted, err := ConvertToLinuxPath(scriptAbsPath) 191 if err != nil { 192 return err 193 } 194 config.Scripts[index] = converted 195 } 196 // Interoperability issues with WSL makes creating and running tempfiles 197 // via golang's os package basically impossible. 198 if len(config.Inline) > 0 { 199 errs = packer.MultiErrorAppend(errs, 200 fmt.Errorf("Packer is unable to use the Command and Inline "+ 201 "features with the Windows Linux Subsystem. Please use "+ 202 "the Script or Scripts options instead")) 203 } 204 } 205 // This is currently undocumented and not a feature users are expected to 206 // interact with. 207 if config.EnvVarFormat == "" { 208 if (runtime.GOOS == "windows") && !config.UseLinuxPathing { 209 config.EnvVarFormat = "set %s=%s && " 210 } else { 211 config.EnvVarFormat = "%s='%s' " 212 } 213 } 214 215 // drop unnecessary "." in extension; we add this later. 216 if config.TempfileExtension != "" { 217 if strings.HasPrefix(config.TempfileExtension, ".") { 218 config.TempfileExtension = config.TempfileExtension[1:] 219 } 220 } 221 222 // Do a check for bad environment variables, such as '=foo', 'foobar' 223 for _, kv := range config.Vars { 224 vs := strings.SplitN(kv, "=", 2) 225 if len(vs) != 2 || vs[0] == "" { 226 errs = packer.MultiErrorAppend(errs, 227 fmt.Errorf("Environment variable not in format 'key=value': %s", kv)) 228 } 229 } 230 231 if errs != nil && len(errs.Errors) > 0 { 232 return errs 233 } 234 235 return nil 236 } 237 238 // C:/path/to/your/file becomes /mnt/c/path/to/your/file 239 func ConvertToLinuxPath(winAbsPath string) (string, error) { 240 // get absolute path of script, and morph it into the bash path 241 winAbsPath = strings.Replace(winAbsPath, "\\", "/", -1) 242 splitPath := strings.SplitN(winAbsPath, ":/", 2) 243 if len(splitPath) == 2 { 244 winBashPath := fmt.Sprintf("/mnt/%s/%s", strings.ToLower(splitPath[0]), splitPath[1]) 245 return winBashPath, nil 246 } else { 247 err := fmt.Errorf("There was an error splitting your absolute path; expected "+ 248 "to find a drive following the format ':/' but did not: absolute "+ 249 "path: %s", winAbsPath) 250 return "", err 251 } 252 }