diff --git a/internal/command/jsonformat/diff.go b/internal/command/jsonformat/diff.go index 3a4a77ee4b..fd1db21694 100644 --- a/internal/command/jsonformat/diff.go +++ b/internal/command/jsonformat/diff.go @@ -43,7 +43,7 @@ func precomputeDiffs(plan Plan, mode plans.Mode) diffs { continue } - schema := plan.ProviderSchemas[drift.ProviderName].ResourceSchemas[drift.Type] + schema := plan.GetSchema(drift) diffs.drift = append(diffs.drift, diff{ change: drift, diff: differ.FromJsonChange(drift.Change, relevantAttrs).ComputeDiffForBlock(schema.Block), @@ -51,7 +51,7 @@ func precomputeDiffs(plan Plan, mode plans.Mode) diffs { } for _, change := range plan.ResourceChanges { - schema := plan.ProviderSchemas[change.ProviderName].ResourceSchemas[change.Type] + schema := plan.GetSchema(change) diffs.changes = append(diffs.changes, diff{ change: change, diff: differ.FromJsonChange(change.Change, attribute_path.AlwaysMatcher()).ComputeDiffForBlock(schema.Block), diff --git a/internal/command/jsonformat/renderer.go b/internal/command/jsonformat/renderer.go index 25c45c9059..4047529420 100644 --- a/internal/command/jsonformat/renderer.go +++ b/internal/command/jsonformat/renderer.go @@ -39,6 +39,17 @@ type Plan struct { ProviderSchemas map[string]*jsonprovider.Provider `json:"provider_schemas"` } +func (plan Plan) GetSchema(change jsonplan.ResourceChange) *jsonprovider.Schema { + switch change.Mode { + case jsonplan.ManagedResourceMode: + return plan.ProviderSchemas[change.ProviderName].ResourceSchemas[change.Type] + case jsonplan.DataResourceMode: + return plan.ProviderSchemas[change.ProviderName].DataSourceSchemas[change.Type] + default: + panic("found unrecognized resource mode: " + change.Mode) + } +} + type Renderer struct { Streams *terminal.Streams Colorize *colorstring.Colorize @@ -78,7 +89,7 @@ func (r Renderer) RenderHumanPlan(plan Plan, mode plans.Mode, opts ...RendererOp // Don't show anything for NoOp changes. continue } - if action == plans.Delete && diff.change.Mode != "managed" { + if action == plans.Delete && diff.change.Mode != jsonplan.ManagedResourceMode { // Don't render anything for deleted data sources. continue } @@ -464,7 +475,7 @@ func resourceChangeComment(resource jsonplan.ResourceChange, action plans.Action func resourceChangeHeader(change jsonplan.ResourceChange) string { mode := "resource" - if change.Mode != "managed" { + if change.Mode != jsonplan.ManagedResourceMode { mode = "data" } return fmt.Sprintf("%s \"%s\" \"%s\"", mode, change.Type, change.Name) diff --git a/internal/command/jsonplan/plan.go b/internal/command/jsonplan/plan.go index 57bf7dd5f8..e425c602c7 100644 --- a/internal/command/jsonplan/plan.go +++ b/internal/command/jsonplan/plan.go @@ -39,6 +39,9 @@ const ( ResourceInstanceDeleteBecauseNoMoveTarget = "delete_because_no_move_target" ResourceInstanceReadBecauseConfigUnknown = "read_because_config_unknown" ResourceInstanceReadBecauseDependencyPending = "read_because_dependency_pending" + + ManagedResourceMode = "managed" + DataResourceMode = "data" ) // Plan is the top-level representation of the json format of a plan. It includes @@ -445,9 +448,9 @@ func MarshalResourceChanges(resources []*plans.ResourceInstanceChangeSrc, schema switch addr.Resource.Resource.Mode { case addrs.ManagedResourceMode: - r.Mode = "managed" + r.Mode = ManagedResourceMode case addrs.DataResourceMode: - r.Mode = "data" + r.Mode = DataResourceMode default: return nil, fmt.Errorf("resource %s has an unsupported mode %s", r.Address, addr.Resource.Resource.Mode.String()) } diff --git a/internal/command/views/operation_test.go b/internal/command/views/operation_test.go index 9b0323a276..39da895903 100644 --- a/internal/command/views/operation_test.go +++ b/internal/command/views/operation_test.go @@ -356,6 +356,78 @@ Plan: 1 to add, 0 to change, 0 to destroy. } } +func TestOperation_planWithDatasource(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + v := NewOperation(arguments.ViewHuman, true, NewView(streams)) + + plan := testPlanWithDatasource(t) + schemas := testSchemas() + v.Plan(plan, schemas) + + want := ` +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + + create + <= read (data resources) + +Terraform will perform the following actions: + + # data.test_data_source.bar will be read during apply + <= data "test_data_source" "bar" { + + bar = "foo" + + id = "C6743020-40BD-4591-81E6-CD08494341D3" + } + + # test_resource.foo will be created + + resource "test_resource" "foo" { + + foo = "bar" + + id = (known after apply) + } + +Plan: 1 to add, 0 to change, 0 to destroy. +` + + if got := done(t).Stdout(); got != want { + t.Errorf("unexpected output\ngot:\n%s\nwant:\n%s", got, want) + } +} + +func TestOperation_planWithDatasourceAndDrift(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + v := NewOperation(arguments.ViewHuman, true, NewView(streams)) + + plan := testPlanWithDatasource(t) + schemas := testSchemas() + v.Plan(plan, schemas) + + want := ` +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + + create + <= read (data resources) + +Terraform will perform the following actions: + + # data.test_data_source.bar will be read during apply + <= data "test_data_source" "bar" { + + bar = "foo" + + id = "C6743020-40BD-4591-81E6-CD08494341D3" + } + + # test_resource.foo will be created + + resource "test_resource" "foo" { + + foo = "bar" + + id = (known after apply) + } + +Plan: 1 to add, 0 to change, 0 to destroy. +` + + if got := done(t).Stdout(); got != want { + t.Errorf("unexpected output\ngot:\n%s\nwant:\n%s", got, want) + } +} + func TestOperation_planNextStep(t *testing.T) { testCases := map[string]struct { path string diff --git a/internal/command/views/plan_test.go b/internal/command/views/plan_test.go index 757c7162dd..33205738ad 100644 --- a/internal/command/views/plan_test.go +++ b/internal/command/views/plan_test.go @@ -88,6 +88,45 @@ func testPlan(t *testing.T) *plans.Plan { } } +func testPlanWithDatasource(t *testing.T) *plans.Plan { + plan := testPlan(t) + + addr := addrs.Resource{ + Mode: addrs.DataResourceMode, + Type: "test_data_source", + Name: "bar", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) + + dataVal := cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("C6743020-40BD-4591-81E6-CD08494341D3"), + "bar": cty.StringVal("foo"), + }) + priorValRaw, err := plans.NewDynamicValue(cty.NullVal(dataVal.Type()), dataVal.Type()) + if err != nil { + t.Fatal(err) + } + plannedValRaw, err := plans.NewDynamicValue(dataVal, dataVal.Type()) + if err != nil { + t.Fatal(err) + } + + plan.Changes.SyncWrapper().AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{ + Addr: addr, + PrevRunAddr: addr, + ProviderAddr: addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ChangeSrc: plans.ChangeSrc{ + Action: plans.Read, + Before: priorValRaw, + After: plannedValRaw, + }, + }) + + return plan +} + func testSchemas() *terraform.Schemas { provider := testProvider() return &terraform.Schemas{ @@ -123,5 +162,15 @@ func testProviderSchema() *providers.GetProviderSchemaResponse { }, }, }, + DataSources: map[string]providers.Schema{ + "test_data_source": { + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Required: true}, + "bar": {Type: cty.String, Optional: true}, + }, + }, + }, + }, } }