From fa391ed0fda5da1b605eb7733abb07ba57e52c59 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Fri, 16 Aug 2024 11:05:41 -0400 Subject: [PATCH 01/10] initial ruff resource implementation --- .../resource_refinements.go | 193 ++++++++++++++++++ internal/protocolv6provider/server.go | 19 ++ 2 files changed, 212 insertions(+) create mode 100644 internal/protocolv6provider/resource_refinements.go diff --git a/internal/protocolv6provider/resource_refinements.go b/internal/protocolv6provider/resource_refinements.go new file mode 100644 index 00000000..406a6061 --- /dev/null +++ b/internal/protocolv6provider/resource_refinements.go @@ -0,0 +1,193 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package protocolv6 + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-go/tftypes/refinement" +) + +type resourceRefinements struct{} + +func (r resourceRefinements) schemaType() tftypes.Type { + return tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "str_value": tftypes.String, + }, + } +} + +func (r resourceRefinements) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyResourceChangeRequest) (*tfprotov6.ApplyResourceChangeResponse, error) { + plannedState, err := tftypes.ValueFromMsgPack(req.PlannedState.MsgPack, r.schemaType()) //nolint + if err != nil { + return &tfprotov6.ApplyResourceChangeResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Error decoding prior state", + Detail: fmt.Sprintf("Error decoding prior state: %s", err.Error()), + }, + }, + }, nil + } + + // Destroy op, state should be null + if plannedState.IsNull() { + return &tfprotov6.ApplyResourceChangeResponse{ + NewState: req.PlannedState, + }, nil + } + + objVal := map[string]tftypes.Value{} + + plannedState.As(&objVal) //nolint + + newStrValue := tftypes.NewValue(tftypes.String, "hello world!") + + // If the value exists in config, use it. + if strValue, ok := objVal["str_value"]; ok && strValue.IsKnown() { + newStrValue = strValue + } + + newState, err := tfprotov6.NewDynamicValue( + r.schemaType(), + tftypes.NewValue( + r.schemaType(), + map[string]tftypes.Value{ + "str_value": newStrValue, + }, + ), + ) + if err != nil { + return &tfprotov6.ApplyResourceChangeResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Error encoding state", + Detail: fmt.Sprintf("Error encoding planned state: %s", err.Error()), + }, + }, + }, nil + } + + return &tfprotov6.ApplyResourceChangeResponse{ + NewState: &newState, + }, nil +} + +func (r resourceRefinements) ImportResourceState(ctx context.Context, req *tfprotov6.ImportResourceStateRequest) (*tfprotov6.ImportResourceStateResponse, error) { + return &tfprotov6.ImportResourceStateResponse{}, nil +} + +func (r resourceRefinements) MoveResourceState(ctx context.Context, req *tfprotov6.MoveResourceStateRequest) (*tfprotov6.MoveResourceStateResponse, error) { + return &tfprotov6.MoveResourceStateResponse{}, nil +} + +func (r resourceRefinements) PlanResourceChange(ctx context.Context, req *tfprotov6.PlanResourceChangeRequest) (*tfprotov6.PlanResourceChangeResponse, error) { + if req.PriorState.MsgPack != nil { + priorState, err := tftypes.ValueFromMsgPack(req.PriorState.MsgPack, r.schemaType()) //nolint + if err != nil { + return &tfprotov6.PlanResourceChangeResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Error decoding prior state", + Detail: fmt.Sprintf("Error decoding prior state: %s", err.Error()), + }, + }, + }, nil + } + + // Update op, keep prior state + if !priorState.IsNull() { + return &tfprotov6.PlanResourceChangeResponse{ + PlannedState: req.PriorState, + }, nil + } + } + + proposedNewState, err := tftypes.ValueFromMsgPack(req.ProposedNewState.MsgPack, r.schemaType()) //nolint + if err != nil { + return &tfprotov6.PlanResourceChangeResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Error decoding proposed state", + Detail: fmt.Sprintf("Error decoding proposed state: %s", err.Error()), + }, + }, + }, nil + } + + objVal := map[string]tftypes.Value{} + + proposedNewState.As(&objVal) //nolint + + newStrValue := tftypes.NewValue(tftypes.String, tftypes.UnknownValue). + Refine(refinement.Refinements{ + // str_value will never be null + refinement.KeyNullness: refinement.Nullness(false), + }) + + // If the value exists in config (unknown or known), keep it. + if strValue, ok := objVal["str_value"]; ok && !strValue.IsNull() { + newStrValue = strValue + } + + plannedState, err := tfprotov6.NewDynamicValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "str_value": tftypes.String, + }, + }, tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "str_value": tftypes.String, + }, + }, map[string]tftypes.Value{ + "str_value": newStrValue, + })) + if err != nil { + return &tfprotov6.PlanResourceChangeResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Error encoding state", + Detail: fmt.Sprintf("Error encoding planned state: %s", err.Error()), + }, + }, + }, nil + } + + return &tfprotov6.PlanResourceChangeResponse{ + PlannedState: &plannedState, + }, nil +} + +func (r resourceRefinements) ReadResource(ctx context.Context, req *tfprotov6.ReadResourceRequest) (*tfprotov6.ReadResourceResponse, error) { + return &tfprotov6.ReadResourceResponse{ + NewState: req.CurrentState, + }, nil +} + +func (r resourceRefinements) UpgradeResourceState(ctx context.Context, req *tfprotov6.UpgradeResourceStateRequest) (*tfprotov6.UpgradeResourceStateResponse, error) { + rawStateValue, _ := req.RawState.UnmarshalWithOpts(r.schemaType(), tfprotov6.UnmarshalOpts{ + ValueFromJSONOpts: tftypes.ValueFromJSONOpts{ + IgnoreUndefinedAttributes: true, + }, + }) + + upgradedState, _ := tfprotov6.NewDynamicValue(r.schemaType(), rawStateValue) + + return &tfprotov6.UpgradeResourceStateResponse{ + UpgradedState: &upgradedState, + Diagnostics: []*tfprotov6.Diagnostic{}, + }, nil +} + +func (r resourceRefinements) ValidateResourceConfig(ctx context.Context, req *tfprotov6.ValidateResourceConfigRequest) (*tfprotov6.ValidateResourceConfigResponse, error) { + return &tfprotov6.ValidateResourceConfigResponse{}, nil +} diff --git a/internal/protocolv6provider/server.go b/internal/protocolv6provider/server.go index acc4ecc4..1e9258e4 100644 --- a/internal/protocolv6provider/server.go +++ b/internal/protocolv6provider/server.go @@ -129,6 +129,25 @@ func Server() tfprotov6.ProviderServer { "corner_v6_time": dataSourceTime{}, "corner_v6_deferred_action": dataSourceDeferredAction{}, }, + resourceSchemas: map[string]*tfprotov6.Schema{ + "corner_v6_refinements": { + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "str_value", + Type: tftypes.String, + Description: "Computed string value that will definitely not be null.", + DescriptionKind: tfprotov6.StringKindPlain, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + resourceRouter: resourceRouter{ + "corner_v6_refinements": resourceRefinements{}, + }, functions: map[string]*tfprotov6.Function{ "bool": { Parameters: []*tfprotov6.FunctionParameter{ From 2fc9836254bb7a3bf9938ac09c242620bb18f5f7 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Fri, 16 Aug 2024 11:05:55 -0400 Subject: [PATCH 02/10] add test for working refinement roundtrip --- go.mod | 12 +-- go.sum | 20 ++--- .../resource_refinements_test.go | 83 +++++++++++++++++++ 3 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 internal/protocolv6provider/resource_refinements_test.go diff --git a/go.mod b/go.mod index 160c63fa..d94e27eb 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/hashicorp/terraform-provider-corner go 1.21 +// replace github.com/hashicorp/terraform-plugin-go => /Users/austin.valle/code/terraform-plugin-go + toolchain go1.21.6 require ( @@ -10,7 +12,7 @@ require ( github.com/hashicorp/terraform-plugin-framework v1.11.0 github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 - github.com/hashicorp/terraform-plugin-go v0.23.0 + github.com/hashicorp/terraform-plugin-go v0.23.1-0.20240816150203-1e2a00dbd3f4 github.com/hashicorp/terraform-plugin-mux v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 @@ -32,7 +34,7 @@ require ( github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.6.0 // indirect + github.com/hashicorp/go-plugin v1.6.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.7.0 // indirect @@ -64,7 +66,7 @@ require ( golang.org/x/text v0.17.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/grpc v1.63.2 // indirect - google.golang.org/protobuf v1.34.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect ) diff --git a/go.sum b/go.sum index 531ae023..86723ccf 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8Io github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= -github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= +github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= +github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -87,8 +87,8 @@ github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 h1:gm5b1kHgFFhaK github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1/go.mod h1:MsjL1sQ9L7wGwzJ5RjcI6FzEMdyoBnw+XK8ZnOvQOLY= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 h1:v3DapR8gsp3EM8fKMh6up9cJUFQ2iRaFsYLP8UJnCco= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0/go.mod h1:c3PnGE9pHBDfdEVG9t1S1C9ia5LW+gkFR0CygXlM8ak= -github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= -github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ= +github.com/hashicorp/terraform-plugin-go v0.23.1-0.20240816150203-1e2a00dbd3f4 h1:qVtgDJqnQUjNjrHbBkAHrK6hzsHB9pB6qSn2aMDl7fM= +github.com/hashicorp/terraform-plugin-go v0.23.1-0.20240816150203-1e2a00dbd3f4/go.mod h1:Jy3/cuajdDO+nGdX69P6Fwe5Qe65WroXGPVw0+khIcQ= 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-plugin-mux v0.16.0 h1:RCzXHGDYwUwwqfYYWJKBFaS3fQsWn/ZECEiW7p2023I= @@ -211,14 +211,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= -google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/protocolv6provider/resource_refinements_test.go b/internal/protocolv6provider/resource_refinements_test.go new file mode 100644 index 00000000..68d2b393 --- /dev/null +++ b/internal/protocolv6provider/resource_refinements_test.go @@ -0,0 +1,83 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package protocolv6 + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestAccResourceRefinements(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + Source: "registry.terraform.io/hashicorp/random", + }, + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + //nolint:unparam // False positive in unparam related to map: https://github.com/mvdan/unparam/issues/40 + "corner": func() (tfprotov6.ProviderServer, error) { + return Server(), nil + }, + }, + Steps: []resource.TestStep{ + { + // Typically, this config would return an error like: + // + // Error: Invalid count argument + // + // on terraform_plugin_test.tf line 23, in resource "random_string" "other_str": + // 23: count = corner_v6_refinements.foo.str_value != null ? 1 : 0 + // + // The "count" value depends on resource attributes that cannot be determined + // until apply, so Terraform cannot predict how many instances will be created. + // To work around this, use the -target argument to first apply only the + // resources that the count depends on. + // + // This error occurs because the expression populating "str_value" is passing the provider an unknown value + // with a refinement (the result will definitely not be null, regardless of the value of "random_string.str.id") + // that is eventually lost by the provider during PlanResourceChange. + // + // When the provider implementation preserves the unknown value refinement, this configuration can + // plan/apply successfully and will create 3 resources, including the "random_string.other_str" resource. + // + Config: ` + resource "random_string" "str" { + length = 12 + } + + resource "corner_v6_refinements" "foo" { + str_value = "This string is ${random_string.str.id}!" + } + + resource "random_string" "other_str" { + count = corner_v6_refinements.foo.str_value != null ? 1 : 0 + length = 12 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("corner_v6_refinements.foo", tfjsonpath.New("str_value")), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("corner_v6_refinements.foo", tfjsonpath.New("str_value"), knownvalue.StringRegexp(regexp.MustCompile(`This string is`))), + statecheck.ExpectKnownValue("random_string.other_str[0]", tfjsonpath.New("id"), knownvalue.NotNull()), + }, + }, + }, + }) +} From 215bc518f634e0f35c98d7e5948a04c7064e7da2 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Fri, 16 Aug 2024 17:02:55 -0400 Subject: [PATCH 03/10] add string prefix refinement --- go.mod | 2 +- go.sum | 4 +- .../resource_refinements_test.go | 72 ++++++++++++++++++- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index d94e27eb..9ef98bcc 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/hashicorp/terraform-plugin-framework v1.11.0 github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 - github.com/hashicorp/terraform-plugin-go v0.23.1-0.20240816150203-1e2a00dbd3f4 + github.com/hashicorp/terraform-plugin-go v0.23.1-0.20240816210126-55b26ba04c8d github.com/hashicorp/terraform-plugin-mux v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 diff --git a/go.sum b/go.sum index 86723ccf..d2b78a83 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 h1:gm5b1kHgFFhaK github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1/go.mod h1:MsjL1sQ9L7wGwzJ5RjcI6FzEMdyoBnw+XK8ZnOvQOLY= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 h1:v3DapR8gsp3EM8fKMh6up9cJUFQ2iRaFsYLP8UJnCco= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0/go.mod h1:c3PnGE9pHBDfdEVG9t1S1C9ia5LW+gkFR0CygXlM8ak= -github.com/hashicorp/terraform-plugin-go v0.23.1-0.20240816150203-1e2a00dbd3f4 h1:qVtgDJqnQUjNjrHbBkAHrK6hzsHB9pB6qSn2aMDl7fM= -github.com/hashicorp/terraform-plugin-go v0.23.1-0.20240816150203-1e2a00dbd3f4/go.mod h1:Jy3/cuajdDO+nGdX69P6Fwe5Qe65WroXGPVw0+khIcQ= +github.com/hashicorp/terraform-plugin-go v0.23.1-0.20240816210126-55b26ba04c8d h1:Db3jZiOTbfzNkEmOtZLKJufQqwX/QwfD2RiCNndJUE8= +github.com/hashicorp/terraform-plugin-go v0.23.1-0.20240816210126-55b26ba04c8d/go.mod h1:Jy3/cuajdDO+nGdX69P6Fwe5Qe65WroXGPVw0+khIcQ= 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-plugin-mux v0.16.0 h1:RCzXHGDYwUwwqfYYWJKBFaS3fQsWn/ZECEiW7p2023I= diff --git a/internal/protocolv6provider/resource_refinements_test.go b/internal/protocolv6provider/resource_refinements_test.go index 68d2b393..0a4c82eb 100644 --- a/internal/protocolv6provider/resource_refinements_test.go +++ b/internal/protocolv6provider/resource_refinements_test.go @@ -16,7 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfversion" ) -func TestAccResourceRefinements(t *testing.T) { +func TestAccResourceRefinements_Nullness(t *testing.T) { resource.UnitTest(t, resource.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty @@ -35,7 +35,7 @@ func TestAccResourceRefinements(t *testing.T) { }, Steps: []resource.TestStep{ { - // Typically, this config would return an error like: + // Without refinement support, this config would return an error like: // // Error: Invalid count argument // @@ -51,7 +51,7 @@ func TestAccResourceRefinements(t *testing.T) { // with a refinement (the result will definitely not be null, regardless of the value of "random_string.str.id") // that is eventually lost by the provider during PlanResourceChange. // - // When the provider implementation preserves the unknown value refinement, this configuration can + // When the provider implementation preserves the unknown value refinement for "nullness", this configuration can // plan/apply successfully and will create 3 resources, including the "random_string.other_str" resource. // Config: ` @@ -81,3 +81,69 @@ func TestAccResourceRefinements(t *testing.T) { }, }) } + +func TestAccResourceRefinements_Prefix(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + Source: "registry.terraform.io/hashicorp/random", + }, + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + //nolint:unparam // False positive in unparam related to map: https://github.com/mvdan/unparam/issues/40 + "corner": func() (tfprotov6.ProviderServer, error) { + return Server(), nil + }, + }, + Steps: []resource.TestStep{ + // Without refinement support, this config would return an error like: + // + // Error: Invalid count argument + // + // on terraform_plugin_test.tf line 25, in resource "random_string" "other_str": + // 25: count = startswith(corner_v6_refinements.foo.str_value, "known-prefix-") ? 1 : 0 + // + // The "count" value depends on resource attributes that cannot be determined + // until apply, so Terraform cannot predict how many instances will be created. + // To work around this, use the -target argument to first apply only the + // resources that the count depends on. + // + // This error occurs because the expression populating "str_value" is passing the provider an unknown value + // with a refinement (the result will definitely not be null and will have a prefix of "known-prefix-", regardless + // of the value of "random_string.str.id") that is eventually lost by the provider during PlanResourceChange. + // + // When the provider implementation preserves this unknown value refinement for string prefixing, this configuration can + // plan/apply successfully and will create 3 resources, including the "random_string.other_str" resource. + // + { + Config: ` + resource "random_string" "str" { + length = 12 + } + + resource "corner_v6_refinements" "foo" { + str_value = "known-prefix-${random_string.str.id}!" + } + + resource "random_string" "other_str" { + count = startswith(corner_v6_refinements.foo.str_value, "known-prefix-") ? 1 : 0 + length = 12 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("corner_v6_refinements.foo", tfjsonpath.New("str_value")), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("corner_v6_refinements.foo", tfjsonpath.New("str_value"), knownvalue.StringRegexp(regexp.MustCompile(`known-prefix-`))), + statecheck.ExpectKnownValue("random_string.other_str[0]", tfjsonpath.New("id"), knownvalue.NotNull()), + }, + }, + }, + }) +} From dd5ddf18f4b3568ab3edc8c6bdd78d743c608df3 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Fri, 18 Oct 2024 14:13:22 -0400 Subject: [PATCH 04/10] go mod --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index dbcf8f5a..4dedfa78 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/hashicorp/terraform-plugin-framework v1.12.0 github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 - github.com/hashicorp/terraform-plugin-go v0.24.0 + github.com/hashicorp/terraform-plugin-go v0.24.1-0.20241018175754-b81768d88041 github.com/hashicorp/terraform-plugin-mux v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 @@ -56,13 +56,13 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/net v0.28.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.23.0 // indirect + golang.org/x/sys v0.24.0 // indirect golang.org/x/text v0.17.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect - google.golang.org/grpc v1.66.2 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect ) diff --git a/go.sum b/go.sum index 555a45d5..eb4e0c83 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 h1:gm5b1kHgFFhaK github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1/go.mod h1:MsjL1sQ9L7wGwzJ5RjcI6FzEMdyoBnw+XK8ZnOvQOLY= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 h1:v3DapR8gsp3EM8fKMh6up9cJUFQ2iRaFsYLP8UJnCco= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0/go.mod h1:c3PnGE9pHBDfdEVG9t1S1C9ia5LW+gkFR0CygXlM8ak= -github.com/hashicorp/terraform-plugin-go v0.24.0 h1:2WpHhginCdVhFIrWHxDEg6RBn3YaWzR2o6qUeIEat2U= -github.com/hashicorp/terraform-plugin-go v0.24.0/go.mod h1:tUQ53lAsOyYSckFGEefGC5C8BAaO0ENqzFd3bQeuYQg= +github.com/hashicorp/terraform-plugin-go v0.24.1-0.20241018175754-b81768d88041 h1:+l7BxXAuR6gHF/mItMR5XbDMzzXwm+mY3A3pT0J4JhY= +github.com/hashicorp/terraform-plugin-go v0.24.1-0.20241018175754-b81768d88041/go.mod h1:K/lZ0kqNMhN0cTXEO1fhF+l6t5/1IWSDPe/G6ONvdIQ= 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-plugin-mux v0.16.0 h1:RCzXHGDYwUwwqfYYWJKBFaS3fQsWn/ZECEiW7p2023I= @@ -171,8 +171,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -190,8 +190,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -211,14 +211,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= -google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 7e045a5e86ba8582e0a4d49c5df4d42da76b71e3 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Tue, 17 Dec 2024 11:54:08 -0500 Subject: [PATCH 05/10] go mod --- go.mod | 8 ++++---- go.sum | 19 ++++++++++--------- .../resource_refinements.go | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 4dedfa78..d2dfce1a 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.22.7 require ( github.com/hashicorp/go-memdb v1.3.4 github.com/hashicorp/terraform-json v0.22.1 - github.com/hashicorp/terraform-plugin-framework v1.12.0 + github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20241203160659-a85b9d5263f0 github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 - github.com/hashicorp/terraform-plugin-go v0.24.1-0.20241018175754-b81768d88041 + github.com/hashicorp/terraform-plugin-go v0.25.1-0.20241126200214-bd716fcfe407 github.com/hashicorp/terraform-plugin-mux v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 @@ -30,7 +30,7 @@ require ( github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.6.1 // indirect + github.com/hashicorp/go-plugin v1.6.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.7.0 // indirect @@ -64,5 +64,5 @@ require ( google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.35.1 // indirect + google.golang.org/protobuf v1.35.2 // indirect ) diff --git a/go.sum b/go.sum index eb4e0c83..a23437b5 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8Io github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= -github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= +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-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -81,14 +81,14 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= -github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ= -github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE= +github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20241203160659-a85b9d5263f0 h1:Ui+0hth964oEzzhSwkuEfp2D9kF9A9gdcMmukOoj/xU= +github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20241203160659-a85b9d5263f0/go.mod h1:Rdj606MBP6yGeLrPjOEdIFpMsRZWP3E9lzGx++Zth6k= github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 h1:gm5b1kHgFFhaKFhm4h2TgvMUlNzFAtUqlcOWnWPm+9E= github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1/go.mod h1:MsjL1sQ9L7wGwzJ5RjcI6FzEMdyoBnw+XK8ZnOvQOLY= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 h1:v3DapR8gsp3EM8fKMh6up9cJUFQ2iRaFsYLP8UJnCco= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0/go.mod h1:c3PnGE9pHBDfdEVG9t1S1C9ia5LW+gkFR0CygXlM8ak= -github.com/hashicorp/terraform-plugin-go v0.24.1-0.20241018175754-b81768d88041 h1:+l7BxXAuR6gHF/mItMR5XbDMzzXwm+mY3A3pT0J4JhY= -github.com/hashicorp/terraform-plugin-go v0.24.1-0.20241018175754-b81768d88041/go.mod h1:K/lZ0kqNMhN0cTXEO1fhF+l6t5/1IWSDPe/G6ONvdIQ= +github.com/hashicorp/terraform-plugin-go v0.25.1-0.20241126200214-bd716fcfe407 h1:oLzKb+YiJIEq0EY3qGgQTxCLW2CaXN1rJp3yg1H11qI= +github.com/hashicorp/terraform-plugin-go v0.25.1-0.20241126200214-bd716fcfe407/go.mod h1:f8P2pHGkZrtdKLpCI2qIvrewUY+c4nTvtayqjJR9IcY= 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-plugin-mux v0.16.0 h1:RCzXHGDYwUwwqfYYWJKBFaS3fQsWn/ZECEiW7p2023I= @@ -144,8 +144,9 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= @@ -217,8 +218,8 @@ google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/protocolv6provider/resource_refinements.go b/internal/protocolv6provider/resource_refinements.go index 406a6061..6d2aef2d 100644 --- a/internal/protocolv6provider/resource_refinements.go +++ b/internal/protocolv6provider/resource_refinements.go @@ -131,7 +131,7 @@ func (r resourceRefinements) PlanResourceChange(ctx context.Context, req *tfprot newStrValue := tftypes.NewValue(tftypes.String, tftypes.UnknownValue). Refine(refinement.Refinements{ // str_value will never be null - refinement.KeyNullness: refinement.Nullness(false), + refinement.KeyNullness: refinement.NewNullness(false), }) // If the value exists in config (unknown or known), keep it. From c11826b7b5ee2b63c9da3f50e0bfe1adef39f7b3 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Thu, 16 Jan 2025 14:08:13 -0500 Subject: [PATCH 06/10] update go mod --- go.mod | 18 +++++++++--------- go.sum | 52 ++++++++++++++++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index c26aba81..81423a93 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.22.7 require ( github.com/hashicorp/go-memdb v1.3.4 github.com/hashicorp/terraform-json v0.24.0 - github.com/hashicorp/terraform-plugin-framework v1.13.0 + github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20250116190529-2e147507972f github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0 github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 - github.com/hashicorp/terraform-plugin-go v0.25.0 + github.com/hashicorp/terraform-plugin-go v0.25.1-0.20250116190359-f977ddce3f6c github.com/hashicorp/terraform-plugin-mux v0.17.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 github.com/hashicorp/terraform-plugin-testing v1.11.0 @@ -40,7 +40,7 @@ require ( github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect - github.com/hashicorp/terraform-registry-address v0.2.3 // indirect + github.com/hashicorp/terraform-registry-address v0.2.4 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -54,15 +54,15 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - golang.org/x/crypto v0.31.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/net v0.34.0 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect - google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.35.1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect ) diff --git a/go.sum b/go.sum index 3376b91e..ec9233eb 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,10 @@ github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+ github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -42,6 +46,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= @@ -81,14 +87,14 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q= github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow= -github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw= -github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU= +github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20250116190529-2e147507972f h1:q2bmhRSM68j8z76TygKncKkcXf7N0DJCtyn/FllnXOI= +github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20250116190529-2e147507972f/go.mod h1:zrIM+i1WrpIUn7ui8DDsJVg+nJCmteS/gisopH0M3AE= github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0 h1:I/N0g/eLZ1ZkLZXUQ0oRSXa8YG/EF0CEuQP1wXdrzKw= github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0/go.mod h1:t339KhmxnaF4SzdpxmqW8HnQBHVGYazwtfxU0qCs4eE= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 h1:v3DapR8gsp3EM8fKMh6up9cJUFQ2iRaFsYLP8UJnCco= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0/go.mod h1:c3PnGE9pHBDfdEVG9t1S1C9ia5LW+gkFR0CygXlM8ak= -github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974rdTxjqEhXJjbAyks= -github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw= +github.com/hashicorp/terraform-plugin-go v0.25.1-0.20250116190359-f977ddce3f6c h1:jRBaf696GIfT5LT+ZflOQUWI6MNEdntXFp6YO11ACUU= +github.com/hashicorp/terraform-plugin-go v0.25.1-0.20250116190359-f977ddce3f6c/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY= 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-plugin-mux v0.17.0 h1:/J3vv3Ps2ISkbLPiZOLspFcIZ0v5ycUXCEQScudGCCw= @@ -97,8 +103,8 @@ github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 h1:wyKCCtn6pBBL46c1uIIBNUOW github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0/go.mod h1:B0Al8NyYVr8Mp/KLwssKXG1RqnTk7FySqSn4fRuLNgw= github.com/hashicorp/terraform-plugin-testing v1.11.0 h1:MeDT5W3YHbONJt2aPQyaBsgQeAIckwPX41EUHXEn29A= github.com/hashicorp/terraform-plugin-testing v1.11.0/go.mod h1:WNAHQ3DcgV/0J+B15WTE6hDvxcUdkPPpnB1FR3M910U= -github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= -github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= +github.com/hashicorp/terraform-registry-address v0.2.4 h1:JXu/zHB2Ymg/TGVCRu10XqNa4Sh2bWcqCNyKWjnCPJA= +github.com/hashicorp/terraform-registry-address v0.2.4/go.mod h1:tUNYTVyCtU4OIGXXMDp7WNcJ+0W1B4nmstVDgHMjfAU= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= @@ -161,10 +167,20 @@ github.com/zclconf/go-cty v1.16.1 h1:a5TZEPzBFFR53udlIKApXzj8JIF4ZNQ6abH79z5R1S0 github.com/zclconf/go-cty v1.16.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +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= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= @@ -172,8 +188,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -191,8 +207,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -212,14 +228,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From bc82d3cdf5eaeff700223f6136c289bdb0482198 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Thu, 16 Jan 2025 14:14:36 -0500 Subject: [PATCH 07/10] remove temp plugin-go tests --- .../resource_refinements.go | 193 ------------------ .../resource_refinements_test.go | 149 -------------- internal/protocolv6provider/server.go | 19 -- 3 files changed, 361 deletions(-) delete mode 100644 internal/protocolv6provider/resource_refinements.go delete mode 100644 internal/protocolv6provider/resource_refinements_test.go diff --git a/internal/protocolv6provider/resource_refinements.go b/internal/protocolv6provider/resource_refinements.go deleted file mode 100644 index 6d2aef2d..00000000 --- a/internal/protocolv6provider/resource_refinements.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package protocolv6 - -import ( - "context" - "fmt" - - "github.com/hashicorp/terraform-plugin-go/tfprotov6" - "github.com/hashicorp/terraform-plugin-go/tftypes" - "github.com/hashicorp/terraform-plugin-go/tftypes/refinement" -) - -type resourceRefinements struct{} - -func (r resourceRefinements) schemaType() tftypes.Type { - return tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "str_value": tftypes.String, - }, - } -} - -func (r resourceRefinements) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyResourceChangeRequest) (*tfprotov6.ApplyResourceChangeResponse, error) { - plannedState, err := tftypes.ValueFromMsgPack(req.PlannedState.MsgPack, r.schemaType()) //nolint - if err != nil { - return &tfprotov6.ApplyResourceChangeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error decoding prior state", - Detail: fmt.Sprintf("Error decoding prior state: %s", err.Error()), - }, - }, - }, nil - } - - // Destroy op, state should be null - if plannedState.IsNull() { - return &tfprotov6.ApplyResourceChangeResponse{ - NewState: req.PlannedState, - }, nil - } - - objVal := map[string]tftypes.Value{} - - plannedState.As(&objVal) //nolint - - newStrValue := tftypes.NewValue(tftypes.String, "hello world!") - - // If the value exists in config, use it. - if strValue, ok := objVal["str_value"]; ok && strValue.IsKnown() { - newStrValue = strValue - } - - newState, err := tfprotov6.NewDynamicValue( - r.schemaType(), - tftypes.NewValue( - r.schemaType(), - map[string]tftypes.Value{ - "str_value": newStrValue, - }, - ), - ) - if err != nil { - return &tfprotov6.ApplyResourceChangeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error encoding state", - Detail: fmt.Sprintf("Error encoding planned state: %s", err.Error()), - }, - }, - }, nil - } - - return &tfprotov6.ApplyResourceChangeResponse{ - NewState: &newState, - }, nil -} - -func (r resourceRefinements) ImportResourceState(ctx context.Context, req *tfprotov6.ImportResourceStateRequest) (*tfprotov6.ImportResourceStateResponse, error) { - return &tfprotov6.ImportResourceStateResponse{}, nil -} - -func (r resourceRefinements) MoveResourceState(ctx context.Context, req *tfprotov6.MoveResourceStateRequest) (*tfprotov6.MoveResourceStateResponse, error) { - return &tfprotov6.MoveResourceStateResponse{}, nil -} - -func (r resourceRefinements) PlanResourceChange(ctx context.Context, req *tfprotov6.PlanResourceChangeRequest) (*tfprotov6.PlanResourceChangeResponse, error) { - if req.PriorState.MsgPack != nil { - priorState, err := tftypes.ValueFromMsgPack(req.PriorState.MsgPack, r.schemaType()) //nolint - if err != nil { - return &tfprotov6.PlanResourceChangeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error decoding prior state", - Detail: fmt.Sprintf("Error decoding prior state: %s", err.Error()), - }, - }, - }, nil - } - - // Update op, keep prior state - if !priorState.IsNull() { - return &tfprotov6.PlanResourceChangeResponse{ - PlannedState: req.PriorState, - }, nil - } - } - - proposedNewState, err := tftypes.ValueFromMsgPack(req.ProposedNewState.MsgPack, r.schemaType()) //nolint - if err != nil { - return &tfprotov6.PlanResourceChangeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error decoding proposed state", - Detail: fmt.Sprintf("Error decoding proposed state: %s", err.Error()), - }, - }, - }, nil - } - - objVal := map[string]tftypes.Value{} - - proposedNewState.As(&objVal) //nolint - - newStrValue := tftypes.NewValue(tftypes.String, tftypes.UnknownValue). - Refine(refinement.Refinements{ - // str_value will never be null - refinement.KeyNullness: refinement.NewNullness(false), - }) - - // If the value exists in config (unknown or known), keep it. - if strValue, ok := objVal["str_value"]; ok && !strValue.IsNull() { - newStrValue = strValue - } - - plannedState, err := tfprotov6.NewDynamicValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "str_value": tftypes.String, - }, - }, tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "str_value": tftypes.String, - }, - }, map[string]tftypes.Value{ - "str_value": newStrValue, - })) - if err != nil { - return &tfprotov6.PlanResourceChangeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error encoding state", - Detail: fmt.Sprintf("Error encoding planned state: %s", err.Error()), - }, - }, - }, nil - } - - return &tfprotov6.PlanResourceChangeResponse{ - PlannedState: &plannedState, - }, nil -} - -func (r resourceRefinements) ReadResource(ctx context.Context, req *tfprotov6.ReadResourceRequest) (*tfprotov6.ReadResourceResponse, error) { - return &tfprotov6.ReadResourceResponse{ - NewState: req.CurrentState, - }, nil -} - -func (r resourceRefinements) UpgradeResourceState(ctx context.Context, req *tfprotov6.UpgradeResourceStateRequest) (*tfprotov6.UpgradeResourceStateResponse, error) { - rawStateValue, _ := req.RawState.UnmarshalWithOpts(r.schemaType(), tfprotov6.UnmarshalOpts{ - ValueFromJSONOpts: tftypes.ValueFromJSONOpts{ - IgnoreUndefinedAttributes: true, - }, - }) - - upgradedState, _ := tfprotov6.NewDynamicValue(r.schemaType(), rawStateValue) - - return &tfprotov6.UpgradeResourceStateResponse{ - UpgradedState: &upgradedState, - Diagnostics: []*tfprotov6.Diagnostic{}, - }, nil -} - -func (r resourceRefinements) ValidateResourceConfig(ctx context.Context, req *tfprotov6.ValidateResourceConfigRequest) (*tfprotov6.ValidateResourceConfigResponse, error) { - return &tfprotov6.ValidateResourceConfigResponse{}, nil -} diff --git a/internal/protocolv6provider/resource_refinements_test.go b/internal/protocolv6provider/resource_refinements_test.go deleted file mode 100644 index 0a4c82eb..00000000 --- a/internal/protocolv6provider/resource_refinements_test.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package protocolv6 - -import ( - "regexp" - "testing" - - "github.com/hashicorp/terraform-plugin-go/tfprotov6" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/knownvalue" - "github.com/hashicorp/terraform-plugin-testing/plancheck" - "github.com/hashicorp/terraform-plugin-testing/statecheck" - "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" - "github.com/hashicorp/terraform-plugin-testing/tfversion" -) - -func TestAccResourceRefinements_Nullness(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty - tfversion.SkipBelow(tfversion.Version1_6_0), - }, - ExternalProviders: map[string]resource.ExternalProvider{ - "random": { - Source: "registry.terraform.io/hashicorp/random", - }, - }, - ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ - //nolint:unparam // False positive in unparam related to map: https://github.com/mvdan/unparam/issues/40 - "corner": func() (tfprotov6.ProviderServer, error) { - return Server(), nil - }, - }, - Steps: []resource.TestStep{ - { - // Without refinement support, this config would return an error like: - // - // Error: Invalid count argument - // - // on terraform_plugin_test.tf line 23, in resource "random_string" "other_str": - // 23: count = corner_v6_refinements.foo.str_value != null ? 1 : 0 - // - // The "count" value depends on resource attributes that cannot be determined - // until apply, so Terraform cannot predict how many instances will be created. - // To work around this, use the -target argument to first apply only the - // resources that the count depends on. - // - // This error occurs because the expression populating "str_value" is passing the provider an unknown value - // with a refinement (the result will definitely not be null, regardless of the value of "random_string.str.id") - // that is eventually lost by the provider during PlanResourceChange. - // - // When the provider implementation preserves the unknown value refinement for "nullness", this configuration can - // plan/apply successfully and will create 3 resources, including the "random_string.other_str" resource. - // - Config: ` - resource "random_string" "str" { - length = 12 - } - - resource "corner_v6_refinements" "foo" { - str_value = "This string is ${random_string.str.id}!" - } - - resource "random_string" "other_str" { - count = corner_v6_refinements.foo.str_value != null ? 1 : 0 - length = 12 - } - `, - ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{ - plancheck.ExpectUnknownValue("corner_v6_refinements.foo", tfjsonpath.New("str_value")), - }, - }, - ConfigStateChecks: []statecheck.StateCheck{ - statecheck.ExpectKnownValue("corner_v6_refinements.foo", tfjsonpath.New("str_value"), knownvalue.StringRegexp(regexp.MustCompile(`This string is`))), - statecheck.ExpectKnownValue("random_string.other_str[0]", tfjsonpath.New("id"), knownvalue.NotNull()), - }, - }, - }, - }) -} - -func TestAccResourceRefinements_Prefix(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty - tfversion.SkipBelow(tfversion.Version1_6_0), - }, - ExternalProviders: map[string]resource.ExternalProvider{ - "random": { - Source: "registry.terraform.io/hashicorp/random", - }, - }, - ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ - //nolint:unparam // False positive in unparam related to map: https://github.com/mvdan/unparam/issues/40 - "corner": func() (tfprotov6.ProviderServer, error) { - return Server(), nil - }, - }, - Steps: []resource.TestStep{ - // Without refinement support, this config would return an error like: - // - // Error: Invalid count argument - // - // on terraform_plugin_test.tf line 25, in resource "random_string" "other_str": - // 25: count = startswith(corner_v6_refinements.foo.str_value, "known-prefix-") ? 1 : 0 - // - // The "count" value depends on resource attributes that cannot be determined - // until apply, so Terraform cannot predict how many instances will be created. - // To work around this, use the -target argument to first apply only the - // resources that the count depends on. - // - // This error occurs because the expression populating "str_value" is passing the provider an unknown value - // with a refinement (the result will definitely not be null and will have a prefix of "known-prefix-", regardless - // of the value of "random_string.str.id") that is eventually lost by the provider during PlanResourceChange. - // - // When the provider implementation preserves this unknown value refinement for string prefixing, this configuration can - // plan/apply successfully and will create 3 resources, including the "random_string.other_str" resource. - // - { - Config: ` - resource "random_string" "str" { - length = 12 - } - - resource "corner_v6_refinements" "foo" { - str_value = "known-prefix-${random_string.str.id}!" - } - - resource "random_string" "other_str" { - count = startswith(corner_v6_refinements.foo.str_value, "known-prefix-") ? 1 : 0 - length = 12 - } - `, - ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{ - plancheck.ExpectUnknownValue("corner_v6_refinements.foo", tfjsonpath.New("str_value")), - }, - }, - ConfigStateChecks: []statecheck.StateCheck{ - statecheck.ExpectKnownValue("corner_v6_refinements.foo", tfjsonpath.New("str_value"), knownvalue.StringRegexp(regexp.MustCompile(`known-prefix-`))), - statecheck.ExpectKnownValue("random_string.other_str[0]", tfjsonpath.New("id"), knownvalue.NotNull()), - }, - }, - }, - }) -} diff --git a/internal/protocolv6provider/server.go b/internal/protocolv6provider/server.go index 1e9258e4..acc4ecc4 100644 --- a/internal/protocolv6provider/server.go +++ b/internal/protocolv6provider/server.go @@ -129,25 +129,6 @@ func Server() tfprotov6.ProviderServer { "corner_v6_time": dataSourceTime{}, "corner_v6_deferred_action": dataSourceDeferredAction{}, }, - resourceSchemas: map[string]*tfprotov6.Schema{ - "corner_v6_refinements": { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "str_value", - Type: tftypes.String, - Description: "Computed string value that will definitely not be null.", - DescriptionKind: tfprotov6.StringKindPlain, - Optional: true, - Computed: true, - }, - }, - }, - }, - }, - resourceRouter: resourceRouter{ - "corner_v6_refinements": resourceRefinements{}, - }, functions: map[string]*tfprotov6.Function{ "bool": { Parameters: []*tfprotov6.FunctionParameter{ From ca749c93b95fe09e4889c1f19faf707025107272 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Thu, 16 Jan 2025 18:12:55 -0500 Subject: [PATCH 08/10] add refinement producer, consumer, and invalid tests --- go.mod | 1 + go.sum | 2 + .../invalid_refinement_resource.go | 88 ++++++ .../invalid_refinement_resource_test.go | 48 +++ internal/framework5provider/provider.go | 3 + .../refinement_consumer_resource.go | 144 +++++++++ .../refinement_consumer_resource_test.go | 204 +++++++++++++ .../refinement_producer_resource.go | 158 ++++++++++ .../refinement_producer_resource_test.go | 286 ++++++++++++++++++ .../invalid_refinement_resource.go | 88 ++++++ .../invalid_refinement_resource_test.go | 48 +++ internal/framework6provider/provider.go | 3 + .../refinement_consumer_resource.go | 144 +++++++++ .../refinement_consumer_resource_test.go | 204 +++++++++++++ .../refinement_producer_resource.go | 158 ++++++++++ .../refinement_producer_resource_test.go | 286 ++++++++++++++++++ 16 files changed, 1865 insertions(+) create mode 100644 internal/framework5provider/invalid_refinement_resource.go create mode 100644 internal/framework5provider/invalid_refinement_resource_test.go create mode 100644 internal/framework5provider/refinement_consumer_resource.go create mode 100644 internal/framework5provider/refinement_consumer_resource_test.go create mode 100644 internal/framework5provider/refinement_producer_resource.go create mode 100644 internal/framework5provider/refinement_producer_resource_test.go create mode 100644 internal/framework6provider/invalid_refinement_resource.go create mode 100644 internal/framework6provider/invalid_refinement_resource_test.go create mode 100644 internal/framework6provider/refinement_consumer_resource.go create mode 100644 internal/framework6provider/refinement_consumer_resource_test.go create mode 100644 internal/framework6provider/refinement_producer_resource.go create mode 100644 internal/framework6provider/refinement_producer_resource_test.go diff --git a/go.mod b/go.mod index 81423a93..707a1d3c 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20250116190529-2e147507972f github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0 github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.16.1-0.20250116191909-1452aa1f60c0 github.com/hashicorp/terraform-plugin-go v0.25.1-0.20250116190359-f977ddce3f6c github.com/hashicorp/terraform-plugin-mux v0.17.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 diff --git a/go.sum b/go.sum index ec9233eb..fba02832 100644 --- a/go.sum +++ b/go.sum @@ -93,6 +93,8 @@ github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0 h1:I/N0g/eLZ1ZkL github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0/go.mod h1:t339KhmxnaF4SzdpxmqW8HnQBHVGYazwtfxU0qCs4eE= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 h1:v3DapR8gsp3EM8fKMh6up9cJUFQ2iRaFsYLP8UJnCco= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0/go.mod h1:c3PnGE9pHBDfdEVG9t1S1C9ia5LW+gkFR0CygXlM8ak= +github.com/hashicorp/terraform-plugin-framework-validators v0.16.1-0.20250116191909-1452aa1f60c0 h1:KaMZ7N/IYOT8OumvsSUADHSn31fWGEpxbdQwecoNNT0= +github.com/hashicorp/terraform-plugin-framework-validators v0.16.1-0.20250116191909-1452aa1f60c0/go.mod h1:MkoT/VDC6IAoKipt31OthEQXfgkP+7DnV6Q8tSwn27A= github.com/hashicorp/terraform-plugin-go v0.25.1-0.20250116190359-f977ddce3f6c h1:jRBaf696GIfT5LT+ZflOQUWI6MNEdntXFp6YO11ACUU= github.com/hashicorp/terraform-plugin-go v0.25.1-0.20250116190359-f977ddce3f6c/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= diff --git a/internal/framework5provider/invalid_refinement_resource.go b/internal/framework5provider/invalid_refinement_resource.go new file mode 100644 index 00000000..d13f2ee1 --- /dev/null +++ b/internal/framework5provider/invalid_refinement_resource.go @@ -0,0 +1,88 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = InvalidRefinementResource{} + +func NewInvalidRefinement() resource.Resource { + return &InvalidRefinementResource{} +} + +type InvalidRefinementResource struct{} + +func (r InvalidRefinementResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_invalid_refinement" +} + +func (r InvalidRefinementResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "string_with_prefix": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.WillHavePrefix("prefix://"), + }, + }, + }, + } +} + +func (r InvalidRefinementResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data InvalidRefinementResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + data.StringWithPrefix = types.StringValue("not correct") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r InvalidRefinementResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data InvalidRefinementResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + data.StringWithPrefix = types.StringValue("not correct") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r InvalidRefinementResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data InvalidRefinementResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + data.StringWithPrefix = types.StringValue("not correct") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r InvalidRefinementResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +type InvalidRefinementResourceModel struct { + StringWithPrefix types.String `tfsdk:"string_with_prefix"` +} diff --git a/internal/framework5provider/invalid_refinement_resource_test.go b/internal/framework5provider/invalid_refinement_resource_test.go new file mode 100644 index 00000000..b5b6a944 --- /dev/null +++ b/internal/framework5provider/invalid_refinement_resource_test.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +// This resource tests Terraform's data consistency rules for refinements. It has an invalid +// promise (string must have prefix of "prefix://") in the plan, which will fail during apply. +func TestInvalidRefinementResource(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_invalid_refinement" "test" {} + + resource "terraform_data" "test_out" { + count = startswith(framework_invalid_refinement.test.string_with_prefix, "prefix://") ? 1 : 0 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("framework_invalid_refinement.test", tfjsonpath.New("string_with_prefix")), + plancheck.ExpectResourceAction("terraform_data.test_out[0]", plancheck.ResourceActionCreate), + }, + }, + ExpectError: regexp.MustCompile(`Error: Provider produced inconsistent result after apply`), + }, + }, + }) +} diff --git a/internal/framework5provider/provider.go b/internal/framework5provider/provider.go index 76aa4077..438268a4 100644 --- a/internal/framework5provider/provider.go +++ b/internal/framework5provider/provider.go @@ -91,6 +91,9 @@ func (p *testProvider) Resources(_ context.Context) []func() resource.Resource { NewTFSDKReflectionResource, NewMoveStateResource, NewSetNestedBlockWithDefaultsResource, + NewRefinementProducer, + NewRefinementConsumer, + NewInvalidRefinement, } } diff --git a/internal/framework5provider/refinement_consumer_resource.go b/internal/framework5provider/refinement_consumer_resource.go new file mode 100644 index 00000000..2c3200d1 --- /dev/null +++ b/internal/framework5provider/refinement_consumer_resource.go @@ -0,0 +1,144 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/float64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = RefinementConsumerResource{} +var _ resource.ResourceWithConfigValidators = RefinementConsumerResource{} + +func NewRefinementConsumer() resource.Resource { + return &RefinementConsumerResource{} +} + +type RefinementConsumerResource struct{} + +func (r RefinementConsumerResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_refinement_consumer" +} + +func (r RefinementConsumerResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.Conflicting( + path.MatchRoot("conflicting_bool_one"), + path.MatchRoot("conflicting_bool_two"), + ), + } +} + +func (r RefinementConsumerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "conflicting_bool_one": schema.BoolAttribute{ + Optional: true, + }, + "conflicting_bool_two": schema.BoolAttribute{ + Optional: true, + }, + "at_most_int64": schema.Int64Attribute{ + Optional: true, + Validators: []validator.Int64{ + int64validator.AtMost(9), + }, + }, + "at_least_float64": schema.Float64Attribute{ + Optional: true, + Validators: []validator.Float64{ + float64validator.AtLeast(20.235), + }, + }, + "at_least_list_size": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(6), + }, + }, + "at_most_list_size": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "at_least_set_size": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(6), + }, + }, + "at_most_string_length": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtMost(8), + }, + }, + }, + } +} + +func (r RefinementConsumerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data RefinementConsumerResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r RefinementConsumerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data RefinementConsumerResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r RefinementConsumerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data RefinementConsumerResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r RefinementConsumerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +type RefinementConsumerResourceModel struct { + ConflictingBoolOne types.Bool `tfsdk:"conflicting_bool_one"` + ConflictingBoolTwo types.Bool `tfsdk:"conflicting_bool_two"` + AtMostInt64 types.Int64 `tfsdk:"at_most_int64"` + AtLeastFloat64 types.Float64 `tfsdk:"at_least_float64"` + AtLeastListSize types.List `tfsdk:"at_least_list_size"` + AtMostListSize types.List `tfsdk:"at_most_list_size"` + AtLeastSetSize types.Set `tfsdk:"at_least_set_size"` + AtMostStringLength types.String `tfsdk:"at_most_string_length"` +} diff --git a/internal/framework5provider/refinement_consumer_resource_test.go b/internal/framework5provider/refinement_consumer_resource_test.go new file mode 100644 index 00000000..d605132e --- /dev/null +++ b/internal/framework5provider/refinement_consumer_resource_test.go @@ -0,0 +1,204 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestRefinementConsumerResource_conflicting_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + conflicting_bool_one = framework_refinement_producer.test.bool_with_not_null + conflicting_bool_two = framework_refinement_producer.test.bool_with_not_null + } + `, + // Even though "framework_refinement_producer.test.bool_with_not_null" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Error: Invalid Attribute Combination`), + }, + }, + }) +} + +func TestRefinementConsumerResource_at_most_string_length_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + at_most_string_length = framework_refinement_producer.test.string_with_prefix + } + `, + // Even though "framework_refinement_producer.test.string_with_prefix" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Attribute at_most_string_length string length must be at most 8`), + }, + }, + }) +} + +func TestRefinementConsumerResource_at_most_int64_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + at_most_int64 = framework_refinement_producer.test.int64_with_bounds + } + `, + // Even though "framework_refinement_producer.test.int64_with_bounds" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Attribute at_most_int64 value must be at most 9`), + }, + }, + }) +} + +func TestRefinementConsumerResource_at_least_float64_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + at_least_float64 = framework_refinement_producer.test.float64_with_bounds + } + `, + // Even though "framework_refinement_producer.test.float64_with_bounds" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Attribute at_least_float64 value must be at least 20.235000`), + }, + }, + }) +} + +func TestRefinementConsumerResource_at_least_list_size_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + at_least_list_size = framework_refinement_producer.test.list_with_length_bounds + } + `, + // Even though "framework_refinement_producer.test.list_with_length_bounds" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Attribute at_least_list_size list must contain at least 6 elements`), + }, + }, + }) +} + +func TestRefinementConsumerResource_at_most_list_size_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + at_most_list_size = framework_refinement_producer.test.list_with_length_bounds + } + `, + // Even though "framework_refinement_producer.test.list_with_length_bounds" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Attribute at_most_list_size list must contain at most 1 elements`), + }, + }, + }) +} + +func TestRefinementConsumerResource_at_least_set_size_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + at_least_set_size = framework_refinement_producer.test.list_with_length_bounds + } + `, + // Even though "framework_refinement_producer.test.list_with_length_bounds" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Attribute at_least_set_size set must contain at least 6 elements`), + }, + }, + }) +} diff --git a/internal/framework5provider/refinement_producer_resource.go b/internal/framework5provider/refinement_producer_resource.go new file mode 100644 index 00000000..932a5d44 --- /dev/null +++ b/internal/framework5provider/refinement_producer_resource.go @@ -0,0 +1,158 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/float64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = RefinementProducerResource{} + +func NewRefinementProducer() resource.Resource { + return &RefinementProducerResource{} +} + +type RefinementProducerResource struct{} + +func (r RefinementProducerResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_refinement_producer" +} + +func (r RefinementProducerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "bool_with_not_null": schema.BoolAttribute{ + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.WillNotBeNull(), + }, + }, + "int64_with_bounds": schema.Int64Attribute{ + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.WillBeAtLeast(10), + int64planmodifier.WillBeAtMost(20), + }, + }, + "float64_with_bounds": schema.Float64Attribute{ + Computed: true, + PlanModifiers: []planmodifier.Float64{ + float64planmodifier.WillBeAtLeast(10.234), + float64planmodifier.WillBeAtMost(20.234), + }, + }, + "list_with_length_bounds": schema.ListAttribute{ + ElementType: types.StringType, + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.WillHaveSizeAtLeast(2), + listplanmodifier.WillHaveSizeAtMost(5), + }, + }, + "string_with_prefix": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.WillHavePrefix("prefix://"), + }, + }, + }, + } +} + +func (r RefinementProducerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data RefinementProducerResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + data.BoolWithNotNull = types.BoolValue(true) + data.Int64WithBounds = types.Int64Value(15) + data.Float64WithBounds = types.Float64Value(12.102) + data.ListWithLengthBounds = types.ListValueMust( + types.StringType, + []attr.Value{ + types.StringValue("hello"), + types.StringValue("there"), + types.StringValue("world!"), + }, + ) + data.StringWithPrefix = types.StringValue("prefix://hello-world") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r RefinementProducerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data RefinementProducerResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + data.BoolWithNotNull = types.BoolValue(true) + data.Int64WithBounds = types.Int64Value(15) + data.Float64WithBounds = types.Float64Value(12.102) + data.ListWithLengthBounds = types.ListValueMust( + types.StringType, + []attr.Value{ + types.StringValue("hello"), + types.StringValue("there"), + types.StringValue("world!"), + }, + ) + data.StringWithPrefix = types.StringValue("prefix://hello-world") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r RefinementProducerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data RefinementProducerResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + data.BoolWithNotNull = types.BoolValue(true) + data.Int64WithBounds = types.Int64Value(15) + data.Float64WithBounds = types.Float64Value(12.102) + data.ListWithLengthBounds = types.ListValueMust( + types.StringType, + []attr.Value{ + types.StringValue("hello"), + types.StringValue("there"), + types.StringValue("world!"), + }, + ) + data.StringWithPrefix = types.StringValue("prefix://hello-world") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r RefinementProducerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +type RefinementProducerResourceModel struct { + BoolWithNotNull types.Bool `tfsdk:"bool_with_not_null"` + Int64WithBounds types.Int64 `tfsdk:"int64_with_bounds"` + Float64WithBounds types.Float64 `tfsdk:"float64_with_bounds"` + ListWithLengthBounds types.List `tfsdk:"list_with_length_bounds"` + StringWithPrefix types.String `tfsdk:"string_with_prefix"` +} diff --git a/internal/framework5provider/refinement_producer_resource_test.go b/internal/framework5provider/refinement_producer_resource_test.go new file mode 100644 index 00000000..f264ce68 --- /dev/null +++ b/internal/framework5provider/refinement_producer_resource_test.go @@ -0,0 +1,286 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestRefinementProducerResource_basic(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // This test runs against all Terraform versions to ensure provider-returned refinements don't cause unexpected issues (earlier versions of core should ignore them) + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + output "test_out" { + value = framework_refinement_producer.test + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownOutputValueAtPath("test_out", tfjsonpath.New("bool_with_not_null")), + plancheck.ExpectUnknownOutputValueAtPath("test_out", tfjsonpath.New("int64_with_bounds")), + plancheck.ExpectUnknownOutputValueAtPath("test_out", tfjsonpath.New("float64_with_bounds")), + plancheck.ExpectUnknownOutputValueAtPath("test_out", tfjsonpath.New("list_with_length_bounds")), + plancheck.ExpectUnknownOutputValueAtPath("test_out", tfjsonpath.New("string_with_prefix")), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue("test_out", knownvalue.ObjectExact( + map[string]knownvalue.Check{ + "bool_with_not_null": knownvalue.Bool(true), + "int64_with_bounds": knownvalue.Int64Exact(15), + "float64_with_bounds": knownvalue.Float64Exact(12.102), + "list_with_length_bounds": knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("hello"), + knownvalue.StringExact("there"), + knownvalue.StringExact("world!"), + }), + "string_with_prefix": knownvalue.StringExact("prefix://hello-world"), + }, + )), + }, + }, + }, + }) +} + +func TestRefinementProducerResource_notnull(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + // Without refinement support in the provider, this config would return an error like: + // + // Error: Invalid count argument + // + // on terraform_plugin_test.tf line 15, in resource "terraform_data" "test_out": + // 15: count = framework_refinement_producer.test.bool_with_not_null != null ? 1 : 0 + // + // The "count" value depends on resource attributes that cannot be determined + // until apply, so Terraform cannot predict how many instances will be created. + // To work around this, use the -target argument to first apply only the + // resources that the count depends on. + // + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "terraform_data" "test_out" { + count = framework_refinement_producer.test.bool_with_not_null != null ? 1 : 0 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("framework_refinement_producer.test", tfjsonpath.New("bool_with_not_null")), + plancheck.ExpectResourceAction("terraform_data.test_out[0]", plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("framework_refinement_producer.test", tfjsonpath.New("bool_with_not_null"), knownvalue.Bool(true)), + }, + }, + }, + }) +} + +func TestRefinementProducerResource_stringprefix(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + // Without refinement support in the provider, this config would return an error like: + // + // Error: Invalid count argument + // + // on terraform_plugin_test.tf line 15, in resource "terraform_data" "test_out": + // 15: count = startswith(framework_refinement_producer.test.string_with_prefix, "prefix://") ? 1 : 0 + // + // The "count" value depends on resource attributes that cannot be determined + // until apply, so Terraform cannot predict how many instances will be created. + // To work around this, use the -target argument to first apply only the + // resources that the count depends on. + // + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "terraform_data" "test_out" { + count = startswith(framework_refinement_producer.test.string_with_prefix, "prefix://") ? 1 : 0 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("framework_refinement_producer.test", tfjsonpath.New("string_with_prefix")), + plancheck.ExpectResourceAction("terraform_data.test_out[0]", plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("framework_refinement_producer.test", tfjsonpath.New("string_with_prefix"), knownvalue.StringExact("prefix://hello-world")), + }, + }, + }, + }) +} + +func TestRefinementProducerResource_int64_bounds(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + // Without refinement support in the provider, this config would return an error like: + // + // Error: Invalid count argument + // + // on terraform_plugin_test.tf line 15, in resource "terraform_data" "test_out": + // 15: count = framework_refinement_producer.test.int64_with_bounds > 9 ? 1 : 0 + // + // The "count" value depends on resource attributes that cannot be determined + // until apply, so Terraform cannot predict how many instances will be created. + // To work around this, use the -target argument to first apply only the + // resources that the count depends on. + // + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "terraform_data" "test_out" { + count = framework_refinement_producer.test.int64_with_bounds > 9 ? 1 : 0 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("framework_refinement_producer.test", tfjsonpath.New("int64_with_bounds")), + plancheck.ExpectResourceAction("terraform_data.test_out[0]", plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("framework_refinement_producer.test", tfjsonpath.New("int64_with_bounds"), knownvalue.Int64Exact(15)), + }, + }, + }, + }) +} +func TestRefinementProducerResource_float64_bounds(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + // Without refinement support in the provider, this config would return an error like: + // + // Error: Invalid count argument + // + // on terraform_plugin_test.tf line 15, in resource "terraform_data" "test_out": + // 15: count = framework_refinement_producer.test.float64_with_bounds > 10.233 ? 1 : 0 + // + // The "count" value depends on resource attributes that cannot be determined + // until apply, so Terraform cannot predict how many instances will be created. + // To work around this, use the -target argument to first apply only the + // resources that the count depends on. + // + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "terraform_data" "test_out" { + count = framework_refinement_producer.test.float64_with_bounds > 10.233 ? 1 : 0 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("framework_refinement_producer.test", tfjsonpath.New("float64_with_bounds")), + plancheck.ExpectResourceAction("terraform_data.test_out[0]", plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("framework_refinement_producer.test", tfjsonpath.New("float64_with_bounds"), knownvalue.Float64Exact(12.102)), + }, + }, + }, + }) +} + +func TestRefinementProducerResource_list_length_bounds(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + // Without refinement support in the provider, this config would return an error like: + // + // Error: Invalid count argument + + // on terraform_plugin_test.tf line 15, in resource "terraform_data" "test_out": + // 15: count = length(framework_refinement_producer.test.list_with_length_bounds) > 1 ? 1 : 0 + + // The "count" value depends on resource attributes that cannot be determined + // until apply, so Terraform cannot predict how many instances will be created. + // To work around this, use the -target argument to first apply only the + // resources that the count depends on. + // + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "terraform_data" "test_out" { + count = length(framework_refinement_producer.test.list_with_length_bounds) > 1 ? 1 : 0 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("framework_refinement_producer.test", tfjsonpath.New("list_with_length_bounds")), + plancheck.ExpectResourceAction("terraform_data.test_out[0]", plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("framework_refinement_producer.test", tfjsonpath.New("list_with_length_bounds"), knownvalue.ListExact( + []knownvalue.Check{ + knownvalue.StringExact("hello"), + knownvalue.StringExact("there"), + knownvalue.StringExact("world!"), + }, + )), + }, + }, + }, + }) +} diff --git a/internal/framework6provider/invalid_refinement_resource.go b/internal/framework6provider/invalid_refinement_resource.go new file mode 100644 index 00000000..d13f2ee1 --- /dev/null +++ b/internal/framework6provider/invalid_refinement_resource.go @@ -0,0 +1,88 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = InvalidRefinementResource{} + +func NewInvalidRefinement() resource.Resource { + return &InvalidRefinementResource{} +} + +type InvalidRefinementResource struct{} + +func (r InvalidRefinementResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_invalid_refinement" +} + +func (r InvalidRefinementResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "string_with_prefix": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.WillHavePrefix("prefix://"), + }, + }, + }, + } +} + +func (r InvalidRefinementResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data InvalidRefinementResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + data.StringWithPrefix = types.StringValue("not correct") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r InvalidRefinementResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data InvalidRefinementResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + data.StringWithPrefix = types.StringValue("not correct") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r InvalidRefinementResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data InvalidRefinementResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + data.StringWithPrefix = types.StringValue("not correct") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r InvalidRefinementResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +type InvalidRefinementResourceModel struct { + StringWithPrefix types.String `tfsdk:"string_with_prefix"` +} diff --git a/internal/framework6provider/invalid_refinement_resource_test.go b/internal/framework6provider/invalid_refinement_resource_test.go new file mode 100644 index 00000000..87149abc --- /dev/null +++ b/internal/framework6provider/invalid_refinement_resource_test.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +// This resource tests Terraform's data consistency rules for refinements. It has an invalid +// promise (string must have prefix of "prefix://") in the plan, which will fail during apply. +func TestInvalidRefinementResource(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_invalid_refinement" "test" {} + + resource "terraform_data" "test_out" { + count = startswith(framework_invalid_refinement.test.string_with_prefix, "prefix://") ? 1 : 0 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("framework_invalid_refinement.test", tfjsonpath.New("string_with_prefix")), + plancheck.ExpectResourceAction("terraform_data.test_out[0]", plancheck.ResourceActionCreate), + }, + }, + ExpectError: regexp.MustCompile(`Error: Provider produced inconsistent result after apply`), + }, + }, + }) +} diff --git a/internal/framework6provider/provider.go b/internal/framework6provider/provider.go index e5a3df36..a9be0ef1 100644 --- a/internal/framework6provider/provider.go +++ b/internal/framework6provider/provider.go @@ -91,6 +91,9 @@ func (p *testProvider) Resources(_ context.Context) []func() resource.Resource { NewMoveStateResource, NewSetNestedBlockWithDefaultsResource, NewSetNestedAttributeWithDefaultsResource, + NewRefinementProducer, + NewRefinementConsumer, + NewInvalidRefinement, } } diff --git a/internal/framework6provider/refinement_consumer_resource.go b/internal/framework6provider/refinement_consumer_resource.go new file mode 100644 index 00000000..2c3200d1 --- /dev/null +++ b/internal/framework6provider/refinement_consumer_resource.go @@ -0,0 +1,144 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/float64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = RefinementConsumerResource{} +var _ resource.ResourceWithConfigValidators = RefinementConsumerResource{} + +func NewRefinementConsumer() resource.Resource { + return &RefinementConsumerResource{} +} + +type RefinementConsumerResource struct{} + +func (r RefinementConsumerResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_refinement_consumer" +} + +func (r RefinementConsumerResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.Conflicting( + path.MatchRoot("conflicting_bool_one"), + path.MatchRoot("conflicting_bool_two"), + ), + } +} + +func (r RefinementConsumerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "conflicting_bool_one": schema.BoolAttribute{ + Optional: true, + }, + "conflicting_bool_two": schema.BoolAttribute{ + Optional: true, + }, + "at_most_int64": schema.Int64Attribute{ + Optional: true, + Validators: []validator.Int64{ + int64validator.AtMost(9), + }, + }, + "at_least_float64": schema.Float64Attribute{ + Optional: true, + Validators: []validator.Float64{ + float64validator.AtLeast(20.235), + }, + }, + "at_least_list_size": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(6), + }, + }, + "at_most_list_size": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "at_least_set_size": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(6), + }, + }, + "at_most_string_length": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtMost(8), + }, + }, + }, + } +} + +func (r RefinementConsumerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data RefinementConsumerResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r RefinementConsumerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data RefinementConsumerResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r RefinementConsumerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data RefinementConsumerResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r RefinementConsumerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +type RefinementConsumerResourceModel struct { + ConflictingBoolOne types.Bool `tfsdk:"conflicting_bool_one"` + ConflictingBoolTwo types.Bool `tfsdk:"conflicting_bool_two"` + AtMostInt64 types.Int64 `tfsdk:"at_most_int64"` + AtLeastFloat64 types.Float64 `tfsdk:"at_least_float64"` + AtLeastListSize types.List `tfsdk:"at_least_list_size"` + AtMostListSize types.List `tfsdk:"at_most_list_size"` + AtLeastSetSize types.Set `tfsdk:"at_least_set_size"` + AtMostStringLength types.String `tfsdk:"at_most_string_length"` +} diff --git a/internal/framework6provider/refinement_consumer_resource_test.go b/internal/framework6provider/refinement_consumer_resource_test.go new file mode 100644 index 00000000..eb68babf --- /dev/null +++ b/internal/framework6provider/refinement_consumer_resource_test.go @@ -0,0 +1,204 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestRefinementConsumerResource_conflicting_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + conflicting_bool_one = framework_refinement_producer.test.bool_with_not_null + conflicting_bool_two = framework_refinement_producer.test.bool_with_not_null + } + `, + // Even though "framework_refinement_producer.test.bool_with_not_null" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Error: Invalid Attribute Combination`), + }, + }, + }) +} + +func TestRefinementConsumerResource_at_most_string_length_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + at_most_string_length = framework_refinement_producer.test.string_with_prefix + } + `, + // Even though "framework_refinement_producer.test.string_with_prefix" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Attribute at_most_string_length string length must be at most 8`), + }, + }, + }) +} + +func TestRefinementConsumerResource_at_most_int64_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + at_most_int64 = framework_refinement_producer.test.int64_with_bounds + } + `, + // Even though "framework_refinement_producer.test.int64_with_bounds" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Attribute at_most_int64 value must be at most 9`), + }, + }, + }) +} + +func TestRefinementConsumerResource_at_least_float64_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + at_least_float64 = framework_refinement_producer.test.float64_with_bounds + } + `, + // Even though "framework_refinement_producer.test.float64_with_bounds" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Attribute at_least_float64 value must be at least 20.235000`), + }, + }, + }) +} + +func TestRefinementConsumerResource_at_least_list_size_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + at_least_list_size = framework_refinement_producer.test.list_with_length_bounds + } + `, + // Even though "framework_refinement_producer.test.list_with_length_bounds" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Attribute at_least_list_size list must contain at least 6 elements`), + }, + }, + }) +} + +func TestRefinementConsumerResource_at_most_list_size_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + at_most_list_size = framework_refinement_producer.test.list_with_length_bounds + } + `, + // Even though "framework_refinement_producer.test.list_with_length_bounds" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Attribute at_most_list_size list must contain at most 1 elements`), + }, + }, + }) +} + +func TestRefinementConsumerResource_at_least_set_size_validation_error(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "framework_refinement_consumer" "test" { + at_least_set_size = framework_refinement_producer.test.list_with_length_bounds + } + `, + // Even though "framework_refinement_producer.test.list_with_length_bounds" is unknown, there is + // enough information to fail validation early. + PlanOnly: true, + ExpectError: regexp.MustCompile(`Attribute at_least_set_size set must contain at least 6 elements`), + }, + }, + }) +} diff --git a/internal/framework6provider/refinement_producer_resource.go b/internal/framework6provider/refinement_producer_resource.go new file mode 100644 index 00000000..932a5d44 --- /dev/null +++ b/internal/framework6provider/refinement_producer_resource.go @@ -0,0 +1,158 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/float64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = RefinementProducerResource{} + +func NewRefinementProducer() resource.Resource { + return &RefinementProducerResource{} +} + +type RefinementProducerResource struct{} + +func (r RefinementProducerResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_refinement_producer" +} + +func (r RefinementProducerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "bool_with_not_null": schema.BoolAttribute{ + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.WillNotBeNull(), + }, + }, + "int64_with_bounds": schema.Int64Attribute{ + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.WillBeAtLeast(10), + int64planmodifier.WillBeAtMost(20), + }, + }, + "float64_with_bounds": schema.Float64Attribute{ + Computed: true, + PlanModifiers: []planmodifier.Float64{ + float64planmodifier.WillBeAtLeast(10.234), + float64planmodifier.WillBeAtMost(20.234), + }, + }, + "list_with_length_bounds": schema.ListAttribute{ + ElementType: types.StringType, + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.WillHaveSizeAtLeast(2), + listplanmodifier.WillHaveSizeAtMost(5), + }, + }, + "string_with_prefix": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.WillHavePrefix("prefix://"), + }, + }, + }, + } +} + +func (r RefinementProducerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data RefinementProducerResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + data.BoolWithNotNull = types.BoolValue(true) + data.Int64WithBounds = types.Int64Value(15) + data.Float64WithBounds = types.Float64Value(12.102) + data.ListWithLengthBounds = types.ListValueMust( + types.StringType, + []attr.Value{ + types.StringValue("hello"), + types.StringValue("there"), + types.StringValue("world!"), + }, + ) + data.StringWithPrefix = types.StringValue("prefix://hello-world") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r RefinementProducerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data RefinementProducerResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + data.BoolWithNotNull = types.BoolValue(true) + data.Int64WithBounds = types.Int64Value(15) + data.Float64WithBounds = types.Float64Value(12.102) + data.ListWithLengthBounds = types.ListValueMust( + types.StringType, + []attr.Value{ + types.StringValue("hello"), + types.StringValue("there"), + types.StringValue("world!"), + }, + ) + data.StringWithPrefix = types.StringValue("prefix://hello-world") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r RefinementProducerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data RefinementProducerResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + data.BoolWithNotNull = types.BoolValue(true) + data.Int64WithBounds = types.Int64Value(15) + data.Float64WithBounds = types.Float64Value(12.102) + data.ListWithLengthBounds = types.ListValueMust( + types.StringType, + []attr.Value{ + types.StringValue("hello"), + types.StringValue("there"), + types.StringValue("world!"), + }, + ) + data.StringWithPrefix = types.StringValue("prefix://hello-world") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r RefinementProducerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +type RefinementProducerResourceModel struct { + BoolWithNotNull types.Bool `tfsdk:"bool_with_not_null"` + Int64WithBounds types.Int64 `tfsdk:"int64_with_bounds"` + Float64WithBounds types.Float64 `tfsdk:"float64_with_bounds"` + ListWithLengthBounds types.List `tfsdk:"list_with_length_bounds"` + StringWithPrefix types.String `tfsdk:"string_with_prefix"` +} diff --git a/internal/framework6provider/refinement_producer_resource_test.go b/internal/framework6provider/refinement_producer_resource_test.go new file mode 100644 index 00000000..265ee082 --- /dev/null +++ b/internal/framework6provider/refinement_producer_resource_test.go @@ -0,0 +1,286 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestRefinementProducerResource_basic(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // This test runs against all Terraform versions to ensure provider-returned refinements don't cause unexpected issues (earlier versions of core should ignore them) + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + output "test_out" { + value = framework_refinement_producer.test + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownOutputValueAtPath("test_out", tfjsonpath.New("bool_with_not_null")), + plancheck.ExpectUnknownOutputValueAtPath("test_out", tfjsonpath.New("int64_with_bounds")), + plancheck.ExpectUnknownOutputValueAtPath("test_out", tfjsonpath.New("float64_with_bounds")), + plancheck.ExpectUnknownOutputValueAtPath("test_out", tfjsonpath.New("list_with_length_bounds")), + plancheck.ExpectUnknownOutputValueAtPath("test_out", tfjsonpath.New("string_with_prefix")), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue("test_out", knownvalue.ObjectExact( + map[string]knownvalue.Check{ + "bool_with_not_null": knownvalue.Bool(true), + "int64_with_bounds": knownvalue.Int64Exact(15), + "float64_with_bounds": knownvalue.Float64Exact(12.102), + "list_with_length_bounds": knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("hello"), + knownvalue.StringExact("there"), + knownvalue.StringExact("world!"), + }), + "string_with_prefix": knownvalue.StringExact("prefix://hello-world"), + }, + )), + }, + }, + }, + }) +} + +func TestRefinementProducerResource_notnull(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + // Without refinement support in the provider, this config would return an error like: + // + // Error: Invalid count argument + // + // on terraform_plugin_test.tf line 15, in resource "terraform_data" "test_out": + // 15: count = framework_refinement_producer.test.bool_with_not_null != null ? 1 : 0 + // + // The "count" value depends on resource attributes that cannot be determined + // until apply, so Terraform cannot predict how many instances will be created. + // To work around this, use the -target argument to first apply only the + // resources that the count depends on. + // + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "terraform_data" "test_out" { + count = framework_refinement_producer.test.bool_with_not_null != null ? 1 : 0 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("framework_refinement_producer.test", tfjsonpath.New("bool_with_not_null")), + plancheck.ExpectResourceAction("terraform_data.test_out[0]", plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("framework_refinement_producer.test", tfjsonpath.New("bool_with_not_null"), knownvalue.Bool(true)), + }, + }, + }, + }) +} + +func TestRefinementProducerResource_stringprefix(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + // Without refinement support in the provider, this config would return an error like: + // + // Error: Invalid count argument + // + // on terraform_plugin_test.tf line 15, in resource "terraform_data" "test_out": + // 15: count = startswith(framework_refinement_producer.test.string_with_prefix, "prefix://") ? 1 : 0 + // + // The "count" value depends on resource attributes that cannot be determined + // until apply, so Terraform cannot predict how many instances will be created. + // To work around this, use the -target argument to first apply only the + // resources that the count depends on. + // + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "terraform_data" "test_out" { + count = startswith(framework_refinement_producer.test.string_with_prefix, "prefix://") ? 1 : 0 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("framework_refinement_producer.test", tfjsonpath.New("string_with_prefix")), + plancheck.ExpectResourceAction("terraform_data.test_out[0]", plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("framework_refinement_producer.test", tfjsonpath.New("string_with_prefix"), knownvalue.StringExact("prefix://hello-world")), + }, + }, + }, + }) +} + +func TestRefinementProducerResource_int64_bounds(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + // Without refinement support in the provider, this config would return an error like: + // + // Error: Invalid count argument + // + // on terraform_plugin_test.tf line 15, in resource "terraform_data" "test_out": + // 15: count = framework_refinement_producer.test.int64_with_bounds > 9 ? 1 : 0 + // + // The "count" value depends on resource attributes that cannot be determined + // until apply, so Terraform cannot predict how many instances will be created. + // To work around this, use the -target argument to first apply only the + // resources that the count depends on. + // + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "terraform_data" "test_out" { + count = framework_refinement_producer.test.int64_with_bounds > 9 ? 1 : 0 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("framework_refinement_producer.test", tfjsonpath.New("int64_with_bounds")), + plancheck.ExpectResourceAction("terraform_data.test_out[0]", plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("framework_refinement_producer.test", tfjsonpath.New("int64_with_bounds"), knownvalue.Int64Exact(15)), + }, + }, + }, + }) +} +func TestRefinementProducerResource_float64_bounds(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + // Without refinement support in the provider, this config would return an error like: + // + // Error: Invalid count argument + // + // on terraform_plugin_test.tf line 15, in resource "terraform_data" "test_out": + // 15: count = framework_refinement_producer.test.float64_with_bounds > 10.233 ? 1 : 0 + // + // The "count" value depends on resource attributes that cannot be determined + // until apply, so Terraform cannot predict how many instances will be created. + // To work around this, use the -target argument to first apply only the + // resources that the count depends on. + // + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "terraform_data" "test_out" { + count = framework_refinement_producer.test.float64_with_bounds > 10.233 ? 1 : 0 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("framework_refinement_producer.test", tfjsonpath.New("float64_with_bounds")), + plancheck.ExpectResourceAction("terraform_data.test_out[0]", plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("framework_refinement_producer.test", tfjsonpath.New("float64_with_bounds"), knownvalue.Float64Exact(12.102)), + }, + }, + }, + }) +} + +func TestRefinementProducerResource_list_length_bounds(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Unknown value refinements were introduced to Terraform v1.6.0 via go-cty + tfversion.SkipBelow(tfversion.Version1_6_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + // Without refinement support in the provider, this config would return an error like: + // + // Error: Invalid count argument + + // on terraform_plugin_test.tf line 15, in resource "terraform_data" "test_out": + // 15: count = length(framework_refinement_producer.test.list_with_length_bounds) > 1 ? 1 : 0 + + // The "count" value depends on resource attributes that cannot be determined + // until apply, so Terraform cannot predict how many instances will be created. + // To work around this, use the -target argument to first apply only the + // resources that the count depends on. + // + Config: ` + resource "framework_refinement_producer" "test" {} + + resource "terraform_data" "test_out" { + count = length(framework_refinement_producer.test.list_with_length_bounds) > 1 ? 1 : 0 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("framework_refinement_producer.test", tfjsonpath.New("list_with_length_bounds")), + plancheck.ExpectResourceAction("terraform_data.test_out[0]", plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("framework_refinement_producer.test", tfjsonpath.New("list_with_length_bounds"), knownvalue.ListExact( + []knownvalue.Check{ + knownvalue.StringExact("hello"), + knownvalue.StringExact("there"), + knownvalue.StringExact("world!"), + }, + )), + }, + }, + }, + }) +} From 966b32d1f1265a18137bb2b83ad7c7df0a186b18 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Thu, 16 Jan 2025 18:34:35 -0500 Subject: [PATCH 09/10] more fine grained assertions --- .../refinement_producer_resource_test.go | 50 ++++++++++++++++++- .../refinement_producer_resource_test.go | 50 ++++++++++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/internal/framework5provider/refinement_producer_resource_test.go b/internal/framework5provider/refinement_producer_resource_test.go index f264ce68..045dde23 100644 --- a/internal/framework5provider/refinement_producer_resource_test.go +++ b/internal/framework5provider/refinement_producer_resource_test.go @@ -16,9 +16,57 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfversion" ) +func TestRefinementProducerResource_basic_pre_1_3(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // This test runs against earlier Terraform versions to ensure provider-returned refinements don't cause unexpected issues (core should ignore them) + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Terraform 1.2 and older treat the entire output value as unknown + tfversion.SkipAbove(tfversion.Version1_2_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + output "test_out" { + value = framework_refinement_producer.test + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownOutputValue("test_out"), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue("test_out", knownvalue.ObjectExact( + map[string]knownvalue.Check{ + "bool_with_not_null": knownvalue.Bool(true), + "int64_with_bounds": knownvalue.Int64Exact(15), + "float64_with_bounds": knownvalue.Float64Exact(12.102), + "list_with_length_bounds": knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("hello"), + knownvalue.StringExact("there"), + knownvalue.StringExact("world!"), + }), + "string_with_prefix": knownvalue.StringExact("prefix://hello-world"), + }, + )), + }, + }, + }, + }) +} + func TestRefinementProducerResource_basic(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - // This test runs against all Terraform versions to ensure provider-returned refinements don't cause unexpected issues (earlier versions of core should ignore them) + // This test runs against earlier Terraform versions to ensure provider-returned refinements don't cause unexpected issues (core should ignore them) + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Terraform 1.3 and above have more fine-grained unknown output values to assert during plan + tfversion.SkipBelow(tfversion.Version1_3_0), + }, ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ "framework": providerserver.NewProtocol5WithError(New()), }, diff --git a/internal/framework6provider/refinement_producer_resource_test.go b/internal/framework6provider/refinement_producer_resource_test.go index 265ee082..98cde19c 100644 --- a/internal/framework6provider/refinement_producer_resource_test.go +++ b/internal/framework6provider/refinement_producer_resource_test.go @@ -16,9 +16,57 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfversion" ) +func TestRefinementProducerResource_basic_pre_1_3(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // This test runs against earlier Terraform versions to ensure provider-returned refinements don't cause unexpected issues (core should ignore them) + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Terraform 1.2 and older treat the entire output value as unknown + tfversion.SkipAbove(tfversion.Version1_2_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + resource "framework_refinement_producer" "test" {} + + output "test_out" { + value = framework_refinement_producer.test + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownOutputValue("test_out"), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue("test_out", knownvalue.ObjectExact( + map[string]knownvalue.Check{ + "bool_with_not_null": knownvalue.Bool(true), + "int64_with_bounds": knownvalue.Int64Exact(15), + "float64_with_bounds": knownvalue.Float64Exact(12.102), + "list_with_length_bounds": knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("hello"), + knownvalue.StringExact("there"), + knownvalue.StringExact("world!"), + }), + "string_with_prefix": knownvalue.StringExact("prefix://hello-world"), + }, + )), + }, + }, + }, + }) +} + func TestRefinementProducerResource_basic(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - // This test runs against all Terraform versions to ensure provider-returned refinements don't cause unexpected issues (earlier versions of core should ignore them) + // This test runs against earlier Terraform versions to ensure provider-returned refinements don't cause unexpected issues (core should ignore them) + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // Terraform 1.3+ have more fine-grained unknown output values to assert during plan + tfversion.SkipBelow(tfversion.Version1_3_0), + }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "framework": providerserver.NewProtocol6WithError(New()), }, From 71e3ac9cd1509e2565e7b04d632e9a3d215380ba Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Thu, 16 Jan 2025 18:38:28 -0500 Subject: [PATCH 10/10] comment --- .../framework5provider/refinement_producer_resource_test.go | 3 ++- .../framework6provider/refinement_producer_resource_test.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/framework5provider/refinement_producer_resource_test.go b/internal/framework5provider/refinement_producer_resource_test.go index 045dde23..540993df 100644 --- a/internal/framework5provider/refinement_producer_resource_test.go +++ b/internal/framework5provider/refinement_producer_resource_test.go @@ -18,7 +18,8 @@ import ( func TestRefinementProducerResource_basic_pre_1_3(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - // This test runs against earlier Terraform versions to ensure provider-returned refinements don't cause unexpected issues (core should ignore them) + // This test runs against earlier Terraform versions to ensure provider-returned refinements + // don't cause unexpected issues (core should ignore them) TerraformVersionChecks: []tfversion.TerraformVersionCheck{ // Terraform 1.2 and older treat the entire output value as unknown tfversion.SkipAbove(tfversion.Version1_2_0), diff --git a/internal/framework6provider/refinement_producer_resource_test.go b/internal/framework6provider/refinement_producer_resource_test.go index 98cde19c..ae1f09e0 100644 --- a/internal/framework6provider/refinement_producer_resource_test.go +++ b/internal/framework6provider/refinement_producer_resource_test.go @@ -18,7 +18,8 @@ import ( func TestRefinementProducerResource_basic_pre_1_3(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - // This test runs against earlier Terraform versions to ensure provider-returned refinements don't cause unexpected issues (core should ignore them) + // This test runs against earlier Terraform versions to ensure provider-returned refinements + // don't cause unexpected issues (core should ignore them) TerraformVersionChecks: []tfversion.TerraformVersionCheck{ // Terraform 1.2 and older treat the entire output value as unknown tfversion.SkipAbove(tfversion.Version1_2_0),