github.com/hashicorp/packer@v1.14.3/provisioner/powershell/provisioner_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package powershell 5 6 import ( 7 "bytes" 8 "context" 9 "fmt" 10 "os" 11 "regexp" 12 "strings" 13 "testing" 14 "time" 15 16 "github.com/hashicorp/packer-plugin-sdk/common" 17 "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" 18 packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 19 "github.com/stretchr/testify/assert" 20 ) 21 22 func TestProvisionerPrepare_extractScript(t *testing.T) { 23 config := testConfig() 24 p := new(Provisioner) 25 _ = p.Prepare(config) 26 p.generatedData = generatedData() 27 file, err := extractInlineScript(p) 28 defer os.Remove(file) 29 if err != nil { 30 t.Fatalf("Should not be error: %s", err) 31 } 32 t.Logf("File: %s", file) 33 if strings.Index(file, os.TempDir()) != 0 { 34 t.Fatalf("Temp file should reside in %s. File location: %s", os.TempDir(), file) 35 } 36 37 // File contents should contain 2 lines concatenated by newlines: foo\nbar 38 readFile, err := os.ReadFile(file) 39 expectedContents := "if (Test-Path variable:global:ProgressPreference) {\n set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue'\n }\n \n $exitCode = 0\n try {\n $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; \n foo\n bar\n \n $exitCode = 0\n } catch {\n Write-Error \"An error occurred: $_\"\n $exitCode = 1\n }\n \n if ($LASTEXITCODE -ne $null -and $LASTEXITCODE -ne 0) {\n $exitCode = $LASTEXITCODE\n }\n exit $exitCode" 40 normalizedExpectedContent := normalizeWhiteSpace(expectedContents) 41 if err != nil { 42 t.Fatalf("Should not be error: %s", err) 43 } 44 s := string(readFile[:]) 45 normalizedString := normalizeWhiteSpace(s) 46 if normalizedString != normalizedExpectedContent { 47 t.Fatalf("Expected generated inlineScript to equal '%s', got '%s'", normalizedExpectedContent, normalizedString) 48 } 49 } 50 51 func TestProvisioner_Impl(t *testing.T) { 52 var raw interface{} 53 raw = &Provisioner{} 54 if _, ok := raw.(packersdk.Provisioner); !ok { 55 t.Fatalf("must be a Provisioner") 56 } 57 } 58 59 func TestProvisionerPrepare_Defaults(t *testing.T) { 60 var p Provisioner 61 config := testConfig() 62 63 err := p.Prepare(config) 64 if err != nil { 65 t.Fatalf("err: %s", err) 66 } 67 68 matched, _ := regexp.MatchString("c:/Windows/Temp/script-.*.ps1", p.config.RemotePath) 69 if !matched { 70 t.Errorf("unexpected remote path: %s", p.config.RemotePath) 71 } 72 73 if p.config.ElevatedUser != "" { 74 t.Error("expected elevated_user to be empty") 75 } 76 if p.config.ElevatedPassword != "" { 77 t.Error("expected elevated_password to be empty") 78 } 79 80 if p.config.ExecuteCommand != `powershell -executionpolicy bypass -file {{.Path}}` { 81 t.Fatalf(`Default command should be 'powershell -executionpolicy bypass -file {{.Path}}', but got '%s'`, p.config.ExecuteCommand) 82 } 83 84 if p.config.ElevatedExecuteCommand != `powershell -executionpolicy bypass -file {{.Path}}` { 85 t.Fatalf(`Default command should be 'powershell -executionpolicy bypass -file {{.Path}}', but got '%s'`, p.config.ElevatedExecuteCommand) 86 } 87 88 if p.config.ElevatedEnvVarFormat != `$env:%s="%s"; ` { 89 t.Fatalf(`Default command should be powershell '$env:%%s="%%s"; ', but got %s`, p.config.ElevatedEnvVarFormat) 90 } 91 } 92 93 func TestProvisionerPrepare_Config(t *testing.T) { 94 config := testConfig() 95 config["elevated_user"] = "{{user `user`}}" 96 config["elevated_password"] = "{{user `password`}}" 97 config[common.UserVariablesConfigKey] = map[string]string{ 98 "user": "myusername", 99 "password": "mypassword", 100 } 101 102 var p Provisioner 103 err := p.Prepare(config) 104 if err != nil { 105 t.Fatalf("err: %s", err) 106 } 107 108 if p.config.ElevatedUser != "myusername" { 109 t.Fatalf("Expected 'myusername' for key `elevated_user`: %s", p.config.ElevatedUser) 110 } 111 if p.config.ElevatedPassword != "mypassword" { 112 t.Fatalf("Expected 'mypassword' for key `elevated_password`: %s", p.config.ElevatedPassword) 113 } 114 } 115 116 func TestProvisionerPrepare_DebugMode(t *testing.T) { 117 config := testConfig() 118 config["debug_mode"] = 1 119 120 var p Provisioner 121 err := p.Prepare(config) 122 if err != nil { 123 t.Fatalf("err: %s", err) 124 } 125 126 command := `powershell -executionpolicy bypass -file {{.Path}}` 127 if p.config.ExecuteCommand != command { 128 t.Fatalf(`Expected command should be '%s' but got '%s'`, command, p.config.ExecuteCommand) 129 } 130 } 131 132 func TestProvisionerPrepare_InvalidDebugMode(t *testing.T) { 133 config := testConfig() 134 config["debug_mode"] = -1 135 136 var p Provisioner 137 err := p.Prepare(config) 138 if err == nil { 139 t.Fatalf("should have error") 140 } 141 142 message := "invalid Trace level for `debug_mode`; valid values are 0, 1, and 2" 143 if !strings.Contains(err.Error(), message) { 144 t.Fatalf("expected Prepare() error %q to contain %q", err.Error(), message) 145 } 146 } 147 148 func TestProvisionerPrepare_InvalidKey(t *testing.T) { 149 var p Provisioner 150 config := testConfig() 151 152 // Add a random key 153 config["i_should_not_be_valid"] = true 154 err := p.Prepare(config) 155 if err == nil { 156 t.Fatal("should have error") 157 } 158 } 159 160 func TestProvisionerPrepare_Elevated(t *testing.T) { 161 var p Provisioner 162 config := testConfig() 163 164 // Add a random key 165 config["elevated_user"] = "vagrant" 166 err := p.Prepare(config) 167 168 if err != nil { 169 t.Fatal("should not have error") 170 } 171 172 config["elevated_password"] = "vagrant" 173 err = p.Prepare(config) 174 175 if err != nil { 176 t.Fatal("should not have error") 177 } 178 } 179 180 func TestProvisionerPrepare_Script(t *testing.T) { 181 config := testConfig() 182 delete(config, "inline") 183 184 config["script"] = "/this/should/not/exist" 185 p := new(Provisioner) 186 err := p.Prepare(config) 187 if err == nil { 188 t.Fatal("should have error") 189 } 190 191 // Test with a good one 192 tf, err := os.CreateTemp("", "packer") 193 if err != nil { 194 t.Fatalf("error tempfile: %s", err) 195 } 196 defer os.Remove(tf.Name()) 197 defer tf.Close() 198 199 config["script"] = tf.Name() 200 p = new(Provisioner) 201 err = p.Prepare(config) 202 if err != nil { 203 t.Fatalf("should not have error: %s", err) 204 } 205 } 206 207 func TestProvisionerPrepare_ScriptAndInline(t *testing.T) { 208 var p Provisioner 209 config := testConfig() 210 211 delete(config, "inline") 212 delete(config, "script") 213 err := p.Prepare(config) 214 if err == nil { 215 t.Fatal("should have error") 216 } 217 218 // Test with both 219 tf, err := os.CreateTemp("", "packer") 220 if err != nil { 221 t.Fatalf("error tempfile: %s", err) 222 } 223 defer os.Remove(tf.Name()) 224 defer tf.Close() 225 226 config["inline"] = []interface{}{"foo"} 227 config["script"] = tf.Name() 228 err = p.Prepare(config) 229 if err == nil { 230 t.Fatal("should have error") 231 } 232 } 233 234 func TestProvisionerPrepare_ScriptAndScripts(t *testing.T) { 235 var p Provisioner 236 config := testConfig() 237 238 // Test with both 239 tf, err := os.CreateTemp("", "packer") 240 if err != nil { 241 t.Fatalf("error tempfile: %s", err) 242 } 243 defer os.Remove(tf.Name()) 244 defer tf.Close() 245 246 config["inline"] = []interface{}{"foo"} 247 config["scripts"] = []string{tf.Name()} 248 err = p.Prepare(config) 249 if err == nil { 250 t.Fatal("should have error") 251 } 252 } 253 254 func TestProvisionerPrepare_Scripts(t *testing.T) { 255 config := testConfig() 256 delete(config, "inline") 257 258 config["scripts"] = []string{} 259 p := new(Provisioner) 260 err := p.Prepare(config) 261 if err == nil { 262 t.Fatal("should have error") 263 } 264 265 // Test with a good one 266 tf, err := os.CreateTemp("", "packer") 267 if err != nil { 268 t.Fatalf("error tempfile: %s", err) 269 } 270 defer os.Remove(tf.Name()) 271 defer tf.Close() 272 273 config["scripts"] = []string{tf.Name()} 274 p = new(Provisioner) 275 err = p.Prepare(config) 276 if err != nil { 277 t.Fatalf("should not have error: %s", err) 278 } 279 } 280 281 func TestProvisionerPrepare_Pwsh(t *testing.T) { 282 283 config := testConfig() 284 285 config["use_pwsh"] = true 286 287 p := new(Provisioner) 288 err := p.Prepare(config) 289 290 if err != nil { 291 t.Fatalf("Should not be error: %s", err) 292 } 293 294 if !p.config.UsePwsh { 295 t.Fatalf("Expected 'pwsh' to be: true") 296 } 297 } 298 299 func TestProvisionerPrepare_EnvironmentVars(t *testing.T) { 300 config := testConfig() 301 302 // Test with a bad case 303 config["environment_vars"] = []string{"badvar", "good=var"} 304 p := new(Provisioner) 305 err := p.Prepare(config) 306 if err == nil { 307 t.Fatal("should have error") 308 } 309 310 // Test with a trickier case 311 config["environment_vars"] = []string{"=bad"} 312 p = new(Provisioner) 313 err = p.Prepare(config) 314 if err == nil { 315 t.Fatal("should have error") 316 } 317 318 // Test with a good case 319 // Note: baz= is a real env variable, just empty 320 config["environment_vars"] = []string{"FOO=bar", "baz="} 321 p = new(Provisioner) 322 err = p.Prepare(config) 323 if err != nil { 324 t.Fatalf("should not have error: %s", err) 325 } 326 327 // Test when the env variable value contains an equals sign 328 config["environment_vars"] = []string{"good=withequals=true"} 329 p = new(Provisioner) 330 err = p.Prepare(config) 331 if err != nil { 332 t.Fatalf("should not have error: %s", err) 333 } 334 335 // Test when the env variable value starts with an equals sign 336 config["environment_vars"] = []string{"good==true"} 337 p = new(Provisioner) 338 err = p.Prepare(config) 339 if err != nil { 340 t.Fatalf("should not have error: %s", err) 341 } 342 343 } 344 345 func TestProvisionerQuote_EnvironmentVars(t *testing.T) { 346 config := testConfig() 347 348 config["environment_vars"] = []string{ 349 "keyone=valueone", 350 "keytwo=value\ntwo", 351 "keythree='valuethree'", 352 "keyfour='value\nfour'", 353 "keyfive='value=five'", 354 "keysix='=six'", 355 } 356 357 expected := []string{ 358 "keyone=valueone", 359 "keytwo=value\ntwo", 360 "keythree='valuethree'", 361 "keyfour='value\nfour'", 362 "keyfive='value=five'", 363 "keysix='=six'", 364 } 365 366 p := new(Provisioner) 367 p.Prepare(config) 368 369 for i, expectedValue := range expected { 370 if p.config.Vars[i] != expectedValue { 371 t.Fatalf("%s should be equal to %s", p.config.Vars[i], expectedValue) 372 } 373 } 374 } 375 376 func testUi() *packersdk.BasicUi { 377 return &packersdk.BasicUi{ 378 Reader: new(bytes.Buffer), 379 Writer: new(bytes.Buffer), 380 ErrorWriter: new(bytes.Buffer), 381 } 382 } 383 384 func TestProvisionerProvision_ValidExitCodes(t *testing.T) { 385 config := testConfig() 386 delete(config, "inline") 387 388 // Defaults provided by Packer 389 config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" 390 config["inline"] = []string{"whoami"} 391 ui := testUi() 392 p := new(Provisioner) 393 394 // Defaults provided by Packer 395 p.config.PackerBuildName = "vmware" 396 p.config.PackerBuilderType = "iso" 397 p.config.ValidExitCodes = []int{0, 200} 398 comm := new(packersdk.MockCommunicator) 399 comm.StartExitStatus = 200 400 p.Prepare(config) 401 err := p.Provision(context.Background(), ui, comm, generatedData()) 402 if err != nil { 403 t.Fatal("should not have error") 404 } 405 } 406 407 func TestProvisionerProvision_PauseAfter(t *testing.T) { 408 config := testConfig() 409 delete(config, "inline") 410 411 // Defaults provided by Packer 412 config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" 413 config["inline"] = []string{"whoami"} 414 ui := testUi() 415 p := new(Provisioner) 416 417 // Defaults provided by Packer 418 p.config.PackerBuildName = "vmware" 419 p.config.PackerBuilderType = "iso" 420 p.config.ValidExitCodes = []int{0, 200} 421 pause_amount := time.Second 422 p.config.PauseAfter = pause_amount 423 comm := new(packersdk.MockCommunicator) 424 comm.StartExitStatus = 200 425 err := p.Prepare(config) 426 if err != nil { 427 t.Fatalf("Prepar failed: %s", err) 428 } 429 430 start_time := time.Now() 431 err = p.Provision(context.Background(), ui, comm, generatedData()) 432 end_time := time.Now() 433 434 if err != nil { 435 t.Fatal("should not have error") 436 } 437 438 if end_time.Sub(start_time) < pause_amount { 439 t.Fatal("Didn't wait pause_amount") 440 } 441 } 442 443 func TestProvisionerProvision_InvalidExitCodes(t *testing.T) { 444 config := testConfig() 445 delete(config, "inline") 446 447 // Defaults provided by Packer 448 config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" 449 config["inline"] = []string{"whoami"} 450 ui := testUi() 451 p := new(Provisioner) 452 453 // Defaults provided by Packer 454 p.config.PackerBuildName = "vmware" 455 p.config.PackerBuilderType = "iso" 456 p.config.ValidExitCodes = []int{0, 200} 457 comm := new(packersdk.MockCommunicator) 458 comm.StartExitStatus = 201 // Invalid! 459 p.Prepare(config) 460 err := p.Provision(context.Background(), ui, comm, generatedData()) 461 if err == nil { 462 t.Fatal("should have error") 463 } 464 } 465 466 func TestProvisionerProvision_Inline(t *testing.T) { 467 // skip_clean is set to true otherwise the last command executed by the provisioner is the cleanup. 468 config := testConfigWithSkipClean() 469 delete(config, "inline") 470 471 // Defaults provided by Packer 472 config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" 473 config["inline"] = []string{"whoami"} 474 ui := testUi() 475 p := new(Provisioner) 476 477 // Defaults provided by Packer - env vars should not appear in cmd 478 p.config.PackerBuildName = "vmware" 479 p.config.PackerBuilderType = "iso" 480 comm := new(packersdk.MockCommunicator) 481 _ = p.Prepare(config) 482 483 err := p.Provision(context.Background(), ui, comm, generatedData()) 484 if err != nil { 485 t.Fatal("should not have error") 486 } 487 488 cmd := comm.StartCmd.Command 489 re := regexp.MustCompile(`powershell -executionpolicy bypass -file c:/Windows/Temp/inlineScript.ps1`) 490 491 matched := re.MatchString(cmd) 492 if !matched { 493 t.Fatalf("Got unexpected command: %s", cmd) 494 } 495 496 // User supplied env vars should not change things 497 envVars := make([]string, 2) 498 envVars[0] = "FOO=BAR" 499 envVars[1] = "BAR=BAZ" 500 config["environment_vars"] = envVars 501 config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" 502 503 p.Prepare(config) 504 err = p.Provision(context.Background(), ui, comm, generatedData()) 505 if err != nil { 506 t.Fatal("should not have error") 507 } 508 509 cmd = comm.StartCmd.Command 510 re = regexp.MustCompile(`powershell -executionpolicy bypass -file c:/Windows/Temp/inlineScript.ps1`) 511 matched = re.MatchString(cmd) 512 if !matched { 513 t.Fatalf("Got unexpected command: %s", cmd) 514 } 515 } 516 517 func TestProvisionerProvision_Scripts(t *testing.T) { 518 tempFile, _ := os.CreateTemp("", "packer") 519 defer os.Remove(tempFile.Name()) 520 defer tempFile.Close() 521 522 // skip_clean is set to true otherwise the last command executed by the provisioner is the cleanup. 523 config := testConfigWithSkipClean() 524 delete(config, "inline") 525 config["scripts"] = []string{tempFile.Name()} 526 config["packer_build_name"] = "foobuild" 527 config["packer_builder_type"] = "footype" 528 config["remote_path"] = "c:/Windows/Temp/script.ps1" 529 ui := testUi() 530 531 p := new(Provisioner) 532 comm := new(packersdk.MockCommunicator) 533 p.Prepare(config) 534 err := p.Provision(context.Background(), ui, comm, generatedData()) 535 if err != nil { 536 t.Fatal("should not have error") 537 } 538 539 cmd := comm.StartCmd.Command 540 re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1'; exit \$LastExitCode }"`) 541 matched := re.MatchString(cmd) 542 if !matched { 543 t.Fatalf("Got unexpected command: %s", cmd) 544 } 545 } 546 547 func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { 548 tempFile, _ := os.CreateTemp("", "packer") 549 ui := testUi() 550 defer os.Remove(tempFile.Name()) 551 defer tempFile.Close() 552 553 // skip_clean is set to true otherwise the last command executed by the provisioner is the cleanup. 554 config := testConfigWithSkipClean() 555 delete(config, "inline") 556 557 config["scripts"] = []string{tempFile.Name()} 558 config["packer_build_name"] = "foobuild" 559 config["packer_builder_type"] = "footype" 560 561 // Env vars - currently should not effect them 562 envVars := make([]string, 2) 563 envVars[0] = "FOO=BAR" 564 envVars[1] = "BAR=BAZ" 565 config["environment_vars"] = envVars 566 config["remote_path"] = "c:/Windows/Temp/script.ps1" 567 568 p := new(Provisioner) 569 comm := new(packersdk.MockCommunicator) 570 p.Prepare(config) 571 err := p.Provision(context.Background(), ui, comm, generatedData()) 572 if err != nil { 573 t.Fatal("should not have error") 574 } 575 576 cmd := comm.StartCmd.Command 577 re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1'; exit \$LastExitCode }"`) 578 matched := re.MatchString(cmd) 579 if !matched { 580 t.Fatalf("Got unexpected command: %s", cmd) 581 } 582 } 583 584 func TestProvisionerProvision_SkipClean(t *testing.T) { 585 tempFile, _ := os.CreateTemp("", "packer") 586 defer func() { 587 tempFile.Close() 588 os.Remove(tempFile.Name()) 589 }() 590 591 config := map[string]interface{}{ 592 "scripts": []string{tempFile.Name()}, 593 "remote_path": "c:/Windows/Temp/script.ps1", 594 } 595 596 tt := []struct { 597 SkipClean bool 598 LastExecutedCommandRegex string 599 }{ 600 { 601 SkipClean: true, 602 LastExecutedCommandRegex: `powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1'; exit \$LastExitCode }"`, 603 }, 604 { 605 SkipClean: false, 606 LastExecutedCommandRegex: `powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/packer-cleanup-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1'; exit \$LastExitCode }"`, 607 }, 608 } 609 610 for _, tc := range tt { 611 tc := tc 612 p := new(Provisioner) 613 ui := testUi() 614 comm := new(packersdk.MockCommunicator) 615 616 config["skip_clean"] = tc.SkipClean 617 if err := p.Prepare(config); err != nil { 618 t.Fatalf("failed to prepare config when SkipClean is %t: %s", tc.SkipClean, err) 619 } 620 err := p.Provision(context.Background(), ui, comm, generatedData()) 621 if err != nil { 622 t.Fatal("should not have error") 623 } 624 625 // When SkipClean is false the last executed command should be the clean up command; 626 // otherwise it will be the execution command for the provisioning script. 627 cmd := comm.StartCmd.Command 628 re := regexp.MustCompile(tc.LastExecutedCommandRegex) 629 matched := re.MatchString(cmd) 630 if !matched { 631 t.Fatalf(`Got unexpected command when SkipClean is %t: %s`, tc.SkipClean, cmd) 632 } 633 } 634 } 635 636 func TestProvisionerProvision_UploadFails(t *testing.T) { 637 config := testConfig() 638 ui := testUi() 639 640 p := new(Provisioner) 641 comm := new(packersdk.ScriptUploadErrorMockCommunicator) 642 p.Prepare(config) 643 p.config.StartRetryTimeout = 1 * time.Second 644 err := p.Provision(context.Background(), ui, comm, generatedData()) 645 if !strings.Contains(err.Error(), packersdk.ScriptUploadErrorMockCommunicatorError.Error()) { 646 t.Fatalf("expected Provision() error %q to contain %q", 647 err.Error(), 648 packersdk.ScriptUploadErrorMockCommunicatorError.Error()) 649 } 650 } 651 652 func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { 653 var flattenedEnvVars string 654 config := testConfig() 655 656 userEnvVarTests := [][]string{ 657 {}, // No user env var 658 {"FOO=bar"}, // Single user env var 659 {"FOO=bar", "BAZ=qux"}, // Multiple user env vars 660 {"FOO=bar=baz"}, // User env var with value containing equals 661 {"FOO==bar"}, // User env var with value starting with equals 662 // Test escaping of characters special to PowerShell 663 {"FOO=bar$baz"}, // User env var with value containing dollar 664 {"FOO=bar\"baz"}, // User env var with value containing a double quote 665 {"FOO=bar'baz"}, // User env var with value containing a single quote 666 {"FOO=bar`baz"}, // User env var with value containing a backtick 667 668 } 669 userEnvVarmapTests := []map[string]string{ 670 {}, 671 { 672 "BAR": "foo", 673 }, 674 { 675 "BAR": "foo", 676 "YAR": "yaa", 677 }, 678 { 679 "BAR": "foo=yaa", 680 }, 681 { 682 "BAR": "=foo", 683 }, 684 { 685 "BAR": "foo$yaa", 686 }, 687 { 688 "BAR": "foo\"yaa", 689 }, 690 { 691 "BAR": "foo'yaa", 692 }, 693 { 694 "BAR": "foo`yaa", 695 }, 696 } 697 expected := []string{ 698 `$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `, 699 `$env:BAR="foo"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `, 700 `$env:BAR="foo"; $env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; $env:YAR="yaa"; `, 701 `$env:BAR="foo=yaa"; $env:FOO="bar=baz"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `, 702 `$env:BAR="=foo"; $env:FOO="=bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `, 703 "$env:BAR=\"foo`$yaa\"; $env:FOO=\"bar`$baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ", 704 "$env:BAR=\"foo`\"yaa\"; $env:FOO=\"bar`\"baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ", 705 "$env:BAR=\"foo`'yaa\"; $env:FOO=\"bar`'baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ", 706 "$env:BAR=\"foo``yaa\"; $env:FOO=\"bar``baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ", 707 } 708 709 p := new(Provisioner) 710 p.generatedData = generatedData() 711 p.Prepare(config) 712 713 // Defaults provided by Packer 714 p.config.PackerBuildName = "vmware" 715 p.config.PackerBuilderType = "iso" 716 717 for i, expectedValue := range expected { 718 p.config.Vars = userEnvVarTests[i] 719 p.config.Env = userEnvVarmapTests[i] 720 flattenedEnvVars = p.createFlattenedEnvVars(true) 721 if flattenedEnvVars != expectedValue { 722 t.Fatalf("expected flattened env vars to be: %s, got %s.", expectedValue, flattenedEnvVars) 723 } 724 } 725 } 726 727 func TestProvisionerCorrectlyInterpolatesValidExitCodes(t *testing.T) { 728 type testCases struct { 729 Input interface{} 730 Expected []int 731 } 732 validExitCodeTests := []testCases{ 733 {"0", []int{0}}, 734 {[]string{"0"}, []int{0}}, 735 {[]int{0, 12345}, []int{0, 12345}}, 736 {[]string{"0", "12345"}, []int{0, 12345}}, 737 {"0,12345", []int{0, 12345}}, 738 } 739 740 for _, tc := range validExitCodeTests { 741 p := new(Provisioner) 742 config := testConfig() 743 config["valid_exit_codes"] = tc.Input 744 err := p.Prepare(config) 745 746 if err != nil { 747 t.Fatalf("Shouldn't have had error interpolating exit codes") 748 } 749 assert.ElementsMatchf(t, p.config.ValidExitCodes, tc.Expected, 750 fmt.Sprintf("expected exit codes to be: %#v, got %#v.", p.config.ValidExitCodes, tc.Expected)) 751 } 752 } 753 754 func TestProvisionerCorrectlyInterpolatesExecutionPolicy(t *testing.T) { 755 type testCases struct { 756 Input interface{} 757 Expected ExecutionPolicy 758 ErrExpected bool 759 } 760 tests := []testCases{ 761 { 762 Input: "bypass", 763 Expected: ExecutionPolicy(0), 764 ErrExpected: false, 765 }, 766 { 767 Input: "allsigned", 768 Expected: ExecutionPolicy(1), 769 ErrExpected: false, 770 }, 771 { 772 Input: "default", 773 Expected: ExecutionPolicy(2), 774 ErrExpected: false, 775 }, 776 { 777 Input: "remotesigned", 778 Expected: ExecutionPolicy(3), 779 ErrExpected: false, 780 }, 781 { 782 Input: "restricted", 783 Expected: ExecutionPolicy(4), 784 ErrExpected: false, 785 }, 786 { 787 Input: "undefined", 788 Expected: ExecutionPolicy(5), 789 ErrExpected: false, 790 }, 791 { 792 Input: "unrestricted", 793 Expected: ExecutionPolicy(6), 794 ErrExpected: false, 795 }, 796 { 797 Input: "none", 798 Expected: ExecutionPolicy(7), 799 ErrExpected: false, 800 }, 801 { 802 Input: "0", // User can supply a valid number for policy, too 803 Expected: 0, 804 ErrExpected: false, 805 }, 806 { 807 Input: "invalid", 808 Expected: 0, 809 ErrExpected: true, 810 }, 811 { 812 Input: "100", // If number is invalid policy, reject. 813 Expected: 100, 814 ErrExpected: true, 815 }, 816 } 817 818 for _, tc := range tests { 819 p := new(Provisioner) 820 config := testConfig() 821 config["execution_policy"] = tc.Input 822 err := p.Prepare(config) 823 824 if (err != nil) != tc.ErrExpected { 825 t.Fatalf("Either err was expected, or shouldn't have happened: %#v", tc) 826 } 827 if err == nil { 828 assert.Equal(t, p.config.ExecutionPolicy, tc.Expected, 829 fmt.Sprintf("expected %#v, got %#v.", p.config.ExecutionPolicy, tc.Expected)) 830 } 831 } 832 } 833 834 func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { 835 var flattenedEnvVars string 836 config := testConfig() 837 838 userEnvVarTests := [][]string{ 839 {}, // No user env var 840 {"FOO=bar"}, // Single user env var 841 {"FOO=bar", "BAZ=qux"}, // Multiple user env vars 842 {"FOO=bar=baz"}, // User env var with value containing equals 843 {"FOO==bar"}, // User env var with value starting with equals 844 // Test escaping of characters special to PowerShell 845 {"FOO=bar$baz"}, // User env var with value containing dollar 846 {"FOO=bar\"baz"}, // User env var with value containing a double quote 847 {"FOO=bar'baz"}, // User env var with value containing a single quote 848 {"FOO=bar`baz"}, // User env var with value containing a backtick 849 } 850 userEnvVarmapTests := []map[string]string{ 851 {}, 852 { 853 "BAR": "foo", 854 }, 855 { 856 "BAR": "foo", 857 "YAR": "yaa", 858 }, 859 { 860 "BAR": "foo=yaa", 861 }, 862 { 863 "BAR": "=foo", 864 }, 865 { 866 "BAR": "foo$yaa", 867 }, 868 { 869 "BAR": "foo\"yaa", 870 }, 871 { 872 "BAR": "foo'yaa", 873 }, 874 { 875 "BAR": "foo`yaa", 876 }, 877 } 878 expected := []string{ 879 `$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `, 880 `$env:BAR="foo"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `, 881 `$env:BAR="foo"; $env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; $env:YAR="yaa"; `, 882 `$env:BAR="foo=yaa"; $env:FOO="bar=baz"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `, 883 `$env:BAR="=foo"; $env:FOO="=bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `, 884 "$env:BAR=\"foo`$yaa\"; $env:FOO=\"bar`$baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ", 885 "$env:BAR=\"foo`\"yaa\"; $env:FOO=\"bar`\"baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ", 886 "$env:BAR=\"foo`'yaa\"; $env:FOO=\"bar`'baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ", 887 "$env:BAR=\"foo``yaa\"; $env:FOO=\"bar``baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ", 888 } 889 890 p := new(Provisioner) 891 p.generatedData = generatedData() 892 p.Prepare(config) 893 894 // Defaults provided by Packer 895 p.config.PackerBuildName = "vmware" 896 p.config.PackerBuilderType = "iso" 897 898 for i, expectedValue := range expected { 899 p.config.Vars = userEnvVarTests[i] 900 p.config.Env = userEnvVarmapTests[i] 901 flattenedEnvVars = p.createFlattenedEnvVars(false) 902 if flattenedEnvVars != expectedValue { 903 t.Fatalf("expected flattened env vars to be: %s, got %s.", expectedValue, flattenedEnvVars) 904 } 905 } 906 } 907 908 func TestProvision_createCommandText(t *testing.T) { 909 config := testConfig() 910 config["remote_path"] = "c:/Windows/Temp/script.ps1" 911 p := new(Provisioner) 912 comm := new(packersdk.MockCommunicator) 913 p.communicator = comm 914 _ = p.Prepare(config) 915 916 // Defaults provided by Packer 917 p.config.PackerBuildName = "vmware" 918 p.config.PackerBuilderType = "iso" 919 920 // Non-elevated 921 p.generatedData = make(map[string]interface{}) 922 cmd, _ := p.createCommandText() 923 924 re := regexp.MustCompile(`powershell -executionpolicy bypass -file c:/Windows/Temp/script.ps1`) 925 matched := re.MatchString(cmd) 926 if !matched { 927 t.Fatalf("Got unexpected command: %s", cmd) 928 } 929 930 // Elevated 931 p.config.ElevatedUser = "vagrant" 932 p.config.ElevatedPassword = "vagrant" 933 cmd, _ = p.createCommandText() 934 re = regexp.MustCompile(`powershell -executionpolicy bypass -file "C:/Windows/Temp/packer-elevated-shell-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1"`) 935 matched = re.MatchString(cmd) 936 if !matched { 937 t.Fatalf("Got unexpected elevated command: %s", cmd) 938 } 939 } 940 941 func TestProvision_createCommandTextNoneExecutionPolicy(t *testing.T) { 942 config := testConfig() 943 config["remote_path"] = "c:/Windows/Temp/script.ps1" 944 p := new(Provisioner) 945 946 comm := new(packersdk.MockCommunicator) 947 p.communicator = comm 948 config["execution_policy"] = ExecutionPolicyNone 949 _ = p.Prepare(config) 950 951 // Non-elevated 952 p.generatedData = make(map[string]interface{}) 953 954 cmd, _ := p.createCommandText() 955 re := regexp.MustCompile(`-file c:/Windows/Temp/script.ps1`) 956 matched := re.MatchString(cmd) 957 if !matched { 958 t.Fatalf("Got unexpected command: %s", cmd) 959 } 960 961 } 962 963 func TestProvision_uploadEnvVars(t *testing.T) { 964 p := new(Provisioner) 965 comm := new(packersdk.MockCommunicator) 966 p.communicator = comm 967 968 flattenedEnvVars := `$env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild";` 969 970 err := p.uploadEnvVars(flattenedEnvVars) 971 if err != nil { 972 t.Fatalf("Did not expect error: %s", err.Error()) 973 } 974 975 if comm.UploadCalled != true { 976 t.Fatalf("Failed to upload env var file") 977 } 978 } 979 980 func TestCancel(t *testing.T) { 981 // Don't actually call Cancel() as it performs an os.Exit(0) 982 // which kills the 'go test' tool 983 } 984 985 func testConfig() map[string]interface{} { 986 return map[string]interface{}{ 987 "inline": []interface{}{"foo", "bar"}, 988 } 989 } 990 991 func testConfigWithSkipClean() map[string]interface{} { 992 return map[string]interface{}{ 993 "inline": []interface{}{"foo", "bar"}, 994 "skip_clean": true, 995 } 996 } 997 998 func generatedData() map[string]interface{} { 999 return map[string]interface{}{ 1000 "PackerHTTPAddr": commonsteps.HttpAddrNotImplemented, 1001 "PackerHTTPIP": commonsteps.HttpIPNotImplemented, 1002 "PackerHTTPPort": commonsteps.HttpPortNotImplemented, 1003 } 1004 } 1005 1006 func normalizeWhiteSpace(s string) string { 1007 // Replace multiple spaces/tabs with a single space 1008 re := regexp.MustCompile(`[\t ]+`) 1009 s = re.ReplaceAllString(s, " ") 1010 1011 // Trim leading/trailing spaces and newlines 1012 s = strings.TrimSpace(s) 1013 1014 // Normalize line breaks (remove excessive empty lines) 1015 s = strings.ReplaceAll(s, "\r\n", "\n") // Convert Windows line endings to Unix 1016 s = strings.ReplaceAll(s, "\r", "\n") // Convert old Mac line endings to Unix 1017 1018 return s 1019 }