github.com/hashicorp/packer@v1.14.3/provisioner/windows-shell/provisioner_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package shell 5 6 import ( 7 "bytes" 8 "context" 9 "log" 10 "os" 11 "strings" 12 "testing" 13 14 "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" 15 packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 16 ) 17 18 func testConfig() map[string]interface{} { 19 return map[string]interface{}{ 20 "inline": []interface{}{"foo", "bar"}, 21 } 22 } 23 24 func TestProvisionerPrepare_extractScript(t *testing.T) { 25 config := testConfig() 26 p := new(Provisioner) 27 _ = p.Prepare(config) 28 file, err := extractScript(p) 29 defer os.Remove(file) 30 if err != nil { 31 t.Fatalf("Should not be error: %s", err) 32 } 33 log.Printf("File: %s", file) 34 if strings.Index(file, os.TempDir()) != 0 { 35 t.Fatalf("Temp file should reside in %s. File location: %s", os.TempDir(), file) 36 } 37 38 // File contents should contain 2 lines concatenated by newlines: foo\nbar 39 readFile, err := os.ReadFile(file) 40 if err != nil { 41 t.Fatalf("Should not be error: %s", err) 42 } 43 expectedContents := "foo\nbar\n" 44 s := string(readFile[:]) 45 if s != expectedContents { 46 t.Fatalf("Expected generated inlineScript to equal '%s', got '%s'", expectedContents, s) 47 } 48 } 49 50 func TestProvisioner_Impl(t *testing.T) { 51 var raw interface{} 52 raw = &Provisioner{} 53 if _, ok := raw.(packersdk.Provisioner); !ok { 54 t.Fatalf("must be a Provisioner") 55 } 56 } 57 58 func TestProvisionerPrepare_Defaults(t *testing.T) { 59 var p Provisioner 60 config := testConfig() 61 62 err := p.Prepare(config) 63 if err != nil { 64 t.Fatalf("err: %s", err) 65 } 66 67 if p.config.RemotePath != DefaultRemotePath { 68 t.Errorf("unexpected remote path: %s", p.config.RemotePath) 69 } 70 71 if p.config.ExecuteCommand != "{{.Vars}}\"{{.Path}}\"" { 72 t.Fatalf("Default command should be powershell {{.Vars}}\"{{.Path}}\", but got %s", p.config.ExecuteCommand) 73 } 74 } 75 76 func TestProvisionerPrepare_Config(t *testing.T) { 77 78 } 79 80 func TestProvisionerPrepare_InvalidKey(t *testing.T) { 81 var p Provisioner 82 config := testConfig() 83 84 // Add a random key 85 config["i_should_not_be_valid"] = true 86 err := p.Prepare(config) 87 if err == nil { 88 t.Fatal("should have error") 89 } 90 } 91 92 func TestProvisionerPrepare_Script(t *testing.T) { 93 config := testConfig() 94 delete(config, "inline") 95 96 config["script"] = "/this/should/not/exist" 97 p := new(Provisioner) 98 err := p.Prepare(config) 99 if err == nil { 100 t.Fatal("should have error") 101 } 102 103 // Test with a good one 104 tf, err := os.CreateTemp("", "packer") 105 if err != nil { 106 t.Fatalf("error tempfile: %s", err) 107 } 108 defer os.Remove(tf.Name()) 109 defer tf.Close() 110 111 config["script"] = tf.Name() 112 p = new(Provisioner) 113 err = p.Prepare(config) 114 if err != nil { 115 t.Fatalf("should not have error: %s", err) 116 } 117 } 118 119 func TestProvisionerPrepare_ScriptAndInline(t *testing.T) { 120 var p Provisioner 121 config := testConfig() 122 123 delete(config, "inline") 124 delete(config, "script") 125 err := p.Prepare(config) 126 if err == nil { 127 t.Fatal("should have error") 128 } 129 130 // Test with both 131 tf, err := os.CreateTemp("", "packer") 132 if err != nil { 133 t.Fatalf("error tempfile: %s", err) 134 } 135 defer os.Remove(tf.Name()) 136 defer tf.Close() 137 138 config["inline"] = []interface{}{"foo"} 139 config["script"] = tf.Name() 140 err = p.Prepare(config) 141 if err == nil { 142 t.Fatal("should have error") 143 } 144 } 145 146 func TestProvisionerPrepare_ScriptAndScripts(t *testing.T) { 147 var p Provisioner 148 config := testConfig() 149 150 // Test with both 151 tf, err := os.CreateTemp("", "packer") 152 if err != nil { 153 t.Fatalf("error tempfile: %s", err) 154 } 155 defer os.Remove(tf.Name()) 156 defer tf.Close() 157 158 config["inline"] = []interface{}{"foo"} 159 config["scripts"] = []string{tf.Name()} 160 err = p.Prepare(config) 161 if err == nil { 162 t.Fatal("should have error") 163 } 164 } 165 166 func TestProvisionerPrepare_Scripts(t *testing.T) { 167 config := testConfig() 168 delete(config, "inline") 169 170 config["scripts"] = []string{} 171 p := new(Provisioner) 172 err := p.Prepare(config) 173 if err == nil { 174 t.Fatal("should have error") 175 } 176 177 // Test with a good one 178 tf, err := os.CreateTemp("", "packer") 179 if err != nil { 180 t.Fatalf("error tempfile: %s", err) 181 } 182 defer os.Remove(tf.Name()) 183 defer tf.Close() 184 185 config["scripts"] = []string{tf.Name()} 186 p = new(Provisioner) 187 err = p.Prepare(config) 188 if err != nil { 189 t.Fatalf("should not have error: %s", err) 190 } 191 } 192 193 func TestProvisionerPrepare_EnvironmentVars(t *testing.T) { 194 config := testConfig() 195 196 // Test with a bad case 197 config["environment_vars"] = []string{"badvar", "good=var"} 198 p := new(Provisioner) 199 err := p.Prepare(config) 200 if err == nil { 201 t.Fatal("should have error") 202 } 203 204 // Test with a trickier case 205 config["environment_vars"] = []string{"=bad"} 206 p = new(Provisioner) 207 err = p.Prepare(config) 208 if err == nil { 209 t.Fatal("should have error") 210 } 211 212 // Test with a good case 213 // Note: baz= is a real env variable, just empty 214 config["environment_vars"] = []string{"FOO=bar", "baz="} 215 p = new(Provisioner) 216 err = p.Prepare(config) 217 if err != nil { 218 t.Fatalf("should not have error: %s", err) 219 } 220 221 // Test when the env variable value contains an equals sign 222 config["environment_vars"] = []string{"good=withequals=true"} 223 p = new(Provisioner) 224 err = p.Prepare(config) 225 if err != nil { 226 t.Fatalf("should not have error: %s", err) 227 } 228 229 // Test when the env variable value starts with an equals sign 230 config["environment_vars"] = []string{"good==true"} 231 p = new(Provisioner) 232 err = p.Prepare(config) 233 if err != nil { 234 t.Fatalf("should not have error: %s", err) 235 } 236 } 237 238 func TestProvisionerQuote_EnvironmentVars(t *testing.T) { 239 config := testConfig() 240 241 config["environment_vars"] = []string{ 242 "keyone=valueone", 243 "keytwo=value\ntwo", 244 "keythree='valuethree'", 245 "keyfour='value\nfour'", 246 "keyfive='value=five'", 247 "keysix='=six'", 248 } 249 250 expected := []string{ 251 "keyone=valueone", 252 "keytwo=value\ntwo", 253 "keythree='valuethree'", 254 "keyfour='value\nfour'", 255 "keyfive='value=five'", 256 "keysix='=six'", 257 } 258 259 p := new(Provisioner) 260 p.Prepare(config) 261 262 for i, expectedValue := range expected { 263 if p.config.Vars[i] != expectedValue { 264 t.Fatalf("%s should be equal to %s", p.config.Vars[i], expectedValue) 265 } 266 } 267 268 } 269 270 func testUi() *packersdk.BasicUi { 271 return &packersdk.BasicUi{ 272 Reader: new(bytes.Buffer), 273 Writer: new(bytes.Buffer), 274 ErrorWriter: new(bytes.Buffer), 275 } 276 } 277 278 func TestProvisionerProvision_Inline(t *testing.T) { 279 config := testConfig() 280 delete(config, "inline") 281 282 // Defaults provided by Packer 283 config["remote_path"] = "c:/Windows/Temp/inlineScript.bat" 284 config["inline"] = []string{"whoami"} 285 ui := testUi() 286 p := new(Provisioner) 287 288 // Defaults provided by Packer 289 p.config.PackerBuildName = "vmware" 290 p.config.PackerBuilderType = "iso" 291 comm := new(packersdk.MockCommunicator) 292 p.Prepare(config) 293 294 err := p.Provision(context.Background(), ui, comm, generatedData()) 295 if err != nil { 296 t.Fatal("should not have error") 297 } 298 299 expectedCommand := `set "PACKER_BUILDER_TYPE=iso" && set "PACKER_BUILD_NAME=vmware" && "c:/Windows/Temp/inlineScript.bat"` 300 301 // Should run the command without alteration 302 if comm.StartCmd.Command != expectedCommand { 303 t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command) 304 } 305 306 envVars := make([]string, 2) 307 envVars[0] = "FOO=BAR" 308 envVars[1] = "BAR=BAZ" 309 config["environment_vars"] = envVars 310 config["remote_path"] = "c:/Windows/Temp/inlineScript.bat" 311 312 p.Prepare(config) 313 err = p.Provision(context.Background(), ui, comm, generatedData()) 314 if err != nil { 315 t.Fatal("should not have error") 316 } 317 318 expectedCommand = `set "BAR=BAZ" && set "FOO=BAR" && set "PACKER_BUILDER_TYPE=iso" && set "PACKER_BUILD_NAME=vmware" && "c:/Windows/Temp/inlineScript.bat"` 319 320 // Should run the command without alteration 321 if comm.StartCmd.Command != expectedCommand { 322 t.Fatalf("Expect command to be: %s, got: %s", expectedCommand, comm.StartCmd.Command) 323 } 324 } 325 326 func TestProvisionerProvision_Scripts(t *testing.T) { 327 tf, err := os.CreateTemp("", "packer") 328 if err != nil { 329 t.Fatalf("error tempfile: %s", err) 330 } 331 defer os.Remove(tf.Name()) 332 defer tf.Close() 333 334 config := testConfig() 335 delete(config, "inline") 336 config["scripts"] = []string{tf.Name()} 337 config["packer_build_name"] = "foobuild" 338 config["packer_builder_type"] = "footype" 339 ui := testUi() 340 341 p := new(Provisioner) 342 comm := new(packersdk.MockCommunicator) 343 p.Prepare(config) 344 err = p.Provision(context.Background(), ui, comm, generatedData()) 345 if err != nil { 346 t.Fatal("should not have error") 347 } 348 349 //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 350 expectedCommand := `set "PACKER_BUILDER_TYPE=footype" && set "PACKER_BUILD_NAME=foobuild" && "c:/Windows/Temp/script.bat"` 351 352 // Should run the command without alteration 353 if comm.StartCmd.Command != expectedCommand { 354 t.Fatalf("Expect command to be %s NOT %s", expectedCommand, comm.StartCmd.Command) 355 } 356 } 357 358 func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { 359 tf, err := os.CreateTemp("", "packer") 360 if err != nil { 361 t.Fatalf("error tempfile: %s", err) 362 } 363 defer os.Remove(tf.Name()) 364 defer tf.Close() 365 366 config := testConfig() 367 ui := testUi() 368 delete(config, "inline") 369 370 config["scripts"] = []string{tf.Name()} 371 config["packer_build_name"] = "foobuild" 372 config["packer_builder_type"] = "footype" 373 374 // Env vars - currently should not effect them 375 envVars := make([]string, 2) 376 envVars[0] = "FOO=BAR" 377 envVars[1] = "BAR=BAZ" 378 config["environment_vars"] = envVars 379 380 p := new(Provisioner) 381 comm := new(packersdk.MockCommunicator) 382 p.Prepare(config) 383 err = p.Provision(context.Background(), ui, comm, generatedData()) 384 if err != nil { 385 t.Fatal("should not have error") 386 } 387 388 expectedCommand := `set "BAR=BAZ" && set "FOO=BAR" && set "PACKER_BUILDER_TYPE=footype" && set "PACKER_BUILD_NAME=foobuild" && "c:/Windows/Temp/script.bat"` 389 390 // Should run the command without alteration 391 if comm.StartCmd.Command != expectedCommand { 392 t.Fatalf("Expect command to be %s NOT %s", expectedCommand, comm.StartCmd.Command) 393 } 394 } 395 396 func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { 397 var flattenedEnvVars string 398 config := testConfig() 399 400 userEnvVarTests := [][]string{ 401 {}, // No user env var 402 {"FOO=bar"}, // Single user env var 403 {"FOO=bar", "BAZ=qux"}, // Multiple user env vars 404 {"FOO=bar=baz"}, // User env var with value containing equals 405 {"FOO==bar"}, // User env var with value starting with equals 406 } 407 userEnvVarmapTests := []map[string]string{ 408 {}, 409 { 410 "BAR": "foo", 411 }, 412 { 413 "BAR": "foo", 414 "YAR": "yaa", 415 }, 416 { 417 "BAR": "foo=yar", 418 }, 419 { 420 "BAR": "=foo", 421 }, 422 } 423 expected := []string{ 424 `set "PACKER_BUILDER_TYPE=iso" && set "PACKER_BUILD_NAME=vmware" && `, 425 `set "BAR=foo" && set "FOO=bar" && set "PACKER_BUILDER_TYPE=iso" && set "PACKER_BUILD_NAME=vmware" && `, 426 `set "BAR=foo" && set "BAZ=qux" && set "FOO=bar" && set "PACKER_BUILDER_TYPE=iso" && set "PACKER_BUILD_NAME=vmware" && set "YAR=yaa" && `, 427 `set "BAR=foo=yar" && set "FOO=bar=baz" && set "PACKER_BUILDER_TYPE=iso" && set "PACKER_BUILD_NAME=vmware" && `, 428 `set "BAR==foo" && set "FOO==bar" && set "PACKER_BUILDER_TYPE=iso" && set "PACKER_BUILD_NAME=vmware" && `, 429 } 430 431 p := new(Provisioner) 432 p.generatedData = generatedData() 433 p.Prepare(config) 434 435 // Defaults provided by Packer 436 p.config.PackerBuildName = "vmware" 437 p.config.PackerBuilderType = "iso" 438 439 for i, expectedValue := range expected { 440 p.config.Vars = userEnvVarTests[i] 441 p.config.Env = userEnvVarmapTests[i] 442 flattenedEnvVars = p.createFlattenedEnvVars() 443 if flattenedEnvVars != expectedValue { 444 t.Fatalf("expected flattened env vars to be: %s, got %s.", expectedValue, flattenedEnvVars) 445 } 446 } 447 } 448 449 func TestCancel(t *testing.T) { 450 // Don't actually call Cancel() as it performs an os.Exit(0) 451 // which kills the 'go test' tool 452 } 453 func generatedData() map[string]interface{} { 454 return map[string]interface{}{ 455 "PackerHTTPAddr": commonsteps.HttpAddrNotImplemented, 456 "PackerHTTPIP": commonsteps.HttpIPNotImplemented, 457 "PackerHTTPPort": commonsteps.HttpPortNotImplemented, 458 } 459 }