1package command 2 3import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "log" 10 "os" 11 "path/filepath" 12 "strings" 13 "testing" 14 15 "github.com/davecgh/go-spew/spew" 16 "github.com/google/go-cmp/cmp" 17 "github.com/mitchellh/cli" 18 "github.com/zclconf/go-cty/cty" 19 20 "github.com/hashicorp/terraform/internal/addrs" 21 "github.com/hashicorp/terraform/internal/configs" 22 "github.com/hashicorp/terraform/internal/configs/configschema" 23 "github.com/hashicorp/terraform/internal/depsfile" 24 "github.com/hashicorp/terraform/internal/getproviders" 25 "github.com/hashicorp/terraform/internal/providercache" 26 "github.com/hashicorp/terraform/internal/states" 27 "github.com/hashicorp/terraform/internal/states/statemgr" 28) 29 30func TestInit_empty(t *testing.T) { 31 // Create a temporary working directory that is empty 32 td := tempDir(t) 33 os.MkdirAll(td, 0755) 34 defer os.RemoveAll(td) 35 defer testChdir(t, td)() 36 37 ui := new(cli.MockUi) 38 view, _ := testView(t) 39 c := &InitCommand{ 40 Meta: Meta{ 41 testingOverrides: metaOverridesForProvider(testProvider()), 42 Ui: ui, 43 View: view, 44 }, 45 } 46 47 args := []string{} 48 if code := c.Run(args); code != 0 { 49 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 50 } 51} 52 53func TestInit_multipleArgs(t *testing.T) { 54 // Create a temporary working directory that is empty 55 td := tempDir(t) 56 os.MkdirAll(td, 0755) 57 defer os.RemoveAll(td) 58 defer testChdir(t, td)() 59 60 ui := new(cli.MockUi) 61 view, _ := testView(t) 62 c := &InitCommand{ 63 Meta: Meta{ 64 testingOverrides: metaOverridesForProvider(testProvider()), 65 Ui: ui, 66 View: view, 67 }, 68 } 69 70 args := []string{ 71 "bad", 72 "bad", 73 } 74 if code := c.Run(args); code != 1 { 75 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 76 } 77} 78 79func TestInit_fromModule_cwdDest(t *testing.T) { 80 // Create a temporary working directory that is empty 81 td := tempDir(t) 82 os.MkdirAll(td, os.ModePerm) 83 defer os.RemoveAll(td) 84 defer testChdir(t, td)() 85 86 ui := new(cli.MockUi) 87 view, _ := testView(t) 88 c := &InitCommand{ 89 Meta: Meta{ 90 testingOverrides: metaOverridesForProvider(testProvider()), 91 Ui: ui, 92 View: view, 93 }, 94 } 95 96 args := []string{ 97 "-from-module=" + testFixturePath("init"), 98 } 99 if code := c.Run(args); code != 0 { 100 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 101 } 102 103 if _, err := os.Stat(filepath.Join(td, "hello.tf")); err != nil { 104 t.Fatalf("err: %s", err) 105 } 106} 107 108// https://github.com/hashicorp/terraform/issues/518 109func TestInit_fromModule_dstInSrc(t *testing.T) { 110 dir := tempDir(t) 111 if err := os.MkdirAll(dir, 0755); err != nil { 112 t.Fatalf("err: %s", err) 113 } 114 defer os.RemoveAll(dir) 115 116 // Change to the temporary directory 117 cwd, err := os.Getwd() 118 if err != nil { 119 t.Fatalf("err: %s", err) 120 } 121 if err := os.Chdir(dir); err != nil { 122 t.Fatalf("err: %s", err) 123 } 124 defer os.Chdir(cwd) 125 126 if err := os.Mkdir("foo", os.ModePerm); err != nil { 127 t.Fatal(err) 128 } 129 130 if _, err := os.Create("issue518.tf"); err != nil { 131 t.Fatalf("err: %s", err) 132 } 133 134 if err := os.Chdir("foo"); err != nil { 135 t.Fatalf("err: %s", err) 136 } 137 138 ui := new(cli.MockUi) 139 view, _ := testView(t) 140 c := &InitCommand{ 141 Meta: Meta{ 142 testingOverrides: metaOverridesForProvider(testProvider()), 143 Ui: ui, 144 View: view, 145 }, 146 } 147 148 args := []string{ 149 "-from-module=./..", 150 } 151 if code := c.Run(args); code != 0 { 152 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 153 } 154 155 if _, err := os.Stat(filepath.Join(dir, "foo", "issue518.tf")); err != nil { 156 t.Fatalf("err: %s", err) 157 } 158} 159 160func TestInit_get(t *testing.T) { 161 // Create a temporary working directory that is empty 162 td := tempDir(t) 163 testCopyDir(t, testFixturePath("init-get"), td) 164 defer os.RemoveAll(td) 165 defer testChdir(t, td)() 166 167 ui := new(cli.MockUi) 168 view, _ := testView(t) 169 c := &InitCommand{ 170 Meta: Meta{ 171 testingOverrides: metaOverridesForProvider(testProvider()), 172 Ui: ui, 173 View: view, 174 }, 175 } 176 177 args := []string{} 178 if code := c.Run(args); code != 0 { 179 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 180 } 181 182 // Check output 183 output := ui.OutputWriter.String() 184 if !strings.Contains(output, "foo in foo") { 185 t.Fatalf("doesn't look like we installed module 'foo': %s", output) 186 } 187} 188 189func TestInit_getUpgradeModules(t *testing.T) { 190 // Create a temporary working directory that is empty 191 td := tempDir(t) 192 testCopyDir(t, testFixturePath("init-get"), td) 193 defer os.RemoveAll(td) 194 defer testChdir(t, td)() 195 196 ui := new(cli.MockUi) 197 view, _ := testView(t) 198 c := &InitCommand{ 199 Meta: Meta{ 200 testingOverrides: metaOverridesForProvider(testProvider()), 201 Ui: ui, 202 View: view, 203 }, 204 } 205 206 args := []string{ 207 "-get=true", 208 "-upgrade", 209 } 210 if code := c.Run(args); code != 0 { 211 t.Fatalf("command did not complete successfully:\n%s", ui.ErrorWriter.String()) 212 } 213 214 // Check output 215 output := ui.OutputWriter.String() 216 if !strings.Contains(output, "Upgrading modules...") { 217 t.Fatalf("doesn't look like get upgrade: %s", output) 218 } 219} 220 221func TestInit_backend(t *testing.T) { 222 // Create a temporary working directory that is empty 223 td := tempDir(t) 224 testCopyDir(t, testFixturePath("init-backend"), td) 225 defer os.RemoveAll(td) 226 defer testChdir(t, td)() 227 228 ui := new(cli.MockUi) 229 view, _ := testView(t) 230 c := &InitCommand{ 231 Meta: Meta{ 232 testingOverrides: metaOverridesForProvider(testProvider()), 233 Ui: ui, 234 View: view, 235 }, 236 } 237 238 args := []string{} 239 if code := c.Run(args); code != 0 { 240 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 241 } 242 243 if _, err := os.Stat(filepath.Join(DefaultDataDir, DefaultStateFilename)); err != nil { 244 t.Fatalf("err: %s", err) 245 } 246} 247 248func TestInit_backendUnset(t *testing.T) { 249 // Create a temporary working directory that is empty 250 td := tempDir(t) 251 testCopyDir(t, testFixturePath("init-backend"), td) 252 defer os.RemoveAll(td) 253 defer testChdir(t, td)() 254 255 { 256 log.Printf("[TRACE] TestInit_backendUnset: beginning first init") 257 258 ui := cli.NewMockUi() 259 view, _ := testView(t) 260 c := &InitCommand{ 261 Meta: Meta{ 262 testingOverrides: metaOverridesForProvider(testProvider()), 263 Ui: ui, 264 View: view, 265 }, 266 } 267 268 // Init 269 args := []string{} 270 if code := c.Run(args); code != 0 { 271 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 272 } 273 log.Printf("[TRACE] TestInit_backendUnset: first init complete") 274 t.Logf("First run output:\n%s", ui.OutputWriter.String()) 275 t.Logf("First run errors:\n%s", ui.ErrorWriter.String()) 276 277 if _, err := os.Stat(filepath.Join(DefaultDataDir, DefaultStateFilename)); err != nil { 278 t.Fatalf("err: %s", err) 279 } 280 } 281 282 { 283 log.Printf("[TRACE] TestInit_backendUnset: beginning second init") 284 285 // Unset 286 if err := ioutil.WriteFile("main.tf", []byte(""), 0644); err != nil { 287 t.Fatalf("err: %s", err) 288 } 289 290 ui := cli.NewMockUi() 291 view, _ := testView(t) 292 c := &InitCommand{ 293 Meta: Meta{ 294 testingOverrides: metaOverridesForProvider(testProvider()), 295 Ui: ui, 296 View: view, 297 }, 298 } 299 300 args := []string{"-force-copy"} 301 if code := c.Run(args); code != 0 { 302 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 303 } 304 log.Printf("[TRACE] TestInit_backendUnset: second init complete") 305 t.Logf("Second run output:\n%s", ui.OutputWriter.String()) 306 t.Logf("Second run errors:\n%s", ui.ErrorWriter.String()) 307 308 s := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) 309 if !s.Backend.Empty() { 310 t.Fatal("should not have backend config") 311 } 312 } 313} 314 315func TestInit_backendConfigFile(t *testing.T) { 316 // Create a temporary working directory that is empty 317 td := tempDir(t) 318 testCopyDir(t, testFixturePath("init-backend-config-file"), td) 319 defer os.RemoveAll(td) 320 defer testChdir(t, td)() 321 322 t.Run("good-config-file", func(t *testing.T) { 323 ui := new(cli.MockUi) 324 view, _ := testView(t) 325 c := &InitCommand{ 326 Meta: Meta{ 327 testingOverrides: metaOverridesForProvider(testProvider()), 328 Ui: ui, 329 View: view, 330 }, 331 } 332 args := []string{"-backend-config", "input.config"} 333 if code := c.Run(args); code != 0 { 334 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 335 } 336 337 // Read our saved backend config and verify we have our settings 338 state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) 339 if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want { 340 t.Errorf("wrong config\ngot: %s\nwant: %s", got, want) 341 } 342 }) 343 344 // the backend config file must not be a full terraform block 345 t.Run("full-backend-config-file", func(t *testing.T) { 346 ui := new(cli.MockUi) 347 view, _ := testView(t) 348 c := &InitCommand{ 349 Meta: Meta{ 350 testingOverrides: metaOverridesForProvider(testProvider()), 351 Ui: ui, 352 View: view, 353 }, 354 } 355 args := []string{"-backend-config", "backend.config"} 356 if code := c.Run(args); code != 1 { 357 t.Fatalf("expected error, got success\n") 358 } 359 if !strings.Contains(ui.ErrorWriter.String(), "Unsupported block type") { 360 t.Fatalf("wrong error: %s", ui.ErrorWriter) 361 } 362 }) 363 364 // the backend config file must match the schema for the backend 365 t.Run("invalid-config-file", func(t *testing.T) { 366 ui := new(cli.MockUi) 367 view, _ := testView(t) 368 c := &InitCommand{ 369 Meta: Meta{ 370 testingOverrides: metaOverridesForProvider(testProvider()), 371 Ui: ui, 372 View: view, 373 }, 374 } 375 args := []string{"-backend-config", "invalid.config"} 376 if code := c.Run(args); code != 1 { 377 t.Fatalf("expected error, got success\n") 378 } 379 if !strings.Contains(ui.ErrorWriter.String(), "Unsupported argument") { 380 t.Fatalf("wrong error: %s", ui.ErrorWriter) 381 } 382 }) 383 384 // missing file is an error 385 t.Run("missing-config-file", func(t *testing.T) { 386 ui := new(cli.MockUi) 387 view, _ := testView(t) 388 c := &InitCommand{ 389 Meta: Meta{ 390 testingOverrides: metaOverridesForProvider(testProvider()), 391 Ui: ui, 392 View: view, 393 }, 394 } 395 args := []string{"-backend-config", "missing.config"} 396 if code := c.Run(args); code != 1 { 397 t.Fatalf("expected error, got success\n") 398 } 399 if !strings.Contains(ui.ErrorWriter.String(), "Failed to read file") { 400 t.Fatalf("wrong error: %s", ui.ErrorWriter) 401 } 402 }) 403 404 // blank filename clears the backend config 405 t.Run("blank-config-file", func(t *testing.T) { 406 ui := new(cli.MockUi) 407 view, _ := testView(t) 408 c := &InitCommand{ 409 Meta: Meta{ 410 testingOverrides: metaOverridesForProvider(testProvider()), 411 Ui: ui, 412 View: view, 413 }, 414 } 415 args := []string{"-backend-config=", "-migrate-state"} 416 if code := c.Run(args); code != 0 { 417 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 418 } 419 420 // Read our saved backend config and verify the backend config is empty 421 state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) 422 if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":null,"workspace_dir":null}`; got != want { 423 t.Errorf("wrong config\ngot: %s\nwant: %s", got, want) 424 } 425 }) 426 427 // simulate the local backend having a required field which is not 428 // specified in the override file 429 t.Run("required-argument", func(t *testing.T) { 430 c := &InitCommand{} 431 schema := &configschema.Block{ 432 Attributes: map[string]*configschema.Attribute{ 433 "path": { 434 Type: cty.String, 435 Optional: true, 436 }, 437 "workspace_dir": { 438 Type: cty.String, 439 Required: true, 440 }, 441 }, 442 } 443 flagConfigExtra := newRawFlags("-backend-config") 444 flagConfigExtra.Set("input.config") 445 _, diags := c.backendConfigOverrideBody(flagConfigExtra, schema) 446 if len(diags) != 0 { 447 t.Errorf("expected no diags, got: %s", diags.Err()) 448 } 449 }) 450} 451 452func TestInit_backendConfigFilePowershellConfusion(t *testing.T) { 453 // Create a temporary working directory that is empty 454 td := tempDir(t) 455 testCopyDir(t, testFixturePath("init-backend-config-file"), td) 456 defer os.RemoveAll(td) 457 defer testChdir(t, td)() 458 459 ui := new(cli.MockUi) 460 view, _ := testView(t) 461 c := &InitCommand{ 462 Meta: Meta{ 463 testingOverrides: metaOverridesForProvider(testProvider()), 464 Ui: ui, 465 View: view, 466 }, 467 } 468 469 // SUBTLE: when using -flag=value with Powershell, unquoted values are 470 // broken into separate arguments. This results in the init command 471 // interpreting the flags as an empty backend-config setting (which is 472 // semantically valid!) followed by a custom configuration path. 473 // 474 // Adding the "=" here forces this codepath to be checked, and it should 475 // result in an early exit with a diagnostic that the provided 476 // configuration file is not a diretory. 477 args := []string{"-backend-config=", "./input.config"} 478 if code := c.Run(args); code != 1 { 479 t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String()) 480 } 481 482 output := ui.ErrorWriter.String() 483 if got, want := output, `Too many command line arguments`; !strings.Contains(got, want) { 484 t.Fatalf("wrong output\ngot:\n%s\n\nwant: message containing %q", got, want) 485 } 486} 487 488func TestInit_backendConfigFileChange(t *testing.T) { 489 // Create a temporary working directory that is empty 490 td := tempDir(t) 491 testCopyDir(t, testFixturePath("init-backend-config-file-change"), td) 492 defer os.RemoveAll(td) 493 defer testChdir(t, td)() 494 495 // Ask input 496 defer testInputMap(t, map[string]string{ 497 "backend-migrate-to-new": "no", 498 })() 499 500 ui := new(cli.MockUi) 501 view, _ := testView(t) 502 c := &InitCommand{ 503 Meta: Meta{ 504 testingOverrides: metaOverridesForProvider(testProvider()), 505 Ui: ui, 506 View: view, 507 }, 508 } 509 510 args := []string{"-backend-config", "input.config", "-migrate-state"} 511 if code := c.Run(args); code != 0 { 512 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 513 } 514 515 // Read our saved backend config and verify we have our settings 516 state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) 517 if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want { 518 t.Errorf("wrong config\ngot: %s\nwant: %s", got, want) 519 } 520} 521 522func TestInit_backendMigrateWhileLocked(t *testing.T) { 523 // Create a temporary working directory that is empty 524 td := tempDir(t) 525 testCopyDir(t, testFixturePath("init-backend-migrate-while-locked"), td) 526 defer os.RemoveAll(td) 527 defer testChdir(t, td)() 528 529 providerSource, close := newMockProviderSource(t, map[string][]string{ 530 "hashicorp/test": {"1.2.3"}, 531 }) 532 defer close() 533 534 ui := new(cli.MockUi) 535 view, _ := testView(t) 536 c := &InitCommand{ 537 Meta: Meta{ 538 testingOverrides: metaOverridesForProvider(testProvider()), 539 ProviderSource: providerSource, 540 Ui: ui, 541 View: view, 542 }, 543 } 544 545 // Create some state, so the backend has something to migrate from 546 f, err := os.Create("local-state.tfstate") 547 if err != nil { 548 t.Fatalf("err: %s", err) 549 } 550 err = writeStateForTesting(testState(), f) 551 f.Close() 552 if err != nil { 553 t.Fatalf("err: %s", err) 554 } 555 556 // Lock the source state 557 unlock, err := testLockState(testDataDir, "local-state.tfstate") 558 if err != nil { 559 t.Fatal(err) 560 } 561 defer unlock() 562 563 // Attempt to migrate 564 args := []string{"-backend-config", "input.config", "-migrate-state", "-force-copy"} 565 if code := c.Run(args); code == 0 { 566 t.Fatalf("expected nonzero exit code: %s", ui.OutputWriter.String()) 567 } 568 569 // Disabling locking should work 570 args = []string{"-backend-config", "input.config", "-migrate-state", "-force-copy", "-lock=false"} 571 if code := c.Run(args); code != 0 { 572 t.Fatalf("expected zero exit code, got %d: %s", code, ui.ErrorWriter.String()) 573 } 574} 575 576func TestInit_backendConfigKV(t *testing.T) { 577 // Create a temporary working directory that is empty 578 td := tempDir(t) 579 testCopyDir(t, testFixturePath("init-backend-config-kv"), td) 580 defer os.RemoveAll(td) 581 defer testChdir(t, td)() 582 583 ui := new(cli.MockUi) 584 view, _ := testView(t) 585 c := &InitCommand{ 586 Meta: Meta{ 587 testingOverrides: metaOverridesForProvider(testProvider()), 588 Ui: ui, 589 View: view, 590 }, 591 } 592 593 args := []string{"-backend-config", "path=hello"} 594 if code := c.Run(args); code != 0 { 595 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 596 } 597 598 // Read our saved backend config and verify we have our settings 599 state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) 600 if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want { 601 t.Errorf("wrong config\ngot: %s\nwant: %s", got, want) 602 } 603} 604 605func TestInit_backendConfigKVReInit(t *testing.T) { 606 // Create a temporary working directory that is empty 607 td := tempDir(t) 608 testCopyDir(t, testFixturePath("init-backend-config-kv"), td) 609 defer os.RemoveAll(td) 610 defer testChdir(t, td)() 611 612 ui := new(cli.MockUi) 613 view, _ := testView(t) 614 c := &InitCommand{ 615 Meta: Meta{ 616 testingOverrides: metaOverridesForProvider(testProvider()), 617 Ui: ui, 618 View: view, 619 }, 620 } 621 622 args := []string{"-backend-config", "path=test"} 623 if code := c.Run(args); code != 0 { 624 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 625 } 626 627 ui = new(cli.MockUi) 628 c = &InitCommand{ 629 Meta: Meta{ 630 testingOverrides: metaOverridesForProvider(testProvider()), 631 Ui: ui, 632 View: view, 633 }, 634 } 635 636 // a second init should require no changes, nor should it change the backend. 637 args = []string{"-input=false"} 638 if code := c.Run(args); code != 0 { 639 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 640 } 641 642 // make sure the backend is configured how we expect 643 configState := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) 644 cfg := map[string]interface{}{} 645 if err := json.Unmarshal(configState.Backend.ConfigRaw, &cfg); err != nil { 646 t.Fatal(err) 647 } 648 if cfg["path"] != "test" { 649 t.Fatalf(`expected backend path="test", got path="%v"`, cfg["path"]) 650 } 651 652 // override the -backend-config options by settings 653 args = []string{"-input=false", "-backend-config", "", "-migrate-state"} 654 if code := c.Run(args); code != 0 { 655 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 656 } 657 658 // make sure the backend is configured how we expect 659 configState = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) 660 cfg = map[string]interface{}{} 661 if err := json.Unmarshal(configState.Backend.ConfigRaw, &cfg); err != nil { 662 t.Fatal(err) 663 } 664 if cfg["path"] != nil { 665 t.Fatalf(`expected backend path="<nil>", got path="%v"`, cfg["path"]) 666 } 667} 668 669func TestInit_backendConfigKVReInitWithConfigDiff(t *testing.T) { 670 // Create a temporary working directory that is empty 671 td := tempDir(t) 672 testCopyDir(t, testFixturePath("init-backend"), td) 673 defer os.RemoveAll(td) 674 defer testChdir(t, td)() 675 676 ui := new(cli.MockUi) 677 view, _ := testView(t) 678 c := &InitCommand{ 679 Meta: Meta{ 680 testingOverrides: metaOverridesForProvider(testProvider()), 681 Ui: ui, 682 View: view, 683 }, 684 } 685 686 args := []string{"-input=false"} 687 if code := c.Run(args); code != 0 { 688 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 689 } 690 691 ui = new(cli.MockUi) 692 c = &InitCommand{ 693 Meta: Meta{ 694 testingOverrides: metaOverridesForProvider(testProvider()), 695 Ui: ui, 696 View: view, 697 }, 698 } 699 700 // a second init with identical config should require no changes, nor 701 // should it change the backend. 702 args = []string{"-input=false", "-backend-config", "path=foo"} 703 if code := c.Run(args); code != 0 { 704 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 705 } 706 707 // make sure the backend is configured how we expect 708 configState := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) 709 cfg := map[string]interface{}{} 710 if err := json.Unmarshal(configState.Backend.ConfigRaw, &cfg); err != nil { 711 t.Fatal(err) 712 } 713 if cfg["path"] != "foo" { 714 t.Fatalf(`expected backend path="foo", got path="%v"`, cfg["foo"]) 715 } 716} 717 718func TestInit_backendCli_no_config_block(t *testing.T) { 719 // Create a temporary working directory that is empty 720 td := tempDir(t) 721 testCopyDir(t, testFixturePath("init"), td) 722 defer os.RemoveAll(td) 723 defer testChdir(t, td)() 724 725 ui := new(cli.MockUi) 726 view, _ := testView(t) 727 c := &InitCommand{ 728 Meta: Meta{ 729 testingOverrides: metaOverridesForProvider(testProvider()), 730 Ui: ui, 731 View: view, 732 }, 733 } 734 735 args := []string{"-backend-config", "path=test"} 736 if code := c.Run(args); code != 0 { 737 t.Fatalf("got exit status %d; want 0\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String()) 738 } 739 740 errMsg := ui.ErrorWriter.String() 741 if !strings.Contains(errMsg, "Warning: Missing backend configuration") { 742 t.Fatal("expected missing backend block warning, got", errMsg) 743 } 744} 745 746func TestInit_backendReinitWithExtra(t *testing.T) { 747 td := tempDir(t) 748 testCopyDir(t, testFixturePath("init-backend-empty"), td) 749 defer os.RemoveAll(td) 750 defer testChdir(t, td)() 751 752 m := testMetaBackend(t, nil) 753 opts := &BackendOpts{ 754 ConfigOverride: configs.SynthBody("synth", map[string]cty.Value{ 755 "path": cty.StringVal("hello"), 756 }), 757 Init: true, 758 } 759 760 _, cHash, err := m.backendConfig(opts) 761 if err != nil { 762 t.Fatal(err) 763 } 764 765 ui := new(cli.MockUi) 766 view, _ := testView(t) 767 c := &InitCommand{ 768 Meta: Meta{ 769 testingOverrides: metaOverridesForProvider(testProvider()), 770 Ui: ui, 771 View: view, 772 }, 773 } 774 775 args := []string{"-backend-config", "path=hello"} 776 if code := c.Run(args); code != 0 { 777 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 778 } 779 780 // Read our saved backend config and verify we have our settings 781 state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) 782 if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want { 783 t.Errorf("wrong config\ngot: %s\nwant: %s", got, want) 784 } 785 786 if state.Backend.Hash != uint64(cHash) { 787 t.Fatal("mismatched state and config backend hashes") 788 } 789 790 // init again and make sure nothing changes 791 if code := c.Run(args); code != 0 { 792 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 793 } 794 state = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) 795 if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want { 796 t.Errorf("wrong config\ngot: %s\nwant: %s", got, want) 797 } 798 if state.Backend.Hash != uint64(cHash) { 799 t.Fatal("mismatched state and config backend hashes") 800 } 801} 802 803// move option from config to -backend-config args 804func TestInit_backendReinitConfigToExtra(t *testing.T) { 805 td := tempDir(t) 806 testCopyDir(t, testFixturePath("init-backend"), td) 807 defer os.RemoveAll(td) 808 defer testChdir(t, td)() 809 810 ui := new(cli.MockUi) 811 view, _ := testView(t) 812 c := &InitCommand{ 813 Meta: Meta{ 814 testingOverrides: metaOverridesForProvider(testProvider()), 815 Ui: ui, 816 View: view, 817 }, 818 } 819 820 if code := c.Run([]string{"-input=false"}); code != 0 { 821 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 822 } 823 824 // Read our saved backend config and verify we have our settings 825 state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) 826 if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"foo","workspace_dir":null}`; got != want { 827 t.Errorf("wrong config\ngot: %s\nwant: %s", got, want) 828 } 829 830 backendHash := state.Backend.Hash 831 832 // init again but remove the path option from the config 833 cfg := "terraform {\n backend \"local\" {}\n}\n" 834 if err := ioutil.WriteFile("main.tf", []byte(cfg), 0644); err != nil { 835 t.Fatal(err) 836 } 837 838 // We need a fresh InitCommand here because the old one now has our configuration 839 // file cached inside it, so it won't re-read the modification we just made. 840 c = &InitCommand{ 841 Meta: Meta{ 842 testingOverrides: metaOverridesForProvider(testProvider()), 843 Ui: ui, 844 View: view, 845 }, 846 } 847 848 args := []string{"-input=false", "-backend-config=path=foo"} 849 if code := c.Run(args); code != 0 { 850 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 851 } 852 state = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) 853 if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"foo","workspace_dir":null}`; got != want { 854 t.Errorf("wrong config after moving to arg\ngot: %s\nwant: %s", got, want) 855 } 856 857 if state.Backend.Hash == backendHash { 858 t.Fatal("state.Backend.Hash was not updated") 859 } 860} 861 862// make sure inputFalse stops execution on migrate 863func TestInit_inputFalse(t *testing.T) { 864 td := tempDir(t) 865 testCopyDir(t, testFixturePath("init-backend"), td) 866 defer os.RemoveAll(td) 867 defer testChdir(t, td)() 868 869 ui := new(cli.MockUi) 870 view, _ := testView(t) 871 c := &InitCommand{ 872 Meta: Meta{ 873 testingOverrides: metaOverridesForProvider(testProvider()), 874 Ui: ui, 875 View: view, 876 }, 877 } 878 879 args := []string{"-input=false", "-backend-config=path=foo"} 880 if code := c.Run(args); code != 0 { 881 t.Fatalf("bad: \n%s", ui.ErrorWriter) 882 } 883 884 // write different states for foo and bar 885 fooState := states.BuildState(func(s *states.SyncState) { 886 s.SetOutputValue( 887 addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance), 888 cty.StringVal("foo"), 889 false, // not sensitive 890 ) 891 }) 892 if err := statemgr.NewFilesystem("foo").WriteState(fooState); err != nil { 893 t.Fatal(err) 894 } 895 barState := states.BuildState(func(s *states.SyncState) { 896 s.SetOutputValue( 897 addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance), 898 cty.StringVal("bar"), 899 false, // not sensitive 900 ) 901 }) 902 if err := statemgr.NewFilesystem("bar").WriteState(barState); err != nil { 903 t.Fatal(err) 904 } 905 906 ui = new(cli.MockUi) 907 c = &InitCommand{ 908 Meta: Meta{ 909 testingOverrides: metaOverridesForProvider(testProvider()), 910 Ui: ui, 911 View: view, 912 }, 913 } 914 915 args = []string{"-input=false", "-backend-config=path=bar", "-migrate-state"} 916 if code := c.Run(args); code == 0 { 917 t.Fatal("init should have failed", ui.OutputWriter) 918 } 919 920 errMsg := ui.ErrorWriter.String() 921 if !strings.Contains(errMsg, "input disabled") { 922 t.Fatal("expected input disabled error, got", errMsg) 923 } 924 925 ui = new(cli.MockUi) 926 c = &InitCommand{ 927 Meta: Meta{ 928 testingOverrides: metaOverridesForProvider(testProvider()), 929 Ui: ui, 930 View: view, 931 }, 932 } 933 934 // A missing input=false should abort rather than loop infinitely 935 args = []string{"-backend-config=path=baz"} 936 if code := c.Run(args); code == 0 { 937 t.Fatal("init should have failed", ui.OutputWriter) 938 } 939} 940 941func TestInit_getProvider(t *testing.T) { 942 // Create a temporary working directory that is empty 943 td := tempDir(t) 944 testCopyDir(t, testFixturePath("init-get-providers"), td) 945 defer os.RemoveAll(td) 946 defer testChdir(t, td)() 947 948 overrides := metaOverridesForProvider(testProvider()) 949 ui := new(cli.MockUi) 950 view, _ := testView(t) 951 providerSource, close := newMockProviderSource(t, map[string][]string{ 952 // looking for an exact version 953 "exact": {"1.2.3"}, 954 // config requires >= 2.3.3 955 "greater-than": {"2.3.4", "2.3.3", "2.3.0"}, 956 // config specifies 957 "between": {"3.4.5", "2.3.4", "1.2.3"}, 958 }) 959 defer close() 960 m := Meta{ 961 testingOverrides: overrides, 962 Ui: ui, 963 View: view, 964 ProviderSource: providerSource, 965 } 966 967 c := &InitCommand{ 968 Meta: m, 969 } 970 971 args := []string{ 972 "-backend=false", // should be possible to install plugins without backend init 973 } 974 if code := c.Run(args); code != 0 { 975 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 976 } 977 978 // check that we got the providers for our config 979 exactPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/exact/1.2.3/%s", getproviders.CurrentPlatform) 980 if _, err := os.Stat(exactPath); os.IsNotExist(err) { 981 t.Fatal("provider 'exact' not downloaded") 982 } 983 greaterThanPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/greater-than/2.3.4/%s", getproviders.CurrentPlatform) 984 if _, err := os.Stat(greaterThanPath); os.IsNotExist(err) { 985 t.Fatal("provider 'greater-than' not downloaded") 986 } 987 betweenPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/between/2.3.4/%s", getproviders.CurrentPlatform) 988 if _, err := os.Stat(betweenPath); os.IsNotExist(err) { 989 t.Fatal("provider 'between' not downloaded") 990 } 991 992 t.Run("future-state", func(t *testing.T) { 993 // getting providers should fail if a state from a newer version of 994 // terraform exists, since InitCommand.getProviders needs to inspect that 995 // state. 996 997 f, err := os.Create(DefaultStateFilename) 998 if err != nil { 999 t.Fatalf("err: %s", err) 1000 } 1001 defer f.Close() 1002 1003 // Construct a mock state file from the far future 1004 type FutureState struct { 1005 Version uint `json:"version"` 1006 Lineage string `json:"lineage"` 1007 TerraformVersion string `json:"terraform_version"` 1008 Outputs map[string]interface{} `json:"outputs"` 1009 Resources []map[string]interface{} `json:"resources"` 1010 } 1011 fs := &FutureState{ 1012 Version: 999, 1013 Lineage: "123-456-789", 1014 TerraformVersion: "999.0.0", 1015 Outputs: make(map[string]interface{}), 1016 Resources: make([]map[string]interface{}, 0), 1017 } 1018 src, err := json.MarshalIndent(fs, "", " ") 1019 if err != nil { 1020 t.Fatalf("failed to marshal future state: %s", err) 1021 } 1022 src = append(src, '\n') 1023 _, err = f.Write(src) 1024 if err != nil { 1025 t.Fatal(err) 1026 } 1027 1028 ui := new(cli.MockUi) 1029 view, _ := testView(t) 1030 m.Ui = ui 1031 m.View = view 1032 c := &InitCommand{ 1033 Meta: m, 1034 } 1035 1036 if code := c.Run(nil); code == 0 { 1037 t.Fatal("expected error, got:", ui.OutputWriter) 1038 } 1039 1040 errMsg := ui.ErrorWriter.String() 1041 if !strings.Contains(errMsg, "Unsupported state file format") { 1042 t.Fatal("unexpected error:", errMsg) 1043 } 1044 }) 1045} 1046 1047func TestInit_getProviderSource(t *testing.T) { 1048 // Create a temporary working directory that is empty 1049 td := tempDir(t) 1050 testCopyDir(t, testFixturePath("init-get-provider-source"), td) 1051 defer os.RemoveAll(td) 1052 defer testChdir(t, td)() 1053 1054 overrides := metaOverridesForProvider(testProvider()) 1055 ui := new(cli.MockUi) 1056 view, _ := testView(t) 1057 providerSource, close := newMockProviderSource(t, map[string][]string{ 1058 // looking for an exact version 1059 "acme/alpha": {"1.2.3"}, 1060 // config doesn't specify versions for other providers 1061 "registry.example.com/acme/beta": {"1.0.0"}, 1062 "gamma": {"2.0.0"}, 1063 }) 1064 defer close() 1065 m := Meta{ 1066 testingOverrides: overrides, 1067 Ui: ui, 1068 View: view, 1069 ProviderSource: providerSource, 1070 } 1071 1072 c := &InitCommand{ 1073 Meta: m, 1074 } 1075 1076 args := []string{ 1077 "-backend=false", // should be possible to install plugins without backend init 1078 } 1079 if code := c.Run(args); code != 0 { 1080 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 1081 } 1082 1083 // check that we got the providers for our config 1084 exactPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/acme/alpha/1.2.3/%s", getproviders.CurrentPlatform) 1085 if _, err := os.Stat(exactPath); os.IsNotExist(err) { 1086 t.Error("provider 'alpha' not downloaded") 1087 } 1088 greaterThanPath := fmt.Sprintf(".terraform/providers/registry.example.com/acme/beta/1.0.0/%s", getproviders.CurrentPlatform) 1089 if _, err := os.Stat(greaterThanPath); os.IsNotExist(err) { 1090 t.Error("provider 'beta' not downloaded") 1091 } 1092 betweenPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/gamma/2.0.0/%s", getproviders.CurrentPlatform) 1093 if _, err := os.Stat(betweenPath); os.IsNotExist(err) { 1094 t.Error("provider 'gamma' not downloaded") 1095 } 1096} 1097 1098func TestInit_getProviderLegacyFromState(t *testing.T) { 1099 // Create a temporary working directory that is empty 1100 td := tempDir(t) 1101 testCopyDir(t, testFixturePath("init-get-provider-legacy-from-state"), td) 1102 defer os.RemoveAll(td) 1103 defer testChdir(t, td)() 1104 1105 overrides := metaOverridesForProvider(testProvider()) 1106 ui := new(cli.MockUi) 1107 view, _ := testView(t) 1108 providerSource, close := newMockProviderSource(t, map[string][]string{ 1109 "acme/alpha": {"1.2.3"}, 1110 }) 1111 defer close() 1112 m := Meta{ 1113 testingOverrides: overrides, 1114 Ui: ui, 1115 View: view, 1116 ProviderSource: providerSource, 1117 } 1118 1119 c := &InitCommand{ 1120 Meta: m, 1121 } 1122 1123 if code := c.Run(nil); code != 1 { 1124 t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String()) 1125 } 1126 1127 // Expect this diagnostic output 1128 wants := []string{ 1129 "Invalid legacy provider address", 1130 "You must complete the Terraform 0.13 upgrade process", 1131 } 1132 got := ui.ErrorWriter.String() 1133 for _, want := range wants { 1134 if !strings.Contains(got, want) { 1135 t.Fatalf("expected output to contain %q, got:\n\n%s", want, got) 1136 } 1137 } 1138} 1139 1140func TestInit_getProviderInvalidPackage(t *testing.T) { 1141 // Create a temporary working directory that is empty 1142 td := tempDir(t) 1143 testCopyDir(t, testFixturePath("init-get-provider-invalid-package"), td) 1144 defer os.RemoveAll(td) 1145 defer testChdir(t, td)() 1146 1147 overrides := metaOverridesForProvider(testProvider()) 1148 ui := new(cli.MockUi) 1149 view, _ := testView(t) 1150 1151 // create a provider source which allows installing an invalid package 1152 addr := addrs.MustParseProviderSourceString("invalid/package") 1153 version := getproviders.MustParseVersion("1.0.0") 1154 meta, close, err := getproviders.FakeInstallablePackageMeta( 1155 addr, 1156 version, 1157 getproviders.VersionList{getproviders.MustParseVersion("5.0")}, 1158 getproviders.CurrentPlatform, 1159 "terraform-package", // should be "terraform-provider-package" 1160 ) 1161 defer close() 1162 if err != nil { 1163 t.Fatalf("failed to prepare fake package for %s %s: %s", addr.ForDisplay(), version, err) 1164 } 1165 providerSource := getproviders.NewMockSource([]getproviders.PackageMeta{meta}, nil) 1166 1167 m := Meta{ 1168 testingOverrides: overrides, 1169 Ui: ui, 1170 View: view, 1171 ProviderSource: providerSource, 1172 } 1173 1174 c := &InitCommand{ 1175 Meta: m, 1176 } 1177 1178 args := []string{ 1179 "-backend=false", // should be possible to install plugins without backend init 1180 } 1181 if code := c.Run(args); code != 1 { 1182 t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String()) 1183 } 1184 1185 // invalid provider should be installed 1186 packagePath := fmt.Sprintf(".terraform/providers/registry.terraform.io/invalid/package/1.0.0/%s/terraform-package", getproviders.CurrentPlatform) 1187 if _, err := os.Stat(packagePath); os.IsNotExist(err) { 1188 t.Fatal("provider 'invalid/package' not downloaded") 1189 } 1190 1191 wantErrors := []string{ 1192 "Failed to install provider", 1193 "could not find executable file starting with terraform-provider-package", 1194 } 1195 got := ui.ErrorWriter.String() 1196 for _, wantError := range wantErrors { 1197 if !strings.Contains(got, wantError) { 1198 t.Fatalf("missing error:\nwant: %q\ngot:\n%s", wantError, got) 1199 } 1200 } 1201} 1202 1203func TestInit_getProviderDetectedLegacy(t *testing.T) { 1204 // Create a temporary working directory that is empty 1205 td := tempDir(t) 1206 testCopyDir(t, testFixturePath("init-get-provider-detected-legacy"), td) 1207 defer os.RemoveAll(td) 1208 defer testChdir(t, td)() 1209 1210 // We need to construct a multisource with a mock source and a registry 1211 // source: the mock source will return ErrRegistryProviderNotKnown for an 1212 // unknown provider, and the registry source will allow us to look up the 1213 // appropriate namespace if possible. 1214 providerSource, psClose := newMockProviderSource(t, map[string][]string{ 1215 "hashicorp/foo": {"1.2.3"}, 1216 "terraform-providers/baz": {"2.3.4"}, // this will not be installed 1217 }) 1218 defer psClose() 1219 registrySource, rsClose := testRegistrySource(t) 1220 defer rsClose() 1221 multiSource := getproviders.MultiSource{ 1222 {Source: providerSource}, 1223 {Source: registrySource}, 1224 } 1225 1226 ui := new(cli.MockUi) 1227 view, _ := testView(t) 1228 m := Meta{ 1229 Ui: ui, 1230 View: view, 1231 ProviderSource: multiSource, 1232 } 1233 1234 c := &InitCommand{ 1235 Meta: m, 1236 } 1237 1238 args := []string{ 1239 "-backend=false", // should be possible to install plugins without backend init 1240 } 1241 if code := c.Run(args); code == 0 { 1242 t.Fatalf("expected error, got output: \n%s", ui.OutputWriter.String()) 1243 } 1244 1245 // foo should be installed 1246 fooPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/foo/1.2.3/%s", getproviders.CurrentPlatform) 1247 if _, err := os.Stat(fooPath); os.IsNotExist(err) { 1248 t.Error("provider 'foo' not installed") 1249 } 1250 // baz should not be installed 1251 bazPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/terraform-providers/baz/2.3.4/%s", getproviders.CurrentPlatform) 1252 if _, err := os.Stat(bazPath); !os.IsNotExist(err) { 1253 t.Error("provider 'baz' installed, but should not be") 1254 } 1255 1256 // error output is the main focus of this test 1257 errOutput := ui.ErrorWriter.String() 1258 errors := []string{ 1259 "Failed to query available provider packages", 1260 "Could not retrieve the list of available versions", 1261 "registry.terraform.io/hashicorp/baz", 1262 "registry.terraform.io/hashicorp/frob", 1263 } 1264 for _, want := range errors { 1265 if !strings.Contains(errOutput, want) { 1266 t.Fatalf("expected error %q: %s", want, errOutput) 1267 } 1268 } 1269} 1270 1271func TestInit_providerSource(t *testing.T) { 1272 // Create a temporary working directory that is empty 1273 td := tempDir(t) 1274 testCopyDir(t, testFixturePath("init-required-providers"), td) 1275 defer os.RemoveAll(td) 1276 defer testChdir(t, td)() 1277 1278 providerSource, close := newMockProviderSource(t, map[string][]string{ 1279 "test": {"1.2.3", "1.2.4"}, 1280 "test-beta": {"1.2.4"}, 1281 "source": {"1.2.2", "1.2.3", "1.2.1"}, 1282 }) 1283 defer close() 1284 1285 ui := new(cli.MockUi) 1286 view, _ := testView(t) 1287 m := Meta{ 1288 testingOverrides: metaOverridesForProvider(testProvider()), 1289 Ui: ui, 1290 View: view, 1291 ProviderSource: providerSource, 1292 } 1293 1294 c := &InitCommand{ 1295 Meta: m, 1296 } 1297 1298 args := []string{} 1299 1300 if code := c.Run(args); code != 0 { 1301 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 1302 } 1303 if strings.Contains(ui.OutputWriter.String(), "Terraform has initialized, but configuration upgrades may be needed") { 1304 t.Fatalf("unexpected \"configuration upgrade\" warning in output") 1305 } 1306 1307 cacheDir := m.providerLocalCacheDir() 1308 gotPackages := cacheDir.AllAvailablePackages() 1309 wantPackages := map[addrs.Provider][]providercache.CachedProvider{ 1310 addrs.NewDefaultProvider("test"): { 1311 { 1312 Provider: addrs.NewDefaultProvider("test"), 1313 Version: getproviders.MustParseVersion("1.2.3"), 1314 PackageDir: expectedPackageInstallPath("test", "1.2.3", false), 1315 }, 1316 }, 1317 addrs.NewDefaultProvider("test-beta"): { 1318 { 1319 Provider: addrs.NewDefaultProvider("test-beta"), 1320 Version: getproviders.MustParseVersion("1.2.4"), 1321 PackageDir: expectedPackageInstallPath("test-beta", "1.2.4", false), 1322 }, 1323 }, 1324 addrs.NewDefaultProvider("source"): { 1325 { 1326 Provider: addrs.NewDefaultProvider("source"), 1327 Version: getproviders.MustParseVersion("1.2.3"), 1328 PackageDir: expectedPackageInstallPath("source", "1.2.3", false), 1329 }, 1330 }, 1331 } 1332 if diff := cmp.Diff(wantPackages, gotPackages); diff != "" { 1333 t.Errorf("wrong cache directory contents after upgrade\n%s", diff) 1334 } 1335 1336 locks, err := m.lockedDependencies() 1337 if err != nil { 1338 t.Fatalf("failed to get locked dependencies: %s", err) 1339 } 1340 gotProviderLocks := locks.AllProviders() 1341 wantProviderLocks := map[addrs.Provider]*depsfile.ProviderLock{ 1342 addrs.NewDefaultProvider("test-beta"): depsfile.NewProviderLock( 1343 addrs.NewDefaultProvider("test-beta"), 1344 getproviders.MustParseVersion("1.2.4"), 1345 getproviders.MustParseVersionConstraints("= 1.2.4"), 1346 []getproviders.Hash{ 1347 getproviders.HashScheme1.New("see6W06w09Ea+AobFJ+mbvPTie6ASqZAAdlFZbs8BSM="), 1348 }, 1349 ), 1350 addrs.NewDefaultProvider("test"): depsfile.NewProviderLock( 1351 addrs.NewDefaultProvider("test"), 1352 getproviders.MustParseVersion("1.2.3"), 1353 getproviders.MustParseVersionConstraints("= 1.2.3"), 1354 []getproviders.Hash{ 1355 getproviders.HashScheme1.New("wlbEC2mChQZ2hhgUhl6SeVLPP7fMqOFUZAQhQ9GIIno="), 1356 }, 1357 ), 1358 addrs.NewDefaultProvider("source"): depsfile.NewProviderLock( 1359 addrs.NewDefaultProvider("source"), 1360 getproviders.MustParseVersion("1.2.3"), 1361 getproviders.MustParseVersionConstraints("= 1.2.3"), 1362 []getproviders.Hash{ 1363 getproviders.HashScheme1.New("myS3qb3px3tRBq1ZWRYJeUH+kySWpBc0Yy8rw6W7/p4="), 1364 }, 1365 ), 1366 } 1367 if diff := cmp.Diff(gotProviderLocks, wantProviderLocks, depsfile.ProviderLockComparer); diff != "" { 1368 t.Errorf("wrong version selections after upgrade\n%s", diff) 1369 } 1370 1371 outputStr := ui.OutputWriter.String() 1372 if want := "Installed hashicorp/test v1.2.3 (verified checksum)"; !strings.Contains(outputStr, want) { 1373 t.Fatalf("unexpected output: %s\nexpected to include %q", outputStr, want) 1374 } 1375} 1376 1377func TestInit_cancel(t *testing.T) { 1378 // This test runs `terraform init` as if SIGINT (or similar on other 1379 // platforms) were sent to it, testing that it is interruptible. 1380 1381 td := tempDir(t) 1382 testCopyDir(t, testFixturePath("init-required-providers"), td) 1383 defer os.RemoveAll(td) 1384 defer testChdir(t, td)() 1385 1386 providerSource, closeSrc := newMockProviderSource(t, map[string][]string{ 1387 "test": {"1.2.3", "1.2.4"}, 1388 "test-beta": {"1.2.4"}, 1389 "source": {"1.2.2", "1.2.3", "1.2.1"}, 1390 }) 1391 defer closeSrc() 1392 1393 // our shutdown channel is pre-closed so init will exit as soon as it 1394 // starts a cancelable portion of the process. 1395 shutdownCh := make(chan struct{}) 1396 close(shutdownCh) 1397 1398 ui := cli.NewMockUi() 1399 view, _ := testView(t) 1400 m := Meta{ 1401 testingOverrides: metaOverridesForProvider(testProvider()), 1402 Ui: ui, 1403 View: view, 1404 ProviderSource: providerSource, 1405 ShutdownCh: shutdownCh, 1406 } 1407 1408 c := &InitCommand{ 1409 Meta: m, 1410 } 1411 1412 args := []string{} 1413 1414 if code := c.Run(args); code == 0 { 1415 t.Fatalf("succeeded; wanted error") 1416 } 1417 // Currently the first operation that is cancelable is provider 1418 // installation, so our error message comes from there. If we 1419 // make the earlier steps cancelable in future then it'd be 1420 // expected for this particular message to change. 1421 if got, want := ui.ErrorWriter.String(), `Provider installation was canceled by an interrupt signal`; !strings.Contains(got, want) { 1422 t.Fatalf("wrong error message\nshould contain: %s\ngot:\n%s", want, got) 1423 } 1424} 1425 1426func TestInit_getUpgradePlugins(t *testing.T) { 1427 // Create a temporary working directory that is empty 1428 td := tempDir(t) 1429 testCopyDir(t, testFixturePath("init-get-providers"), td) 1430 defer os.RemoveAll(td) 1431 defer testChdir(t, td)() 1432 1433 providerSource, close := newMockProviderSource(t, map[string][]string{ 1434 // looking for an exact version 1435 "exact": {"1.2.3"}, 1436 // config requires >= 2.3.3 1437 "greater-than": {"2.3.4", "2.3.3", "2.3.0"}, 1438 // config specifies > 1.0.0 , < 3.0.0 1439 "between": {"3.4.5", "2.3.4", "1.2.3"}, 1440 }) 1441 defer close() 1442 1443 ui := new(cli.MockUi) 1444 view, _ := testView(t) 1445 m := Meta{ 1446 testingOverrides: metaOverridesForProvider(testProvider()), 1447 Ui: ui, 1448 View: view, 1449 ProviderSource: providerSource, 1450 } 1451 1452 installFakeProviderPackages(t, &m, map[string][]string{ 1453 "exact": {"0.0.1"}, 1454 "greater-than": {"2.3.3"}, 1455 }) 1456 1457 c := &InitCommand{ 1458 Meta: m, 1459 } 1460 1461 args := []string{ 1462 "-upgrade=true", 1463 } 1464 if code := c.Run(args); code != 0 { 1465 t.Fatalf("command did not complete successfully:\n%s", ui.ErrorWriter.String()) 1466 } 1467 1468 cacheDir := m.providerLocalCacheDir() 1469 gotPackages := cacheDir.AllAvailablePackages() 1470 wantPackages := map[addrs.Provider][]providercache.CachedProvider{ 1471 // "between" wasn't previously installed at all, so we installed 1472 // the newest available version that matched the version constraints. 1473 addrs.NewDefaultProvider("between"): { 1474 { 1475 Provider: addrs.NewDefaultProvider("between"), 1476 Version: getproviders.MustParseVersion("2.3.4"), 1477 PackageDir: expectedPackageInstallPath("between", "2.3.4", false), 1478 }, 1479 }, 1480 // The existing version of "exact" did not match the version constraints, 1481 // so we installed what the configuration selected as well. 1482 addrs.NewDefaultProvider("exact"): { 1483 { 1484 Provider: addrs.NewDefaultProvider("exact"), 1485 Version: getproviders.MustParseVersion("1.2.3"), 1486 PackageDir: expectedPackageInstallPath("exact", "1.2.3", false), 1487 }, 1488 // Previous version is still there, but not selected 1489 { 1490 Provider: addrs.NewDefaultProvider("exact"), 1491 Version: getproviders.MustParseVersion("0.0.1"), 1492 PackageDir: expectedPackageInstallPath("exact", "0.0.1", false), 1493 }, 1494 }, 1495 // The existing version of "greater-than" _did_ match the constraints, 1496 // but a newer version was available and the user specified 1497 // -upgrade and so we upgraded it anyway. 1498 addrs.NewDefaultProvider("greater-than"): { 1499 { 1500 Provider: addrs.NewDefaultProvider("greater-than"), 1501 Version: getproviders.MustParseVersion("2.3.4"), 1502 PackageDir: expectedPackageInstallPath("greater-than", "2.3.4", false), 1503 }, 1504 // Previous version is still there, but not selected 1505 { 1506 Provider: addrs.NewDefaultProvider("greater-than"), 1507 Version: getproviders.MustParseVersion("2.3.3"), 1508 PackageDir: expectedPackageInstallPath("greater-than", "2.3.3", false), 1509 }, 1510 }, 1511 } 1512 if diff := cmp.Diff(wantPackages, gotPackages); diff != "" { 1513 t.Errorf("wrong cache directory contents after upgrade\n%s", diff) 1514 } 1515 1516 locks, err := m.lockedDependencies() 1517 if err != nil { 1518 t.Fatalf("failed to get locked dependencies: %s", err) 1519 } 1520 gotProviderLocks := locks.AllProviders() 1521 wantProviderLocks := map[addrs.Provider]*depsfile.ProviderLock{ 1522 addrs.NewDefaultProvider("between"): depsfile.NewProviderLock( 1523 addrs.NewDefaultProvider("between"), 1524 getproviders.MustParseVersion("2.3.4"), 1525 getproviders.MustParseVersionConstraints("> 1.0.0, < 3.0.0"), 1526 []getproviders.Hash{ 1527 getproviders.HashScheme1.New("JVqAvZz88A+hS2wHVtTWQkHaxoA/LrUAz0H3jPBWPIA="), 1528 }, 1529 ), 1530 addrs.NewDefaultProvider("exact"): depsfile.NewProviderLock( 1531 addrs.NewDefaultProvider("exact"), 1532 getproviders.MustParseVersion("1.2.3"), 1533 getproviders.MustParseVersionConstraints("= 1.2.3"), 1534 []getproviders.Hash{ 1535 getproviders.HashScheme1.New("H1TxWF8LyhBb6B4iUdKhLc/S9sC/jdcrCykpkbGcfbg="), 1536 }, 1537 ), 1538 addrs.NewDefaultProvider("greater-than"): depsfile.NewProviderLock( 1539 addrs.NewDefaultProvider("greater-than"), 1540 getproviders.MustParseVersion("2.3.4"), 1541 getproviders.MustParseVersionConstraints(">= 2.3.3"), 1542 []getproviders.Hash{ 1543 getproviders.HashScheme1.New("SJPpXx/yoFE/W+7eCipjJ+G21xbdnTBD7lWodZ8hWkU="), 1544 }, 1545 ), 1546 } 1547 if diff := cmp.Diff(gotProviderLocks, wantProviderLocks, depsfile.ProviderLockComparer); diff != "" { 1548 t.Errorf("wrong version selections after upgrade\n%s", diff) 1549 } 1550} 1551 1552func TestInit_getProviderMissing(t *testing.T) { 1553 // Create a temporary working directory that is empty 1554 td := tempDir(t) 1555 testCopyDir(t, testFixturePath("init-get-providers"), td) 1556 defer os.RemoveAll(td) 1557 defer testChdir(t, td)() 1558 1559 providerSource, close := newMockProviderSource(t, map[string][]string{ 1560 // looking for exact version 1.2.3 1561 "exact": {"1.2.4"}, 1562 // config requires >= 2.3.3 1563 "greater-than": {"2.3.4", "2.3.3", "2.3.0"}, 1564 // config specifies 1565 "between": {"3.4.5", "2.3.4", "1.2.3"}, 1566 }) 1567 defer close() 1568 1569 ui := new(cli.MockUi) 1570 view, _ := testView(t) 1571 m := Meta{ 1572 testingOverrides: metaOverridesForProvider(testProvider()), 1573 Ui: ui, 1574 View: view, 1575 ProviderSource: providerSource, 1576 } 1577 1578 c := &InitCommand{ 1579 Meta: m, 1580 } 1581 1582 args := []string{} 1583 if code := c.Run(args); code == 0 { 1584 t.Fatalf("expected error, got output: \n%s", ui.OutputWriter.String()) 1585 } 1586 1587 if !strings.Contains(ui.ErrorWriter.String(), "no available releases match") { 1588 t.Fatalf("unexpected error output: %s", ui.ErrorWriter) 1589 } 1590} 1591 1592func TestInit_checkRequiredVersion(t *testing.T) { 1593 // Create a temporary working directory that is empty 1594 td := tempDir(t) 1595 testCopyDir(t, testFixturePath("init-check-required-version"), td) 1596 defer os.RemoveAll(td) 1597 defer testChdir(t, td)() 1598 1599 ui := cli.NewMockUi() 1600 view, _ := testView(t) 1601 c := &InitCommand{ 1602 Meta: Meta{ 1603 testingOverrides: metaOverridesForProvider(testProvider()), 1604 Ui: ui, 1605 View: view, 1606 }, 1607 } 1608 1609 args := []string{} 1610 if code := c.Run(args); code != 1 { 1611 t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String()) 1612 } 1613 errStr := ui.ErrorWriter.String() 1614 if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) { 1615 t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr) 1616 } 1617 if strings.Contains(errStr, `required_version = ">= 0.13.0"`) { 1618 t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr) 1619 } 1620} 1621 1622// Verify that init will error out with an invalid version constraint, even if 1623// there are other invalid configuration constructs. 1624func TestInit_checkRequiredVersionFirst(t *testing.T) { 1625 t.Run("root_module", func(t *testing.T) { 1626 td := tempDir(t) 1627 testCopyDir(t, testFixturePath("init-check-required-version-first"), td) 1628 defer os.RemoveAll(td) 1629 defer testChdir(t, td)() 1630 1631 ui := cli.NewMockUi() 1632 view, _ := testView(t) 1633 c := &InitCommand{ 1634 Meta: Meta{ 1635 testingOverrides: metaOverridesForProvider(testProvider()), 1636 Ui: ui, 1637 View: view, 1638 }, 1639 } 1640 1641 args := []string{} 1642 if code := c.Run(args); code != 1 { 1643 t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String()) 1644 } 1645 errStr := ui.ErrorWriter.String() 1646 if !strings.Contains(errStr, `Unsupported Terraform Core version`) { 1647 t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr) 1648 } 1649 }) 1650 t.Run("sub_module", func(t *testing.T) { 1651 td := tempDir(t) 1652 testCopyDir(t, testFixturePath("init-check-required-version-first-module"), td) 1653 defer os.RemoveAll(td) 1654 defer testChdir(t, td)() 1655 1656 ui := cli.NewMockUi() 1657 view, _ := testView(t) 1658 c := &InitCommand{ 1659 Meta: Meta{ 1660 testingOverrides: metaOverridesForProvider(testProvider()), 1661 Ui: ui, 1662 View: view, 1663 }, 1664 } 1665 1666 args := []string{} 1667 if code := c.Run(args); code != 1 { 1668 t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String()) 1669 } 1670 errStr := ui.ErrorWriter.String() 1671 if !strings.Contains(errStr, `Unsupported Terraform Core version`) { 1672 t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr) 1673 } 1674 }) 1675} 1676 1677func TestInit_providerLockFile(t *testing.T) { 1678 // Create a temporary working directory that is empty 1679 td := tempDir(t) 1680 testCopyDir(t, testFixturePath("init-provider-lock-file"), td) 1681 defer os.RemoveAll(td) 1682 defer testChdir(t, td)() 1683 1684 providerSource, close := newMockProviderSource(t, map[string][]string{ 1685 "test": {"1.2.3"}, 1686 }) 1687 defer close() 1688 1689 ui := new(cli.MockUi) 1690 view, _ := testView(t) 1691 m := Meta{ 1692 testingOverrides: metaOverridesForProvider(testProvider()), 1693 Ui: ui, 1694 View: view, 1695 ProviderSource: providerSource, 1696 } 1697 1698 c := &InitCommand{ 1699 Meta: m, 1700 } 1701 1702 args := []string{} 1703 if code := c.Run(args); code != 0 { 1704 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 1705 } 1706 1707 lockFile := ".terraform.lock.hcl" 1708 buf, err := ioutil.ReadFile(lockFile) 1709 if err != nil { 1710 t.Fatalf("failed to read dependency lock file %s: %s", lockFile, err) 1711 } 1712 buf = bytes.TrimSpace(buf) 1713 // The hash in here is for the fake package that newMockProviderSource produces 1714 // (so it'll change if newMockProviderSource starts producing different contents) 1715 wantLockFile := strings.TrimSpace(` 1716# This file is maintained automatically by "terraform init". 1717# Manual edits may be lost in future updates. 1718 1719provider "registry.terraform.io/hashicorp/test" { 1720 version = "1.2.3" 1721 constraints = "1.2.3" 1722 hashes = [ 1723 "h1:wlbEC2mChQZ2hhgUhl6SeVLPP7fMqOFUZAQhQ9GIIno=", 1724 ] 1725} 1726`) 1727 if diff := cmp.Diff(wantLockFile, string(buf)); diff != "" { 1728 t.Errorf("wrong dependency lock file contents\n%s", diff) 1729 } 1730 1731 // Make the local directory read-only, and verify that rerunning init 1732 // succeeds, to ensure that we don't try to rewrite an unchanged lock file 1733 os.Chmod(".", 0555) 1734 if code := c.Run(args); code != 0 { 1735 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 1736 } 1737} 1738 1739func TestInit_providerLockFileReadonly(t *testing.T) { 1740 // The hash in here is for the fake package that newMockProviderSource produces 1741 // (so it'll change if newMockProviderSource starts producing different contents) 1742 inputLockFile := strings.TrimSpace(` 1743# This file is maintained automatically by "terraform init". 1744# Manual edits may be lost in future updates. 1745 1746provider "registry.terraform.io/hashicorp/test" { 1747 version = "1.2.3" 1748 constraints = "1.2.3" 1749 hashes = [ 1750 "zh:e919b507a91e23a00da5c2c4d0b64bcc7900b68d43b3951ac0f6e5d80387fbdc", 1751 ] 1752} 1753`) 1754 1755 badLockFile := strings.TrimSpace(` 1756# This file is maintained automatically by "terraform init". 1757# Manual edits may be lost in future updates. 1758 1759provider "registry.terraform.io/hashicorp/test" { 1760 version = "1.2.3" 1761 constraints = "1.2.3" 1762 hashes = [ 1763 "zh:0000000000000000000000000000000000000000000000000000000000000000", 1764 ] 1765} 1766`) 1767 1768 updatedLockFile := strings.TrimSpace(` 1769# This file is maintained automatically by "terraform init". 1770# Manual edits may be lost in future updates. 1771 1772provider "registry.terraform.io/hashicorp/test" { 1773 version = "1.2.3" 1774 constraints = "1.2.3" 1775 hashes = [ 1776 "h1:wlbEC2mChQZ2hhgUhl6SeVLPP7fMqOFUZAQhQ9GIIno=", 1777 "zh:e919b507a91e23a00da5c2c4d0b64bcc7900b68d43b3951ac0f6e5d80387fbdc", 1778 ] 1779} 1780`) 1781 1782 cases := []struct { 1783 desc string 1784 fixture string 1785 providers map[string][]string 1786 input string 1787 args []string 1788 ok bool 1789 want string 1790 }{ 1791 { 1792 desc: "default", 1793 fixture: "init-provider-lock-file", 1794 providers: map[string][]string{"test": {"1.2.3"}}, 1795 input: inputLockFile, 1796 args: []string{}, 1797 ok: true, 1798 want: updatedLockFile, 1799 }, 1800 { 1801 desc: "readonly", 1802 fixture: "init-provider-lock-file", 1803 providers: map[string][]string{"test": {"1.2.3"}}, 1804 input: inputLockFile, 1805 args: []string{"-lockfile=readonly"}, 1806 ok: true, 1807 want: inputLockFile, 1808 }, 1809 { 1810 desc: "conflict", 1811 fixture: "init-provider-lock-file", 1812 providers: map[string][]string{"test": {"1.2.3"}}, 1813 input: inputLockFile, 1814 args: []string{"-lockfile=readonly", "-upgrade"}, 1815 ok: false, 1816 want: inputLockFile, 1817 }, 1818 { 1819 desc: "checksum mismatch", 1820 fixture: "init-provider-lock-file", 1821 providers: map[string][]string{"test": {"1.2.3"}}, 1822 input: badLockFile, 1823 args: []string{"-lockfile=readonly"}, 1824 ok: false, 1825 want: badLockFile, 1826 }, 1827 { 1828 desc: "reject to change required provider dependences", 1829 fixture: "init-provider-lock-file-readonly-add", 1830 providers: map[string][]string{ 1831 "test": {"1.2.3"}, 1832 "foo": {"1.0.0"}, 1833 }, 1834 input: inputLockFile, 1835 args: []string{"-lockfile=readonly"}, 1836 ok: false, 1837 want: inputLockFile, 1838 }, 1839 } 1840 1841 for _, tc := range cases { 1842 t.Run(tc.desc, func(t *testing.T) { 1843 // Create a temporary working directory that is empty 1844 td := tempDir(t) 1845 testCopyDir(t, testFixturePath(tc.fixture), td) 1846 defer os.RemoveAll(td) 1847 defer testChdir(t, td)() 1848 1849 providerSource, close := newMockProviderSource(t, tc.providers) 1850 defer close() 1851 1852 ui := new(cli.MockUi) 1853 m := Meta{ 1854 testingOverrides: metaOverridesForProvider(testProvider()), 1855 Ui: ui, 1856 ProviderSource: providerSource, 1857 } 1858 1859 c := &InitCommand{ 1860 Meta: m, 1861 } 1862 1863 // write input lockfile 1864 lockFile := ".terraform.lock.hcl" 1865 if err := ioutil.WriteFile(lockFile, []byte(tc.input), 0644); err != nil { 1866 t.Fatalf("failed to write input lockfile: %s", err) 1867 } 1868 1869 code := c.Run(tc.args) 1870 if tc.ok && code != 0 { 1871 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 1872 } 1873 if !tc.ok && code == 0 { 1874 t.Fatalf("expected error, got output: \n%s", ui.OutputWriter.String()) 1875 } 1876 1877 buf, err := ioutil.ReadFile(lockFile) 1878 if err != nil { 1879 t.Fatalf("failed to read dependency lock file %s: %s", lockFile, err) 1880 } 1881 buf = bytes.TrimSpace(buf) 1882 if diff := cmp.Diff(tc.want, string(buf)); diff != "" { 1883 t.Errorf("wrong dependency lock file contents\n%s", diff) 1884 } 1885 }) 1886 } 1887} 1888 1889func TestInit_pluginDirReset(t *testing.T) { 1890 td := testTempDir(t) 1891 defer os.RemoveAll(td) 1892 defer testChdir(t, td)() 1893 1894 // An empty provider source 1895 providerSource, close := newMockProviderSource(t, nil) 1896 defer close() 1897 1898 ui := new(cli.MockUi) 1899 view, _ := testView(t) 1900 c := &InitCommand{ 1901 Meta: Meta{ 1902 testingOverrides: metaOverridesForProvider(testProvider()), 1903 Ui: ui, 1904 View: view, 1905 ProviderSource: providerSource, 1906 }, 1907 } 1908 1909 // make our vendor paths 1910 pluginPath := []string{"a", "b", "c"} 1911 for _, p := range pluginPath { 1912 if err := os.MkdirAll(p, 0755); err != nil { 1913 t.Fatal(err) 1914 } 1915 } 1916 1917 // run once and save the -plugin-dir 1918 args := []string{"-plugin-dir", "a"} 1919 if code := c.Run(args); code != 0 { 1920 t.Fatalf("bad: \n%s", ui.ErrorWriter) 1921 } 1922 1923 pluginDirs, err := c.loadPluginPath() 1924 if err != nil { 1925 t.Fatal(err) 1926 } 1927 1928 if len(pluginDirs) != 1 || pluginDirs[0] != "a" { 1929 t.Fatalf(`expected plugin dir ["a"], got %q`, pluginDirs) 1930 } 1931 1932 ui = new(cli.MockUi) 1933 c = &InitCommand{ 1934 Meta: Meta{ 1935 testingOverrides: metaOverridesForProvider(testProvider()), 1936 Ui: ui, 1937 View: view, 1938 ProviderSource: providerSource, // still empty 1939 }, 1940 } 1941 1942 // make sure we remove the plugin-dir record 1943 args = []string{"-plugin-dir="} 1944 if code := c.Run(args); code != 0 { 1945 t.Fatalf("bad: \n%s", ui.ErrorWriter) 1946 } 1947 1948 pluginDirs, err = c.loadPluginPath() 1949 if err != nil { 1950 t.Fatal(err) 1951 } 1952 1953 if len(pluginDirs) != 0 { 1954 t.Fatalf("expected no plugin dirs got %q", pluginDirs) 1955 } 1956} 1957 1958// Test user-supplied -plugin-dir 1959func TestInit_pluginDirProviders(t *testing.T) { 1960 td := tempDir(t) 1961 testCopyDir(t, testFixturePath("init-get-providers"), td) 1962 defer os.RemoveAll(td) 1963 defer testChdir(t, td)() 1964 1965 // An empty provider source 1966 providerSource, close := newMockProviderSource(t, nil) 1967 defer close() 1968 1969 ui := new(cli.MockUi) 1970 view, _ := testView(t) 1971 m := Meta{ 1972 testingOverrides: metaOverridesForProvider(testProvider()), 1973 Ui: ui, 1974 View: view, 1975 ProviderSource: providerSource, 1976 } 1977 1978 c := &InitCommand{ 1979 Meta: m, 1980 } 1981 1982 // make our vendor paths 1983 pluginPath := []string{"a", "b", "c"} 1984 for _, p := range pluginPath { 1985 if err := os.MkdirAll(p, 0755); err != nil { 1986 t.Fatal(err) 1987 } 1988 } 1989 1990 // We'll put some providers in our plugin dirs. To do this, we'll pretend 1991 // for a moment that they are provider cache directories just because that 1992 // allows us to lean on our existing test helper functions to do this. 1993 for i, def := range [][]string{ 1994 {"exact", "1.2.3"}, 1995 {"greater-than", "2.3.4"}, 1996 {"between", "2.3.4"}, 1997 } { 1998 name, version := def[0], def[1] 1999 dir := providercache.NewDir(pluginPath[i]) 2000 installFakeProviderPackagesElsewhere(t, dir, map[string][]string{ 2001 name: {version}, 2002 }) 2003 } 2004 2005 args := []string{ 2006 "-plugin-dir", "a", 2007 "-plugin-dir", "b", 2008 "-plugin-dir", "c", 2009 } 2010 if code := c.Run(args); code != 0 { 2011 t.Fatalf("bad: \n%s", ui.ErrorWriter) 2012 } 2013 2014 locks, err := m.lockedDependencies() 2015 if err != nil { 2016 t.Fatalf("failed to get locked dependencies: %s", err) 2017 } 2018 gotProviderLocks := locks.AllProviders() 2019 wantProviderLocks := map[addrs.Provider]*depsfile.ProviderLock{ 2020 addrs.NewDefaultProvider("between"): depsfile.NewProviderLock( 2021 addrs.NewDefaultProvider("between"), 2022 getproviders.MustParseVersion("2.3.4"), 2023 getproviders.MustParseVersionConstraints("> 1.0.0, < 3.0.0"), 2024 []getproviders.Hash{ 2025 getproviders.HashScheme1.New("JVqAvZz88A+hS2wHVtTWQkHaxoA/LrUAz0H3jPBWPIA="), 2026 }, 2027 ), 2028 addrs.NewDefaultProvider("exact"): depsfile.NewProviderLock( 2029 addrs.NewDefaultProvider("exact"), 2030 getproviders.MustParseVersion("1.2.3"), 2031 getproviders.MustParseVersionConstraints("= 1.2.3"), 2032 []getproviders.Hash{ 2033 getproviders.HashScheme1.New("H1TxWF8LyhBb6B4iUdKhLc/S9sC/jdcrCykpkbGcfbg="), 2034 }, 2035 ), 2036 addrs.NewDefaultProvider("greater-than"): depsfile.NewProviderLock( 2037 addrs.NewDefaultProvider("greater-than"), 2038 getproviders.MustParseVersion("2.3.4"), 2039 getproviders.MustParseVersionConstraints(">= 2.3.3"), 2040 []getproviders.Hash{ 2041 getproviders.HashScheme1.New("SJPpXx/yoFE/W+7eCipjJ+G21xbdnTBD7lWodZ8hWkU="), 2042 }, 2043 ), 2044 } 2045 if diff := cmp.Diff(gotProviderLocks, wantProviderLocks, depsfile.ProviderLockComparer); diff != "" { 2046 t.Errorf("wrong version selections after upgrade\n%s", diff) 2047 } 2048 2049 // -plugin-dir overrides the normal provider source, so it should not have 2050 // seen any calls at all. 2051 if calls := providerSource.CallLog(); len(calls) > 0 { 2052 t.Errorf("unexpected provider source calls (want none)\n%s", spew.Sdump(calls)) 2053 } 2054} 2055 2056// Test user-supplied -plugin-dir doesn't allow auto-install 2057func TestInit_pluginDirProvidersDoesNotGet(t *testing.T) { 2058 td := tempDir(t) 2059 testCopyDir(t, testFixturePath("init-get-providers"), td) 2060 defer os.RemoveAll(td) 2061 defer testChdir(t, td)() 2062 2063 // Our provider source has a suitable package for "between" available, 2064 // but we should ignore it because -plugin-dir is set and thus this 2065 // source is temporarily overridden during install. 2066 providerSource, close := newMockProviderSource(t, map[string][]string{ 2067 "between": {"2.3.4"}, 2068 }) 2069 defer close() 2070 2071 ui := cli.NewMockUi() 2072 view, _ := testView(t) 2073 m := Meta{ 2074 testingOverrides: metaOverridesForProvider(testProvider()), 2075 Ui: ui, 2076 View: view, 2077 ProviderSource: providerSource, 2078 } 2079 2080 c := &InitCommand{ 2081 Meta: m, 2082 } 2083 2084 // make our vendor paths 2085 pluginPath := []string{"a", "b"} 2086 for _, p := range pluginPath { 2087 if err := os.MkdirAll(p, 0755); err != nil { 2088 t.Fatal(err) 2089 } 2090 } 2091 2092 // We'll put some providers in our plugin dirs. To do this, we'll pretend 2093 // for a moment that they are provider cache directories just because that 2094 // allows us to lean on our existing test helper functions to do this. 2095 for i, def := range [][]string{ 2096 {"exact", "1.2.3"}, 2097 {"greater-than", "2.3.4"}, 2098 } { 2099 name, version := def[0], def[1] 2100 dir := providercache.NewDir(pluginPath[i]) 2101 installFakeProviderPackagesElsewhere(t, dir, map[string][]string{ 2102 name: {version}, 2103 }) 2104 } 2105 2106 args := []string{ 2107 "-plugin-dir", "a", 2108 "-plugin-dir", "b", 2109 } 2110 if code := c.Run(args); code == 0 { 2111 // should have been an error 2112 t.Fatalf("succeeded; want error\nstdout:\n%s\nstderr\n%s", ui.OutputWriter, ui.ErrorWriter) 2113 } 2114 2115 // The error output should mention the "between" provider but should not 2116 // mention either the "exact" or "greater-than" provider, because the 2117 // latter two are available via the -plugin-dir directories. 2118 errStr := ui.ErrorWriter.String() 2119 if subStr := "hashicorp/between"; !strings.Contains(errStr, subStr) { 2120 t.Errorf("error output should mention the 'between' provider\nwant substr: %s\ngot:\n%s", subStr, errStr) 2121 } 2122 if subStr := "hashicorp/exact"; strings.Contains(errStr, subStr) { 2123 t.Errorf("error output should not mention the 'exact' provider\ndo not want substr: %s\ngot:\n%s", subStr, errStr) 2124 } 2125 if subStr := "hashicorp/greater-than"; strings.Contains(errStr, subStr) { 2126 t.Errorf("error output should not mention the 'greater-than' provider\ndo not want substr: %s\ngot:\n%s", subStr, errStr) 2127 } 2128 2129 if calls := providerSource.CallLog(); len(calls) > 0 { 2130 t.Errorf("unexpected provider source calls (want none)\n%s", spew.Sdump(calls)) 2131 } 2132} 2133 2134// Verify that plugin-dir doesn't prevent discovery of internal providers 2135func TestInit_pluginDirWithBuiltIn(t *testing.T) { 2136 td := tempDir(t) 2137 testCopyDir(t, testFixturePath("init-internal"), td) 2138 defer os.RemoveAll(td) 2139 defer testChdir(t, td)() 2140 2141 // An empty provider source 2142 providerSource, close := newMockProviderSource(t, nil) 2143 defer close() 2144 2145 ui := cli.NewMockUi() 2146 view, _ := testView(t) 2147 m := Meta{ 2148 testingOverrides: metaOverridesForProvider(testProvider()), 2149 Ui: ui, 2150 View: view, 2151 ProviderSource: providerSource, 2152 } 2153 2154 c := &InitCommand{ 2155 Meta: m, 2156 } 2157 2158 args := []string{"-plugin-dir", "./"} 2159 if code := c.Run(args); code != 0 { 2160 t.Fatalf("error: %s", ui.ErrorWriter) 2161 } 2162 2163 outputStr := ui.OutputWriter.String() 2164 if subStr := "terraform.io/builtin/terraform is built in to Terraform"; !strings.Contains(outputStr, subStr) { 2165 t.Errorf("output should mention the terraform provider\nwant substr: %s\ngot:\n%s", subStr, outputStr) 2166 } 2167} 2168 2169func TestInit_invalidBuiltInProviders(t *testing.T) { 2170 // This test fixture includes two invalid provider dependencies: 2171 // - an implied dependency on terraform.io/builtin/terraform with an 2172 // explicit version number, which is not allowed because it's builtin. 2173 // - an explicit dependency on terraform.io/builtin/nonexist, which does 2174 // not exist at all. 2175 td := tempDir(t) 2176 testCopyDir(t, testFixturePath("init-internal-invalid"), td) 2177 defer os.RemoveAll(td) 2178 defer testChdir(t, td)() 2179 2180 // An empty provider source 2181 providerSource, close := newMockProviderSource(t, nil) 2182 defer close() 2183 2184 ui := cli.NewMockUi() 2185 view, _ := testView(t) 2186 m := Meta{ 2187 testingOverrides: metaOverridesForProvider(testProvider()), 2188 Ui: ui, 2189 View: view, 2190 ProviderSource: providerSource, 2191 } 2192 2193 c := &InitCommand{ 2194 Meta: m, 2195 } 2196 2197 if code := c.Run(nil); code == 0 { 2198 t.Fatalf("succeeded, but was expecting error\nstdout:\n%s\nstderr:\n%s", ui.OutputWriter, ui.ErrorWriter) 2199 } 2200 2201 errStr := ui.ErrorWriter.String() 2202 if subStr := "Cannot use terraform.io/builtin/terraform: built-in"; !strings.Contains(errStr, subStr) { 2203 t.Errorf("error output should mention the terraform provider\nwant substr: %s\ngot:\n%s", subStr, errStr) 2204 } 2205 if subStr := "Cannot use terraform.io/builtin/nonexist: this Terraform release"; !strings.Contains(errStr, subStr) { 2206 t.Errorf("error output should mention the 'nonexist' provider\nwant substr: %s\ngot:\n%s", subStr, errStr) 2207 } 2208} 2209 2210// newMockProviderSource is a helper to succinctly construct a mock provider 2211// source that contains a set of packages matching the given provider versions 2212// that are available for installation (from temporary local files). 2213// 2214// The caller must call the returned close callback once the source is no 2215// longer needed, at which point it will clean up all of the temporary files 2216// and the packages in the source will no longer be available for installation. 2217// 2218// Provider addresses must be valid source strings, and passing only the 2219// provider name will be interpreted as a "default" provider under 2220// registry.terraform.io/hashicorp. If you need more control over the 2221// provider addresses, pass a full provider source string. 2222// 2223// This function also registers providers as belonging to the current platform, 2224// to ensure that they will be available to a provider installer operating in 2225// its default configuration. 2226// 2227// In case of any errors while constructing the source, this function will 2228// abort the current test using the given testing.T. Therefore a caller can 2229// assume that if this function returns then the result is valid and ready 2230// to use. 2231func newMockProviderSource(t *testing.T, availableProviderVersions map[string][]string) (source *getproviders.MockSource, close func()) { 2232 t.Helper() 2233 var packages []getproviders.PackageMeta 2234 var closes []func() 2235 close = func() { 2236 for _, f := range closes { 2237 f() 2238 } 2239 } 2240 for source, versions := range availableProviderVersions { 2241 addr := addrs.MustParseProviderSourceString(source) 2242 for _, versionStr := range versions { 2243 version, err := getproviders.ParseVersion(versionStr) 2244 if err != nil { 2245 close() 2246 t.Fatalf("failed to parse %q as a version number for %q: %s", versionStr, addr.ForDisplay(), err) 2247 } 2248 meta, close, err := getproviders.FakeInstallablePackageMeta(addr, version, getproviders.VersionList{getproviders.MustParseVersion("5.0")}, getproviders.CurrentPlatform, "") 2249 if err != nil { 2250 close() 2251 t.Fatalf("failed to prepare fake package for %s %s: %s", addr.ForDisplay(), versionStr, err) 2252 } 2253 closes = append(closes, close) 2254 packages = append(packages, meta) 2255 } 2256 } 2257 2258 return getproviders.NewMockSource(packages, nil), close 2259} 2260 2261// installFakeProviderPackages installs a fake package for the given provider 2262// names (interpreted as a "default" provider address) and versions into the 2263// local plugin cache for the given "meta". 2264// 2265// Any test using this must be using testChdir or some similar mechanism to 2266// make sure that it isn't writing directly into a test fixture or source 2267// directory within the codebase. 2268// 2269// If a requested package cannot be installed for some reason, this function 2270// will abort the test using the given testing.T. Therefore if this function 2271// returns the caller can assume that the requested providers have been 2272// installed. 2273func installFakeProviderPackages(t *testing.T, meta *Meta, providerVersions map[string][]string) { 2274 t.Helper() 2275 2276 cacheDir := meta.providerLocalCacheDir() 2277 installFakeProviderPackagesElsewhere(t, cacheDir, providerVersions) 2278} 2279 2280// installFakeProviderPackagesElsewhere is a variant of installFakeProviderPackages 2281// that will install packages into the given provider cache directory, rather 2282// than forcing the use of the local cache of the current "Meta". 2283func installFakeProviderPackagesElsewhere(t *testing.T, cacheDir *providercache.Dir, providerVersions map[string][]string) { 2284 t.Helper() 2285 2286 // It can be hard to spot the mistake of forgetting to run testChdir before 2287 // modifying the working directory, so we'll use a simple heuristic here 2288 // to try to detect that mistake and make a noisy error about it instead. 2289 wd, err := os.Getwd() 2290 if err == nil { 2291 wd = filepath.Clean(wd) 2292 // If the directory we're in is named "command" or if we're under a 2293 // directory named "testdata" then we'll assume a mistake and generate 2294 // an error. This will cause the test to fail but won't block it from 2295 // running. 2296 if filepath.Base(wd) == "command" || filepath.Base(wd) == "testdata" || strings.Contains(filepath.ToSlash(wd), "/testdata/") { 2297 t.Errorf("installFakeProviderPackage may be used only by tests that switch to a temporary working directory, e.g. using testChdir") 2298 } 2299 } 2300 2301 for name, versions := range providerVersions { 2302 addr := addrs.NewDefaultProvider(name) 2303 for _, versionStr := range versions { 2304 version, err := getproviders.ParseVersion(versionStr) 2305 if err != nil { 2306 t.Fatalf("failed to parse %q as a version number for %q: %s", versionStr, name, err) 2307 } 2308 meta, close, err := getproviders.FakeInstallablePackageMeta(addr, version, getproviders.VersionList{getproviders.MustParseVersion("5.0")}, getproviders.CurrentPlatform, "") 2309 // We're going to install all these fake packages before we return, 2310 // so we don't need to preserve them afterwards. 2311 defer close() 2312 if err != nil { 2313 t.Fatalf("failed to prepare fake package for %s %s: %s", name, versionStr, err) 2314 } 2315 _, err = cacheDir.InstallPackage(context.Background(), meta, nil) 2316 if err != nil { 2317 t.Fatalf("failed to install fake package for %s %s: %s", name, versionStr, err) 2318 } 2319 } 2320 } 2321} 2322 2323// expectedPackageInstallPath is a companion to installFakeProviderPackages 2324// that returns the path where the provider with the given name and version 2325// would be installed and, relatedly, where the installer will expect to 2326// find an already-installed version. 2327// 2328// Just as with installFakeProviderPackages, this function is a shortcut helper 2329// for "default-namespaced" providers as we commonly use in tests. If you need 2330// more control over the provider addresses, use functions of the underlying 2331// getproviders and providercache packages instead. 2332// 2333// The result always uses forward slashes, even on Windows, for consistency 2334// with how the getproviders and providercache packages build paths. 2335func expectedPackageInstallPath(name, version string, exe bool) string { 2336 platform := getproviders.CurrentPlatform 2337 baseDir := ".terraform/providers" 2338 if exe { 2339 p := fmt.Sprintf("registry.terraform.io/hashicorp/%s/%s/%s/terraform-provider-%s_%s", name, version, platform, name, version) 2340 if platform.OS == "windows" { 2341 p += ".exe" 2342 } 2343 return filepath.ToSlash(filepath.Join(baseDir, p)) 2344 } 2345 return filepath.ToSlash(filepath.Join( 2346 baseDir, fmt.Sprintf("registry.terraform.io/hashicorp/%s/%s/%s", name, version, platform), 2347 )) 2348} 2349