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