diff --git a/.changes/unreleased/FEATURES-20250218-113243.yaml b/.changes/unreleased/FEATURES-20250218-113243.yaml new file mode 100644 index 0000000..fa474c8 --- /dev/null +++ b/.changes/unreleased/FEATURES-20250218-113243.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'tf5muxserver+tf6muxserver+tf6to5server+tf5to6server: Upgraded protocols and added types to support the new resource identity feature' +time: 2025-02-18T11:32:43.959465-05:00 +custom: + Issue: "278" diff --git a/.golangci.yml b/.golangci.yml index 0396bb9..7853804 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,13 +1,13 @@ issues: - max-per-linter: 0 + max-issues-per-linter: 0 max-same-issues: 0 linters: disable-all: true enable: + - copyloopvar - durationcheck - errcheck - - exportloopref - forcetypeassert - gofmt - gosimple @@ -19,7 +19,7 @@ linters: - paralleltest - predeclared - staticcheck - - tenv - unconvert - unparam - unused + - usetesting diff --git a/go.mod b/go.mod index add70f1..c368bf5 100644 --- a/go.mod +++ b/go.mod @@ -6,16 +6,16 @@ toolchain go1.22.7 require ( github.com/google/go-cmp v0.6.0 - github.com/hashicorp/terraform-plugin-go v0.26.0 + github.com/hashicorp/terraform-plugin-go v0.26.1-0.20250210164912-1d335b3d83c3 github.com/hashicorp/terraform-plugin-log v0.9.0 - google.golang.org/grpc v1.69.4 + google.golang.org/grpc v1.70.0 ) require ( github.com/fatih/color v1.13.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect - github.com/hashicorp/go-plugin v1.6.2 // indirect + github.com/hashicorp/go-plugin v1.6.3 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/terraform-registry-address v0.2.4 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect @@ -29,6 +29,6 @@ require ( golang.org/x/net v0.34.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect - google.golang.org/protobuf v1.36.3 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect + google.golang.org/protobuf v1.36.4 // indirect ) diff --git a/go.sum b/go.sum index 5a58652..7b16699 100644 --- a/go.sum +++ b/go.sum @@ -17,12 +17,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= -github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= +github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= +github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M= -github.com/hashicorp/terraform-plugin-go v0.26.0/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY= +github.com/hashicorp/terraform-plugin-go v0.26.1-0.20250210164912-1d335b3d83c3 h1:1mWCKXiCv8KChHk9mHJjJvhWHBQ+MFkHfowvVkiiQk0= +github.com/hashicorp/terraform-plugin-go v0.26.1-0.20250210164912-1d335b3d83c3/go.mod h1:6Ivn6mt9Ov7gMiYNNCRsQm4N0Kaq28gv3z/Q64qcBwI= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/hashicorp/terraform-registry-address v0.2.4 h1:JXu/zHB2Ymg/TGVCRu10XqNa4Sh2bWcqCNyKWjnCPJA= @@ -54,16 +54,16 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= -go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= -go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= -go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= -go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= -go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= -go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= -go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= -go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= -go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -76,12 +76,12 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= -google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= -google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= -google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= -google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/tf5testserver/tf5testserver.go b/internal/tf5testserver/tf5testserver.go index 83b11de..ee70718 100644 --- a/internal/tf5testserver/tf5testserver.go +++ b/internal/tf5testserver/tf5testserver.go @@ -32,6 +32,9 @@ type TestServer struct { GetProviderSchemaCalled bool GetProviderSchemaResponse *tfprotov5.GetProviderSchemaResponse + GetResourceIdentitySchemasCalled bool + GetResourceIdentitySchemasResponse *tfprotov5.GetResourceIdentitySchemasResponse + ImportResourceStateCalled map[string]bool MoveResourceStateCalled map[string]bool @@ -52,6 +55,8 @@ type TestServer struct { StopProviderCalled bool StopProviderResponse *tfprotov5.StopProviderResponse + UpgradeResourceIdentityCalled map[string]bool + UpgradeResourceStateCalled map[string]bool ValidateEphemeralResourceConfigCalled map[string]bool @@ -136,6 +141,16 @@ func (s *TestServer) GetProviderSchema(_ context.Context, _ *tfprotov5.GetProvid return &tfprotov5.GetProviderSchemaResponse{}, nil } +func (s *TestServer) GetResourceIdentitySchemas(_ context.Context, _ *tfprotov5.GetResourceIdentitySchemasRequest) (*tfprotov5.GetResourceIdentitySchemasResponse, error) { + s.GetResourceIdentitySchemasCalled = true + + if s.GetResourceIdentitySchemasResponse != nil { + return s.GetResourceIdentitySchemasResponse, nil + } + + return &tfprotov5.GetResourceIdentitySchemasResponse{}, nil +} + func (s *TestServer) ImportResourceState(_ context.Context, req *tfprotov5.ImportResourceStateRequest) (*tfprotov5.ImportResourceStateResponse, error) { if s.ImportResourceStateCalled == nil { s.ImportResourceStateCalled = make(map[string]bool) @@ -209,6 +224,15 @@ func (s *TestServer) StopProvider(_ context.Context, _ *tfprotov5.StopProviderRe return &tfprotov5.StopProviderResponse{}, nil } +func (s *TestServer) UpgradeResourceIdentity(_ context.Context, req *tfprotov5.UpgradeResourceIdentityRequest) (*tfprotov5.UpgradeResourceIdentityResponse, error) { + if s.UpgradeResourceIdentityCalled == nil { + s.UpgradeResourceIdentityCalled = make(map[string]bool) + } + + s.UpgradeResourceIdentityCalled[req.TypeName] = true + return nil, nil +} + func (s *TestServer) UpgradeResourceState(_ context.Context, req *tfprotov5.UpgradeResourceStateRequest) (*tfprotov5.UpgradeResourceStateResponse, error) { if s.UpgradeResourceStateCalled == nil { s.UpgradeResourceStateCalled = make(map[string]bool) diff --git a/internal/tf6testserver/tf6testserver.go b/internal/tf6testserver/tf6testserver.go index 3c605f0..c908588 100644 --- a/internal/tf6testserver/tf6testserver.go +++ b/internal/tf6testserver/tf6testserver.go @@ -32,6 +32,9 @@ type TestServer struct { GetProviderSchemaCalled bool GetProviderSchemaResponse *tfprotov6.GetProviderSchemaResponse + GetResourceIdentitySchemasCalled bool + GetResourceIdentitySchemasResponse *tfprotov6.GetResourceIdentitySchemasResponse + ImportResourceStateCalled map[string]bool MoveResourceStateCalled map[string]bool @@ -49,6 +52,8 @@ type TestServer struct { StopProviderCalled bool StopProviderResponse *tfprotov6.StopProviderResponse + UpgradeResourceIdentityCalled map[string]bool + UpgradeResourceStateCalled map[string]bool ValidateDataResourceConfigCalled map[string]bool @@ -136,6 +141,16 @@ func (s *TestServer) GetProviderSchema(_ context.Context, _ *tfprotov6.GetProvid return &tfprotov6.GetProviderSchemaResponse{}, nil } +func (s *TestServer) GetResourceIdentitySchemas(_ context.Context, _ *tfprotov6.GetResourceIdentitySchemasRequest) (*tfprotov6.GetResourceIdentitySchemasResponse, error) { + s.GetResourceIdentitySchemasCalled = true + + if s.GetResourceIdentitySchemasResponse != nil { + return s.GetResourceIdentitySchemasResponse, nil + } + + return &tfprotov6.GetResourceIdentitySchemasResponse{}, nil +} + func (s *TestServer) ImportResourceState(_ context.Context, req *tfprotov6.ImportResourceStateRequest) (*tfprotov6.ImportResourceStateResponse, error) { if s.ImportResourceStateCalled == nil { s.ImportResourceStateCalled = make(map[string]bool) @@ -209,6 +224,15 @@ func (s *TestServer) StopProvider(_ context.Context, _ *tfprotov6.StopProviderRe return &tfprotov6.StopProviderResponse{}, nil } +func (s *TestServer) UpgradeResourceIdentity(_ context.Context, req *tfprotov6.UpgradeResourceIdentityRequest) (*tfprotov6.UpgradeResourceIdentityResponse, error) { + if s.UpgradeResourceIdentityCalled == nil { + s.UpgradeResourceIdentityCalled = make(map[string]bool) + } + + s.UpgradeResourceIdentityCalled[req.TypeName] = true + return nil, nil +} + func (s *TestServer) UpgradeResourceState(_ context.Context, req *tfprotov6.UpgradeResourceStateRequest) (*tfprotov6.UpgradeResourceStateResponse, error) { if s.UpgradeResourceStateCalled == nil { s.UpgradeResourceStateCalled = make(map[string]bool) diff --git a/internal/tfprotov5tov6/tfprotov5tov6.go b/internal/tfprotov5tov6/tfprotov5tov6.go index 12f1478..ab313b0 100644 --- a/internal/tfprotov5tov6/tfprotov5tov6.go +++ b/internal/tfprotov5tov6/tfprotov5tov6.go @@ -14,12 +14,13 @@ func ApplyResourceChangeRequest(in *tfprotov5.ApplyResourceChangeRequest) *tfpro } return &tfprotov6.ApplyResourceChangeRequest{ - Config: DynamicValue(in.Config), - PlannedPrivate: in.PlannedPrivate, - PlannedState: DynamicValue(in.PlannedState), - PriorState: DynamicValue(in.PriorState), - ProviderMeta: DynamicValue(in.ProviderMeta), - TypeName: in.TypeName, + Config: DynamicValue(in.Config), + PlannedPrivate: in.PlannedPrivate, + PlannedState: DynamicValue(in.PlannedState), + PriorState: DynamicValue(in.PriorState), + ProviderMeta: DynamicValue(in.ProviderMeta), + TypeName: in.TypeName, + PlannedIdentity: ResourceIdentityData(in.PlannedIdentity), } } @@ -33,6 +34,7 @@ func ApplyResourceChangeResponse(in *tfprotov5.ApplyResourceChangeResponse) *tfp NewState: DynamicValue(in.NewState), Private: in.Private, UnsafeToUseLegacyTypeSystem: in.UnsafeToUseLegacyTypeSystem, //nolint:staticcheck + NewIdentity: ResourceIdentityData(in.NewIdentity), } } @@ -172,6 +174,16 @@ func DynamicValue(in *tfprotov5.DynamicValue) *tfprotov6.DynamicValue { } } +func ResourceIdentityData(in *tfprotov5.ResourceIdentityData) *tfprotov6.ResourceIdentityData { + if in == nil { + return nil + } + + return &tfprotov6.ResourceIdentityData{ + IdentityData: DynamicValue(in.IdentityData), + } +} + func EphemeralResourceMetadata(in tfprotov5.EphemeralResourceMetadata) tfprotov6.EphemeralResourceMetadata { return tfprotov6.EphemeralResourceMetadata{ TypeName: in.TypeName, @@ -359,6 +371,31 @@ func GetProviderSchemaResponse(in *tfprotov5.GetProviderSchemaResponse) *tfproto } } +func GetResourceIdentitySchemasRequest(in *tfprotov5.GetResourceIdentitySchemasRequest) *tfprotov6.GetResourceIdentitySchemasRequest { + if in == nil { + return nil + } + + return &tfprotov6.GetResourceIdentitySchemasRequest{} +} + +func GetResourceIdentitySchemasResponse(in *tfprotov5.GetResourceIdentitySchemasResponse) *tfprotov6.GetResourceIdentitySchemasResponse { + if in == nil { + return nil + } + + identitySchemas := make(map[string]*tfprotov6.ResourceIdentitySchema, len(in.IdentitySchemas)) + + for k, v := range in.IdentitySchemas { + identitySchemas[k] = ResourceIdentitySchema(v) + } + + return &tfprotov6.GetResourceIdentitySchemasResponse{ + Diagnostics: Diagnostics(in.Diagnostics), + IdentitySchemas: identitySchemas, + } +} + func ImportResourceStateRequest(in *tfprotov5.ImportResourceStateRequest) *tfprotov6.ImportResourceStateRequest { if in == nil { return nil @@ -368,6 +405,7 @@ func ImportResourceStateRequest(in *tfprotov5.ImportResourceStateRequest) *tfpro ClientCapabilities: ImportResourceStateClientCapabilities(in.ClientCapabilities), ID: in.ID, TypeName: in.TypeName, + Identity: ResourceIdentityData(in.Identity), } } @@ -412,6 +450,7 @@ func ImportedResources(in []*tfprotov5.ImportedResource) []*tfprotov6.ImportedRe Private: imp.Private, State: DynamicValue(imp.State), TypeName: imp.TypeName, + Identity: ResourceIdentityData(imp.Identity), }) } @@ -430,6 +469,7 @@ func MoveResourceStateRequest(in *tfprotov5.MoveResourceStateRequest) *tfprotov6 SourceState: RawState(in.SourceState), SourceTypeName: in.SourceTypeName, TargetTypeName: in.TargetTypeName, + SourceIdentity: ResourceIdentityData(in.SourceIdentity), } } @@ -439,9 +479,10 @@ func MoveResourceStateResponse(in *tfprotov5.MoveResourceStateResponse) *tfproto } return &tfprotov6.MoveResourceStateResponse{ - Diagnostics: Diagnostics(in.Diagnostics), - TargetPrivate: in.TargetPrivate, - TargetState: DynamicValue(in.TargetState), + Diagnostics: Diagnostics(in.Diagnostics), + TargetPrivate: in.TargetPrivate, + TargetState: DynamicValue(in.TargetState), + TargetIdentity: ResourceIdentityData(in.TargetIdentity), } } @@ -496,6 +537,7 @@ func PlanResourceChangeRequest(in *tfprotov5.PlanResourceChangeRequest) *tfproto ProposedNewState: DynamicValue(in.ProposedNewState), ProviderMeta: DynamicValue(in.ProviderMeta), TypeName: in.TypeName, + PriorIdentity: ResourceIdentityData(in.PriorIdentity), } } @@ -523,6 +565,7 @@ func PlanResourceChangeResponse(in *tfprotov5.PlanResourceChangeResponse) *tfpro PlannedState: DynamicValue(in.PlannedState), RequiresReplace: in.RequiresReplace, UnsafeToUseLegacyTypeSystem: in.UnsafeToUseLegacyTypeSystem, //nolint:staticcheck + PlannedIdentity: ResourceIdentityData(in.PlannedIdentity), } } @@ -537,6 +580,16 @@ func RawState(in *tfprotov5.RawState) *tfprotov6.RawState { } } +func RawIdentity(in *tfprotov5.RawIdentity) *tfprotov6.RawIdentity { + if in == nil { + return nil + } + + return &tfprotov6.RawIdentity{ + JSON: in.JSON, + } +} + func ReadDataSourceRequest(in *tfprotov5.ReadDataSourceRequest) *tfprotov6.ReadDataSourceRequest { if in == nil { return nil @@ -584,6 +637,7 @@ func ReadResourceRequest(in *tfprotov5.ReadResourceRequest) *tfprotov6.ReadResou Private: in.Private, ProviderMeta: DynamicValue(in.ProviderMeta), TypeName: in.TypeName, + CurrentIdentity: ResourceIdentityData(in.CurrentIdentity), } } @@ -609,6 +663,7 @@ func ReadResourceResponse(in *tfprotov5.ReadResourceResponse) *tfprotov6.ReadRes Diagnostics: Diagnostics(in.Diagnostics), NewState: DynamicValue(in.NewState), Private: in.Private, + NewIdentity: ResourceIdentityData(in.NewIdentity), } } @@ -720,6 +775,41 @@ func SchemaNestedBlock(in *tfprotov5.SchemaNestedBlock) *tfprotov6.SchemaNestedB } } +func ResourceIdentitySchema(in *tfprotov5.ResourceIdentitySchema) *tfprotov6.ResourceIdentitySchema { + if in == nil { + return nil + } + + var attrs []*tfprotov6.ResourceIdentitySchemaAttribute + + if in.IdentityAttributes != nil { + attrs = make([]*tfprotov6.ResourceIdentitySchemaAttribute, 0, len(in.IdentityAttributes)) + + for _, attr := range in.IdentityAttributes { + attrs = append(attrs, ResourceIdentitySchemaAttribute(attr)) + } + } + + return &tfprotov6.ResourceIdentitySchema{ + Version: in.Version, + IdentityAttributes: attrs, + } +} + +func ResourceIdentitySchemaAttribute(in *tfprotov5.ResourceIdentitySchemaAttribute) *tfprotov6.ResourceIdentitySchemaAttribute { + if in == nil { + return nil + } + + return &tfprotov6.ResourceIdentitySchemaAttribute{ + Name: in.Name, + Type: in.Type, + RequiredForImport: in.RequiredForImport, + OptionalForImport: in.OptionalForImport, + Description: in.Description, + } +} + func ServerCapabilities(in *tfprotov5.ServerCapabilities) *tfprotov6.ServerCapabilities { if in == nil { return nil @@ -777,6 +867,29 @@ func UpgradeResourceStateResponse(in *tfprotov5.UpgradeResourceStateResponse) *t } } +func UpgradeResourceIdentityRequest(in *tfprotov5.UpgradeResourceIdentityRequest) *tfprotov6.UpgradeResourceIdentityRequest { + if in == nil { + return nil + } + + return &tfprotov6.UpgradeResourceIdentityRequest{ + TypeName: in.TypeName, + Version: in.Version, + RawIdentity: RawIdentity(in.RawIdentity), + } +} + +func UpgradeResourceIdentityResponse(in *tfprotov5.UpgradeResourceIdentityResponse) *tfprotov6.UpgradeResourceIdentityResponse { + if in == nil { + return nil + } + + return &tfprotov6.UpgradeResourceIdentityResponse{ + Diagnostics: Diagnostics(in.Diagnostics), + UpgradedIdentity: ResourceIdentityData(in.UpgradedIdentity), + } +} + func ValidateEphemeralResourceConfigRequest(in *tfprotov5.ValidateEphemeralResourceConfigRequest) *tfprotov6.ValidateEphemeralResourceConfigRequest { if in == nil { return nil diff --git a/internal/tfprotov5tov6/tfprotov5tov6_test.go b/internal/tfprotov5tov6/tfprotov5tov6_test.go index 71b2993..6bd36e8 100644 --- a/internal/tfprotov5tov6/tfprotov5tov6_test.go +++ b/internal/tfprotov5tov6/tfprotov5tov6_test.go @@ -44,6 +44,9 @@ var ( testTfprotov5DynamicValue tfprotov5.DynamicValue testTfprotov6DynamicValue tfprotov6.DynamicValue + testTfprotov5ResourceIdentityData tfprotov5.ResourceIdentityData + testTfprotov6ResourceIdentityData tfprotov6.ResourceIdentityData + testTfprotov5EphemeralResourceMetadata tfprotov5.EphemeralResourceMetadata = tfprotov5.EphemeralResourceMetadata{ TypeName: "test_ephemeral_resource", } @@ -117,12 +120,53 @@ var ( Version: 1, } + testTfprotov5ResourceIdentitySchema *tfprotov5.ResourceIdentitySchema = &tfprotov5.ResourceIdentitySchema{ + Version: 1, + IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{ + { + Name: "req", + Type: tftypes.String, + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + Type: tftypes.String, + OptionalForImport: true, + Description: "this one's optional", + }, + }, + } + testTfprotov6ResourceIdentitySchema *tfprotov6.ResourceIdentitySchema = &tfprotov6.ResourceIdentitySchema{ + Version: 1, + IdentityAttributes: []*tfprotov6.ResourceIdentitySchemaAttribute{ + { + Name: "req", + Type: tftypes.String, + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + Type: tftypes.String, + OptionalForImport: true, + Description: "this one's optional", + }, + }, + } + testTime time.Time = time.Date(2000, 1, 2, 3, 4, 5, 6, time.UTC) ) func init() { testTfprotov5DynamicValue, _ = tfprotov5.NewDynamicValue(tftypes.String, tftypes.NewValue(tftypes.String, "test")) testTfprotov6DynamicValue, _ = tfprotov6.NewDynamicValue(tftypes.String, tftypes.NewValue(tftypes.String, "test")) + testTfprotov5ResourceIdentityData = tfprotov5.ResourceIdentityData{ + IdentityData: &testTfprotov5DynamicValue, + } + testTfprotov6ResourceIdentityData = tfprotov6.ResourceIdentityData{ + IdentityData: &testTfprotov6DynamicValue, + } } func TestApplyResourceChangeRequest(t *testing.T) { @@ -138,26 +182,27 @@ func TestApplyResourceChangeRequest(t *testing.T) { }, "all-valid-fields": { in: &tfprotov5.ApplyResourceChangeRequest{ - Config: &testTfprotov5DynamicValue, - PlannedPrivate: testBytes, - PlannedState: &testTfprotov5DynamicValue, - PriorState: &testTfprotov5DynamicValue, - ProviderMeta: &testTfprotov5DynamicValue, - TypeName: "test", + Config: &testTfprotov5DynamicValue, + PlannedPrivate: testBytes, + PlannedState: &testTfprotov5DynamicValue, + PriorState: &testTfprotov5DynamicValue, + ProviderMeta: &testTfprotov5DynamicValue, + TypeName: "test", + PlannedIdentity: &testTfprotov5ResourceIdentityData, }, expected: &tfprotov6.ApplyResourceChangeRequest{ - Config: &testTfprotov6DynamicValue, - PlannedPrivate: testBytes, - PlannedState: &testTfprotov6DynamicValue, - PriorState: &testTfprotov6DynamicValue, - ProviderMeta: &testTfprotov6DynamicValue, - TypeName: "test", + Config: &testTfprotov6DynamicValue, + PlannedPrivate: testBytes, + PlannedState: &testTfprotov6DynamicValue, + PriorState: &testTfprotov6DynamicValue, + ProviderMeta: &testTfprotov6DynamicValue, + TypeName: "test", + PlannedIdentity: &testTfprotov6ResourceIdentityData, }, }, } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -188,18 +233,19 @@ func TestApplyResourceChangeResponse(t *testing.T) { NewState: &testTfprotov5DynamicValue, Private: testBytes, UnsafeToUseLegacyTypeSystem: true, + NewIdentity: &testTfprotov5ResourceIdentityData, }, expected: &tfprotov6.ApplyResourceChangeResponse{ Diagnostics: testTfprotov6Diagnostics, NewState: &testTfprotov6DynamicValue, Private: testBytes, UnsafeToUseLegacyTypeSystem: true, + NewIdentity: &testTfprotov6ResourceIdentityData, }, }, } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -241,7 +287,6 @@ func TestCallFunctionRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -279,7 +324,6 @@ func TestCallFunctionResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -317,7 +361,6 @@ func TestCloseEphemeralResourceRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -353,7 +396,6 @@ func TestCloseEphemeralResourceResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -407,7 +449,6 @@ func TestConfigureProviderRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -443,7 +484,6 @@ func TestConfigureProviderResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -505,7 +545,6 @@ func TestDiagnostics(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -543,7 +582,6 @@ func TestDynamicValue(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -557,6 +595,41 @@ func TestDynamicValue(t *testing.T) { } } +func TestResourceIdentityData(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov5.ResourceIdentityData + expected *tfprotov6.ResourceIdentityData + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov5.ResourceIdentityData{ + IdentityData: &testTfprotov5DynamicValue, + }, + expected: &tfprotov6.ResourceIdentityData{ + IdentityData: &testTfprotov6DynamicValue, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov5tov6.ResourceIdentityData(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + func TestFunction(t *testing.T) { t.Parallel() @@ -607,7 +680,6 @@ func TestFunction(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -639,7 +711,6 @@ func TestFunctionMetadata(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -679,7 +750,6 @@ func TestFunctionParameter(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -715,7 +785,6 @@ func TestFunctionReturn(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -747,7 +816,6 @@ func TestGetFunctionsRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -789,7 +857,6 @@ func TestGetFunctionsResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -821,7 +888,6 @@ func TestGetMetadataRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -881,7 +947,6 @@ func TestGetMetadataResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -913,7 +978,6 @@ func TestGetProviderSchemaRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -977,7 +1041,6 @@ func TestGetProviderSchemaResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -991,6 +1054,78 @@ func TestGetProviderSchemaResponse(t *testing.T) { } } +func TestGetResourceIdentitySchemasRequest(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov5.GetResourceIdentitySchemasRequest + expected *tfprotov6.GetResourceIdentitySchemasRequest + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov5.GetResourceIdentitySchemasRequest{}, + expected: &tfprotov6.GetResourceIdentitySchemasRequest{}, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov5tov6.GetResourceIdentitySchemasRequest(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestGetResourceIdentitySchemasResponse(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov5.GetResourceIdentitySchemasResponse + expected *tfprotov6.GetResourceIdentitySchemasResponse + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov5.GetResourceIdentitySchemasResponse{ + Diagnostics: testTfprotov5Diagnostics, + IdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{ + "test_resource": testTfprotov5ResourceIdentitySchema, + }, + }, + expected: &tfprotov6.GetResourceIdentitySchemasResponse{ + Diagnostics: testTfprotov6Diagnostics, + IdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{ + "test_resource": testTfprotov6ResourceIdentitySchema, + }, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov5tov6.GetResourceIdentitySchemasResponse(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + func TestImportResourceStateRequest(t *testing.T) { t.Parallel() @@ -1006,10 +1141,12 @@ func TestImportResourceStateRequest(t *testing.T) { in: &tfprotov5.ImportResourceStateRequest{ ID: "test-id", TypeName: "test_resource", + Identity: &testTfprotov5ResourceIdentityData, }, expected: &tfprotov6.ImportResourceStateRequest{ ID: "test-id", TypeName: "test_resource", + Identity: &testTfprotov6ResourceIdentityData, }, }, "client-capabilities-deferral-allowed": { @@ -1031,7 +1168,6 @@ func TestImportResourceStateRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1064,6 +1200,7 @@ func TestImportResourceStateResponse(t *testing.T) { Private: testBytes, State: &testTfprotov5DynamicValue, TypeName: "test_resource1", + Identity: &testTfprotov5ResourceIdentityData, }, }, }, @@ -1074,6 +1211,7 @@ func TestImportResourceStateResponse(t *testing.T) { Private: testBytes, State: &testTfprotov6DynamicValue, TypeName: "test_resource1", + Identity: &testTfprotov6ResourceIdentityData, }, }, }, @@ -1109,7 +1247,6 @@ func TestImportResourceStateResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1144,6 +1281,7 @@ func TestImportedResources(t *testing.T) { Private: testBytes, State: &testTfprotov5DynamicValue, TypeName: "test_resource1", + Identity: &testTfprotov5ResourceIdentityData, }, }, expected: []*tfprotov6.ImportedResource{ @@ -1151,6 +1289,7 @@ func TestImportedResources(t *testing.T) { Private: testBytes, State: &testTfprotov6DynamicValue, TypeName: "test_resource1", + Identity: &testTfprotov6ResourceIdentityData, }, }, }, @@ -1184,7 +1323,6 @@ func TestImportedResources(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1219,6 +1357,7 @@ func TestMoveResourceStateRequest(t *testing.T) { }, SourceTypeName: "test_source", TargetTypeName: "test_target", + SourceIdentity: &testTfprotov5ResourceIdentityData, }, expected: &tfprotov6.MoveResourceStateRequest{ SourcePrivate: testBytes, @@ -1229,12 +1368,12 @@ func TestMoveResourceStateRequest(t *testing.T) { }, SourceTypeName: "test_source", TargetTypeName: "test_target", + SourceIdentity: &testTfprotov6ResourceIdentityData, }, }, } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1261,20 +1400,21 @@ func TestMoveResourceStateResponse(t *testing.T) { }, "all-valid-fields": { in: &tfprotov5.MoveResourceStateResponse{ - Diagnostics: testTfprotov5Diagnostics, - TargetPrivate: testBytes, - TargetState: &testTfprotov5DynamicValue, + Diagnostics: testTfprotov5Diagnostics, + TargetPrivate: testBytes, + TargetState: &testTfprotov5DynamicValue, + TargetIdentity: &testTfprotov5ResourceIdentityData, }, expected: &tfprotov6.MoveResourceStateResponse{ - Diagnostics: testTfprotov6Diagnostics, - TargetState: &testTfprotov6DynamicValue, - TargetPrivate: testBytes, + Diagnostics: testTfprotov6Diagnostics, + TargetState: &testTfprotov6DynamicValue, + TargetPrivate: testBytes, + TargetIdentity: &testTfprotov6ResourceIdentityData, }, }, } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1328,7 +1468,6 @@ func TestOpenEphemeralResourceRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1386,7 +1525,6 @@ func TestOpenEphemeralResourceResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1419,6 +1557,7 @@ func TestPlanResourceChangeRequest(t *testing.T) { ProposedNewState: &testTfprotov5DynamicValue, ProviderMeta: &testTfprotov5DynamicValue, TypeName: "test_resource", + PriorIdentity: &testTfprotov5ResourceIdentityData, }, expected: &tfprotov6.PlanResourceChangeRequest{ Config: &testTfprotov6DynamicValue, @@ -1427,6 +1566,7 @@ func TestPlanResourceChangeRequest(t *testing.T) { ProposedNewState: &testTfprotov6DynamicValue, ProviderMeta: &testTfprotov6DynamicValue, TypeName: "test_resource", + PriorIdentity: &testTfprotov6ResourceIdentityData, }, }, "client-capabilities-deferral-allowed": { @@ -1437,6 +1577,7 @@ func TestPlanResourceChangeRequest(t *testing.T) { ProposedNewState: &testTfprotov5DynamicValue, ProviderMeta: &testTfprotov5DynamicValue, TypeName: "test_resource", + PriorIdentity: &testTfprotov5ResourceIdentityData, ClientCapabilities: &tfprotov5.PlanResourceChangeClientCapabilities{ DeferralAllowed: true, }, @@ -1448,6 +1589,7 @@ func TestPlanResourceChangeRequest(t *testing.T) { ProposedNewState: &testTfprotov6DynamicValue, ProviderMeta: &testTfprotov6DynamicValue, TypeName: "test_resource", + PriorIdentity: &testTfprotov6ResourceIdentityData, ClientCapabilities: &tfprotov6.PlanResourceChangeClientCapabilities{ DeferralAllowed: true, }, @@ -1456,7 +1598,6 @@ func TestPlanResourceChangeRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1490,6 +1631,7 @@ func TestPlanResourceChangeResponse(t *testing.T) { tftypes.NewAttributePath().WithAttributeName("test"), }, UnsafeToUseLegacyTypeSystem: true, + PlannedIdentity: &testTfprotov5ResourceIdentityData, }, expected: &tfprotov6.PlanResourceChangeResponse{ Diagnostics: testTfprotov6Diagnostics, @@ -1499,6 +1641,7 @@ func TestPlanResourceChangeResponse(t *testing.T) { tftypes.NewAttributePath().WithAttributeName("test"), }, UnsafeToUseLegacyTypeSystem: true, + PlannedIdentity: &testTfprotov6ResourceIdentityData, }, }, "deferred-reason": { @@ -1510,6 +1653,7 @@ func TestPlanResourceChangeResponse(t *testing.T) { tftypes.NewAttributePath().WithAttributeName("test"), }, UnsafeToUseLegacyTypeSystem: true, + PlannedIdentity: &testTfprotov5ResourceIdentityData, Deferred: &tfprotov5.Deferred{ Reason: tfprotov5.DeferredReasonResourceConfigUnknown, }, @@ -1522,6 +1666,7 @@ func TestPlanResourceChangeResponse(t *testing.T) { tftypes.NewAttributePath().WithAttributeName("test"), }, UnsafeToUseLegacyTypeSystem: true, + PlannedIdentity: &testTfprotov6ResourceIdentityData, Deferred: &tfprotov6.Deferred{ Reason: tfprotov6.DeferredReasonResourceConfigUnknown, }, @@ -1530,7 +1675,6 @@ func TestPlanResourceChangeResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1568,7 +1712,6 @@ func TestRawState(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1582,6 +1725,41 @@ func TestRawState(t *testing.T) { } } +func TestRawIdentity(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov5.RawIdentity + expected *tfprotov6.RawIdentity + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov5.RawIdentity{ + JSON: testBytes, + }, + expected: &tfprotov6.RawIdentity{ + JSON: testBytes, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov5tov6.RawIdentity(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + func TestReadDataSourceRequest(t *testing.T) { t.Parallel() @@ -1626,7 +1804,6 @@ func TestReadDataSourceRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1680,7 +1857,6 @@ func TestReadDataSourceResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1707,33 +1883,37 @@ func TestReadResourceRequest(t *testing.T) { }, "all-valid-fields": { in: &tfprotov5.ReadResourceRequest{ - CurrentState: &testTfprotov5DynamicValue, - Private: testBytes, - ProviderMeta: &testTfprotov5DynamicValue, - TypeName: "test_resource", + CurrentState: &testTfprotov5DynamicValue, + Private: testBytes, + ProviderMeta: &testTfprotov5DynamicValue, + TypeName: "test_resource", + CurrentIdentity: &testTfprotov5ResourceIdentityData, }, expected: &tfprotov6.ReadResourceRequest{ - CurrentState: &testTfprotov6DynamicValue, - Private: testBytes, - ProviderMeta: &testTfprotov6DynamicValue, - TypeName: "test_resource", + CurrentState: &testTfprotov6DynamicValue, + Private: testBytes, + ProviderMeta: &testTfprotov6DynamicValue, + TypeName: "test_resource", + CurrentIdentity: &testTfprotov6ResourceIdentityData, }, }, "client-capabilities-deferral-allowed": { in: &tfprotov5.ReadResourceRequest{ - CurrentState: &testTfprotov5DynamicValue, - Private: testBytes, - ProviderMeta: &testTfprotov5DynamicValue, - TypeName: "test_resource", + CurrentState: &testTfprotov5DynamicValue, + Private: testBytes, + ProviderMeta: &testTfprotov5DynamicValue, + TypeName: "test_resource", + CurrentIdentity: &testTfprotov5ResourceIdentityData, ClientCapabilities: &tfprotov5.ReadResourceClientCapabilities{ DeferralAllowed: true, }, }, expected: &tfprotov6.ReadResourceRequest{ - CurrentState: &testTfprotov6DynamicValue, - Private: testBytes, - ProviderMeta: &testTfprotov6DynamicValue, - TypeName: "test_resource", + CurrentState: &testTfprotov6DynamicValue, + Private: testBytes, + ProviderMeta: &testTfprotov6DynamicValue, + TypeName: "test_resource", + CurrentIdentity: &testTfprotov6ResourceIdentityData, ClientCapabilities: &tfprotov6.ReadResourceClientCapabilities{ DeferralAllowed: true, }, @@ -1742,7 +1922,6 @@ func TestReadResourceRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1772,11 +1951,13 @@ func TestReadResourceResponse(t *testing.T) { Diagnostics: testTfprotov5Diagnostics, NewState: &testTfprotov5DynamicValue, Private: testBytes, + NewIdentity: &testTfprotov5ResourceIdentityData, }, expected: &tfprotov6.ReadResourceResponse{ Diagnostics: testTfprotov6Diagnostics, NewState: &testTfprotov6DynamicValue, Private: testBytes, + NewIdentity: &testTfprotov6ResourceIdentityData, }, }, "deferred-reason": { @@ -1784,6 +1965,7 @@ func TestReadResourceResponse(t *testing.T) { Diagnostics: testTfprotov5Diagnostics, NewState: &testTfprotov5DynamicValue, Private: testBytes, + NewIdentity: &testTfprotov5ResourceIdentityData, Deferred: &tfprotov5.Deferred{ Reason: tfprotov5.DeferredReasonResourceConfigUnknown, }, @@ -1792,6 +1974,7 @@ func TestReadResourceResponse(t *testing.T) { Diagnostics: testTfprotov6Diagnostics, NewState: &testTfprotov6DynamicValue, Private: testBytes, + NewIdentity: &testTfprotov6ResourceIdentityData, Deferred: &tfprotov6.Deferred{ Reason: tfprotov6.DeferredReasonResourceConfigUnknown, }, @@ -1800,7 +1983,6 @@ func TestReadResourceResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1838,7 +2020,6 @@ func TestRenewEphemeralResourceRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1878,7 +2059,6 @@ func TestRenewEphemeralResourceResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1910,7 +2090,6 @@ func TestSchema(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1964,7 +2143,6 @@ func TestSchemaAttribute(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2044,7 +2222,6 @@ func TestSchemaBlock(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2102,7 +2279,6 @@ func TestSchemaNestedBlock(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2116,6 +2292,80 @@ func TestSchemaNestedBlock(t *testing.T) { } } +func TestResourceIdentitySchema(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov5.ResourceIdentitySchema + expected *tfprotov6.ResourceIdentitySchema + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: testTfprotov5ResourceIdentitySchema, + expected: testTfprotov6ResourceIdentitySchema, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov5tov6.ResourceIdentitySchema(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestResourceIdentitySchemaAttribute(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov5.ResourceIdentitySchemaAttribute + expected *tfprotov6.ResourceIdentitySchemaAttribute + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov5.ResourceIdentitySchemaAttribute{ + Name: "test", + Description: "test description", + Type: tftypes.String, + RequiredForImport: true, + OptionalForImport: true, + }, + expected: &tfprotov6.ResourceIdentitySchemaAttribute{ + Name: "test", + Description: "test description", + Type: tftypes.String, + RequiredForImport: true, + OptionalForImport: true, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov5tov6.ResourceIdentitySchemaAttribute(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + func TestStopProviderRequest(t *testing.T) { t.Parallel() @@ -2134,7 +2384,6 @@ func TestStopProviderRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2170,7 +2419,6 @@ func TestStopProviderResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2202,7 +2450,6 @@ func TestStringKind(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2246,7 +2493,6 @@ func TestUpgradeResourceStateRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2284,7 +2530,6 @@ func TestUpgradeResourceStateResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2298,6 +2543,86 @@ func TestUpgradeResourceStateResponse(t *testing.T) { } } +func TestUpgradeResourceIdentityRequest(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov5.UpgradeResourceIdentityRequest + expected *tfprotov6.UpgradeResourceIdentityRequest + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov5.UpgradeResourceIdentityRequest{ + RawIdentity: &tfprotov5.RawIdentity{ + JSON: testBytes, + }, + TypeName: "test_resource", + Version: 1, + }, + expected: &tfprotov6.UpgradeResourceIdentityRequest{ + RawIdentity: &tfprotov6.RawIdentity{ + JSON: testBytes, + }, + TypeName: "test_resource", + Version: 1, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov5tov6.UpgradeResourceIdentityRequest(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestUpgradeResourceIdentityResponse(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov5.UpgradeResourceIdentityResponse + expected *tfprotov6.UpgradeResourceIdentityResponse + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov5.UpgradeResourceIdentityResponse{ + Diagnostics: testTfprotov5Diagnostics, + UpgradedIdentity: &testTfprotov5ResourceIdentityData, + }, + expected: &tfprotov6.UpgradeResourceIdentityResponse{ + Diagnostics: testTfprotov6Diagnostics, + UpgradedIdentity: &testTfprotov6ResourceIdentityData, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov5tov6.UpgradeResourceIdentityResponse(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + func TestValidateDataResourceConfigRequest(t *testing.T) { t.Parallel() @@ -2322,7 +2647,6 @@ func TestValidateDataResourceConfigRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2358,7 +2682,6 @@ func TestValidateDataResourceConfigResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2396,7 +2719,6 @@ func TestValidateEphemeralResourceConfigRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2432,7 +2754,6 @@ func TestValidateEphemeralResourceConfigResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2468,7 +2789,6 @@ func TestValidateProviderConfigRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2506,7 +2826,6 @@ func TestValidateProviderConfigResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2560,7 +2879,6 @@ func TestValidateResourceConfigRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2596,7 +2914,6 @@ func TestValidateResourceConfigResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/internal/tfprotov6tov5/tfprotov6tov5.go b/internal/tfprotov6tov5/tfprotov6tov5.go index 5c082bf..6ad7417 100644 --- a/internal/tfprotov6tov5/tfprotov6tov5.go +++ b/internal/tfprotov6tov5/tfprotov6tov5.go @@ -19,12 +19,13 @@ func ApplyResourceChangeRequest(in *tfprotov6.ApplyResourceChangeRequest) *tfpro } return &tfprotov5.ApplyResourceChangeRequest{ - Config: DynamicValue(in.Config), - PlannedPrivate: in.PlannedPrivate, - PlannedState: DynamicValue(in.PlannedState), - PriorState: DynamicValue(in.PriorState), - ProviderMeta: DynamicValue(in.ProviderMeta), - TypeName: in.TypeName, + Config: DynamicValue(in.Config), + PlannedPrivate: in.PlannedPrivate, + PlannedState: DynamicValue(in.PlannedState), + PriorState: DynamicValue(in.PriorState), + ProviderMeta: DynamicValue(in.ProviderMeta), + TypeName: in.TypeName, + PlannedIdentity: ResourceIdentityData(in.PlannedIdentity), } } @@ -38,6 +39,7 @@ func ApplyResourceChangeResponse(in *tfprotov6.ApplyResourceChangeResponse) *tfp NewState: DynamicValue(in.NewState), Private: in.Private, UnsafeToUseLegacyTypeSystem: in.UnsafeToUseLegacyTypeSystem, //nolint:staticcheck + NewIdentity: ResourceIdentityData(in.NewIdentity), } } @@ -177,6 +179,16 @@ func DynamicValue(in *tfprotov6.DynamicValue) *tfprotov5.DynamicValue { } } +func ResourceIdentityData(in *tfprotov6.ResourceIdentityData) *tfprotov5.ResourceIdentityData { + if in == nil { + return nil + } + + return &tfprotov5.ResourceIdentityData{ + IdentityData: DynamicValue(in.IdentityData), + } +} + func EphemeralResourceMetadata(in tfprotov6.EphemeralResourceMetadata) tfprotov5.EphemeralResourceMetadata { return tfprotov5.EphemeralResourceMetadata{ TypeName: in.TypeName, @@ -393,6 +405,31 @@ func GetProviderSchemaResponse(in *tfprotov6.GetProviderSchemaResponse) (*tfprot }, nil } +func GetResourceIdentitySchemasRequest(in *tfprotov6.GetResourceIdentitySchemasRequest) *tfprotov5.GetResourceIdentitySchemasRequest { + if in == nil { + return nil + } + + return &tfprotov5.GetResourceIdentitySchemasRequest{} +} + +func GetResourceIdentitySchemasResponse(in *tfprotov6.GetResourceIdentitySchemasResponse) *tfprotov5.GetResourceIdentitySchemasResponse { + if in == nil { + return nil + } + + identitySchemas := make(map[string]*tfprotov5.ResourceIdentitySchema, len(in.IdentitySchemas)) + + for k, v := range in.IdentitySchemas { + identitySchemas[k] = ResourceIdentitySchema(v) + } + + return &tfprotov5.GetResourceIdentitySchemasResponse{ + Diagnostics: Diagnostics(in.Diagnostics), + IdentitySchemas: identitySchemas, + } +} + func ImportResourceStateRequest(in *tfprotov6.ImportResourceStateRequest) *tfprotov5.ImportResourceStateRequest { if in == nil { return nil @@ -402,6 +439,7 @@ func ImportResourceStateRequest(in *tfprotov6.ImportResourceStateRequest) *tfpro ClientCapabilities: ImportResourceStateClientCapabilities(in.ClientCapabilities), ID: in.ID, TypeName: in.TypeName, + Identity: ResourceIdentityData(in.Identity), } } @@ -446,6 +484,7 @@ func ImportedResources(in []*tfprotov6.ImportedResource) []*tfprotov5.ImportedRe Private: imp.Private, State: DynamicValue(imp.State), TypeName: imp.TypeName, + Identity: ResourceIdentityData(imp.Identity), }) } @@ -464,6 +503,7 @@ func MoveResourceStateRequest(in *tfprotov6.MoveResourceStateRequest) *tfprotov5 SourceState: RawState(in.SourceState), SourceTypeName: in.SourceTypeName, TargetTypeName: in.TargetTypeName, + SourceIdentity: ResourceIdentityData(in.SourceIdentity), } } @@ -473,9 +513,10 @@ func MoveResourceStateResponse(in *tfprotov6.MoveResourceStateResponse) *tfproto } return &tfprotov5.MoveResourceStateResponse{ - Diagnostics: Diagnostics(in.Diagnostics), - TargetPrivate: in.TargetPrivate, - TargetState: DynamicValue(in.TargetState), + Diagnostics: Diagnostics(in.Diagnostics), + TargetPrivate: in.TargetPrivate, + TargetState: DynamicValue(in.TargetState), + TargetIdentity: ResourceIdentityData(in.TargetIdentity), } } @@ -529,6 +570,7 @@ func PlanResourceChangeRequest(in *tfprotov6.PlanResourceChangeRequest) *tfproto ProposedNewState: DynamicValue(in.ProposedNewState), ProviderMeta: DynamicValue(in.ProviderMeta), TypeName: in.TypeName, + PriorIdentity: ResourceIdentityData(in.PriorIdentity), } } @@ -556,6 +598,7 @@ func PlanResourceChangeResponse(in *tfprotov6.PlanResourceChangeResponse) *tfpro PlannedState: DynamicValue(in.PlannedState), RequiresReplace: in.RequiresReplace, UnsafeToUseLegacyTypeSystem: in.UnsafeToUseLegacyTypeSystem, //nolint:staticcheck + PlannedIdentity: ResourceIdentityData(in.PlannedIdentity), } } @@ -591,6 +634,16 @@ func RawState(in *tfprotov6.RawState) *tfprotov5.RawState { } } +func RawIdentity(in *tfprotov6.RawIdentity) *tfprotov5.RawIdentity { + if in == nil { + return nil + } + + return &tfprotov5.RawIdentity{ + JSON: in.JSON, + } +} + func ReadDataSourceRequest(in *tfprotov6.ReadDataSourceRequest) *tfprotov5.ReadDataSourceRequest { if in == nil { return nil @@ -638,6 +691,7 @@ func ReadResourceRequest(in *tfprotov6.ReadResourceRequest) *tfprotov5.ReadResou Private: in.Private, ProviderMeta: DynamicValue(in.ProviderMeta), TypeName: in.TypeName, + CurrentIdentity: ResourceIdentityData(in.CurrentIdentity), } } @@ -663,6 +717,7 @@ func ReadResourceResponse(in *tfprotov6.ReadResourceResponse) *tfprotov5.ReadRes Diagnostics: Diagnostics(in.Diagnostics), NewState: DynamicValue(in.NewState), Private: in.Private, + NewIdentity: ResourceIdentityData(in.NewIdentity), } } @@ -802,6 +857,41 @@ func SchemaNestedBlock(in *tfprotov6.SchemaNestedBlock) (*tfprotov5.SchemaNested }, nil } +func ResourceIdentitySchema(in *tfprotov6.ResourceIdentitySchema) *tfprotov5.ResourceIdentitySchema { + if in == nil { + return nil + } + + var attrs []*tfprotov5.ResourceIdentitySchemaAttribute + + if in.IdentityAttributes != nil { + attrs = make([]*tfprotov5.ResourceIdentitySchemaAttribute, 0, len(in.IdentityAttributes)) + + for _, attr := range in.IdentityAttributes { + attrs = append(attrs, ResourceIdentitySchemaAttribute(attr)) + } + } + + return &tfprotov5.ResourceIdentitySchema{ + Version: in.Version, + IdentityAttributes: attrs, + } +} + +func ResourceIdentitySchemaAttribute(in *tfprotov6.ResourceIdentitySchemaAttribute) *tfprotov5.ResourceIdentitySchemaAttribute { + if in == nil { + return nil + } + + return &tfprotov5.ResourceIdentitySchemaAttribute{ + Name: in.Name, + Type: in.Type, + RequiredForImport: in.RequiredForImport, + OptionalForImport: in.OptionalForImport, + Description: in.Description, + } +} + func ServerCapabilities(in *tfprotov6.ServerCapabilities) *tfprotov5.ServerCapabilities { if in == nil { return nil @@ -880,6 +970,29 @@ func ValidateDataSourceConfigResponse(in *tfprotov6.ValidateDataResourceConfigRe } } +func UpgradeResourceIdentityRequest(in *tfprotov6.UpgradeResourceIdentityRequest) *tfprotov5.UpgradeResourceIdentityRequest { + if in == nil { + return nil + } + + return &tfprotov5.UpgradeResourceIdentityRequest{ + TypeName: in.TypeName, + Version: in.Version, + RawIdentity: RawIdentity(in.RawIdentity), + } +} + +func UpgradeResourceIdentityResponse(in *tfprotov6.UpgradeResourceIdentityResponse) *tfprotov5.UpgradeResourceIdentityResponse { + if in == nil { + return nil + } + + return &tfprotov5.UpgradeResourceIdentityResponse{ + Diagnostics: Diagnostics(in.Diagnostics), + UpgradedIdentity: ResourceIdentityData(in.UpgradedIdentity), + } +} + func ValidateEphemeralResourceConfigRequest(in *tfprotov6.ValidateEphemeralResourceConfigRequest) *tfprotov5.ValidateEphemeralResourceConfigRequest { if in == nil { return nil diff --git a/internal/tfprotov6tov5/tfprotov6tov5_test.go b/internal/tfprotov6tov5/tfprotov6tov5_test.go index 84b0eae..a1e5e3b 100644 --- a/internal/tfprotov6tov5/tfprotov6tov5_test.go +++ b/internal/tfprotov6tov5/tfprotov6tov5_test.go @@ -46,6 +46,9 @@ var ( testTfprotov5DynamicValue tfprotov5.DynamicValue testTfprotov6DynamicValue tfprotov6.DynamicValue + testTfprotov5ResourceIdentityData tfprotov5.ResourceIdentityData + testTfprotov6ResourceIdentityData tfprotov6.ResourceIdentityData + testTfprotov5EphemeralResourceMetadata tfprotov5.EphemeralResourceMetadata = tfprotov5.EphemeralResourceMetadata{ TypeName: "test_ephemeral_resource", } @@ -119,12 +122,53 @@ var ( Version: 1, } + testTfprotov5ResourceIdentitySchema *tfprotov5.ResourceIdentitySchema = &tfprotov5.ResourceIdentitySchema{ + Version: 1, + IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{ + { + Name: "req", + Type: tftypes.String, + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + Type: tftypes.String, + OptionalForImport: true, + Description: "this one's optional", + }, + }, + } + testTfprotov6ResourceIdentitySchema *tfprotov6.ResourceIdentitySchema = &tfprotov6.ResourceIdentitySchema{ + Version: 1, + IdentityAttributes: []*tfprotov6.ResourceIdentitySchemaAttribute{ + { + Name: "req", + Type: tftypes.String, + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + Type: tftypes.String, + OptionalForImport: true, + Description: "this one's optional", + }, + }, + } + testTime time.Time = time.Date(2000, 1, 2, 3, 4, 5, 6, time.UTC) ) func init() { testTfprotov5DynamicValue, _ = tfprotov5.NewDynamicValue(tftypes.String, tftypes.NewValue(tftypes.String, "test")) testTfprotov6DynamicValue, _ = tfprotov6.NewDynamicValue(tftypes.String, tftypes.NewValue(tftypes.String, "test")) + testTfprotov5ResourceIdentityData = tfprotov5.ResourceIdentityData{ + IdentityData: &testTfprotov5DynamicValue, + } + testTfprotov6ResourceIdentityData = tfprotov6.ResourceIdentityData{ + IdentityData: &testTfprotov6DynamicValue, + } } func TestApplyResourceChangeRequest(t *testing.T) { @@ -140,26 +184,27 @@ func TestApplyResourceChangeRequest(t *testing.T) { }, "all-valid-fields": { in: &tfprotov6.ApplyResourceChangeRequest{ - Config: &testTfprotov6DynamicValue, - PlannedPrivate: testBytes, - PlannedState: &testTfprotov6DynamicValue, - PriorState: &testTfprotov6DynamicValue, - ProviderMeta: &testTfprotov6DynamicValue, - TypeName: "test", + Config: &testTfprotov6DynamicValue, + PlannedPrivate: testBytes, + PlannedState: &testTfprotov6DynamicValue, + PriorState: &testTfprotov6DynamicValue, + ProviderMeta: &testTfprotov6DynamicValue, + TypeName: "test", + PlannedIdentity: &testTfprotov6ResourceIdentityData, }, expected: &tfprotov5.ApplyResourceChangeRequest{ - Config: &testTfprotov5DynamicValue, - PlannedPrivate: testBytes, - PlannedState: &testTfprotov5DynamicValue, - PriorState: &testTfprotov5DynamicValue, - ProviderMeta: &testTfprotov5DynamicValue, - TypeName: "test", + Config: &testTfprotov5DynamicValue, + PlannedPrivate: testBytes, + PlannedState: &testTfprotov5DynamicValue, + PriorState: &testTfprotov5DynamicValue, + ProviderMeta: &testTfprotov5DynamicValue, + TypeName: "test", + PlannedIdentity: &testTfprotov5ResourceIdentityData, }, }, } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -190,18 +235,19 @@ func TestApplyResourceChangeResponse(t *testing.T) { NewState: &testTfprotov6DynamicValue, Private: testBytes, UnsafeToUseLegacyTypeSystem: true, + NewIdentity: &testTfprotov6ResourceIdentityData, }, expected: &tfprotov5.ApplyResourceChangeResponse{ Diagnostics: testTfprotov5Diagnostics, NewState: &testTfprotov5DynamicValue, Private: testBytes, UnsafeToUseLegacyTypeSystem: true, + NewIdentity: &testTfprotov5ResourceIdentityData, }, }, } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -243,7 +289,6 @@ func TestCallFunctionRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -281,7 +326,6 @@ func TestCallFunctionResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -319,7 +363,6 @@ func TestCloseEphemeralResourceRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -355,7 +398,6 @@ func TestCloseEphemeralResourceResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -409,7 +451,6 @@ func TestConfigureProviderRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -445,7 +486,6 @@ func TestConfigureProviderResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -507,7 +547,6 @@ func TestDiagnostics(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -545,7 +584,6 @@ func TestDynamicValue(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -559,6 +597,41 @@ func TestDynamicValue(t *testing.T) { } } +func TestResourceIdentityData(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov6.ResourceIdentityData + expected *tfprotov5.ResourceIdentityData + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov6.ResourceIdentityData{ + IdentityData: &testTfprotov6DynamicValue, + }, + expected: &tfprotov5.ResourceIdentityData{ + IdentityData: &testTfprotov5DynamicValue, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov6tov5.ResourceIdentityData(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + func TestFunction(t *testing.T) { t.Parallel() @@ -609,7 +682,6 @@ func TestFunction(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -641,7 +713,6 @@ func TestFunctionMetadata(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -681,7 +752,6 @@ func TestFunctionParameter(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -717,7 +787,6 @@ func TestFunctionReturn(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -749,7 +818,6 @@ func TestGetFunctionsRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -791,7 +859,6 @@ func TestGetFunctionsResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -823,7 +890,6 @@ func TestGetMetadataRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -883,7 +949,6 @@ func TestGetMetadataResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -915,7 +980,6 @@ func TestGetProviderSchemaRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1106,7 +1170,6 @@ func TestGetProviderSchemaResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1132,6 +1195,78 @@ func TestGetProviderSchemaResponse(t *testing.T) { } } +func TestGetResourceIdentitySchemasRequest(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov6.GetResourceIdentitySchemasRequest + expected *tfprotov5.GetResourceIdentitySchemasRequest + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov6.GetResourceIdentitySchemasRequest{}, + expected: &tfprotov5.GetResourceIdentitySchemasRequest{}, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov6tov5.GetResourceIdentitySchemasRequest(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestGetResourceIdentitySchemasResponse(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov6.GetResourceIdentitySchemasResponse + expected *tfprotov5.GetResourceIdentitySchemasResponse + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov6.GetResourceIdentitySchemasResponse{ + Diagnostics: testTfprotov6Diagnostics, + IdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{ + "test_resource": testTfprotov6ResourceIdentitySchema, + }, + }, + expected: &tfprotov5.GetResourceIdentitySchemasResponse{ + Diagnostics: testTfprotov5Diagnostics, + IdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{ + "test_resource": testTfprotov5ResourceIdentitySchema, + }, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov6tov5.GetResourceIdentitySchemasResponse(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + func TestImportResourceStateRequest(t *testing.T) { t.Parallel() @@ -1147,10 +1282,12 @@ func TestImportResourceStateRequest(t *testing.T) { in: &tfprotov6.ImportResourceStateRequest{ ID: "test-id", TypeName: "test_resource", + Identity: &testTfprotov6ResourceIdentityData, }, expected: &tfprotov5.ImportResourceStateRequest{ ID: "test-id", TypeName: "test_resource", + Identity: &testTfprotov5ResourceIdentityData, }, }, "client-capabilities-deferral-allowed": { @@ -1172,7 +1309,6 @@ func TestImportResourceStateRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1205,6 +1341,7 @@ func TestImportResourceStateResponse(t *testing.T) { Private: testBytes, State: &testTfprotov6DynamicValue, TypeName: "test_resource1", + Identity: &testTfprotov6ResourceIdentityData, }, }, }, @@ -1215,6 +1352,7 @@ func TestImportResourceStateResponse(t *testing.T) { Private: testBytes, State: &testTfprotov5DynamicValue, TypeName: "test_resource1", + Identity: &testTfprotov5ResourceIdentityData, }, }, }, @@ -1250,7 +1388,6 @@ func TestImportResourceStateResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1285,6 +1422,7 @@ func TestImportedResources(t *testing.T) { Private: testBytes, State: &testTfprotov6DynamicValue, TypeName: "test_resource1", + Identity: &testTfprotov6ResourceIdentityData, }, }, expected: []*tfprotov5.ImportedResource{ @@ -1292,6 +1430,7 @@ func TestImportedResources(t *testing.T) { Private: testBytes, State: &testTfprotov5DynamicValue, TypeName: "test_resource1", + Identity: &testTfprotov5ResourceIdentityData, }, }, }, @@ -1325,7 +1464,6 @@ func TestImportedResources(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1360,6 +1498,7 @@ func TestMoveResourceStateRequest(t *testing.T) { }, SourceTypeName: "test_source", TargetTypeName: "test_target", + SourceIdentity: &testTfprotov6ResourceIdentityData, }, expected: &tfprotov5.MoveResourceStateRequest{ SourcePrivate: testBytes, @@ -1370,12 +1509,12 @@ func TestMoveResourceStateRequest(t *testing.T) { }, SourceTypeName: "test_source", TargetTypeName: "test_target", + SourceIdentity: &testTfprotov5ResourceIdentityData, }, }, } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1402,20 +1541,21 @@ func TestMoveResourceStateResponse(t *testing.T) { }, "all-valid-fields": { in: &tfprotov6.MoveResourceStateResponse{ - Diagnostics: testTfprotov6Diagnostics, - TargetPrivate: testBytes, - TargetState: &testTfprotov6DynamicValue, + Diagnostics: testTfprotov6Diagnostics, + TargetPrivate: testBytes, + TargetState: &testTfprotov6DynamicValue, + TargetIdentity: &testTfprotov6ResourceIdentityData, }, expected: &tfprotov5.MoveResourceStateResponse{ - Diagnostics: testTfprotov5Diagnostics, - TargetState: &testTfprotov5DynamicValue, - TargetPrivate: testBytes, + Diagnostics: testTfprotov5Diagnostics, + TargetState: &testTfprotov5DynamicValue, + TargetPrivate: testBytes, + TargetIdentity: &testTfprotov5ResourceIdentityData, }, }, } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1469,7 +1609,6 @@ func TestOpenEphemeralResourceRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1527,7 +1666,6 @@ func TestOpenEphemeralResourceResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1560,6 +1698,7 @@ func TestPlanResourceChangeRequest(t *testing.T) { ProposedNewState: &testTfprotov6DynamicValue, ProviderMeta: &testTfprotov6DynamicValue, TypeName: "test_resource", + PriorIdentity: &testTfprotov6ResourceIdentityData, }, expected: &tfprotov5.PlanResourceChangeRequest{ Config: &testTfprotov5DynamicValue, @@ -1568,6 +1707,7 @@ func TestPlanResourceChangeRequest(t *testing.T) { ProposedNewState: &testTfprotov5DynamicValue, ProviderMeta: &testTfprotov5DynamicValue, TypeName: "test_resource", + PriorIdentity: &testTfprotov5ResourceIdentityData, }, }, "client-capabilities-deferral-allowed": { @@ -1578,6 +1718,7 @@ func TestPlanResourceChangeRequest(t *testing.T) { ProposedNewState: &testTfprotov6DynamicValue, ProviderMeta: &testTfprotov6DynamicValue, TypeName: "test_resource", + PriorIdentity: &testTfprotov6ResourceIdentityData, ClientCapabilities: &tfprotov6.PlanResourceChangeClientCapabilities{ DeferralAllowed: true, }, @@ -1589,6 +1730,7 @@ func TestPlanResourceChangeRequest(t *testing.T) { ProposedNewState: &testTfprotov5DynamicValue, ProviderMeta: &testTfprotov5DynamicValue, TypeName: "test_resource", + PriorIdentity: &testTfprotov5ResourceIdentityData, ClientCapabilities: &tfprotov5.PlanResourceChangeClientCapabilities{ DeferralAllowed: true, }, @@ -1597,7 +1739,6 @@ func TestPlanResourceChangeRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1631,6 +1772,7 @@ func TestPlanResourceChangeResponse(t *testing.T) { tftypes.NewAttributePath().WithAttributeName("test"), }, UnsafeToUseLegacyTypeSystem: true, + PlannedIdentity: &testTfprotov6ResourceIdentityData, }, expected: &tfprotov5.PlanResourceChangeResponse{ Diagnostics: testTfprotov5Diagnostics, @@ -1640,6 +1782,7 @@ func TestPlanResourceChangeResponse(t *testing.T) { tftypes.NewAttributePath().WithAttributeName("test"), }, UnsafeToUseLegacyTypeSystem: true, + PlannedIdentity: &testTfprotov5ResourceIdentityData, }, }, "deferred-reason": { @@ -1651,6 +1794,7 @@ func TestPlanResourceChangeResponse(t *testing.T) { tftypes.NewAttributePath().WithAttributeName("test"), }, UnsafeToUseLegacyTypeSystem: true, + PlannedIdentity: &testTfprotov6ResourceIdentityData, Deferred: &tfprotov6.Deferred{ Reason: tfprotov6.DeferredReasonResourceConfigUnknown, }, @@ -1663,6 +1807,7 @@ func TestPlanResourceChangeResponse(t *testing.T) { tftypes.NewAttributePath().WithAttributeName("test"), }, UnsafeToUseLegacyTypeSystem: true, + PlannedIdentity: &testTfprotov5ResourceIdentityData, Deferred: &tfprotov5.Deferred{ Reason: tfprotov5.DeferredReasonResourceConfigUnknown, }, @@ -1671,7 +1816,6 @@ func TestPlanResourceChangeResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1707,7 +1851,6 @@ func TestPrepareProviderConfigRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1745,7 +1888,6 @@ func TestPrepareProviderConfigResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1783,7 +1925,6 @@ func TestRawState(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1797,6 +1938,41 @@ func TestRawState(t *testing.T) { } } +func TestRawIdentity(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov6.RawIdentity + expected *tfprotov5.RawIdentity + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov6.RawIdentity{ + JSON: testBytes, + }, + expected: &tfprotov5.RawIdentity{ + JSON: testBytes, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov6tov5.RawIdentity(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + func TestReadDataSourceRequest(t *testing.T) { t.Parallel() @@ -1841,7 +2017,6 @@ func TestReadDataSourceRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1895,7 +2070,6 @@ func TestReadDataSourceResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1922,33 +2096,37 @@ func TestReadResourceRequest(t *testing.T) { }, "all-valid-fields": { in: &tfprotov6.ReadResourceRequest{ - CurrentState: &testTfprotov6DynamicValue, - Private: testBytes, - ProviderMeta: &testTfprotov6DynamicValue, - TypeName: "test_resource", + CurrentState: &testTfprotov6DynamicValue, + Private: testBytes, + ProviderMeta: &testTfprotov6DynamicValue, + TypeName: "test_resource", + CurrentIdentity: &testTfprotov6ResourceIdentityData, }, expected: &tfprotov5.ReadResourceRequest{ - CurrentState: &testTfprotov5DynamicValue, - Private: testBytes, - ProviderMeta: &testTfprotov5DynamicValue, - TypeName: "test_resource", + CurrentState: &testTfprotov5DynamicValue, + Private: testBytes, + ProviderMeta: &testTfprotov5DynamicValue, + TypeName: "test_resource", + CurrentIdentity: &testTfprotov5ResourceIdentityData, }, }, "client-capabilities-deferral-allowed": { in: &tfprotov6.ReadResourceRequest{ - CurrentState: &testTfprotov6DynamicValue, - Private: testBytes, - ProviderMeta: &testTfprotov6DynamicValue, - TypeName: "test_resource", + CurrentState: &testTfprotov6DynamicValue, + Private: testBytes, + ProviderMeta: &testTfprotov6DynamicValue, + TypeName: "test_resource", + CurrentIdentity: &testTfprotov6ResourceIdentityData, ClientCapabilities: &tfprotov6.ReadResourceClientCapabilities{ DeferralAllowed: true, }, }, expected: &tfprotov5.ReadResourceRequest{ - CurrentState: &testTfprotov5DynamicValue, - Private: testBytes, - ProviderMeta: &testTfprotov5DynamicValue, - TypeName: "test_resource", + CurrentState: &testTfprotov5DynamicValue, + Private: testBytes, + ProviderMeta: &testTfprotov5DynamicValue, + TypeName: "test_resource", + CurrentIdentity: &testTfprotov5ResourceIdentityData, ClientCapabilities: &tfprotov5.ReadResourceClientCapabilities{ DeferralAllowed: true, }, @@ -1957,7 +2135,6 @@ func TestReadResourceRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1987,11 +2164,13 @@ func TestReadResourceResponse(t *testing.T) { Diagnostics: testTfprotov6Diagnostics, NewState: &testTfprotov6DynamicValue, Private: testBytes, + NewIdentity: &testTfprotov6ResourceIdentityData, }, expected: &tfprotov5.ReadResourceResponse{ Diagnostics: testTfprotov5Diagnostics, NewState: &testTfprotov5DynamicValue, Private: testBytes, + NewIdentity: &testTfprotov5ResourceIdentityData, }, }, "deferred-reason": { @@ -1999,6 +2178,7 @@ func TestReadResourceResponse(t *testing.T) { Diagnostics: testTfprotov6Diagnostics, NewState: &testTfprotov6DynamicValue, Private: testBytes, + NewIdentity: &testTfprotov6ResourceIdentityData, Deferred: &tfprotov6.Deferred{ Reason: tfprotov6.DeferredReasonResourceConfigUnknown, }, @@ -2007,6 +2187,7 @@ func TestReadResourceResponse(t *testing.T) { Diagnostics: testTfprotov5Diagnostics, NewState: &testTfprotov5DynamicValue, Private: testBytes, + NewIdentity: &testTfprotov5ResourceIdentityData, Deferred: &tfprotov5.Deferred{ Reason: tfprotov5.DeferredReasonResourceConfigUnknown, }, @@ -2015,7 +2196,6 @@ func TestReadResourceResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2053,7 +2233,6 @@ func TestRenewEphemeralResourceRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2093,7 +2272,6 @@ func TestRenewEphemeralResourceResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2148,7 +2326,6 @@ func TestSchema(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2231,7 +2408,6 @@ func TestSchemaAttribute(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2344,7 +2520,6 @@ func TestSchemaBlock(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2439,7 +2614,6 @@ func TestSchemaNestedBlock(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2465,6 +2639,80 @@ func TestSchemaNestedBlock(t *testing.T) { } } +func TestResourceIdentitySchema(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov6.ResourceIdentitySchema + expected *tfprotov5.ResourceIdentitySchema + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: testTfprotov6ResourceIdentitySchema, + expected: testTfprotov5ResourceIdentitySchema, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov6tov5.ResourceIdentitySchema(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestResourceIdentitySchemaAttribute(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov6.ResourceIdentitySchemaAttribute + expected *tfprotov5.ResourceIdentitySchemaAttribute + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov6.ResourceIdentitySchemaAttribute{ + Name: "test", + Description: "test description", + Type: tftypes.String, + RequiredForImport: true, + OptionalForImport: true, + }, + expected: &tfprotov5.ResourceIdentitySchemaAttribute{ + Name: "test", + Description: "test description", + Type: tftypes.String, + RequiredForImport: true, + OptionalForImport: true, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov6tov5.ResourceIdentitySchemaAttribute(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + func TestStopProviderRequest(t *testing.T) { t.Parallel() @@ -2483,7 +2731,6 @@ func TestStopProviderRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2519,7 +2766,6 @@ func TestStopProviderResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2551,7 +2797,6 @@ func TestStringKind(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2595,7 +2840,6 @@ func TestUpgradeResourceStateRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2633,7 +2877,6 @@ func TestUpgradeResourceStateResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2647,6 +2890,86 @@ func TestUpgradeResourceStateResponse(t *testing.T) { } } +func TestUpgradeResourceIdentityRequest(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov6.UpgradeResourceIdentityRequest + expected *tfprotov5.UpgradeResourceIdentityRequest + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov6.UpgradeResourceIdentityRequest{ + RawIdentity: &tfprotov6.RawIdentity{ + JSON: testBytes, + }, + TypeName: "test_resource", + Version: 1, + }, + expected: &tfprotov5.UpgradeResourceIdentityRequest{ + RawIdentity: &tfprotov5.RawIdentity{ + JSON: testBytes, + }, + TypeName: "test_resource", + Version: 1, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov6tov5.UpgradeResourceIdentityRequest(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestUpgradeResourceIdentityResponse(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov6.UpgradeResourceIdentityResponse + expected *tfprotov5.UpgradeResourceIdentityResponse + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov6.UpgradeResourceIdentityResponse{ + Diagnostics: testTfprotov6Diagnostics, + UpgradedIdentity: &testTfprotov6ResourceIdentityData, + }, + expected: &tfprotov5.UpgradeResourceIdentityResponse{ + Diagnostics: testTfprotov5Diagnostics, + UpgradedIdentity: &testTfprotov5ResourceIdentityData, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov6tov5.UpgradeResourceIdentityResponse(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + func TestValidateDataSourceConfigRequest(t *testing.T) { t.Parallel() @@ -2671,7 +2994,6 @@ func TestValidateDataSourceConfigRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2707,7 +3029,6 @@ func TestValidateDataSourceConfigResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2745,7 +3066,6 @@ func TestValidateEphemeralResourceConfigRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2781,7 +3101,6 @@ func TestValidateEphemeralResourceConfigResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2835,7 +3154,6 @@ func TestValidateResourceTypeConfigRequest(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -2871,7 +3189,6 @@ func TestValidateResourceTypeConfigResponse(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/tf5muxserver/diagnostics.go b/tf5muxserver/diagnostics.go index e0761ff..014a738 100644 --- a/tf5muxserver/diagnostics.go +++ b/tf5muxserver/diagnostics.go @@ -102,3 +102,14 @@ func resourceMissingError(typeName string) *tfprotov5.Diagnostic { "Missing resource type: " + typeName, } } + +func resourceIdentityDuplicateError(typeName string) *tfprotov5.Diagnostic { + return &tfprotov5.Diagnostic{ + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Invalid Provider Server Combination", + Detail: "The combined provider has multiple implementations of the same resource identity across underlying providers. " + + "Resource identity types must be implemented by only one underlying provider. " + + "This is always an issue in the provider implementation and should be reported to the provider developers.\n\n" + + "Duplicate identity type for resource: " + typeName, + } +} diff --git a/tf5muxserver/mux_server.go b/tf5muxserver/mux_server.go index ba23971..1253bd9 100644 --- a/tf5muxserver/mux_server.go +++ b/tf5muxserver/mux_server.go @@ -349,6 +349,7 @@ func (s *muxServer) serverDiscovery(ctx context.Context) error { // - Only one provider implements each data source // - Only one provider implements each function // - Only one provider implements each ephemeral resource +// - Only one provider implements each resource identity func NewMuxServer(_ context.Context, servers ...func() tfprotov5.ProviderServer) (*muxServer, error) { result := muxServer{ dataSources: make(map[string]tfprotov5.ProviderServer), diff --git a/tf5muxserver/mux_server_GetFunctions_test.go b/tf5muxserver/mux_server_GetFunctions_test.go index 0c65873..f164d0b 100644 --- a/tf5muxserver/mux_server_GetFunctions_test.go +++ b/tf5muxserver/mux_server_GetFunctions_test.go @@ -300,7 +300,6 @@ func TestMuxServerGetFunctions(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/tf5muxserver/mux_server_GetMetadata_test.go b/tf5muxserver/mux_server_GetMetadata_test.go index 2e02960..604b628 100644 --- a/tf5muxserver/mux_server_GetMetadata_test.go +++ b/tf5muxserver/mux_server_GetMetadata_test.go @@ -575,7 +575,6 @@ func TestMuxServerGetMetadata(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/tf5muxserver/mux_server_GetProviderSchema_test.go b/tf5muxserver/mux_server_GetProviderSchema_test.go index 2ee470f..52eb6bc 100644 --- a/tf5muxserver/mux_server_GetProviderSchema_test.go +++ b/tf5muxserver/mux_server_GetProviderSchema_test.go @@ -1157,7 +1157,6 @@ func TestMuxServerGetProviderSchema(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/tf5muxserver/mux_server_GetResourceIdentitySchemas.go b/tf5muxserver/mux_server_GetResourceIdentitySchemas.go new file mode 100644 index 0000000..dcd576f --- /dev/null +++ b/tf5muxserver/mux_server_GetResourceIdentitySchemas.go @@ -0,0 +1,68 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tf5muxserver + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + + "github.com/hashicorp/terraform-plugin-mux/internal/logging" +) + +// GetResourceIdentitySchemas merges the schemas returned by the +// tfprotov5.ResourceIdentitySchema associated with muxServer into a single schema. +// Everything must be returned from only one server. +// Schemas must be identical between all servers. +func (s *muxServer) GetResourceIdentitySchemas(ctx context.Context, req *tfprotov5.GetResourceIdentitySchemasRequest) (*tfprotov5.GetResourceIdentitySchemasResponse, error) { + rpc := "GetResourceIdentitySchemas" + ctx = logging.InitContext(ctx) + ctx = logging.RpcContext(ctx, rpc) + + resp := &tfprotov5.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{}, + Diagnostics: []*tfprotov5.Diagnostic{}, + } + + for _, server := range s.servers { + ctx := logging.Tfprotov5ProviderServerContext(ctx, server) + logging.MuxTrace(ctx, "calling downstream server") + + // TODO: Remove and call server.GetResourceIdentitySchemas below directly once interface becomes required. + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := server.(tfprotov5.ProviderServerWithResourceIdentity) + + if !ok { + resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{ + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "GetResourceIdentitySchemas Not Implemented", + Detail: "A GetResourceIdentitySchemas call was received by the provider, however the provider does not implement GetResourceIdentitySchemas. " + + "Either upgrade the provider to a version that implements GetResourceIdentitySchemas or this is a bug in Terraform that should be reported to the Terraform maintainers.", + }) + + continue + } + + resourceIdentitySchemas, err := resourceIdentityServer.GetResourceIdentitySchemas(ctx, req) + + if err != nil { + return resp, fmt.Errorf("error calling GetResourceIdentitySchemas for %T: %w", server, err) + } + + resp.Diagnostics = append(resp.Diagnostics, resourceIdentitySchemas.Diagnostics...) + + for resourceIdentityType, schema := range resourceIdentitySchemas.IdentitySchemas { + if _, ok := resp.IdentitySchemas[resourceIdentityType]; ok { + resp.Diagnostics = append(resp.Diagnostics, resourceIdentityDuplicateError(resourceIdentityType)) + + continue + } + + resp.IdentitySchemas[resourceIdentityType] = schema + } + } + + return resp, nil +} diff --git a/tf5muxserver/mux_server_GetResourceIdentitySchemas_test.go b/tf5muxserver/mux_server_GetResourceIdentitySchemas_test.go new file mode 100644 index 0000000..879609d --- /dev/null +++ b/tf5muxserver/mux_server_GetResourceIdentitySchemas_test.go @@ -0,0 +1,369 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tf5muxserver_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + + "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" +) + +func TestMuxServerGetResourceIdentitySchema(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + servers []func() tfprotov5.ProviderServer + expectedIdentitySchemas map[string]*tfprotov5.ResourceIdentitySchema + expectedDiagnostics []*tfprotov5.Diagnostic + }{ + "combined": { + servers: []func() tfprotov5.ProviderServer{ + (&tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{ + "test_resource_identity_foo": { + Version: 1, + IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{ + { + Name: "req", + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + OptionalForImport: true, + Description: "this one's optional", + }, + }, + }, + "test_resource_identity_bar": { + Version: 1, + IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{ + { + Name: "req", + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + OptionalForImport: true, + Description: "this one's optional", + }, + }, + }, + }, + }, + }).ProviderServer, + (&tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{ + "test_resource_identity_foobar": { + Version: 1, + IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{ + { + Name: "req", + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + OptionalForImport: true, + Description: "this one's optional", + }, + }, + }, + }, + }, + }).ProviderServer, + }, + expectedIdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{ + "test_resource_identity_foo": { + Version: 1, + IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{ + { + Name: "req", + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + OptionalForImport: true, + Description: "this one's optional", + }, + }, + }, + "test_resource_identity_bar": { + Version: 1, + IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{ + { + Name: "req", + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + OptionalForImport: true, + Description: "this one's optional", + }, + }, + }, + "test_resource_identity_foobar": { + Version: 1, + IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{ + { + Name: "req", + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + OptionalForImport: true, + Description: "this one's optional", + }, + }, + }, + }, + expectedDiagnostics: []*tfprotov5.Diagnostic{}, + }, + "duplicate-identity-schema-type": { + servers: []func() tfprotov5.ProviderServer{ + (&tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{ + "test_foo": {}, + }, + }, + }).ProviderServer, + (&tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{ + "test_foo": {}, + }, + }, + }).ProviderServer, + }, + expectedIdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{ + "test_foo": {}, + }, + expectedDiagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Invalid Provider Server Combination", + Detail: "The combined provider has multiple implementations of the same resource identity across underlying providers. " + + "Resource identity types must be implemented by only one underlying provider. " + + "This is always an issue in the provider implementation and should be reported to the provider developers.\n\n" + + "Duplicate identity type for resource: test_foo", + }, + }, + }, + "error-once": { + servers: []func() tfprotov5.ProviderServer{ + (&tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + }, + }).ProviderServer, + (&tf5testserver.TestServer{}).ProviderServer, + (&tf5testserver.TestServer{}).ProviderServer, + }, + expectedIdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{}, + expectedDiagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + }, + "error-multiple": { + servers: []func() tfprotov5.ProviderServer{ + (&tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + }, + }).ProviderServer, + (&tf5testserver.TestServer{}).ProviderServer, + (&tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + }, + }).ProviderServer, + }, + expectedIdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{}, + expectedDiagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + }, + "warning-once": { + servers: []func() tfprotov5.ProviderServer{ + (&tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + }, + }, + }).ProviderServer, + (&tf5testserver.TestServer{}).ProviderServer, + (&tf5testserver.TestServer{}).ProviderServer, + }, + expectedIdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{}, + expectedDiagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + }, + }, + "warning-multiple": { + servers: []func() tfprotov5.ProviderServer{ + (&tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + }, + }, + }).ProviderServer, + (&tf5testserver.TestServer{}).ProviderServer, + (&tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + }, + }, + }).ProviderServer, + }, + expectedIdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{}, + expectedDiagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + { + Severity: tfprotov5.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + }, + }, + "warning-then-error": { + servers: []func() tfprotov5.ProviderServer{ + (&tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + }, + }, + }).ProviderServer, + (&tf5testserver.TestServer{}).ProviderServer, + (&tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + }, + }).ProviderServer, + }, + expectedDiagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + expectedIdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{}, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + muxServer, err := tf5muxserver.NewMuxServer(context.Background(), testCase.servers...) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := muxServer.ProviderServer().(tfprotov5.ProviderServerWithResourceIdentity) + if !ok { + t.Fatal("muxServer should implement tfprotov5.ProviderServerWithResourceIdentity") + } + + resp, err := resourceIdentityServer.GetResourceIdentitySchemas(context.Background(), &tfprotov5.GetResourceIdentitySchemasRequest{}) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if diff := cmp.Diff(resp.Diagnostics, testCase.expectedDiagnostics); diff != "" { + t.Errorf("diagnostics didn't match expectations: %s", diff) + } + + if diff := cmp.Diff(resp.IdentitySchemas, testCase.expectedIdentitySchemas); diff != "" { + t.Errorf("identity schemas didn't match expectations: %s", diff) + } + }) + } +} diff --git a/tf5muxserver/mux_server_PrepareProviderConfig_test.go b/tf5muxserver/mux_server_PrepareProviderConfig_test.go index 161c0fd..50d6673 100644 --- a/tf5muxserver/mux_server_PrepareProviderConfig_test.go +++ b/tf5muxserver/mux_server_PrepareProviderConfig_test.go @@ -398,7 +398,6 @@ func TestMuxServerPrepareProviderConfig(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/tf5muxserver/mux_server_UpgradeResourceIdentity.go b/tf5muxserver/mux_server_UpgradeResourceIdentity.go new file mode 100644 index 0000000..0ddca12 --- /dev/null +++ b/tf5muxserver/mux_server_UpgradeResourceIdentity.go @@ -0,0 +1,55 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tf5muxserver + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-mux/internal/logging" +) + +// UpgradeResourceState calls the UpgradeResourceState method, passing `req`, +// on the provider that returned the resource specified by req.TypeName in its +// schema. +func (s *muxServer) UpgradeResourceIdentity(ctx context.Context, req *tfprotov5.UpgradeResourceIdentityRequest) (*tfprotov5.UpgradeResourceIdentityResponse, error) { + rpc := "UpgradeResourceIdentity" + ctx = logging.InitContext(ctx) + ctx = logging.RpcContext(ctx, rpc) + + server, diags, err := s.getResourceServer(ctx, req.TypeName) + + if err != nil { + return nil, err + } + + if diagnosticsHasError(diags) { + return &tfprotov5.UpgradeResourceIdentityResponse{ + Diagnostics: diags, + }, nil + } + + // TODO: Remove and call server.UpgradeResourceIdentity below directly once interface becomes required. + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := server.(tfprotov5.ProviderServerWithResourceIdentity) + if !ok { + resp := &tfprotov5.UpgradeResourceIdentityResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "UpgradeResourceIdentity Not Implemented", + Detail: "A UpgradeResourceIdentity call was received by the provider, however the provider does not implement UpgradeResourceIdentity. " + + "Either upgrade the provider to a version that implements UpgradeResourceIdentity or this is a bug in Terraform that should be reported to the Terraform maintainers.", + }, + }, + } + + return resp, nil + } + + ctx = logging.Tfprotov5ProviderServerContext(ctx, server) + logging.MuxTrace(ctx, "calling downstream server") + + return resourceIdentityServer.UpgradeResourceIdentity(ctx, req) +} diff --git a/tf5muxserver/mux_server_UpgradeResourceIdentity_test.go b/tf5muxserver/mux_server_UpgradeResourceIdentity_test.go new file mode 100644 index 0000000..f4e9711 --- /dev/null +++ b/tf5muxserver/mux_server_UpgradeResourceIdentity_test.go @@ -0,0 +1,89 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tf5muxserver_test + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + + "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" +) + +func TestMuxServerUpgradeResourceIdentity(t *testing.T) { + t.Parallel() + + ctx := context.Background() + testServer1 := &tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{ + "test_resource_server1": {}, + }, + }, + GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ + ResourceSchemas: map[string]*tfprotov5.Schema{ + "test_resource_server1": {}, + }, + }, + } + testServer2 := &tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{ + "test_resource_server2": {}, + }, + }, + GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ + ResourceSchemas: map[string]*tfprotov5.Schema{ + "test_resource_server2": {}, + }, + }, + } + + servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} + muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) + + if err != nil { + t.Fatalf("unexpected error setting up factory: %s", err) + } + + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := muxServer.ProviderServer().(tfprotov5.ProviderServerWithResourceIdentity) + if !ok { + t.Fatal("muxServer should implement tfprotov5.ProviderServerWithEphemeralResources") + } + + _, err = resourceIdentityServer.UpgradeResourceIdentity(ctx, &tfprotov5.UpgradeResourceIdentityRequest{ + TypeName: "test_resource_server1", + }) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !testServer1.UpgradeResourceIdentityCalled["test_resource_server1"] { + t.Errorf("expected test_resource_server1 UpgradeResourceIdentity to be called on server1") + } + + if testServer2.UpgradeResourceIdentityCalled["test_resource_server1"] { + t.Errorf("unexpected test_resource_server1 UpgradeResourceIdentity called on server2") + } + + _, err = resourceIdentityServer.UpgradeResourceIdentity(ctx, &tfprotov5.UpgradeResourceIdentityRequest{ + TypeName: "test_resource_server2", + }) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if testServer1.UpgradeResourceIdentityCalled["test_resource_server2"] { + t.Errorf("unexpected test_resource_server2 UpgradeResourceIdentity called on server1") + } + + if !testServer2.UpgradeResourceIdentityCalled["test_resource_server2"] { + t.Errorf("expected test_resource_server2 UpgradeResourceIdentity to be called on server2") + } +} diff --git a/tf5muxserver/mux_server_test.go b/tf5muxserver/mux_server_test.go index 2eadd66..94a37f8 100644 --- a/tf5muxserver/mux_server_test.go +++ b/tf5muxserver/mux_server_test.go @@ -1787,7 +1787,6 @@ func TestNewMuxServer(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/tf5to6server/tf5to6server.go b/tf5to6server/tf5to6server.go index 4202f66..749a0e3 100644 --- a/tf5to6server/tf5to6server.go +++ b/tf5to6server/tf5to6server.go @@ -110,6 +110,35 @@ func (s v5tov6Server) GetProviderSchema(ctx context.Context, req *tfprotov6.GetP return tfprotov5tov6.GetProviderSchemaResponse(v5Resp), nil } +func (s v5tov6Server) GetResourceIdentitySchemas(ctx context.Context, req *tfprotov6.GetResourceIdentitySchemasRequest) (*tfprotov6.GetResourceIdentitySchemasResponse, error) { + // TODO: Remove and call s.v6Server.GetResourceIdentitySchemas below directly once interface becomes required + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := s.v5Server.(tfprotov5.ProviderServerWithResourceIdentity) + if !ok { + v6Resp := &tfprotov6.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "GetResourceIdentitySchemas Not Implemented", + Detail: "A GetResourceIdentitySchemas call was received by the provider, however the provider does not implement the RPC. " + + "Either upgrade the provider to a version that implements GetResourceIdentitySchemas or this is a bug in Terraform that should be reported to the Terraform maintainers.", + }, + }, + } + + return v6Resp, nil + } + + v5Req := tfprotov6tov5.GetResourceIdentitySchemasRequest(req) + v5Resp, err := resourceIdentityServer.GetResourceIdentitySchemas(ctx, v5Req) + + if err != nil { + return nil, err + } + + return tfprotov5tov6.GetResourceIdentitySchemasResponse(v5Resp), nil +} + func (s v5tov6Server) ImportResourceState(ctx context.Context, req *tfprotov6.ImportResourceStateRequest) (*tfprotov6.ImportResourceStateResponse, error) { v5Req := tfprotov6tov5.ImportResourceStateRequest(req) v5Resp, err := s.v5Server.ImportResourceState(ctx, v5Req) @@ -214,6 +243,34 @@ func (s v5tov6Server) UpgradeResourceState(ctx context.Context, req *tfprotov6.U return tfprotov5tov6.UpgradeResourceStateResponse(v5Resp), nil } +func (s v5tov6Server) UpgradeResourceIdentity(ctx context.Context, req *tfprotov6.UpgradeResourceIdentityRequest) (*tfprotov6.UpgradeResourceIdentityResponse, error) { + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := s.v5Server.(tfprotov5.ProviderServerWithResourceIdentity) + if !ok { + v6Resp := &tfprotov6.UpgradeResourceIdentityResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "UpgradeResourceIdentity Not Implemented", + Detail: "A UpgradeResourceIdentity call was received by the provider, however the provider does not implement the RPC. " + + "Either upgrade the provider to a version that implements UpgradeResourceIdentity or this is a bug in Terraform that should be reported to the Terraform maintainers.", + }, + }, + } + + return v6Resp, nil + } + + v5Req := tfprotov6tov5.UpgradeResourceIdentityRequest(req) + v5Resp, err := resourceIdentityServer.UpgradeResourceIdentity(ctx, v5Req) + + if err != nil { + return nil, err + } + + return tfprotov5tov6.UpgradeResourceIdentityResponse(v5Resp), nil +} + func (s v5tov6Server) ValidateDataResourceConfig(ctx context.Context, req *tfprotov6.ValidateDataResourceConfigRequest) (*tfprotov6.ValidateDataResourceConfigResponse, error) { v5Req := tfprotov6tov5.ValidateDataSourceConfigRequest(req) v5Resp, err := s.v5Server.ValidateDataSourceConfig(ctx, v5Req) diff --git a/tf5to6server/tf5to6server_test.go b/tf5to6server/tf5to6server_test.go index d0f32ec..d1b308e 100644 --- a/tf5to6server/tf5to6server_test.go +++ b/tf5to6server/tf5to6server_test.go @@ -108,7 +108,6 @@ func TestUpgradeServer(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -343,6 +342,37 @@ func TestV6ToV5ServerGetProviderSchema(t *testing.T) { } } +func TestV6ToV5ServerGetResourceIdentitySchemas(t *testing.T) { + t.Parallel() + + ctx := context.Background() + v5server := &tf5testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{}, + } + + v6server, err := tf5to6server.UpgradeServer(context.Background(), v5server.ProviderServer) + + if err != nil { + t.Fatalf("unexpected error downgrading server: %s", err) + } + + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := v6server.(tfprotov6.ProviderServerWithResourceIdentity) + if !ok { + t.Fatal("v6server should implement tfprotov6.ProviderServerWithResourceIdentity") + } + + _, err = resourceIdentityServer.GetResourceIdentitySchemas(ctx, &tfprotov6.GetResourceIdentitySchemasRequest{}) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !v5server.GetResourceIdentitySchemasCalled { + t.Errorf("expected GetResourceIdentitySchemas to be called") + } +} + func TestV6ToV5ServerImportResourceState(t *testing.T) { t.Parallel() @@ -620,6 +650,43 @@ func TestV6ToV5ServerUpgradeResourceState(t *testing.T) { } } +func TestV6ToV5ServerUpgradeResourceIdentity(t *testing.T) { + t.Parallel() + + ctx := context.Background() + v5server := &tf5testserver.TestServer{ + GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ + ResourceSchemas: map[string]*tfprotov5.Schema{ + "test_resource": {}, + }, + }, + } + + v6server, err := tf5to6server.UpgradeServer(context.Background(), v5server.ProviderServer) + + if err != nil { + t.Fatalf("unexpected error downgrading server: %s", err) + } + + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := v6server.(tfprotov6.ProviderServerWithResourceIdentity) + if !ok { + t.Fatal("v6server should implement tfprotov6.ProviderServerWithResourceIdentity") + } + + _, err = resourceIdentityServer.UpgradeResourceIdentity(ctx, &tfprotov6.UpgradeResourceIdentityRequest{ + TypeName: "test_resource", + }) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !v5server.UpgradeResourceIdentityCalled["test_resource"] { + t.Errorf("expected test_resource UpgradeResourceState to be called") + } +} + func TestV6ToV5ServerValidateDataResourceConfig(t *testing.T) { t.Parallel() diff --git a/tf6muxserver/diagnostics.go b/tf6muxserver/diagnostics.go index 9e4bb29..36c706a 100644 --- a/tf6muxserver/diagnostics.go +++ b/tf6muxserver/diagnostics.go @@ -104,3 +104,14 @@ func resourceMissingError(typeName string) *tfprotov6.Diagnostic { "Missing resource type: " + typeName, } } + +func resourceIdentityDuplicateError(typeName string) *tfprotov6.Diagnostic { + return &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Invalid Provider Server Combination", + Detail: "The combined provider has multiple implementations of the same resource identity across underlying providers. " + + "Resource identity types must be implemented by only one underlying provider. " + + "This is always an issue in the provider implementation and should be reported to the provider developers.\n\n" + + "Duplicate identity type for resource: " + typeName, + } +} diff --git a/tf6muxserver/mux_server.go b/tf6muxserver/mux_server.go index b83bb56..9ca8db5 100644 --- a/tf6muxserver/mux_server.go +++ b/tf6muxserver/mux_server.go @@ -350,6 +350,7 @@ func (s *muxServer) serverDiscovery(ctx context.Context) error { // - Only one provider implements each data source // - Only one provider implements each function // - Only one provider implements each ephemeral resource +// - Only one provider implements each resource identity func NewMuxServer(_ context.Context, servers ...func() tfprotov6.ProviderServer) (*muxServer, error) { result := muxServer{ dataSources: make(map[string]tfprotov6.ProviderServer), diff --git a/tf6muxserver/mux_server_GetFunctions_test.go b/tf6muxserver/mux_server_GetFunctions_test.go index 0168acc..0c09c41 100644 --- a/tf6muxserver/mux_server_GetFunctions_test.go +++ b/tf6muxserver/mux_server_GetFunctions_test.go @@ -300,7 +300,6 @@ func TestMuxServerGetFunctions(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/tf6muxserver/mux_server_GetMetadata_test.go b/tf6muxserver/mux_server_GetMetadata_test.go index ffb02c9..6449e63 100644 --- a/tf6muxserver/mux_server_GetMetadata_test.go +++ b/tf6muxserver/mux_server_GetMetadata_test.go @@ -575,7 +575,6 @@ func TestMuxServerGetMetadata(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/tf6muxserver/mux_server_GetProviderSchema_test.go b/tf6muxserver/mux_server_GetProviderSchema_test.go index 346ba27..2e83699 100644 --- a/tf6muxserver/mux_server_GetProviderSchema_test.go +++ b/tf6muxserver/mux_server_GetProviderSchema_test.go @@ -1156,7 +1156,6 @@ func TestMuxServerGetProviderSchema(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/tf6muxserver/mux_server_GetResourceIdentitySchemas.go b/tf6muxserver/mux_server_GetResourceIdentitySchemas.go new file mode 100644 index 0000000..a4697c6 --- /dev/null +++ b/tf6muxserver/mux_server_GetResourceIdentitySchemas.go @@ -0,0 +1,68 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tf6muxserver + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-mux/internal/logging" +) + +// GetResourceIdentitySchemas merges the schemas returned by the +// tfprotov6.ResourceIdentitySchema associated with muxServer into a single schema. +// Everything must be returned from only one server. +// Schemas must be identical between all servers. +func (s *muxServer) GetResourceIdentitySchemas(ctx context.Context, req *tfprotov6.GetResourceIdentitySchemasRequest) (*tfprotov6.GetResourceIdentitySchemasResponse, error) { + rpc := "GetResourceIdentitySchemas" + ctx = logging.InitContext(ctx) + ctx = logging.RpcContext(ctx, rpc) + + resp := &tfprotov6.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{}, + Diagnostics: []*tfprotov6.Diagnostic{}, + } + + for _, server := range s.servers { + ctx := logging.Tfprotov6ProviderServerContext(ctx, server) + logging.MuxTrace(ctx, "calling downstream server") + + // TODO: Remove and call server.GetResourceIdentitySchemas below directly once interface becomes required. + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := server.(tfprotov6.ProviderServerWithResourceIdentity) + + if !ok { + resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "GetResourceIdentitySchemas Not Implemented", + Detail: "A GetResourceIdentitySchemas call was received by the provider, however the provider does not implement GetResourceIdentitySchemas. " + + "Either upgrade the provider to a version that implements GetResourceIdentitySchemas or this is a bug in Terraform that should be reported to the Terraform maintainers.", + }) + + continue + } + + resourceIdentitySchemas, err := resourceIdentityServer.GetResourceIdentitySchemas(ctx, req) + + if err != nil { + return resp, fmt.Errorf("error calling GetResourceIdentitySchemas for %T: %w", server, err) + } + + resp.Diagnostics = append(resp.Diagnostics, resourceIdentitySchemas.Diagnostics...) + + for resourceIdentityType, schema := range resourceIdentitySchemas.IdentitySchemas { + if _, ok := resp.IdentitySchemas[resourceIdentityType]; ok { + resp.Diagnostics = append(resp.Diagnostics, resourceIdentityDuplicateError(resourceIdentityType)) + + continue + } + + resp.IdentitySchemas[resourceIdentityType] = schema + } + } + + return resp, nil +} diff --git a/tf6muxserver/mux_server_GetResourceIdentitySchemas_test.go b/tf6muxserver/mux_server_GetResourceIdentitySchemas_test.go new file mode 100644 index 0000000..a13f5fc --- /dev/null +++ b/tf6muxserver/mux_server_GetResourceIdentitySchemas_test.go @@ -0,0 +1,369 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tf6muxserver_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" +) + +func TestMuxServerGetResourceIdentitySchema(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + servers []func() tfprotov6.ProviderServer + expectedIdentitySchemas map[string]*tfprotov6.ResourceIdentitySchema + expectedDiagnostics []*tfprotov6.Diagnostic + }{ + "combined": { + servers: []func() tfprotov6.ProviderServer{ + (&tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{ + "test_resource_identity_foo": { + Version: 1, + IdentityAttributes: []*tfprotov6.ResourceIdentitySchemaAttribute{ + { + Name: "req", + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + OptionalForImport: true, + Description: "this one's optional", + }, + }, + }, + "test_resource_identity_bar": { + Version: 1, + IdentityAttributes: []*tfprotov6.ResourceIdentitySchemaAttribute{ + { + Name: "req", + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + OptionalForImport: true, + Description: "this one's optional", + }, + }, + }, + }, + }, + }).ProviderServer, + (&tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{ + "test_resource_identity_foobar": { + Version: 1, + IdentityAttributes: []*tfprotov6.ResourceIdentitySchemaAttribute{ + { + Name: "req", + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + OptionalForImport: true, + Description: "this one's optional", + }, + }, + }, + }, + }, + }).ProviderServer, + }, + expectedIdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{ + "test_resource_identity_foo": { + Version: 1, + IdentityAttributes: []*tfprotov6.ResourceIdentitySchemaAttribute{ + { + Name: "req", + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + OptionalForImport: true, + Description: "this one's optional", + }, + }, + }, + "test_resource_identity_bar": { + Version: 1, + IdentityAttributes: []*tfprotov6.ResourceIdentitySchemaAttribute{ + { + Name: "req", + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + OptionalForImport: true, + Description: "this one's optional", + }, + }, + }, + "test_resource_identity_foobar": { + Version: 1, + IdentityAttributes: []*tfprotov6.ResourceIdentitySchemaAttribute{ + { + Name: "req", + RequiredForImport: true, + Description: "this one's required", + }, + { + Name: "opt", + OptionalForImport: true, + Description: "this one's optional", + }, + }, + }, + }, + expectedDiagnostics: []*tfprotov6.Diagnostic{}, + }, + "duplicate-identity-schema-type": { + servers: []func() tfprotov6.ProviderServer{ + (&tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{ + "test_foo": {}, + }, + }, + }).ProviderServer, + (&tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{ + "test_foo": {}, + }, + }, + }).ProviderServer, + }, + expectedIdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{ + "test_foo": {}, + }, + expectedDiagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Invalid Provider Server Combination", + Detail: "The combined provider has multiple implementations of the same resource identity across underlying providers. " + + "Resource identity types must be implemented by only one underlying provider. " + + "This is always an issue in the provider implementation and should be reported to the provider developers.\n\n" + + "Duplicate identity type for resource: test_foo", + }, + }, + }, + "error-once": { + servers: []func() tfprotov6.ProviderServer{ + (&tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + }, + }).ProviderServer, + (&tf6testserver.TestServer{}).ProviderServer, + (&tf6testserver.TestServer{}).ProviderServer, + }, + expectedIdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{}, + expectedDiagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + }, + "error-multiple": { + servers: []func() tfprotov6.ProviderServer{ + (&tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + }, + }).ProviderServer, + (&tf6testserver.TestServer{}).ProviderServer, + (&tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + }, + }).ProviderServer, + }, + expectedIdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{}, + expectedDiagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + }, + "warning-once": { + servers: []func() tfprotov6.ProviderServer{ + (&tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + }, + }, + }).ProviderServer, + (&tf6testserver.TestServer{}).ProviderServer, + (&tf6testserver.TestServer{}).ProviderServer, + }, + expectedIdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{}, + expectedDiagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + }, + }, + "warning-multiple": { + servers: []func() tfprotov6.ProviderServer{ + (&tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + }, + }, + }).ProviderServer, + (&tf6testserver.TestServer{}).ProviderServer, + (&tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + }, + }, + }).ProviderServer, + }, + expectedIdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{}, + expectedDiagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + { + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + }, + }, + "warning-then-error": { + servers: []func() tfprotov6.ProviderServer{ + (&tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + }, + }, + }).ProviderServer, + (&tf6testserver.TestServer{}).ProviderServer, + (&tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + }, + }).ProviderServer, + }, + expectedDiagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "test warning summary", + Detail: "test warning details", + }, + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "test error summary", + Detail: "test error details", + }, + }, + expectedIdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{}, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + muxServer, err := tf6muxserver.NewMuxServer(context.Background(), testCase.servers...) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := muxServer.ProviderServer().(tfprotov6.ProviderServerWithResourceIdentity) + if !ok { + t.Fatal("muxServer should implement tfprotov6.ProviderServerWithResourceIdentity") + } + + resp, err := resourceIdentityServer.GetResourceIdentitySchemas(context.Background(), &tfprotov6.GetResourceIdentitySchemasRequest{}) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if diff := cmp.Diff(resp.Diagnostics, testCase.expectedDiagnostics); diff != "" { + t.Errorf("diagnostics didn't match expectations: %s", diff) + } + + if diff := cmp.Diff(resp.IdentitySchemas, testCase.expectedIdentitySchemas); diff != "" { + t.Errorf("identity schemas didn't match expectations: %s", diff) + } + }) + } +} diff --git a/tf6muxserver/mux_server_UpgradeResourceIdentity.go b/tf6muxserver/mux_server_UpgradeResourceIdentity.go new file mode 100644 index 0000000..4d1267a --- /dev/null +++ b/tf6muxserver/mux_server_UpgradeResourceIdentity.go @@ -0,0 +1,55 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tf6muxserver + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-mux/internal/logging" +) + +// UpgradeResourceState calls the UpgradeResourceState method, passing `req`, +// on the provider that returned the resource specified by req.TypeName in its +// schema. +func (s *muxServer) UpgradeResourceIdentity(ctx context.Context, req *tfprotov6.UpgradeResourceIdentityRequest) (*tfprotov6.UpgradeResourceIdentityResponse, error) { + rpc := "UpgradeResourceIdentity" + ctx = logging.InitContext(ctx) + ctx = logging.RpcContext(ctx, rpc) + + server, diags, err := s.getResourceServer(ctx, req.TypeName) + + if err != nil { + return nil, err + } + + if diagnosticsHasError(diags) { + return &tfprotov6.UpgradeResourceIdentityResponse{ + Diagnostics: diags, + }, nil + } + + // TODO: Remove and call server.UpgradeResourceIdentity below directly once interface becomes required. + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := server.(tfprotov6.ProviderServerWithResourceIdentity) + if !ok { + resp := &tfprotov6.UpgradeResourceIdentityResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "UpgradeResourceIdentity Not Implemented", + Detail: "A UpgradeResourceIdentity call was received by the provider, however the provider does not implement UpgradeResourceIdentity. " + + "Either upgrade the provider to a version that implements UpgradeResourceIdentity or this is a bug in Terraform that should be reported to the Terraform maintainers.", + }, + }, + } + + return resp, nil + } + + ctx = logging.Tfprotov6ProviderServerContext(ctx, server) + logging.MuxTrace(ctx, "calling downstream server") + + return resourceIdentityServer.UpgradeResourceIdentity(ctx, req) +} diff --git a/tf6muxserver/mux_server_UpgradeResourceIdentity_test.go b/tf6muxserver/mux_server_UpgradeResourceIdentity_test.go new file mode 100644 index 0000000..1732028 --- /dev/null +++ b/tf6muxserver/mux_server_UpgradeResourceIdentity_test.go @@ -0,0 +1,89 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tf6muxserver_test + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" +) + +func TestMuxServerUpgradeResourceIdentity(t *testing.T) { + t.Parallel() + + ctx := context.Background() + testServer1 := &tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{ + "test_resource_server1": {}, + }, + }, + GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ + ResourceSchemas: map[string]*tfprotov6.Schema{ + "test_resource_server1": {}, + }, + }, + } + testServer2 := &tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ + IdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{ + "test_resource_server2": {}, + }, + }, + GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ + ResourceSchemas: map[string]*tfprotov6.Schema{ + "test_resource_server2": {}, + }, + }, + } + + servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} + muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) + + if err != nil { + t.Fatalf("unexpected error setting up factory: %s", err) + } + + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := muxServer.ProviderServer().(tfprotov6.ProviderServerWithResourceIdentity) + if !ok { + t.Fatal("muxServer should implement tfprotov6.ProviderServerWithEphemeralResources") + } + + _, err = resourceIdentityServer.UpgradeResourceIdentity(ctx, &tfprotov6.UpgradeResourceIdentityRequest{ + TypeName: "test_resource_server1", + }) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !testServer1.UpgradeResourceIdentityCalled["test_resource_server1"] { + t.Errorf("expected test_resource_server1 UpgradeResourceIdentity to be called on server1") + } + + if testServer2.UpgradeResourceIdentityCalled["test_resource_server1"] { + t.Errorf("unexpected test_resource_server1 UpgradeResourceIdentity called on server2") + } + + _, err = resourceIdentityServer.UpgradeResourceIdentity(ctx, &tfprotov6.UpgradeResourceIdentityRequest{ + TypeName: "test_resource_server2", + }) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if testServer1.UpgradeResourceIdentityCalled["test_resource_server2"] { + t.Errorf("unexpected test_resource_server2 UpgradeResourceIdentity called on server1") + } + + if !testServer2.UpgradeResourceIdentityCalled["test_resource_server2"] { + t.Errorf("expected test_resource_server2 UpgradeResourceIdentity to be called on server2") + } +} diff --git a/tf6muxserver/mux_server_ValidateProviderConfig_test.go b/tf6muxserver/mux_server_ValidateProviderConfig_test.go index 3f24fa0..5e58be0 100644 --- a/tf6muxserver/mux_server_ValidateProviderConfig_test.go +++ b/tf6muxserver/mux_server_ValidateProviderConfig_test.go @@ -415,7 +415,6 @@ func TestMuxServerValidateProviderConfig(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/tf6muxserver/mux_server_test.go b/tf6muxserver/mux_server_test.go index 54e45df..55f731a 100644 --- a/tf6muxserver/mux_server_test.go +++ b/tf6muxserver/mux_server_test.go @@ -1787,7 +1787,6 @@ func TestNewMuxServer(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/tf6to5server/tf6to5server.go b/tf6to5server/tf6to5server.go index aee6383..55f2bd9 100644 --- a/tf6to5server/tf6to5server.go +++ b/tf6to5server/tf6to5server.go @@ -122,6 +122,35 @@ func (s v6tov5Server) GetProviderSchema(ctx context.Context, req *tfprotov5.GetP return tfprotov6tov5.GetProviderSchemaResponse(v6Resp) } +func (s v6tov5Server) GetResourceIdentitySchemas(ctx context.Context, req *tfprotov5.GetResourceIdentitySchemasRequest) (*tfprotov5.GetResourceIdentitySchemasResponse, error) { + // TODO: Remove and call s.v6Server.GetResourceIdentitySchemas below directly once interface becomes required + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := s.v6Server.(tfprotov6.ProviderServerWithResourceIdentity) + if !ok { + v5Resp := &tfprotov5.GetResourceIdentitySchemasResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "GetResourceIdentitySchemas Not Implemented", + Detail: "A GetResourceIdentitySchemas call was received by the provider, however the provider does not implement the RPC. " + + "Either upgrade the provider to a version that implements GetResourceIdentitySchemas or this is a bug in Terraform that should be reported to the Terraform maintainers.", + }, + }, + } + + return v5Resp, nil + } + + v6Req := tfprotov5tov6.GetResourceIdentitySchemasRequest(req) + v6Resp, err := resourceIdentityServer.GetResourceIdentitySchemas(ctx, v6Req) + + if err != nil { + return nil, err + } + + return tfprotov6tov5.GetResourceIdentitySchemasResponse(v6Resp), nil +} + func (s v6tov5Server) ImportResourceState(ctx context.Context, req *tfprotov5.ImportResourceStateRequest) (*tfprotov5.ImportResourceStateResponse, error) { v6Req := tfprotov5tov6.ImportResourceStateRequest(req) v6Resp, err := s.v6Server.ImportResourceState(ctx, v6Req) @@ -237,6 +266,35 @@ func (s v6tov5Server) UpgradeResourceState(ctx context.Context, req *tfprotov5.U return tfprotov6tov5.UpgradeResourceStateResponse(v6Resp), nil } +func (s v6tov5Server) UpgradeResourceIdentity(ctx context.Context, req *tfprotov5.UpgradeResourceIdentityRequest) (*tfprotov5.UpgradeResourceIdentityResponse, error) { + // TODO: Remove and call s.v6Server.UpgradeResourceIdentity below directly once interface becomes required + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := s.v6Server.(tfprotov6.ProviderServerWithResourceIdentity) + if !ok { + v5Resp := &tfprotov5.UpgradeResourceIdentityResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "UpgradeResourceIdentity Not Implemented", + Detail: "A UpgradeResourceIdentity call was received by the provider, however the provider does not implement the RPC. " + + "Either upgrade the provider to a version that implements UpgradeResourceIdentity or this is a bug in Terraform that should be reported to the Terraform maintainers.", + }, + }, + } + + return v5Resp, nil + } + + v6Req := tfprotov5tov6.UpgradeResourceIdentityRequest(req) + v6Resp, err := resourceIdentityServer.UpgradeResourceIdentity(ctx, v6Req) + + if err != nil { + return nil, err + } + + return tfprotov6tov5.UpgradeResourceIdentityResponse(v6Resp), nil +} + func (s v6tov5Server) ValidateDataSourceConfig(ctx context.Context, req *tfprotov5.ValidateDataSourceConfigRequest) (*tfprotov5.ValidateDataSourceConfigResponse, error) { v6Req := tfprotov5tov6.ValidateDataResourceConfigRequest(req) v6Resp, err := s.v6Server.ValidateDataResourceConfig(ctx, v6Req) diff --git a/tf6to5server/tf6to5server_test.go b/tf6to5server/tf6to5server_test.go index 4977d8f..326c78f 100644 --- a/tf6to5server/tf6to5server_test.go +++ b/tf6to5server/tf6to5server_test.go @@ -206,7 +206,6 @@ func TestDowngradeServer(t *testing.T) { } for name, testCase := range testCases { - name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -441,6 +440,37 @@ func TestV6ToV5ServerGetProviderSchema(t *testing.T) { } } +func TestV6ToV5ServerGetResourceIdentitySchemas(t *testing.T) { + t.Parallel() + + ctx := context.Background() + v6server := &tf6testserver.TestServer{ + GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{}, + } + + v5server, err := tf6to5server.DowngradeServer(context.Background(), v6server.ProviderServer) + + if err != nil { + t.Fatalf("unexpected error downgrading server: %s", err) + } + + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := v5server.(tfprotov5.ProviderServerWithResourceIdentity) + if !ok { + t.Fatal("v5server should implement tfprotov5.ProviderServerWithResourceIdentity") + } + + _, err = resourceIdentityServer.GetResourceIdentitySchemas(ctx, &tfprotov5.GetResourceIdentitySchemasRequest{}) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !v6server.GetResourceIdentitySchemasCalled { + t.Errorf("expected GetResourceIdentitySchemas to be called") + } +} + func TestV6ToV5ServerImportResourceState(t *testing.T) { t.Parallel() @@ -747,6 +777,43 @@ func TestV6ToV5ServerUpgradeResourceState(t *testing.T) { } } +func TestV6ToV5ServerUpgradeResourceIdentity(t *testing.T) { + t.Parallel() + + ctx := context.Background() + v6server := &tf6testserver.TestServer{ + GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ + ResourceSchemas: map[string]*tfprotov6.Schema{ + "test_resource": {}, + }, + }, + } + + v5server, err := tf6to5server.DowngradeServer(context.Background(), v6server.ProviderServer) + + if err != nil { + t.Fatalf("unexpected error downgrading server: %s", err) + } + + //nolint:staticcheck // Intentionally verifying interface implementation + resourceIdentityServer, ok := v5server.(tfprotov5.ProviderServerWithResourceIdentity) + if !ok { + t.Fatal("v5server should implement tfprotov5.ProviderServerWithResourceIdentity") + } + + _, err = resourceIdentityServer.UpgradeResourceIdentity(ctx, &tfprotov5.UpgradeResourceIdentityRequest{ + TypeName: "test_resource", + }) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !v6server.UpgradeResourceIdentityCalled["test_resource"] { + t.Errorf("expected test_resource UpgradeResourceState to be called") + } +} + func TestV6ToV5ServerValidateDataSourceConfig(t *testing.T) { t.Parallel()