From 41525a30ab3bd270383a91d3eabf75b3a839529a Mon Sep 17 00:00:00 2001 From: Katy Moe Date: Tue, 27 Oct 2020 11:58:11 +0000 Subject: [PATCH] remove internal/helper/plugin package --- go.sum | 1 + .../context => helper/schema}/context.go | 2 +- helper/schema/grpc_provider.go | 3 +- helper/schema/grpc_provider_test.go | 1566 +++++++++++++++++ helper/schema/provider.go | 3 +- .../plugin => helper/schema}/unknown_test.go | 2 +- internal/helper/plugin/doc.go | 6 - internal/helper/plugin/unknown.go | 132 -- 8 files changed, 1571 insertions(+), 144 deletions(-) rename {internal/helper/plugin/context => helper/schema}/context.go (79%) create mode 100644 helper/schema/grpc_provider_test.go rename {internal/helper/plugin => helper/schema}/unknown_test.go (99%) delete mode 100644 internal/helper/plugin/doc.go delete mode 100644 internal/helper/plugin/unknown.go diff --git a/go.sum b/go.sum index 7263d3317a4..9e3dd5048ab 100644 --- a/go.sum +++ b/go.sum @@ -198,6 +198,7 @@ github.com/hashicorp/terraform-json v0.5.0/go.mod h1:eAbqb4w0pSlRmdvl8fOyHAi/+8j github.com/hashicorp/terraform-plugin-go v0.0.0-20201007135710-95da7fbd4bb8/go.mod h1:iVxhkJdmLuDh+8BKTj9bdL+/lbusHKxAEuptE8VCjdM= github.com/hashicorp/terraform-plugin-go v0.0.0-20201020231029-49daeca5241c h1:rQPyr6gQsLIw95kCxN604RhWB6QYBDtLgu1QjcXFiTU= github.com/hashicorp/terraform-plugin-go v0.0.0-20201020231029-49daeca5241c/go.mod h1:iVxhkJdmLuDh+8BKTj9bdL+/lbusHKxAEuptE8VCjdM= +github.com/hashicorp/terraform-plugin-sdk v1.16.0 h1:NrkXMRjHErUPPTHQkZ6JIn6bByiJzGnlJzH1rVdNEuE= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= diff --git a/internal/helper/plugin/context/context.go b/helper/schema/context.go similarity index 79% rename from internal/helper/plugin/context/context.go rename to helper/schema/context.go index 0939edeff66..ed8d0964bfa 100644 --- a/internal/helper/plugin/context/context.go +++ b/helper/schema/context.go @@ -1,4 +1,4 @@ -package context +package schema type Key string diff --git a/helper/schema/grpc_provider.go b/helper/schema/grpc_provider.go index 670af30069f..8776fff82be 100644 --- a/helper/schema/grpc_provider.go +++ b/helper/schema/grpc_provider.go @@ -16,7 +16,6 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov5/tftypes" "github.com/hashicorp/terraform-plugin-sdk/v2/internal/configs/configschema" "github.com/hashicorp/terraform-plugin-sdk/v2/internal/configs/hcl2shim" - c "github.com/hashicorp/terraform-plugin-sdk/v2/internal/helper/plugin/context" "github.com/hashicorp/terraform-plugin-sdk/v2/internal/plans/objchange" "github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugin/convert" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -516,7 +515,7 @@ func (s *GRPCProviderServer) ConfigureProvider(ctx context.Context, req *proto.C // request scoped context. This provides a substitute for the removed provider.StopContext() // function. Ideally a provider should migrate to the context aware API that receives // request scoped contexts, however this is a large undertaking for very large providers. - ctxHack := context.WithValue(ctx, c.StopContextKey, s.StopContext(context.Background())) + ctxHack := context.WithValue(ctx, StopContextKey, s.StopContext(context.Background())) diags := s.provider.Configure(ctxHack, config) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, diags) diff --git a/helper/schema/grpc_provider_test.go b/helper/schema/grpc_provider_test.go new file mode 100644 index 00000000000..19a46cc4979 --- /dev/null +++ b/helper/schema/grpc_provider_test.go @@ -0,0 +1,1566 @@ +package schema + +import ( + "context" + "fmt" + "strconv" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/go-cty/cty/msgpack" + + proto "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugin/convert" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +// The GRPCProviderServer will directly implement the go protobuf server +var _ proto.ProviderServer = (*GRPCProviderServer)(nil) + +func TestUpgradeState_jsonState(t *testing.T) { + r := &Resource{ + SchemaVersion: 2, + Schema: map[string]*Schema{ + "two": { + Type: TypeInt, + Optional: true, + }, + }, + } + + r.StateUpgraders = []StateUpgrader{ + { + Version: 0, + Type: cty.Object(map[string]cty.Type{ + "id": cty.String, + "zero": cty.Number, + }), + Upgrade: func(ctx context.Context, m map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + _, ok := m["zero"].(float64) + if !ok { + return nil, fmt.Errorf("zero not found in %#v", m) + } + m["one"] = float64(1) + delete(m, "zero") + return m, nil + }, + }, + { + Version: 1, + Type: cty.Object(map[string]cty.Type{ + "id": cty.String, + "one": cty.Number, + }), + Upgrade: func(ctx context.Context, m map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + _, ok := m["one"].(float64) + if !ok { + return nil, fmt.Errorf("one not found in %#v", m) + } + m["two"] = float64(2) + delete(m, "one") + return m, nil + }, + }, + } + + server := NewGRPCProviderServer(&Provider{ + ResourcesMap: map[string]*Resource{ + "test": r, + }, + }) + + req := &proto.UpgradeResourceStateRequest{ + TypeName: "test", + Version: 0, + RawState: &proto.RawState{ + JSON: []byte(`{"id":"bar","zero":0}`), + }, + } + + resp, err := server.UpgradeResourceState(nil, req) + if err != nil { + t.Fatal(err) + } + + if len(resp.Diagnostics) > 0 { + for _, d := range resp.Diagnostics { + t.Errorf("%#v", d) + } + t.Fatal("error") + } + + val, err := msgpack.Unmarshal(resp.UpgradedState.MsgPack, r.CoreConfigSchema().ImpliedType()) + if err != nil { + t.Fatal(err) + } + + expected := cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + "two": cty.NumberIntVal(2), + }) + + if !cmp.Equal(expected, val, valueComparer, equateEmpty) { + t.Fatal(cmp.Diff(expected, val, valueComparer, equateEmpty)) + } +} + +func TestUpgradeState_removedAttr(t *testing.T) { + r1 := &Resource{ + Schema: map[string]*Schema{ + "two": { + Type: TypeString, + Optional: true, + }, + }, + } + + r2 := &Resource{ + Schema: map[string]*Schema{ + "multi": { + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "set": { + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "required": { + Type: TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, + } + + r3 := &Resource{ + Schema: map[string]*Schema{ + "config_mode_attr": { + Type: TypeList, + ConfigMode: SchemaConfigModeAttr, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "foo": { + Type: TypeString, + Optional: true, + }, + }, + }, + }, + }, + } + + p := &Provider{ + ResourcesMap: map[string]*Resource{ + "r1": r1, + "r2": r2, + "r3": r3, + }, + } + + server := NewGRPCProviderServer(p) + + for _, tc := range []struct { + name string + raw string + expected cty.Value + }{ + { + name: "r1", + raw: `{"id":"bar","removed":"removed","two":"2"}`, + expected: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + "two": cty.StringVal("2"), + }), + }, + { + name: "r2", + raw: `{"id":"bar","multi":[{"set":[{"required":"ok","removed":"removed"}]}]}`, + expected: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + "multi": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "set": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "required": cty.StringVal("ok"), + }), + }), + }), + }), + }), + }, + { + name: "r3", + raw: `{"id":"bar","config_mode_attr":[{"foo":"ok","removed":"removed"}]}`, + expected: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + "config_mode_attr": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("ok"), + }), + }), + }), + }, + } { + t.Run(tc.name, func(t *testing.T) { + req := &proto.UpgradeResourceStateRequest{ + TypeName: tc.name, + Version: 0, + RawState: &proto.RawState{ + JSON: []byte(tc.raw), + }, + } + resp, err := server.UpgradeResourceState(nil, req) + if err != nil { + t.Fatal(err) + } + + if len(resp.Diagnostics) > 0 { + for _, d := range resp.Diagnostics { + t.Errorf("%#v", d) + } + t.Fatal("error") + } + val, err := msgpack.Unmarshal(resp.UpgradedState.MsgPack, p.ResourcesMap[tc.name].CoreConfigSchema().ImpliedType()) + if err != nil { + t.Fatal(err) + } + if !tc.expected.RawEquals(val) { + t.Fatalf("\nexpected: %#v\ngot: %#v\n", tc.expected, val) + } + }) + } + +} + +func TestUpgradeState_flatmapState(t *testing.T) { + r := &Resource{ + SchemaVersion: 4, + Schema: map[string]*Schema{ + "four": { + Type: TypeInt, + Required: true, + }, + "block": { + Type: TypeList, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "attr": { + Type: TypeString, + Optional: true, + }, + }, + }, + }, + }, + // this MigrateState will take the state to version 2 + MigrateState: func(v int, is *terraform.InstanceState, _ interface{}) (*terraform.InstanceState, error) { + switch v { + case 0: + _, ok := is.Attributes["zero"] + if !ok { + return nil, fmt.Errorf("zero not found in %#v", is.Attributes) + } + is.Attributes["one"] = "1" + delete(is.Attributes, "zero") + fallthrough + case 1: + _, ok := is.Attributes["one"] + if !ok { + return nil, fmt.Errorf("one not found in %#v", is.Attributes) + } + is.Attributes["two"] = "2" + delete(is.Attributes, "one") + default: + return nil, fmt.Errorf("invalid schema version %d", v) + } + return is, nil + }, + } + + r.StateUpgraders = []StateUpgrader{ + { + Version: 2, + Type: cty.Object(map[string]cty.Type{ + "id": cty.String, + "two": cty.Number, + }), + Upgrade: func(ctx context.Context, m map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + _, ok := m["two"].(float64) + if !ok { + return nil, fmt.Errorf("two not found in %#v", m) + } + m["three"] = float64(3) + delete(m, "two") + return m, nil + }, + }, + { + Version: 3, + Type: cty.Object(map[string]cty.Type{ + "id": cty.String, + "three": cty.Number, + }), + Upgrade: func(ctx context.Context, m map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + _, ok := m["three"].(float64) + if !ok { + return nil, fmt.Errorf("three not found in %#v", m) + } + m["four"] = float64(4) + delete(m, "three") + return m, nil + }, + }, + } + + server := NewGRPCProviderServer(&Provider{ + ResourcesMap: map[string]*Resource{ + "test": r, + }, + }) + + testReqs := []*proto.UpgradeResourceStateRequest{ + { + TypeName: "test", + Version: 0, + RawState: &proto.RawState{ + Flatmap: map[string]string{ + "id": "bar", + "zero": "0", + }, + }, + }, + { + TypeName: "test", + Version: 1, + RawState: &proto.RawState{ + Flatmap: map[string]string{ + "id": "bar", + "one": "1", + }, + }, + }, + // two and up could be stored in flatmap or json states + { + TypeName: "test", + Version: 2, + RawState: &proto.RawState{ + Flatmap: map[string]string{ + "id": "bar", + "two": "2", + }, + }, + }, + { + TypeName: "test", + Version: 2, + RawState: &proto.RawState{ + JSON: []byte(`{"id":"bar","two":2}`), + }, + }, + { + TypeName: "test", + Version: 3, + RawState: &proto.RawState{ + Flatmap: map[string]string{ + "id": "bar", + "three": "3", + }, + }, + }, + { + TypeName: "test", + Version: 3, + RawState: &proto.RawState{ + JSON: []byte(`{"id":"bar","three":3}`), + }, + }, + { + TypeName: "test", + Version: 4, + RawState: &proto.RawState{ + Flatmap: map[string]string{ + "id": "bar", + "four": "4", + }, + }, + }, + { + TypeName: "test", + Version: 4, + RawState: &proto.RawState{ + JSON: []byte(`{"id":"bar","four":4}`), + }, + }, + } + + for i, req := range testReqs { + t.Run(fmt.Sprintf("%d-%d", i, req.Version), func(t *testing.T) { + resp, err := server.UpgradeResourceState(nil, req) + if err != nil { + t.Fatal(err) + } + + if len(resp.Diagnostics) > 0 { + for _, d := range resp.Diagnostics { + t.Errorf("%#v", d) + } + t.Fatal("error") + } + + val, err := msgpack.Unmarshal(resp.UpgradedState.MsgPack, r.CoreConfigSchema().ImpliedType()) + if err != nil { + t.Fatal(err) + } + + expected := cty.ObjectVal(map[string]cty.Value{ + "block": cty.ListValEmpty(cty.Object(map[string]cty.Type{"attr": cty.String})), + "id": cty.StringVal("bar"), + "four": cty.NumberIntVal(4), + }) + + if !cmp.Equal(expected, val, valueComparer, equateEmpty) { + t.Fatal(cmp.Diff(expected, val, valueComparer, equateEmpty)) + } + }) + } +} + +func TestUpgradeState_flatmapStateMissingMigrateState(t *testing.T) { + r := &Resource{ + SchemaVersion: 1, + Schema: map[string]*Schema{ + "one": { + Type: TypeInt, + Required: true, + }, + }, + } + + server := NewGRPCProviderServer(&Provider{ + ResourcesMap: map[string]*Resource{ + "test": r, + }, + }) + + testReqs := []*proto.UpgradeResourceStateRequest{ + { + TypeName: "test", + Version: 0, + RawState: &proto.RawState{ + Flatmap: map[string]string{ + "id": "bar", + "one": "1", + }, + }, + }, + { + TypeName: "test", + Version: 1, + RawState: &proto.RawState{ + Flatmap: map[string]string{ + "id": "bar", + "one": "1", + }, + }, + }, + { + TypeName: "test", + Version: 1, + RawState: &proto.RawState{ + JSON: []byte(`{"id":"bar","one":1}`), + }, + }, + } + + for i, req := range testReqs { + t.Run(fmt.Sprintf("%d-%d", i, req.Version), func(t *testing.T) { + resp, err := server.UpgradeResourceState(nil, req) + if err != nil { + t.Fatal(err) + } + + if len(resp.Diagnostics) > 0 { + for _, d := range resp.Diagnostics { + t.Errorf("%#v", d) + } + t.Fatal("error") + } + + val, err := msgpack.Unmarshal(resp.UpgradedState.MsgPack, r.CoreConfigSchema().ImpliedType()) + if err != nil { + t.Fatal(err) + } + + expected := cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + "one": cty.NumberIntVal(1), + }) + + if !cmp.Equal(expected, val, valueComparer, equateEmpty) { + t.Fatal(cmp.Diff(expected, val, valueComparer, equateEmpty)) + } + }) + } +} + +func TestPlanResourceChange(t *testing.T) { + r := &Resource{ + SchemaVersion: 4, + Schema: map[string]*Schema{ + "foo": { + Type: TypeInt, + Optional: true, + }, + }, + } + + server := NewGRPCProviderServer(&Provider{ + ResourcesMap: map[string]*Resource{ + "test": r, + }, + }) + + schema := r.CoreConfigSchema() + priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + // A propsed state with only the ID unknown will produce a nil diff, and + // should return the propsed state value. + proposedVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + })) + if err != nil { + t.Fatal(err) + } + proposedState, err := msgpack.Marshal(proposedVal, schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + testReq := &proto.PlanResourceChangeRequest{ + TypeName: "test", + PriorState: &proto.DynamicValue{ + MsgPack: priorState, + }, + ProposedNewState: &proto.DynamicValue{ + MsgPack: proposedState, + }, + } + + resp, err := server.PlanResourceChange(context.Background(), testReq) + if err != nil { + t.Fatal(err) + } + + plannedStateVal, err := msgpack.Unmarshal(resp.PlannedState.MsgPack, schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + if !cmp.Equal(proposedVal, plannedStateVal, valueComparer) { + t.Fatal(cmp.Diff(proposedVal, plannedStateVal, valueComparer)) + } +} + +func TestApplyResourceChange(t *testing.T) { + r := &Resource{ + SchemaVersion: 4, + Schema: map[string]*Schema{ + "foo": { + Type: TypeInt, + Optional: true, + }, + }, + CreateContext: func(_ context.Context, rd *ResourceData, _ interface{}) diag.Diagnostics { + rd.SetId("bar") + return nil + }, + } + + server := NewGRPCProviderServer(&Provider{ + ResourcesMap: map[string]*Resource{ + "test": r, + }, + }) + + schema := r.CoreConfigSchema() + priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + // A proposed state with only the ID unknown will produce a nil diff, and + // should return the proposed state value. + plannedVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + })) + if err != nil { + t.Fatal(err) + } + plannedState, err := msgpack.Marshal(plannedVal, schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + testReq := &proto.ApplyResourceChangeRequest{ + TypeName: "test", + PriorState: &proto.DynamicValue{ + MsgPack: priorState, + }, + PlannedState: &proto.DynamicValue{ + MsgPack: plannedState, + }, + } + + resp, err := server.ApplyResourceChange(context.Background(), testReq) + if err != nil { + t.Fatal(err) + } + + newStateVal, err := msgpack.Unmarshal(resp.NewState.MsgPack, schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + id := newStateVal.GetAttr("id").AsString() + if id != "bar" { + t.Fatalf("incorrect final state: %#v\n", newStateVal) + } +} + +func TestPrepareProviderConfig(t *testing.T) { + for _, tc := range []struct { + Name string + Schema map[string]*Schema + ConfigVal cty.Value + ExpectError string + ExpectConfig cty.Value + }{ + { + Name: "test prepare", + Schema: map[string]*Schema{ + "foo": { + Type: TypeString, + Optional: true, + }, + }, + ConfigVal: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("bar"), + }), + ExpectConfig: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("bar"), + }), + }, + { + Name: "test default", + Schema: map[string]*Schema{ + "foo": { + Type: TypeString, + Optional: true, + Default: "default", + }, + }, + ConfigVal: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.NullVal(cty.String), + }), + ExpectConfig: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("default"), + }), + }, + { + Name: "test defaultfunc", + Schema: map[string]*Schema{ + "foo": { + Type: TypeString, + Optional: true, + DefaultFunc: func() (interface{}, error) { + return "defaultfunc", nil + }, + }, + }, + ConfigVal: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.NullVal(cty.String), + }), + ExpectConfig: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("defaultfunc"), + }), + }, + { + Name: "test default required", + Schema: map[string]*Schema{ + "foo": { + Type: TypeString, + Required: true, + DefaultFunc: func() (interface{}, error) { + return "defaultfunc", nil + }, + }, + }, + ConfigVal: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.NullVal(cty.String), + }), + ExpectConfig: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("defaultfunc"), + }), + }, + { + Name: "test incorrect type", + Schema: map[string]*Schema{ + "foo": { + Type: TypeString, + Required: true, + }, + }, + ConfigVal: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.NumberIntVal(3), + }), + ExpectConfig: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("3"), + }), + }, + { + Name: "test incorrect default type", + Schema: map[string]*Schema{ + "foo": { + Type: TypeString, + Optional: true, + Default: true, + }, + }, + ConfigVal: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.NullVal(cty.String), + }), + ExpectConfig: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("true"), + }), + }, + { + Name: "test incorrect default bool type", + Schema: map[string]*Schema{ + "foo": { + Type: TypeBool, + Optional: true, + Default: "", + }, + }, + ConfigVal: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.NullVal(cty.Bool), + }), + ExpectConfig: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.False, + }), + }, + } { + t.Run(tc.Name, func(t *testing.T) { + server := NewGRPCProviderServer(&Provider{ + Schema: tc.Schema, + }) + + block := InternalMap(tc.Schema).CoreConfigSchema() + + rawConfig, err := msgpack.Marshal(tc.ConfigVal, block.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + testReq := &proto.PrepareProviderConfigRequest{ + Config: &proto.DynamicValue{ + MsgPack: rawConfig, + }, + } + + resp, err := server.PrepareProviderConfig(nil, testReq) + if err != nil { + t.Fatal(err) + } + + if tc.ExpectError != "" && len(resp.Diagnostics) > 0 { + for _, d := range resp.Diagnostics { + if !strings.Contains(d.Summary, tc.ExpectError) { + t.Fatalf("Unexpected error: %s/%s", d.Summary, d.Detail) + } + } + return + } + + // we should have no errors past this point + for _, d := range resp.Diagnostics { + if d.Severity == proto.DiagnosticSeverityError { + t.Fatal(resp.Diagnostics) + } + } + + val, err := msgpack.Unmarshal(resp.PreparedConfig.MsgPack, block.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + if tc.ExpectConfig.GoString() != val.GoString() { + t.Fatalf("\nexpected: %#v\ngot: %#v", tc.ExpectConfig, val) + } + }) + } +} + +func TestGetSchemaTimeouts(t *testing.T) { + r := &Resource{ + SchemaVersion: 4, + Timeouts: &ResourceTimeout{ + Create: DefaultTimeout(time.Second), + Read: DefaultTimeout(2 * time.Second), + Update: DefaultTimeout(3 * time.Second), + Default: DefaultTimeout(10 * time.Second), + }, + Schema: map[string]*Schema{ + "foo": { + Type: TypeInt, + Optional: true, + }, + }, + } + + // verify that the timeouts appear in the schema as defined + block := r.CoreConfigSchema() + timeoutsBlock := block.BlockTypes["timeouts"] + if timeoutsBlock == nil { + t.Fatal("missing timeouts in schema") + } + + if timeoutsBlock.Attributes["create"] == nil { + t.Fatal("missing create timeout in schema") + } + if timeoutsBlock.Attributes["read"] == nil { + t.Fatal("missing read timeout in schema") + } + if timeoutsBlock.Attributes["update"] == nil { + t.Fatal("missing update timeout in schema") + } + if d := timeoutsBlock.Attributes["delete"]; d != nil { + t.Fatalf("unexpected delete timeout in schema: %#v", d) + } + if timeoutsBlock.Attributes["default"] == nil { + t.Fatal("missing default timeout in schema") + } +} + +func TestNormalizeNullValues(t *testing.T) { + for i, tc := range []struct { + Src, Dst, Expect cty.Value + Apply bool + }{ + { + // The known set value is copied over the null set value + Src: cty.ObjectVal(map[string]cty.Value{ + "set": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.NullVal(cty.String), + }), + }), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ + "foo": cty.String, + }))), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "set": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.NullVal(cty.String), + }), + }), + }), + Apply: true, + }, + { + // A zero set value is kept + Src: cty.ObjectVal(map[string]cty.Value{ + "set": cty.SetValEmpty(cty.String), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "set": cty.SetValEmpty(cty.String), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "set": cty.SetValEmpty(cty.String), + }), + }, + { + // The known set value is copied over the null set value + Src: cty.ObjectVal(map[string]cty.Value{ + "set": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.NullVal(cty.String), + }), + }), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ + "foo": cty.String, + }))), + }), + // If we're only in a plan, we can't compare sets at all + Expect: cty.ObjectVal(map[string]cty.Value{ + "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ + "foo": cty.String, + }))), + }), + }, + { + // The empty map is copied over the null map + Src: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapValEmpty(cty.String), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "map": cty.NullVal(cty.Map(cty.String)), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapValEmpty(cty.String), + }), + Apply: true, + }, + { + // A zero value primitive is copied over a null primitive + Src: cty.ObjectVal(map[string]cty.Value{ + "string": cty.StringVal(""), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "string": cty.NullVal(cty.String), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "string": cty.StringVal(""), + }), + Apply: true, + }, + { + // Plan primitives are kept + Src: cty.ObjectVal(map[string]cty.Value{ + "string": cty.NumberIntVal(0), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "string": cty.NullVal(cty.Number), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "string": cty.NullVal(cty.Number), + }), + }, + { + // Neither plan nor apply should remove empty strings + Src: cty.ObjectVal(map[string]cty.Value{ + "string": cty.StringVal(""), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "string": cty.NullVal(cty.String), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "string": cty.StringVal(""), + }), + }, + { + // Neither plan nor apply should remove empty strings + Src: cty.ObjectVal(map[string]cty.Value{ + "string": cty.StringVal(""), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "string": cty.NullVal(cty.String), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "string": cty.StringVal(""), + }), + Apply: true, + }, + { + // The null map is retained, because the src was unknown + Src: cty.ObjectVal(map[string]cty.Value{ + "map": cty.UnknownVal(cty.Map(cty.String)), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "map": cty.NullVal(cty.Map(cty.String)), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "map": cty.NullVal(cty.Map(cty.String)), + }), + Apply: true, + }, + { + // the nul set is retained, because the src set contains an unknown value + Src: cty.ObjectVal(map[string]cty.Value{ + "set": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.UnknownVal(cty.String), + }), + }), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ + "foo": cty.String, + }))), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ + "foo": cty.String, + }))), + }), + Apply: true, + }, + { + // Retain don't re-add unexpected planned values in a map + Src: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + "b": cty.StringVal(""), + }), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + }), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + }), + }), + }, + { + // Remove extra values after apply + Src: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + "b": cty.StringVal("b"), + }), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + }), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + }), + }), + Apply: true, + }, + { + Src: cty.ObjectVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + }), + Dst: cty.EmptyObjectVal, + Expect: cty.ObjectVal(map[string]cty.Value{ + "a": cty.NullVal(cty.String), + }), + }, + + // a list in an object in a list, going from null to empty + { + Src: cty.ObjectVal(map[string]cty.Value{ + "network_interface": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "network_ip": cty.UnknownVal(cty.String), + "access_config": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String}))), + "address": cty.NullVal(cty.String), + "name": cty.StringVal("nic0"), + })}), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "network_interface": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "network_ip": cty.StringVal("10.128.0.64"), + "access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})), + "address": cty.StringVal("address"), + "name": cty.StringVal("nic0"), + }), + }), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "network_interface": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "network_ip": cty.StringVal("10.128.0.64"), + "access_config": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String}))), + "address": cty.StringVal("address"), + "name": cty.StringVal("nic0"), + }), + }), + }), + Apply: true, + }, + + // a list in an object in a list, going from empty to null + { + Src: cty.ObjectVal(map[string]cty.Value{ + "network_interface": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "network_ip": cty.UnknownVal(cty.String), + "access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})), + "address": cty.NullVal(cty.String), + "name": cty.StringVal("nic0"), + })}), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "network_interface": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "network_ip": cty.StringVal("10.128.0.64"), + "access_config": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String}))), + "address": cty.StringVal("address"), + "name": cty.StringVal("nic0"), + }), + }), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "network_interface": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "network_ip": cty.StringVal("10.128.0.64"), + "access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})), + "address": cty.StringVal("address"), + "name": cty.StringVal("nic0"), + }), + }), + }), + Apply: true, + }, + // the empty list should be transferred, but the new unknown should not be overridden + { + Src: cty.ObjectVal(map[string]cty.Value{ + "network_interface": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "network_ip": cty.StringVal("10.128.0.64"), + "access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})), + "address": cty.NullVal(cty.String), + "name": cty.StringVal("nic0"), + })}), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "network_interface": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "network_ip": cty.UnknownVal(cty.String), + "access_config": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String}))), + "address": cty.StringVal("address"), + "name": cty.StringVal("nic0"), + }), + }), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "network_interface": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "network_ip": cty.UnknownVal(cty.String), + "access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})), + "address": cty.StringVal("address"), + "name": cty.StringVal("nic0"), + }), + }), + }), + }, + { + // fix unknowns added to a map + Src: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + "b": cty.StringVal(""), + }), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + "b": cty.UnknownVal(cty.String), + }), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + "b": cty.StringVal(""), + }), + }), + }, + { + // fix unknowns lost from a list + Src: cty.ObjectVal(map[string]cty.Value{ + "top": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "list": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "values": cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}), + }), + }), + }), + }), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "top": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "list": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "values": cty.NullVal(cty.List(cty.String)), + }), + }), + }), + }), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "top": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "list": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "values": cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}), + }), + }), + }), + }), + }), + }, + { + Src: cty.ObjectVal(map[string]cty.Value{ + "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ + "list": cty.List(cty.String), + }))), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "set": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "list": cty.List(cty.String), + })), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "set": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "list": cty.List(cty.String), + })), + }), + }, + { + Src: cty.ObjectVal(map[string]cty.Value{ + "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ + "list": cty.List(cty.String), + }))), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "set": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "list": cty.List(cty.String), + })), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ + "list": cty.List(cty.String), + }))), + }), + Apply: true, + }, + } { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + got := normalizeNullValues(tc.Dst, tc.Src, tc.Apply) + if !got.RawEquals(tc.Expect) { + t.Fatalf("\nexpected: %#v\ngot: %#v\n", tc.Expect, got) + } + }) + } +} + +func TestValidateNulls(t *testing.T) { + for i, tc := range []struct { + Cfg cty.Value + Err bool + }{ + { + Cfg: cty.ObjectVal(map[string]cty.Value{ + "list": cty.ListVal([]cty.Value{ + cty.StringVal("string"), + cty.NullVal(cty.String), + }), + }), + Err: true, + }, + { + Cfg: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "string": cty.StringVal("string"), + "null": cty.NullVal(cty.String), + }), + }), + Err: false, + }, + { + Cfg: cty.ObjectVal(map[string]cty.Value{ + "object": cty.ObjectVal(map[string]cty.Value{ + "list": cty.ListVal([]cty.Value{ + cty.StringVal("string"), + cty.NullVal(cty.String), + }), + }), + }), + Err: true, + }, + { + Cfg: cty.ObjectVal(map[string]cty.Value{ + "object": cty.ObjectVal(map[string]cty.Value{ + "list": cty.ListVal([]cty.Value{ + cty.StringVal("string"), + cty.NullVal(cty.String), + }), + "list2": cty.ListVal([]cty.Value{ + cty.StringVal("string"), + cty.NullVal(cty.String), + }), + }), + }), + Err: true, + }, + { + Cfg: cty.ObjectVal(map[string]cty.Value{ + "object": cty.ObjectVal(map[string]cty.Value{ + "list": cty.SetVal([]cty.Value{ + cty.StringVal("string"), + cty.NullVal(cty.String), + }), + }), + }), + Err: true, + }, + } { + t.Run(strconv.Itoa(i), func(t *testing.T) { + d := validateConfigNulls(tc.Cfg, nil) + diags := convert.ProtoToDiags(d) + switch { + case tc.Err: + if !diags.HasError() { + t.Fatal("expected error") + } + default: + for _, d := range diags { + if d.Severity == diag.Error { + t.Fatalf("unexpected error: %q", d) + } + } + } + }) + } +} + +func TestStopContext_grpc(t *testing.T) { + r := &Resource{ + SchemaVersion: 4, + Schema: map[string]*Schema{ + "foo": { + Type: TypeInt, + Optional: true, + }, + }, + CreateContext: func(ctx context.Context, rd *ResourceData, _ interface{}) diag.Diagnostics { + <-ctx.Done() + rd.SetId("bar") + return nil + }, + } + + server := NewGRPCProviderServer(&Provider{ + ResourcesMap: map[string]*Resource{ + "test": r, + }, + }) + + schema := r.CoreConfigSchema() + priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + plannedVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + })) + if err != nil { + t.Fatal(err) + } + plannedState, err := msgpack.Marshal(plannedVal, schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + testReq := &proto.ApplyResourceChangeRequest{ + TypeName: "test", + PriorState: &proto.DynamicValue{ + MsgPack: priorState, + }, + PlannedState: &proto.DynamicValue{ + MsgPack: plannedState, + }, + } + ctx, cancel := context.WithCancel(context.Background()) + ctx = server.StopContext(ctx) + doneCh := make(chan struct{}) + go func() { + if _, err := server.ApplyResourceChange(ctx, testReq); err != nil { + t.Fatal(err) + } + close(doneCh) + }() + // GRPC request cancel + cancel() + select { + case <-doneCh: + case <-time.After(5 * time.Second): + t.Fatal("context cancel did not propagate") + } +} + +func TestStopContext_stop(t *testing.T) { + r := &Resource{ + SchemaVersion: 4, + Schema: map[string]*Schema{ + "foo": { + Type: TypeInt, + Optional: true, + }, + }, + CreateContext: func(ctx context.Context, rd *ResourceData, _ interface{}) diag.Diagnostics { + <-ctx.Done() + rd.SetId("bar") + return nil + }, + } + + server := NewGRPCProviderServer(&Provider{ + ResourcesMap: map[string]*Resource{ + "test": r, + }, + }) + + schema := r.CoreConfigSchema() + priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + plannedVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + })) + if err != nil { + t.Fatal(err) + } + plannedState, err := msgpack.Marshal(plannedVal, schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + testReq := &proto.ApplyResourceChangeRequest{ + TypeName: "test", + PriorState: &proto.DynamicValue{ + MsgPack: priorState, + }, + PlannedState: &proto.DynamicValue{ + MsgPack: plannedState, + }, + } + + ctx := server.StopContext(context.Background()) + doneCh := make(chan struct{}) + go func() { + if _, err := server.ApplyResourceChange(ctx, testReq); err != nil { + t.Fatal(err) + } + close(doneCh) + }() + server.StopProvider(context.Background(), &proto.StopProviderRequest{}) + select { + case <-doneCh: + case <-time.After(5 * time.Second): + t.Fatal("Stop message did not cancel request context") + } +} + +func TestStopContext_stopReset(t *testing.T) { + r := &Resource{ + SchemaVersion: 4, + Schema: map[string]*Schema{ + "foo": { + Type: TypeInt, + Optional: true, + }, + }, + CreateContext: func(ctx context.Context, rd *ResourceData, _ interface{}) diag.Diagnostics { + <-ctx.Done() + rd.SetId("bar") + return nil + }, + } + + server := NewGRPCProviderServer(&Provider{ + ResourcesMap: map[string]*Resource{ + "test": r, + }, + }) + + schema := r.CoreConfigSchema() + priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + plannedVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + })) + if err != nil { + t.Fatal(err) + } + plannedState, err := msgpack.Marshal(plannedVal, schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + testReq := &proto.ApplyResourceChangeRequest{ + TypeName: "test", + PriorState: &proto.DynamicValue{ + MsgPack: priorState, + }, + PlannedState: &proto.DynamicValue{ + MsgPack: plannedState, + }, + } + + // test first stop + ctx := server.StopContext(context.Background()) + if ctx.Err() != nil { + t.Fatal("StopContext does not produce a non-closed context") + } + doneCh := make(chan struct{}) + go func(d chan struct{}) { + if _, err := server.ApplyResourceChange(ctx, testReq); err != nil { + t.Fatal(err) + } + close(d) + }(doneCh) + server.StopProvider(context.Background(), &proto.StopProviderRequest{}) + select { + case <-doneCh: + case <-time.After(5 * time.Second): + t.Fatal("Stop message did not cancel request context") + } + + // test internal stop synchronization was reset + ctx = server.StopContext(context.Background()) + if ctx.Err() != nil { + t.Fatal("StopContext does not produce a non-closed context") + } + doneCh = make(chan struct{}) + go func(d chan struct{}) { + if _, err := server.ApplyResourceChange(ctx, testReq); err != nil { + t.Fatal(err) + } + close(d) + }(doneCh) + server.StopProvider(context.Background(), &proto.StopProviderRequest{}) + select { + case <-doneCh: + case <-time.After(5 * time.Second): + t.Fatal("Stop message did not cancel request context") + } +} diff --git a/helper/schema/provider.go b/helper/schema/provider.go index cb1264ee10b..25148e17c10 100644 --- a/helper/schema/provider.go +++ b/helper/schema/provider.go @@ -14,7 +14,6 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/internal/configs/configschema" - grpcpluginctx "github.com/hashicorp/terraform-plugin-sdk/v2/internal/helper/plugin/context" "github.com/hashicorp/terraform-plugin-sdk/v2/meta" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) @@ -34,7 +33,7 @@ var ReservedProviderFields = []string{ // Deprecated: The use of a global context is discouraged. Please use the new // context aware CRUD methods. func StopContext(ctx context.Context) (context.Context, bool) { - stopContext, ok := ctx.Value(grpcpluginctx.StopContextKey).(context.Context) + stopContext, ok := ctx.Value(StopContextKey).(context.Context) return stopContext, ok } diff --git a/internal/helper/plugin/unknown_test.go b/helper/schema/unknown_test.go similarity index 99% rename from internal/helper/plugin/unknown_test.go rename to helper/schema/unknown_test.go index 3aede448ab8..be279117fa3 100644 --- a/internal/helper/plugin/unknown_test.go +++ b/helper/schema/unknown_test.go @@ -1,4 +1,4 @@ -package plugin +package schema import ( "testing" diff --git a/internal/helper/plugin/doc.go b/internal/helper/plugin/doc.go deleted file mode 100644 index 82b5937bfe2..00000000000 --- a/internal/helper/plugin/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Package plugin contains types and functions to help Terraform plugins -// implement the plugin rpc interface. -// The primary Provider type will be responsible for converting from the grpc -// wire protocol to the types and methods known to the provider -// implementations. -package plugin diff --git a/internal/helper/plugin/unknown.go b/internal/helper/plugin/unknown.go deleted file mode 100644 index fcf156b2dad..00000000000 --- a/internal/helper/plugin/unknown.go +++ /dev/null @@ -1,132 +0,0 @@ -package plugin - -import ( - "fmt" - - "github.com/hashicorp/go-cty/cty" - - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/configs/configschema" -) - -// SetUnknowns takes a cty.Value, and compares it to the schema setting any null -// values which are computed to unknown. -func SetUnknowns(val cty.Value, schema *configschema.Block) cty.Value { - if !val.IsKnown() { - return val - } - - // If the object was null, we still need to handle the top level attributes - // which might be computed, but we don't need to expand the blocks. - if val.IsNull() { - objMap := map[string]cty.Value{} - allNull := true - for name, attr := range schema.Attributes { - switch { - case attr.Computed: - objMap[name] = cty.UnknownVal(attr.Type) - allNull = false - default: - objMap[name] = cty.NullVal(attr.Type) - } - } - - // If this object has no unknown attributes, then we can leave it null. - if allNull { - return val - } - - return cty.ObjectVal(objMap) - } - - valMap := val.AsValueMap() - newVals := make(map[string]cty.Value) - - for name, attr := range schema.Attributes { - v := valMap[name] - - if attr.Computed && v.IsNull() { - newVals[name] = cty.UnknownVal(attr.Type) - continue - } - - newVals[name] = v - } - - for name, blockS := range schema.BlockTypes { - blockVal := valMap[name] - if blockVal.IsNull() || !blockVal.IsKnown() { - newVals[name] = blockVal - continue - } - - blockValType := blockVal.Type() - blockElementType := blockS.Block.ImpliedType() - - // This switches on the value type here, so we can correctly switch - // between Tuples/Lists and Maps/Objects. - switch { - case blockS.Nesting == configschema.NestingSingle || blockS.Nesting == configschema.NestingGroup: - // NestingSingle is the only exception here, where we treat the - // block directly as an object - newVals[name] = SetUnknowns(blockVal, &blockS.Block) - - case blockValType.IsSetType(), blockValType.IsListType(), blockValType.IsTupleType(): - listVals := blockVal.AsValueSlice() - newListVals := make([]cty.Value, 0, len(listVals)) - - for _, v := range listVals { - newListVals = append(newListVals, SetUnknowns(v, &blockS.Block)) - } - - switch { - case blockValType.IsSetType(): - switch len(newListVals) { - case 0: - newVals[name] = cty.SetValEmpty(blockElementType) - default: - newVals[name] = cty.SetVal(newListVals) - } - case blockValType.IsListType(): - switch len(newListVals) { - case 0: - newVals[name] = cty.ListValEmpty(blockElementType) - default: - newVals[name] = cty.ListVal(newListVals) - } - case blockValType.IsTupleType(): - newVals[name] = cty.TupleVal(newListVals) - } - - case blockValType.IsMapType(), blockValType.IsObjectType(): - mapVals := blockVal.AsValueMap() - newMapVals := make(map[string]cty.Value) - - for k, v := range mapVals { - newMapVals[k] = SetUnknowns(v, &blockS.Block) - } - - switch { - case blockValType.IsMapType(): - switch len(newMapVals) { - case 0: - newVals[name] = cty.MapValEmpty(blockElementType) - default: - newVals[name] = cty.MapVal(newMapVals) - } - case blockValType.IsObjectType(): - if len(newMapVals) == 0 { - // We need to populate empty values to make a valid object. - for attr, ty := range blockElementType.AttributeTypes() { - newMapVals[attr] = cty.NullVal(ty) - } - } - newVals[name] = cty.ObjectVal(newMapVals) - } - - default: - panic(fmt.Sprintf("failed to set unknown values for nested block %q:%#v", name, blockValType)) - } - } - - return cty.ObjectVal(newVals) -}