kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/command/show_test.go (about) 1 package command 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 "kubeform.dev/terraform-backend-sdk/addrs" 14 "kubeform.dev/terraform-backend-sdk/configs/configschema" 15 "kubeform.dev/terraform-backend-sdk/plans" 16 "kubeform.dev/terraform-backend-sdk/providers" 17 "kubeform.dev/terraform-backend-sdk/states" 18 "kubeform.dev/terraform-backend-sdk/terraform" 19 "github.com/mitchellh/cli" 20 "github.com/zclconf/go-cty/cty" 21 ) 22 23 func TestShow(t *testing.T) { 24 ui := new(cli.MockUi) 25 view, _ := testView(t) 26 c := &ShowCommand{ 27 Meta: Meta{ 28 testingOverrides: metaOverridesForProvider(testProvider()), 29 Ui: ui, 30 View: view, 31 }, 32 } 33 34 args := []string{ 35 "bad", 36 "bad", 37 } 38 if code := c.Run(args); code != 1 { 39 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 40 } 41 } 42 43 func TestShow_noArgs(t *testing.T) { 44 // Get a temp cwd 45 tmp, cwd := testCwd(t) 46 defer testFixCwd(t, tmp, cwd) 47 // Create the default state 48 testStateFileDefault(t, testState()) 49 50 ui := new(cli.MockUi) 51 view, _ := testView(t) 52 c := &ShowCommand{ 53 Meta: Meta{ 54 testingOverrides: metaOverridesForProvider(testProvider()), 55 Ui: ui, 56 View: view, 57 }, 58 } 59 60 if code := c.Run([]string{}); code != 0 { 61 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 62 } 63 64 if !strings.Contains(ui.OutputWriter.String(), "# test_instance.foo:") { 65 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 66 } 67 } 68 69 // https://github.com/hashicorp/terraform/issues/21462 70 func TestShow_aliasedProvider(t *testing.T) { 71 // Create the default state with aliased resource 72 testState := states.BuildState(func(s *states.SyncState) { 73 s.SetResourceInstanceCurrent( 74 addrs.Resource{ 75 Mode: addrs.ManagedResourceMode, 76 Type: "test_instance", 77 Name: "foo", 78 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 79 &states.ResourceInstanceObjectSrc{ 80 // The weird whitespace here is reflective of how this would 81 // get written out in a real state file, due to the indentation 82 // of all of the containing wrapping objects and arrays. 83 AttrsJSON: []byte("{\n \"id\": \"bar\"\n }"), 84 Status: states.ObjectReady, 85 Dependencies: []addrs.ConfigResource{}, 86 }, 87 addrs.RootModuleInstance.ProviderConfigAliased(addrs.NewDefaultProvider("test"), "alias"), 88 ) 89 }) 90 91 statePath := testStateFile(t, testState) 92 stateDir := filepath.Dir(statePath) 93 defer os.RemoveAll(stateDir) 94 defer testChdir(t, stateDir)() 95 96 ui := new(cli.MockUi) 97 view, _ := testView(t) 98 c := &ShowCommand{ 99 Meta: Meta{ 100 testingOverrides: metaOverridesForProvider(testProvider()), 101 Ui: ui, 102 View: view, 103 }, 104 } 105 106 fmt.Println(os.Getwd()) 107 108 // the statefile created by testStateFile is named state.tfstate 109 args := []string{"state.tfstate"} 110 if code := c.Run(args); code != 0 { 111 t.Fatalf("bad exit code: \n%s", ui.OutputWriter.String()) 112 } 113 114 if strings.Contains(ui.OutputWriter.String(), "# missing schema for provider \"test.alias\"") { 115 t.Fatalf("bad output: \n%s", ui.OutputWriter.String()) 116 } 117 } 118 119 func TestShow_noArgsNoState(t *testing.T) { 120 // Create the default state 121 statePath := testStateFile(t, testState()) 122 stateDir := filepath.Dir(statePath) 123 defer os.RemoveAll(stateDir) 124 defer testChdir(t, stateDir)() 125 126 ui := new(cli.MockUi) 127 view, _ := testView(t) 128 c := &ShowCommand{ 129 Meta: Meta{ 130 testingOverrides: metaOverridesForProvider(testProvider()), 131 Ui: ui, 132 View: view, 133 }, 134 } 135 136 // the statefile created by testStateFile is named state.tfstate 137 args := []string{"state.tfstate"} 138 if code := c.Run(args); code != 0 { 139 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 140 } 141 } 142 143 func TestShow_planNoop(t *testing.T) { 144 planPath := testPlanFileNoop(t) 145 146 ui := cli.NewMockUi() 147 view, done := testView(t) 148 c := &ShowCommand{ 149 Meta: Meta{ 150 testingOverrides: metaOverridesForProvider(testProvider()), 151 Ui: ui, 152 View: view, 153 }, 154 } 155 156 args := []string{ 157 planPath, 158 } 159 if code := c.Run(args); code != 0 { 160 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 161 } 162 163 want := `No changes. Your infrastructure matches the configuration.` 164 got := done(t).Stdout() 165 if !strings.Contains(got, want) { 166 t.Errorf("missing expected output\nwant: %s\ngot:\n%s", want, got) 167 } 168 } 169 170 func TestShow_planWithChanges(t *testing.T) { 171 planPathWithChanges := showFixturePlanFile(t, plans.DeleteThenCreate) 172 173 ui := cli.NewMockUi() 174 view, done := testView(t) 175 c := &ShowCommand{ 176 Meta: Meta{ 177 testingOverrides: metaOverridesForProvider(showFixtureProvider()), 178 Ui: ui, 179 View: view, 180 }, 181 } 182 183 args := []string{ 184 planPathWithChanges, 185 } 186 187 if code := c.Run(args); code != 0 { 188 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 189 } 190 191 want := `test_instance.foo must be replaced` 192 got := done(t).Stdout() 193 if !strings.Contains(got, want) { 194 t.Errorf("missing expected output\nwant: %s\ngot:\n%s", want, got) 195 } 196 } 197 198 func TestShow_planWithForceReplaceChange(t *testing.T) { 199 // The main goal of this test is to see that the "replace by request" 200 // resource instance action reason can round-trip through a plan file and 201 // be reflected correctly in the "terraform show" output, the same way 202 // as it would appear in "terraform plan" output. 203 204 _, snap := testModuleWithSnapshot(t, "show") 205 plannedVal := cty.ObjectVal(map[string]cty.Value{ 206 "id": cty.UnknownVal(cty.String), 207 "ami": cty.StringVal("bar"), 208 }) 209 priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type()) 210 if err != nil { 211 t.Fatal(err) 212 } 213 plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type()) 214 if err != nil { 215 t.Fatal(err) 216 } 217 plan := testPlan(t) 218 plan.Changes.SyncWrapper().AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{ 219 Addr: addrs.Resource{ 220 Mode: addrs.ManagedResourceMode, 221 Type: "test_instance", 222 Name: "foo", 223 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 224 ProviderAddr: addrs.AbsProviderConfig{ 225 Provider: addrs.NewDefaultProvider("test"), 226 Module: addrs.RootModule, 227 }, 228 ChangeSrc: plans.ChangeSrc{ 229 Action: plans.CreateThenDelete, 230 Before: priorValRaw, 231 After: plannedValRaw, 232 }, 233 ActionReason: plans.ResourceInstanceReplaceByRequest, 234 }) 235 planFilePath := testPlanFile( 236 t, 237 snap, 238 states.NewState(), 239 plan, 240 ) 241 242 ui := cli.NewMockUi() 243 view, done := testView(t) 244 c := &ShowCommand{ 245 Meta: Meta{ 246 testingOverrides: metaOverridesForProvider(showFixtureProvider()), 247 Ui: ui, 248 View: view, 249 }, 250 } 251 252 args := []string{ 253 planFilePath, 254 } 255 256 if code := c.Run(args); code != 0 { 257 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 258 } 259 260 got := done(t).Stdout() 261 if want := `test_instance.foo will be replaced, as requested`; !strings.Contains(got, want) { 262 t.Errorf("wrong output\ngot:\n%s\n\nwant substring: %s", got, want) 263 } 264 if want := `Plan: 1 to add, 0 to change, 1 to destroy.`; !strings.Contains(got, want) { 265 t.Errorf("wrong output\ngot:\n%s\n\nwant substring: %s", got, want) 266 } 267 268 } 269 270 func TestShow_plan_json(t *testing.T) { 271 planPath := showFixturePlanFile(t, plans.Create) 272 273 ui := new(cli.MockUi) 274 view, _ := testView(t) 275 c := &ShowCommand{ 276 Meta: Meta{ 277 testingOverrides: metaOverridesForProvider(showFixtureProvider()), 278 Ui: ui, 279 View: view, 280 }, 281 } 282 283 args := []string{ 284 "-json", 285 planPath, 286 } 287 if code := c.Run(args); code != 0 { 288 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 289 } 290 } 291 292 func TestShow_state(t *testing.T) { 293 originalState := testState() 294 statePath := testStateFile(t, originalState) 295 defer os.RemoveAll(filepath.Dir(statePath)) 296 297 ui := new(cli.MockUi) 298 view, _ := testView(t) 299 c := &ShowCommand{ 300 Meta: Meta{ 301 testingOverrides: metaOverridesForProvider(testProvider()), 302 Ui: ui, 303 View: view, 304 }, 305 } 306 307 args := []string{ 308 statePath, 309 } 310 if code := c.Run(args); code != 0 { 311 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 312 } 313 } 314 315 func TestShow_json_output(t *testing.T) { 316 fixtureDir := "testdata/show-json" 317 testDirs, err := ioutil.ReadDir(fixtureDir) 318 if err != nil { 319 t.Fatal(err) 320 } 321 322 for _, entry := range testDirs { 323 if !entry.IsDir() { 324 continue 325 } 326 327 t.Run(entry.Name(), func(t *testing.T) { 328 td := tempDir(t) 329 inputDir := filepath.Join(fixtureDir, entry.Name()) 330 testCopyDir(t, inputDir, td) 331 defer os.RemoveAll(td) 332 defer testChdir(t, td)() 333 334 expectError := strings.Contains(entry.Name(), "error") 335 336 providerSource, close := newMockProviderSource(t, map[string][]string{ 337 "test": {"1.2.3"}, 338 }) 339 defer close() 340 341 p := showFixtureProvider() 342 ui := new(cli.MockUi) 343 view, _ := testView(t) 344 m := Meta{ 345 testingOverrides: metaOverridesForProvider(p), 346 Ui: ui, 347 View: view, 348 ProviderSource: providerSource, 349 } 350 351 // init 352 ic := &InitCommand{ 353 Meta: m, 354 } 355 if code := ic.Run([]string{}); code != 0 { 356 if expectError { 357 // this should error, but not panic. 358 return 359 } 360 t.Fatalf("init failed\n%s", ui.ErrorWriter) 361 } 362 363 pc := &PlanCommand{ 364 Meta: m, 365 } 366 367 args := []string{ 368 "-out=terraform.plan", 369 } 370 371 if code := pc.Run(args); code != 0 { 372 t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String()) 373 } 374 375 // flush the plan output from the mock ui 376 ui.OutputWriter.Reset() 377 sc := &ShowCommand{ 378 Meta: m, 379 } 380 381 args = []string{ 382 "-json", 383 "terraform.plan", 384 } 385 defer os.Remove("terraform.plan") 386 387 if code := sc.Run(args); code != 0 { 388 t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String()) 389 } 390 391 // compare ui output to wanted output 392 var got, want plan 393 394 gotString := ui.OutputWriter.String() 395 json.Unmarshal([]byte(gotString), &got) 396 397 wantFile, err := os.Open("output.json") 398 if err != nil { 399 t.Fatalf("err: %s", err) 400 } 401 defer wantFile.Close() 402 byteValue, err := ioutil.ReadAll(wantFile) 403 if err != nil { 404 t.Fatalf("err: %s", err) 405 } 406 json.Unmarshal([]byte(byteValue), &want) 407 408 if !cmp.Equal(got, want) { 409 t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) 410 } 411 }) 412 } 413 } 414 415 func TestShow_json_output_sensitive(t *testing.T) { 416 td := tempDir(t) 417 inputDir := "testdata/show-json-sensitive" 418 testCopyDir(t, inputDir, td) 419 defer os.RemoveAll(td) 420 defer testChdir(t, td)() 421 422 providerSource, close := newMockProviderSource(t, map[string][]string{"test": {"1.2.3"}}) 423 defer close() 424 425 p := showFixtureSensitiveProvider() 426 ui := new(cli.MockUi) 427 view, _ := testView(t) 428 m := Meta{ 429 testingOverrides: metaOverridesForProvider(p), 430 Ui: ui, 431 View: view, 432 ProviderSource: providerSource, 433 } 434 435 // init 436 ic := &InitCommand{ 437 Meta: m, 438 } 439 if code := ic.Run([]string{}); code != 0 { 440 t.Fatalf("init failed\n%s", ui.ErrorWriter) 441 } 442 443 // flush init output 444 ui.OutputWriter.Reset() 445 446 pc := &PlanCommand{ 447 Meta: m, 448 } 449 450 args := []string{ 451 "-out=terraform.plan", 452 } 453 454 if code := pc.Run(args); code != 0 { 455 fmt.Println(ui.OutputWriter.String()) 456 t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String()) 457 } 458 459 // flush the plan output from the mock ui 460 ui.OutputWriter.Reset() 461 sc := &ShowCommand{ 462 Meta: m, 463 } 464 465 args = []string{ 466 "-json", 467 "terraform.plan", 468 } 469 defer os.Remove("terraform.plan") 470 471 if code := sc.Run(args); code != 0 { 472 t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String()) 473 } 474 475 // compare ui output to wanted output 476 var got, want plan 477 478 gotString := ui.OutputWriter.String() 479 json.Unmarshal([]byte(gotString), &got) 480 481 wantFile, err := os.Open("output.json") 482 if err != nil { 483 t.Fatalf("err: %s", err) 484 } 485 defer wantFile.Close() 486 byteValue, err := ioutil.ReadAll(wantFile) 487 if err != nil { 488 t.Fatalf("err: %s", err) 489 } 490 json.Unmarshal([]byte(byteValue), &want) 491 492 if !cmp.Equal(got, want) { 493 t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) 494 } 495 } 496 497 // similar test as above, without the plan 498 func TestShow_json_output_state(t *testing.T) { 499 fixtureDir := "testdata/show-json-state" 500 testDirs, err := ioutil.ReadDir(fixtureDir) 501 if err != nil { 502 t.Fatal(err) 503 } 504 505 for _, entry := range testDirs { 506 if !entry.IsDir() { 507 continue 508 } 509 510 t.Run(entry.Name(), func(t *testing.T) { 511 td := tempDir(t) 512 inputDir := filepath.Join(fixtureDir, entry.Name()) 513 testCopyDir(t, inputDir, td) 514 defer os.RemoveAll(td) 515 defer testChdir(t, td)() 516 517 providerSource, close := newMockProviderSource(t, map[string][]string{ 518 "test": {"1.2.3"}, 519 }) 520 defer close() 521 522 p := showFixtureProvider() 523 ui := new(cli.MockUi) 524 view, _ := testView(t) 525 m := Meta{ 526 testingOverrides: metaOverridesForProvider(p), 527 Ui: ui, 528 View: view, 529 ProviderSource: providerSource, 530 } 531 532 // init 533 ic := &InitCommand{ 534 Meta: m, 535 } 536 if code := ic.Run([]string{}); code != 0 { 537 t.Fatalf("init failed\n%s", ui.ErrorWriter) 538 } 539 540 // flush the plan output from the mock ui 541 ui.OutputWriter.Reset() 542 sc := &ShowCommand{ 543 Meta: m, 544 } 545 546 if code := sc.Run([]string{"-json"}); code != 0 { 547 t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String()) 548 } 549 550 // compare ui output to wanted output 551 type state struct { 552 FormatVersion string `json:"format_version,omitempty"` 553 TerraformVersion string `json:"terraform_version"` 554 Values map[string]interface{} `json:"values,omitempty"` 555 SensitiveValues map[string]bool `json:"sensitive_values,omitempty"` 556 } 557 var got, want state 558 559 gotString := ui.OutputWriter.String() 560 json.Unmarshal([]byte(gotString), &got) 561 562 wantFile, err := os.Open("output.json") 563 if err != nil { 564 t.Fatalf("err: %s", err) 565 } 566 defer wantFile.Close() 567 byteValue, err := ioutil.ReadAll(wantFile) 568 if err != nil { 569 t.Fatalf("err: %s", err) 570 } 571 json.Unmarshal([]byte(byteValue), &want) 572 573 if !cmp.Equal(got, want) { 574 t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) 575 } 576 }) 577 } 578 } 579 580 // showFixtureSchema returns a schema suitable for processing the configuration 581 // in testdata/show. This schema should be assigned to a mock provider 582 // named "test". 583 func showFixtureSchema() *providers.GetProviderSchemaResponse { 584 return &providers.GetProviderSchemaResponse{ 585 Provider: providers.Schema{ 586 Block: &configschema.Block{ 587 Attributes: map[string]*configschema.Attribute{ 588 "region": {Type: cty.String, Optional: true}, 589 }, 590 }, 591 }, 592 ResourceTypes: map[string]providers.Schema{ 593 "test_instance": { 594 Block: &configschema.Block{ 595 Attributes: map[string]*configschema.Attribute{ 596 "id": {Type: cty.String, Optional: true, Computed: true}, 597 "ami": {Type: cty.String, Optional: true}, 598 }, 599 }, 600 }, 601 }, 602 } 603 } 604 605 // showFixtureSensitiveSchema returns a schema suitable for processing the configuration 606 // in testdata/show. This schema should be assigned to a mock provider 607 // named "test". It includes a sensitive attribute. 608 func showFixtureSensitiveSchema() *providers.GetProviderSchemaResponse { 609 return &providers.GetProviderSchemaResponse{ 610 Provider: providers.Schema{ 611 Block: &configschema.Block{ 612 Attributes: map[string]*configschema.Attribute{ 613 "region": {Type: cty.String, Optional: true}, 614 }, 615 }, 616 }, 617 ResourceTypes: map[string]providers.Schema{ 618 "test_instance": { 619 Block: &configschema.Block{ 620 Attributes: map[string]*configschema.Attribute{ 621 "id": {Type: cty.String, Optional: true, Computed: true}, 622 "ami": {Type: cty.String, Optional: true}, 623 "password": {Type: cty.String, Optional: true, Sensitive: true}, 624 }, 625 }, 626 }, 627 }, 628 } 629 } 630 631 // showFixtureProvider returns a mock provider that is configured for basic 632 // operation with the configuration in testdata/show. This mock has 633 // GetSchemaResponse, PlanResourceChangeFn, and ApplyResourceChangeFn populated, 634 // with the plan/apply steps just passing through the data determined by 635 // Terraform Core. 636 func showFixtureProvider() *terraform.MockProvider { 637 p := testProvider() 638 p.GetProviderSchemaResponse = showFixtureSchema() 639 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 640 idVal := req.PriorState.GetAttr("id") 641 amiVal := req.PriorState.GetAttr("ami") 642 if amiVal.RawEquals(cty.StringVal("refresh-me")) { 643 amiVal = cty.StringVal("refreshed") 644 } 645 return providers.ReadResourceResponse{ 646 NewState: cty.ObjectVal(map[string]cty.Value{ 647 "id": idVal, 648 "ami": amiVal, 649 }), 650 Private: req.Private, 651 } 652 } 653 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 654 idVal := req.ProposedNewState.GetAttr("id") 655 amiVal := req.ProposedNewState.GetAttr("ami") 656 if idVal.IsNull() { 657 idVal = cty.UnknownVal(cty.String) 658 } 659 var reqRep []cty.Path 660 if amiVal.RawEquals(cty.StringVal("force-replace")) { 661 reqRep = append(reqRep, cty.GetAttrPath("ami")) 662 } 663 return providers.PlanResourceChangeResponse{ 664 PlannedState: cty.ObjectVal(map[string]cty.Value{ 665 "id": idVal, 666 "ami": amiVal, 667 }), 668 RequiresReplace: reqRep, 669 } 670 } 671 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 672 idVal := req.PlannedState.GetAttr("id") 673 amiVal := req.PlannedState.GetAttr("ami") 674 if !idVal.IsKnown() { 675 idVal = cty.StringVal("placeholder") 676 } 677 return providers.ApplyResourceChangeResponse{ 678 NewState: cty.ObjectVal(map[string]cty.Value{ 679 "id": idVal, 680 "ami": amiVal, 681 }), 682 } 683 } 684 return p 685 } 686 687 // showFixtureSensitiveProvider returns a mock provider that is configured for basic 688 // operation with the configuration in testdata/show. This mock has 689 // GetSchemaResponse, PlanResourceChangeFn, and ApplyResourceChangeFn populated, 690 // with the plan/apply steps just passing through the data determined by 691 // Terraform Core. It also has a sensitive attribute in the provider schema. 692 func showFixtureSensitiveProvider() *terraform.MockProvider { 693 p := testProvider() 694 p.GetProviderSchemaResponse = showFixtureSensitiveSchema() 695 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 696 idVal := req.ProposedNewState.GetAttr("id") 697 if idVal.IsNull() { 698 idVal = cty.UnknownVal(cty.String) 699 } 700 return providers.PlanResourceChangeResponse{ 701 PlannedState: cty.ObjectVal(map[string]cty.Value{ 702 "id": idVal, 703 "ami": req.ProposedNewState.GetAttr("ami"), 704 "password": req.ProposedNewState.GetAttr("password"), 705 }), 706 } 707 } 708 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 709 idVal := req.PlannedState.GetAttr("id") 710 if !idVal.IsKnown() { 711 idVal = cty.StringVal("placeholder") 712 } 713 return providers.ApplyResourceChangeResponse{ 714 NewState: cty.ObjectVal(map[string]cty.Value{ 715 "id": idVal, 716 "ami": req.PlannedState.GetAttr("ami"), 717 "password": req.PlannedState.GetAttr("password"), 718 }), 719 } 720 } 721 return p 722 } 723 724 // showFixturePlanFile creates a plan file at a temporary location containing a 725 // single change to create or update the test_instance.foo that is included in the "show" 726 // test fixture, returning the location of that plan file. 727 // `action` is the planned change you would like to elicit 728 func showFixturePlanFile(t *testing.T, action plans.Action) string { 729 _, snap := testModuleWithSnapshot(t, "show") 730 plannedVal := cty.ObjectVal(map[string]cty.Value{ 731 "id": cty.UnknownVal(cty.String), 732 "ami": cty.StringVal("bar"), 733 }) 734 priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type()) 735 if err != nil { 736 t.Fatal(err) 737 } 738 plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type()) 739 if err != nil { 740 t.Fatal(err) 741 } 742 plan := testPlan(t) 743 plan.Changes.SyncWrapper().AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{ 744 Addr: addrs.Resource{ 745 Mode: addrs.ManagedResourceMode, 746 Type: "test_instance", 747 Name: "foo", 748 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 749 ProviderAddr: addrs.AbsProviderConfig{ 750 Provider: addrs.NewDefaultProvider("test"), 751 Module: addrs.RootModule, 752 }, 753 ChangeSrc: plans.ChangeSrc{ 754 Action: action, 755 Before: priorValRaw, 756 After: plannedValRaw, 757 }, 758 }) 759 return testPlanFile( 760 t, 761 snap, 762 states.NewState(), 763 plan, 764 ) 765 } 766 767 // this simplified plan struct allows us to preserve field order when marshaling 768 // the command output. NOTE: we are leaving "terraform_version" out of this test 769 // to avoid needing to constantly update the expected output; as a potential 770 // TODO we could write a jsonplan compare function. 771 type plan struct { 772 FormatVersion string `json:"format_version,omitempty"` 773 Variables map[string]interface{} `json:"variables,omitempty"` 774 PlannedValues map[string]interface{} `json:"planned_values,omitempty"` 775 ResourceDrift []interface{} `json:"resource_drift,omitempty"` 776 ResourceChanges []interface{} `json:"resource_changes,omitempty"` 777 OutputChanges map[string]interface{} `json:"output_changes,omitempty"` 778 PriorState priorState `json:"prior_state,omitempty"` 779 Config map[string]interface{} `json:"configuration,omitempty"` 780 } 781 782 type priorState struct { 783 FormatVersion string `json:"format_version,omitempty"` 784 Values map[string]interface{} `json:"values,omitempty"` 785 SensitiveValues map[string]bool `json:"sensitive_values,omitempty"` 786 }