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