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