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