github.com/hashicorp/packer@v1.14.3/provisioner/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 "os" 8 "regexp" 9 "strings" 10 "testing" 11 12 "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" 13 packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 14 ) 15 16 func testConfig() map[string]interface{} { 17 return map[string]interface{}{ 18 "inline": []interface{}{"foo", "bar"}, 19 } 20 } 21 22 func TestProvisioner_Impl(t *testing.T) { 23 var raw interface{} 24 raw = &Provisioner{} 25 if _, ok := raw.(packersdk.Provisioner); !ok { 26 t.Fatalf("must be a Provisioner") 27 } 28 } 29 30 func TestProvisionerPrepare_Defaults(t *testing.T) { 31 var p Provisioner 32 config := testConfig() 33 34 err := p.Prepare(config) 35 if err != nil { 36 t.Fatalf("err: %s", err) 37 } 38 39 if p.config.ExpectDisconnect != false { 40 t.Errorf("expected ExpectDisconnect to default to false") 41 } 42 43 if p.config.RemotePath == "" { 44 t.Errorf("unexpected remote path: %s", p.config.RemotePath) 45 } 46 } 47 48 func TestProvisionerPrepare_ExpectDisconnect(t *testing.T) { 49 config := testConfig() 50 p := new(Provisioner) 51 config["expect_disconnect"] = false 52 53 err := p.Prepare(config) 54 if err != nil { 55 t.Fatalf("err: %s", err) 56 } 57 58 if p.config.ExpectDisconnect != false { 59 t.Errorf("expected ExpectDisconnect to be false") 60 } 61 62 config["expect_disconnect"] = true 63 p = new(Provisioner) 64 err = p.Prepare(config) 65 if err != nil { 66 t.Fatalf("err: %s", err) 67 } 68 69 if p.config.ExpectDisconnect != true { 70 t.Errorf("expected ExpectDisconnect to be true") 71 } 72 } 73 74 func TestProvisionerPrepare_InlineShebang(t *testing.T) { 75 config := testConfig() 76 77 delete(config, "inline_shebang") 78 p := new(Provisioner) 79 err := p.Prepare(config) 80 if err != nil { 81 t.Fatalf("should not have error: %s", err) 82 } 83 84 if p.config.InlineShebang != "/bin/sh -e" { 85 t.Fatalf("bad value: %s", p.config.InlineShebang) 86 } 87 88 // Test with a good one 89 config["inline_shebang"] = "foo" 90 p = new(Provisioner) 91 err = p.Prepare(config) 92 if err != nil { 93 t.Fatalf("should not have error: %s", err) 94 } 95 96 if p.config.InlineShebang != "foo" { 97 t.Fatalf("bad value: %s", p.config.InlineShebang) 98 } 99 } 100 101 func TestProvisionerPrepare_InvalidKey(t *testing.T) { 102 var p Provisioner 103 config := testConfig() 104 105 // Add a random key 106 config["i_should_not_be_valid"] = true 107 err := p.Prepare(config) 108 if err == nil { 109 t.Fatal("should have error") 110 } 111 } 112 113 func TestProvisionerPrepare_Script(t *testing.T) { 114 config := testConfig() 115 delete(config, "inline") 116 117 config["script"] = "/this/should/not/exist" 118 p := new(Provisioner) 119 err := p.Prepare(config) 120 if err == nil { 121 t.Fatal("should have error") 122 } 123 124 // Test with a good one 125 tf, err := os.CreateTemp("", "packer") 126 if err != nil { 127 t.Fatalf("error tempfile: %s", err) 128 } 129 defer os.Remove(tf.Name()) 130 131 config["script"] = tf.Name() 132 p = new(Provisioner) 133 err = p.Prepare(config) 134 if err != nil { 135 t.Fatalf("should not have error: %s", err) 136 } 137 } 138 139 func TestProvisionerPrepare_ScriptAndInline(t *testing.T) { 140 var p Provisioner 141 config := testConfig() 142 143 delete(config, "inline") 144 delete(config, "script") 145 err := p.Prepare(config) 146 if err == nil { 147 t.Fatal("should have error") 148 } 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 157 config["inline"] = []interface{}{"foo"} 158 config["script"] = tf.Name() 159 err = p.Prepare(config) 160 if err == nil { 161 t.Fatal("should have error") 162 } 163 } 164 165 func TestProvisionerPrepare_ScriptAndScripts(t *testing.T) { 166 var p Provisioner 167 config := testConfig() 168 169 // Test with both 170 tf, err := os.CreateTemp("", "packer") 171 if err != nil { 172 t.Fatalf("error tempfile: %s", err) 173 } 174 defer os.Remove(tf.Name()) 175 176 config["inline"] = []interface{}{"foo"} 177 config["scripts"] = []string{tf.Name()} 178 err = p.Prepare(config) 179 if err == nil { 180 t.Fatal("should have error") 181 } 182 } 183 184 func TestProvisionerPrepare_Scripts(t *testing.T) { 185 config := testConfig() 186 delete(config, "inline") 187 188 config["scripts"] = []string{} 189 p := new(Provisioner) 190 err := p.Prepare(config) 191 if err == nil { 192 t.Fatal("should have error") 193 } 194 195 // Test with a good one 196 tf, err := os.CreateTemp("", "packer") 197 if err != nil { 198 t.Fatalf("error tempfile: %s", err) 199 } 200 defer os.Remove(tf.Name()) 201 202 config["scripts"] = []string{tf.Name()} 203 p = new(Provisioner) 204 err = p.Prepare(config) 205 if err != nil { 206 t.Fatalf("should not have error: %s", err) 207 } 208 } 209 210 func TestProvisionerPrepare_EnvironmentVars(t *testing.T) { 211 config := testConfig() 212 213 // Test with a bad case 214 config["environment_vars"] = []string{"badvar", "good=var"} 215 p := new(Provisioner) 216 err := p.Prepare(config) 217 if err == nil { 218 t.Fatal("should have error") 219 } 220 221 // Test with a trickier case 222 config["environment_vars"] = []string{"=bad"} 223 p = new(Provisioner) 224 err = p.Prepare(config) 225 if err == nil { 226 t.Fatal("should have error") 227 } 228 229 // Test with a good case 230 // Note: baz= is a real env variable, just empty 231 config["environment_vars"] = []string{"FOO=bar", "baz="} 232 p = new(Provisioner) 233 err = p.Prepare(config) 234 if err != nil { 235 t.Fatalf("should not have error: %s", err) 236 } 237 238 // Test when the env variable value contains an equals sign 239 config["environment_vars"] = []string{"good=withequals=true"} 240 p = new(Provisioner) 241 err = p.Prepare(config) 242 if err != nil { 243 t.Fatalf("should not have error: %s", err) 244 } 245 246 // Test when the env variable value starts with an equals sign 247 config["environment_vars"] = []string{"good==true"} 248 p = new(Provisioner) 249 err = p.Prepare(config) 250 if err != nil { 251 t.Fatalf("should not have error: %s", err) 252 } 253 } 254 255 func TestProvisioner_createFlattenedEnvVars(t *testing.T) { 256 var flattenedEnvVars string 257 config := testConfig() 258 259 userEnvVarTests := [][]string{ 260 {}, // No user env var 261 {"FOO=bar"}, // Single user env var 262 {"FOO=bar's"}, // User env var with single quote in value 263 {"FOO=bar", "BAZ=qux"}, // Multiple user env vars 264 {"FOO=bar=baz"}, // User env var with value containing equals 265 {"FOO==bar"}, // User env var with value starting with equals 266 } 267 userEnvVarEnvmapTests := []map[string]string{ 268 {}, 269 { 270 "BAR": "foo", 271 }, 272 { 273 "BAR": "foo's", 274 }, 275 { 276 "BAR": "foo", 277 "YAR": "yaa", 278 }, 279 { 280 "BAR": "foo=yar", 281 }, 282 { 283 "BAR": "=foo", 284 }, 285 } 286 expected := []string{ 287 `PACKER_BUILDER_TYPE='iso' PACKER_BUILD_NAME='vmware' `, 288 `BAR='foo' FOO='bar' PACKER_BUILDER_TYPE='iso' PACKER_BUILD_NAME='vmware' `, 289 `BAR='foo'"'"'s' FOO='bar'"'"'s' PACKER_BUILDER_TYPE='iso' PACKER_BUILD_NAME='vmware' `, 290 `BAR='foo' BAZ='qux' FOO='bar' PACKER_BUILDER_TYPE='iso' PACKER_BUILD_NAME='vmware' YAR='yaa' `, 291 `BAR='foo=yar' FOO='bar=baz' PACKER_BUILDER_TYPE='iso' PACKER_BUILD_NAME='vmware' `, 292 `BAR='=foo' FOO='=bar' PACKER_BUILDER_TYPE='iso' PACKER_BUILD_NAME='vmware' `, 293 } 294 295 p := new(Provisioner) 296 p.generatedData = generatedData() 297 p.Prepare(config) 298 299 // Defaults provided by Packer 300 p.config.PackerBuildName = "vmware" 301 p.config.PackerBuilderType = "iso" 302 303 for i, expectedValue := range expected { 304 p.config.Vars = userEnvVarTests[i] 305 p.config.Env = userEnvVarEnvmapTests[i] 306 flattenedEnvVars = p.createFlattenedEnvVars() 307 if flattenedEnvVars != expectedValue { 308 t.Fatalf("expected flattened env vars to be: %s, got %s.", expectedValue, flattenedEnvVars) 309 } 310 } 311 } 312 313 func TestProvisioner_createFlattenedEnvVars_withEnvVarFormat(t *testing.T) { 314 var flattenedEnvVars string 315 config := testConfig() 316 317 userEnvVarTests := [][]string{ 318 {}, // No user env var 319 {"FOO=bar"}, // Single user env var 320 {"FOO=bar's"}, // User env var with single quote in value 321 {"FOO=bar", "BAZ=qux"}, // Multiple user env vars 322 {"FOO=bar=baz"}, // User env var with value containing equals 323 {"FOO==bar"}, // User env var with value starting with equals 324 } 325 userEnvVarEnvmapTests := []map[string]string{ 326 {}, 327 { 328 "BAR": "foo", 329 }, 330 { 331 "BAR": "foo's", 332 }, 333 { 334 "BAR": "foo", 335 "YAR": "yaa", 336 }, 337 { 338 "BAR": "foo=yar", 339 }, 340 { 341 "BAR": "=foo", 342 }, 343 } 344 expected := []string{ 345 `PACKER_BUILDER_TYPE=iso PACKER_BUILD_NAME=vmware `, 346 `BAR=foo FOO=bar PACKER_BUILDER_TYPE=iso PACKER_BUILD_NAME=vmware `, 347 `BAR=foo'"'"'s FOO=bar'"'"'s PACKER_BUILDER_TYPE=iso PACKER_BUILD_NAME=vmware `, 348 `BAR=foo BAZ=qux FOO=bar PACKER_BUILDER_TYPE=iso PACKER_BUILD_NAME=vmware YAR=yaa `, 349 `BAR=foo=yar FOO=bar=baz PACKER_BUILDER_TYPE=iso PACKER_BUILD_NAME=vmware `, 350 `BAR==foo FOO==bar PACKER_BUILDER_TYPE=iso PACKER_BUILD_NAME=vmware `, 351 } 352 353 p := new(Provisioner) 354 p.generatedData = generatedData() 355 p.config.EnvVarFormat = "%s=%s " 356 p.Prepare(config) 357 358 // Defaults provided by Packer 359 p.config.PackerBuildName = "vmware" 360 p.config.PackerBuilderType = "iso" 361 362 for i, expectedValue := range expected { 363 p.config.Vars = userEnvVarTests[i] 364 p.config.Env = userEnvVarEnvmapTests[i] 365 flattenedEnvVars = p.createFlattenedEnvVars() 366 if flattenedEnvVars != expectedValue { 367 t.Fatalf("expected flattened env vars to be: %s, got %s.", expectedValue, flattenedEnvVars) 368 } 369 } 370 } 371 372 func TestProvisioner_createEnvVarFileContent(t *testing.T) { 373 var flattenedEnvVars string 374 config := testConfig() 375 376 userEnvVarTests := [][]string{ 377 {}, // No user env var 378 {"FOO=bar"}, // Single user env var 379 {"FOO=bar's"}, // User env var with single quote in value 380 {"FOO=bar", "BAZ=qux"}, // Multiple user env vars 381 {"FOO=bar=baz"}, // User env var with value containing equals 382 {"FOO==bar"}, // User env var with value starting with equals 383 } 384 userEnvVarEnvmapTests := []map[string]string{ 385 {}, 386 { 387 "BAR": "foo", 388 }, 389 { 390 "BAR": "foo's", 391 }, 392 { 393 "BAR": "foo", 394 "YAR": "yaa", 395 }, 396 { 397 "BAR": "foo=yar", 398 }, 399 { 400 "BAR": "=foo", 401 }, 402 } 403 expected := []string{ 404 `export PACKER_BUILDER_TYPE='iso' 405 export PACKER_BUILD_NAME='vmware' 406 `, 407 `export BAR='foo' 408 export FOO='bar' 409 export PACKER_BUILDER_TYPE='iso' 410 export PACKER_BUILD_NAME='vmware' 411 `, 412 `export BAR='foo'"'"'s' 413 export FOO='bar'"'"'s' 414 export PACKER_BUILDER_TYPE='iso' 415 export PACKER_BUILD_NAME='vmware' 416 `, 417 `export BAR='foo' 418 export BAZ='qux' 419 export FOO='bar' 420 export PACKER_BUILDER_TYPE='iso' 421 export PACKER_BUILD_NAME='vmware' 422 export YAR='yaa' 423 `, 424 `export BAR='foo=yar' 425 export FOO='bar=baz' 426 export PACKER_BUILDER_TYPE='iso' 427 export PACKER_BUILD_NAME='vmware' 428 `, 429 `export BAR='=foo' 430 export FOO='=bar' 431 export PACKER_BUILDER_TYPE='iso' 432 export PACKER_BUILD_NAME='vmware' 433 `, 434 } 435 436 p := new(Provisioner) 437 p.generatedData = generatedData() 438 p.config.UseEnvVarFile = true 439 p.Prepare(config) 440 441 // Defaults provided by Packer 442 p.config.PackerBuildName = "vmware" 443 p.config.PackerBuilderType = "iso" 444 445 for i, expectedValue := range expected { 446 p.config.Vars = userEnvVarTests[i] 447 p.config.Env = userEnvVarEnvmapTests[i] 448 flattenedEnvVars = p.createEnvVarFileContent() 449 if flattenedEnvVars != expectedValue { 450 t.Fatalf("expected flattened env vars to be: %s, got %s.", expectedValue, flattenedEnvVars) 451 } 452 } 453 } 454 455 func TestProvisioner_createEnvVarFileContent_withEnvVarFormat(t *testing.T) { 456 var flattenedEnvVars string 457 config := testConfig() 458 459 userEnvVarTests := [][]string{ 460 {}, // No user env var 461 {"FOO=bar", "BAZ=qux"}, // Multiple user env vars 462 {"FOO=bar=baz"}, // User env var with value containing equals 463 {"FOO==bar"}, // User env var with value starting with equals 464 } 465 userEnvVarEnvmapTests := []map[string]string{ 466 {}, 467 { 468 "BAR": "foo", 469 "YAR": "yaa", 470 }, 471 { 472 "BAR": "foo=yar", 473 }, 474 { 475 "BAR": "=foo", 476 }, 477 } 478 expected := []string{ 479 `PACKER_BUILDER_TYPE=iso 480 PACKER_BUILD_NAME=vmware 481 `, 482 `BAR=foo 483 BAZ=qux 484 FOO=bar 485 PACKER_BUILDER_TYPE=iso 486 PACKER_BUILD_NAME=vmware 487 YAR=yaa 488 `, 489 `BAR=foo=yar 490 FOO=bar=baz 491 PACKER_BUILDER_TYPE=iso 492 PACKER_BUILD_NAME=vmware 493 `, 494 `BAR==foo 495 FOO==bar 496 PACKER_BUILDER_TYPE=iso 497 PACKER_BUILD_NAME=vmware 498 `, 499 } 500 501 p := new(Provisioner) 502 p.generatedData = generatedData() 503 p.config.UseEnvVarFile = true 504 //User provided env_var_format without export prefix 505 p.config.EnvVarFormat = "%s=%s\n" 506 p.Prepare(config) 507 508 // Defaults provided by Packer 509 p.config.PackerBuildName = "vmware" 510 p.config.PackerBuilderType = "iso" 511 512 for i, expectedValue := range expected { 513 p.config.Vars = userEnvVarTests[i] 514 p.config.Env = userEnvVarEnvmapTests[i] 515 flattenedEnvVars = p.createEnvVarFileContent() 516 if flattenedEnvVars != expectedValue { 517 t.Fatalf("expected flattened env vars to be: %q, got %q.", expectedValue, flattenedEnvVars) 518 } 519 } 520 } 521 522 func TestProvisioner_RemoteFolderSetSuccessfully(t *testing.T) { 523 config := testConfig() 524 525 expectedRemoteFolder := "/example/path" 526 config["remote_folder"] = expectedRemoteFolder 527 528 p := new(Provisioner) 529 err := p.Prepare(config) 530 if err != nil { 531 t.Fatalf("should not have error: %s", err) 532 } 533 534 if !strings.Contains(p.config.RemotePath, expectedRemoteFolder) { 535 t.Fatalf("remote path does not contain remote_folder") 536 } 537 } 538 539 func TestProvisioner_RemoteFolderDefaultsToTmp(t *testing.T) { 540 config := testConfig() 541 542 p := new(Provisioner) 543 err := p.Prepare(config) 544 if err != nil { 545 t.Fatalf("should not have error: %s", err) 546 } 547 548 if p.config.RemoteFolder != "/tmp" { 549 t.Fatalf("remote_folder did not default to /tmp") 550 } 551 552 if !strings.Contains(p.config.RemotePath, "/tmp") { 553 t.Fatalf("remote path does not contain remote_folder") 554 } 555 } 556 557 func TestProvisioner_RemoteFileSetSuccessfully(t *testing.T) { 558 config := testConfig() 559 560 expectedRemoteFile := "example.sh" 561 config["remote_file"] = expectedRemoteFile 562 563 p := new(Provisioner) 564 err := p.Prepare(config) 565 if err != nil { 566 t.Fatalf("should not have error: %s", err) 567 } 568 569 if !strings.Contains(p.config.RemotePath, expectedRemoteFile) { 570 t.Fatalf("remote path does not contain remote_file") 571 } 572 } 573 574 func TestProvisioner_RemoteFileDefaultsToScriptnnnn(t *testing.T) { 575 config := testConfig() 576 577 p := new(Provisioner) 578 err := p.Prepare(config) 579 if err != nil { 580 t.Fatalf("should not have error: %s", err) 581 } 582 583 remoteFileRegex := regexp.MustCompile("script_[0-9]{1,4}.sh") 584 585 if !remoteFileRegex.MatchString(p.config.RemoteFile) { 586 t.Fatalf("remote_file did not default to script_nnnn.sh: %q", p.config.RemoteFile) 587 } 588 589 if !remoteFileRegex.MatchString(p.config.RemotePath) { 590 t.Fatalf("remote_path did not match script_nnnn.sh: %q", p.config.RemotePath) 591 } 592 } 593 594 func TestProvisioner_RemotePathSetViaRemotePathAndRemoteFile(t *testing.T) { 595 config := testConfig() 596 597 expectedRemoteFile := "example.sh" 598 expectedRemoteFolder := "/example/path" 599 config["remote_file"] = expectedRemoteFile 600 config["remote_folder"] = expectedRemoteFolder 601 602 p := new(Provisioner) 603 err := p.Prepare(config) 604 if err != nil { 605 t.Fatalf("should not have error: %s", err) 606 } 607 608 if p.config.RemotePath != expectedRemoteFolder+"/"+expectedRemoteFile { 609 t.Fatalf("remote path does not contain remote_file: %q", p.config.RemotePath) 610 } 611 } 612 613 func TestProvisioner_RemotePathOverridesRemotePathAndRemoteFile(t *testing.T) { 614 config := testConfig() 615 616 expectedRemoteFile := "example.sh" 617 expectedRemoteFolder := "/example/path" 618 expectedRemotePath := "/example/remote/path/script.sh" 619 config["remote_file"] = expectedRemoteFile 620 config["remote_folder"] = expectedRemoteFolder 621 config["remote_path"] = expectedRemotePath 622 623 p := new(Provisioner) 624 err := p.Prepare(config) 625 if err != nil { 626 t.Fatalf("should not have error: %s", err) 627 } 628 629 if p.config.RemotePath != expectedRemotePath { 630 t.Fatalf("remote path does not contain remote_path: %q", p.config.RemotePath) 631 } 632 } 633 634 func TestProvisionerRemotePathDefaultsSuccessfully(t *testing.T) { 635 config := testConfig() 636 637 p := new(Provisioner) 638 err := p.Prepare(config) 639 if err != nil { 640 t.Fatalf("should not have error: %s", err) 641 } 642 643 remotePathRegex := regexp.MustCompile("/tmp/script_[0-9]{1,4}.sh") 644 645 if !remotePathRegex.MatchString(p.config.RemotePath) { 646 t.Fatalf("remote path does not match the expected default regex: %q", p.config.RemotePath) 647 } 648 } 649 650 func generatedData() map[string]interface{} { 651 return map[string]interface{}{ 652 "PackerHTTPAddr": commonsteps.HttpAddrNotImplemented, 653 "PackerHTTPIP": commonsteps.HttpIPNotImplemented, 654 "PackerHTTPPort": commonsteps.HttpPortNotImplemented, 655 } 656 }