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