From e302d7b95902b8edd8e8073632052640950116fe Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 29 Apr 2022 13:02:13 +0100 Subject: [PATCH 01/96] Configuring muxing (#177) --- go.mod | 3 ++- go.sum | 16 ++++++++++++++++ main.go | 36 +++++++++++++++++++++++++++++++++--- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 45fe1ad2..85542884 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,8 @@ require ( github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/terraform-plugin-docs v0.8.1 github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0 + github.com/hashicorp/terraform-plugin-framework v0.8.0 + github.com/hashicorp/terraform-plugin-go v0.9.1 ) require ( @@ -35,7 +37,6 @@ require ( github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.16.1 // indirect github.com/hashicorp/terraform-json v0.13.0 // indirect - github.com/hashicorp/terraform-plugin-go v0.9.0 // indirect github.com/hashicorp/terraform-plugin-log v0.4.0 // indirect github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 // indirect github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect diff --git a/go.sum b/go.sum index 69cbbcd2..ab4bc0ee 100644 --- a/go.sum +++ b/go.sum @@ -124,6 +124,7 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -141,15 +142,30 @@ github.com/hashicorp/terraform-exec v0.16.1 h1:NAwZFJW2L2SaCBVZoVaH8LPImLOGbPLkS github.com/hashicorp/terraform-exec v0.16.1/go.mod h1:aj0lVshy8l+MHhFNoijNHtqTJQI3Xlowv5EOsEaGO7M= github.com/hashicorp/terraform-json v0.13.0 h1:Li9L+lKD1FO5RVFRM1mMMIBDoUHslOniyEi5CM+FWGY= github.com/hashicorp/terraform-json v0.13.0/go.mod h1:y5OdLBCT+rxbwnpxZs9kGL7R9ExU76+cpdY8zHwoazk= +<<<<<<< HEAD github.com/hashicorp/terraform-plugin-docs v0.8.1 h1:XJC/cDvmE7zJfDFCtOI1bURaencBQC0xYx3DZ5cWbhE= github.com/hashicorp/terraform-plugin-docs v0.8.1/go.mod h1:p40z/69HYNUN/G2RDYp8XUCA5B1VzGTZl7/N9V+BWXU= +======= +github.com/hashicorp/terraform-plugin-docs v0.8.0 h1:qNuHNTEqVCT258+h1GsejtfgW6qxTBAXc6qJbzd8C3U= +github.com/hashicorp/terraform-plugin-docs v0.8.0/go.mod h1:MjeyK5CEI/jZbTQXq1Ay0UwTGdqxEcQexqhHP1WcLZc= +github.com/hashicorp/terraform-plugin-framework v0.7.0 h1:vFM17wfu1Iq5XJ4S/wUooayMMpmlwIBbSBQEs3XfsD4= +github.com/hashicorp/terraform-plugin-framework v0.7.0/go.mod h1:1iyRcwMnjsCvH9XpDBUFd1l6gJcgAT2dZ+RJGh56vj4= +github.com/hashicorp/terraform-plugin-go v0.8.0/go.mod h1:E3GuvfX0Pz2Azcl6BegD6t51StXsVZMOYQoGO8mkHM0= +>>>>>>> 37128b5 (Configuring muxing (#177)) github.com/hashicorp/terraform-plugin-go v0.9.0 h1:FvLY/3z4SNVatPZdoFcyrlNbCar+WyyOTv5X4Tp+WZc= github.com/hashicorp/terraform-plugin-go v0.9.0/go.mod h1:EawBkgjBWNf7jiKnVoyDyF39OSV+u6KUX+Y73EPj3oM= github.com/hashicorp/terraform-plugin-log v0.3.0/go.mod h1:EjueSP/HjlyFAsDqt+okpCPjkT4NDynAe32AeDC4vps= github.com/hashicorp/terraform-plugin-log v0.4.0 h1:F3eVnm8r2EfQCe2k9blPIiF/r2TT01SHijXnS7bujvc= github.com/hashicorp/terraform-plugin-log v0.4.0/go.mod h1:9KclxdunFownr4pIm1jdmwKRmE4d6HVG2c9XDq47rpg= +<<<<<<< HEAD github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0 h1:9fjPgCenJqnbjo95SDcbJ+YdLyEC1N35cwKWcRWhJTQ= github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0/go.mod h1:hLa0sTiySU/AWEgV2GxJh0/pQIqcCmm30IPja9N9lTg= +======= +github.com/hashicorp/terraform-plugin-mux v0.6.0 h1:HMUdyltYurTQr9yyijnB3aj1olpNxuLfn3i7HPpupeE= +github.com/hashicorp/terraform-plugin-mux v0.6.0/go.mod h1:qtg6FWnYEwwU3jMugPetmjVYKKn2VWhIPYga5fCtzd4= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.15.0 h1:nBRM7JBvaYDV8bJjtBUyDZZrprr7UWur1/P8j4vgvqY= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.15.0/go.mod h1:oCj7EsihOUat3iY2WNFQzH8OLThqjmQ6tJxsmD7Ay9U= +>>>>>>> 37128b5 (Configuring muxing (#177)) github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 h1:1FGtlkJw87UsTMg5s8jrekrHmUPUJaMcu6ELiVhQrNw= github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896/go.mod h1:bzBPnUIkI0RxauU8Dqo+2KrZZ28Cf48s8V6IHt3p4co= github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= diff --git a/main.go b/main.go index 6575fd60..f41cc004 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,15 @@ package main import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" - + "context" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" + "github.com/hashicorp/terraform-plugin-mux/tf6to5server" "github.com/terraform-providers/terraform-provider-random/internal/provider" + "log" ) // Run "go generate" to format example terraform files and generate the docs for the registry/website @@ -17,5 +23,29 @@ import ( //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs func main() { - plugin.Serve(&plugin.ServeOpts{ProviderFunc: provider.New}) + ctx := context.Background() + + downgradedFrameworkProvider, err := tf6to5server.DowngradeServer(ctx, func() tfprotov6.ProviderServer { + return providerserver.NewProtocol6(provider.NewFramework())() + }) + if err != nil { + log.Fatal(err) + } + + providers := []func() tfprotov5.ProviderServer{ + func() tfprotov5.ProviderServer { + return downgradedFrameworkProvider + }, + provider.New().GRPCProvider, + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + if err != nil { + log.Fatal(err) + } + + err = tf5server.Serve("registry.terraform.io/hashicorp/random", muxServer.ProviderServer) + if err != nil { + log.Fatal(err) + } } From b8cc0572378ab1d18f307a6c7c8eea453367a571 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 29 Apr 2022 13:18:54 +0100 Subject: [PATCH 02/96] Migrating uuid resource to framework (#177) --- internal/provider/provider.go | 1 - internal/provider_fm/models.go | 8 ++ internal/provider_fm/provider.go | 33 ++++++ internal/provider_fm/provider_test.go | 16 +++ internal/provider_fm/resource_uuid.go | 117 +++++++++++++++++++++ internal/provider_fm/resource_uuid_test.go | 64 +++++++++++ main.go | 3 +- 7 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 internal/provider_fm/models.go create mode 100644 internal/provider_fm/provider.go create mode 100644 internal/provider_fm/provider_test.go create mode 100644 internal/provider_fm/resource_uuid.go create mode 100644 internal/provider_fm/resource_uuid_test.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 1fc2f1a8..e2cefd79 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -22,7 +22,6 @@ func New() *schema.Provider { "random_string": resourceString(), "random_password": resourcePassword(), "random_integer": resourceInteger(), - "random_uuid": resourceUuid(), }, } } diff --git a/internal/provider_fm/models.go b/internal/provider_fm/models.go new file mode 100644 index 00000000..1ad841ad --- /dev/null +++ b/internal/provider_fm/models.go @@ -0,0 +1,8 @@ +package provider_fm + +import "github.com/hashicorp/terraform-plugin-framework/types" + +type UUID struct { + ID types.String `tfsdk:"id"` + Result types.String `tfsdk:"result"` +} diff --git a/internal/provider_fm/provider.go b/internal/provider_fm/provider.go new file mode 100644 index 00000000..44601a1d --- /dev/null +++ b/internal/provider_fm/provider.go @@ -0,0 +1,33 @@ +package provider_fm + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +type provider struct { + configured bool +} + +func NewFramework() tfsdk.Provider { + return &provider{} +} + +func (p *provider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{}, nil +} + +func (p *provider) Configure(ctx context.Context, req tfsdk.ConfigureProviderRequest, resp *tfsdk.ConfigureProviderResponse) { + p.configured = true +} + +func (p *provider) GetResources(ctx context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "random_uuid": resourceUUIDType{}, + }, nil +} + +func (p *provider) GetDataSources(ctx context.Context) (map[string]tfsdk.DataSourceType, diag.Diagnostics) { + return map[string]tfsdk.DataSourceType{}, nil +} diff --git a/internal/provider_fm/provider_test.go b/internal/provider_fm/provider_test.go new file mode 100644 index 00000000..1f4fc05e --- /dev/null +++ b/internal/provider_fm/provider_test.go @@ -0,0 +1,16 @@ +package provider_fm + +import ( + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "testing" +) + +func testAccPreCheck(t *testing.T) { +} + +var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ + "random": func() (tfprotov6.ProviderServer, error) { + return providerserver.NewProtocol6(NewFramework())(), nil + }, +} diff --git a/internal/provider_fm/resource_uuid.go b/internal/provider_fm/resource_uuid.go new file mode 100644 index 00000000..b23fe992 --- /dev/null +++ b/internal/provider_fm/resource_uuid.go @@ -0,0 +1,117 @@ +package provider_fm + +import ( + "context" + "fmt" + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +type resourceUUIDType struct{} + +func (r resourceUUIDType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Description: "The resource `random_uuid` generates random uuid string that is intended to be " + + "used as unique identifiers for other resources.\n" + + "\n" + + "This resource uses [hashicorp/go-uuid](https://github.com/hashicorp/go-uuid) to generate a " + + "UUID-formatted string for use with services needed a unique string identifier.", + Attributes: map[string]tfsdk.Attribute{ + "result": { + Description: "The generated uuid presented in string format.", + Type: types.StringType, + Computed: true, + }, + "id": { + Description: "The generated uuid presented in string format.", + Type: types.StringType, + Computed: true, + }, + }, + }, nil +} + +func (r resourceUUIDType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return resourceUUID{ + p: *(p.(*provider)), + }, nil +} + +type resourceUUID struct { + p provider +} + +func (r resourceUUID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + if !r.p.configured { + resp.Diagnostics.AddError( + "provider not configured", + "provider not configured", + ) + } + + result, err := uuid.GenerateUUID() + if err != nil { + resp.Diagnostics.AddError( + "error generating uuid", + fmt.Sprintf("could not generate uuid: %s", err)) + return + } + + u := &UUID{ + ID: types.String{Value: result}, + Result: types.String{Value: result}, + } + + diags := resp.State.Set(ctx, u) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r resourceUUID) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { + // Intentionally left blank. +} + +func (r resourceUUID) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + //TODO implement me + panic("implement me") +} + +func (r resourceUUID) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.State.RemoveResource(ctx) +} + +func (r resourceUUID) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + tfsdk.ResourceImportStatePassthroughID(ctx, tftypes.NewAttributePath().WithAttributeName("id"), req, resp) + + bytes, err := uuid.ParseUUID(req.ID) + if err != nil { + resp.Diagnostics.AddError( + "error parsing uuid bytes", + fmt.Sprintf("error parsing uuid bytes: %s", err)) + return + } + + result, err := uuid.FormatUUID(bytes) + if err != nil { + resp.Diagnostics.AddError( + "error formatting uuid bytes", + fmt.Sprintf("error formatting uuid bytes: %s", err)) + return + } + + var state UUID + + state.ID.Value = result + state.Result.Value = result + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/internal/provider_fm/resource_uuid_test.go b/internal/provider_fm/resource_uuid_test.go new file mode 100644 index 00000000..da06f46c --- /dev/null +++ b/internal/provider_fm/resource_uuid_test.go @@ -0,0 +1,64 @@ +package provider_fm + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccResourceUUID_fw(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccResourceUUIDConfig, + Check: resource.ComposeTestCheckFunc( + testAccResourceUUIDCheck("random_uuid.foo"), + ), + }, + { + ResourceName: "random_uuid.foo", + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "random_uuid.bar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccResourceUUIDCheck(id string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[id] + if !ok { + return fmt.Errorf("Not found: %s", id) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + result := rs.Primary.Attributes["result"] + matched, err := regexp.MatchString( + "[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}", result) + if !matched || err != nil { + return fmt.Errorf("result string format incorrect, is %s", result) + } + + return nil + } +} + +const ( + testAccResourceUUIDConfig = ` +resource "random_uuid" "foo" { } + +resource "random_uuid" "bar" { } +` +) diff --git a/main.go b/main.go index f41cc004..e1c2fa15 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" "github.com/hashicorp/terraform-plugin-mux/tf6to5server" "github.com/terraform-providers/terraform-provider-random/internal/provider" + "github.com/terraform-providers/terraform-provider-random/internal/provider_fm" "log" ) @@ -26,7 +27,7 @@ func main() { ctx := context.Background() downgradedFrameworkProvider, err := tf6to5server.DowngradeServer(ctx, func() tfprotov6.ProviderServer { - return providerserver.NewProtocol6(provider.NewFramework())() + return providerserver.NewProtocol6(provider_fm.NewFramework())() }) if err != nil { log.Fatal(err) From 05aabad91718fa8fe335a451a39ad309e70a5c44 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 3 May 2022 09:35:24 +0100 Subject: [PATCH 03/96] Adding keepers into uuid resource schema (#177) --- internal/provider_fm/models.go | 5 +++-- internal/provider_fm/resource_uuid.go | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/internal/provider_fm/models.go b/internal/provider_fm/models.go index 1ad841ad..cb780e89 100644 --- a/internal/provider_fm/models.go +++ b/internal/provider_fm/models.go @@ -3,6 +3,7 @@ package provider_fm import "github.com/hashicorp/terraform-plugin-framework/types" type UUID struct { - ID types.String `tfsdk:"id"` - Result types.String `tfsdk:"result"` + ID types.String `tfsdk:"id"` + Result types.String `tfsdk:"result"` + Keepers types.Map `tfsdk:"keepers"` } diff --git a/internal/provider_fm/resource_uuid.go b/internal/provider_fm/resource_uuid.go index b23fe992..0b7d929c 100644 --- a/internal/provider_fm/resource_uuid.go +++ b/internal/provider_fm/resource_uuid.go @@ -20,6 +20,15 @@ func (r resourceUUIDType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnos "This resource uses [hashicorp/go-uuid](https://github.com/hashicorp/go-uuid) to generate a " + "UUID-formatted string for use with services needed a unique string identifier.", Attributes: map[string]tfsdk.Attribute{ + "keepers": { + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + Type: types.MapType{ + ElemType: types.StringType, + }, + Optional: true, + //ForceNew: true, + }, "result": { Description: "The generated uuid presented in string format.", Type: types.StringType, @@ -77,8 +86,13 @@ func (r resourceUUID) Read(ctx context.Context, req tfsdk.ReadResourceRequest, r } func (r resourceUUID) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - //TODO implement me - panic("implement me") + var plan UUID + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } func (r resourceUUID) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { From 5668eb621cce37813f8527477a03042af0c57050 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 3 May 2022 11:30:22 +0100 Subject: [PATCH 04/96] Incorporating keepers for uuid resource (#177) --- internal/provider/resource_uuid_test.go | 38 ------------------- internal/provider_fm/models.go | 2 +- internal/provider_fm/resource_uuid.go | 19 +++++++--- internal/provider_fm/resource_uuid_test.go | 43 +++++----------------- 4 files changed, 24 insertions(+), 78 deletions(-) delete mode 100644 internal/provider/resource_uuid_test.go diff --git a/internal/provider/resource_uuid_test.go b/internal/provider/resource_uuid_test.go deleted file mode 100644 index 4df36c51..00000000 --- a/internal/provider/resource_uuid_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package provider - -import ( - "regexp" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAccResourceUUID(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, - Steps: []resource.TestStep{ - { - Config: testAccResourceUUIDConfig, - Check: resource.ComposeTestCheckFunc( - resource.TestMatchResourceAttr( - "random_uuid.basic", - "result", - regexp.MustCompile(`[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}`), - ), - ), - }, - { - ResourceName: "random_uuid.basic", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -const ( - testAccResourceUUIDConfig = ` -resource "random_uuid" "basic" { } -` -) diff --git a/internal/provider_fm/models.go b/internal/provider_fm/models.go index cb780e89..39b92d0d 100644 --- a/internal/provider_fm/models.go +++ b/internal/provider_fm/models.go @@ -5,5 +5,5 @@ import "github.com/hashicorp/terraform-plugin-framework/types" type UUID struct { ID types.String `tfsdk:"id"` Result types.String `tfsdk:"result"` - Keepers types.Map `tfsdk:"keepers"` + Keepers types.Map `tfsdk:"keepers""` } diff --git a/internal/provider_fm/resource_uuid.go b/internal/provider_fm/resource_uuid.go index 0b7d929c..49666d26 100644 --- a/internal/provider_fm/resource_uuid.go +++ b/internal/provider_fm/resource_uuid.go @@ -26,8 +26,8 @@ func (r resourceUUIDType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnos Type: types.MapType{ ElemType: types.StringType, }, - Optional: true, - //ForceNew: true, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, }, "result": { Description: "The generated uuid presented in string format.", @@ -69,12 +69,20 @@ func (r resourceUUID) Create(ctx context.Context, req tfsdk.CreateResourceReques return } + var plan UUID + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + u := &UUID{ - ID: types.String{Value: result}, - Result: types.String{Value: result}, + ID: types.String{Value: result}, + Result: types.String{Value: result}, + Keepers: plan.Keepers, } - diags := resp.State.Set(ctx, u) + diags = resp.State.Set(ctx, u) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -122,6 +130,7 @@ func (r resourceUUID) ImportState(ctx context.Context, req tfsdk.ImportResourceS state.ID.Value = result state.Result.Value = result + state.Keepers.ElemType = types.StringType diags := resp.State.Set(ctx, &state) resp.Diagnostics.Append(diags...) diff --git a/internal/provider_fm/resource_uuid_test.go b/internal/provider_fm/resource_uuid_test.go index da06f46c..51b6ac65 100644 --- a/internal/provider_fm/resource_uuid_test.go +++ b/internal/provider_fm/resource_uuid_test.go @@ -1,15 +1,13 @@ package provider_fm import ( - "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "regexp" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestAccResourceUUID_fw(t *testing.T) { +func TestAccResourceUUID(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, @@ -17,16 +15,15 @@ func TestAccResourceUUID_fw(t *testing.T) { { Config: testAccResourceUUIDConfig, Check: resource.ComposeTestCheckFunc( - testAccResourceUUIDCheck("random_uuid.foo"), + resource.TestMatchResourceAttr( + "random_uuid.basic", + "result", + regexp.MustCompile(`[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}`), + ), ), }, { - ResourceName: "random_uuid.foo", - ImportState: true, - ImportStateVerify: true, - }, - { - ResourceName: "random_uuid.bar", + ResourceName: "random_uuid.basic", ImportState: true, ImportStateVerify: true, }, @@ -34,31 +31,9 @@ func TestAccResourceUUID_fw(t *testing.T) { }) } -func testAccResourceUUIDCheck(id string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[id] - if !ok { - return fmt.Errorf("Not found: %s", id) - } - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - result := rs.Primary.Attributes["result"] - matched, err := regexp.MatchString( - "[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}", result) - if !matched || err != nil { - return fmt.Errorf("result string format incorrect, is %s", result) - } - - return nil - } -} - const ( testAccResourceUUIDConfig = ` -resource "random_uuid" "foo" { } - -resource "random_uuid" "bar" { } +resource "random_uuid" "basic" { +} ` ) From 1f6fcd9b46b6ba7cd495e04e96b2c9d7450e46bc Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 3 May 2022 15:39:53 +0100 Subject: [PATCH 05/96] Migrating id resource to framework (#177) --- internal/provider/provider.go | 1 - internal/provider_fm/models.go | 13 +- internal/provider_fm/provider.go | 1 + internal/provider_fm/resource_id.go | 219 ++++++++++++++++++ .../resource_id_test.go | 10 +- internal/provider_fm/resource_uuid.go | 11 +- 6 files changed, 238 insertions(+), 17 deletions(-) create mode 100644 internal/provider_fm/resource_id.go rename internal/{provider => provider_fm}/resource_id_test.go (90%) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e2cefd79..2366e367 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -16,7 +16,6 @@ func New() *schema.Provider { Schema: map[string]*schema.Schema{}, ResourcesMap: map[string]*schema.Resource{ - "random_id": resourceId(), "random_shuffle": resourceShuffle(), "random_pet": resourcePet(), "random_string": resourceString(), diff --git a/internal/provider_fm/models.go b/internal/provider_fm/models.go index 39b92d0d..83fb958d 100644 --- a/internal/provider_fm/models.go +++ b/internal/provider_fm/models.go @@ -2,8 +2,19 @@ package provider_fm import "github.com/hashicorp/terraform-plugin-framework/types" +type ID struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + ByteLength types.Int64 `tfsdk:"byte_length"` + Prefix types.String `tfsdk:"prefix"` + B64URL types.String `tfsdk:"b64_url"` + B64Std types.String `tfsdk:"b64_std"` + Hex types.String `tfsdk:"hex"` + Dec types.String `tfsdk:"dec"` +} + type UUID struct { ID types.String `tfsdk:"id"` Result types.String `tfsdk:"result"` - Keepers types.Map `tfsdk:"keepers""` + Keepers types.Map `tfsdk:"keepers"` } diff --git a/internal/provider_fm/provider.go b/internal/provider_fm/provider.go index 44601a1d..de5d427f 100644 --- a/internal/provider_fm/provider.go +++ b/internal/provider_fm/provider.go @@ -24,6 +24,7 @@ func (p *provider) Configure(ctx context.Context, req tfsdk.ConfigureProviderReq func (p *provider) GetResources(ctx context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { return map[string]tfsdk.ResourceType{ + "random_id": resourceIDType{}, "random_uuid": resourceUUIDType{}, }, nil } diff --git a/internal/provider_fm/resource_id.go b/internal/provider_fm/resource_id.go new file mode 100644 index 00000000..6677500d --- /dev/null +++ b/internal/provider_fm/resource_id.go @@ -0,0 +1,219 @@ +package provider_fm + +import ( + "context" + "crypto/rand" + "encoding/base64" + hex2 "encoding/hex" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "math/big" + "strings" +) + +type resourceIDType struct{} + +func (r resourceIDType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Description: ` +The resource ` + "`random_id`" + ` generates random numbers that are intended to be +used as unique identifiers for other resources. + +This resource *does* use a cryptographic random number generator in order +to minimize the chance of collisions, making the results of this resource +when a 16-byte identifier is requested of equivalent uniqueness to a +type-4 UUID. + +This resource can be used in conjunction with resources that have +the ` + "`create_before_destroy`" + ` lifecycle flag set to avoid conflicts with +unique names during the brief period where both the old and new resources +exist concurrently. +`, + Attributes: map[string]tfsdk.Attribute{ + "keepers": { + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + Type: types.MapType{ + ElemType: types.StringType, + }, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + "byte_length": { + Description: "The number of random bytes to produce. The minimum value is 1, which produces " + + "eight bits of randomness.", + Type: types.Int64Type, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + "prefix": { + Description: "Arbitrary string to prefix the output value with. This string is supplied as-is, " + + "meaning it is not guaranteed to be URL-safe or base64 encoded.", + Type: types.StringType, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + "b64_url": { + Description: "The generated id presented in base64, using the URL-friendly character set: " + + "case-sensitive letters, digits and the characters `_` and `-`.", + Type: types.StringType, + Computed: true, + }, + "b64_std": { + Description: "The generated id presented in base64 without additional transformations.", + Type: types.StringType, + Computed: true, + }, + "hex": { + Description: "The generated id presented in padded hexadecimal digits. This result will " + + "always be twice as long as the requested byte length.", + Type: types.StringType, + Computed: true, + }, + "dec": { + Description: "The generated id presented in non-padded decimal digits.", + Type: types.StringType, + Computed: true, + }, + "id": { + Description: "The generated id presented in base64 without additional transformations or prefix.", + Type: types.StringType, + Computed: true, + }, + }, + }, nil +} + +func (r resourceIDType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return resourceID{ + p: *(p.(*provider)), + }, nil +} + +type resourceID struct { + p provider +} + +func (r resourceID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + if !r.p.configured { + resp.Diagnostics.AddError( + "provider not configured", + "provider not configured", + ) + } + + var plan ID + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + byteLength := plan.ByteLength.Value + bytes := make([]byte, byteLength) + + n, err := rand.Reader.Read(bytes) + if int64(n) != byteLength { + resp.Diagnostics.AddError( + "generated insufficient random bytes: %s", + fmt.Sprintf("generated insufficient random bytes: %s", err), + ) + return + } + if err != nil { + resp.Diagnostics.AddError( + "error generating random bytes", + fmt.Sprintf("error generating random bytes: %s", err), + ) + return + } + + id := base64.RawURLEncoding.EncodeToString(bytes) + prefix := plan.Prefix.Value + b64Std := base64.StdEncoding.EncodeToString(bytes) + hex := hex2.EncodeToString(bytes) + + bigInt := big.Int{} + bigInt.SetBytes(bytes) + dec := bigInt.String() + + i := ID{ + ID: types.String{Value: id}, + Keepers: plan.Keepers, + ByteLength: types.Int64{Value: plan.ByteLength.Value}, + Prefix: plan.Prefix, + B64URL: types.String{Value: prefix + id}, + B64Std: types.String{Value: prefix + b64Std}, + Hex: types.String{Value: prefix + hex}, + Dec: types.String{Value: prefix + dec}, + } + + diags = resp.State.Set(ctx, i) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r resourceID) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { + // Intentionally left blank. +} + +func (r resourceID) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + // Intentionally left blank. +} + +func (r resourceID) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.State.RemoveResource(ctx) +} + +func (r resourceID) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + id := req.ID + var prefix string + + sep := strings.LastIndex(id, ",") + if sep != -1 { + prefix = id[:sep] + id = id[sep+1:] + } + + bytes, err := base64.RawURLEncoding.DecodeString(id) + if err != nil { + resp.Diagnostics.AddError( + "error decoding ID", + fmt.Sprintf("error decoding ID: %s", err)) + return + } + + b64Std := base64.StdEncoding.EncodeToString(bytes) + hex := hex2.EncodeToString(bytes) + + bigInt := big.Int{} + bigInt.SetBytes(bytes) + dec := bigInt.String() + + var state ID + + state.ID.Value = id + state.ByteLength.Value = int64(len(bytes)) + state.Keepers.ElemType = types.StringType + state.B64Std.Value = prefix + b64Std + state.B64URL.Value = prefix + id + state.Hex.Value = prefix + hex + state.Dec.Value = prefix + dec + + if prefix == "" { + state.Prefix.Null = true + } else { + state.Prefix.Value = prefix + } + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/internal/provider/resource_id_test.go b/internal/provider_fm/resource_id_test.go similarity index 90% rename from internal/provider/resource_id_test.go rename to internal/provider_fm/resource_id_test.go index d1d03afd..7dda7ec0 100644 --- a/internal/provider/resource_id_test.go +++ b/internal/provider_fm/resource_id_test.go @@ -1,4 +1,4 @@ -package provider +package provider_fm import ( "fmt" @@ -16,8 +16,8 @@ type idLens struct { func TestAccResourceID(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourceIDConfig, @@ -40,8 +40,8 @@ func TestAccResourceID(t *testing.T) { func TestAccResourceID_importWithPrefix(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourceIDConfigWithPrefix, diff --git a/internal/provider_fm/resource_uuid.go b/internal/provider_fm/resource_uuid.go index 49666d26..3bd7f7c7 100644 --- a/internal/provider_fm/resource_uuid.go +++ b/internal/provider_fm/resource_uuid.go @@ -7,7 +7,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) type resourceUUIDType struct{} @@ -94,13 +93,7 @@ func (r resourceUUID) Read(ctx context.Context, req tfsdk.ReadResourceRequest, r } func (r resourceUUID) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - var plan UUID - diags := req.Plan.Get(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - + // Intentionally left blank. } func (r resourceUUID) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { @@ -108,8 +101,6 @@ func (r resourceUUID) Delete(ctx context.Context, req tfsdk.DeleteResourceReques } func (r resourceUUID) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - tfsdk.ResourceImportStatePassthroughID(ctx, tftypes.NewAttributePath().WithAttributeName("id"), req, resp) - bytes, err := uuid.ParseUUID(req.ID) if err != nil { resp.Diagnostics.AddError( From f53727709c8571d57e807a4f96bbb2ec61dcb91b Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 3 May 2022 15:54:53 +0100 Subject: [PATCH 06/96] Removing dead code (#177) --- internal/provider/resource_id.go | 180 -------------------------- internal/provider/resource_uuid.go | 87 ------------- internal/provider_fm/provider_test.go | 1 + 3 files changed, 1 insertion(+), 267 deletions(-) delete mode 100644 internal/provider/resource_id.go delete mode 100644 internal/provider/resource_uuid.go diff --git a/internal/provider/resource_id.go b/internal/provider/resource_id.go deleted file mode 100644 index 936ef168..00000000 --- a/internal/provider/resource_id.go +++ /dev/null @@ -1,180 +0,0 @@ -package provider - -import ( - "context" - "crypto/rand" - "encoding/base64" - "encoding/hex" - "fmt" - "math/big" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func resourceId() *schema.Resource { - return &schema.Resource{ - Description: ` -The resource ` + "`random_id`" + ` generates random numbers that are intended to be -used as unique identifiers for other resources. - -This resource *does* use a cryptographic random number generator in order -to minimize the chance of collisions, making the results of this resource -when a 16-byte identifier is requested of equivalent uniqueness to a -type-4 UUID. - -This resource can be used in conjunction with resources that have -the ` + "`create_before_destroy`" + ` lifecycle flag set to avoid conflicts with -unique names during the brief period where both the old and new resources -exist concurrently. -`, - CreateContext: CreateID, - ReadContext: RepopulateEncodings, - DeleteContext: RemoveResourceFromState, - Importer: &schema.ResourceImporter{ - StateContext: ImportID, - }, - - Schema: map[string]*schema.Schema{ - "keepers": { - Description: "Arbitrary map of values that, when changed, will trigger recreation of " + - "resource. See [the main provider documentation](../index.html) for more information.", - Type: schema.TypeMap, - Optional: true, - ForceNew: true, - }, - - "byte_length": { - Description: "The number of random bytes to produce. The minimum value is 1, which produces " + - "eight bits of randomness.", - Type: schema.TypeInt, - Required: true, - ForceNew: true, - }, - - "prefix": { - Description: "Arbitrary string to prefix the output value with. This string is supplied as-is, " + - "meaning it is not guaranteed to be URL-safe or base64 encoded.", - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - - "b64_url": { - Description: "The generated id presented in base64, using the URL-friendly character set: " + - "case-sensitive letters, digits and the characters `_` and `-`.", - Type: schema.TypeString, - Computed: true, - }, - - "b64_std": { - Description: "The generated id presented in base64 without additional transformations.", - Type: schema.TypeString, - Computed: true, - }, - - "hex": { - Description: "The generated id presented in padded hexadecimal digits. This result will " + - "always be twice as long as the requested byte length.", - Type: schema.TypeString, - Computed: true, - }, - - "dec": { - Description: "The generated id presented in non-padded decimal digits.", - Type: schema.TypeString, - Computed: true, - }, - - "id": { - Description: "The generated id presented in base64 without additional transformations or prefix.", - Type: schema.TypeString, - Computed: true, - }, - }, - } -} - -func CreateID(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - byteLength := d.Get("byte_length").(int) - bytes := make([]byte, byteLength) - - n, err := rand.Reader.Read(bytes) - if n != byteLength { - return append(diags, diag.Errorf("generated insufficient random bytes: %s", err)...) - } - if err != nil { - return append(diags, diag.Errorf("error generating random bytes: %s", err)...) - } - - b64Str := base64.RawURLEncoding.EncodeToString(bytes) - d.SetId(b64Str) - - repopEncsDiags := RepopulateEncodings(ctx, d, meta) - if repopEncsDiags != nil { - return append(diags, repopEncsDiags...) - } - - return diags -} - -func RepopulateEncodings(_ context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics { - var diags diag.Diagnostics - prefix := d.Get("prefix").(string) - base64Str := d.Id() - - bytes, err := base64.RawURLEncoding.DecodeString(base64Str) - if err != nil { - return append(diags, diag.Errorf("error decoding ID: %s", err)...) - } - - b64StdStr := base64.StdEncoding.EncodeToString(bytes) - hexStr := hex.EncodeToString(bytes) - - bigInt := big.Int{} - bigInt.SetBytes(bytes) - decStr := bigInt.String() - - if err := d.Set("b64_url", prefix+base64Str); err != nil { - return append(diags, diag.Errorf("error setting b64_url: %s", err)...) - } - if err := d.Set("b64_std", prefix+b64StdStr); err != nil { - return append(diags, diag.Errorf("error setting b64_std: %s", err)...) - } - if err := d.Set("hex", prefix+hexStr); err != nil { - return append(diags, diag.Errorf("error setting hex: %s", err)...) - } - if err := d.Set("dec", prefix+decStr); err != nil { - return append(diags, diag.Errorf("error setting dec: %s", err)...) - } - - return nil -} - -func ImportID(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { - id := d.Id() - - sep := strings.LastIndex(id, ",") - if sep != -1 { - if err := d.Set("prefix", id[:sep]); err != nil { - return nil, fmt.Errorf("error setting prefix: %w", err) - } - - id = id[sep+1:] - } - - bytes, err := base64.RawURLEncoding.DecodeString(id) - if err != nil { - return nil, fmt.Errorf("error decoding ID: %w", err) - } - - if err := d.Set("byte_length", len(bytes)); err != nil { - return nil, fmt.Errorf("error setting byte_length: %w", err) - } - - d.SetId(id) - - return []*schema.ResourceData{d}, nil -} diff --git a/internal/provider/resource_uuid.go b/internal/provider/resource_uuid.go deleted file mode 100644 index 06f423f2..00000000 --- a/internal/provider/resource_uuid.go +++ /dev/null @@ -1,87 +0,0 @@ -package provider - -import ( - "context" - "fmt" - - "github.com/hashicorp/go-uuid" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func resourceUuid() *schema.Resource { - return &schema.Resource{ - Description: "The resource `random_uuid` generates random uuid string that is intended to be " + - "used as unique identifiers for other resources.\n" + - "\n" + - "This resource uses [hashicorp/go-uuid](https://github.com/hashicorp/go-uuid) to generate a " + - "UUID-formatted string for use with services needed a unique string identifier.", - CreateContext: CreateUuid, - ReadContext: schema.NoopContext, - DeleteContext: RemoveResourceFromState, - Importer: &schema.ResourceImporter{ - StateContext: ImportUuid, - }, - - Schema: map[string]*schema.Schema{ - "keepers": { - Description: "Arbitrary map of values that, when changed, will trigger recreation of " + - "resource. See [the main provider documentation](../index.html) for more information.", - Type: schema.TypeMap, - Optional: true, - ForceNew: true, - }, - - "result": { - Description: "The generated uuid presented in string format.", - Type: schema.TypeString, - Computed: true, - }, - - "id": { - Description: "The generated uuid presented in string format.", - Type: schema.TypeString, - Computed: true, - }, - }, - } -} - -func CreateUuid(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - - result, err := uuid.GenerateUUID() - if err != nil { - return append(diags, diag.Errorf("error generating uuid: %s", err)...) - } - - if err := d.Set("result", result); err != nil { - return append(diags, diag.Errorf("error setting result: %s", err)...) - } - - d.SetId(result) - - return nil -} - -func ImportUuid(_ context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - id := d.Id() - - bytes, err := uuid.ParseUUID(id) - if err != nil { - return nil, fmt.Errorf("error parsing uuid bytes: %w", err) - } - - result, err := uuid.FormatUUID(bytes) - if err != nil { - return nil, fmt.Errorf("error formatting uuid bytes: %w", err) - } - - if err := d.Set("result", result); err != nil { - return nil, fmt.Errorf("error setting result: %w", err) - } - - d.SetId(result) - - return []*schema.ResourceData{d}, nil -} diff --git a/internal/provider_fm/provider_test.go b/internal/provider_fm/provider_test.go index 1f4fc05e..a7357b7b 100644 --- a/internal/provider_fm/provider_test.go +++ b/internal/provider_fm/provider_test.go @@ -9,6 +9,7 @@ import ( func testAccPreCheck(t *testing.T) { } +//nolint:unparam var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ "random": func() (tfprotov6.ProviderServer, error) { return providerserver.NewProtocol6(NewFramework())(), nil From de8217e9fe632af264c68c5b8a2d0f9711369b81 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 4 May 2022 09:11:04 +0100 Subject: [PATCH 07/96] Migrate integer resource to framework (#177) --- internal/provider/provider.go | 1 - internal/provider_fm/models.go | 11 +- internal/provider_fm/provider.go | 5 +- internal/provider_fm/resource_integer.go | 192 ++++++++++++++++++ .../resource_integer_test.go | 22 +- internal/provider_fm/seed.go | 24 +++ 6 files changed, 240 insertions(+), 15 deletions(-) create mode 100644 internal/provider_fm/resource_integer.go rename internal/{provider => provider_fm}/resource_integer_test.go (87%) create mode 100644 internal/provider_fm/seed.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 2366e367..598e77c0 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -20,7 +20,6 @@ func New() *schema.Provider { "random_pet": resourcePet(), "random_string": resourceString(), "random_password": resourcePassword(), - "random_integer": resourceInteger(), }, } } diff --git a/internal/provider_fm/models.go b/internal/provider_fm/models.go index 83fb958d..fab86258 100644 --- a/internal/provider_fm/models.go +++ b/internal/provider_fm/models.go @@ -13,8 +13,17 @@ type ID struct { Dec types.String `tfsdk:"dec"` } +type Integer struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Min types.Int64 `tfsdk:"min"` + Max types.Int64 `tfsdk:"max"` + Seed types.String `tfsdk:"seed"` + Result types.Int64 `tfsdk:"result"` +} + type UUID struct { ID types.String `tfsdk:"id"` - Result types.String `tfsdk:"result"` Keepers types.Map `tfsdk:"keepers"` + Result types.String `tfsdk:"result"` } diff --git a/internal/provider_fm/provider.go b/internal/provider_fm/provider.go index de5d427f..c7c6f92f 100644 --- a/internal/provider_fm/provider.go +++ b/internal/provider_fm/provider.go @@ -24,8 +24,9 @@ func (p *provider) Configure(ctx context.Context, req tfsdk.ConfigureProviderReq func (p *provider) GetResources(ctx context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { return map[string]tfsdk.ResourceType{ - "random_id": resourceIDType{}, - "random_uuid": resourceUUIDType{}, + "random_id": resourceIDType{}, + "random_integer": resourceIntegerType{}, + "random_uuid": resourceUUIDType{}, }, nil } diff --git a/internal/provider_fm/resource_integer.go b/internal/provider_fm/resource_integer.go new file mode 100644 index 00000000..5f6e52f9 --- /dev/null +++ b/internal/provider_fm/resource_integer.go @@ -0,0 +1,192 @@ +package provider_fm + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "strconv" + "strings" +) + +type resourceIntegerType struct{} + +func (r resourceIntegerType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Description: "The resource `random_integer` generates random values from a given range, described " + + "by the `min` and `max` attributes of a given resource.\n" + + "\n" + + "This resource can be used in conjunction with resources that have the `create_before_destroy` " + + "lifecycle flag set, to avoid conflicts with unique names during the brief period where both the " + + "old and new resources exist concurrently.", + Attributes: map[string]tfsdk.Attribute{ + "keepers": { + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + Type: types.MapType{ + ElemType: types.StringType, + }, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + "min": { + Description: "The minimum inclusive value of the range.", + Type: types.Int64Type, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + "max": { + Description: "The maximum inclusive value of the range.", + Type: types.Int64Type, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + "seed": { + Description: "A custom seed to always produce the same value.", + Type: types.StringType, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + "result": { + Description: "The random integer result.", + Type: types.Int64Type, + Computed: true, + }, + "id": { + Description: "The generated uuid presented in string format.", + Type: types.StringType, + Computed: true, + }, + }, + }, nil +} + +func (r resourceIntegerType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return resourceInteger{ + p: *(p.(*provider)), + }, nil +} + +type resourceInteger struct { + p provider +} + +func (r resourceInteger) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + if !r.p.configured { + resp.Diagnostics.AddError( + "provider not configured", + "provider not configured", + ) + } + + var plan Integer + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + max := int(plan.Max.Value) + min := int(plan.Min.Value) + seed := plan.Seed.Value + + if max < min { + resp.Diagnostics.AddError( + "minimum value needs to be smaller than or equal to maximum value", + "minimum value needs to be smaller than or equal to maximum value", + ) + return + } + + rand := NewRand(seed) + number := rand.Intn((max+1)-min) + min + + u := &Integer{ + ID: types.String{Value: strconv.Itoa(number)}, + Keepers: plan.Keepers, + Min: types.Int64{Value: int64(min)}, + Max: types.Int64{Value: int64(max)}, + Result: types.Int64{Value: int64(number)}, + } + + if seed != "" { + u.Seed.Value = seed + } else { + u.Seed.Null = true + } + + diags = resp.State.Set(ctx, u) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r resourceInteger) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { + // Intentionally left blank. +} + +func (r resourceInteger) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + // Intentionally left blank. +} + +func (r resourceInteger) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.State.RemoveResource(ctx) +} + +func (r resourceInteger) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + parts := strings.Split(req.ID, ",") + if len(parts) != 3 && len(parts) != 4 { + resp.Diagnostics.AddError( + "Invalid import usage: expecting {result},{min},{max} or {result},{min},{max},{seed}", + "Invalid import usage: expecting {result},{min},{max} or {result},{min},{max},{seed}", + ) + return + } + + result, err := strconv.ParseInt(parts[0], 10, 64) + if err != nil { + resp.Diagnostics.AddError( + "error parsing result", + fmt.Sprintf("error parsing result: %s", err), + ) + return + } + + min, err := strconv.ParseInt(parts[1], 10, 64) + if err != nil { + resp.Diagnostics.AddError( + "error parsing min", + fmt.Sprintf("error parsing min: %s", err), + ) + return + } + + max, err := strconv.ParseInt(parts[2], 10, 64) + if err != nil { + resp.Diagnostics.AddError( + "error parsing max", + fmt.Sprintf("error parsing max: %s", err), + ) + return + } + + var state Integer + + state.ID.Value = parts[0] + state.Keepers.ElemType = types.StringType + state.Result.Value = int64(result) + state.Min.Value = int64(min) + state.Max.Value = int64(max) + + if len(parts) == 4 { + state.Seed.Value = parts[3] + } + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/internal/provider/resource_integer_test.go b/internal/provider_fm/resource_integer_test.go similarity index 87% rename from internal/provider/resource_integer_test.go rename to internal/provider_fm/resource_integer_test.go index b9ad040a..df2c74e3 100644 --- a/internal/provider/resource_integer_test.go +++ b/internal/provider_fm/resource_integer_test.go @@ -1,4 +1,4 @@ -package provider +package provider_fm import ( "fmt" @@ -11,8 +11,8 @@ import ( func TestAccResourceIntegerBasic(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testRandomIntegerBasic, @@ -33,8 +33,8 @@ func TestAccResourceIntegerBasic(t *testing.T) { func TestAccResourceIntegerUpdate(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testRandomIntegerBasic, @@ -55,8 +55,8 @@ func TestAccResourceIntegerUpdate(t *testing.T) { func TestAccResourceIntegerSeedless_to_seeded(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testRandomIntegerSeedless, @@ -77,8 +77,8 @@ func TestAccResourceIntegerSeedless_to_seeded(t *testing.T) { func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testRandomIntegerBasic, @@ -99,8 +99,8 @@ func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) { func TestAccResourceIntegerBig(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testRandomIntegerBig, diff --git a/internal/provider_fm/seed.go b/internal/provider_fm/seed.go new file mode 100644 index 00000000..ac0283a4 --- /dev/null +++ b/internal/provider_fm/seed.go @@ -0,0 +1,24 @@ +package provider_fm + +import ( + "hash/crc64" + "math/rand" + "time" +) + +// NewRand returns a seeded random number generator, using a seed derived +// from the provided string. +// +// If the seed string is empty, the current time is used as a seed. +func NewRand(seed string) *rand.Rand { + var seedInt int64 + if seed != "" { + crcTable := crc64.MakeTable(crc64.ISO) + seedInt = int64(crc64.Checksum([]byte(seed), crcTable)) + } else { + seedInt = time.Now().UnixNano() + } + + randSource := rand.NewSource(seedInt) + return rand.New(randSource) +} From bf645bd7fe5aeaf3ccd8fc17646e898cc11e063b Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 4 May 2022 09:32:23 +0100 Subject: [PATCH 08/96] Removing dead code and unnecessary type conversions (#177) --- internal/provider/resource_integer.go | 140 ----------------------- internal/provider_fm/resource_integer.go | 6 +- 2 files changed, 3 insertions(+), 143 deletions(-) delete mode 100644 internal/provider/resource_integer.go diff --git a/internal/provider/resource_integer.go b/internal/provider/resource_integer.go deleted file mode 100644 index be66f2dc..00000000 --- a/internal/provider/resource_integer.go +++ /dev/null @@ -1,140 +0,0 @@ -package provider - -import ( - "context" - "fmt" - "strconv" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func resourceInteger() *schema.Resource { - return &schema.Resource{ - Description: "The resource `random_integer` generates random values from a given range, described " + - "by the `min` and `max` attributes of a given resource.\n" + - "\n" + - "This resource can be used in conjunction with resources that have the `create_before_destroy` " + - "lifecycle flag set, to avoid conflicts with unique names during the brief period where both the " + - "old and new resources exist concurrently.", - CreateContext: CreateInteger, - ReadContext: schema.NoopContext, - DeleteContext: RemoveResourceFromState, - Importer: &schema.ResourceImporter{ - StateContext: ImportInteger, - }, - - Schema: map[string]*schema.Schema{ - "keepers": { - Description: "Arbitrary map of values that, when changed, will trigger recreation of " + - "resource. See [the main provider documentation](../index.html) for more information.", - Type: schema.TypeMap, - Optional: true, - ForceNew: true, - }, - - "min": { - Description: "The minimum inclusive value of the range.", - Type: schema.TypeInt, - Required: true, - ForceNew: true, - }, - - "max": { - Description: "The maximum inclusive value of the range.", - Type: schema.TypeInt, - Required: true, - ForceNew: true, - }, - - "seed": { - Description: "A custom seed to always produce the same value.", - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - - "result": { - Description: "The random integer result.", - Type: schema.TypeInt, - Computed: true, - }, - - "id": { - Description: "The string representation of the integer result.", - Type: schema.TypeString, - Computed: true, - }, - }, - UseJSONNumber: true, - } -} - -func CreateInteger(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - min := d.Get("min").(int) - max := d.Get("max").(int) - seed := d.Get("seed").(string) - - if max < min { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "minimum value needs to be smaller than or equal to maximum value", - }) - } - rand := NewRand(seed) - number := rand.Intn((max+1)-min) + min - - if err := d.Set("result", number); err != nil { - return diag.Errorf("error setting result: %s", err) - } - - d.SetId(strconv.Itoa(number)) - - return nil -} - -func ImportInteger(_ context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - parts := strings.Split(d.Id(), ",") - if len(parts) != 3 && len(parts) != 4 { - return nil, fmt.Errorf("Invalid import usage: expecting {result},{min},{max} or {result},{min},{max},{seed}") - } - - result, err := strconv.Atoi(parts[0]) - if err != nil { - return nil, fmt.Errorf("error parsing result: %w", err) - } - - if err := d.Set("result", result); err != nil { - return nil, fmt.Errorf("error setting result: %w", err) - } - - min, err := strconv.Atoi(parts[1]) - if err != nil { - return nil, fmt.Errorf("error parsing min: %w", err) - } - - if err := d.Set("min", min); err != nil { - return nil, fmt.Errorf("error setting min: %w", err) - } - - max, err := strconv.Atoi(parts[2]) - if err != nil { - return nil, fmt.Errorf("error parsing max: %w", err) - } - - if err := d.Set("max", max); err != nil { - return nil, fmt.Errorf("error setting max: %w", err) - } - - if len(parts) == 4 { - if err := d.Set("seed", parts[3]); err != nil { - return nil, fmt.Errorf("error setting seed: %w", err) - } - } - - d.SetId(parts[0]) - - return []*schema.ResourceData{d}, nil -} diff --git a/internal/provider_fm/resource_integer.go b/internal/provider_fm/resource_integer.go index 5f6e52f9..0603135c 100644 --- a/internal/provider_fm/resource_integer.go +++ b/internal/provider_fm/resource_integer.go @@ -176,9 +176,9 @@ func (r resourceInteger) ImportState(ctx context.Context, req tfsdk.ImportResour state.ID.Value = parts[0] state.Keepers.ElemType = types.StringType - state.Result.Value = int64(result) - state.Min.Value = int64(min) - state.Max.Value = int64(max) + state.Result.Value = result + state.Min.Value = min + state.Max.Value = max if len(parts) == 4 { state.Seed.Value = parts[3] From 0ea5717acff36186a2b8337ab27d74e3322147e5 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 4 May 2022 10:48:06 +0100 Subject: [PATCH 09/96] Updating types and adding empty validators and plan modifiers (#177) --- internal/provider_fm/string.go | 326 +++++++++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 internal/provider_fm/string.go diff --git a/internal/provider_fm/string.go b/internal/provider_fm/string.go new file mode 100644 index 00000000..fad058e0 --- /dev/null +++ b/internal/provider_fm/string.go @@ -0,0 +1,326 @@ +package provider_fm + +import ( + "context" + "crypto/rand" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "math/big" + "sort" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func stringSchemaV1(sensitive bool, description string) tfsdk.Schema { + idDesc := "The generated random string." + if sensitive { + idDesc = "A static value used internally by Terraform, this should not be referenced in configurations." + } + + return tfsdk.Schema{ + Description: description, + Attributes: map[string]tfsdk.Attribute{ + "keepers": { + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + Type: types.MapType{ + ElemType: types.StringType, + }, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + + "length": { + Description: "The length of the string desired. The minimum value for length is 1 and, length " + + "must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`).", + Type: types.Int64Type, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + // TODO: Implement Validate func. + Validators: []tfsdk.AttributeValidator{lengthValidator{}}, + //ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)), + }, + + "special": { + Description: "Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`.", + Type: types.BoolType, + Optional: true, + // TODO: Implement Modify func. + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + defaultBool{}, + }, + //Default: true, + }, + + "upper": { + Description: "Include uppercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + // TODO: Implement Modify func. + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + defaultBool{}, + }, + //Default: true, + }, + + "lower": { + Description: "Include lowercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + // TODO: Implement Modify func. + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + defaultBool{}, + }, + //Default: true, + }, + + "number": { + Description: "Include numeric characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + // TODO: Implement Modify func. + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + defaultBool{}, + }, + //Default: true, + }, + + "min_numeric": { + Description: "Minimum number of numeric characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + // TODO: Implement Modify func. + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + defaultInt{}, + }, + //Default: 0, + }, + + "min_upper": { + Description: "Minimum number of uppercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + // TODO: Implement Modify func. + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + defaultInt{}, + }, + //Default: 0, + }, + + "min_lower": { + Description: "Minimum number of lowercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + // TODO: Implement Modify func. + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + defaultInt{}, + }, + //Default: 0, + }, + + "min_special": { + Description: "Minimum number of special characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + // TODO: Implement Modify func. + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + defaultInt{}, + }, + //Default: 0, + }, + + "override_special": { + Description: "Supply your own list of special characters to use for string generation. This " + + "overrides the default character list in the special argument. The `special` argument must " + + "still be set to true for any overwritten characters to be used in generation.", + Type: types.StringType, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + + "result": { + Description: "The generated random string.", + Type: types.StringType, + Computed: true, + Sensitive: sensitive, + }, + + "id": { + Description: idDesc, + Computed: true, + Type: types.StringType, + }, + }, + } +} + +type lengthValidator struct{} + +func (l lengthValidator) Description(context.Context) string { + return "Length validator ensures that length is at least 1" +} + +func (l lengthValidator) MarkdownDescription(context.Context) string { + return "Length validator ensures that `length` is at least 1" +} + +func (l lengthValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { + +} + +type defaultBool struct{} + +func (d defaultBool) Description(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set." +} + +func (d defaultBool) MarkdownDescription(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set." +} + +func (d defaultBool) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + +} + +type defaultInt struct{} + +func (d defaultInt) Description(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set." +} + +func (d defaultInt) MarkdownDescription(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set." +} + +func (d defaultInt) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + +} + +func createStringFunc(sensitive bool) func(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return func(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + const numChars = "0123456789" + const lowerChars = "abcdefghijklmnopqrstuvwxyz" + const upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + var ( + specialChars = "!@#$%&*()-_=+[]{}<>:?" + diags diag.Diagnostics + ) + + length := d.Get("length").(int) + upper := d.Get("upper").(bool) + minUpper := d.Get("min_upper").(int) + lower := d.Get("lower").(bool) + minLower := d.Get("min_lower").(int) + number := d.Get("number").(bool) + minNumeric := d.Get("min_numeric").(int) + special := d.Get("special").(bool) + minSpecial := d.Get("min_special").(int) + overrideSpecial := d.Get("override_special").(string) + + if length < minUpper+minLower+minNumeric+minSpecial { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("length (%d) must be >= min_upper + min_lower + min_numeric + min_special (%d)", length, minUpper+minLower+minNumeric+minSpecial), + }) + } + + if overrideSpecial != "" { + specialChars = overrideSpecial + } + + var chars = string("") + if upper { + chars += upperChars + } + if lower { + chars += lowerChars + } + if number { + chars += numChars + } + if special { + chars += specialChars + } + + minMapping := map[string]int{ + numChars: minNumeric, + lowerChars: minLower, + upperChars: minUpper, + specialChars: minSpecial, + } + var result = make([]byte, 0, length) + for k, v := range minMapping { + s, err := generateRandomBytes(&k, v) + if err != nil { + return append(diags, diag.Errorf("error generating random bytes: %s", err)...) + } + result = append(result, s...) + } + s, err := generateRandomBytes(&chars, length-len(result)) + if err != nil { + return append(diags, diag.Errorf("error generating random bytes: %s", err)...) + } + result = append(result, s...) + order := make([]byte, len(result)) + if _, err := rand.Read(order); err != nil { + return append(diags, diag.Errorf("error generating random bytes: %s", err)...) + } + sort.Slice(result, func(i, j int) bool { + return order[i] < order[j] + }) + + if err := d.Set("result", string(result)); err != nil { + return append(diags, diag.Errorf("error setting result: %s", err)...) + } + + if sensitive { + d.SetId("none") + } else { + d.SetId(string(result)) + } + return nil + } +} + +func generateRandomBytes(charSet *string, length int) ([]byte, error) { + bytes := make([]byte, length) + setLen := big.NewInt(int64(len(*charSet))) + for i := range bytes { + idx, err := rand.Int(rand.Reader, setLen) + if err != nil { + return nil, err + } + bytes[i] = (*charSet)[idx.Int64()] + } + return bytes, nil +} + +func readNil(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return nil +} + +func importStringFunc(sensitive bool) schema.StateContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + val := d.Id() + + if sensitive { + d.SetId("none") + } + + if err := d.Set("result", val); err != nil { + return nil, fmt.Errorf("error setting result: %w", err) + } + + return []*schema.ResourceData{d}, nil + } +} From 69c254790ecc75f28051026798ab7dbf1f777cd0 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 4 May 2022 16:59:06 +0100 Subject: [PATCH 10/96] Migrating password and string resources from SDKv2 to Framework (#177) --- internal/provider/provider.go | 6 +- internal/provider/resource_password.go | 22 -- internal/provider/resource_string.go | 29 -- .../provider/resource_string_migration.go | 2 +- internal/provider/string.go | 247 -------------- internal/provider_fm/models.go | 16 + internal/provider_fm/provider.go | 8 +- internal/provider_fm/resource_password.go | 52 +++ .../resource_pasword_test.go | 16 +- internal/provider_fm/resource_string.go | 55 ++++ .../resource_string_test.go | 20 +- internal/provider_fm/string.go | 305 +++++++++++------- 12 files changed, 341 insertions(+), 437 deletions(-) delete mode 100644 internal/provider/resource_password.go delete mode 100644 internal/provider/resource_string.go delete mode 100644 internal/provider/string.go create mode 100644 internal/provider_fm/resource_password.go rename internal/{provider => provider_fm}/resource_pasword_test.go (88%) create mode 100644 internal/provider_fm/resource_string.go rename internal/{provider => provider_fm}/resource_string_test.go (89%) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 598e77c0..770b3ed5 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -16,10 +16,8 @@ func New() *schema.Provider { Schema: map[string]*schema.Schema{}, ResourcesMap: map[string]*schema.Resource{ - "random_shuffle": resourceShuffle(), - "random_pet": resourcePet(), - "random_string": resourceString(), - "random_password": resourcePassword(), + "random_shuffle": resourceShuffle(), + "random_pet": resourcePet(), }, } } diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go deleted file mode 100644 index 6120beb7..00000000 --- a/internal/provider/resource_password.go +++ /dev/null @@ -1,22 +0,0 @@ -package provider - -import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func resourcePassword() *schema.Resource { - return &schema.Resource{ - Description: "Identical to [random_string](string.html) with the exception that the result is " + - "treated as sensitive and, thus, _not_ displayed in console output. Read more about sensitive " + - "data handling in the [Terraform documentation](https://www.terraform.io/docs/language/state/sensitive-data.html).\n" + - "\n" + - "This resource *does* use a cryptographic random number generator.", - CreateContext: createStringFunc(true), - ReadContext: readNil, - DeleteContext: RemoveResourceFromState, - Schema: stringSchemaV1(true), - Importer: &schema.ResourceImporter{ - StateContext: importStringFunc(true), - }, - } -} diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go deleted file mode 100644 index ce77d92d..00000000 --- a/internal/provider/resource_string.go +++ /dev/null @@ -1,29 +0,0 @@ -package provider - -import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func resourceString() *schema.Resource { - return &schema.Resource{ - Description: "The resource `random_string` generates a random permutation of alphanumeric " + - "characters and optionally special characters.\n" + - "\n" + - "This resource *does* use a cryptographic random number generator.\n" + - "\n" + - "Historically this resource's intended usage has been ambiguous as the original example used " + - "it in a password. For backwards compatibility it will continue to exist. For unique ids please " + - "use [random_id](id.html), for sensitive random values please use [random_password](password.html).", - CreateContext: createStringFunc(false), - ReadContext: readNil, - DeleteContext: RemoveResourceFromState, - // MigrateState is deprecated but the implementation is being left in place as per the - // [SDK documentation](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/helper/schema/resource.go#L91). - MigrateState: resourceRandomStringMigrateState, - SchemaVersion: 1, - Schema: stringSchemaV1(false), - Importer: &schema.ResourceImporter{ - StateContext: importStringFunc(false), - }, - } -} diff --git a/internal/provider/resource_string_migration.go b/internal/provider/resource_string_migration.go index ad565c17..2b3e652a 100644 --- a/internal/provider/resource_string_migration.go +++ b/internal/provider/resource_string_migration.go @@ -8,7 +8,7 @@ import ( ) func resourceRandomStringMigrateState( - v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { + v int, is *terraform.InstanceState, _ interface{}) (*terraform.InstanceState, error) { switch v { case 0: log.Println("[INFO] Found random string state v0; migrating to v1") diff --git a/internal/provider/string.go b/internal/provider/string.go deleted file mode 100644 index 3529ce3d..00000000 --- a/internal/provider/string.go +++ /dev/null @@ -1,247 +0,0 @@ -// This file provides shared functionality between `resource_string` and `resource_password`. -// There is no intent to permanently couple their implementations -// Over time they could diverge, or one becomes deprecated -package provider - -import ( - "context" - "crypto/rand" - "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "math/big" - "sort" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func stringSchemaV1(sensitive bool) map[string]*schema.Schema { - idDesc := "The generated random string." - if sensitive { - idDesc = "A static value used internally by Terraform, this should not be referenced in configurations." - } - - return map[string]*schema.Schema{ - "keepers": { - Description: "Arbitrary map of values that, when changed, will trigger recreation of " + - "resource. See [the main provider documentation](../index.html) for more information.", - Type: schema.TypeMap, - Optional: true, - ForceNew: true, - }, - - "length": { - Description: "The length of the string desired. The minimum value for length is 1 and, length " + - "must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`).", - Type: schema.TypeInt, - Required: true, - ForceNew: true, - ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)), - }, - - "special": { - Description: "Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`.", - Type: schema.TypeBool, - Optional: true, - Default: true, - ForceNew: true, - }, - - "upper": { - Description: "Include uppercase alphabet characters in the result. Default value is `true`.", - Type: schema.TypeBool, - Optional: true, - Default: true, - ForceNew: true, - }, - - "lower": { - Description: "Include lowercase alphabet characters in the result. Default value is `true`.", - Type: schema.TypeBool, - Optional: true, - Default: true, - ForceNew: true, - }, - - "number": { - Description: "Include numeric characters in the result. Default value is `true`.", - Type: schema.TypeBool, - Optional: true, - Default: true, - ForceNew: true, - }, - - "min_numeric": { - Description: "Minimum number of numeric characters in the result. Default value is `0`.", - Type: schema.TypeInt, - Optional: true, - Default: 0, - ForceNew: true, - }, - - "min_upper": { - Description: "Minimum number of uppercase alphabet characters in the result. Default value is `0`.", - Type: schema.TypeInt, - Optional: true, - Default: 0, - ForceNew: true, - }, - - "min_lower": { - Description: "Minimum number of lowercase alphabet characters in the result. Default value is `0`.", - Type: schema.TypeInt, - Optional: true, - Default: 0, - ForceNew: true, - }, - - "min_special": { - Description: "Minimum number of special characters in the result. Default value is `0`.", - Type: schema.TypeInt, - Optional: true, - Default: 0, - ForceNew: true, - }, - - "override_special": { - Description: "Supply your own list of special characters to use for string generation. This " + - "overrides the default character list in the special argument. The `special` argument must " + - "still be set to true for any overwritten characters to be used in generation.", - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - - "result": { - Description: "The generated random string.", - Type: schema.TypeString, - Computed: true, - Sensitive: sensitive, - }, - - "id": { - Description: idDesc, - Computed: true, - Type: schema.TypeString, - }, - } -} - -func createStringFunc(sensitive bool) func(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - return func(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - const numChars = "0123456789" - const lowerChars = "abcdefghijklmnopqrstuvwxyz" - const upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - var ( - specialChars = "!@#$%&*()-_=+[]{}<>:?" - diags diag.Diagnostics - ) - - length := d.Get("length").(int) - upper := d.Get("upper").(bool) - minUpper := d.Get("min_upper").(int) - lower := d.Get("lower").(bool) - minLower := d.Get("min_lower").(int) - number := d.Get("number").(bool) - minNumeric := d.Get("min_numeric").(int) - special := d.Get("special").(bool) - minSpecial := d.Get("min_special").(int) - overrideSpecial := d.Get("override_special").(string) - - if length < minUpper+minLower+minNumeric+minSpecial { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("length (%d) must be >= min_upper + min_lower + min_numeric + min_special (%d)", length, minUpper+minLower+minNumeric+minSpecial), - }) - } - - if overrideSpecial != "" { - specialChars = overrideSpecial - } - - var chars = string("") - if upper { - chars += upperChars - } - if lower { - chars += lowerChars - } - if number { - chars += numChars - } - if special { - chars += specialChars - } - - minMapping := map[string]int{ - numChars: minNumeric, - lowerChars: minLower, - upperChars: minUpper, - specialChars: minSpecial, - } - var result = make([]byte, 0, length) - for k, v := range minMapping { - s, err := generateRandomBytes(&k, v) - if err != nil { - return append(diags, diag.Errorf("error generating random bytes: %s", err)...) - } - result = append(result, s...) - } - s, err := generateRandomBytes(&chars, length-len(result)) - if err != nil { - return append(diags, diag.Errorf("error generating random bytes: %s", err)...) - } - result = append(result, s...) - order := make([]byte, len(result)) - if _, err := rand.Read(order); err != nil { - return append(diags, diag.Errorf("error generating random bytes: %s", err)...) - } - sort.Slice(result, func(i, j int) bool { - return order[i] < order[j] - }) - - if err := d.Set("result", string(result)); err != nil { - return append(diags, diag.Errorf("error setting result: %s", err)...) - } - - if sensitive { - d.SetId("none") - } else { - d.SetId(string(result)) - } - return nil - } -} - -func generateRandomBytes(charSet *string, length int) ([]byte, error) { - bytes := make([]byte, length) - setLen := big.NewInt(int64(len(*charSet))) - for i := range bytes { - idx, err := rand.Int(rand.Reader, setLen) - if err != nil { - return nil, err - } - bytes[i] = (*charSet)[idx.Int64()] - } - return bytes, nil -} - -func readNil(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - return nil -} - -func importStringFunc(sensitive bool) schema.StateContextFunc { - return func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - val := d.Id() - - if sensitive { - d.SetId("none") - } - - if err := d.Set("result", val); err != nil { - return nil, fmt.Errorf("error setting result: %w", err) - } - - return []*schema.ResourceData{d}, nil - } -} diff --git a/internal/provider_fm/models.go b/internal/provider_fm/models.go index fab86258..c8b0e2ba 100644 --- a/internal/provider_fm/models.go +++ b/internal/provider_fm/models.go @@ -22,6 +22,22 @@ type Integer struct { Result types.Int64 `tfsdk:"result"` } +type String struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Number types.Bool `tfsdk:"number"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Result types.String `tfsdk:"result"` +} + type UUID struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` diff --git a/internal/provider_fm/provider.go b/internal/provider_fm/provider.go index c7c6f92f..34858ccd 100644 --- a/internal/provider_fm/provider.go +++ b/internal/provider_fm/provider.go @@ -24,9 +24,11 @@ func (p *provider) Configure(ctx context.Context, req tfsdk.ConfigureProviderReq func (p *provider) GetResources(ctx context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { return map[string]tfsdk.ResourceType{ - "random_id": resourceIDType{}, - "random_integer": resourceIntegerType{}, - "random_uuid": resourceUUIDType{}, + "random_id": resourceIDType{}, + "random_integer": resourceIntegerType{}, + "random_password": resourcePasswordType{}, + "random_string": resourceStringType{}, + "random_uuid": resourceUUIDType{}, }, nil } diff --git a/internal/provider_fm/resource_password.go b/internal/provider_fm/resource_password.go new file mode 100644 index 00000000..9cfe10b1 --- /dev/null +++ b/internal/provider_fm/resource_password.go @@ -0,0 +1,52 @@ +package provider_fm + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +type resourcePasswordType struct{} + +func (r resourcePasswordType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { + description := "Identical to [random_string](string.html) with the exception that the result is " + + "treated as sensitive and, thus, _not_ displayed in console output. Read more about sensitive " + + "data handling in the [Terraform documentation](https://www.terraform.io/docs/language/state/sensitive-data.html).\n" + + "\n" + + "This resource *does* use a cryptographic random number generator." + return getStringSchema(true, description), nil +} + +func (r resourcePasswordType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return resourcePassword{ + p: *(p.(*provider)), + }, nil +} + +type resourcePassword struct { + p provider +} + +func (r resourcePassword) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + createString(ctx, req, resp, true) +} + +func (r resourcePassword) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { + // Intentionally left blank. +} + +func (r resourcePassword) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + // Intentionally left blank. +} + +func (r resourcePassword) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.State.RemoveResource(ctx) +} + +func (r resourcePassword) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + importString(ctx, req, resp, true) +} + +func (r resourcePassword) ValidateConfig(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { + validateLength(ctx, req, resp) +} diff --git a/internal/provider/resource_pasword_test.go b/internal/provider_fm/resource_pasword_test.go similarity index 88% rename from internal/provider/resource_pasword_test.go rename to internal/provider_fm/resource_pasword_test.go index 61342a7e..0653f9c4 100644 --- a/internal/provider/resource_pasword_test.go +++ b/internal/provider_fm/resource_pasword_test.go @@ -1,18 +1,18 @@ -package provider +package provider_fm import ( "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "regexp" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccResourcePasswordBasic(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourcePasswordBasic, @@ -49,8 +49,8 @@ func TestAccResourcePasswordBasic(t *testing.T) { func TestAccResourcePasswordOverride(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourcePasswordOverride, @@ -67,8 +67,8 @@ func TestAccResourcePasswordOverride(t *testing.T) { func TestAccResourcePasswordMin(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourcePasswordMin, diff --git a/internal/provider_fm/resource_string.go b/internal/provider_fm/resource_string.go new file mode 100644 index 00000000..1092d4fe --- /dev/null +++ b/internal/provider_fm/resource_string.go @@ -0,0 +1,55 @@ +package provider_fm + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +type resourceStringType struct{} + +func (r resourceStringType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { + description := "The resource `random_string` generates a random permutation of alphanumeric " + + "characters and optionally special characters.\n" + + "\n" + + "This resource *does* use a cryptographic random number generator.\n" + + "\n" + + "Historically this resource's intended usage has been ambiguous as the original example used " + + "it in a password. For backwards compatibility it will continue to exist. For unique ids please " + + "use [random_id](id.html), for sensitive random values please use [random_password](password.html)." + return getStringSchema(false, description), nil +} + +func (r resourceStringType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return resourceString{ + p: *(p.(*provider)), + }, nil +} + +type resourceString struct { + p provider +} + +func (r resourceString) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + createString(ctx, req, resp, false) +} + +func (r resourceString) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { + // Intentionally left blank. +} + +func (r resourceString) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + // Intentionally left blank. +} + +func (r resourceString) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.State.RemoveResource(ctx) +} + +func (r resourceString) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + importString(ctx, req, resp, false) +} + +func (r resourceString) ValidateConfig(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { + validateLength(ctx, req, resp) +} diff --git a/internal/provider/resource_string_test.go b/internal/provider_fm/resource_string_test.go similarity index 89% rename from internal/provider/resource_string_test.go rename to internal/provider_fm/resource_string_test.go index 54f2a391..5405d3e0 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider_fm/resource_string_test.go @@ -1,4 +1,4 @@ -package provider +package provider_fm import ( "fmt" @@ -14,8 +14,8 @@ type customLens struct { func TestAccResourceString(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourceStringBasic, @@ -37,8 +37,8 @@ func TestAccResourceString(t *testing.T) { func TestAccResourceStringOverride(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourceStringOverride, @@ -55,8 +55,8 @@ func TestAccResourceStringOverride(t *testing.T) { func TestAccResourceStringMin(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourceStringMin, @@ -76,8 +76,8 @@ func TestAccResourceStringMin(t *testing.T) { func TestAccResourceStringErrors(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourceStringInvalidConfig, @@ -85,7 +85,7 @@ func TestAccResourceStringErrors(t *testing.T) { }, { Config: testAccResourceStringLengthTooShortConfig, - ExpectError: regexp.MustCompile(`.*expected length to be at least \(1\), got 0`), + ExpectError: regexp.MustCompile(`.*expected length to be at least 1, got 0`), }, }, }) diff --git a/internal/provider_fm/string.go b/internal/provider_fm/string.go index fad058e0..cb71f628 100644 --- a/internal/provider_fm/string.go +++ b/internal/provider_fm/string.go @@ -8,12 +8,9 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "math/big" "sort" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func stringSchemaV1(sensitive bool, description string) tfsdk.Schema { +func getStringSchema(sensitive bool, description string) tfsdk.Schema { idDesc := "The generated random string." if sensitive { idDesc = "A static value used internally by Terraform, this should not be referenced in configurations." @@ -38,114 +35,108 @@ func stringSchemaV1(sensitive bool, description string) tfsdk.Schema { Type: types.Int64Type, Required: true, PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - // TODO: Implement Validate func. - Validators: []tfsdk.AttributeValidator{lengthValidator{}}, - //ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)), + Validators: []tfsdk.AttributeValidator{lengthValidator{}}, }, "special": { Description: "Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`.", Type: types.BoolType, Optional: true, - // TODO: Implement Modify func. + Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), defaultBool{}, }, - //Default: true, }, "upper": { Description: "Include uppercase alphabet characters in the result. Default value is `true`.", Type: types.BoolType, Optional: true, - // TODO: Implement Modify func. + Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), defaultBool{}, }, - //Default: true, }, "lower": { Description: "Include lowercase alphabet characters in the result. Default value is `true`.", Type: types.BoolType, Optional: true, - // TODO: Implement Modify func. + Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), defaultBool{}, }, - //Default: true, }, "number": { Description: "Include numeric characters in the result. Default value is `true`.", Type: types.BoolType, Optional: true, - // TODO: Implement Modify func. + Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), defaultBool{}, }, - //Default: true, }, "min_numeric": { Description: "Minimum number of numeric characters in the result. Default value is `0`.", Type: types.Int64Type, Optional: true, - // TODO: Implement Modify func. + Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), defaultInt{}, }, - //Default: 0, }, "min_upper": { Description: "Minimum number of uppercase alphabet characters in the result. Default value is `0`.", Type: types.Int64Type, Optional: true, - // TODO: Implement Modify func. + Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), defaultInt{}, }, - //Default: 0, }, "min_lower": { Description: "Minimum number of lowercase alphabet characters in the result. Default value is `0`.", Type: types.Int64Type, Optional: true, - // TODO: Implement Modify func. + Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), defaultInt{}, }, - //Default: 0, }, "min_special": { Description: "Minimum number of special characters in the result. Default value is `0`.", Type: types.Int64Type, Optional: true, - // TODO: Implement Modify func. + Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), defaultInt{}, }, - //Default: 0, }, "override_special": { Description: "Supply your own list of special characters to use for string generation. This " + "overrides the default character list in the special argument. The `special` argument must " + "still be set to true for any overwritten characters to be used in generation.", - Type: types.StringType, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Type: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + defaultOverrideSpecial{}, + }, }, "result": { @@ -175,7 +166,14 @@ func (l lengthValidator) MarkdownDescription(context.Context) string { } func (l lengthValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { + t := req.AttributeConfig.(types.Int64) + if t.Value < 1 { + resp.Diagnostics.AddError( + fmt.Sprintf("expected length to be at least 1, got %d", t.Value), + fmt.Sprintf("expected length to be at least 1, got %d", t.Value), + ) + } } type defaultBool struct{} @@ -189,7 +187,13 @@ func (d defaultBool) MarkdownDescription(ctx context.Context) string { } func (d defaultBool) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + t := req.AttributeConfig.(types.Bool) + if t.Null { + resp.AttributePlan = types.Bool{ + Value: true, + } + } } type defaultInt struct{} @@ -203,96 +207,152 @@ func (d defaultInt) MarkdownDescription(ctx context.Context) string { } func (d defaultInt) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + t := req.AttributeConfig.(types.Int64) + if t.Null { + resp.AttributePlan = types.Int64{ + Null: false, + Value: 0, + } + } } -func createStringFunc(sensitive bool) func(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - return func(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - const numChars = "0123456789" - const lowerChars = "abcdefghijklmnopqrstuvwxyz" - const upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - var ( - specialChars = "!@#$%&*()-_=+[]{}<>:?" - diags diag.Diagnostics - ) +type defaultOverrideSpecial struct{} - length := d.Get("length").(int) - upper := d.Get("upper").(bool) - minUpper := d.Get("min_upper").(int) - lower := d.Get("lower").(bool) - minLower := d.Get("min_lower").(int) - number := d.Get("number").(bool) - minNumeric := d.Get("min_numeric").(int) - special := d.Get("special").(bool) - minSpecial := d.Get("min_special").(int) - overrideSpecial := d.Get("override_special").(string) - - if length < minUpper+minLower+minNumeric+minSpecial { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("length (%d) must be >= min_upper + min_lower + min_numeric + min_special (%d)", length, minUpper+minLower+minNumeric+minSpecial), - }) - } +func (d defaultOverrideSpecial) Description(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set." +} - if overrideSpecial != "" { - specialChars = overrideSpecial - } +func (d defaultOverrideSpecial) MarkdownDescription(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set." +} - var chars = string("") - if upper { - chars += upperChars - } - if lower { - chars += lowerChars - } - if number { - chars += numChars - } - if special { - chars += specialChars - } +func (d defaultOverrideSpecial) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + t := req.AttributeConfig.(types.String) - minMapping := map[string]int{ - numChars: minNumeric, - lowerChars: minLower, - upperChars: minUpper, - specialChars: minSpecial, + if t.Null { + resp.AttributePlan = types.String{ + Null: false, + Value: "", } - var result = make([]byte, 0, length) - for k, v := range minMapping { - s, err := generateRandomBytes(&k, v) - if err != nil { - return append(diags, diag.Errorf("error generating random bytes: %s", err)...) - } - result = append(result, s...) - } - s, err := generateRandomBytes(&chars, length-len(result)) + } +} + +func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse, sensitive bool) { + const numChars = "0123456789" + const lowerChars = "abcdefghijklmnopqrstuvwxyz" + const upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + var specialChars = "!@#$%&*()-_=+[]{}<>:?" + var plan String + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + length := plan.Length.Value + upper := plan.Upper.Value + minUpper := plan.MinUpper.Value + lower := plan.Lower.Value + minLower := plan.MinLower.Value + number := plan.Number.Value + minNumeric := plan.MinNumeric.Value + special := plan.Special.Value + minSpecial := plan.MinSpecial.Value + overrideSpecial := plan.OverrideSpecial.Value + + if overrideSpecial != "" { + specialChars = overrideSpecial + } + + var chars = string("") + if upper { + chars += upperChars + } + if lower { + chars += lowerChars + } + if number { + chars += numChars + } + if special { + chars += specialChars + } + + minMapping := map[string]int64{ + numChars: minNumeric, + lowerChars: minLower, + upperChars: minUpper, + specialChars: minSpecial, + } + + var result = make([]byte, 0, length) + + for k, v := range minMapping { + s, err := generateRandomBytes(&k, v) if err != nil { - return append(diags, diag.Errorf("error generating random bytes: %s", err)...) + resp.Diagnostics.AddError( + "error generating random bytes", + fmt.Sprintf("error generating random bytes: %s", err), + ) + return } result = append(result, s...) - order := make([]byte, len(result)) - if _, err := rand.Read(order); err != nil { - return append(diags, diag.Errorf("error generating random bytes: %s", err)...) - } - sort.Slice(result, func(i, j int) bool { - return order[i] < order[j] - }) + } - if err := d.Set("result", string(result)); err != nil { - return append(diags, diag.Errorf("error setting result: %s", err)...) - } + s, err := generateRandomBytes(&chars, length-int64(len(result))) + if err != nil { + resp.Diagnostics.AddError( + "error generating random bytes", + fmt.Sprintf("error generating random bytes: %s", err), + ) + return + } - if sensitive { - d.SetId("none") - } else { - d.SetId(string(result)) - } - return nil + result = append(result, s...) + + order := make([]byte, len(result)) + if _, err := rand.Read(order); err != nil { + resp.Diagnostics.AddError( + "error generating random bytes", + fmt.Sprintf("error generating random bytes: %s", err), + ) + return + } + + sort.Slice(result, func(i, j int) bool { + return order[i] < order[j] + }) + + str := String{ + ID: types.String{Value: string(result)}, + Keepers: plan.Keepers, + Length: types.Int64{Value: length}, + Special: types.Bool{Value: special}, + Upper: types.Bool{Value: upper}, + Lower: types.Bool{Value: lower}, + Number: types.Bool{Value: number}, + MinNumeric: types.Int64{Value: minNumeric}, + MinUpper: types.Int64{Value: minUpper}, + MinLower: types.Int64{Value: minLower}, + MinSpecial: types.Int64{Value: minSpecial}, + OverrideSpecial: types.String{Value: overrideSpecial}, + Result: types.String{Value: string(result)}, + } + + if sensitive { + str.ID.Value = "none" + } + + diags = resp.State.Set(ctx, str) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } } -func generateRandomBytes(charSet *string, length int) ([]byte, error) { +func generateRandomBytes(charSet *string, length int64) ([]byte, error) { bytes := make([]byte, length) setLen := big.NewInt(int64(len(*charSet))) for i := range bytes { @@ -305,22 +365,41 @@ func generateRandomBytes(charSet *string, length int) ([]byte, error) { return bytes, nil } -func readNil(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - return nil -} +func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse, sensitive bool) { + id := req.ID -func importStringFunc(sensitive bool) schema.StateContextFunc { - return func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - val := d.Id() + state := String{ + ID: types.String{Value: id}, + Result: types.String{Value: id}, + } - if sensitive { - d.SetId("none") - } + state.Keepers.ElemType = types.StringType - if err := d.Set("result", val); err != nil { - return nil, fmt.Errorf("error setting result: %w", err) - } + if sensitive { + state.ID.Value = "none" + } - return []*schema.ResourceData{d}, nil + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func validateLength(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { + var config String + req.Config.Get(ctx, &config) + + length := config.Length.Value + minUpper := config.MinUpper.Value + minLower := config.MinLower.Value + minNumeric := config.MinNumeric.Value + minSpecial := config.MinSpecial.Value + + if length < minUpper+minLower+minNumeric+minSpecial { + resp.Diagnostics.AddError( + fmt.Sprintf("length (%d) must be >= min_upper + min_lower + min_numeric + min_special (%d)", length, minUpper+minLower+minNumeric+minSpecial), + fmt.Sprintf("length (%d) must be >= min_upper + min_lower + min_numeric + min_special (%d)", length, minUpper+minLower+minNumeric+minSpecial), + ) } } From 4c0a6cf1129f68af00aea1a1f135d8d78e0d4840 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 4 May 2022 17:11:43 +0100 Subject: [PATCH 11/96] Fixing description (#177) --- internal/provider_fm/resource_integer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider_fm/resource_integer.go b/internal/provider_fm/resource_integer.go index 0603135c..bbba51a8 100644 --- a/internal/provider_fm/resource_integer.go +++ b/internal/provider_fm/resource_integer.go @@ -54,7 +54,7 @@ func (r resourceIntegerType) GetSchema(context.Context) (tfsdk.Schema, diag.Diag Computed: true, }, "id": { - Description: "The generated uuid presented in string format.", + Description: "The string representation of the integer result.", Type: types.StringType, Computed: true, }, From ccc05d737c19414ae184b521e63876f5e1cdd572 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 5 May 2022 10:39:30 +0100 Subject: [PATCH 12/96] Adding UpgradeState to string resource (#177) --- internal/provider_fm/resource_password.go | 2 +- internal/provider_fm/resource_string.go | 14 ++- .../provider_fm/resource_string_migration.go | 43 +++++++++ .../resource_string_migration_test.go | 94 +++++++++++++++++++ internal/provider_fm/string.go | 2 +- 5 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 internal/provider_fm/resource_string_migration.go create mode 100644 internal/provider_fm/resource_string_migration_test.go diff --git a/internal/provider_fm/resource_password.go b/internal/provider_fm/resource_password.go index 9cfe10b1..d992f4b6 100644 --- a/internal/provider_fm/resource_password.go +++ b/internal/provider_fm/resource_password.go @@ -14,7 +14,7 @@ func (r resourcePasswordType) GetSchema(context.Context) (tfsdk.Schema, diag.Dia "data handling in the [Terraform documentation](https://www.terraform.io/docs/language/state/sensitive-data.html).\n" + "\n" + "This resource *does* use a cryptographic random number generator." - return getStringSchema(true, description), nil + return getStringSchemaV1(true, description), nil } func (r resourcePasswordType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { diff --git a/internal/provider_fm/resource_string.go b/internal/provider_fm/resource_string.go index 1092d4fe..1053ebda 100644 --- a/internal/provider_fm/resource_string.go +++ b/internal/provider_fm/resource_string.go @@ -17,7 +17,11 @@ func (r resourceStringType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagn "Historically this resource's intended usage has been ambiguous as the original example used " + "it in a password. For backwards compatibility it will continue to exist. For unique ids please " + "use [random_id](id.html), for sensitive random values please use [random_password](password.html)." - return getStringSchema(false, description), nil + + schema := getStringSchemaV1(false, description) + schema.Version = 1 + + return schema, nil } func (r resourceStringType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { @@ -53,3 +57,11 @@ func (r resourceString) ImportState(ctx context.Context, req tfsdk.ImportResourc func (r resourceString) ValidateConfig(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { validateLength(ctx, req, resp) } + +func (r resourceString) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { + return map[int64]tfsdk.ResourceStateUpgrader{ + 0: tfsdk.ResourceStateUpgrader{ + StateUpgrader: migrateStringStateV0toV1, + }, + } +} diff --git a/internal/provider_fm/resource_string_migration.go b/internal/provider_fm/resource_string_migration.go new file mode 100644 index 00000000..df105e1e --- /dev/null +++ b/internal/provider_fm/resource_string_migration.go @@ -0,0 +1,43 @@ +package provider_fm + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +//func resourceRandomStringMigrateState(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse { +// switch v { +// case 0: +// log.Println("[INFO] Found random string state v0; migrating to v1") +// return migrateStringStateV0toV1(is) +// default: +// return is, fmt.Errorf("Unexpected schema version: %d", v) +// } +//} + +//func resourceRandomStringMigrateState( +// v int, is *terraform.InstanceState, _ interface{}) (*terraform.InstanceState, error) { +// switch v { +// case 0: +// log.Println("[INFO] Found random string state v0; migrating to v1") +// return migrateStringStateV0toV1(is) +// default: +// return is, fmt.Errorf("Unexpected schema version: %d", v) +// } +//} + +//func redactAttributes(s String) String { +// s.ID.Value = "" +// s.Result.Value = "" +// +// return s +//} + +func migrateStringStateV0toV1(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { + s := String{} + req.State.Get(ctx, &s) + resp.State.Set(ctx, s) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/internal/provider_fm/resource_string_migration_test.go b/internal/provider_fm/resource_string_migration_test.go new file mode 100644 index 00000000..3d64977e --- /dev/null +++ b/internal/provider_fm/resource_string_migration_test.go @@ -0,0 +1,94 @@ +package provider_fm + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "testing" +) + +func TestResourceStringMigrateState(t *testing.T) { + //cases := map[string]struct { + // StateVersion int + // ID string + // InputAttributes map[string]string + // ExpectedAttributes map[string]string + // Meta interface{} + //}{ + // "v0_1_simple": { + // StateVersion: 0, + // ID: "some_id", + // InputAttributes: map[string]string{ + // "result": "foo", + // "id": "foo", + // "length": "3", + // }, + // ExpectedAttributes: map[string]string{ + // "result": "foo", + // "id": "foo", + // "length": "3", + // "min_numeric": "0", + // "min_special": "0", + // "min_lower": "0", + // "min_upper": "0", + // }, + // }, + // "v0_1_special": { + // StateVersion: 0, + // ID: "some_id", + // InputAttributes: map[string]string{ + // "result": "foo", + // "id": "foo", + // "special": "false", + // "length": "3", + // "override_special": "!@", + // }, + // ExpectedAttributes: map[string]string{ + // "result": "foo", + // "id": "foo", + // "special": "false", + // "length": "3", + // "override_special": "!@", + // "min_numeric": "0", + // "min_special": "0", + // "min_lower": "0", + // "min_upper": "0", + // }, + // }, + //} + + tval := tftypes.Value{} + + tval.Type() + + //for tn, tc := range cases { + req := tfsdk.UpgradeResourceStateRequest{ + State: &tfsdk.State{ + Raw: tftypes.Value{}, + Schema: tfsdk.Schema{ + Version: 0, + }, + }, + } + + resp := tfsdk.UpgradeResourceStateResponse{} + + migrateStringStateV0toV1(context.Background(), req, &resp) + + respState := String{} + resp.State.Get(context.Background(), &respState) + fmt.Printf("%+v", respState.MinNumeric) + + //if err != nil { + // t.Fatalf("bad: %s, err: %#v", tn, err) + //} + // + //for k, v := range tc.ExpectedAttributes { + // actual := is.Attributes[k] + // if actual != v { + // t.Fatalf("Bad Random String Migration for %q: %q\n\n expected: %q", k, actual, v) + // } + //} + //} +} diff --git a/internal/provider_fm/string.go b/internal/provider_fm/string.go index cb71f628..9f0b4f7a 100644 --- a/internal/provider_fm/string.go +++ b/internal/provider_fm/string.go @@ -10,7 +10,7 @@ import ( "sort" ) -func getStringSchema(sensitive bool, description string) tfsdk.Schema { +func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { idDesc := "The generated random string." if sensitive { idDesc = "A static value used internally by Terraform, this should not be referenced in configurations." From 1085fc6df85c1583a6c7e9e9b1612c79f77ce044 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 5 May 2022 11:06:38 +0100 Subject: [PATCH 13/96] go fmt (#177) --- internal/provider_fm/resource_string.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider_fm/resource_string.go b/internal/provider_fm/resource_string.go index 1053ebda..38e4250d 100644 --- a/internal/provider_fm/resource_string.go +++ b/internal/provider_fm/resource_string.go @@ -60,7 +60,7 @@ func (r resourceString) ValidateConfig(ctx context.Context, req tfsdk.ValidateRe func (r resourceString) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { return map[int64]tfsdk.ResourceStateUpgrader{ - 0: tfsdk.ResourceStateUpgrader{ + 0: { StateUpgrader: migrateStringStateV0toV1, }, } From 9bb7055852a05d0078f1d35100f1284cde53f348 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 5 May 2022 11:30:15 +0100 Subject: [PATCH 14/96] Making defaults configurable (#177) --- internal/provider_fm/string.go | 41 +++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/internal/provider_fm/string.go b/internal/provider_fm/string.go index 9f0b4f7a..d15f4d14 100644 --- a/internal/provider_fm/string.go +++ b/internal/provider_fm/string.go @@ -45,7 +45,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultBool{}, + defaultBool{true}, }, }, @@ -56,7 +56,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultBool{}, + defaultBool{true}, }, }, @@ -67,7 +67,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultBool{}, + defaultBool{true}, }, }, @@ -78,7 +78,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultBool{}, + defaultBool{true}, }, }, @@ -89,7 +89,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt{}, + defaultInt{0}, }, }, @@ -100,7 +100,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt{}, + defaultInt{0}, }, }, @@ -111,7 +111,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt{}, + defaultInt{0}, }, }, @@ -122,7 +122,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt{}, + defaultInt{0}, }, }, @@ -135,7 +135,6 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultOverrideSpecial{}, }, }, @@ -176,7 +175,9 @@ func (l lengthValidator) Validate(ctx context.Context, req tfsdk.ValidateAttribu } } -type defaultBool struct{} +type defaultBool struct { + val bool +} func (d defaultBool) Description(ctx context.Context) string { return "If the plan does not contain a value, a default will be set." @@ -191,12 +192,14 @@ func (d defaultBool) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRe if t.Null { resp.AttributePlan = types.Bool{ - Value: true, + Value: d.val, } } } -type defaultInt struct{} +type defaultInt struct { + val int64 +} func (d defaultInt) Description(ctx context.Context) string { return "If the plan does not contain a value, a default will be set." @@ -212,28 +215,30 @@ func (d defaultInt) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanReq if t.Null { resp.AttributePlan = types.Int64{ Null: false, - Value: 0, + Value: d.val, } } } -type defaultOverrideSpecial struct{} +type defaultString struct { + val string +} -func (d defaultOverrideSpecial) Description(ctx context.Context) string { +func (d defaultString) Description(ctx context.Context) string { return "If the plan does not contain a value, a default will be set." } -func (d defaultOverrideSpecial) MarkdownDescription(ctx context.Context) string { +func (d defaultString) MarkdownDescription(ctx context.Context) string { return "If the plan does not contain a value, a default will be set." } -func (d defaultOverrideSpecial) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { +func (d defaultString) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { t := req.AttributeConfig.(types.String) if t.Null { resp.AttributePlan = types.String{ Null: false, - Value: "", + Value: d.val, } } } From d34a5f73f084c984861cabea047e5808b2ca2c60 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 5 May 2022 14:05:27 +0100 Subject: [PATCH 15/96] Renaming models (#177) --- internal/provider_fm/models.go | 16 ++++++++++++---- internal/provider_fm/resource_id.go | 6 +++--- internal/provider_fm/resource_integer.go | 6 +++--- .../provider_fm/resource_string_migration.go | 4 ++-- .../resource_string_migration_test.go | 6 ++---- internal/provider_fm/resource_uuid.go | 6 +++--- internal/provider_fm/string.go | 8 ++++---- 7 files changed, 29 insertions(+), 23 deletions(-) diff --git a/internal/provider_fm/models.go b/internal/provider_fm/models.go index c8b0e2ba..f01724bd 100644 --- a/internal/provider_fm/models.go +++ b/internal/provider_fm/models.go @@ -2,7 +2,7 @@ package provider_fm import "github.com/hashicorp/terraform-plugin-framework/types" -type ID struct { +type IDModel struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` ByteLength types.Int64 `tfsdk:"byte_length"` @@ -13,7 +13,7 @@ type ID struct { Dec types.String `tfsdk:"dec"` } -type Integer struct { +type IntegerModel struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` Min types.Int64 `tfsdk:"min"` @@ -22,7 +22,15 @@ type Integer struct { Result types.Int64 `tfsdk:"result"` } -type String struct { +type PetNameModel struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Prefix types.String `tfsdk:"prefix"` + Separator types.String `tfsdk:"separator"` +} + +type StringModel struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` Length types.Int64 `tfsdk:"length"` @@ -38,7 +46,7 @@ type String struct { Result types.String `tfsdk:"result"` } -type UUID struct { +type UUIDModel struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` Result types.String `tfsdk:"result"` diff --git a/internal/provider_fm/resource_id.go b/internal/provider_fm/resource_id.go index 6677500d..07bd657e 100644 --- a/internal/provider_fm/resource_id.go +++ b/internal/provider_fm/resource_id.go @@ -104,7 +104,7 @@ func (r resourceID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, ) } - var plan ID + var plan IDModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -140,7 +140,7 @@ func (r resourceID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, bigInt.SetBytes(bytes) dec := bigInt.String() - i := ID{ + i := IDModel{ ID: types.String{Value: id}, Keepers: plan.Keepers, ByteLength: types.Int64{Value: plan.ByteLength.Value}, @@ -195,7 +195,7 @@ func (r resourceID) ImportState(ctx context.Context, req tfsdk.ImportResourceSta bigInt.SetBytes(bytes) dec := bigInt.String() - var state ID + var state IDModel state.ID.Value = id state.ByteLength.Value = int64(len(bytes)) diff --git a/internal/provider_fm/resource_integer.go b/internal/provider_fm/resource_integer.go index bbba51a8..f99874c2 100644 --- a/internal/provider_fm/resource_integer.go +++ b/internal/provider_fm/resource_integer.go @@ -80,7 +80,7 @@ func (r resourceInteger) Create(ctx context.Context, req tfsdk.CreateResourceReq ) } - var plan Integer + var plan IntegerModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -102,7 +102,7 @@ func (r resourceInteger) Create(ctx context.Context, req tfsdk.CreateResourceReq rand := NewRand(seed) number := rand.Intn((max+1)-min) + min - u := &Integer{ + u := &IntegerModel{ ID: types.String{Value: strconv.Itoa(number)}, Keepers: plan.Keepers, Min: types.Int64{Value: int64(min)}, @@ -172,7 +172,7 @@ func (r resourceInteger) ImportState(ctx context.Context, req tfsdk.ImportResour return } - var state Integer + var state IntegerModel state.ID.Value = parts[0] state.Keepers.ElemType = types.StringType diff --git a/internal/provider_fm/resource_string_migration.go b/internal/provider_fm/resource_string_migration.go index df105e1e..a9ef50f4 100644 --- a/internal/provider_fm/resource_string_migration.go +++ b/internal/provider_fm/resource_string_migration.go @@ -26,7 +26,7 @@ import ( // } //} -//func redactAttributes(s String) String { +//func redactAttributes(s StringModel) StringModel { // s.ID.Value = "" // s.Result.Value = "" // @@ -34,7 +34,7 @@ import ( //} func migrateStringStateV0toV1(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { - s := String{} + s := StringModel{} req.State.Get(ctx, &s) resp.State.Set(ctx, s) if resp.Diagnostics.HasError() { diff --git a/internal/provider_fm/resource_string_migration_test.go b/internal/provider_fm/resource_string_migration_test.go index 3d64977e..632c7eaa 100644 --- a/internal/provider_fm/resource_string_migration_test.go +++ b/internal/provider_fm/resource_string_migration_test.go @@ -2,7 +2,6 @@ package provider_fm import ( "context" - "fmt" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" "testing" @@ -76,9 +75,8 @@ func TestResourceStringMigrateState(t *testing.T) { migrateStringStateV0toV1(context.Background(), req, &resp) - respState := String{} + respState := StringModel{} resp.State.Get(context.Background(), &respState) - fmt.Printf("%+v", respState.MinNumeric) //if err != nil { // t.Fatalf("bad: %s, err: %#v", tn, err) @@ -87,7 +85,7 @@ func TestResourceStringMigrateState(t *testing.T) { //for k, v := range tc.ExpectedAttributes { // actual := is.Attributes[k] // if actual != v { - // t.Fatalf("Bad Random String Migration for %q: %q\n\n expected: %q", k, actual, v) + // t.Fatalf("Bad Random StringModel Migration for %q: %q\n\n expected: %q", k, actual, v) // } //} //} diff --git a/internal/provider_fm/resource_uuid.go b/internal/provider_fm/resource_uuid.go index 3bd7f7c7..26973d20 100644 --- a/internal/provider_fm/resource_uuid.go +++ b/internal/provider_fm/resource_uuid.go @@ -68,14 +68,14 @@ func (r resourceUUID) Create(ctx context.Context, req tfsdk.CreateResourceReques return } - var plan UUID + var plan UUIDModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - u := &UUID{ + u := &UUIDModel{ ID: types.String{Value: result}, Result: types.String{Value: result}, Keepers: plan.Keepers, @@ -117,7 +117,7 @@ func (r resourceUUID) ImportState(ctx context.Context, req tfsdk.ImportResourceS return } - var state UUID + var state UUIDModel state.ID.Value = result state.Result.Value = result diff --git a/internal/provider_fm/string.go b/internal/provider_fm/string.go index d15f4d14..8948ea51 100644 --- a/internal/provider_fm/string.go +++ b/internal/provider_fm/string.go @@ -248,7 +248,7 @@ func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tf const lowerChars = "abcdefghijklmnopqrstuvwxyz" const upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" var specialChars = "!@#$%&*()-_=+[]{}<>:?" - var plan String + var plan StringModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -330,7 +330,7 @@ func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tf return order[i] < order[j] }) - str := String{ + str := StringModel{ ID: types.String{Value: string(result)}, Keepers: plan.Keepers, Length: types.Int64{Value: length}, @@ -373,7 +373,7 @@ func generateRandomBytes(charSet *string, length int64) ([]byte, error) { func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse, sensitive bool) { id := req.ID - state := String{ + state := StringModel{ ID: types.String{Value: id}, Result: types.String{Value: id}, } @@ -392,7 +392,7 @@ func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, res } func validateLength(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { - var config String + var config StringModel req.Config.Get(ctx, &config) length := config.Length.Value From 16c842f000c80b0b5bad69cfd94a6e8b316ba301 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 5 May 2022 14:06:35 +0100 Subject: [PATCH 16/96] Migrating petname resource from SDKv2 to Framework (#177) --- internal/provider/provider.go | 1 - internal/provider_fm/provider.go | 1 + internal/provider_fm/resource_pet.go | 130 ++++++++++++++++++ .../resource_pet_test.go | 18 +-- 4 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 internal/provider_fm/resource_pet.go rename internal/{provider => provider_fm}/resource_pet_test.go (81%) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 770b3ed5..578cdac3 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -17,7 +17,6 @@ func New() *schema.Provider { ResourcesMap: map[string]*schema.Resource{ "random_shuffle": resourceShuffle(), - "random_pet": resourcePet(), }, } } diff --git a/internal/provider_fm/provider.go b/internal/provider_fm/provider.go index 34858ccd..1f8ec92b 100644 --- a/internal/provider_fm/provider.go +++ b/internal/provider_fm/provider.go @@ -27,6 +27,7 @@ func (p *provider) GetResources(ctx context.Context) (map[string]tfsdk.ResourceT "random_id": resourceIDType{}, "random_integer": resourceIntegerType{}, "random_password": resourcePasswordType{}, + "random_pet": resourcePetType{}, "random_string": resourceStringType{}, "random_uuid": resourceUUIDType{}, }, nil diff --git a/internal/provider_fm/resource_pet.go b/internal/provider_fm/resource_pet.go new file mode 100644 index 00000000..d8224c40 --- /dev/null +++ b/internal/provider_fm/resource_pet.go @@ -0,0 +1,130 @@ +package provider_fm + +import ( + "context" + "fmt" + petname "github.com/dustinkirkland/golang-petname" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "strings" +) + +type resourcePetType struct{} + +func (r resourcePetType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { + // This is necessary to ensure each call to petname is properly randomised: + // the library uses `rand.Intn()` and does NOT seed `rand.Seed()` by default, + // so this call takes care of that. + petname.NonDeterministicMode() + + return tfsdk.Schema{ + Description: "The resource `random_pet` generates random pet names that are intended to be used as " + + "unique identifiers for other resources.\n" + + "\n" + + "This resource can be used in conjunction with resources that have the `create_before_destroy` " + + "lifecycle flag set, to avoid conflicts with unique names during the brief period where both the old " + + "and new resources exist concurrently.", + Attributes: map[string]tfsdk.Attribute{ + "keepers": { + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + Type: types.MapType{ + ElemType: types.StringType, + }, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + "length": { + Description: "The length (in words) of the pet name. Defaults to 2", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + defaultInt{2}, + }, + }, + "prefix": { + Description: "A string to prefix the name with.", + Type: types.StringType, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + "separator": { + Description: "The character to separate words in the pet name. Defaults to \"-\"", + Type: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + defaultString{"-"}, + }, + }, + "id": { + Description: "The generated uuid presented in string format.", + Type: types.StringType, + Computed: true, + }, + }, + }, nil +} + +func (r resourcePetType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return resourcePet{ + p: *(p.(*provider)), + }, nil +} + +type resourcePet struct { + p provider +} + +func (r resourcePet) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan PetNameModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + length := plan.Length.Value + separator := plan.Separator.Value + prefix := plan.Prefix.Value + + pet := strings.ToLower(petname.Generate(int(length), separator)) + + pn := PetNameModel{ + Keepers: plan.Keepers, + Length: types.Int64{Value: length}, + Separator: types.String{Value: separator}, + } + + if prefix != "" { + pet = fmt.Sprintf("%s%s%s", prefix, separator, pet) + pn.Prefix.Value = prefix + } else { + pn.Prefix.Null = true + } + + pn.ID.Value = pet + + diags = resp.State.Set(ctx, pn) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r resourcePet) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { + // Intentionally left blank. +} + +func (r resourcePet) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + // Intentionally left blank. +} + +func (r resourcePet) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.State.RemoveResource(ctx) +} diff --git a/internal/provider/resource_pet_test.go b/internal/provider_fm/resource_pet_test.go similarity index 81% rename from internal/provider/resource_pet_test.go rename to internal/provider_fm/resource_pet_test.go index 0f838ff5..5d2c9217 100644 --- a/internal/provider/resource_pet_test.go +++ b/internal/provider_fm/resource_pet_test.go @@ -1,4 +1,4 @@ -package provider +package provider_fm import ( "fmt" @@ -12,8 +12,8 @@ import ( func TestAccResourcePet_basic(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourcePet_basic, @@ -27,8 +27,8 @@ func TestAccResourcePet_basic(t *testing.T) { func TestAccResourcePet_length(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourcePet_length, @@ -42,8 +42,8 @@ func TestAccResourcePet_length(t *testing.T) { func TestAccResourcePet_prefix(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourcePet_prefix, @@ -59,8 +59,8 @@ func TestAccResourcePet_prefix(t *testing.T) { func TestAccResourcePet_separator(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourcePet_separator, From c9eb988125d500f7fc1d9ccf75df0a88e157e964 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 5 May 2022 15:11:30 +0100 Subject: [PATCH 17/96] Removing dead code (#177) --- internal/provider/resource_pet.go | 85 ------------------------------- 1 file changed, 85 deletions(-) delete mode 100644 internal/provider/resource_pet.go diff --git a/internal/provider/resource_pet.go b/internal/provider/resource_pet.go deleted file mode 100644 index 0d29f33d..00000000 --- a/internal/provider/resource_pet.go +++ /dev/null @@ -1,85 +0,0 @@ -package provider - -import ( - "context" - "fmt" - "strings" - - petname "github.com/dustinkirkland/golang-petname" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func resourcePet() *schema.Resource { - // This is necessary to ensure each call to petname is properly randomised: - // the library uses `rand.Intn()` and does NOT seed `rand.Seed()` by default, - // so this call takes care of that. - petname.NonDeterministicMode() - - return &schema.Resource{ - Description: "The resource `random_pet` generates random pet names that are intended to be used as " + - "unique identifiers for other resources.\n" + - "\n" + - "This resource can be used in conjunction with resources that have the `create_before_destroy` " + - "lifecycle flag set, to avoid conflicts with unique names during the brief period where both the old " + - "and new resources exist concurrently.", - CreateContext: CreatePet, - ReadContext: schema.NoopContext, - DeleteContext: RemoveResourceFromState, - - Schema: map[string]*schema.Schema{ - "keepers": { - Description: "Arbitrary map of values that, when changed, will trigger recreation of " + - "resource. See [the main provider documentation](../index.html) for more information.", - Type: schema.TypeMap, - Optional: true, - ForceNew: true, - }, - - "length": { - Description: "The length (in words) of the pet name. Defaults to 2", - Type: schema.TypeInt, - Optional: true, - Default: 2, - ForceNew: true, - }, - - "prefix": { - Description: "A string to prefix the name with.", - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - - "separator": { - Description: "The character to separate words in the pet name. Defaults to \"-\"", - Type: schema.TypeString, - Optional: true, - Default: "-", - ForceNew: true, - }, - - "id": { - Description: "The random pet name", - Type: schema.TypeString, - Computed: true, - }, - }, - } -} - -func CreatePet(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - length := d.Get("length").(int) - separator := d.Get("separator").(string) - prefix := d.Get("prefix").(string) - - pet := strings.ToLower(petname.Generate(length, separator)) - - if prefix != "" { - pet = fmt.Sprintf("%s%s%s", prefix, separator, pet) - } - - d.SetId(pet) - - return nil -} From cce21aef09a49e2c4460ba11244f5911ffd49965 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 5 May 2022 16:24:46 +0100 Subject: [PATCH 18/96] Migrating shuffle resource from SDKv2 to Framework (#177) --- internal/provider/provider.go | 4 +- internal/provider_fm/models.go | 9 + internal/provider_fm/provider.go | 1 + internal/provider_fm/resource_shuffle.go | 158 ++++++++++++++++++ .../resource_shuffle_test.go | 22 +-- 5 files changed, 180 insertions(+), 14 deletions(-) create mode 100644 internal/provider_fm/resource_shuffle.go rename internal/{provider => provider_fm}/resource_shuffle_test.go (86%) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 578cdac3..8cda3537 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -15,9 +15,7 @@ func New() *schema.Provider { return &schema.Provider{ Schema: map[string]*schema.Schema{}, - ResourcesMap: map[string]*schema.Resource{ - "random_shuffle": resourceShuffle(), - }, + ResourcesMap: map[string]*schema.Resource{}, } } diff --git a/internal/provider_fm/models.go b/internal/provider_fm/models.go index f01724bd..4cf8237c 100644 --- a/internal/provider_fm/models.go +++ b/internal/provider_fm/models.go @@ -30,6 +30,15 @@ type PetNameModel struct { Separator types.String `tfsdk:"separator"` } +type ShuffleModel struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Seed types.String `tfsdk:"seed"` + Input types.List `tfsdk:"input"` + ResultCount types.Int64 `tfsdk:"result_count"` + Result types.List `tfsdk:"result"` +} + type StringModel struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` diff --git a/internal/provider_fm/provider.go b/internal/provider_fm/provider.go index 1f8ec92b..61324190 100644 --- a/internal/provider_fm/provider.go +++ b/internal/provider_fm/provider.go @@ -28,6 +28,7 @@ func (p *provider) GetResources(ctx context.Context) (map[string]tfsdk.ResourceT "random_integer": resourceIntegerType{}, "random_password": resourcePasswordType{}, "random_pet": resourcePetType{}, + "random_shuffle": resourceShuffleType{}, "random_string": resourceStringType{}, "random_uuid": resourceUUIDType{}, }, nil diff --git a/internal/provider_fm/resource_shuffle.go b/internal/provider_fm/resource_shuffle.go new file mode 100644 index 00000000..2c66e745 --- /dev/null +++ b/internal/provider_fm/resource_shuffle.go @@ -0,0 +1,158 @@ +package provider_fm + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type resourceShuffleType struct{} + +func (r resourceShuffleType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Description: "The resource `random_shuffle` generates a random permutation of a list of strings " + + "given as an argument.", + Attributes: map[string]tfsdk.Attribute{ + "keepers": { + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + Type: types.MapType{ + ElemType: types.StringType, + }, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + "seed": { + Description: "Arbitrary string with which to seed the random number generator, in order to " + + "produce less-volatile permutations of the list.\n" + + "\n" + + "**Important:** Even with an identical seed, it is not guaranteed that the same permutation " + + "will be produced across different versions of Terraform. This argument causes the " + + "result to be *less volatile*, but not fixed for all time.", + Type: types.StringType, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + "input": { + Description: "The list of strings to shuffle.", + Type: types.ListType{ + ElemType: types.StringType, + }, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + "result_count": { + Description: "The number of results to return. Defaults to the number of items in the " + + "`input` list. If fewer items are requested, some elements will be excluded from the " + + "result. If more items are requested, items will be repeated in the result but not more " + + "frequently than the number of items in the input list.", + Type: types.Int64Type, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + "result": { + Description: "Random permutation of the list of strings given in `input`.", + Type: types.ListType{ + ElemType: types.StringType, + }, + Computed: true, + }, + "id": { + Description: "The generated uuid presented in string format.", + Type: types.StringType, + Computed: true, + }, + }, + }, nil +} + +func (r resourceShuffleType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return resourceShuffle{ + p: *(p.(*provider)), + }, nil +} + +type resourceShuffle struct { + p provider +} + +func (r resourceShuffle) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan ShuffleModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + input := plan.Input + seed := plan.Seed.Value + resultCount := plan.ResultCount.Value + + if resultCount == 0 { + resultCount = int64(len(input.Elems)) + } + + result := make([]attr.Value, 0, resultCount) + + if len(input.Elems) > 0 { + rand := NewRand(seed) + + // Keep producing permutations until we fill our result + Batches: + for { + perm := rand.Perm(len(input.Elems)) + + for _, i := range perm { + result = append(result, input.Elems[i]) + + if int64(len(result)) >= resultCount { + break Batches + } + } + } + } + + s := ShuffleModel{ + ID: types.String{Value: "-"}, + Keepers: plan.Keepers, + Input: plan.Input, + Result: types.List{ + Unknown: false, + Null: false, + Elems: result, + ElemType: types.StringType, + }, + } + + if plan.Seed.Null { + s.Seed.Null = true + } else { + s.Seed.Value = seed + } + + if plan.ResultCount.Null { + s.ResultCount.Null = true + } else { + s.ResultCount.Value = resultCount + } + + diags = resp.State.Set(ctx, s) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r resourceShuffle) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { + // Intentionally left blank. +} + +func (r resourceShuffle) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + // Intentionally left blank. +} + +func (r resourceShuffle) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.State.RemoveResource(ctx) +} diff --git a/internal/provider/resource_shuffle_test.go b/internal/provider_fm/resource_shuffle_test.go similarity index 86% rename from internal/provider/resource_shuffle_test.go rename to internal/provider_fm/resource_shuffle_test.go index f796a006..de20df46 100644 --- a/internal/provider/resource_shuffle_test.go +++ b/internal/provider_fm/resource_shuffle_test.go @@ -1,4 +1,4 @@ -package provider +package provider_fm import ( "fmt" @@ -20,8 +20,8 @@ import ( // guaranteed consistent across Terraform releases. func TestAccResourceShuffleDefault(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigDefault, @@ -38,8 +38,8 @@ func TestAccResourceShuffleDefault(t *testing.T) { func TestAccResourceShuffleShorter(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigShorter, @@ -56,8 +56,8 @@ func TestAccResourceShuffleShorter(t *testing.T) { func TestAccResourceShuffleLonger(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigLonger, @@ -74,8 +74,8 @@ func TestAccResourceShuffleLonger(t *testing.T) { func TestAccResourceShuffleEmpty(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigEmpty, @@ -92,8 +92,8 @@ func TestAccResourceShuffleEmpty(t *testing.T) { func TestAccResourceShuffleOne(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigOne, From 617a893ee6485509c0969876a1d23d0af5362743 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 5 May 2022 16:46:56 +0100 Subject: [PATCH 19/96] Removing SDKv2 provider (#177) --- internal/provider/provider.go | 25 ------ internal/provider/provider_test.go | 30 ------- internal/provider/resource_shuffle.go | 113 -------------------------- internal/provider/seed.go | 24 ------ main.go | 2 - 5 files changed, 194 deletions(-) delete mode 100644 internal/provider/provider.go delete mode 100644 internal/provider/provider_test.go delete mode 100644 internal/provider/resource_shuffle.go delete mode 100644 internal/provider/seed.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go deleted file mode 100644 index 8cda3537..00000000 --- a/internal/provider/provider.go +++ /dev/null @@ -1,25 +0,0 @@ -package provider - -import ( - "context" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func init() { - schema.DescriptionKind = schema.StringMarkdown -} - -// New returns a *schema.Provider. -func New() *schema.Provider { - return &schema.Provider{ - Schema: map[string]*schema.Schema{}, - - ResourcesMap: map[string]*schema.Resource{}, - } -} - -func RemoveResourceFromState(_ context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics { - d.SetId("") - return nil -} diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go deleted file mode 100644 index bde42d24..00000000 --- a/internal/provider/provider_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package provider - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -var testAccProvider *schema.Provider -var testAccProviders map[string]func() (*schema.Provider, error) - -func init() { - testAccProvider = New() - testAccProviders = map[string]func() (*schema.Provider, error){ - "random": func() (*schema.Provider, error) { return testAccProvider, nil }, - } -} - -func TestProvider(t *testing.T) { - if err := New().InternalValidate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestProvider_impl(t *testing.T) { - var _ *schema.Provider = New() -} - -func testAccPreCheck(t *testing.T) { -} diff --git a/internal/provider/resource_shuffle.go b/internal/provider/resource_shuffle.go deleted file mode 100644 index ccf04932..00000000 --- a/internal/provider/resource_shuffle.go +++ /dev/null @@ -1,113 +0,0 @@ -package provider - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func resourceShuffle() *schema.Resource { - return &schema.Resource{ - Description: "The resource `random_shuffle` generates a random permutation of a list of strings " + - "given as an argument.", - CreateContext: CreateShuffle, - ReadContext: schema.NoopContext, - DeleteContext: RemoveResourceFromState, - - Schema: map[string]*schema.Schema{ - "keepers": { - Description: "Arbitrary map of values that, when changed, will trigger recreation of " + - "resource. See [the main provider documentation](../index.html) for more information.", - Type: schema.TypeMap, - Optional: true, - ForceNew: true, - }, - - "seed": { - Description: "Arbitrary string with which to seed the random number generator, in order to " + - "produce less-volatile permutations of the list.\n" + - "\n" + - "**Important:** Even with an identical seed, it is not guaranteed that the same permutation " + - "will be produced across different versions of Terraform. This argument causes the " + - "result to be *less volatile*, but not fixed for all time.", - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - - "input": { - Description: "The list of strings to shuffle.", - Type: schema.TypeList, - Required: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - - "result_count": { - Description: "The number of results to return. Defaults to the number of items in the " + - "`input` list. If fewer items are requested, some elements will be excluded from the " + - "result. If more items are requested, items will be repeated in the result but not more " + - "frequently than the number of items in the input list.", - Type: schema.TypeInt, - Optional: true, - ForceNew: true, - }, - - "result": { - Description: "Random permutation of the list of strings given in `input`.", - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - - "id": { - Description: "A static value used internally by Terraform, this should not be referenced in configurations.", - Type: schema.TypeString, - Computed: true, - }, - }, - } -} - -func CreateShuffle(_ context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics { - input := d.Get("input").([]interface{}) - seed := d.Get("seed").(string) - - resultCount := d.Get("result_count").(int) - if resultCount == 0 { - resultCount = len(input) - } - result := make([]interface{}, 0, resultCount) - - if len(input) > 0 { - rand := NewRand(seed) - - // Keep producing permutations until we fill our result - Batches: - for { - perm := rand.Perm(len(input)) - - for _, i := range perm { - result = append(result, input[i]) - - if len(result) >= resultCount { - break Batches - } - } - } - - } - - d.SetId("-") - - if err := d.Set("result", result); err != nil { - return diag.Errorf("error setting result: %s", err) - } - - return nil -} diff --git a/internal/provider/seed.go b/internal/provider/seed.go deleted file mode 100644 index 4d3c5265..00000000 --- a/internal/provider/seed.go +++ /dev/null @@ -1,24 +0,0 @@ -package provider - -import ( - "hash/crc64" - "math/rand" - "time" -) - -// NewRand returns a seeded random number generator, using a seed derived -// from the provided string. -// -// If the seed string is empty, the current time is used as a seed. -func NewRand(seed string) *rand.Rand { - var seedInt int64 - if seed != "" { - crcTable := crc64.MakeTable(crc64.ISO) - seedInt = int64(crc64.Checksum([]byte(seed), crcTable)) - } else { - seedInt = time.Now().UnixNano() - } - - randSource := rand.NewSource(seedInt) - return rand.New(randSource) -} diff --git a/main.go b/main.go index e1c2fa15..73a99c70 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" "github.com/hashicorp/terraform-plugin-mux/tf6to5server" - "github.com/terraform-providers/terraform-provider-random/internal/provider" "github.com/terraform-providers/terraform-provider-random/internal/provider_fm" "log" ) @@ -37,7 +36,6 @@ func main() { func() tfprotov5.ProviderServer { return downgradedFrameworkProvider }, - provider.New().GRPCProvider, } muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) From 4e7ae417ebc5b87035966ffca0694ded0c1b8c31 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 6 May 2022 09:32:26 +0100 Subject: [PATCH 20/96] Using Protov5ProviderFactories in tests for compatibility with TF >= 0.12 (#177) --- internal/provider_fm/provider_test.go | 21 +++++++++--- internal/provider_fm/resource_id_test.go | 4 +-- internal/provider_fm/resource_integer_test.go | 34 +++---------------- internal/provider_fm/resource_pasword_test.go | 6 ++-- internal/provider_fm/resource_pet_test.go | 8 ++--- internal/provider_fm/resource_shuffle_test.go | 10 +++--- internal/provider_fm/resource_string_test.go | 8 ++--- internal/provider_fm/resource_uuid_test.go | 2 +- 8 files changed, 40 insertions(+), 53 deletions(-) diff --git a/internal/provider_fm/provider_test.go b/internal/provider_fm/provider_test.go index a7357b7b..961d1c1d 100644 --- a/internal/provider_fm/provider_test.go +++ b/internal/provider_fm/provider_test.go @@ -1,8 +1,12 @@ package provider_fm import ( + "context" "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-mux/tf6to5server" + "log" "testing" ) @@ -10,8 +14,17 @@ func testAccPreCheck(t *testing.T) { } //nolint:unparam -var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ - "random": func() (tfprotov6.ProviderServer, error) { - return providerserver.NewProtocol6(NewFramework())(), nil - }, +func testAccProtoV5ProviderFactories() map[string]func() (tfprotov5.ProviderServer, error) { + downgradedFrameworkProvider, err := tf6to5server.DowngradeServer(context.Background(), func() tfprotov6.ProviderServer { + return providerserver.NewProtocol6(NewFramework())() + }) + if err != nil { + log.Fatal(err) + } + + return map[string]func() (tfprotov5.ProviderServer, error){ + "random": func() (tfprotov5.ProviderServer, error) { + return downgradedFrameworkProvider, nil + }, + } } diff --git a/internal/provider_fm/resource_id_test.go b/internal/provider_fm/resource_id_test.go index 7dda7ec0..ac16dc62 100644 --- a/internal/provider_fm/resource_id_test.go +++ b/internal/provider_fm/resource_id_test.go @@ -17,7 +17,7 @@ type idLens struct { func TestAccResourceID(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceIDConfig, @@ -41,7 +41,7 @@ func TestAccResourceID(t *testing.T) { func TestAccResourceID_importWithPrefix(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceIDConfigWithPrefix, diff --git a/internal/provider_fm/resource_integer_test.go b/internal/provider_fm/resource_integer_test.go index df2c74e3..c8f67c12 100644 --- a/internal/provider_fm/resource_integer_test.go +++ b/internal/provider_fm/resource_integer_test.go @@ -12,7 +12,7 @@ func TestAccResourceIntegerBasic(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testRandomIntegerBasic, @@ -34,7 +34,7 @@ func TestAccResourceIntegerUpdate(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testRandomIntegerBasic, @@ -56,7 +56,7 @@ func TestAccResourceIntegerSeedless_to_seeded(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testRandomIntegerSeedless, @@ -78,7 +78,7 @@ func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testRandomIntegerBasic, @@ -96,25 +96,6 @@ func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) { }) } -func TestAccResourceIntegerBig(t *testing.T) { - t.Parallel() - resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - { - Config: testRandomIntegerBig, - }, - { - ResourceName: "random_integer.integer_1", - ImportState: true, - ImportStateId: "7227701560655103598,7227701560655103597,7227701560655103598,12345", - ImportStateVerify: true, - }, - }, - }) -} - func testAccResourceIntegerBasic(id string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[id] @@ -205,11 +186,4 @@ resource "random_integer" "integer_1" { max = 3 } ` - - testRandomIntegerBig = ` -resource "random_integer" "integer_1" { - max = 7227701560655103598 - min = 7227701560655103597 - seed = 12345 -}` ) diff --git a/internal/provider_fm/resource_pasword_test.go b/internal/provider_fm/resource_pasword_test.go index 0653f9c4..29ccf61f 100644 --- a/internal/provider_fm/resource_pasword_test.go +++ b/internal/provider_fm/resource_pasword_test.go @@ -12,7 +12,7 @@ import ( func TestAccResourcePasswordBasic(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePasswordBasic, @@ -50,7 +50,7 @@ func TestAccResourcePasswordBasic(t *testing.T) { func TestAccResourcePasswordOverride(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePasswordOverride, @@ -68,7 +68,7 @@ func TestAccResourcePasswordOverride(t *testing.T) { func TestAccResourcePasswordMin(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePasswordMin, diff --git a/internal/provider_fm/resource_pet_test.go b/internal/provider_fm/resource_pet_test.go index 5d2c9217..a822682b 100644 --- a/internal/provider_fm/resource_pet_test.go +++ b/internal/provider_fm/resource_pet_test.go @@ -13,7 +13,7 @@ import ( func TestAccResourcePet_basic(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePet_basic, @@ -28,7 +28,7 @@ func TestAccResourcePet_basic(t *testing.T) { func TestAccResourcePet_length(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePet_length, @@ -43,7 +43,7 @@ func TestAccResourcePet_length(t *testing.T) { func TestAccResourcePet_prefix(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePet_prefix, @@ -60,7 +60,7 @@ func TestAccResourcePet_prefix(t *testing.T) { func TestAccResourcePet_separator(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePet_separator, diff --git a/internal/provider_fm/resource_shuffle_test.go b/internal/provider_fm/resource_shuffle_test.go index de20df46..865ea4c7 100644 --- a/internal/provider_fm/resource_shuffle_test.go +++ b/internal/provider_fm/resource_shuffle_test.go @@ -21,7 +21,7 @@ import ( func TestAccResourceShuffleDefault(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigDefault, @@ -39,7 +39,7 @@ func TestAccResourceShuffleDefault(t *testing.T) { func TestAccResourceShuffleShorter(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigShorter, @@ -57,7 +57,7 @@ func TestAccResourceShuffleShorter(t *testing.T) { func TestAccResourceShuffleLonger(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigLonger, @@ -75,7 +75,7 @@ func TestAccResourceShuffleLonger(t *testing.T) { func TestAccResourceShuffleEmpty(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigEmpty, @@ -93,7 +93,7 @@ func TestAccResourceShuffleEmpty(t *testing.T) { func TestAccResourceShuffleOne(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigOne, diff --git a/internal/provider_fm/resource_string_test.go b/internal/provider_fm/resource_string_test.go index 5405d3e0..b931ce73 100644 --- a/internal/provider_fm/resource_string_test.go +++ b/internal/provider_fm/resource_string_test.go @@ -15,7 +15,7 @@ type customLens struct { func TestAccResourceString(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceStringBasic, @@ -38,7 +38,7 @@ func TestAccResourceString(t *testing.T) { func TestAccResourceStringOverride(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceStringOverride, @@ -56,7 +56,7 @@ func TestAccResourceStringOverride(t *testing.T) { func TestAccResourceStringMin(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceStringMin, @@ -77,7 +77,7 @@ func TestAccResourceStringMin(t *testing.T) { func TestAccResourceStringErrors(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceStringInvalidConfig, diff --git a/internal/provider_fm/resource_uuid_test.go b/internal/provider_fm/resource_uuid_test.go index 51b6ac65..fccf7da4 100644 --- a/internal/provider_fm/resource_uuid_test.go +++ b/internal/provider_fm/resource_uuid_test.go @@ -10,7 +10,7 @@ import ( func TestAccResourceUUID(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceUUIDConfig, From 5daecfe3fea1dd541fd53e458f0065852565421f Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 6 May 2022 09:54:38 +0100 Subject: [PATCH 21/96] Fixing docs (#177) --- docs/resources/pet.md | 2 +- internal/provider_fm/resource_pet.go | 2 +- internal/provider_fm/resource_shuffle.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/resources/pet.md b/docs/resources/pet.md index 536681a5..a610ba83 100644 --- a/docs/resources/pet.md +++ b/docs/resources/pet.md @@ -52,6 +52,6 @@ resource "aws_instance" "server" { ### Read-Only -- `id` (String) The random pet name +- `id` (String) The random pet name. diff --git a/internal/provider_fm/resource_pet.go b/internal/provider_fm/resource_pet.go index d8224c40..7ce0c524 100644 --- a/internal/provider_fm/resource_pet.go +++ b/internal/provider_fm/resource_pet.go @@ -62,7 +62,7 @@ func (r resourcePetType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnost }, }, "id": { - Description: "The generated uuid presented in string format.", + Description: "The random pet name.", Type: types.StringType, Computed: true, }, diff --git a/internal/provider_fm/resource_shuffle.go b/internal/provider_fm/resource_shuffle.go index 2c66e745..f6105076 100644 --- a/internal/provider_fm/resource_shuffle.go +++ b/internal/provider_fm/resource_shuffle.go @@ -60,7 +60,7 @@ func (r resourceShuffleType) GetSchema(context.Context) (tfsdk.Schema, diag.Diag Computed: true, }, "id": { - Description: "The generated uuid presented in string format.", + Description: "A static value used internally by Terraform, this should not be referenced in configurations.", Type: types.StringType, Computed: true, }, From 1f1f1339c66de738794b7bfd8ade35dbfa7c83dd Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 6 May 2022 10:16:01 +0100 Subject: [PATCH 22/96] Removing unneeded configuration check (#177) --- internal/provider_fm/resource_id.go | 7 ------- internal/provider_fm/resource_integer.go | 8 +------- internal/provider_fm/resource_uuid.go | 8 +------- 3 files changed, 2 insertions(+), 21 deletions(-) diff --git a/internal/provider_fm/resource_id.go b/internal/provider_fm/resource_id.go index 07bd657e..efab2fcd 100644 --- a/internal/provider_fm/resource_id.go +++ b/internal/provider_fm/resource_id.go @@ -97,13 +97,6 @@ type resourceID struct { } func (r resourceID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - if !r.p.configured { - resp.Diagnostics.AddError( - "provider not configured", - "provider not configured", - ) - } - var plan IDModel diags := req.Plan.Get(ctx, &plan) diff --git a/internal/provider_fm/resource_integer.go b/internal/provider_fm/resource_integer.go index f99874c2..3b710c8a 100644 --- a/internal/provider_fm/resource_integer.go +++ b/internal/provider_fm/resource_integer.go @@ -73,14 +73,8 @@ type resourceInteger struct { } func (r resourceInteger) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - if !r.p.configured { - resp.Diagnostics.AddError( - "provider not configured", - "provider not configured", - ) - } - var plan IntegerModel + diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/internal/provider_fm/resource_uuid.go b/internal/provider_fm/resource_uuid.go index 26973d20..7df61d08 100644 --- a/internal/provider_fm/resource_uuid.go +++ b/internal/provider_fm/resource_uuid.go @@ -53,13 +53,6 @@ type resourceUUID struct { } func (r resourceUUID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - if !r.p.configured { - resp.Diagnostics.AddError( - "provider not configured", - "provider not configured", - ) - } - result, err := uuid.GenerateUUID() if err != nil { resp.Diagnostics.AddError( @@ -69,6 +62,7 @@ func (r resourceUUID) Create(ctx context.Context, req tfsdk.CreateResourceReques } var plan UUIDModel + diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { From adae7c07093c94edfcb43cc4031c38ce62af5373 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 6 May 2022 10:21:16 +0100 Subject: [PATCH 23/96] Camel-case config constants (#177) --- internal/provider_fm/resource_pet_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/provider_fm/resource_pet_test.go b/internal/provider_fm/resource_pet_test.go index a822682b..5cb3a929 100644 --- a/internal/provider_fm/resource_pet_test.go +++ b/internal/provider_fm/resource_pet_test.go @@ -16,7 +16,7 @@ func TestAccResourcePet_basic(t *testing.T) { ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourcePet_basic, + Config: testAccResourcePetBasic, Check: resource.ComposeTestCheckFunc( testAccResourcePetLength("random_pet.pet_1", "-", 2), ), @@ -31,7 +31,7 @@ func TestAccResourcePet_length(t *testing.T) { ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourcePet_length, + Config: testAccResourcePetLengthSet, Check: resource.ComposeTestCheckFunc( testAccResourcePetLength("random_pet.pet_1", "-", 4), ), @@ -46,7 +46,7 @@ func TestAccResourcePet_prefix(t *testing.T) { ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourcePet_prefix, + Config: testAccResourcePetPrefix, Check: resource.ComposeTestCheckFunc( testAccResourcePetLength("random_pet.pet_1", "-", 3), resource.TestMatchResourceAttr( @@ -63,7 +63,7 @@ func TestAccResourcePet_separator(t *testing.T) { ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourcePet_separator, + Config: testAccResourcePetSeparator, Check: resource.ComposeTestCheckFunc( testAccResourcePetLength("random_pet.pet_1", "_", 3), ), @@ -92,23 +92,23 @@ func testAccResourcePetLength(id string, separator string, length int) resource. } } -const testAccResourcePet_basic = ` +const testAccResourcePetBasic = ` resource "random_pet" "pet_1" { } ` -const testAccResourcePet_length = ` +const testAccResourcePetLengthSet = ` resource "random_pet" "pet_1" { length = 4 } ` -const testAccResourcePet_prefix = ` +const testAccResourcePetPrefix = ` resource "random_pet" "pet_1" { prefix = "consul" } ` -const testAccResourcePet_separator = ` +const testAccResourcePetSeparator = ` resource "random_pet" "pet_1" { length = 3 separator = "_" From 8ff422fed749e6ec8191d38d730c4152afa164c4 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 6 May 2022 11:35:11 +0100 Subject: [PATCH 24/96] Returning interfaces from validators and plan modifiers (#177) --- internal/provider_fm/resource_pet.go | 4 +- internal/provider_fm/resource_string_test.go | 2 +- internal/provider_fm/string.go | 87 ++++++++++++-------- 3 files changed, 56 insertions(+), 37 deletions(-) diff --git a/internal/provider_fm/resource_pet.go b/internal/provider_fm/resource_pet.go index 7ce0c524..24370c6f 100644 --- a/internal/provider_fm/resource_pet.go +++ b/internal/provider_fm/resource_pet.go @@ -42,7 +42,7 @@ func (r resourcePetType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnost Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt{2}, + defaultInt(2), }, }, "prefix": { @@ -58,7 +58,7 @@ func (r resourcePetType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnost Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultString{"-"}, + defaultString("-"), }, }, "id": { diff --git a/internal/provider_fm/resource_string_test.go b/internal/provider_fm/resource_string_test.go index b931ce73..a7de252e 100644 --- a/internal/provider_fm/resource_string_test.go +++ b/internal/provider_fm/resource_string_test.go @@ -85,7 +85,7 @@ func TestAccResourceStringErrors(t *testing.T) { }, { Config: testAccResourceStringLengthTooShortConfig, - ExpectError: regexp.MustCompile(`.*expected length to be at least 1, got 0`), + ExpectError: regexp.MustCompile(`.*expected attribute to be at least 1, got 0`), }, }, }) diff --git a/internal/provider_fm/string.go b/internal/provider_fm/string.go index 8948ea51..4ff0c754 100644 --- a/internal/provider_fm/string.go +++ b/internal/provider_fm/string.go @@ -35,7 +35,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Type: types.Int64Type, Required: true, PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Validators: []tfsdk.AttributeValidator{lengthValidator{}}, + Validators: []tfsdk.AttributeValidator{validatorMinInt(1)}, }, "special": { @@ -45,7 +45,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultBool{true}, + defaultBool(true), }, }, @@ -56,7 +56,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultBool{true}, + defaultBool(true), }, }, @@ -67,7 +67,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultBool{true}, + defaultBool(true), }, }, @@ -78,7 +78,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultBool{true}, + defaultBool(true), }, }, @@ -89,7 +89,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt{0}, + defaultInt(0), }, }, @@ -100,7 +100,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt{0}, + defaultInt(0), }, }, @@ -111,7 +111,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt{0}, + defaultInt(0), }, }, @@ -122,7 +122,7 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt{0}, + defaultInt(0), }, }, @@ -154,40 +154,51 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { } } -type lengthValidator struct{} +func validatorMinInt(min int64) tfsdk.AttributeValidator { + return minIntValidator{min} +} + +type minIntValidator struct { + val int64 +} -func (l lengthValidator) Description(context.Context) string { - return "Length validator ensures that length is at least 1" +func (m minIntValidator) Description(context.Context) string { + return "MinInt validator ensures that attribute is at least val" } -func (l lengthValidator) MarkdownDescription(context.Context) string { - return "Length validator ensures that `length` is at least 1" +func (m minIntValidator) MarkdownDescription(context.Context) string { + return "MinInt validator ensures that attribute is at least `val`" } -func (l lengthValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { +func (m minIntValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { t := req.AttributeConfig.(types.Int64) - if t.Value < 1 { + if t.Value < m.val { resp.Diagnostics.AddError( - fmt.Sprintf("expected length to be at least 1, got %d", t.Value), - fmt.Sprintf("expected length to be at least 1, got %d", t.Value), + fmt.Sprintf("expected attribute to be at least %d, got %d", m.val, t.Value), + fmt.Sprintf("expected attribute to be at least %d, got %d", m.val, t.Value), ) } } -type defaultBool struct { +//nolint:unparam +func defaultBool(val bool) tfsdk.AttributePlanModifier { + return boolDefault{val} +} + +type boolDefault struct { val bool } -func (d defaultBool) Description(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set." +func (d boolDefault) Description(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set using val." } -func (d defaultBool) MarkdownDescription(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set." +func (d boolDefault) MarkdownDescription(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set using `val`." } -func (d defaultBool) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { +func (d boolDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { t := req.AttributeConfig.(types.Bool) if t.Null { @@ -197,19 +208,23 @@ func (d defaultBool) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRe } } -type defaultInt struct { +func defaultInt(val int64) tfsdk.AttributePlanModifier { + return intDefault{val} +} + +type intDefault struct { val int64 } -func (d defaultInt) Description(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set." +func (d intDefault) Description(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set using val." } -func (d defaultInt) MarkdownDescription(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set." +func (d intDefault) MarkdownDescription(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set using `val`." } -func (d defaultInt) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { +func (d intDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { t := req.AttributeConfig.(types.Int64) if t.Null { @@ -220,19 +235,23 @@ func (d defaultInt) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanReq } } -type defaultString struct { +func defaultString(val string) tfsdk.AttributePlanModifier { + return stringDefault{val} +} + +type stringDefault struct { val string } -func (d defaultString) Description(ctx context.Context) string { +func (d stringDefault) Description(ctx context.Context) string { return "If the plan does not contain a value, a default will be set." } -func (d defaultString) MarkdownDescription(ctx context.Context) string { +func (d stringDefault) MarkdownDescription(ctx context.Context) string { return "If the plan does not contain a value, a default will be set." } -func (d defaultString) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { +func (d stringDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { t := req.AttributeConfig.(types.String) if t.Null { From 7b84c5894181af3e4c46b9c88489899764edbd4c Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 6 May 2022 15:26:06 +0100 Subject: [PATCH 25/96] UpgradeState is being removed as the minimum Terraform version required for the random provider has been v0.12 for a long time. Consequently, there should be vanishingly few/zero instances where the state has not already been upgraded (#177) --- .../provider/resource_string_migration.go | 50 ---------- .../resource_string_migration_test.go | 77 ---------------- internal/provider_fm/resource_string.go | 8 -- .../provider_fm/resource_string_migration.go | 43 --------- .../resource_string_migration_test.go | 92 ------------------- 5 files changed, 270 deletions(-) delete mode 100644 internal/provider/resource_string_migration.go delete mode 100644 internal/provider/resource_string_migration_test.go delete mode 100644 internal/provider_fm/resource_string_migration.go delete mode 100644 internal/provider_fm/resource_string_migration_test.go diff --git a/internal/provider/resource_string_migration.go b/internal/provider/resource_string_migration.go deleted file mode 100644 index 2b3e652a..00000000 --- a/internal/provider/resource_string_migration.go +++ /dev/null @@ -1,50 +0,0 @@ -package provider - -import ( - "fmt" - "log" - - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" -) - -func resourceRandomStringMigrateState( - v int, is *terraform.InstanceState, _ interface{}) (*terraform.InstanceState, error) { - switch v { - case 0: - log.Println("[INFO] Found random string state v0; migrating to v1") - return migrateStringStateV0toV1(is) - default: - return is, fmt.Errorf("Unexpected schema version: %d", v) - } -} - -func redactAttributes(is *terraform.InstanceState) map[string]string { - redactedAttributes := make(map[string]string) - for k, v := range is.Attributes { - redactedAttributes[k] = v - if k == "id" || k == "result" { - redactedAttributes[k] = "" - } - } - return redactedAttributes -} - -func migrateStringStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) { - if is.Empty() { - log.Println("[DEBUG] Empty InstanceState; nothing to migrate.") - return is, nil - } - - log.Printf("[DEBUG] Random String Attributes before Migration: %#v", redactAttributes(is)) - - keys := []string{"min_numeric", "min_upper", "min_lower", "min_special"} - for _, k := range keys { - if v := is.Attributes[k]; v == "" { - is.Attributes[k] = "0" - } - } - - log.Printf("[DEBUG] Random String Attributes after State Migration: %#v", redactAttributes(is)) - - return is, nil -} diff --git a/internal/provider/resource_string_migration_test.go b/internal/provider/resource_string_migration_test.go deleted file mode 100644 index e5172737..00000000 --- a/internal/provider/resource_string_migration_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package provider - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" -) - -func TestResourceStringMigrateState(t *testing.T) { - cases := map[string]struct { - StateVersion int - ID string - InputAttributes map[string]string - ExpectedAttributes map[string]string - Meta interface{} - }{ - "v0_1_simple": { - StateVersion: 0, - ID: "some_id", - InputAttributes: map[string]string{ - "result": "foo", - "id": "foo", - "length": "3", - }, - ExpectedAttributes: map[string]string{ - "result": "foo", - "id": "foo", - "length": "3", - "min_numeric": "0", - "min_special": "0", - "min_lower": "0", - "min_upper": "0", - }, - }, - "v0_1_special": { - StateVersion: 0, - ID: "some_id", - InputAttributes: map[string]string{ - "result": "foo", - "id": "foo", - "special": "false", - "length": "3", - "override_special": "!@", - }, - ExpectedAttributes: map[string]string{ - "result": "foo", - "id": "foo", - "special": "false", - "length": "3", - "override_special": "!@", - "min_numeric": "0", - "min_special": "0", - "min_lower": "0", - "min_upper": "0", - }, - }, - } - - for tn, tc := range cases { - is := &terraform.InstanceState{ - ID: tc.ID, - Attributes: tc.InputAttributes, - } - is, err := resourceRandomStringMigrateState(tc.StateVersion, is, tc.Meta) - - if err != nil { - t.Fatalf("bad: %s, err: %#v", tn, err) - } - - for k, v := range tc.ExpectedAttributes { - actual := is.Attributes[k] - if actual != v { - t.Fatalf("Bad Random String Migration for %q: %q\n\n expected: %q", k, actual, v) - } - } - } -} diff --git a/internal/provider_fm/resource_string.go b/internal/provider_fm/resource_string.go index 38e4250d..d6a2d6b5 100644 --- a/internal/provider_fm/resource_string.go +++ b/internal/provider_fm/resource_string.go @@ -57,11 +57,3 @@ func (r resourceString) ImportState(ctx context.Context, req tfsdk.ImportResourc func (r resourceString) ValidateConfig(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { validateLength(ctx, req, resp) } - -func (r resourceString) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { - return map[int64]tfsdk.ResourceStateUpgrader{ - 0: { - StateUpgrader: migrateStringStateV0toV1, - }, - } -} diff --git a/internal/provider_fm/resource_string_migration.go b/internal/provider_fm/resource_string_migration.go deleted file mode 100644 index a9ef50f4..00000000 --- a/internal/provider_fm/resource_string_migration.go +++ /dev/null @@ -1,43 +0,0 @@ -package provider_fm - -import ( - "context" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" -) - -//func resourceRandomStringMigrateState(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse { -// switch v { -// case 0: -// log.Println("[INFO] Found random string state v0; migrating to v1") -// return migrateStringStateV0toV1(is) -// default: -// return is, fmt.Errorf("Unexpected schema version: %d", v) -// } -//} - -//func resourceRandomStringMigrateState( -// v int, is *terraform.InstanceState, _ interface{}) (*terraform.InstanceState, error) { -// switch v { -// case 0: -// log.Println("[INFO] Found random string state v0; migrating to v1") -// return migrateStringStateV0toV1(is) -// default: -// return is, fmt.Errorf("Unexpected schema version: %d", v) -// } -//} - -//func redactAttributes(s StringModel) StringModel { -// s.ID.Value = "" -// s.Result.Value = "" -// -// return s -//} - -func migrateStringStateV0toV1(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { - s := StringModel{} - req.State.Get(ctx, &s) - resp.State.Set(ctx, s) - if resp.Diagnostics.HasError() { - return - } -} diff --git a/internal/provider_fm/resource_string_migration_test.go b/internal/provider_fm/resource_string_migration_test.go deleted file mode 100644 index 632c7eaa..00000000 --- a/internal/provider_fm/resource_string_migration_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package provider_fm - -import ( - "context" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-go/tftypes" - "testing" -) - -func TestResourceStringMigrateState(t *testing.T) { - //cases := map[string]struct { - // StateVersion int - // ID string - // InputAttributes map[string]string - // ExpectedAttributes map[string]string - // Meta interface{} - //}{ - // "v0_1_simple": { - // StateVersion: 0, - // ID: "some_id", - // InputAttributes: map[string]string{ - // "result": "foo", - // "id": "foo", - // "length": "3", - // }, - // ExpectedAttributes: map[string]string{ - // "result": "foo", - // "id": "foo", - // "length": "3", - // "min_numeric": "0", - // "min_special": "0", - // "min_lower": "0", - // "min_upper": "0", - // }, - // }, - // "v0_1_special": { - // StateVersion: 0, - // ID: "some_id", - // InputAttributes: map[string]string{ - // "result": "foo", - // "id": "foo", - // "special": "false", - // "length": "3", - // "override_special": "!@", - // }, - // ExpectedAttributes: map[string]string{ - // "result": "foo", - // "id": "foo", - // "special": "false", - // "length": "3", - // "override_special": "!@", - // "min_numeric": "0", - // "min_special": "0", - // "min_lower": "0", - // "min_upper": "0", - // }, - // }, - //} - - tval := tftypes.Value{} - - tval.Type() - - //for tn, tc := range cases { - req := tfsdk.UpgradeResourceStateRequest{ - State: &tfsdk.State{ - Raw: tftypes.Value{}, - Schema: tfsdk.Schema{ - Version: 0, - }, - }, - } - - resp := tfsdk.UpgradeResourceStateResponse{} - - migrateStringStateV0toV1(context.Background(), req, &resp) - - respState := StringModel{} - resp.State.Get(context.Background(), &respState) - - //if err != nil { - // t.Fatalf("bad: %s, err: %#v", tn, err) - //} - // - //for k, v := range tc.ExpectedAttributes { - // actual := is.Attributes[k] - // if actual != v { - // t.Fatalf("Bad Random StringModel Migration for %q: %q\n\n expected: %q", k, actual, v) - // } - //} - //} -} From 1ee779afd4aafa9dd347e78a1e3732631912f5ca Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 6 May 2022 15:43:21 +0100 Subject: [PATCH 26/96] Reinstating failing test (#177) --- internal/provider_fm/resource_integer_test.go | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/internal/provider_fm/resource_integer_test.go b/internal/provider_fm/resource_integer_test.go index c8f67c12..dcadd5ec 100644 --- a/internal/provider_fm/resource_integer_test.go +++ b/internal/provider_fm/resource_integer_test.go @@ -96,6 +96,25 @@ func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) { }) } +func TestAccResourceIntegerBig(t *testing.T) { + t.Parallel() + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: testRandomIntegerBig, + }, + { + ResourceName: "random_integer.integer_1", + ImportState: true, + ImportStateId: "7227701560655103598,7227701560655103597,7227701560655103598,12345", + ImportStateVerify: true, + }, + }, + }) +} + func testAccResourceIntegerBasic(id string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[id] @@ -186,4 +205,11 @@ resource "random_integer" "integer_1" { max = 3 } ` + + testRandomIntegerBig = ` +resource "random_integer" "integer_1" { + max = 7227701560655103598 + min = 7227701560655103597 + seed = 12345 +}` ) From 3accd4410df1bbb18dd503a645336f7154e7c1bf Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 9 May 2022 08:33:47 +0100 Subject: [PATCH 27/96] Rename package (#177) --- internal/{provider_fm => provider}/models.go | 2 +- internal/{provider_fm => provider}/provider.go | 2 +- internal/{provider_fm => provider}/provider_test.go | 2 +- internal/{provider_fm => provider}/resource_id.go | 2 +- internal/{provider_fm => provider}/resource_id_test.go | 2 +- internal/{provider_fm => provider}/resource_integer.go | 2 +- internal/{provider_fm => provider}/resource_integer_test.go | 2 +- internal/{provider_fm => provider}/resource_password.go | 2 +- internal/{provider_fm => provider}/resource_pasword_test.go | 2 +- internal/{provider_fm => provider}/resource_pet.go | 2 +- internal/{provider_fm => provider}/resource_pet_test.go | 2 +- internal/{provider_fm => provider}/resource_shuffle.go | 2 +- internal/{provider_fm => provider}/resource_shuffle_test.go | 2 +- internal/{provider_fm => provider}/resource_string.go | 2 +- internal/{provider_fm => provider}/resource_string_test.go | 2 +- internal/{provider_fm => provider}/resource_uuid.go | 2 +- internal/{provider_fm => provider}/resource_uuid_test.go | 2 +- internal/{provider_fm => provider}/seed.go | 2 +- internal/{provider_fm => provider}/string.go | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) rename internal/{provider_fm => provider}/models.go (99%) rename internal/{provider_fm => provider}/provider.go (98%) rename internal/{provider_fm => provider}/provider_test.go (97%) rename internal/{provider_fm => provider}/resource_id.go (99%) rename internal/{provider_fm => provider}/resource_id_test.go (99%) rename internal/{provider_fm => provider}/resource_integer.go (99%) rename internal/{provider_fm => provider}/resource_integer_test.go (99%) rename internal/{provider_fm => provider}/resource_password.go (98%) rename internal/{provider_fm => provider}/resource_pasword_test.go (99%) rename internal/{provider_fm => provider}/resource_pet.go (99%) rename internal/{provider_fm => provider}/resource_pet_test.go (99%) rename internal/{provider_fm => provider}/resource_shuffle.go (99%) rename internal/{provider_fm => provider}/resource_shuffle_test.go (99%) rename internal/{provider_fm => provider}/resource_string.go (99%) rename internal/{provider_fm => provider}/resource_string_test.go (99%) rename internal/{provider_fm => provider}/resource_uuid.go (99%) rename internal/{provider_fm => provider}/resource_uuid_test.go (97%) rename internal/{provider_fm => provider}/seed.go (96%) rename internal/{provider_fm => provider}/string.go (99%) diff --git a/internal/provider_fm/models.go b/internal/provider/models.go similarity index 99% rename from internal/provider_fm/models.go rename to internal/provider/models.go index 4cf8237c..2ad8e552 100644 --- a/internal/provider_fm/models.go +++ b/internal/provider/models.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import "github.com/hashicorp/terraform-plugin-framework/types" diff --git a/internal/provider_fm/provider.go b/internal/provider/provider.go similarity index 98% rename from internal/provider_fm/provider.go rename to internal/provider/provider.go index 61324190..e413b635 100644 --- a/internal/provider_fm/provider.go +++ b/internal/provider/provider.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "context" diff --git a/internal/provider_fm/provider_test.go b/internal/provider/provider_test.go similarity index 97% rename from internal/provider_fm/provider_test.go rename to internal/provider/provider_test.go index 961d1c1d..d173d6d3 100644 --- a/internal/provider_fm/provider_test.go +++ b/internal/provider/provider_test.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "context" diff --git a/internal/provider_fm/resource_id.go b/internal/provider/resource_id.go similarity index 99% rename from internal/provider_fm/resource_id.go rename to internal/provider/resource_id.go index efab2fcd..ddcd4159 100644 --- a/internal/provider_fm/resource_id.go +++ b/internal/provider/resource_id.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "context" diff --git a/internal/provider_fm/resource_id_test.go b/internal/provider/resource_id_test.go similarity index 99% rename from internal/provider_fm/resource_id_test.go rename to internal/provider/resource_id_test.go index ac16dc62..ed208fbb 100644 --- a/internal/provider_fm/resource_id_test.go +++ b/internal/provider/resource_id_test.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "fmt" diff --git a/internal/provider_fm/resource_integer.go b/internal/provider/resource_integer.go similarity index 99% rename from internal/provider_fm/resource_integer.go rename to internal/provider/resource_integer.go index 3b710c8a..d36568b5 100644 --- a/internal/provider_fm/resource_integer.go +++ b/internal/provider/resource_integer.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "context" diff --git a/internal/provider_fm/resource_integer_test.go b/internal/provider/resource_integer_test.go similarity index 99% rename from internal/provider_fm/resource_integer_test.go rename to internal/provider/resource_integer_test.go index dcadd5ec..e31908a8 100644 --- a/internal/provider_fm/resource_integer_test.go +++ b/internal/provider/resource_integer_test.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "fmt" diff --git a/internal/provider_fm/resource_password.go b/internal/provider/resource_password.go similarity index 98% rename from internal/provider_fm/resource_password.go rename to internal/provider/resource_password.go index d992f4b6..0e93fa20 100644 --- a/internal/provider_fm/resource_password.go +++ b/internal/provider/resource_password.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "context" diff --git a/internal/provider_fm/resource_pasword_test.go b/internal/provider/resource_pasword_test.go similarity index 99% rename from internal/provider_fm/resource_pasword_test.go rename to internal/provider/resource_pasword_test.go index 29ccf61f..eb4091d4 100644 --- a/internal/provider_fm/resource_pasword_test.go +++ b/internal/provider/resource_pasword_test.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "fmt" diff --git a/internal/provider_fm/resource_pet.go b/internal/provider/resource_pet.go similarity index 99% rename from internal/provider_fm/resource_pet.go rename to internal/provider/resource_pet.go index 24370c6f..43db17f7 100644 --- a/internal/provider_fm/resource_pet.go +++ b/internal/provider/resource_pet.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "context" diff --git a/internal/provider_fm/resource_pet_test.go b/internal/provider/resource_pet_test.go similarity index 99% rename from internal/provider_fm/resource_pet_test.go rename to internal/provider/resource_pet_test.go index 5cb3a929..34903579 100644 --- a/internal/provider_fm/resource_pet_test.go +++ b/internal/provider/resource_pet_test.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "fmt" diff --git a/internal/provider_fm/resource_shuffle.go b/internal/provider/resource_shuffle.go similarity index 99% rename from internal/provider_fm/resource_shuffle.go rename to internal/provider/resource_shuffle.go index f6105076..618fcf4a 100644 --- a/internal/provider_fm/resource_shuffle.go +++ b/internal/provider/resource_shuffle.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "context" diff --git a/internal/provider_fm/resource_shuffle_test.go b/internal/provider/resource_shuffle_test.go similarity index 99% rename from internal/provider_fm/resource_shuffle_test.go rename to internal/provider/resource_shuffle_test.go index 865ea4c7..3f67909a 100644 --- a/internal/provider_fm/resource_shuffle_test.go +++ b/internal/provider/resource_shuffle_test.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "fmt" diff --git a/internal/provider_fm/resource_string.go b/internal/provider/resource_string.go similarity index 99% rename from internal/provider_fm/resource_string.go rename to internal/provider/resource_string.go index d6a2d6b5..7a29f594 100644 --- a/internal/provider_fm/resource_string.go +++ b/internal/provider/resource_string.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "context" diff --git a/internal/provider_fm/resource_string_test.go b/internal/provider/resource_string_test.go similarity index 99% rename from internal/provider_fm/resource_string_test.go rename to internal/provider/resource_string_test.go index a7de252e..eebd006d 100644 --- a/internal/provider_fm/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "fmt" diff --git a/internal/provider_fm/resource_uuid.go b/internal/provider/resource_uuid.go similarity index 99% rename from internal/provider_fm/resource_uuid.go rename to internal/provider/resource_uuid.go index 7df61d08..53b11fbe 100644 --- a/internal/provider_fm/resource_uuid.go +++ b/internal/provider/resource_uuid.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "context" diff --git a/internal/provider_fm/resource_uuid_test.go b/internal/provider/resource_uuid_test.go similarity index 97% rename from internal/provider_fm/resource_uuid_test.go rename to internal/provider/resource_uuid_test.go index fccf7da4..9a38fc18 100644 --- a/internal/provider_fm/resource_uuid_test.go +++ b/internal/provider/resource_uuid_test.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "regexp" diff --git a/internal/provider_fm/seed.go b/internal/provider/seed.go similarity index 96% rename from internal/provider_fm/seed.go rename to internal/provider/seed.go index ac0283a4..4d3c5265 100644 --- a/internal/provider_fm/seed.go +++ b/internal/provider/seed.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "hash/crc64" diff --git a/internal/provider_fm/string.go b/internal/provider/string.go similarity index 99% rename from internal/provider_fm/string.go rename to internal/provider/string.go index 4ff0c754..70279a6b 100644 --- a/internal/provider_fm/string.go +++ b/internal/provider/string.go @@ -1,4 +1,4 @@ -package provider_fm +package provider import ( "context" From e338d911a1945ab30482401e72ca073b46e21214 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 9 May 2022 08:43:08 +0100 Subject: [PATCH 28/96] Bumping framework version (#177) --- go.mod | 25 +++++++++++++------------ go.sum | 56 +++++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 85542884..fc29cbbe 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0 github.com/hashicorp/terraform-plugin-framework v0.8.0 github.com/hashicorp/terraform-plugin-go v0.9.1 + github.com/hashicorp/terraform-plugin-go v0.9.1 ) require ( @@ -20,7 +21,7 @@ require ( github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fatih/color v1.7.0 // indirect + github.com/fatih/color v1.13.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/google/uuid v1.1.2 // indirect @@ -30,7 +31,7 @@ require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect github.com/hashicorp/go-hclog v1.2.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.4.3 // indirect + github.com/hashicorp/go-plugin v1.4.4 // indirect github.com/hashicorp/go-version v1.4.0 // indirect github.com/hashicorp/hc-install v0.3.2 // indirect github.com/hashicorp/hcl/v2 v2.12.0 // indirect @@ -38,9 +39,9 @@ require ( github.com/hashicorp/terraform-exec v0.16.1 // indirect github.com/hashicorp/terraform-json v0.13.0 // indirect github.com/hashicorp/terraform-plugin-log v0.4.0 // indirect - github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 // indirect + github.com/hashicorp/terraform-registry-address v0.0.0-20220422185603-6772e136ec01 // indirect github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect - github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect + github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/mattn/go-colorable v0.1.12 // indirect @@ -51,21 +52,21 @@ require ( github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/oklog/run v1.0.0 // indirect + github.com/oklog/run v1.1.0 // indirect github.com/posener/complete v1.1.1 // indirect github.com/russross/blackfriday v1.6.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect - github.com/vmihailenco/tagparser v0.1.1 // indirect + github.com/vmihailenco/tagparser v0.1.2 // indirect github.com/zclconf/go-cty v1.10.0 // indirect golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect - golang.org/x/net v0.0.0-20210326060303-6b1517762897 // indirect - golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect - golang.org/x/text v0.3.5 // indirect - google.golang.org/appengine v1.6.6 // indirect - google.golang.org/genproto v0.0.0-20200711021454-869866162049 // indirect - google.golang.org/grpc v1.45.0 // indirect + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect + golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 // indirect + google.golang.org/grpc v1.46.0 // indirect google.golang.org/protobuf v1.28.0 // indirect ) diff --git a/go.sum b/go.sum index ab4bc0ee..58b3c815 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,7 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -55,9 +56,11 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= @@ -121,8 +124,9 @@ github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39E github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 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.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= +github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= @@ -148,8 +152,8 @@ github.com/hashicorp/terraform-plugin-docs v0.8.1/go.mod h1:p40z/69HYNUN/G2RDYp8 ======= github.com/hashicorp/terraform-plugin-docs v0.8.0 h1:qNuHNTEqVCT258+h1GsejtfgW6qxTBAXc6qJbzd8C3U= github.com/hashicorp/terraform-plugin-docs v0.8.0/go.mod h1:MjeyK5CEI/jZbTQXq1Ay0UwTGdqxEcQexqhHP1WcLZc= -github.com/hashicorp/terraform-plugin-framework v0.7.0 h1:vFM17wfu1Iq5XJ4S/wUooayMMpmlwIBbSBQEs3XfsD4= -github.com/hashicorp/terraform-plugin-framework v0.7.0/go.mod h1:1iyRcwMnjsCvH9XpDBUFd1l6gJcgAT2dZ+RJGh56vj4= +github.com/hashicorp/terraform-plugin-framework v0.8.0 h1:2nxk+5qAKlGWOrpWZbAZNkO+AoC87l4+9d/rjtQd6Wo= +github.com/hashicorp/terraform-plugin-framework v0.8.0/go.mod h1:jUhqrbeI48gAleP8LXzg9jtRH07EAcpwEGQlYmKNIVg= github.com/hashicorp/terraform-plugin-go v0.8.0/go.mod h1:E3GuvfX0Pz2Azcl6BegD6t51StXsVZMOYQoGO8mkHM0= >>>>>>> 37128b5 (Configuring muxing (#177)) github.com/hashicorp/terraform-plugin-go v0.9.0 h1:FvLY/3z4SNVatPZdoFcyrlNbCar+WyyOTv5X4Tp+WZc= @@ -165,14 +169,20 @@ github.com/hashicorp/terraform-plugin-mux v0.6.0 h1:HMUdyltYurTQr9yyijnB3aj1olpN github.com/hashicorp/terraform-plugin-mux v0.6.0/go.mod h1:qtg6FWnYEwwU3jMugPetmjVYKKn2VWhIPYga5fCtzd4= github.com/hashicorp/terraform-plugin-sdk/v2 v2.15.0 h1:nBRM7JBvaYDV8bJjtBUyDZZrprr7UWur1/P8j4vgvqY= github.com/hashicorp/terraform-plugin-sdk/v2 v2.15.0/go.mod h1:oCj7EsihOUat3iY2WNFQzH8OLThqjmQ6tJxsmD7Ay9U= +<<<<<<< HEAD >>>>>>> 37128b5 (Configuring muxing (#177)) github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 h1:1FGtlkJw87UsTMg5s8jrekrHmUPUJaMcu6ELiVhQrNw= +======= +>>>>>>> 828da2b (Bumping framework version (#177)) github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896/go.mod h1:bzBPnUIkI0RxauU8Dqo+2KrZZ28Cf48s8V6IHt3p4co= +github.com/hashicorp/terraform-registry-address v0.0.0-20220422185603-6772e136ec01 h1:HgJRxDmThXP6Jdjv2pHufKfESG23Y3x8VtXgmtMDsq8= +github.com/hashicorp/terraform-registry-address v0.0.0-20220422185603-6772e136ec01/go.mod h1:bdLC+qQlJIBHKbCMA6GipcuaKjmjcvZlnVdpU583z3Y= github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= +github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -200,11 +210,13 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mitchellh/cli v1.1.3 h1:xrX6lWnp1wgXZ65TGY2SB5URdQYcXu6VILdxDf5NttQ= @@ -228,8 +240,9 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -265,8 +278,9 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaU github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= +github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -308,10 +322,13 @@ golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -329,22 +346,32 @@ golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -359,15 +386,17 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200711021454-869866162049 h1:YFTFpQhgvrLrmxtiIncJxFXeCyq84ixuKWVCaCAi9Oc= google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 h1:q1kiSVscqoDeqTF27eQ2NnLLDmqF0I373qQNXYMy0fo= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -376,8 +405,9 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 86ffa14e34aa28e6f8cfd185f2f880cbf401aa07 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 9 May 2022 08:56:14 +0100 Subject: [PATCH 29/96] Skipping caching on golangci-lint action (#177) --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 37cd4688..37c15e78 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,6 +34,7 @@ jobs: uses: golangci/golangci-lint-action@v3 with: version: latest + skip-cache: true - name: Generate run: make generate From 13760faaf5c73dfa0a38578b997eb1fee93f8d69 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 9 May 2022 12:53:08 +0100 Subject: [PATCH 30/96] Fixing import (#177) --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 73a99c70..77513eac 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" "github.com/hashicorp/terraform-plugin-mux/tf6to5server" - "github.com/terraform-providers/terraform-provider-random/internal/provider_fm" + "github.com/terraform-providers/terraform-provider-random/internal/provider" "log" ) @@ -26,7 +26,7 @@ func main() { ctx := context.Background() downgradedFrameworkProvider, err := tf6to5server.DowngradeServer(ctx, func() tfprotov6.ProviderServer { - return providerserver.NewProtocol6(provider_fm.NewFramework())() + return providerserver.NewProtocol6(provider.NewFramework())() }) if err != nil { log.Fatal(err) From c5402974b1a10213139fcf6793aadfd15cc753b8 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 10 May 2022 10:11:59 +0100 Subject: [PATCH 31/96] Removing unnecessary skip cache (#177) --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 37c15e78..37cd4688 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,6 @@ jobs: uses: golangci/golangci-lint-action@v3 with: version: latest - skip-cache: true - name: Generate run: make generate From 085d0f1ceb35f08b1835fa16f9fb9386a5d986bb Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 10 May 2022 10:14:04 +0100 Subject: [PATCH 32/96] Removed unused configured field on provider struct (#177) --- internal/provider/provider.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e413b635..1028e959 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -7,7 +7,6 @@ import ( ) type provider struct { - configured bool } func NewFramework() tfsdk.Provider { @@ -19,7 +18,6 @@ func (p *provider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostic } func (p *provider) Configure(ctx context.Context, req tfsdk.ConfigureProviderRequest, resp *tfsdk.ConfigureProviderResponse) { - p.configured = true } func (p *provider) GetResources(ctx context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { From 369448ef594138bad7453056ac347cfd7448df45 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 10 May 2022 10:27:34 +0100 Subject: [PATCH 33/96] Remove explicit calling of resp.State.RemoveResource (#177) --- internal/provider/resource_id.go | 3 ++- internal/provider/resource_integer.go | 3 ++- internal/provider/resource_password.go | 3 ++- internal/provider/resource_pet.go | 3 ++- internal/provider/resource_shuffle.go | 3 ++- internal/provider/resource_string.go | 3 ++- internal/provider/resource_uuid.go | 3 ++- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/internal/provider/resource_id.go b/internal/provider/resource_id.go index ddcd4159..ef185329 100644 --- a/internal/provider/resource_id.go +++ b/internal/provider/resource_id.go @@ -159,8 +159,9 @@ func (r resourceID) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, // Intentionally left blank. } +// Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the +// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). func (r resourceID) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - resp.State.RemoveResource(ctx) } func (r resourceID) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { diff --git a/internal/provider/resource_integer.go b/internal/provider/resource_integer.go index d36568b5..374982e5 100644 --- a/internal/provider/resource_integer.go +++ b/internal/provider/resource_integer.go @@ -125,8 +125,9 @@ func (r resourceInteger) Update(ctx context.Context, req tfsdk.UpdateResourceReq // Intentionally left blank. } +// Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the +// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). func (r resourceInteger) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - resp.State.RemoveResource(ctx) } func (r resourceInteger) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 0e93fa20..c35b9de8 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -39,8 +39,9 @@ func (r resourcePassword) Update(ctx context.Context, req tfsdk.UpdateResourceRe // Intentionally left blank. } +// Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the +// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). func (r resourcePassword) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - resp.State.RemoveResource(ctx) } func (r resourcePassword) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { diff --git a/internal/provider/resource_pet.go b/internal/provider/resource_pet.go index 43db17f7..95dc5b2b 100644 --- a/internal/provider/resource_pet.go +++ b/internal/provider/resource_pet.go @@ -125,6 +125,7 @@ func (r resourcePet) Update(ctx context.Context, req tfsdk.UpdateResourceRequest // Intentionally left blank. } +// Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the +// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). func (r resourcePet) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - resp.State.RemoveResource(ctx) } diff --git a/internal/provider/resource_shuffle.go b/internal/provider/resource_shuffle.go index 618fcf4a..f0e45892 100644 --- a/internal/provider/resource_shuffle.go +++ b/internal/provider/resource_shuffle.go @@ -153,6 +153,7 @@ func (r resourceShuffle) Update(ctx context.Context, req tfsdk.UpdateResourceReq // Intentionally left blank. } +// Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the +// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). func (r resourceShuffle) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - resp.State.RemoveResource(ctx) } diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index 7a29f594..22e767b9 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -46,8 +46,9 @@ func (r resourceString) Update(ctx context.Context, req tfsdk.UpdateResourceRequ // Intentionally left blank. } +// Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the +// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). func (r resourceString) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - resp.State.RemoveResource(ctx) } func (r resourceString) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { diff --git a/internal/provider/resource_uuid.go b/internal/provider/resource_uuid.go index 53b11fbe..94ecfa05 100644 --- a/internal/provider/resource_uuid.go +++ b/internal/provider/resource_uuid.go @@ -90,8 +90,9 @@ func (r resourceUUID) Update(ctx context.Context, req tfsdk.UpdateResourceReques // Intentionally left blank. } +// Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the +// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). func (r resourceUUID) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - resp.State.RemoveResource(ctx) } func (r resourceUUID) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { From c4f2c25e10077b6c57f8885c2361467fbf39382a Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 10 May 2022 10:39:31 +0100 Subject: [PATCH 34/96] Adding comments for Read and Update operations (#177) --- internal/provider/resource_id.go | 4 +++- internal/provider/resource_integer.go | 5 +++-- internal/provider/resource_password.go | 5 +++-- internal/provider/resource_pet.go | 5 +++-- internal/provider/resource_shuffle.go | 5 +++-- internal/provider/resource_string.go | 5 +++-- internal/provider/resource_uuid.go | 5 +++-- 7 files changed, 21 insertions(+), 13 deletions(-) diff --git a/internal/provider/resource_id.go b/internal/provider/resource_id.go index ef185329..889f7b2b 100644 --- a/internal/provider/resource_id.go +++ b/internal/provider/resource_id.go @@ -151,10 +151,12 @@ func (r resourceID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, } } +// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. func (r resourceID) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { - // Intentionally left blank. } +// Update is intentionally left blank as all required and optional attributes force replacement of the resource +// through the RequiresReplace AttributePlanModifier. func (r resourceID) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { // Intentionally left blank. } diff --git a/internal/provider/resource_integer.go b/internal/provider/resource_integer.go index 374982e5..3db16e6f 100644 --- a/internal/provider/resource_integer.go +++ b/internal/provider/resource_integer.go @@ -117,12 +117,13 @@ func (r resourceInteger) Create(ctx context.Context, req tfsdk.CreateResourceReq } } +// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. func (r resourceInteger) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { - // Intentionally left blank. } +// Update is intentionally left blank as all required and optional attributes force replacement of the resource +// through the RequiresReplace AttributePlanModifier. func (r resourceInteger) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - // Intentionally left blank. } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index c35b9de8..e99a9a43 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -31,12 +31,13 @@ func (r resourcePassword) Create(ctx context.Context, req tfsdk.CreateResourceRe createString(ctx, req, resp, true) } +// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. func (r resourcePassword) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { - // Intentionally left blank. } +// Update is intentionally left blank as all required and optional attributes force replacement of the resource +// through the RequiresReplace AttributePlanModifier. func (r resourcePassword) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - // Intentionally left blank. } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the diff --git a/internal/provider/resource_pet.go b/internal/provider/resource_pet.go index 95dc5b2b..1c364cd4 100644 --- a/internal/provider/resource_pet.go +++ b/internal/provider/resource_pet.go @@ -117,12 +117,13 @@ func (r resourcePet) Create(ctx context.Context, req tfsdk.CreateResourceRequest } } +// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. func (r resourcePet) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { - // Intentionally left blank. } +// Update is intentionally left blank as all required and optional attributes force replacement of the resource +// through the RequiresReplace AttributePlanModifier. func (r resourcePet) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - // Intentionally left blank. } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the diff --git a/internal/provider/resource_shuffle.go b/internal/provider/resource_shuffle.go index f0e45892..69dd9acf 100644 --- a/internal/provider/resource_shuffle.go +++ b/internal/provider/resource_shuffle.go @@ -145,12 +145,13 @@ func (r resourceShuffle) Create(ctx context.Context, req tfsdk.CreateResourceReq } } +// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. func (r resourceShuffle) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { - // Intentionally left blank. } +// Update is intentionally left blank as all required and optional attributes force replacement of the resource +// through the RequiresReplace AttributePlanModifier. func (r resourceShuffle) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - // Intentionally left blank. } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index 22e767b9..fdc7bbd5 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -38,12 +38,13 @@ func (r resourceString) Create(ctx context.Context, req tfsdk.CreateResourceRequ createString(ctx, req, resp, false) } +// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. func (r resourceString) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { - // Intentionally left blank. } +// Update is intentionally left blank as all required and optional attributes force replacement of the resource +// through the RequiresReplace AttributePlanModifier. func (r resourceString) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - // Intentionally left blank. } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the diff --git a/internal/provider/resource_uuid.go b/internal/provider/resource_uuid.go index 94ecfa05..14993c52 100644 --- a/internal/provider/resource_uuid.go +++ b/internal/provider/resource_uuid.go @@ -82,12 +82,13 @@ func (r resourceUUID) Create(ctx context.Context, req tfsdk.CreateResourceReques } } +// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. func (r resourceUUID) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { - // Intentionally left blank. } +// Update is intentionally left blank as all required and optional attributes force replacement of the resource +// through the RequiresReplace AttributePlanModifier. func (r resourceUUID) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - // Intentionally left blank. } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the From 5e87dd4b7b186ba3cc18566887878bb248b7fd90 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 10 May 2022 10:54:43 +0100 Subject: [PATCH 35/96] Moving validators to separate file (#177) --- internal/provider/string.go | 27 ----------------------- internal/provider/validators.go | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 27 deletions(-) create mode 100644 internal/provider/validators.go diff --git a/internal/provider/string.go b/internal/provider/string.go index 70279a6b..95040cd5 100644 --- a/internal/provider/string.go +++ b/internal/provider/string.go @@ -154,33 +154,6 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { } } -func validatorMinInt(min int64) tfsdk.AttributeValidator { - return minIntValidator{min} -} - -type minIntValidator struct { - val int64 -} - -func (m minIntValidator) Description(context.Context) string { - return "MinInt validator ensures that attribute is at least val" -} - -func (m minIntValidator) MarkdownDescription(context.Context) string { - return "MinInt validator ensures that attribute is at least `val`" -} - -func (m minIntValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { - t := req.AttributeConfig.(types.Int64) - - if t.Value < m.val { - resp.Diagnostics.AddError( - fmt.Sprintf("expected attribute to be at least %d, got %d", m.val, t.Value), - fmt.Sprintf("expected attribute to be at least %d, got %d", m.val, t.Value), - ) - } -} - //nolint:unparam func defaultBool(val bool) tfsdk.AttributePlanModifier { return boolDefault{val} diff --git a/internal/provider/validators.go b/internal/provider/validators.go new file mode 100644 index 00000000..7d5bb748 --- /dev/null +++ b/internal/provider/validators.go @@ -0,0 +1,38 @@ +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// validatorMinInt accepts an int64 and returns a struct that implements the AttributeValidator interface. +func validatorMinInt(min int64) tfsdk.AttributeValidator { + return minIntValidator{min} +} + +type minIntValidator struct { + val int64 +} + +func (m minIntValidator) Description(ctx context.Context) string { + return "MinInt validator ensures that attribute is at least val" +} + +func (m minIntValidator) MarkdownDescription(context.Context) string { + return "MinInt validator ensures that attribute is at least `val`" +} + +// Validate checks that the value of the attribute in the configuration is greater than or, equal to the value supplied +// when the minIntValidator struct was initialised. +func (m minIntValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { + t := req.AttributeConfig.(types.Int64) + + if t.Value < m.val { + resp.Diagnostics.AddError( + fmt.Sprintf("expected attribute to be at least %d, got %d", m.val, t.Value), + fmt.Sprintf("expected attribute to be at least %d, got %d", m.val, t.Value), + ) + } +} From 2f0c6729eef5bfca9e97e304d0b047506f32fb7b Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 10 May 2022 11:05:45 +0100 Subject: [PATCH 36/96] Moving plan modifiers to a separate file (#177) --- internal/provider/plan_modifiers.go | 97 +++++++++++++++++++++++++++++ internal/provider/string.go | 81 ------------------------ 2 files changed, 97 insertions(+), 81 deletions(-) create mode 100644 internal/provider/plan_modifiers.go diff --git a/internal/provider/plan_modifiers.go b/internal/provider/plan_modifiers.go new file mode 100644 index 00000000..b83ae0da --- /dev/null +++ b/internal/provider/plan_modifiers.go @@ -0,0 +1,97 @@ +package provider + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// defaultBool accepts a bool and returns a struct that implements the AttributePlanModifier interface. +//nolint:unparam // val is always true +func defaultBool(val bool) tfsdk.AttributePlanModifier { + return boolDefault{val} +} + +type boolDefault struct { + val bool +} + +func (d boolDefault) Description(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set using val." +} + +func (d boolDefault) MarkdownDescription(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set using `val`." +} + +// Modify checks that the value of the attribute in the configuration and, if the attribute is Null, assigns the value +// supplied to the boolDefault struct when it was initialised. +func (d boolDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + t := req.AttributeConfig.(types.Bool) + + if t.Null { + resp.AttributePlan = types.Bool{ + Value: d.val, + } + } +} + +// defaultInt accepts an int64 and returns a struct that implements the AttributePlanModifier interface. +func defaultInt(val int64) tfsdk.AttributePlanModifier { + return intDefault{val} +} + +type intDefault struct { + val int64 +} + +func (d intDefault) Description(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set using val." +} + +func (d intDefault) MarkdownDescription(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set using `val`." +} + +// Modify checks that the value of the attribute in the configuration and, if the attribute is Null, assigns the value +// supplied to the intDefault struct when it was initialised. +func (d intDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + t := req.AttributeConfig.(types.Int64) + + if t.Null { + resp.AttributePlan = types.Int64{ + Null: false, + Value: d.val, + } + } +} + +// defaultString accepts a string and returns a struct that implements the AttributePlanModifier interface. +func defaultString(val string) tfsdk.AttributePlanModifier { + return stringDefault{val} +} + +type stringDefault struct { + val string +} + +func (d stringDefault) Description(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set." +} + +func (d stringDefault) MarkdownDescription(ctx context.Context) string { + return "If the plan does not contain a value, a default will be set." +} + +// Modify checks that the value of the attribute in the configuration and, if the attribute is Null, assigns the value +// supplied to the stringDefault struct when it was initialised. +func (d stringDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + t := req.AttributeConfig.(types.String) + + if t.Null { + resp.AttributePlan = types.String{ + Null: false, + Value: d.val, + } + } +} diff --git a/internal/provider/string.go b/internal/provider/string.go index 95040cd5..ef3b90a4 100644 --- a/internal/provider/string.go +++ b/internal/provider/string.go @@ -154,87 +154,6 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { } } -//nolint:unparam -func defaultBool(val bool) tfsdk.AttributePlanModifier { - return boolDefault{val} -} - -type boolDefault struct { - val bool -} - -func (d boolDefault) Description(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set using val." -} - -func (d boolDefault) MarkdownDescription(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set using `val`." -} - -func (d boolDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { - t := req.AttributeConfig.(types.Bool) - - if t.Null { - resp.AttributePlan = types.Bool{ - Value: d.val, - } - } -} - -func defaultInt(val int64) tfsdk.AttributePlanModifier { - return intDefault{val} -} - -type intDefault struct { - val int64 -} - -func (d intDefault) Description(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set using val." -} - -func (d intDefault) MarkdownDescription(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set using `val`." -} - -func (d intDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { - t := req.AttributeConfig.(types.Int64) - - if t.Null { - resp.AttributePlan = types.Int64{ - Null: false, - Value: d.val, - } - } -} - -func defaultString(val string) tfsdk.AttributePlanModifier { - return stringDefault{val} -} - -type stringDefault struct { - val string -} - -func (d stringDefault) Description(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set." -} - -func (d stringDefault) MarkdownDescription(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set." -} - -func (d stringDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { - t := req.AttributeConfig.(types.String) - - if t.Null { - resp.AttributePlan = types.String{ - Null: false, - Value: d.val, - } - } -} - func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse, sensitive bool) { const numChars = "0123456789" const lowerChars = "abcdefghijklmnopqrstuvwxyz" From b8dd3f305b3498c1605c478cd701107febaf1f7a Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 10 May 2022 14:02:53 +0100 Subject: [PATCH 37/96] Improving diagnostics summary and detail messages (#177) --- internal/provider/diagnostics.go | 21 +++++++++++++++++++++ internal/provider/resource_id.go | 18 ++++++++++-------- internal/provider/resource_integer.go | 21 ++++++++++++--------- internal/provider/resource_uuid.go | 21 +++++++++++++++------ internal/provider/string.go | 19 +++++-------------- 5 files changed, 63 insertions(+), 37 deletions(-) create mode 100644 internal/provider/diagnostics.go diff --git a/internal/provider/diagnostics.go b/internal/provider/diagnostics.go new file mode 100644 index 00000000..7b0fee6f --- /dev/null +++ b/internal/provider/diagnostics.go @@ -0,0 +1,21 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" +) + +const retryMsg = "Retry the Terraform operation. If the error still occurs or happens regularly, please contact the provider developer with hardware and operating system information.\n\n" + +func randomReadError(errMsg string) diag.Diagnostics { + var diags diag.Diagnostics + + diags.AddError( + "Random Read Error", + "While attempting to generate a random value for this resource, a read error was generated.\n\n"+ + retryMsg+ + fmt.Sprintf("Original Error: %s", errMsg), + ) + + return diags +} diff --git a/internal/provider/resource_id.go b/internal/provider/resource_id.go index 889f7b2b..d3415e80 100644 --- a/internal/provider/resource_id.go +++ b/internal/provider/resource_id.go @@ -111,16 +111,15 @@ func (r resourceID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, n, err := rand.Reader.Read(bytes) if int64(n) != byteLength { resp.Diagnostics.AddError( - "generated insufficient random bytes: %s", - fmt.Sprintf("generated insufficient random bytes: %s", err), + "Randomness Generation Error", + "While attempting to generate a random value for this resource, an insufficient number of random bytes were generated. Most commonly, this is a hardware or operating system issue where their random number generator does not provide a sufficient randomness source. Otherwise, it may represent an issue in the randomness handling of the provider.\n\n"+ + "Retry the Terraform operation. If the error still occurs or happens regularly, please contact the provider developer with hardware and operating system information.\n\n"+ + fmt.Sprintf("Original Error: %s", err), ) return } if err != nil { - resp.Diagnostics.AddError( - "error generating random bytes", - fmt.Sprintf("error generating random bytes: %s", err), - ) + resp.Diagnostics.Append(randomReadError(err.Error())...) return } @@ -179,8 +178,11 @@ func (r resourceID) ImportState(ctx context.Context, req tfsdk.ImportResourceSta bytes, err := base64.RawURLEncoding.DecodeString(id) if err != nil { resp.Diagnostics.AddError( - "error decoding ID", - fmt.Sprintf("error decoding ID: %s", err)) + "Import Random ID Error", + "While attempting to import a random id there was a decoding error.\n\n+"+ + retryMsg+ + fmt.Sprintf("Original Error: %s", err), + ) return } diff --git a/internal/provider/resource_integer.go b/internal/provider/resource_integer.go index 3db16e6f..9f0af3ac 100644 --- a/internal/provider/resource_integer.go +++ b/internal/provider/resource_integer.go @@ -87,8 +87,8 @@ func (r resourceInteger) Create(ctx context.Context, req tfsdk.CreateResourceReq if max < min { resp.Diagnostics.AddError( - "minimum value needs to be smaller than or equal to maximum value", - "minimum value needs to be smaller than or equal to maximum value", + "Create Random Integer Error", + "The minimum (min) value needs to be smaller than or equal to maximum (max) value.", ) return } @@ -135,7 +135,7 @@ func (r resourceInteger) ImportState(ctx context.Context, req tfsdk.ImportResour parts := strings.Split(req.ID, ",") if len(parts) != 3 && len(parts) != 4 { resp.Diagnostics.AddError( - "Invalid import usage: expecting {result},{min},{max} or {result},{min},{max},{seed}", + "Import Random Integer Error", "Invalid import usage: expecting {result},{min},{max} or {result},{min},{max},{seed}", ) return @@ -144,8 +144,9 @@ func (r resourceInteger) ImportState(ctx context.Context, req tfsdk.ImportResour result, err := strconv.ParseInt(parts[0], 10, 64) if err != nil { resp.Diagnostics.AddError( - "error parsing result", - fmt.Sprintf("error parsing result: %s", err), + "Import Random Integer Error", + "The value supplied could not be parsed as an integer.\n\n"+ + fmt.Sprintf("Original Error: %s", err), ) return } @@ -153,8 +154,9 @@ func (r resourceInteger) ImportState(ctx context.Context, req tfsdk.ImportResour min, err := strconv.ParseInt(parts[1], 10, 64) if err != nil { resp.Diagnostics.AddError( - "error parsing min", - fmt.Sprintf("error parsing min: %s", err), + "Import Random Integer Error", + "The min value supplied could not be parsed as an integer.\n\n"+ + fmt.Sprintf("Original Error: %s", err), ) return } @@ -162,8 +164,9 @@ func (r resourceInteger) ImportState(ctx context.Context, req tfsdk.ImportResour max, err := strconv.ParseInt(parts[2], 10, 64) if err != nil { resp.Diagnostics.AddError( - "error parsing max", - fmt.Sprintf("error parsing max: %s", err), + "Import Random Integer Error", + "The max value supplied could not be parsed as an integer.\n\n"+ + fmt.Sprintf("Original Error: %s", err), ) return } diff --git a/internal/provider/resource_uuid.go b/internal/provider/resource_uuid.go index 14993c52..97780799 100644 --- a/internal/provider/resource_uuid.go +++ b/internal/provider/resource_uuid.go @@ -56,8 +56,11 @@ func (r resourceUUID) Create(ctx context.Context, req tfsdk.CreateResourceReques result, err := uuid.GenerateUUID() if err != nil { resp.Diagnostics.AddError( - "error generating uuid", - fmt.Sprintf("could not generate uuid: %s", err)) + "Create Random UUID error", + "There was an error during generation of a UUID.\n\n"+ + retryMsg+ + fmt.Sprintf("Original Error: %s", err), + ) return } @@ -100,16 +103,22 @@ func (r resourceUUID) ImportState(ctx context.Context, req tfsdk.ImportResourceS bytes, err := uuid.ParseUUID(req.ID) if err != nil { resp.Diagnostics.AddError( - "error parsing uuid bytes", - fmt.Sprintf("error parsing uuid bytes: %s", err)) + "Import Random UUID Error", + "There was an error during the parsing of the UUID.\n\n"+ + retryMsg+ + fmt.Sprintf("Original Error: %s", err), + ) return } result, err := uuid.FormatUUID(bytes) if err != nil { resp.Diagnostics.AddError( - "error formatting uuid bytes", - fmt.Sprintf("error formatting uuid bytes: %s", err)) + "Import Random UUID Error", + "There was an error during the formatting of the UUID.\n\n"+ + retryMsg+ + fmt.Sprintf("Original Error: %s", err), + ) return } diff --git a/internal/provider/string.go b/internal/provider/string.go index ef3b90a4..93fe43b3 100644 --- a/internal/provider/string.go +++ b/internal/provider/string.go @@ -208,10 +208,7 @@ func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tf for k, v := range minMapping { s, err := generateRandomBytes(&k, v) if err != nil { - resp.Diagnostics.AddError( - "error generating random bytes", - fmt.Sprintf("error generating random bytes: %s", err), - ) + resp.Diagnostics.Append(randomReadError(err.Error())...) return } result = append(result, s...) @@ -219,10 +216,7 @@ func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tf s, err := generateRandomBytes(&chars, length-int64(len(result))) if err != nil { - resp.Diagnostics.AddError( - "error generating random bytes", - fmt.Sprintf("error generating random bytes: %s", err), - ) + resp.Diagnostics.Append(randomReadError(err.Error())...) return } @@ -230,10 +224,7 @@ func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tf order := make([]byte, len(result)) if _, err := rand.Read(order); err != nil { - resp.Diagnostics.AddError( - "error generating random bytes", - fmt.Sprintf("error generating random bytes: %s", err), - ) + resp.Diagnostics.Append(randomReadError(err.Error())...) return } @@ -314,8 +305,8 @@ func validateLength(ctx context.Context, req tfsdk.ValidateResourceConfigRequest if length < minUpper+minLower+minNumeric+minSpecial { resp.Diagnostics.AddError( - fmt.Sprintf("length (%d) must be >= min_upper + min_lower + min_numeric + min_special (%d)", length, minUpper+minLower+minNumeric+minSpecial), - fmt.Sprintf("length (%d) must be >= min_upper + min_lower + min_numeric + min_special (%d)", length, minUpper+minLower+minNumeric+minSpecial), + "Validate Password/String Error", + fmt.Sprintf("The password/string length (%d) must be >= min_upper + min_lower + min_numeric + min_special (%d)", length, minUpper+minLower+minNumeric+minSpecial), ) } } From 8f22feaf4a970718d22312079b649ff99aed9b9a Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 10 May 2022 14:07:29 +0100 Subject: [PATCH 38/96] Removing usage of alias for std lib pkg (#177) --- internal/provider/resource_id.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/provider/resource_id.go b/internal/provider/resource_id.go index d3415e80..d20bc233 100644 --- a/internal/provider/resource_id.go +++ b/internal/provider/resource_id.go @@ -4,7 +4,7 @@ import ( "context" "crypto/rand" "encoding/base64" - hex2 "encoding/hex" + "encoding/hex" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" @@ -126,7 +126,7 @@ func (r resourceID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, id := base64.RawURLEncoding.EncodeToString(bytes) prefix := plan.Prefix.Value b64Std := base64.StdEncoding.EncodeToString(bytes) - hex := hex2.EncodeToString(bytes) + hexStr := hex.EncodeToString(bytes) bigInt := big.Int{} bigInt.SetBytes(bytes) @@ -139,7 +139,7 @@ func (r resourceID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, Prefix: plan.Prefix, B64URL: types.String{Value: prefix + id}, B64Std: types.String{Value: prefix + b64Std}, - Hex: types.String{Value: prefix + hex}, + Hex: types.String{Value: prefix + hexStr}, Dec: types.String{Value: prefix + dec}, } @@ -187,7 +187,7 @@ func (r resourceID) ImportState(ctx context.Context, req tfsdk.ImportResourceSta } b64Std := base64.StdEncoding.EncodeToString(bytes) - hex := hex2.EncodeToString(bytes) + hexStr := hex.EncodeToString(bytes) bigInt := big.Int{} bigInt.SetBytes(bytes) @@ -200,7 +200,7 @@ func (r resourceID) ImportState(ctx context.Context, req tfsdk.ImportResourceSta state.Keepers.ElemType = types.StringType state.B64Std.Value = prefix + b64Std state.B64URL.Value = prefix + id - state.Hex.Value = prefix + hex + state.Hex.Value = prefix + hexStr state.Dec.Value = prefix + dec if prefix == "" { From cd576f2fe3430158089c022d47479c43c4a175b3 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 10 May 2022 14:32:19 +0100 Subject: [PATCH 39/96] Formatting imports (#177) --- go.mod | 2 +- go.sum | 3 ++- internal/provider/diagnostics.go | 1 + internal/provider/plan_modifiers.go | 1 + internal/provider/provider.go | 1 + internal/provider/provider_test.go | 5 +++-- internal/provider/resource_id.go | 5 +++-- internal/provider/resource_integer.go | 5 +++-- internal/provider/resource_password.go | 1 + internal/provider/resource_pasword_test.go | 2 +- internal/provider/resource_pet.go | 3 ++- internal/provider/resource_shuffle.go | 1 + internal/provider/resource_string.go | 1 + internal/provider/resource_string_test.go | 5 +++-- internal/provider/resource_uuid.go | 1 + internal/provider/string.go | 5 +++-- internal/provider/validators.go | 1 + 17 files changed, 29 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index fc29cbbe..cda4ad81 100644 --- a/go.mod +++ b/go.mod @@ -61,7 +61,7 @@ require ( github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect github.com/vmihailenco/tagparser v0.1.2 // indirect github.com/zclconf/go-cty v1.10.0 // indirect - golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index 58b3c815..557c2418 100644 --- a/go.sum +++ b/go.sum @@ -301,8 +301,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= diff --git a/internal/provider/diagnostics.go b/internal/provider/diagnostics.go index 7b0fee6f..c2cb135e 100644 --- a/internal/provider/diagnostics.go +++ b/internal/provider/diagnostics.go @@ -2,6 +2,7 @@ package provider import ( "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" ) diff --git a/internal/provider/plan_modifiers.go b/internal/provider/plan_modifiers.go index b83ae0da..bd4cc135 100644 --- a/internal/provider/plan_modifiers.go +++ b/internal/provider/plan_modifiers.go @@ -2,6 +2,7 @@ package provider import ( "context" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" ) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 1028e959..6986b38e 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -2,6 +2,7 @@ package provider import ( "context" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" ) diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index d173d6d3..a74a8451 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -2,12 +2,13 @@ package provider import ( "context" + "log" + "testing" + "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-mux/tf6to5server" - "log" - "testing" ) func testAccPreCheck(t *testing.T) { diff --git a/internal/provider/resource_id.go b/internal/provider/resource_id.go index d20bc233..87059669 100644 --- a/internal/provider/resource_id.go +++ b/internal/provider/resource_id.go @@ -6,11 +6,12 @@ import ( "encoding/base64" "encoding/hex" "fmt" + "math/big" + "strings" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" - "math/big" - "strings" ) type resourceIDType struct{} diff --git a/internal/provider/resource_integer.go b/internal/provider/resource_integer.go index 9f0af3ac..bc6b511b 100644 --- a/internal/provider/resource_integer.go +++ b/internal/provider/resource_integer.go @@ -3,11 +3,12 @@ package provider import ( "context" "fmt" + "strconv" + "strings" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" - "strconv" - "strings" ) type resourceIntegerType struct{} diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index e99a9a43..ecf789b5 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -2,6 +2,7 @@ package provider import ( "context" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" ) diff --git a/internal/provider/resource_pasword_test.go b/internal/provider/resource_pasword_test.go index eb4091d4..2b393c43 100644 --- a/internal/provider/resource_pasword_test.go +++ b/internal/provider/resource_pasword_test.go @@ -2,11 +2,11 @@ package provider import ( "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "regexp" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccResourcePasswordBasic(t *testing.T) { diff --git a/internal/provider/resource_pet.go b/internal/provider/resource_pet.go index 1c364cd4..178ee595 100644 --- a/internal/provider/resource_pet.go +++ b/internal/provider/resource_pet.go @@ -3,11 +3,12 @@ package provider import ( "context" "fmt" + "strings" + petname "github.com/dustinkirkland/golang-petname" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" - "strings" ) type resourcePetType struct{} diff --git a/internal/provider/resource_shuffle.go b/internal/provider/resource_shuffle.go index 69dd9acf..325a194d 100644 --- a/internal/provider/resource_shuffle.go +++ b/internal/provider/resource_shuffle.go @@ -2,6 +2,7 @@ package provider import ( "context" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index fdc7bbd5..a4977e6b 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -2,6 +2,7 @@ package provider import ( "context" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" ) diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index eebd006d..04b316ac 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -2,10 +2,11 @@ package provider import ( "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "regexp" "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) type customLens struct { diff --git a/internal/provider/resource_uuid.go b/internal/provider/resource_uuid.go index 97780799..77bd8832 100644 --- a/internal/provider/resource_uuid.go +++ b/internal/provider/resource_uuid.go @@ -3,6 +3,7 @@ package provider import ( "context" "fmt" + "github.com/hashicorp/go-uuid" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" diff --git a/internal/provider/string.go b/internal/provider/string.go index 93fe43b3..7e8e0178 100644 --- a/internal/provider/string.go +++ b/internal/provider/string.go @@ -4,10 +4,11 @@ import ( "context" "crypto/rand" "fmt" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" "math/big" "sort" + + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" ) func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { diff --git a/internal/provider/validators.go b/internal/provider/validators.go index 7d5bb748..c766bbd7 100644 --- a/internal/provider/validators.go +++ b/internal/provider/validators.go @@ -3,6 +3,7 @@ package provider import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" ) From c625a9034e3f786c4c734b53b3d591c48932a5ea Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 10 May 2022 14:41:56 +0100 Subject: [PATCH 40/96] Fixing potentially unsafe type assertions (#177) --- internal/provider/plan_modifiers.go | 24 +++++++++++++++++++++--- internal/provider/validators.go | 12 +++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/internal/provider/plan_modifiers.go b/internal/provider/plan_modifiers.go index bd4cc135..20aa19f1 100644 --- a/internal/provider/plan_modifiers.go +++ b/internal/provider/plan_modifiers.go @@ -28,7 +28,13 @@ func (d boolDefault) MarkdownDescription(ctx context.Context) string { // Modify checks that the value of the attribute in the configuration and, if the attribute is Null, assigns the value // supplied to the boolDefault struct when it was initialised. func (d boolDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { - t := req.AttributeConfig.(types.Bool) + var t types.Bool + diags := tfsdk.ValueAs(ctx, req.AttributeConfig, &t) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } if t.Null { resp.AttributePlan = types.Bool{ @@ -57,7 +63,13 @@ func (d intDefault) MarkdownDescription(ctx context.Context) string { // Modify checks that the value of the attribute in the configuration and, if the attribute is Null, assigns the value // supplied to the intDefault struct when it was initialised. func (d intDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { - t := req.AttributeConfig.(types.Int64) + var t types.Int64 + diags := tfsdk.ValueAs(ctx, req.AttributeConfig, &t) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } if t.Null { resp.AttributePlan = types.Int64{ @@ -87,7 +99,13 @@ func (d stringDefault) MarkdownDescription(ctx context.Context) string { // Modify checks that the value of the attribute in the configuration and, if the attribute is Null, assigns the value // supplied to the stringDefault struct when it was initialised. func (d stringDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { - t := req.AttributeConfig.(types.String) + var t types.String + diags := tfsdk.ValueAs(ctx, req.AttributeConfig, &t) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } if t.Null { resp.AttributePlan = types.String{ diff --git a/internal/provider/validators.go b/internal/provider/validators.go index c766bbd7..0e292f08 100644 --- a/internal/provider/validators.go +++ b/internal/provider/validators.go @@ -28,12 +28,18 @@ func (m minIntValidator) MarkdownDescription(context.Context) string { // Validate checks that the value of the attribute in the configuration is greater than or, equal to the value supplied // when the minIntValidator struct was initialised. func (m minIntValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { - t := req.AttributeConfig.(types.Int64) + var t types.Int64 + diags := tfsdk.ValueAs(ctx, req.AttributeConfig, &t) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } if t.Value < m.val { resp.Diagnostics.AddError( - fmt.Sprintf("expected attribute to be at least %d, got %d", m.val, t.Value), - fmt.Sprintf("expected attribute to be at least %d, got %d", m.val, t.Value), + "Validating Min Int Error", + fmt.Sprintf("Expected attribute at %s to be at least %d, got %d", req.AttributePath.String(), m.val, t.Value), ) } } From a0daa2345fcc40220089c3d3f403cc4215737c8a Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 10 May 2022 15:37:56 +0100 Subject: [PATCH 41/96] Fixing test (#177) --- internal/provider/resource_string_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index 04b316ac..cfd22f5e 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -82,11 +82,11 @@ func TestAccResourceStringErrors(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccResourceStringInvalidConfig, - ExpectError: regexp.MustCompile(`.*length \(2\) must be >= min_upper \+ min_lower \+ min_numeric \+ min_special \(3\)`), + ExpectError: regexp.MustCompile(`.*The password/string length \(2\) must be >= min_upper \+ min_lower \+ min_numeric\n\+ min_special \(3\)`), }, { Config: testAccResourceStringLengthTooShortConfig, - ExpectError: regexp.MustCompile(`.*expected attribute to be at least 1, got 0`), + ExpectError: regexp.MustCompile(`.*Expected attribute at AttributeName\("length"\) to be at least 1, got 0`), }, }, }) From 507268036f7715308b49cda8a3fa3b71ca1a1cb7 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 12 May 2022 15:50:58 +0100 Subject: [PATCH 42/96] Bumping terraform-plugin-go v0.9.1 (#177) --- go.mod | 1 + go.sum | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/go.mod b/go.mod index cda4ad81..13c6cb52 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/hashicorp/terraform-plugin-framework v0.8.0 github.com/hashicorp/terraform-plugin-go v0.9.1 github.com/hashicorp/terraform-plugin-go v0.9.1 + github.com/hashicorp/terraform-plugin-mux v0.6.0 ) require ( diff --git a/go.sum b/go.sum index 557c2418..eb65efe2 100644 --- a/go.sum +++ b/go.sum @@ -155,9 +155,14 @@ github.com/hashicorp/terraform-plugin-docs v0.8.0/go.mod h1:MjeyK5CEI/jZbTQXq1Ay github.com/hashicorp/terraform-plugin-framework v0.8.0 h1:2nxk+5qAKlGWOrpWZbAZNkO+AoC87l4+9d/rjtQd6Wo= github.com/hashicorp/terraform-plugin-framework v0.8.0/go.mod h1:jUhqrbeI48gAleP8LXzg9jtRH07EAcpwEGQlYmKNIVg= github.com/hashicorp/terraform-plugin-go v0.8.0/go.mod h1:E3GuvfX0Pz2Azcl6BegD6t51StXsVZMOYQoGO8mkHM0= +<<<<<<< HEAD >>>>>>> 37128b5 (Configuring muxing (#177)) github.com/hashicorp/terraform-plugin-go v0.9.0 h1:FvLY/3z4SNVatPZdoFcyrlNbCar+WyyOTv5X4Tp+WZc= +======= +>>>>>>> 94dbddb (Bumping terraform-plugin-go v0.9.1 (#177)) github.com/hashicorp/terraform-plugin-go v0.9.0/go.mod h1:EawBkgjBWNf7jiKnVoyDyF39OSV+u6KUX+Y73EPj3oM= +github.com/hashicorp/terraform-plugin-go v0.9.1 h1:vXdHaQ6aqL+OF076nMSBV+JKPdmXlzG5mzVDD04WyPs= +github.com/hashicorp/terraform-plugin-go v0.9.1/go.mod h1:ItjVSlQs70otlzcCwlPcU8FRXLdO973oYFRZwAOxy8M= github.com/hashicorp/terraform-plugin-log v0.3.0/go.mod h1:EjueSP/HjlyFAsDqt+okpCPjkT4NDynAe32AeDC4vps= github.com/hashicorp/terraform-plugin-log v0.4.0 h1:F3eVnm8r2EfQCe2k9blPIiF/r2TT01SHijXnS7bujvc= github.com/hashicorp/terraform-plugin-log v0.4.0/go.mod h1:9KclxdunFownr4pIm1jdmwKRmE4d6HVG2c9XDq47rpg= From c1e1583751a0970c5dd59613e90b0213b4f6342e Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 12 May 2022 16:09:21 +0100 Subject: [PATCH 43/96] Removing muxing (#177) --- main.go | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/main.go b/main.go index 77513eac..9dc3fff8 100644 --- a/main.go +++ b/main.go @@ -2,14 +2,11 @@ package main import ( "context" + "log" + "github.com/hashicorp/terraform-plugin-framework/providerserver" - "github.com/hashicorp/terraform-plugin-go/tfprotov5" - "github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" - "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" - "github.com/hashicorp/terraform-plugin-mux/tf6to5server" + "github.com/terraform-providers/terraform-provider-random/internal/provider" - "log" ) // Run "go generate" to format example terraform files and generate the docs for the registry/website @@ -23,28 +20,10 @@ import ( //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs func main() { - ctx := context.Background() - - downgradedFrameworkProvider, err := tf6to5server.DowngradeServer(ctx, func() tfprotov6.ProviderServer { - return providerserver.NewProtocol6(provider.NewFramework())() + err := providerserver.Serve(context.Background(), provider.NewFramework, providerserver.ServeOpts{ + Address: "registry.terraform.io/hashicorp/random", }) if err != nil { log.Fatal(err) } - - providers := []func() tfprotov5.ProviderServer{ - func() tfprotov5.ProviderServer { - return downgradedFrameworkProvider - }, - } - - muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) - if err != nil { - log.Fatal(err) - } - - err = tf5server.Serve("registry.terraform.io/hashicorp/random", muxServer.ProviderServer) - if err != nil { - log.Fatal(err) - } } From 8b352f252d12618961ffe810e0e442df469f7933 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 12 May 2022 16:10:12 +0100 Subject: [PATCH 44/96] Bumping mod versions (#177) --- go.mod | 2 -- go.sum | 10 ++++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 13c6cb52..3de66b34 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,6 @@ require ( github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0 github.com/hashicorp/terraform-plugin-framework v0.8.0 github.com/hashicorp/terraform-plugin-go v0.9.1 - github.com/hashicorp/terraform-plugin-go v0.9.1 - github.com/hashicorp/terraform-plugin-mux v0.6.0 ) require ( diff --git a/go.sum b/go.sum index eb65efe2..f64b5dee 100644 --- a/go.sum +++ b/go.sum @@ -147,11 +147,16 @@ github.com/hashicorp/terraform-exec v0.16.1/go.mod h1:aj0lVshy8l+MHhFNoijNHtqTJQ github.com/hashicorp/terraform-json v0.13.0 h1:Li9L+lKD1FO5RVFRM1mMMIBDoUHslOniyEi5CM+FWGY= github.com/hashicorp/terraform-json v0.13.0/go.mod h1:y5OdLBCT+rxbwnpxZs9kGL7R9ExU76+cpdY8zHwoazk= <<<<<<< HEAD +<<<<<<< HEAD github.com/hashicorp/terraform-plugin-docs v0.8.1 h1:XJC/cDvmE7zJfDFCtOI1bURaencBQC0xYx3DZ5cWbhE= github.com/hashicorp/terraform-plugin-docs v0.8.1/go.mod h1:p40z/69HYNUN/G2RDYp8XUCA5B1VzGTZl7/N9V+BWXU= ======= github.com/hashicorp/terraform-plugin-docs v0.8.0 h1:qNuHNTEqVCT258+h1GsejtfgW6qxTBAXc6qJbzd8C3U= github.com/hashicorp/terraform-plugin-docs v0.8.0/go.mod h1:MjeyK5CEI/jZbTQXq1Ay0UwTGdqxEcQexqhHP1WcLZc= +======= +github.com/hashicorp/terraform-plugin-docs v0.8.1 h1:XJC/cDvmE7zJfDFCtOI1bURaencBQC0xYx3DZ5cWbhE= +github.com/hashicorp/terraform-plugin-docs v0.8.1/go.mod h1:p40z/69HYNUN/G2RDYp8XUCA5B1VzGTZl7/N9V+BWXU= +>>>>>>> 88ed1a0 (Bumping mod versions (#177)) github.com/hashicorp/terraform-plugin-framework v0.8.0 h1:2nxk+5qAKlGWOrpWZbAZNkO+AoC87l4+9d/rjtQd6Wo= github.com/hashicorp/terraform-plugin-framework v0.8.0/go.mod h1:jUhqrbeI48gAleP8LXzg9jtRH07EAcpwEGQlYmKNIVg= github.com/hashicorp/terraform-plugin-go v0.8.0/go.mod h1:E3GuvfX0Pz2Azcl6BegD6t51StXsVZMOYQoGO8mkHM0= @@ -172,6 +177,7 @@ github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0/go.mod h1:hLa0sTiySU/AWEgV2 ======= github.com/hashicorp/terraform-plugin-mux v0.6.0 h1:HMUdyltYurTQr9yyijnB3aj1olpNxuLfn3i7HPpupeE= github.com/hashicorp/terraform-plugin-mux v0.6.0/go.mod h1:qtg6FWnYEwwU3jMugPetmjVYKKn2VWhIPYga5fCtzd4= +<<<<<<< HEAD github.com/hashicorp/terraform-plugin-sdk/v2 v2.15.0 h1:nBRM7JBvaYDV8bJjtBUyDZZrprr7UWur1/P8j4vgvqY= github.com/hashicorp/terraform-plugin-sdk/v2 v2.15.0/go.mod h1:oCj7EsihOUat3iY2WNFQzH8OLThqjmQ6tJxsmD7Ay9U= <<<<<<< HEAD @@ -179,6 +185,10 @@ github.com/hashicorp/terraform-plugin-sdk/v2 v2.15.0/go.mod h1:oCj7EsihOUat3iY2W github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 h1:1FGtlkJw87UsTMg5s8jrekrHmUPUJaMcu6ELiVhQrNw= ======= >>>>>>> 828da2b (Bumping framework version (#177)) +======= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0 h1:9fjPgCenJqnbjo95SDcbJ+YdLyEC1N35cwKWcRWhJTQ= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0/go.mod h1:hLa0sTiySU/AWEgV2GxJh0/pQIqcCmm30IPja9N9lTg= +>>>>>>> 88ed1a0 (Bumping mod versions (#177)) github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896/go.mod h1:bzBPnUIkI0RxauU8Dqo+2KrZZ28Cf48s8V6IHt3p4co= github.com/hashicorp/terraform-registry-address v0.0.0-20220422185603-6772e136ec01 h1:HgJRxDmThXP6Jdjv2pHufKfESG23Y3x8VtXgmtMDsq8= github.com/hashicorp/terraform-registry-address v0.0.0-20220422185603-6772e136ec01/go.mod h1:bdLC+qQlJIBHKbCMA6GipcuaKjmjcvZlnVdpU583z3Y= From ff031c35293bf5bdb444eb87f5d26fb5f70951e1 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 12 May 2022 17:03:00 +0100 Subject: [PATCH 45/96] Removing muxing from tests (#177) --- go.mod | 2 +- go.sum | 32 ---------------------- internal/provider/provider_test.go | 19 +++---------- internal/provider/resource_id_test.go | 4 +-- internal/provider/resource_integer_test.go | 10 +++---- internal/provider/resource_pasword_test.go | 6 ++-- internal/provider/resource_pet_test.go | 8 +++--- internal/provider/resource_shuffle_test.go | 10 +++---- internal/provider/resource_string_test.go | 8 +++--- internal/provider/resource_uuid_test.go | 2 +- 10 files changed, 29 insertions(+), 72 deletions(-) diff --git a/go.mod b/go.mod index 3de66b34..8037c82e 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,9 @@ require ( github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/terraform-plugin-docs v0.8.1 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0 github.com/hashicorp/terraform-plugin-framework v0.8.0 github.com/hashicorp/terraform-plugin-go v0.9.1 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0 ) require ( diff --git a/go.sum b/go.sum index f64b5dee..da37afd0 100644 --- a/go.sum +++ b/go.sum @@ -128,7 +128,6 @@ github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ3 github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -146,49 +145,18 @@ github.com/hashicorp/terraform-exec v0.16.1 h1:NAwZFJW2L2SaCBVZoVaH8LPImLOGbPLkS github.com/hashicorp/terraform-exec v0.16.1/go.mod h1:aj0lVshy8l+MHhFNoijNHtqTJQI3Xlowv5EOsEaGO7M= github.com/hashicorp/terraform-json v0.13.0 h1:Li9L+lKD1FO5RVFRM1mMMIBDoUHslOniyEi5CM+FWGY= github.com/hashicorp/terraform-json v0.13.0/go.mod h1:y5OdLBCT+rxbwnpxZs9kGL7R9ExU76+cpdY8zHwoazk= -<<<<<<< HEAD -<<<<<<< HEAD github.com/hashicorp/terraform-plugin-docs v0.8.1 h1:XJC/cDvmE7zJfDFCtOI1bURaencBQC0xYx3DZ5cWbhE= github.com/hashicorp/terraform-plugin-docs v0.8.1/go.mod h1:p40z/69HYNUN/G2RDYp8XUCA5B1VzGTZl7/N9V+BWXU= -======= -github.com/hashicorp/terraform-plugin-docs v0.8.0 h1:qNuHNTEqVCT258+h1GsejtfgW6qxTBAXc6qJbzd8C3U= -github.com/hashicorp/terraform-plugin-docs v0.8.0/go.mod h1:MjeyK5CEI/jZbTQXq1Ay0UwTGdqxEcQexqhHP1WcLZc= -======= -github.com/hashicorp/terraform-plugin-docs v0.8.1 h1:XJC/cDvmE7zJfDFCtOI1bURaencBQC0xYx3DZ5cWbhE= -github.com/hashicorp/terraform-plugin-docs v0.8.1/go.mod h1:p40z/69HYNUN/G2RDYp8XUCA5B1VzGTZl7/N9V+BWXU= ->>>>>>> 88ed1a0 (Bumping mod versions (#177)) github.com/hashicorp/terraform-plugin-framework v0.8.0 h1:2nxk+5qAKlGWOrpWZbAZNkO+AoC87l4+9d/rjtQd6Wo= github.com/hashicorp/terraform-plugin-framework v0.8.0/go.mod h1:jUhqrbeI48gAleP8LXzg9jtRH07EAcpwEGQlYmKNIVg= -github.com/hashicorp/terraform-plugin-go v0.8.0/go.mod h1:E3GuvfX0Pz2Azcl6BegD6t51StXsVZMOYQoGO8mkHM0= -<<<<<<< HEAD ->>>>>>> 37128b5 (Configuring muxing (#177)) -github.com/hashicorp/terraform-plugin-go v0.9.0 h1:FvLY/3z4SNVatPZdoFcyrlNbCar+WyyOTv5X4Tp+WZc= -======= ->>>>>>> 94dbddb (Bumping terraform-plugin-go v0.9.1 (#177)) github.com/hashicorp/terraform-plugin-go v0.9.0/go.mod h1:EawBkgjBWNf7jiKnVoyDyF39OSV+u6KUX+Y73EPj3oM= github.com/hashicorp/terraform-plugin-go v0.9.1 h1:vXdHaQ6aqL+OF076nMSBV+JKPdmXlzG5mzVDD04WyPs= github.com/hashicorp/terraform-plugin-go v0.9.1/go.mod h1:ItjVSlQs70otlzcCwlPcU8FRXLdO973oYFRZwAOxy8M= github.com/hashicorp/terraform-plugin-log v0.3.0/go.mod h1:EjueSP/HjlyFAsDqt+okpCPjkT4NDynAe32AeDC4vps= github.com/hashicorp/terraform-plugin-log v0.4.0 h1:F3eVnm8r2EfQCe2k9blPIiF/r2TT01SHijXnS7bujvc= github.com/hashicorp/terraform-plugin-log v0.4.0/go.mod h1:9KclxdunFownr4pIm1jdmwKRmE4d6HVG2c9XDq47rpg= -<<<<<<< HEAD -github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0 h1:9fjPgCenJqnbjo95SDcbJ+YdLyEC1N35cwKWcRWhJTQ= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0/go.mod h1:hLa0sTiySU/AWEgV2GxJh0/pQIqcCmm30IPja9N9lTg= -======= -github.com/hashicorp/terraform-plugin-mux v0.6.0 h1:HMUdyltYurTQr9yyijnB3aj1olpNxuLfn3i7HPpupeE= -github.com/hashicorp/terraform-plugin-mux v0.6.0/go.mod h1:qtg6FWnYEwwU3jMugPetmjVYKKn2VWhIPYga5fCtzd4= -<<<<<<< HEAD -github.com/hashicorp/terraform-plugin-sdk/v2 v2.15.0 h1:nBRM7JBvaYDV8bJjtBUyDZZrprr7UWur1/P8j4vgvqY= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.15.0/go.mod h1:oCj7EsihOUat3iY2WNFQzH8OLThqjmQ6tJxsmD7Ay9U= -<<<<<<< HEAD ->>>>>>> 37128b5 (Configuring muxing (#177)) -github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 h1:1FGtlkJw87UsTMg5s8jrekrHmUPUJaMcu6ELiVhQrNw= -======= ->>>>>>> 828da2b (Bumping framework version (#177)) -======= github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0 h1:9fjPgCenJqnbjo95SDcbJ+YdLyEC1N35cwKWcRWhJTQ= github.com/hashicorp/terraform-plugin-sdk/v2 v2.16.0/go.mod h1:hLa0sTiySU/AWEgV2GxJh0/pQIqcCmm30IPja9N9lTg= ->>>>>>> 88ed1a0 (Bumping mod versions (#177)) github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896/go.mod h1:bzBPnUIkI0RxauU8Dqo+2KrZZ28Cf48s8V6IHt3p4co= github.com/hashicorp/terraform-registry-address v0.0.0-20220422185603-6772e136ec01 h1:HgJRxDmThXP6Jdjv2pHufKfESG23Y3x8VtXgmtMDsq8= github.com/hashicorp/terraform-registry-address v0.0.0-20220422185603-6772e136ec01/go.mod h1:bdLC+qQlJIBHKbCMA6GipcuaKjmjcvZlnVdpU583z3Y= diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index a74a8451..bafa1be2 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -1,31 +1,20 @@ package provider import ( - "context" - "log" "testing" "github.com/hashicorp/terraform-plugin-framework/providerserver" - "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-go/tfprotov6" - "github.com/hashicorp/terraform-plugin-mux/tf6to5server" ) func testAccPreCheck(t *testing.T) { } //nolint:unparam -func testAccProtoV5ProviderFactories() map[string]func() (tfprotov5.ProviderServer, error) { - downgradedFrameworkProvider, err := tf6to5server.DowngradeServer(context.Background(), func() tfprotov6.ProviderServer { - return providerserver.NewProtocol6(NewFramework())() - }) - if err != nil { - log.Fatal(err) - } - - return map[string]func() (tfprotov5.ProviderServer, error){ - "random": func() (tfprotov5.ProviderServer, error) { - return downgradedFrameworkProvider, nil +func testAccProtoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { + return map[string]func() (tfprotov6.ProviderServer, error){ + "random": func() (tfprotov6.ProviderServer, error) { + return providerserver.NewProtocol6(NewFramework())(), nil }, } } diff --git a/internal/provider/resource_id_test.go b/internal/provider/resource_id_test.go index ed208fbb..7caf073c 100644 --- a/internal/provider/resource_id_test.go +++ b/internal/provider/resource_id_test.go @@ -17,7 +17,7 @@ type idLens struct { func TestAccResourceID(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceIDConfig, @@ -41,7 +41,7 @@ func TestAccResourceID(t *testing.T) { func TestAccResourceID_importWithPrefix(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceIDConfigWithPrefix, diff --git a/internal/provider/resource_integer_test.go b/internal/provider/resource_integer_test.go index e31908a8..49d0295a 100644 --- a/internal/provider/resource_integer_test.go +++ b/internal/provider/resource_integer_test.go @@ -12,7 +12,7 @@ func TestAccResourceIntegerBasic(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testRandomIntegerBasic, @@ -34,7 +34,7 @@ func TestAccResourceIntegerUpdate(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testRandomIntegerBasic, @@ -56,7 +56,7 @@ func TestAccResourceIntegerSeedless_to_seeded(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testRandomIntegerSeedless, @@ -78,7 +78,7 @@ func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testRandomIntegerBasic, @@ -100,7 +100,7 @@ func TestAccResourceIntegerBig(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testRandomIntegerBig, diff --git a/internal/provider/resource_pasword_test.go b/internal/provider/resource_pasword_test.go index 2b393c43..3b9376a0 100644 --- a/internal/provider/resource_pasword_test.go +++ b/internal/provider/resource_pasword_test.go @@ -12,7 +12,7 @@ import ( func TestAccResourcePasswordBasic(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePasswordBasic, @@ -50,7 +50,7 @@ func TestAccResourcePasswordBasic(t *testing.T) { func TestAccResourcePasswordOverride(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePasswordOverride, @@ -68,7 +68,7 @@ func TestAccResourcePasswordOverride(t *testing.T) { func TestAccResourcePasswordMin(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePasswordMin, diff --git a/internal/provider/resource_pet_test.go b/internal/provider/resource_pet_test.go index 34903579..f0e2b3e8 100644 --- a/internal/provider/resource_pet_test.go +++ b/internal/provider/resource_pet_test.go @@ -13,7 +13,7 @@ import ( func TestAccResourcePet_basic(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePetBasic, @@ -28,7 +28,7 @@ func TestAccResourcePet_basic(t *testing.T) { func TestAccResourcePet_length(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePetLengthSet, @@ -43,7 +43,7 @@ func TestAccResourcePet_length(t *testing.T) { func TestAccResourcePet_prefix(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePetPrefix, @@ -60,7 +60,7 @@ func TestAccResourcePet_prefix(t *testing.T) { func TestAccResourcePet_separator(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourcePetSeparator, diff --git a/internal/provider/resource_shuffle_test.go b/internal/provider/resource_shuffle_test.go index 3f67909a..14899cf5 100644 --- a/internal/provider/resource_shuffle_test.go +++ b/internal/provider/resource_shuffle_test.go @@ -21,7 +21,7 @@ import ( func TestAccResourceShuffleDefault(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigDefault, @@ -39,7 +39,7 @@ func TestAccResourceShuffleDefault(t *testing.T) { func TestAccResourceShuffleShorter(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigShorter, @@ -57,7 +57,7 @@ func TestAccResourceShuffleShorter(t *testing.T) { func TestAccResourceShuffleLonger(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigLonger, @@ -75,7 +75,7 @@ func TestAccResourceShuffleLonger(t *testing.T) { func TestAccResourceShuffleEmpty(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigEmpty, @@ -93,7 +93,7 @@ func TestAccResourceShuffleEmpty(t *testing.T) { func TestAccResourceShuffleOne(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceShuffleConfigOne, diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index cfd22f5e..8c0a4e42 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -16,7 +16,7 @@ type customLens struct { func TestAccResourceString(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceStringBasic, @@ -39,7 +39,7 @@ func TestAccResourceString(t *testing.T) { func TestAccResourceStringOverride(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceStringOverride, @@ -57,7 +57,7 @@ func TestAccResourceStringOverride(t *testing.T) { func TestAccResourceStringMin(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceStringMin, @@ -78,7 +78,7 @@ func TestAccResourceStringMin(t *testing.T) { func TestAccResourceStringErrors(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceStringInvalidConfig, diff --git a/internal/provider/resource_uuid_test.go b/internal/provider/resource_uuid_test.go index 9a38fc18..8ddfa1cd 100644 --- a/internal/provider/resource_uuid_test.go +++ b/internal/provider/resource_uuid_test.go @@ -10,7 +10,7 @@ import ( func TestAccResourceUUID(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: testAccResourceUUIDConfig, From 6daa0cc791a1fe454c2b2a6df5c2528b766f1361 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 12 May 2022 17:10:24 +0100 Subject: [PATCH 46/96] Removing 0.12 - 0.14 from tests (#177) --- .github/workflows/test.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 37cd4688..0e5325ff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,9 +59,6 @@ jobs: - windows-latest - ubuntu-latest terraform: - - '0.12.*' - - '0.13.*' - - '0.14.*' - '0.15.*' - '1.0.*' - '1.1.*' From fa824dc579780db71f2b518d62b8ae3b032c042c Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 13 May 2022 09:27:08 +0100 Subject: [PATCH 47/96] Remove testing of TF v0.15 from test matrix for CI (#177) --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0e5325ff..8772ef34 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,6 @@ jobs: - windows-latest - ubuntu-latest terraform: - - '0.15.*' - '1.0.*' - '1.1.*' steps: From 0801515e76b6486bdb5b25ae94990c99bf1cffc3 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 13 May 2022 17:57:04 +0100 Subject: [PATCH 48/96] Adding UpgradeState for random password bcrypt_hash field --- internal/provider/diagnostics.go | 13 ++ internal/provider/models.go | 21 +++ internal/provider/resource_password.go | 50 ++++- internal/provider/resource_pasword_test.go | 2 +- internal/provider/resource_string.go | 4 +- internal/provider/string.go | 201 +++++++++++++++------ 6 files changed, 230 insertions(+), 61 deletions(-) diff --git a/internal/provider/diagnostics.go b/internal/provider/diagnostics.go index c2cb135e..e4cb5532 100644 --- a/internal/provider/diagnostics.go +++ b/internal/provider/diagnostics.go @@ -20,3 +20,16 @@ func randomReadError(errMsg string) diag.Diagnostics { return diags } + +func hashGenerationError(errMsg string) diag.Diagnostics { + var diags diag.Diagnostics + + diags.AddError( + "Hash Generation Error", + "While attempting to generate a hash from of the password an error occurred.\n\n"+ + "Verify that the state contains a populated 'result' field and retry the operation\n\n"+ + fmt.Sprintf("Original Error: %s", errMsg), + ) + + return diags +} diff --git a/internal/provider/models.go b/internal/provider/models.go index 2ad8e552..1e22de94 100644 --- a/internal/provider/models.go +++ b/internal/provider/models.go @@ -22,6 +22,27 @@ type IntegerModel struct { Result types.Int64 `tfsdk:"result"` } +type PasswordModel struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Number types.Bool `tfsdk:"number"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Result types.String `tfsdk:"result"` + BcryptHash types.String `tfsdk:"bcrypt_hash"` +} + +func (pm PasswordModel) LengthValue() int64 { + return pm.Length.Value +} + type PetNameModel struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index ecf789b5..c0e9e9e8 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -5,6 +5,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "golang.org/x/crypto/bcrypt" ) type resourcePasswordType struct{} @@ -15,7 +17,17 @@ func (r resourcePasswordType) GetSchema(context.Context) (tfsdk.Schema, diag.Dia "data handling in the [Terraform documentation](https://www.terraform.io/docs/language/state/sensitive-data.html).\n" + "\n" + "This resource *does* use a cryptographic random number generator." - return getStringSchemaV1(true, description), nil + + schema := getStringSchemaV1(true, description) + schema.Version = 1 + schema.Attributes["bcrypt_hash"] = tfsdk.Attribute{ + Description: "A bcrypt hash of the generated random string.", + Type: types.StringType, + Computed: true, + Sensitive: true, + } + + return schema, nil } func (r resourcePasswordType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { @@ -29,7 +41,7 @@ type resourcePassword struct { } func (r resourcePassword) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - createString(ctx, req, resp, true) + createPassword(ctx, req, resp) } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. @@ -47,9 +59,41 @@ func (r resourcePassword) Delete(ctx context.Context, req tfsdk.DeleteResourceRe } func (r resourcePassword) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - importString(ctx, req, resp, true) + importPassword(ctx, req, resp) } func (r resourcePassword) ValidateConfig(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { validateLength(ctx, req, resp) } + +func (r resourcePassword) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { + return map[int64]tfsdk.ResourceStateUpgrader{ + 0: { + StateUpgrader: migratePasswordStateV0toV1, + }, + } +} + +func migratePasswordStateV0toV1(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { + p := PasswordModel{} + req.State.Get(ctx, &p) + + hash, err := generateHash(p.Result.Value) + if err != nil { + resp.Diagnostics.Append(hashGenerationError(err.Error())...) + return + } + + p.BcryptHash = types.String{Value: hash} + + resp.State.Set(ctx, p) + if resp.Diagnostics.HasError() { + return + } +} + +func generateHash(toHash string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(toHash), bcrypt.DefaultCost) + + return string(hash), err +} diff --git a/internal/provider/resource_pasword_test.go b/internal/provider/resource_pasword_test.go index 3b9376a0..9cb277d4 100644 --- a/internal/provider/resource_pasword_test.go +++ b/internal/provider/resource_pasword_test.go @@ -41,7 +41,7 @@ func TestAccResourcePasswordBasic(t *testing.T) { }, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"length", "lower", "number", "special", "upper", "min_lower", "min_numeric", "min_special", "min_upper", "override_special"}, + ImportStateVerifyIgnore: []string{"bcrypt_hash", "length", "lower", "number", "special", "upper", "min_lower", "min_numeric", "min_special", "min_upper", "override_special"}, }, }, }) diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index a4977e6b..9e838c55 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -36,7 +36,7 @@ type resourceString struct { } func (r resourceString) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - createString(ctx, req, resp, false) + createString(ctx, req, resp) } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. @@ -54,7 +54,7 @@ func (r resourceString) Delete(ctx context.Context, req tfsdk.DeleteResourceRequ } func (r resourceString) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - importString(ctx, req, resp, false) + importString(ctx, req, resp) } func (r resourceString) ValidateConfig(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { diff --git a/internal/provider/string.go b/internal/provider/string.go index 7e8e0178..134c05ff 100644 --- a/internal/provider/string.go +++ b/internal/provider/string.go @@ -155,105 +155,183 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { } } -func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse, sensitive bool) { +type randomStringParams struct { + length int64 + upper bool + minUpper int64 + lower bool + minLower int64 + number bool + minNumeric int64 + special bool + minSpecial int64 + overrideSpecial string +} + +func createRandomString(input randomStringParams) ([]byte, error) { const numChars = "0123456789" const lowerChars = "abcdefghijklmnopqrstuvwxyz" const upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" var specialChars = "!@#$%&*()-_=+[]{}<>:?" - var plan StringModel - - diags := req.Plan.Get(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } + var result []byte - length := plan.Length.Value - upper := plan.Upper.Value - minUpper := plan.MinUpper.Value - lower := plan.Lower.Value - minLower := plan.MinLower.Value - number := plan.Number.Value - minNumeric := plan.MinNumeric.Value - special := plan.Special.Value - minSpecial := plan.MinSpecial.Value - overrideSpecial := plan.OverrideSpecial.Value - - if overrideSpecial != "" { - specialChars = overrideSpecial + if input.overrideSpecial != "" { + specialChars = input.overrideSpecial } var chars = string("") - if upper { + if input.upper { chars += upperChars } - if lower { + if input.lower { chars += lowerChars } - if number { + if input.number { chars += numChars } - if special { + if input.special { chars += specialChars } minMapping := map[string]int64{ - numChars: minNumeric, - lowerChars: minLower, - upperChars: minUpper, - specialChars: minSpecial, + numChars: input.minNumeric, + lowerChars: input.minLower, + upperChars: input.minUpper, + specialChars: input.minSpecial, } - var result = make([]byte, 0, length) + result = make([]byte, 0, input.length) for k, v := range minMapping { s, err := generateRandomBytes(&k, v) if err != nil { - resp.Diagnostics.Append(randomReadError(err.Error())...) - return + return nil, err } result = append(result, s...) } - s, err := generateRandomBytes(&chars, length-int64(len(result))) + s, err := generateRandomBytes(&chars, input.length-int64(len(result))) if err != nil { - resp.Diagnostics.Append(randomReadError(err.Error())...) - return + return nil, err } result = append(result, s...) order := make([]byte, len(result)) if _, err := rand.Read(order); err != nil { - resp.Diagnostics.Append(randomReadError(err.Error())...) - return + return nil, err } sort.Slice(result, func(i, j int) bool { return order[i] < order[j] }) - str := StringModel{ - ID: types.String{Value: string(result)}, + return result, nil +} + +func createPassword(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan PasswordModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + params := randomStringParams{ + length: plan.Length.Value, + upper: plan.Upper.Value, + minUpper: plan.MinUpper.Value, + lower: plan.Lower.Value, + minLower: plan.MinLower.Value, + number: plan.Number.Value, + minNumeric: plan.MinNumeric.Value, + special: plan.Special.Value, + minSpecial: plan.MinSpecial.Value, + overrideSpecial: plan.OverrideSpecial.Value, + } + + result, err := createRandomString(params) + if err != nil { + resp.Diagnostics.Append(randomReadError(err.Error())...) + return + } + + state := PasswordModel{ + ID: types.String{Value: "none"}, Keepers: plan.Keepers, - Length: types.Int64{Value: length}, - Special: types.Bool{Value: special}, - Upper: types.Bool{Value: upper}, - Lower: types.Bool{Value: lower}, - Number: types.Bool{Value: number}, - MinNumeric: types.Int64{Value: minNumeric}, - MinUpper: types.Int64{Value: minUpper}, - MinLower: types.Int64{Value: minLower}, - MinSpecial: types.Int64{Value: minSpecial}, - OverrideSpecial: types.String{Value: overrideSpecial}, + Length: types.Int64{Value: plan.Length.Value}, + Special: types.Bool{Value: plan.Special.Value}, + Upper: types.Bool{Value: plan.Upper.Value}, + Lower: types.Bool{Value: plan.Lower.Value}, + Number: types.Bool{Value: plan.Number.Value}, + MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, + MinUpper: types.Int64{Value: plan.MinUpper.Value}, + MinLower: types.Int64{Value: plan.MinLower.Value}, + MinSpecial: types.Int64{Value: plan.MinSpecial.Value}, + OverrideSpecial: types.String{Value: plan.OverrideSpecial.Value}, Result: types.String{Value: string(result)}, } - if sensitive { - str.ID.Value = "none" + hash, err := generateHash(plan.Result.Value) + if err != nil { + resp.Diagnostics.Append(hashGenerationError(err.Error())...) + } + + state.BcryptHash = types.String{Value: hash} + + diags = resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } +} + +func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan StringModel - diags = resp.State.Set(ctx, str) + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + params := randomStringParams{ + length: plan.Length.Value, + upper: plan.Upper.Value, + minUpper: plan.MinUpper.Value, + lower: plan.Lower.Value, + minLower: plan.MinLower.Value, + number: plan.Number.Value, + minNumeric: plan.MinNumeric.Value, + special: plan.Special.Value, + minSpecial: plan.MinSpecial.Value, + overrideSpecial: plan.OverrideSpecial.Value, + } + + result, err := createRandomString(params) + if err != nil { + resp.Diagnostics.Append(randomReadError(err.Error())...) + return + } + + state := StringModel{ + ID: types.String{Value: string(result)}, + Keepers: plan.Keepers, + Length: types.Int64{Value: plan.Length.Value}, + Special: types.Bool{Value: plan.Special.Value}, + Upper: types.Bool{Value: plan.Upper.Value}, + Lower: types.Bool{Value: plan.Lower.Value}, + Number: types.Bool{Value: plan.Number.Value}, + MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, + MinUpper: types.Int64{Value: plan.MinUpper.Value}, + MinLower: types.Int64{Value: plan.MinLower.Value}, + MinSpecial: types.Int64{Value: plan.MinSpecial.Value}, + OverrideSpecial: types.String{Value: plan.OverrideSpecial.Value}, + Result: types.String{Value: string(result)}, + } + + diags = resp.State.Set(ctx, state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -273,7 +351,7 @@ func generateRandomBytes(charSet *string, length int64) ([]byte, error) { return bytes, nil } -func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse, sensitive bool) { +func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { id := req.ID state := StringModel{ @@ -283,9 +361,22 @@ func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, res state.Keepers.ElemType = types.StringType - if sensitive { - state.ID.Value = "none" + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } +} + +func importPassword(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + id := req.ID + + state := PasswordModel{ + ID: types.String{Value: "none"}, + Result: types.String{Value: id}, + } + + state.Keepers.ElemType = types.StringType diags := resp.State.Set(ctx, &state) resp.Diagnostics.Append(diags...) From abee8d5affab53b7c3c47a5e6256f6d0a66bcbc5 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 13 May 2022 18:00:44 +0100 Subject: [PATCH 49/96] Removing unused func --- internal/provider/models.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/provider/models.go b/internal/provider/models.go index 1e22de94..820a1ee9 100644 --- a/internal/provider/models.go +++ b/internal/provider/models.go @@ -39,10 +39,6 @@ type PasswordModel struct { BcryptHash types.String `tfsdk:"bcrypt_hash"` } -func (pm PasswordModel) LengthValue() int64 { - return pm.Length.Value -} - type PetNameModel struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` From 56fe4647c57ddb68106c1e65d4faa762cdce7e8b Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 16 May 2022 12:15:03 +0100 Subject: [PATCH 50/96] Adding suggestion to use terraform state show --- internal/provider/diagnostics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/diagnostics.go b/internal/provider/diagnostics.go index e4cb5532..ef87d2eb 100644 --- a/internal/provider/diagnostics.go +++ b/internal/provider/diagnostics.go @@ -27,7 +27,7 @@ func hashGenerationError(errMsg string) diag.Diagnostics { diags.AddError( "Hash Generation Error", "While attempting to generate a hash from of the password an error occurred.\n\n"+ - "Verify that the state contains a populated 'result' field and retry the operation\n\n"+ + "Verify that the state contains a populated 'result' field, using 'terraform state show', and retry the operation\n\n"+ fmt.Sprintf("Original Error: %s", errMsg), ) From 28233d06f002661d6cf3626a28d7333f3eeb88fd Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 16 May 2022 12:17:17 +0100 Subject: [PATCH 51/96] Refactoring to use appropriate schema version name --- internal/provider/resource_password.go | 130 +++++++++++++++++--- internal/provider/resource_string.go | 103 ++++++++++++++-- internal/provider/string.go | 160 +------------------------ 3 files changed, 210 insertions(+), 183 deletions(-) diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index c0e9e9e8..703e29fe 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -12,22 +12,7 @@ import ( type resourcePasswordType struct{} func (r resourcePasswordType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { - description := "Identical to [random_string](string.html) with the exception that the result is " + - "treated as sensitive and, thus, _not_ displayed in console output. Read more about sensitive " + - "data handling in the [Terraform documentation](https://www.terraform.io/docs/language/state/sensitive-data.html).\n" + - "\n" + - "This resource *does* use a cryptographic random number generator." - - schema := getStringSchemaV1(true, description) - schema.Version = 1 - schema.Attributes["bcrypt_hash"] = tfsdk.Attribute{ - Description: "A bcrypt hash of the generated random string.", - Type: types.StringType, - Computed: true, - Sensitive: true, - } - - return schema, nil + return getPasswordSchemaV1(), nil } func (r resourcePasswordType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { @@ -74,6 +59,119 @@ func (r resourcePassword) UpgradeState(context.Context) map[int64]tfsdk.Resource } } +func getPasswordSchemaV1() tfsdk.Schema { + passwordSchema := passwordStringSchema() + + passwordSchema.Description = "Identical to [random_string](string.html) with the exception that the result is " + + "treated as sensitive and, thus, _not_ displayed in console output. Read more about sensitive " + + "data handling in the " + + "[Terraform documentation](https://www.terraform.io/docs/language/state/sensitive-data.html).\n\n" + + "This resource *does* use a cryptographic random number generator." + + id, ok := passwordSchema.Attributes["id"] + if ok { + id.Description = "A static value used internally by Terraform, this should not be referenced in configurations." + } + + result, ok := passwordSchema.Attributes["result"] + if ok { + result.Sensitive = true + } + + passwordSchema.Attributes["bcrypt_hash"] = tfsdk.Attribute{ + Description: "A bcrypt hash of the generated random string.", + Type: types.StringType, + Computed: true, + Sensitive: true, + } + + passwordSchema.Version = 1 + + return passwordSchema +} + +func createPassword(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan PasswordModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + params := randomStringParams{ + length: plan.Length.Value, + upper: plan.Upper.Value, + minUpper: plan.MinUpper.Value, + lower: plan.Lower.Value, + minLower: plan.MinLower.Value, + number: plan.Number.Value, + minNumeric: plan.MinNumeric.Value, + special: plan.Special.Value, + minSpecial: plan.MinSpecial.Value, + overrideSpecial: plan.OverrideSpecial.Value, + } + + result, err := createRandomString(params) + if err != nil { + resp.Diagnostics.Append(randomReadError(err.Error())...) + return + } + + state := PasswordModel{ + ID: types.String{Value: "none"}, + Keepers: plan.Keepers, + Length: types.Int64{Value: plan.Length.Value}, + Special: types.Bool{Value: plan.Special.Value}, + Upper: types.Bool{Value: plan.Upper.Value}, + Lower: types.Bool{Value: plan.Lower.Value}, + Number: types.Bool{Value: plan.Number.Value}, + MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, + MinUpper: types.Int64{Value: plan.MinUpper.Value}, + MinLower: types.Int64{Value: plan.MinLower.Value}, + MinSpecial: types.Int64{Value: plan.MinSpecial.Value}, + OverrideSpecial: types.String{Value: plan.OverrideSpecial.Value}, + Result: types.String{Value: string(result)}, + } + + hash, err := generateHash(plan.Result.Value) + if err != nil { + resp.Diagnostics.Append(hashGenerationError(err.Error())...) + } + + state.BcryptHash = types.String{Value: hash} + + diags = resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func importPassword(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + id := req.ID + + state := PasswordModel{ + ID: types.String{Value: "none"}, + Result: types.String{Value: id}, + } + + state.Keepers.ElemType = types.StringType + + hash, err := generateHash(id) + if err != nil { + resp.Diagnostics.Append(hashGenerationError(err.Error())...) + } + + state.BcryptHash = types.String{Value: hash} + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + func migratePasswordStateV0toV1(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { p := PasswordModel{} req.State.Get(ctx, &p) diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index 9e838c55..9e28c7d8 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -5,24 +5,13 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" ) type resourceStringType struct{} func (r resourceStringType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { - description := "The resource `random_string` generates a random permutation of alphanumeric " + - "characters and optionally special characters.\n" + - "\n" + - "This resource *does* use a cryptographic random number generator.\n" + - "\n" + - "Historically this resource's intended usage has been ambiguous as the original example used " + - "it in a password. For backwards compatibility it will continue to exist. For unique ids please " + - "use [random_id](id.html), for sensitive random values please use [random_password](password.html)." - - schema := getStringSchemaV1(false, description) - schema.Version = 1 - - return schema, nil + return getStringSchemaV1(), nil } func (r resourceStringType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { @@ -60,3 +49,91 @@ func (r resourceString) ImportState(ctx context.Context, req tfsdk.ImportResourc func (r resourceString) ValidateConfig(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { validateLength(ctx, req, resp) } + +func getStringSchemaV1() tfsdk.Schema { + stringSchema := passwordStringSchema() + + stringSchema.Description = "The resource `random_string` generates a random permutation of alphanumeric " + + "characters and optionally special characters.\n" + + "\n" + + "This resource *does* use a cryptographic random number generator.\n" + + "\n" + + "Historically this resource's intended usage has been ambiguous as the original example used " + + "it in a password. For backwards compatibility it will continue to exist. For unique ids please " + + "use [random_id](id.html), for sensitive random values please use [random_password](password.html)." + + id, ok := stringSchema.Attributes["id"] + if ok { + id.Description = "The generated random string." + } + + return stringSchema +} + +func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan StringModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + params := randomStringParams{ + length: plan.Length.Value, + upper: plan.Upper.Value, + minUpper: plan.MinUpper.Value, + lower: plan.Lower.Value, + minLower: plan.MinLower.Value, + number: plan.Number.Value, + minNumeric: plan.MinNumeric.Value, + special: plan.Special.Value, + minSpecial: plan.MinSpecial.Value, + overrideSpecial: plan.OverrideSpecial.Value, + } + + result, err := createRandomString(params) + if err != nil { + resp.Diagnostics.Append(randomReadError(err.Error())...) + return + } + + state := StringModel{ + ID: types.String{Value: string(result)}, + Keepers: plan.Keepers, + Length: types.Int64{Value: plan.Length.Value}, + Special: types.Bool{Value: plan.Special.Value}, + Upper: types.Bool{Value: plan.Upper.Value}, + Lower: types.Bool{Value: plan.Lower.Value}, + Number: types.Bool{Value: plan.Number.Value}, + MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, + MinUpper: types.Int64{Value: plan.MinUpper.Value}, + MinLower: types.Int64{Value: plan.MinLower.Value}, + MinSpecial: types.Int64{Value: plan.MinSpecial.Value}, + OverrideSpecial: types.String{Value: plan.OverrideSpecial.Value}, + Result: types.String{Value: string(result)}, + } + + diags = resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + id := req.ID + + state := StringModel{ + ID: types.String{Value: id}, + Result: types.String{Value: id}, + } + + state.Keepers.ElemType = types.StringType + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/internal/provider/string.go b/internal/provider/string.go index 134c05ff..f328a787 100644 --- a/internal/provider/string.go +++ b/internal/provider/string.go @@ -11,14 +11,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { - idDesc := "The generated random string." - if sensitive { - idDesc = "A static value used internally by Terraform, this should not be referenced in configurations." - } - +// passwordStringSchema contains the common set of Attributes for both password and string resources. +// Specific Schema descriptions, result sensitive, id descriptions and additional attributes (e.g., bcrypt_hash) +// are added in getStringSchemaV1 and getPasswordSchemaV1. +func passwordStringSchema() tfsdk.Schema { return tfsdk.Schema{ - Description: description, Attributes: map[string]tfsdk.Attribute{ "keepers": { Description: "Arbitrary map of values that, when changed, will trigger recreation of " + @@ -143,13 +140,11 @@ func getStringSchemaV1(sensitive bool, description string) tfsdk.Schema { Description: "The generated random string.", Type: types.StringType, Computed: true, - Sensitive: sensitive, }, "id": { - Description: idDesc, - Computed: true, - Type: types.StringType, + Computed: true, + Type: types.StringType, }, }, } @@ -229,115 +224,6 @@ func createRandomString(input randomStringParams) ([]byte, error) { return result, nil } -func createPassword(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan PasswordModel - - diags := req.Plan.Get(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - params := randomStringParams{ - length: plan.Length.Value, - upper: plan.Upper.Value, - minUpper: plan.MinUpper.Value, - lower: plan.Lower.Value, - minLower: plan.MinLower.Value, - number: plan.Number.Value, - minNumeric: plan.MinNumeric.Value, - special: plan.Special.Value, - minSpecial: plan.MinSpecial.Value, - overrideSpecial: plan.OverrideSpecial.Value, - } - - result, err := createRandomString(params) - if err != nil { - resp.Diagnostics.Append(randomReadError(err.Error())...) - return - } - - state := PasswordModel{ - ID: types.String{Value: "none"}, - Keepers: plan.Keepers, - Length: types.Int64{Value: plan.Length.Value}, - Special: types.Bool{Value: plan.Special.Value}, - Upper: types.Bool{Value: plan.Upper.Value}, - Lower: types.Bool{Value: plan.Lower.Value}, - Number: types.Bool{Value: plan.Number.Value}, - MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, - MinUpper: types.Int64{Value: plan.MinUpper.Value}, - MinLower: types.Int64{Value: plan.MinLower.Value}, - MinSpecial: types.Int64{Value: plan.MinSpecial.Value}, - OverrideSpecial: types.String{Value: plan.OverrideSpecial.Value}, - Result: types.String{Value: string(result)}, - } - - hash, err := generateHash(plan.Result.Value) - if err != nil { - resp.Diagnostics.Append(hashGenerationError(err.Error())...) - } - - state.BcryptHash = types.String{Value: hash} - - diags = resp.State.Set(ctx, state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan StringModel - - diags := req.Plan.Get(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - params := randomStringParams{ - length: plan.Length.Value, - upper: plan.Upper.Value, - minUpper: plan.MinUpper.Value, - lower: plan.Lower.Value, - minLower: plan.MinLower.Value, - number: plan.Number.Value, - minNumeric: plan.MinNumeric.Value, - special: plan.Special.Value, - minSpecial: plan.MinSpecial.Value, - overrideSpecial: plan.OverrideSpecial.Value, - } - - result, err := createRandomString(params) - if err != nil { - resp.Diagnostics.Append(randomReadError(err.Error())...) - return - } - - state := StringModel{ - ID: types.String{Value: string(result)}, - Keepers: plan.Keepers, - Length: types.Int64{Value: plan.Length.Value}, - Special: types.Bool{Value: plan.Special.Value}, - Upper: types.Bool{Value: plan.Upper.Value}, - Lower: types.Bool{Value: plan.Lower.Value}, - Number: types.Bool{Value: plan.Number.Value}, - MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, - MinUpper: types.Int64{Value: plan.MinUpper.Value}, - MinLower: types.Int64{Value: plan.MinLower.Value}, - MinSpecial: types.Int64{Value: plan.MinSpecial.Value}, - OverrideSpecial: types.String{Value: plan.OverrideSpecial.Value}, - Result: types.String{Value: string(result)}, - } - - diags = resp.State.Set(ctx, state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - func generateRandomBytes(charSet *string, length int64) ([]byte, error) { bytes := make([]byte, length) setLen := big.NewInt(int64(len(*charSet))) @@ -351,40 +237,6 @@ func generateRandomBytes(charSet *string, length int64) ([]byte, error) { return bytes, nil } -func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - id := req.ID - - state := StringModel{ - ID: types.String{Value: id}, - Result: types.String{Value: id}, - } - - state.Keepers.ElemType = types.StringType - - diags := resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -func importPassword(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - id := req.ID - - state := PasswordModel{ - ID: types.String{Value: "none"}, - Result: types.String{Value: id}, - } - - state.Keepers.ElemType = types.StringType - - diags := resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - func validateLength(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { var config StringModel req.Config.Get(ctx, &config) From 7a60d4e5b0e2f873806fd7a8d6ad9c6c1ab52c0c Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 16 May 2022 13:24:53 +0100 Subject: [PATCH 52/96] Inlining config --- internal/provider/resource_pasword_test.go | 48 ++++++++-------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/internal/provider/resource_pasword_test.go b/internal/provider/resource_pasword_test.go index 9cb277d4..d45836de 100644 --- a/internal/provider/resource_pasword_test.go +++ b/internal/provider/resource_pasword_test.go @@ -15,7 +15,9 @@ func TestAccResourcePasswordBasic(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourcePasswordBasic, + Config: `resource "random_password" "basic" { + length = 12 + }`, Check: resource.ComposeTestCheckFunc( testAccResourceStringCheck("random_password.basic", &customLens{ customLen: 12, @@ -53,7 +55,13 @@ func TestAccResourcePasswordOverride(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourcePasswordOverride, + Config: `resource "random_password" "override" { + length = 4 + override_special = "!" + lower = false + upper = false + number = false + }`, Check: resource.ComposeTestCheckFunc( testAccResourceStringCheck("random_password.override", &customLens{ customLen: 4, @@ -71,7 +79,14 @@ func TestAccResourcePasswordMin(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourcePasswordMin, + Config: `resource "random_password" "min" { + length = 12 + override_special = "!#@" + min_lower = 2 + min_upper = 3 + min_special = 1 + min_numeric = 4 + }`, Check: resource.ComposeTestCheckFunc( testAccResourceStringCheck("random_password.min", &customLens{ customLen: 12, @@ -85,30 +100,3 @@ func TestAccResourcePasswordMin(t *testing.T) { }, }) } - -const ( - testAccResourcePasswordBasic = ` -resource "random_password" "basic" { - length = 12 -}` - - testAccResourcePasswordOverride = ` -resource "random_password" "override" { -length = 4 -override_special = "!" -lower = false -upper = false -number = false -} -` - - testAccResourcePasswordMin = ` -resource "random_password" "min" { -length = 12 -override_special = "!#@" -min_lower = 2 -min_upper = 3 -min_special = 1 -min_numeric = 4 -}` -) From 8346193832350365f6a23b48c0898b8e3843422a Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 17 May 2022 10:35:36 +0100 Subject: [PATCH 53/96] Fixing StateUpgrader --- internal/provider/resource_password.go | 115 +++++++++++++++++++++++-- internal/provider/resource_string.go | 1 + 2 files changed, 109 insertions(+), 7 deletions(-) diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 703e29fe..484e3ffb 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -2,10 +2,13 @@ package provider import ( "context" + "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" "golang.org/x/crypto/bcrypt" ) @@ -71,11 +74,13 @@ func getPasswordSchemaV1() tfsdk.Schema { id, ok := passwordSchema.Attributes["id"] if ok { id.Description = "A static value used internally by Terraform, this should not be referenced in configurations." + passwordSchema.Attributes["id"] = id } result, ok := passwordSchema.Attributes["result"] if ok { result.Sensitive = true + passwordSchema.Attributes["result"] = result } passwordSchema.Attributes["bcrypt_hash"] = tfsdk.Attribute{ @@ -172,22 +177,118 @@ func importPassword(ctx context.Context, req tfsdk.ImportResourceStateRequest, r } } +var passwordDataV0 = tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "keepers": tftypes.Map{ + ElementType: tftypes.String, + }, + "length": tftypes.Number, + "special": tftypes.Bool, + "upper": tftypes.Bool, + "lower": tftypes.Bool, + "number": tftypes.Bool, + "min_numeric": tftypes.Number, + "min_upper": tftypes.Number, + "min_lower": tftypes.Number, + "min_special": tftypes.Number, + "override_special": tftypes.String, + "result": tftypes.String, + "id": tftypes.String, + }, +} + +var passwordDataV1 = tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "bcrypt_hash": tftypes.String, + "keepers": tftypes.Map{ + ElementType: tftypes.String, + }, + "length": tftypes.Number, + "special": tftypes.Bool, + "upper": tftypes.Bool, + "lower": tftypes.Bool, + "number": tftypes.Bool, + "min_numeric": tftypes.Number, + "min_upper": tftypes.Number, + "min_lower": tftypes.Number, + "min_special": tftypes.Number, + "override_special": tftypes.String, + "result": tftypes.String, + "id": tftypes.String, + }, +} + func migratePasswordStateV0toV1(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { - p := PasswordModel{} - req.State.Get(ctx, &p) + rawStateValue, err := req.RawState.Unmarshal(passwordDataV0) + if err != nil { + resp.Diagnostics.AddError( + "Unable to migrate password state from v0 to v1", + fmt.Sprintf("Unmarshalling prior state failed: %s", err), + ) + return + } + + var rawState map[string]tftypes.Value + if err := rawStateValue.As(&rawState); err != nil { + resp.Diagnostics.AddError( + "Unable to migrate password state from v0 to v1", + fmt.Sprintf("Unable to convert prior state: %s", err), + ) + return + } + + if _, ok := rawState["result"]; !ok { + resp.Diagnostics.AddError( + "Unable to migrate password state from v0 to v1", + "Prior state does not contain result.", + ) + return + } - hash, err := generateHash(p.Result.Value) + var result string + if err := rawState["result"].As(&result); err != nil { + resp.Diagnostics.AddError( + "Unable to migrate password state from v0 to v1", + "Result from prior state could not be converted to string.\n\n"+ + "As result is a sensitive value you will need to inspect it by looking\n"+ + "at the state file directly as 'terraform state show' will display (sensitive value).", + ) + return + } + + hash, err := generateHash(result) if err != nil { resp.Diagnostics.Append(hashGenerationError(err.Error())...) return } - p.BcryptHash = types.String{Value: hash} - - resp.State.Set(ctx, p) - if resp.Diagnostics.HasError() { + dynamicValue, err := tfprotov6.NewDynamicValue( + passwordDataV1, + tftypes.NewValue(passwordDataV1, map[string]tftypes.Value{ + "bcrypt_hash": tftypes.NewValue(tftypes.String, hash), + "keepers": rawState["keepers"], + "length": rawState["length"], + "special": rawState["special"], + "upper": rawState["upper"], + "lower": rawState["lower"], + "number": rawState["number"], + "min_numeric": rawState["min_numeric"], + "min_upper": rawState["min_upper"], + "min_lower": rawState["min_lower"], + "min_special": rawState["min_special"], + "override_special": rawState["override_special"], + "result": rawState["result"], + "id": rawState["id"], + })) + if err != nil { + resp.Diagnostics.AddError( + "Unable to migrate password state from v0 to v1", + fmt.Sprintf("Failed to generate new dynamic value: %s", err), + ) return } + + resp.DynamicValue = &dynamicValue } func generateHash(toHash string) (string, error) { diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index 9e28c7d8..7d721d7d 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -65,6 +65,7 @@ func getStringSchemaV1() tfsdk.Schema { id, ok := stringSchema.Attributes["id"] if ok { id.Description = "The generated random string." + stringSchema.Attributes["id"] = id } return stringSchema From 186e0fe55e2cbd6800c00d488cfadea7b938e7a4 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 17 May 2022 14:25:48 +0100 Subject: [PATCH 54/96] Adding tests for StateUpgrader --- internal/provider/resource_password.go | 80 +++++++++++---------- internal/provider/resource_pasword_test.go | 83 ++++++++++++++++++++++ 2 files changed, 125 insertions(+), 38 deletions(-) diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 484e3ffb..bf0a92d3 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -177,49 +177,53 @@ func importPassword(ctx context.Context, req tfsdk.ImportResourceStateRequest, r } } -var passwordDataV0 = tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "keepers": tftypes.Map{ - ElementType: tftypes.String, +func passwordDataTftypesV0() tftypes.Object { + return tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "keepers": tftypes.Map{ + ElementType: tftypes.String, + }, + "length": tftypes.Number, + "special": tftypes.Bool, + "upper": tftypes.Bool, + "lower": tftypes.Bool, + "number": tftypes.Bool, + "min_numeric": tftypes.Number, + "min_upper": tftypes.Number, + "min_lower": tftypes.Number, + "min_special": tftypes.Number, + "override_special": tftypes.String, + "result": tftypes.String, + "id": tftypes.String, }, - "length": tftypes.Number, - "special": tftypes.Bool, - "upper": tftypes.Bool, - "lower": tftypes.Bool, - "number": tftypes.Bool, - "min_numeric": tftypes.Number, - "min_upper": tftypes.Number, - "min_lower": tftypes.Number, - "min_special": tftypes.Number, - "override_special": tftypes.String, - "result": tftypes.String, - "id": tftypes.String, - }, + } } -var passwordDataV1 = tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "bcrypt_hash": tftypes.String, - "keepers": tftypes.Map{ - ElementType: tftypes.String, +func passwordDataTftypesV1() tftypes.Object { + return tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "bcrypt_hash": tftypes.String, + "keepers": tftypes.Map{ + ElementType: tftypes.String, + }, + "length": tftypes.Number, + "special": tftypes.Bool, + "upper": tftypes.Bool, + "lower": tftypes.Bool, + "number": tftypes.Bool, + "min_numeric": tftypes.Number, + "min_upper": tftypes.Number, + "min_lower": tftypes.Number, + "min_special": tftypes.Number, + "override_special": tftypes.String, + "result": tftypes.String, + "id": tftypes.String, }, - "length": tftypes.Number, - "special": tftypes.Bool, - "upper": tftypes.Bool, - "lower": tftypes.Bool, - "number": tftypes.Bool, - "min_numeric": tftypes.Number, - "min_upper": tftypes.Number, - "min_lower": tftypes.Number, - "min_special": tftypes.Number, - "override_special": tftypes.String, - "result": tftypes.String, - "id": tftypes.String, - }, + } } func migratePasswordStateV0toV1(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { - rawStateValue, err := req.RawState.Unmarshal(passwordDataV0) + rawStateValue, err := req.RawState.Unmarshal(passwordDataTftypesV0()) if err != nil { resp.Diagnostics.AddError( "Unable to migrate password state from v0 to v1", @@ -263,8 +267,8 @@ func migratePasswordStateV0toV1(ctx context.Context, req tfsdk.UpgradeResourceSt } dynamicValue, err := tfprotov6.NewDynamicValue( - passwordDataV1, - tftypes.NewValue(passwordDataV1, map[string]tftypes.Value{ + passwordDataTftypesV1(), + tftypes.NewValue(passwordDataTftypesV1(), map[string]tftypes.Value{ "bcrypt_hash": tftypes.NewValue(tftypes.String, hash), "keepers": rawState["keepers"], "length": rawState["length"], diff --git a/internal/provider/resource_pasword_test.go b/internal/provider/resource_pasword_test.go index d45836de..beea8122 100644 --- a/internal/provider/resource_pasword_test.go +++ b/internal/provider/resource_pasword_test.go @@ -1,10 +1,14 @@ package provider import ( + "context" "fmt" "regexp" "testing" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) @@ -100,3 +104,82 @@ func TestAccResourcePasswordMin(t *testing.T) { }, }) } + +func TestMigratePasswordStateV0toV1(t *testing.T) { + rawStateJSON := `{ + "id": "none", + "keepers": null, + "length": 16, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "override_special": "!#$%\u0026*()-_=+[]{}\u003c\u003e:?", + "result": "DZy_3*tnonj%Q%Yx", + "special": true, + "upper": true + }` + + req := tfsdk.UpgradeResourceStateRequest{ + RawState: &tfprotov6.RawState{ + JSON: []byte(rawStateJSON), + }, + } + + resp := &tfsdk.UpgradeResourceStateResponse{ + State: tfsdk.State{ + Schema: getPasswordSchemaV1(), + }, + } + + migratePasswordStateV0toV1(context.Background(), req, resp) + + val, err := resp.DynamicValue.Unmarshal(passwordDataTftypesV1()) + if err != nil { + t.Error(err) + } + + data := map[string]tftypes.Value{} + + err = val.As(&data) + if err != nil { + t.Error(err) + } + + expectedValues := map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "none"), + "keepers": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), + "length": tftypes.NewValue(tftypes.Number, 16), + "lower": tftypes.NewValue(tftypes.Bool, true), + "min_lower": tftypes.NewValue(tftypes.Number, 0), + "min_numeric": tftypes.NewValue(tftypes.Number, 0), + "min_special": tftypes.NewValue(tftypes.Number, 0), + "min_upper": tftypes.NewValue(tftypes.Number, 0), + "number": tftypes.NewValue(tftypes.Bool, true), + "override_special": tftypes.NewValue(tftypes.String, "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"), + "result": tftypes.NewValue(tftypes.String, "DZy_3*tnonj%Q%Yx"), + "special": tftypes.NewValue(tftypes.Bool, true), + "upper": tftypes.NewValue(tftypes.Bool, true), + } + + for k, v := range expectedValues { + ok := data[k].Equal(v) + if !ok { + t.Errorf("expected: %v, got: %v", v, data[k]) + } + } + + var bcryptHash string + const bcryptHashExpectedLen = 60 + + err = data["bcrypt_hash"].As(&bcryptHash) + if err != nil { + t.Error(err) + } + + if len(bcryptHash) != bcryptHashExpectedLen { + t.Errorf("expected len: %v, got len: %v", bcryptHashExpectedLen, len(bcryptHash)) + } +} From 709a046cced06cf659a74ad9601088832d531f36 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 19 May 2022 12:48:13 +0100 Subject: [PATCH 55/96] Switching to using StateUpgrader with PriorSchema --- internal/provider/models.go | 18 ++- internal/provider/resource_password.go | 158 ++++++--------------- internal/provider/resource_pasword_test.go | 95 ++++++------- 3 files changed, 98 insertions(+), 173 deletions(-) diff --git a/internal/provider/models.go b/internal/provider/models.go index 820a1ee9..69cf3563 100644 --- a/internal/provider/models.go +++ b/internal/provider/models.go @@ -22,7 +22,7 @@ type IntegerModel struct { Result types.Int64 `tfsdk:"result"` } -type PasswordModel struct { +type PasswordModelV1 struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` Length types.Int64 `tfsdk:"length"` @@ -39,6 +39,22 @@ type PasswordModel struct { BcryptHash types.String `tfsdk:"bcrypt_hash"` } +type PasswordModelV0 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Number types.Bool `tfsdk:"number"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Result types.String `tfsdk:"result"` +} + type PetNameModel struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index bf0a92d3..5a1ebef4 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -2,13 +2,10 @@ package provider import ( "context" - "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" - "github.com/hashicorp/terraform-plugin-go/tftypes" "golang.org/x/crypto/bcrypt" ) @@ -55,14 +52,32 @@ func (r resourcePassword) ValidateConfig(ctx context.Context, req tfsdk.Validate } func (r resourcePassword) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { + passwordSchemaV0 := getPasswordSchemaV0() + return map[int64]tfsdk.ResourceStateUpgrader{ 0: { + PriorSchema: &passwordSchemaV0, StateUpgrader: migratePasswordStateV0toV1, }, } } func getPasswordSchemaV1() tfsdk.Schema { + passwordSchema := getPasswordSchemaV0() + + passwordSchema.Attributes["bcrypt_hash"] = tfsdk.Attribute{ + Description: "A bcrypt hash of the generated random string.", + Type: types.StringType, + Computed: true, + Sensitive: true, + } + + passwordSchema.Version = 1 + + return passwordSchema +} + +func getPasswordSchemaV0() tfsdk.Schema { passwordSchema := passwordStringSchema() passwordSchema.Description = "Identical to [random_string](string.html) with the exception that the result is " + @@ -83,20 +98,11 @@ func getPasswordSchemaV1() tfsdk.Schema { passwordSchema.Attributes["result"] = result } - passwordSchema.Attributes["bcrypt_hash"] = tfsdk.Attribute{ - Description: "A bcrypt hash of the generated random string.", - Type: types.StringType, - Computed: true, - Sensitive: true, - } - - passwordSchema.Version = 1 - return passwordSchema } func createPassword(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan PasswordModel + var plan PasswordModelV1 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -123,7 +129,7 @@ func createPassword(ctx context.Context, req tfsdk.CreateResourceRequest, resp * return } - state := PasswordModel{ + state := PasswordModelV1{ ID: types.String{Value: "none"}, Keepers: plan.Keepers, Length: types.Int64{Value: plan.Length.Value}, @@ -156,7 +162,7 @@ func createPassword(ctx context.Context, req tfsdk.CreateResourceRequest, resp * func importPassword(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { id := req.ID - state := PasswordModel{ + state := PasswordModelV1{ ID: types.String{Value: "none"}, Result: types.String{Value: id}, } @@ -177,122 +183,38 @@ func importPassword(ctx context.Context, req tfsdk.ImportResourceStateRequest, r } } -func passwordDataTftypesV0() tftypes.Object { - return tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "keepers": tftypes.Map{ - ElementType: tftypes.String, - }, - "length": tftypes.Number, - "special": tftypes.Bool, - "upper": tftypes.Bool, - "lower": tftypes.Bool, - "number": tftypes.Bool, - "min_numeric": tftypes.Number, - "min_upper": tftypes.Number, - "min_lower": tftypes.Number, - "min_special": tftypes.Number, - "override_special": tftypes.String, - "result": tftypes.String, - "id": tftypes.String, - }, - } -} - -func passwordDataTftypesV1() tftypes.Object { - return tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "bcrypt_hash": tftypes.String, - "keepers": tftypes.Map{ - ElementType: tftypes.String, - }, - "length": tftypes.Number, - "special": tftypes.Bool, - "upper": tftypes.Bool, - "lower": tftypes.Bool, - "number": tftypes.Bool, - "min_numeric": tftypes.Number, - "min_upper": tftypes.Number, - "min_lower": tftypes.Number, - "min_special": tftypes.Number, - "override_special": tftypes.String, - "result": tftypes.String, - "id": tftypes.String, - }, - } -} - func migratePasswordStateV0toV1(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { - rawStateValue, err := req.RawState.Unmarshal(passwordDataTftypesV0()) - if err != nil { - resp.Diagnostics.AddError( - "Unable to migrate password state from v0 to v1", - fmt.Sprintf("Unmarshalling prior state failed: %s", err), - ) - return - } - - var rawState map[string]tftypes.Value - if err := rawStateValue.As(&rawState); err != nil { - resp.Diagnostics.AddError( - "Unable to migrate password state from v0 to v1", - fmt.Sprintf("Unable to convert prior state: %s", err), - ) - return - } + var passwordDataV0 PasswordModelV0 - if _, ok := rawState["result"]; !ok { - resp.Diagnostics.AddError( - "Unable to migrate password state from v0 to v1", - "Prior state does not contain result.", - ) + resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV0)...) + if resp.Diagnostics.HasError() { return } - var result string - if err := rawState["result"].As(&result); err != nil { - resp.Diagnostics.AddError( - "Unable to migrate password state from v0 to v1", - "Result from prior state could not be converted to string.\n\n"+ - "As result is a sensitive value you will need to inspect it by looking\n"+ - "at the state file directly as 'terraform state show' will display (sensitive value).", - ) - return + passwordDataV1 := PasswordModelV1{ + Keepers: passwordDataV0.Keepers, + Length: passwordDataV0.Length, + Special: passwordDataV0.Special, + Upper: passwordDataV0.Upper, + Lower: passwordDataV0.Lower, + Number: passwordDataV0.Number, + MinNumeric: passwordDataV0.MinNumeric, + MinLower: passwordDataV0.MinLower, + MinSpecial: passwordDataV0.MinSpecial, + OverrideSpecial: passwordDataV0.OverrideSpecial, + Result: passwordDataV0.Result, + ID: passwordDataV0.ID, } - hash, err := generateHash(result) + hash, err := generateHash(passwordDataV1.Result.Value) if err != nil { resp.Diagnostics.Append(hashGenerationError(err.Error())...) return } - dynamicValue, err := tfprotov6.NewDynamicValue( - passwordDataTftypesV1(), - tftypes.NewValue(passwordDataTftypesV1(), map[string]tftypes.Value{ - "bcrypt_hash": tftypes.NewValue(tftypes.String, hash), - "keepers": rawState["keepers"], - "length": rawState["length"], - "special": rawState["special"], - "upper": rawState["upper"], - "lower": rawState["lower"], - "number": rawState["number"], - "min_numeric": rawState["min_numeric"], - "min_upper": rawState["min_upper"], - "min_lower": rawState["min_lower"], - "min_special": rawState["min_special"], - "override_special": rawState["override_special"], - "result": rawState["result"], - "id": rawState["id"], - })) - if err != nil { - resp.Diagnostics.AddError( - "Unable to migrate password state from v0 to v1", - fmt.Sprintf("Failed to generate new dynamic value: %s", err), - ) - return - } + passwordDataV1.BcryptHash.Value = hash - resp.DynamicValue = &dynamicValue + resp.Diagnostics.Append(resp.State.Set(ctx, passwordDataV1)...) } func generateHash(toHash string) (string, error) { diff --git a/internal/provider/resource_pasword_test.go b/internal/provider/resource_pasword_test.go index beea8122..5f8a0741 100644 --- a/internal/provider/resource_pasword_test.go +++ b/internal/provider/resource_pasword_test.go @@ -6,11 +6,13 @@ import ( "regexp" "testing" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "golang.org/x/crypto/bcrypt" ) func TestAccResourcePasswordBasic(t *testing.T) { @@ -106,49 +108,7 @@ func TestAccResourcePasswordMin(t *testing.T) { } func TestMigratePasswordStateV0toV1(t *testing.T) { - rawStateJSON := `{ - "id": "none", - "keepers": null, - "length": 16, - "lower": true, - "min_lower": 0, - "min_numeric": 0, - "min_special": 0, - "min_upper": 0, - "number": true, - "override_special": "!#$%\u0026*()-_=+[]{}\u003c\u003e:?", - "result": "DZy_3*tnonj%Q%Yx", - "special": true, - "upper": true - }` - - req := tfsdk.UpgradeResourceStateRequest{ - RawState: &tfprotov6.RawState{ - JSON: []byte(rawStateJSON), - }, - } - - resp := &tfsdk.UpgradeResourceStateResponse{ - State: tfsdk.State{ - Schema: getPasswordSchemaV1(), - }, - } - - migratePasswordStateV0toV1(context.Background(), req, resp) - - val, err := resp.DynamicValue.Unmarshal(passwordDataTftypesV1()) - if err != nil { - t.Error(err) - } - - data := map[string]tftypes.Value{} - - err = val.As(&data) - if err != nil { - t.Error(err) - } - - expectedValues := map[string]tftypes.Value{ + raw := tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ "id": tftypes.NewValue(tftypes.String, "none"), "keepers": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), "length": tftypes.NewValue(tftypes.Number, 16), @@ -162,24 +122,51 @@ func TestMigratePasswordStateV0toV1(t *testing.T) { "result": tftypes.NewValue(tftypes.String, "DZy_3*tnonj%Q%Yx"), "special": tftypes.NewValue(tftypes.Bool, true), "upper": tftypes.NewValue(tftypes.Bool, true), + }) + + req := tfsdk.UpgradeResourceStateRequest{ + State: &tfsdk.State{ + Raw: raw, + Schema: getPasswordSchemaV0(), + }, } - for k, v := range expectedValues { - ok := data[k].Equal(v) - if !ok { - t.Errorf("expected: %v, got: %v", v, data[k]) - } + resp := &tfsdk.UpgradeResourceStateResponse{ + State: tfsdk.State{ + Schema: getPasswordSchemaV1(), + }, + } + + migratePasswordStateV0toV1(context.Background(), req, resp) + + expected := PasswordModelV1{ + ID: types.String{Value: "none"}, + Keepers: types.Map{Null: true, ElemType: types.StringType}, + Length: types.Int64{Value: 16}, + Special: types.Bool{Value: true}, + Upper: types.Bool{Value: true}, + Lower: types.Bool{Value: true}, + Number: types.Bool{Value: true}, + MinNumeric: types.Int64{Value: 0}, + MinUpper: types.Int64{Value: 0}, + MinLower: types.Int64{Value: 0}, + MinSpecial: types.Int64{Value: 0}, + OverrideSpecial: types.String{Value: "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"}, + Result: types.String{Value: "DZy_3*tnonj%Q%Yx"}, } - var bcryptHash string - const bcryptHashExpectedLen = 60 + actual := PasswordModelV1{} + resp.State.Get(context.Background(), &actual) - err = data["bcrypt_hash"].As(&bcryptHash) + err := bcrypt.CompareHashAndPassword([]byte(actual.BcryptHash.Value), []byte(actual.Result.Value)) if err != nil { t.Error(err) } - if len(bcryptHash) != bcryptHashExpectedLen { - t.Errorf("expected len: %v, got len: %v", bcryptHashExpectedLen, len(bcryptHash)) + // Setting actual.BcryptHash to zero value to allow direct comparison of expected and actual. + actual.BcryptHash = types.String{} + + if !cmp.Equal(expected, actual) { + t.Errorf("expected: %+v, got: %+v", expected, actual) } } From 76d329e5e82b12cd47475f18845b075e2572adcb Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 19 May 2022 17:15:25 +0100 Subject: [PATCH 56/96] Adding error context (#177) --- internal/provider/resource_pasword_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/resource_pasword_test.go b/internal/provider/resource_pasword_test.go index 5f8a0741..240738a9 100644 --- a/internal/provider/resource_pasword_test.go +++ b/internal/provider/resource_pasword_test.go @@ -160,7 +160,7 @@ func TestMigratePasswordStateV0toV1(t *testing.T) { err := bcrypt.CompareHashAndPassword([]byte(actual.BcryptHash.Value), []byte(actual.Result.Value)) if err != nil { - t.Error(err) + t.Errorf("unexpected bcrypt comparison error: %s", err) } // Setting actual.BcryptHash to zero value to allow direct comparison of expected and actual. From 6c0c14380d8e780b4bd88b794c8865b64dd2c144 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 20 May 2022 14:42:19 +0100 Subject: [PATCH 57/96] Replacing ValidateConfig with AttributeValidator (#177) --- internal/provider/resource_password.go | 4 -- internal/provider/resource_string.go | 4 -- internal/provider/resource_string_test.go | 2 +- internal/provider/string.go | 31 +++----- internal/provider/validators.go | 87 +++++++++++++++++++++++ 5 files changed, 98 insertions(+), 30 deletions(-) diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 5a1ebef4..2e937a1b 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -47,10 +47,6 @@ func (r resourcePassword) ImportState(ctx context.Context, req tfsdk.ImportResou importPassword(ctx, req, resp) } -func (r resourcePassword) ValidateConfig(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { - validateLength(ctx, req, resp) -} - func (r resourcePassword) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { passwordSchemaV0 := getPasswordSchemaV0() diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index 7d721d7d..37f06e83 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -46,10 +46,6 @@ func (r resourceString) ImportState(ctx context.Context, req tfsdk.ImportResourc importString(ctx, req, resp) } -func (r resourceString) ValidateConfig(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { - validateLength(ctx, req, resp) -} - func getStringSchemaV1() tfsdk.Schema { stringSchema := passwordStringSchema() diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index 8c0a4e42..51dcc87b 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -82,7 +82,7 @@ func TestAccResourceStringErrors(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccResourceStringInvalidConfig, - ExpectError: regexp.MustCompile(`.*The password/string length \(2\) must be >= min_upper \+ min_lower \+ min_numeric\n\+ min_special \(3\)`), + ExpectError: regexp.MustCompile(`.*Attribute "length" \(2\) cannot be less than min_upper \+ min_lower \+\nmin_numeric \+ min_special \(3\).`), }, { Config: testAccResourceStringLengthTooShortConfig, diff --git a/internal/provider/string.go b/internal/provider/string.go index f328a787..5829c579 100644 --- a/internal/provider/string.go +++ b/internal/provider/string.go @@ -1,14 +1,13 @@ package provider import ( - "context" "crypto/rand" - "fmt" "math/big" "sort" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" ) // passwordStringSchema contains the common set of Attributes for both password and string resources. @@ -33,7 +32,15 @@ func passwordStringSchema() tfsdk.Schema { Type: types.Int64Type, Required: true, PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Validators: []tfsdk.AttributeValidator{validatorMinInt(1)}, + Validators: []tfsdk.AttributeValidator{ + validatorMinInt(1), + isAtLeastSumOf( + tftypes.NewAttributePath().WithAttributeName("min_upper"), + tftypes.NewAttributePath().WithAttributeName("min_lower"), + tftypes.NewAttributePath().WithAttributeName("min_numeric"), + tftypes.NewAttributePath().WithAttributeName("min_special"), + ), + }, }, "special": { @@ -236,21 +243,3 @@ func generateRandomBytes(charSet *string, length int64) ([]byte, error) { } return bytes, nil } - -func validateLength(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { - var config StringModel - req.Config.Get(ctx, &config) - - length := config.Length.Value - minUpper := config.MinUpper.Value - minLower := config.MinLower.Value - minNumeric := config.MinNumeric.Value - minSpecial := config.MinSpecial.Value - - if length < minUpper+minLower+minNumeric+minSpecial { - resp.Diagnostics.AddError( - "Validate Password/String Error", - fmt.Sprintf("The password/string length (%d) must be >= min_upper + min_lower + min_numeric + min_special (%d)", length, minUpper+minLower+minNumeric+minSpecial), - ) - } -} diff --git a/internal/provider/validators.go b/internal/provider/validators.go index 0e292f08..0f12cb11 100644 --- a/internal/provider/validators.go +++ b/internal/provider/validators.go @@ -3,9 +3,13 @@ package provider import ( "context" "fmt" + "strconv" + "strings" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-log/tflog" ) // validatorMinInt accepts an int64 and returns a struct that implements the AttributeValidator interface. @@ -43,3 +47,86 @@ func (m minIntValidator) Validate(ctx context.Context, req tfsdk.ValidateAttribu ) } } + +type isAtLeastSumOfValidator struct { + attributesToSum []*tftypes.AttributePath +} + +var _ tfsdk.AttributeValidator = (*isAtLeastSumOfValidator)(nil) + +func isAtLeastSumOf(attributePaths ...*tftypes.AttributePath) tfsdk.AttributeValidator { + return &isAtLeastSumOfValidator{attributePaths} +} + +func (av *isAtLeastSumOfValidator) Description(ctx context.Context) string { + return av.MarkdownDescription(ctx) +} + +func (av *isAtLeastSumOfValidator) MarkdownDescription(context.Context) string { + return fmt.Sprintf("Ensure that attribute has a value >= sum of: %q", av.attributesToSum) +} + +func (av *isAtLeastSumOfValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { + tflog.Debug(ctx, "Validating that attribute has a value at least equal to the attributes to sum", map[string]interface{}{ + "attribute": attrPathToString(req.AttributePath), + "attributesToSum": av.attributesToSum, + }) + + var attrib types.Int64 + resp.Diagnostics.Append(tfsdk.ValueAs(ctx, req.AttributeConfig, &attrib)...) + if resp.Diagnostics.HasError() { + return + } + + var sumOfAttribs int64 + var attributesToSumPaths []string + + for _, path := range av.attributesToSum { + var attribToSum types.Int64 + resp.Diagnostics.Append(req.Config.GetAttribute(ctx, path, &attribToSum)...) + if resp.Diagnostics.HasError() { + return + } + + sumOfAttribs += attribToSum.Value + attributesToSumPaths = append(attributesToSumPaths, attrPathToString(path)) + } + + if attrib.Value < sumOfAttribs { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + fmt.Sprintf("Attribute %q is less than summed attributes.", attrPathToString(req.AttributePath)), + fmt.Sprintf( + "Attribute %q (%d) cannot be less than %s (%d).", + attrPathToString(req.AttributePath), + attrib.Value, + strings.Join(attributesToSumPaths, " + "), + sumOfAttribs, + ), + ) + } +} + +// attrPathToString takes all the tftypes.AttributePathStep in a tftypes.AttributePath and concatenates them, +// using `.` as separator. +// +// This should be used only when trying to "print out" a tftypes.AttributePath in a log or an error message. +func attrPathToString(a *tftypes.AttributePath) string { + var res strings.Builder + for pos, step := range a.Steps() { + if pos != 0 { + res.WriteString(".") + } + switch v := step.(type) { + case tftypes.AttributeName: + res.WriteString(string(v)) + case tftypes.ElementKeyString: + res.WriteString(string(v)) + case tftypes.ElementKeyInt: + res.WriteString(strconv.FormatInt(int64(v), 10)) + case tftypes.ElementKeyValue: + res.WriteString(tftypes.Value(v).String()) + } + } + return res.String() +} From 666c5839395fc88fd45e625d900456e59ab4758a Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 20 May 2022 18:32:59 +0100 Subject: [PATCH 58/96] Adding test for IsAtLeastSumOf (#177) --- internal/provider/validators_test.go | 118 +++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 internal/provider/validators_test.go diff --git a/internal/provider/validators_test.go b/internal/provider/validators_test.go new file mode 100644 index 00000000..82122a35 --- /dev/null +++ b/internal/provider/validators_test.go @@ -0,0 +1,118 @@ +package provider + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestIsAtLeastSumOfValidator_Validate(t *testing.T) { + req := tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("length"), + AttributeConfig: types.Int64{Value: 16}, + Config: tfsdk.Config{ + Schema: getPasswordSchemaV1(), + }, + } + + type expectedRespDiags struct { + expectedRespDiagAttrPath *tftypes.AttributePath + expectedRespDiagSummary string + expectedRespDiagDetail string + } + + cases := []struct { + name string + reqConfigRaw tftypes.Value + attributesToSum []*tftypes.AttributePath + expectedRespDiags []expectedRespDiags + }{ + { + "attribute less than sum of attribute", + tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ + "min_upper": tftypes.NewValue(tftypes.Number, 17), + }), + []*tftypes.AttributePath{tftypes.NewAttributePath().WithAttributeName("min_upper")}, + []expectedRespDiags{ + { + tftypes.NewAttributePath().WithAttributeName("length"), + `Attribute "length" is less than summed attributes.`, + `Attribute "length" (16) cannot be less than min_upper (17).`, + }, + }, + }, + { + "attribute less than sum of attributes", + tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ + "min_upper": tftypes.NewValue(tftypes.Number, 10), + "min_lower": tftypes.NewValue(tftypes.Number, 12), + }), + []*tftypes.AttributePath{ + tftypes.NewAttributePath().WithAttributeName("min_upper"), + tftypes.NewAttributePath().WithAttributeName("min_lower"), + }, + []expectedRespDiags{ + { + tftypes.NewAttributePath().WithAttributeName("length"), + `Attribute "length" is less than summed attributes.`, + `Attribute "length" (16) cannot be less than min_upper + min_lower (22).`, + }, + }, + }, + { + "a summed attribute is of invalid type", + tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ + "min_upper": tftypes.NewValue(tftypes.String, "17"), + }), + []*tftypes.AttributePath{tftypes.NewAttributePath().WithAttributeName("min_upper")}, + []expectedRespDiags{ + { + tftypes.NewAttributePath().WithAttributeName("min_upper"), + `Int64 Type Validation Error`, + `An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. Please report the following to the provider developer: + +Expected Number value, received tftypes.Value with value: tftypes.String<"17">`, + }, + }, + }, + { + "attribute equal to sum of attributes", + tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ + "min_upper": tftypes.NewValue(tftypes.Number, 8), + "min_lower": tftypes.NewValue(tftypes.Number, 8), + }), + []*tftypes.AttributePath{ + tftypes.NewAttributePath().WithAttributeName("min_upper"), + tftypes.NewAttributePath().WithAttributeName("min_lower")}, + []expectedRespDiags{}, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + req.Config.Raw = c.reqConfigRaw + resp := tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{}, + } + + validator := isAtLeastSumOf(c.attributesToSum...) + + validator.Validate(context.Background(), req, &resp) + + expectedDiags := diag.Diagnostics{} + + for _, v := range c.expectedRespDiags { + expectedDiags.AddAttributeError(v.expectedRespDiagAttrPath, v.expectedRespDiagSummary, v.expectedRespDiagDetail) + } + + if !cmp.Equal(expectedDiags, resp.Diagnostics) { + t.Errorf("expecting resp diags: %s, actual resp diags: %s", expectedDiags, resp.Diagnostics) + } + }) + } +} From df3cd782904f2a746a15db80fb9f06f27ea01cbb Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Sun, 22 May 2022 14:11:39 +0100 Subject: [PATCH 59/96] Refactoring validators (#177) --- internal/provider/resource_string_test.go | 2 +- internal/provider/string.go | 4 +- internal/provider/validators.go | 55 +++++++++++++---------- internal/provider/validators_test.go | 2 +- 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index 51dcc87b..54fdc7e0 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -86,7 +86,7 @@ func TestAccResourceStringErrors(t *testing.T) { }, { Config: testAccResourceStringLengthTooShortConfig, - ExpectError: regexp.MustCompile(`.*Expected attribute at AttributeName\("length"\) to be at least 1, got 0`), + ExpectError: regexp.MustCompile(`.*Attribute "length" \(0\) must be at least 1`), }, }, }) diff --git a/internal/provider/string.go b/internal/provider/string.go index 5829c579..1db9ac01 100644 --- a/internal/provider/string.go +++ b/internal/provider/string.go @@ -33,8 +33,8 @@ func passwordStringSchema() tfsdk.Schema { Required: true, PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, Validators: []tfsdk.AttributeValidator{ - validatorMinInt(1), - isAtLeastSumOf( + NewIntAtLeastValidator(1), + NewIntIsAtLeastSumOfValidator( tftypes.NewAttributePath().WithAttributeName("min_upper"), tftypes.NewAttributePath().WithAttributeName("min_lower"), tftypes.NewAttributePath().WithAttributeName("min_numeric"), diff --git a/internal/provider/validators.go b/internal/provider/validators.go index 0f12cb11..26bec438 100644 --- a/internal/provider/validators.go +++ b/internal/provider/validators.go @@ -12,61 +12,68 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" ) -// validatorMinInt accepts an int64 and returns a struct that implements the AttributeValidator interface. -func validatorMinInt(min int64) tfsdk.AttributeValidator { - return minIntValidator{min} +// intAtLeastValidator checks that the value of the attribute in the configuration +// (i.e., AttributeConfig in ValidateAttributeRequest) is greater than or, equal to minVal. +type intAtLeastValidator struct { + minVal int64 } -type minIntValidator struct { - val int64 +var _ tfsdk.AttributeValidator = (*intAtLeastValidator)(nil) + +func NewIntAtLeastValidator(min int64) tfsdk.AttributeValidator { + return &intAtLeastValidator{min} } -func (m minIntValidator) Description(ctx context.Context) string { - return "MinInt validator ensures that attribute is at least val" +func (av *intAtLeastValidator) Description(ctx context.Context) string { + return "intAtLeastValidator ensures that attribute is at least minVal" } -func (m minIntValidator) MarkdownDescription(context.Context) string { - return "MinInt validator ensures that attribute is at least `val`" +func (av *intAtLeastValidator) MarkdownDescription(context.Context) string { + return "intAtLeastValidator ensures that attribute is at least `minVal`" } -// Validate checks that the value of the attribute in the configuration is greater than or, equal to the value supplied -// when the minIntValidator struct was initialised. -func (m minIntValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { +// Validate first determines whether AttributeConfig (attr.Value) can be reflected into types.Int64 and then checks +// that the value is >= minVal. +func (av *intAtLeastValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { var t types.Int64 - diags := tfsdk.ValueAs(ctx, req.AttributeConfig, &t) - resp.Diagnostics.Append(diags...) + resp.Diagnostics.Append(tfsdk.ValueAs(ctx, req.AttributeConfig, &t)...) if resp.Diagnostics.HasError() { return } - if t.Value < m.val { + if t.Value < av.minVal { resp.Diagnostics.AddError( - "Validating Min Int Error", - fmt.Sprintf("Expected attribute at %s to be at least %d, got %d", req.AttributePath.String(), m.val, t.Value), + "Validating Int At Least Error", + fmt.Sprintf("Attribute %q (%d) must be at least %d", attrPathToString(req.AttributePath), t.Value, av.minVal), ) } } -type isAtLeastSumOfValidator struct { +// intIsAtLeastValidator checks that the value of the attribute in the configuration +// (i.e., AttributeConfig in ValidateAttributeRequest) is greater than or, equal to the sum of the values of the +// attributes in the slice of AttributePath. +type intIsAtLeastSumOfValidator struct { attributesToSum []*tftypes.AttributePath } -var _ tfsdk.AttributeValidator = (*isAtLeastSumOfValidator)(nil) +var _ tfsdk.AttributeValidator = (*intIsAtLeastSumOfValidator)(nil) -func isAtLeastSumOf(attributePaths ...*tftypes.AttributePath) tfsdk.AttributeValidator { - return &isAtLeastSumOfValidator{attributePaths} +func NewIntIsAtLeastSumOfValidator(attributePaths ...*tftypes.AttributePath) tfsdk.AttributeValidator { + return &intIsAtLeastSumOfValidator{attributePaths} } -func (av *isAtLeastSumOfValidator) Description(ctx context.Context) string { +func (av *intIsAtLeastSumOfValidator) Description(ctx context.Context) string { return av.MarkdownDescription(ctx) } -func (av *isAtLeastSumOfValidator) MarkdownDescription(context.Context) string { +func (av *intIsAtLeastSumOfValidator) MarkdownDescription(context.Context) string { return fmt.Sprintf("Ensure that attribute has a value >= sum of: %q", av.attributesToSum) } -func (av *isAtLeastSumOfValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { +// Validate first determines whether AttributeConfig (attr.Value) can be reflected into types.Int64 and then checks +// that the value is >= sum of values of the attributes defined in attributesToSum. +func (av *intIsAtLeastSumOfValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { tflog.Debug(ctx, "Validating that attribute has a value at least equal to the attributes to sum", map[string]interface{}{ "attribute": attrPathToString(req.AttributePath), "attributesToSum": av.attributesToSum, diff --git a/internal/provider/validators_test.go b/internal/provider/validators_test.go index 82122a35..683bc79c 100644 --- a/internal/provider/validators_test.go +++ b/internal/provider/validators_test.go @@ -100,7 +100,7 @@ Expected Number value, received tftypes.Value with value: tftypes.String<"17">`, Diagnostics: diag.Diagnostics{}, } - validator := isAtLeastSumOf(c.attributesToSum...) + validator := NewIntIsAtLeastSumOfValidator(c.attributesToSum...) validator.Validate(context.Background(), req, &resp) From 59938bdeee79c83a1bbb0c72767993b2b940293a Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 23 May 2022 10:41:06 +0100 Subject: [PATCH 60/96] Checking req.AttribConfig type in validator (#177) --- internal/provider/validators.go | 32 +++++++++++++++++++--------- internal/provider/validators_test.go | 21 +++++++++++++++++- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/internal/provider/validators.go b/internal/provider/validators.go index 26bec438..e9b7e286 100644 --- a/internal/provider/validators.go +++ b/internal/provider/validators.go @@ -71,8 +71,11 @@ func (av *intIsAtLeastSumOfValidator) MarkdownDescription(context.Context) strin return fmt.Sprintf("Ensure that attribute has a value >= sum of: %q", av.attributesToSum) } -// Validate first determines whether AttributeConfig (attr.Value) can be reflected into types.Int64 and then checks -// that the value is >= sum of values of the attributes defined in attributesToSum. +// Validate runs the following checks: +// 1. Determines whether AttributeConfig (attr.Value) Type is of correct type (i.e., types.Int64). This is required +// because tfsdk.ValueAs will allow a types other than types.Int64 to be supplied as the value. +// 2. Determines if AttributeConfig can be reflected into types.Int64. +// 3. Checks that the AttributeConfig value is >= sum of values of the attributes defined in attributesToSum. func (av *intIsAtLeastSumOfValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { tflog.Debug(ctx, "Validating that attribute has a value at least equal to the attributes to sum", map[string]interface{}{ "attribute": attrPathToString(req.AttributePath), @@ -80,6 +83,19 @@ func (av *intIsAtLeastSumOfValidator) Validate(ctx context.Context, req tfsdk.Va }) var attrib types.Int64 + + if req.AttributeConfig.Type(ctx) != attrib.Type(ctx) { + pathStr := attrPathToString(req.AttributePath) + + resp.Diagnostics.AddAttributeError( + req.AttributePath, + fmt.Sprintf("Attribute %q is of incorrect type for validator.", pathStr), + fmt.Sprintf("Attribute %q (%s) cannot be used as %s.", pathStr, req.AttributeConfig.Type(ctx), attrib.Type(ctx)), + ) + + return + } + resp.Diagnostics.Append(tfsdk.ValueAs(ctx, req.AttributeConfig, &attrib)...) if resp.Diagnostics.HasError() { return @@ -100,16 +116,12 @@ func (av *intIsAtLeastSumOfValidator) Validate(ctx context.Context, req tfsdk.Va } if attrib.Value < sumOfAttribs { + attribPath := attrPathToString(req.AttributePath) + resp.Diagnostics.AddAttributeError( req.AttributePath, - fmt.Sprintf("Attribute %q is less than summed attributes.", attrPathToString(req.AttributePath)), - fmt.Sprintf( - "Attribute %q (%d) cannot be less than %s (%d).", - attrPathToString(req.AttributePath), - attrib.Value, - strings.Join(attributesToSumPaths, " + "), - sumOfAttribs, - ), + fmt.Sprintf("Attribute %q is less than summed attributes.", attribPath), + fmt.Sprintf("Attribute %q (%d) cannot be less than %s (%d).", attribPath, attrib.Value, strings.Join(attributesToSumPaths, " + "), sumOfAttribs), ) } } diff --git a/internal/provider/validators_test.go b/internal/provider/validators_test.go index 683bc79c..4ff3d8b8 100644 --- a/internal/provider/validators_test.go +++ b/internal/provider/validators_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" @@ -12,6 +13,8 @@ import ( ) func TestIsAtLeastSumOfValidator_Validate(t *testing.T) { + t.Parallel() + req := tfsdk.ValidateAttributeRequest{ AttributePath: tftypes.NewAttributePath().WithAttributeName("length"), AttributeConfig: types.Int64{Value: 16}, @@ -28,12 +31,25 @@ func TestIsAtLeastSumOfValidator_Validate(t *testing.T) { cases := []struct { name string + reqAttribConfig attr.Value reqConfigRaw tftypes.Value attributesToSum []*tftypes.AttributePath expectedRespDiags []expectedRespDiags }{ + { + name: "attribute wrong type", + reqAttribConfig: types.String{Value: "16"}, + expectedRespDiags: []expectedRespDiags{ + { + tftypes.NewAttributePath().WithAttributeName("length"), + `Attribute "length" is of incorrect type for validator.`, + `Attribute "length" (types.StringType) cannot be used as types.Int64Type.`, + }, + }, + }, { "attribute less than sum of attribute", + types.Int64{Value: 16}, tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ "min_upper": tftypes.NewValue(tftypes.Number, 17), }), @@ -48,6 +64,7 @@ func TestIsAtLeastSumOfValidator_Validate(t *testing.T) { }, { "attribute less than sum of attributes", + types.Int64{Value: 16}, tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ "min_upper": tftypes.NewValue(tftypes.Number, 10), "min_lower": tftypes.NewValue(tftypes.Number, 12), @@ -66,6 +83,7 @@ func TestIsAtLeastSumOfValidator_Validate(t *testing.T) { }, { "a summed attribute is of invalid type", + types.Int64{Value: 16}, tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ "min_upper": tftypes.NewValue(tftypes.String, "17"), }), @@ -82,6 +100,7 @@ Expected Number value, received tftypes.Value with value: tftypes.String<"17">`, }, { "attribute equal to sum of attributes", + types.Int64{Value: 16}, tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ "min_upper": tftypes.NewValue(tftypes.Number, 8), "min_lower": tftypes.NewValue(tftypes.Number, 8), @@ -95,13 +114,13 @@ Expected Number value, received tftypes.Value with value: tftypes.String<"17">`, for _, c := range cases { t.Run(c.name, func(t *testing.T) { + req.AttributeConfig = c.reqAttribConfig req.Config.Raw = c.reqConfigRaw resp := tfsdk.ValidateAttributeResponse{ Diagnostics: diag.Diagnostics{}, } validator := NewIntIsAtLeastSumOfValidator(c.attributesToSum...) - validator.Validate(context.Background(), req, &resp) expectedDiags := diag.Diagnostics{} From ebc35b4da1a1a8513383974da4f07b7a6cc6a6e9 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 23 May 2022 11:08:13 +0100 Subject: [PATCH 61/96] Checking req.AttribConfig type in int at least validator (#177) --- internal/provider/validators.go | 34 ++++++++++--- internal/provider/validators_test.go | 76 ++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 8 deletions(-) diff --git a/internal/provider/validators.go b/internal/provider/validators.go index e9b7e286..183358fa 100644 --- a/internal/provider/validators.go +++ b/internal/provider/validators.go @@ -32,20 +32,38 @@ func (av *intAtLeastValidator) MarkdownDescription(context.Context) string { return "intAtLeastValidator ensures that attribute is at least `minVal`" } -// Validate first determines whether AttributeConfig (attr.Value) can be reflected into types.Int64 and then checks -// that the value is >= minVal. +// Validate runs the following checks: +// 1. Determines whether AttributeConfig (attr.Value) Type is of correct type (i.e., types.Int64). This is required +// because tfsdk.ValueAs will allow a types other than types.Int64 to be supplied as the value. +// 2. Determines if AttributeConfig can be reflected into types.Int64. +// 3. Checks that the value is >= minVal. func (av *intAtLeastValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { - var t types.Int64 + var attrib types.Int64 + + if req.AttributeConfig.Type(ctx) != attrib.Type(ctx) { + pathStr := attrPathToString(req.AttributePath) + + resp.Diagnostics.AddAttributeError( + req.AttributePath, + fmt.Sprintf("Attribute %q is of incorrect type for validator.", pathStr), + fmt.Sprintf("Attribute %q (%s) cannot be used as %s.", pathStr, req.AttributeConfig.Type(ctx), attrib.Type(ctx)), + ) + + return + } - resp.Diagnostics.Append(tfsdk.ValueAs(ctx, req.AttributeConfig, &t)...) + resp.Diagnostics.Append(tfsdk.ValueAs(ctx, req.AttributeConfig, &attrib)...) if resp.Diagnostics.HasError() { return } - if t.Value < av.minVal { - resp.Diagnostics.AddError( - "Validating Int At Least Error", - fmt.Sprintf("Attribute %q (%d) must be at least %d", attrPathToString(req.AttributePath), t.Value, av.minVal), + if attrib.Value < av.minVal { + pathStr := attrPathToString(req.AttributePath) + + resp.Diagnostics.AddAttributeError( + req.AttributePath, + fmt.Sprintf("Attribute %q is less than minimum required.", pathStr), + fmt.Sprintf("Attribute %q (%d) must be at least %d.", attrPathToString(req.AttributePath), attrib.Value, av.minVal), ) } } diff --git a/internal/provider/validators_test.go b/internal/provider/validators_test.go index 4ff3d8b8..d9b8daeb 100644 --- a/internal/provider/validators_test.go +++ b/internal/provider/validators_test.go @@ -12,6 +12,82 @@ import ( "github.com/hashicorp/terraform-plugin-go/tftypes" ) +func TestIntAtLeastValidator_Validate(t *testing.T) { + t.Parallel() + + req := tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("length"), + Config: tfsdk.Config{ + Schema: getPasswordSchemaV1(), + }, + } + + type expectedRespDiags struct { + expectedRespDiagAttrPath *tftypes.AttributePath + expectedRespDiagSummary string + expectedRespDiagDetail string + } + + cases := []struct { + name string + reqAttribConfig attr.Value + reqConfigRaw tftypes.Value + attributesToSum []*tftypes.AttributePath + expectedRespDiags []expectedRespDiags + }{ + { + name: "attribute wrong type", + reqAttribConfig: types.String{Value: "16"}, + expectedRespDiags: []expectedRespDiags{ + { + tftypes.NewAttributePath().WithAttributeName("length"), + `Attribute "length" is of incorrect type for validator.`, + `Attribute "length" (types.StringType) cannot be used as types.Int64Type.`, + }, + }, + }, + { + name: "attribute less than min val", + reqAttribConfig: types.Int64{Value: 5}, + expectedRespDiags: []expectedRespDiags{ + { + tftypes.NewAttributePath().WithAttributeName("length"), + `Attribute "length" is less than minimum required.`, + `Attribute "length" (5) must be at least 10.`, + }, + }, + }, + { + name: "attribute equal to min val", + reqAttribConfig: types.Int64{Value: 10}, + expectedRespDiags: []expectedRespDiags{}, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + req.AttributeConfig = c.reqAttribConfig + req.Config.Raw = c.reqConfigRaw + resp := tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{}, + } + + validator := NewIntAtLeastValidator(10) + validator.Validate(context.Background(), req, &resp) + + expectedDiags := diag.Diagnostics{} + + for _, v := range c.expectedRespDiags { + expectedDiags.AddAttributeError(v.expectedRespDiagAttrPath, v.expectedRespDiagSummary, v.expectedRespDiagDetail) + } + + if !cmp.Equal(expectedDiags, resp.Diagnostics) { + t.Errorf("expecting resp diags: %s, actual resp diags: %s", expectedDiags, resp.Diagnostics) + } + }) + } +} + func TestIsAtLeastSumOfValidator_Validate(t *testing.T) { t.Parallel() From abff1699e226498d75af3b14c48027af6740a89c Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 26 May 2022 11:54:44 +0100 Subject: [PATCH 62/96] Refactoring default plan modifier (#177) --- internal/provider/plan_modifiers.go | 119 ++++++++-------------------- internal/provider/resource_pet.go | 4 +- internal/provider/string.go | 16 ++-- 3 files changed, 44 insertions(+), 95 deletions(-) diff --git a/internal/provider/plan_modifiers.go b/internal/provider/plan_modifiers.go index 20aa19f1..f31a6dab 100644 --- a/internal/provider/plan_modifiers.go +++ b/internal/provider/plan_modifiers.go @@ -2,115 +2,64 @@ package provider import ( "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" ) -// defaultBool accepts a bool and returns a struct that implements the AttributePlanModifier interface. +// newDefaultValueAttributePlanModifier accepts an attr.Value and returns a struct that implements the +// AttributePlanModifier interface. //nolint:unparam // val is always true -func defaultBool(val bool) tfsdk.AttributePlanModifier { - return boolDefault{val} +func newDefaultValueAttributePlanModifier(val attr.Value) tfsdk.AttributePlanModifier { + return &defaultValueAttributePlanModifier{val} } -type boolDefault struct { - val bool +type defaultValueAttributePlanModifier struct { + val attr.Value } -func (d boolDefault) Description(ctx context.Context) string { +func (d *defaultValueAttributePlanModifier) Description(ctx context.Context) string { return "If the plan does not contain a value, a default will be set using val." } -func (d boolDefault) MarkdownDescription(ctx context.Context) string { +func (d *defaultValueAttributePlanModifier) MarkdownDescription(ctx context.Context) string { return "If the plan does not contain a value, a default will be set using `val`." } -// Modify checks that the value of the attribute in the configuration and, if the attribute is Null, assigns the value -// supplied to the boolDefault struct when it was initialised. -func (d boolDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { - var t types.Bool - diags := tfsdk.ValueAs(ctx, req.AttributeConfig, &t) - resp.Diagnostics.Append(diags...) - - if resp.Diagnostics.HasError() { +// Modify checks that the value of the attribute in the configuration and the plan and only assigns the default value if +// the value in the config is null or the value in the plan is not known, or is known but is null. +func (d *defaultValueAttributePlanModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + // TODO: Remove once attr.Value interface includes IsNull. + attribConfigValue, err := req.AttributeConfig.ToTerraformValue(ctx) + if err != nil { + resp.Diagnostics.AddError( + "Default value attribute plan modifier failed", + fmt.Sprintf("Unable to convert attribute config (%s) to terraform value: %s", req.AttributeConfig.Type(ctx).String(), err), + ) return } - if t.Null { - resp.AttributePlan = types.Bool{ - Value: d.val, - } - } -} - -// defaultInt accepts an int64 and returns a struct that implements the AttributePlanModifier interface. -func defaultInt(val int64) tfsdk.AttributePlanModifier { - return intDefault{val} -} - -type intDefault struct { - val int64 -} - -func (d intDefault) Description(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set using val." -} - -func (d intDefault) MarkdownDescription(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set using `val`." -} - -// Modify checks that the value of the attribute in the configuration and, if the attribute is Null, assigns the value -// supplied to the intDefault struct when it was initialised. -func (d intDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { - var t types.Int64 - diags := tfsdk.ValueAs(ctx, req.AttributeConfig, &t) - resp.Diagnostics.Append(diags...) - - if resp.Diagnostics.HasError() { + // Do not set default if the attribute configuration has been set. + if !attribConfigValue.IsNull() { return } - if t.Null { - resp.AttributePlan = types.Int64{ - Null: false, - Value: d.val, - } + // TODO: Remove once attr.Value interface includes IsUnknown. + attribPlanValue, err := req.AttributePlan.ToTerraformValue(ctx) + if err != nil { + resp.Diagnostics.AddError( + "Default value attribute plan modifier failed", + fmt.Sprintf("Unable to convert attribute plan %s to terraform value: %s", req.AttributePlan.Type(ctx).String(), err), + ) + return } -} - -// defaultString accepts a string and returns a struct that implements the AttributePlanModifier interface. -func defaultString(val string) tfsdk.AttributePlanModifier { - return stringDefault{val} -} - -type stringDefault struct { - val string -} -func (d stringDefault) Description(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set." -} - -func (d stringDefault) MarkdownDescription(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set." -} - -// Modify checks that the value of the attribute in the configuration and, if the attribute is Null, assigns the value -// supplied to the stringDefault struct when it was initialised. -func (d stringDefault) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { - var t types.String - diags := tfsdk.ValueAs(ctx, req.AttributeConfig, &t) - resp.Diagnostics.Append(diags...) - - if resp.Diagnostics.HasError() { + // If the attribute plan is "known" and "not null", then a previous plan modifier in the sequence has already been + // applied and, we don't want to overwrite. + if attribPlanValue.IsKnown() && !attribPlanValue.IsNull() { return } - if t.Null { - resp.AttributePlan = types.String{ - Null: false, - Value: d.val, - } - } + resp.AttributePlan = d.val } diff --git a/internal/provider/resource_pet.go b/internal/provider/resource_pet.go index 178ee595..0e2cf8d7 100644 --- a/internal/provider/resource_pet.go +++ b/internal/provider/resource_pet.go @@ -43,7 +43,7 @@ func (r resourcePetType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnost Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt(2), + newDefaultValueAttributePlanModifier(types.Int64{Value: 2}), }, }, "prefix": { @@ -59,7 +59,7 @@ func (r resourcePetType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnost Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultString("-"), + newDefaultValueAttributePlanModifier(types.String{Value: "-"}), }, }, "id": { diff --git a/internal/provider/string.go b/internal/provider/string.go index 1db9ac01..8d261e8e 100644 --- a/internal/provider/string.go +++ b/internal/provider/string.go @@ -50,7 +50,7 @@ func passwordStringSchema() tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultBool(true), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), }, }, @@ -61,7 +61,7 @@ func passwordStringSchema() tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultBool(true), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), }, }, @@ -72,7 +72,7 @@ func passwordStringSchema() tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultBool(true), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), }, }, @@ -83,7 +83,7 @@ func passwordStringSchema() tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultBool(true), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), }, }, @@ -94,7 +94,7 @@ func passwordStringSchema() tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt(0), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), }, }, @@ -105,7 +105,7 @@ func passwordStringSchema() tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt(0), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), }, }, @@ -116,7 +116,7 @@ func passwordStringSchema() tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt(0), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), }, }, @@ -127,7 +127,7 @@ func passwordStringSchema() tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - defaultInt(0), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), }, }, From 353aae7436d015639bd74fdf77a3095cc923e5a0 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 26 May 2022 15:17:45 +0100 Subject: [PATCH 63/96] Refactoring validators and tests (#177) --- internal/provider/validators.go | 52 +++++------ internal/provider/validators_test.go | 134 +++++++++++++-------------- 2 files changed, 88 insertions(+), 98 deletions(-) diff --git a/internal/provider/validators.go b/internal/provider/validators.go index 183358fa..0f0cd697 100644 --- a/internal/provider/validators.go +++ b/internal/provider/validators.go @@ -33,25 +33,25 @@ func (av *intAtLeastValidator) MarkdownDescription(context.Context) string { } // Validate runs the following checks: -// 1. Determines whether AttributeConfig (attr.Value) Type is of correct type (i.e., types.Int64). This is required -// because tfsdk.ValueAs will allow a types other than types.Int64 to be supplied as the value. -// 2. Determines if AttributeConfig can be reflected into types.Int64. -// 3. Checks that the value is >= minVal. +// 1. Determines if AttributeConfig can be reflected into types.Int64. +// 2. Checks that the value is >= minVal. func (av *intAtLeastValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { - var attrib types.Int64 - - if req.AttributeConfig.Type(ctx) != attrib.Type(ctx) { - pathStr := attrPathToString(req.AttributePath) - - resp.Diagnostics.AddAttributeError( - req.AttributePath, - fmt.Sprintf("Attribute %q is of incorrect type for validator.", pathStr), - fmt.Sprintf("Attribute %q (%s) cannot be used as %s.", pathStr, req.AttributeConfig.Type(ctx), attrib.Type(ctx)), + // TODO: Remove once attr.Value interface includes IsNull. + attribConfigValue, err := req.AttributeConfig.ToTerraformValue(ctx) + if err != nil { + resp.Diagnostics.AddError( + "Int at least validator failed", + fmt.Sprintf("Unable to convert attribute config (%s) to terraform value: %s", req.AttributeConfig.Type(ctx).String(), err), ) + return + } + if attribConfigValue.IsNull() || !attribConfigValue.IsKnown() { return } + var attrib types.Int64 + resp.Diagnostics.Append(tfsdk.ValueAs(ctx, req.AttributeConfig, &attrib)...) if resp.Diagnostics.HasError() { return @@ -90,30 +90,30 @@ func (av *intIsAtLeastSumOfValidator) MarkdownDescription(context.Context) strin } // Validate runs the following checks: -// 1. Determines whether AttributeConfig (attr.Value) Type is of correct type (i.e., types.Int64). This is required -// because tfsdk.ValueAs will allow a types other than types.Int64 to be supplied as the value. -// 2. Determines if AttributeConfig can be reflected into types.Int64. -// 3. Checks that the AttributeConfig value is >= sum of values of the attributes defined in attributesToSum. +// 1. Determines if AttributeConfig can be reflected into types.Int64. +// 2. Checks that the AttributeConfig value is >= sum of values of the attributes defined in attributesToSum. func (av *intIsAtLeastSumOfValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { tflog.Debug(ctx, "Validating that attribute has a value at least equal to the attributes to sum", map[string]interface{}{ "attribute": attrPathToString(req.AttributePath), "attributesToSum": av.attributesToSum, }) - var attrib types.Int64 - - if req.AttributeConfig.Type(ctx) != attrib.Type(ctx) { - pathStr := attrPathToString(req.AttributePath) - - resp.Diagnostics.AddAttributeError( - req.AttributePath, - fmt.Sprintf("Attribute %q is of incorrect type for validator.", pathStr), - fmt.Sprintf("Attribute %q (%s) cannot be used as %s.", pathStr, req.AttributeConfig.Type(ctx), attrib.Type(ctx)), + // TODO: Remove once attr.Value interface includes IsNull. + attribConfigValue, err := req.AttributeConfig.ToTerraformValue(ctx) + if err != nil { + resp.Diagnostics.AddError( + "Int at least sum of validator failed", + fmt.Sprintf("Unable to convert attribute config (%s) to terraform value: %s", req.AttributeConfig.Type(ctx).String(), err), ) + return + } + if attribConfigValue.IsNull() || !attribConfigValue.IsKnown() { return } + var attrib types.Int64 + resp.Diagnostics.Append(tfsdk.ValueAs(ctx, req.AttributeConfig, &attrib)...) if resp.Diagnostics.HasError() { return diff --git a/internal/provider/validators_test.go b/internal/provider/validators_test.go index d9b8daeb..bde0ef1a 100644 --- a/internal/provider/validators_test.go +++ b/internal/provider/validators_test.go @@ -22,52 +22,39 @@ func TestIntAtLeastValidator_Validate(t *testing.T) { }, } - type expectedRespDiags struct { - expectedRespDiagAttrPath *tftypes.AttributePath - expectedRespDiagSummary string - expectedRespDiagDetail string - } - cases := []struct { - name string - reqAttribConfig attr.Value - reqConfigRaw tftypes.Value - attributesToSum []*tftypes.AttributePath - expectedRespDiags []expectedRespDiags + name string + reqAttribConfig attr.Value + expectDiag bool + expectedValidatorDiags diag.Diagnostics }{ { name: "attribute wrong type", reqAttribConfig: types.String{Value: "16"}, - expectedRespDiags: []expectedRespDiags{ - { - tftypes.NewAttributePath().WithAttributeName("length"), - `Attribute "length" is of incorrect type for validator.`, - `Attribute "length" (types.StringType) cannot be used as types.Int64Type.`, - }, - }, + expectDiag: true, }, { name: "attribute less than min val", reqAttribConfig: types.Int64{Value: 5}, - expectedRespDiags: []expectedRespDiags{ - { + expectDiag: true, + expectedValidatorDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( tftypes.NewAttributePath().WithAttributeName("length"), `Attribute "length" is less than minimum required.`, `Attribute "length" (5) must be at least 10.`, - }, + ), }, }, { - name: "attribute equal to min val", - reqAttribConfig: types.Int64{Value: 10}, - expectedRespDiags: []expectedRespDiags{}, + name: "attribute equal to min val", + reqAttribConfig: types.Int64{Value: 10}, + expectDiag: false, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { req.AttributeConfig = c.reqAttribConfig - req.Config.Raw = c.reqConfigRaw resp := tfsdk.ValidateAttributeResponse{ Diagnostics: diag.Diagnostics{}, } @@ -75,14 +62,17 @@ func TestIntAtLeastValidator_Validate(t *testing.T) { validator := NewIntAtLeastValidator(10) validator.Validate(context.Background(), req, &resp) - expectedDiags := diag.Diagnostics{} - - for _, v := range c.expectedRespDiags { - expectedDiags.AddAttributeError(v.expectedRespDiagAttrPath, v.expectedRespDiagSummary, v.expectedRespDiagDetail) + if c.expectDiag { + if len(resp.Diagnostics) != 1 { + t.Errorf("expecting resp diags len: 1, actual resp diags len: %d", len(resp.Diagnostics)) + } } - if !cmp.Equal(expectedDiags, resp.Diagnostics) { - t.Errorf("expecting resp diags: %s, actual resp diags: %s", expectedDiags, resp.Diagnostics) + // Only test the contents of diags that are explicitly under the control of the validator. + if c.expectedValidatorDiags != nil { + if !cmp.Equal(c.expectedValidatorDiags, resp.Diagnostics) { + t.Errorf("expecting resp diags: %s, actual resp diags: %s", c.expectedValidatorDiags, resp.Diagnostics) + } } }) } @@ -99,29 +89,18 @@ func TestIsAtLeastSumOfValidator_Validate(t *testing.T) { }, } - type expectedRespDiags struct { - expectedRespDiagAttrPath *tftypes.AttributePath - expectedRespDiagSummary string - expectedRespDiagDetail string - } - cases := []struct { - name string - reqAttribConfig attr.Value - reqConfigRaw tftypes.Value - attributesToSum []*tftypes.AttributePath - expectedRespDiags []expectedRespDiags + name string + reqAttribConfig attr.Value + reqConfigRaw tftypes.Value + attributesToSum []*tftypes.AttributePath + expectDiag bool + expectedValidatorDiags diag.Diagnostics }{ { name: "attribute wrong type", reqAttribConfig: types.String{Value: "16"}, - expectedRespDiags: []expectedRespDiags{ - { - tftypes.NewAttributePath().WithAttributeName("length"), - `Attribute "length" is of incorrect type for validator.`, - `Attribute "length" (types.StringType) cannot be used as types.Int64Type.`, - }, - }, + expectDiag: true, }, { "attribute less than sum of attribute", @@ -129,13 +108,16 @@ func TestIsAtLeastSumOfValidator_Validate(t *testing.T) { tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ "min_upper": tftypes.NewValue(tftypes.Number, 17), }), - []*tftypes.AttributePath{tftypes.NewAttributePath().WithAttributeName("min_upper")}, - []expectedRespDiags{ - { + []*tftypes.AttributePath{ + tftypes.NewAttributePath().WithAttributeName("min_upper"), + }, + true, + diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( tftypes.NewAttributePath().WithAttributeName("length"), `Attribute "length" is less than summed attributes.`, `Attribute "length" (16) cannot be less than min_upper (17).`, - }, + ), }, }, { @@ -149,12 +131,13 @@ func TestIsAtLeastSumOfValidator_Validate(t *testing.T) { tftypes.NewAttributePath().WithAttributeName("min_upper"), tftypes.NewAttributePath().WithAttributeName("min_lower"), }, - []expectedRespDiags{ - { + true, + diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( tftypes.NewAttributePath().WithAttributeName("length"), `Attribute "length" is less than summed attributes.`, `Attribute "length" (16) cannot be less than min_upper + min_lower (22).`, - }, + ), }, }, { @@ -163,28 +146,32 @@ func TestIsAtLeastSumOfValidator_Validate(t *testing.T) { tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ "min_upper": tftypes.NewValue(tftypes.String, "17"), }), - []*tftypes.AttributePath{tftypes.NewAttributePath().WithAttributeName("min_upper")}, - []expectedRespDiags{ - { + []*tftypes.AttributePath{ + tftypes.NewAttributePath().WithAttributeName("min_upper"), + }, + true, + diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( tftypes.NewAttributePath().WithAttributeName("min_upper"), `Int64 Type Validation Error`, `An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. Please report the following to the provider developer: Expected Number value, received tftypes.Value with value: tftypes.String<"17">`, - }, + ), }, }, { - "attribute equal to sum of attributes", - types.Int64{Value: 16}, - tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ + name: "attribute equal to sum of attributes", + reqAttribConfig: types.Int64{Value: 16}, + reqConfigRaw: tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ "min_upper": tftypes.NewValue(tftypes.Number, 8), "min_lower": tftypes.NewValue(tftypes.Number, 8), }), - []*tftypes.AttributePath{ + attributesToSum: []*tftypes.AttributePath{ tftypes.NewAttributePath().WithAttributeName("min_upper"), - tftypes.NewAttributePath().WithAttributeName("min_lower")}, - []expectedRespDiags{}, + tftypes.NewAttributePath().WithAttributeName("min_lower"), + }, + expectDiag: false, }, } @@ -199,14 +186,17 @@ Expected Number value, received tftypes.Value with value: tftypes.String<"17">`, validator := NewIntIsAtLeastSumOfValidator(c.attributesToSum...) validator.Validate(context.Background(), req, &resp) - expectedDiags := diag.Diagnostics{} - - for _, v := range c.expectedRespDiags { - expectedDiags.AddAttributeError(v.expectedRespDiagAttrPath, v.expectedRespDiagSummary, v.expectedRespDiagDetail) + if c.expectDiag { + if len(resp.Diagnostics) != 1 { + t.Errorf("expecting resp diags len: 1, actual resp diags len: %d", len(resp.Diagnostics)) + } } - if !cmp.Equal(expectedDiags, resp.Diagnostics) { - t.Errorf("expecting resp diags: %s, actual resp diags: %s", expectedDiags, resp.Diagnostics) + // Only test the contents of diags that are explicitly under the control of the validator. + if c.expectedValidatorDiags != nil { + if !cmp.Equal(c.expectedValidatorDiags, resp.Diagnostics) { + t.Errorf("expecting resp diags: %s, actual resp diags: %s", c.expectedValidatorDiags, resp.Diagnostics) + } } }) } From 09450f28da5e986d215e512f3aad3832a265bfd8 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 27 May 2022 12:39:14 +0100 Subject: [PATCH 64/96] Modifying state upgrader for V0 to V2 --- internal/provider/models.go | 18 ++++++++++++ internal/provider/resource_password.go | 33 ++++++++++++++++++---- internal/provider/resource_pasword_test.go | 14 +++++---- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/internal/provider/models.go b/internal/provider/models.go index 69cf3563..002dc7b9 100644 --- a/internal/provider/models.go +++ b/internal/provider/models.go @@ -22,6 +22,24 @@ type IntegerModel struct { Result types.Int64 `tfsdk:"result"` } +type PasswordModelV2 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Number types.Bool `tfsdk:"number"` + Numeric types.Bool `tfsdk:"numeric"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Result types.String `tfsdk:"result"` + BcryptHash types.String `tfsdk:"bcrypt_hash"` +} + type PasswordModelV1 struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 2e937a1b..04559150 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -53,11 +53,30 @@ func (r resourcePassword) UpgradeState(context.Context) map[int64]tfsdk.Resource return map[int64]tfsdk.ResourceStateUpgrader{ 0: { PriorSchema: &passwordSchemaV0, - StateUpgrader: migratePasswordStateV0toV1, + StateUpgrader: migratePasswordStateV0toV2, }, } } +func getPasswordSchemaV2() tfsdk.Schema { + passwordSchema := getPasswordSchemaV1() + + passwordSchema.Attributes["numeric"] = tfsdk.Attribute{ + Description: "Include numeric characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + } + + passwordSchema.Version = 2 + + return passwordSchema +} + func getPasswordSchemaV1() tfsdk.Schema { passwordSchema := getPasswordSchemaV0() @@ -179,7 +198,7 @@ func importPassword(ctx context.Context, req tfsdk.ImportResourceStateRequest, r } } -func migratePasswordStateV0toV1(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { +func migratePasswordStateV0toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { var passwordDataV0 PasswordModelV0 resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV0)...) @@ -187,13 +206,14 @@ func migratePasswordStateV0toV1(ctx context.Context, req tfsdk.UpgradeResourceSt return } - passwordDataV1 := PasswordModelV1{ + passwordDataV2 := PasswordModelV2{ Keepers: passwordDataV0.Keepers, Length: passwordDataV0.Length, Special: passwordDataV0.Special, Upper: passwordDataV0.Upper, Lower: passwordDataV0.Lower, Number: passwordDataV0.Number, + Numeric: passwordDataV0.Number, MinNumeric: passwordDataV0.MinNumeric, MinLower: passwordDataV0.MinLower, MinSpecial: passwordDataV0.MinSpecial, @@ -202,15 +222,16 @@ func migratePasswordStateV0toV1(ctx context.Context, req tfsdk.UpgradeResourceSt ID: passwordDataV0.ID, } - hash, err := generateHash(passwordDataV1.Result.Value) + hash, err := generateHash(passwordDataV2.Result.Value) if err != nil { resp.Diagnostics.Append(hashGenerationError(err.Error())...) return } - passwordDataV1.BcryptHash.Value = hash + passwordDataV2.BcryptHash.Value = hash - resp.Diagnostics.Append(resp.State.Set(ctx, passwordDataV1)...) + diags := resp.State.Set(ctx, passwordDataV2) + resp.Diagnostics.Append(diags...) } func generateHash(toHash string) (string, error) { diff --git a/internal/provider/resource_pasword_test.go b/internal/provider/resource_pasword_test.go index 240738a9..e6f095c9 100644 --- a/internal/provider/resource_pasword_test.go +++ b/internal/provider/resource_pasword_test.go @@ -133,13 +133,13 @@ func TestMigratePasswordStateV0toV1(t *testing.T) { resp := &tfsdk.UpgradeResourceStateResponse{ State: tfsdk.State{ - Schema: getPasswordSchemaV1(), + Schema: getPasswordSchemaV2(), }, } - migratePasswordStateV0toV1(context.Background(), req, resp) + migratePasswordStateV0toV2(context.Background(), req, resp) - expected := PasswordModelV1{ + expected := PasswordModelV2{ ID: types.String{Value: "none"}, Keepers: types.Map{Null: true, ElemType: types.StringType}, Length: types.Int64{Value: 16}, @@ -147,6 +147,7 @@ func TestMigratePasswordStateV0toV1(t *testing.T) { Upper: types.Bool{Value: true}, Lower: types.Bool{Value: true}, Number: types.Bool{Value: true}, + Numeric: types.Bool{Value: true}, MinNumeric: types.Int64{Value: 0}, MinUpper: types.Int64{Value: 0}, MinLower: types.Int64{Value: 0}, @@ -155,8 +156,11 @@ func TestMigratePasswordStateV0toV1(t *testing.T) { Result: types.String{Value: "DZy_3*tnonj%Q%Yx"}, } - actual := PasswordModelV1{} - resp.State.Get(context.Background(), &actual) + actual := PasswordModelV2{} + diags := resp.State.Get(context.Background(), &actual) + if diags.HasError() { + t.Errorf("error getting state: %v", diags) + } err := bcrypt.CompareHashAndPassword([]byte(actual.BcryptHash.Value), []byte(actual.Result.Value)) if err != nil { From 522bef804da0e92080e28cecc1edc108e5e14ef1 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 27 May 2022 12:46:48 +0100 Subject: [PATCH 65/96] Adding state upgrader for V1 to V2 --- internal/provider/resource_password.go | 34 ++++++++++++ internal/provider/resource_pasword_test.go | 64 +++++++++++++++++++++- 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 04559150..5bfba7ee 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -49,12 +49,17 @@ func (r resourcePassword) ImportState(ctx context.Context, req tfsdk.ImportResou func (r resourcePassword) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { passwordSchemaV0 := getPasswordSchemaV0() + passwordSchemaV1 := getPasswordSchemaV1() return map[int64]tfsdk.ResourceStateUpgrader{ 0: { PriorSchema: &passwordSchemaV0, StateUpgrader: migratePasswordStateV0toV2, }, + 1: { + PriorSchema: &passwordSchemaV1, + StateUpgrader: migratePasswordStateV1toV2, + }, } } @@ -234,6 +239,35 @@ func migratePasswordStateV0toV2(ctx context.Context, req tfsdk.UpgradeResourceSt resp.Diagnostics.Append(diags...) } +func migratePasswordStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { + var passwordDataV1 PasswordModelV1 + + resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV1)...) + if resp.Diagnostics.HasError() { + return + } + + passwordDataV2 := PasswordModelV2{ + Keepers: passwordDataV1.Keepers, + Length: passwordDataV1.Length, + Special: passwordDataV1.Special, + Upper: passwordDataV1.Upper, + Lower: passwordDataV1.Lower, + Number: passwordDataV1.Number, + Numeric: passwordDataV1.Number, + MinNumeric: passwordDataV1.MinNumeric, + MinLower: passwordDataV1.MinLower, + MinSpecial: passwordDataV1.MinSpecial, + OverrideSpecial: passwordDataV1.OverrideSpecial, + BcryptHash: passwordDataV1.BcryptHash, + Result: passwordDataV1.Result, + ID: passwordDataV1.ID, + } + + diags := resp.State.Set(ctx, passwordDataV2) + resp.Diagnostics.Append(diags...) +} + func generateHash(toHash string) (string, error) { hash, err := bcrypt.GenerateFromPassword([]byte(toHash), bcrypt.DefaultCost) diff --git a/internal/provider/resource_pasword_test.go b/internal/provider/resource_pasword_test.go index e6f095c9..5d04d9a8 100644 --- a/internal/provider/resource_pasword_test.go +++ b/internal/provider/resource_pasword_test.go @@ -107,7 +107,7 @@ func TestAccResourcePasswordMin(t *testing.T) { }) } -func TestMigratePasswordStateV0toV1(t *testing.T) { +func TestMigratePasswordStateV0toV2(t *testing.T) { raw := tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ "id": tftypes.NewValue(tftypes.String, "none"), "keepers": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), @@ -174,3 +174,65 @@ func TestMigratePasswordStateV0toV1(t *testing.T) { t.Errorf("expected: %+v, got: %+v", expected, actual) } } + +func TestMigratePasswordStateV1toV2(t *testing.T) { + raw := tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "none"), + "keepers": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), + "length": tftypes.NewValue(tftypes.Number, 16), + "lower": tftypes.NewValue(tftypes.Bool, true), + "min_lower": tftypes.NewValue(tftypes.Number, 0), + "min_numeric": tftypes.NewValue(tftypes.Number, 0), + "min_special": tftypes.NewValue(tftypes.Number, 0), + "min_upper": tftypes.NewValue(tftypes.Number, 0), + "number": tftypes.NewValue(tftypes.Bool, true), + "override_special": tftypes.NewValue(tftypes.String, "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"), + "result": tftypes.NewValue(tftypes.String, "DZy_3*tnonj%Q%Yx"), + "special": tftypes.NewValue(tftypes.Bool, true), + "upper": tftypes.NewValue(tftypes.Bool, true), + "bcrypt_hash": tftypes.NewValue(tftypes.String, "bcrypt_hash"), + }) + + req := tfsdk.UpgradeResourceStateRequest{ + State: &tfsdk.State{ + Raw: raw, + Schema: getPasswordSchemaV1(), + }, + } + + resp := &tfsdk.UpgradeResourceStateResponse{ + State: tfsdk.State{ + Schema: getPasswordSchemaV2(), + }, + } + + migratePasswordStateV1toV2(context.Background(), req, resp) + + expected := PasswordModelV2{ + ID: types.String{Value: "none"}, + Keepers: types.Map{Null: true, ElemType: types.StringType}, + Length: types.Int64{Value: 16}, + Special: types.Bool{Value: true}, + Upper: types.Bool{Value: true}, + Lower: types.Bool{Value: true}, + Number: types.Bool{Value: true}, + Numeric: types.Bool{Value: true}, + MinNumeric: types.Int64{Value: 0}, + MinUpper: types.Int64{Value: 0}, + MinLower: types.Int64{Value: 0}, + MinSpecial: types.Int64{Value: 0}, + OverrideSpecial: types.String{Value: "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"}, + BcryptHash: types.String{Value: "bcrypt_hash"}, + Result: types.String{Value: "DZy_3*tnonj%Q%Yx"}, + } + + actual := PasswordModelV2{} + diags := resp.State.Get(context.Background(), &actual) + if diags.HasError() { + t.Errorf("error getting state: %v", diags) + } + + if !cmp.Equal(expected, actual) { + t.Errorf("expected: %+v, got: %+v", expected, actual) + } +} From 95fdaf0c0f04230e33181f556eda1a05c2b7d226 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 8 Jun 2022 12:59:03 +0100 Subject: [PATCH 66/96] Reinstating test (#177) --- internal/provider/resource_password_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/provider/resource_password_test.go b/internal/provider/resource_password_test.go index b307fde8..c13be898 100644 --- a/internal/provider/resource_password_test.go +++ b/internal/provider/resource_password_test.go @@ -138,8 +138,7 @@ func TestAccResourcePassword_StateUpgraders(t *testing.T) { }, afterStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "false"), - resource.TestCheckResourceAttr("random_password.default", "numeric", "false"), - //resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), }, }, { From d737c19fe7058feae937ed1dabd2cb7b5b37d3cb Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 8 Jun 2022 13:14:41 +0100 Subject: [PATCH 67/96] Adding deprecation message to number and adding numeric attribute to string schema --- internal/provider/resource_string.go | 11 +++++++++++ internal/provider/string.go | 10 ++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index 37f06e83..0c45498b 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -64,6 +64,17 @@ func getStringSchemaV1() tfsdk.Schema { stringSchema.Attributes["id"] = id } + stringSchema.Attributes["numeric"] = tfsdk.Attribute{ + Description: "Include numeric characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + } + return stringSchema } diff --git a/internal/provider/string.go b/internal/provider/string.go index dd6dafc9..a73732f3 100644 --- a/internal/provider/string.go +++ b/internal/provider/string.go @@ -77,14 +77,16 @@ func passwordStringSchema() tfsdk.Schema { }, "number": { - Description: "Include numeric characters in the result. Default value is `true`.", - Type: types.BoolType, - Optional: true, - Computed: true, + Description: "Include numeric characters in the result. Default value is `true`. " + + "**NOTE**: This is deprecated, use `numeric` instead.", + Type: types.BoolType, + Optional: true, + Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Bool{Value: true}), }, + DeprecationMessage: "**NOTE**: This is deprecated, use `numeric` instead.", }, "min_numeric": { From f38518a889bae4fc49a84455e0f1b73a955a7dc2 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 8 Jun 2022 13:31:09 +0100 Subject: [PATCH 68/96] Adding numeric to string model (#177) --- internal/provider/models.go | 19 ++++++++++++++++++- internal/provider/resource_password.go | 5 ++--- internal/provider/resource_string.go | 9 +++++---- internal/provider/string.go | 1 - 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/internal/provider/models.go b/internal/provider/models.go index 002dc7b9..c9da0e7f 100644 --- a/internal/provider/models.go +++ b/internal/provider/models.go @@ -90,7 +90,24 @@ type ShuffleModel struct { Result types.List `tfsdk:"result"` } -type StringModel struct { +type StringModelV1 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Number types.Bool `tfsdk:"number"` + Numeric types.Bool `tfsdk:"numeric"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Result types.String `tfsdk:"result"` +} + +type StringModelV0 struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` Length types.Int64 `tfsdk:"length"` diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index bf7ee59b..509c18d4 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -138,8 +138,7 @@ func createPassword(ctx context.Context, req tfsdk.CreateResourceRequest, resp * minUpper: plan.MinUpper.Value, lower: plan.Lower.Value, minLower: plan.MinLower.Value, - number: plan.Number.Value, - numeric: plan.Number.Value, + numeric: plan.Numeric.Value, minNumeric: plan.MinNumeric.Value, special: plan.Special.Value, minSpecial: plan.MinSpecial.Value, @@ -160,7 +159,7 @@ func createPassword(ctx context.Context, req tfsdk.CreateResourceRequest, resp * Upper: types.Bool{Value: plan.Upper.Value}, Lower: types.Bool{Value: plan.Lower.Value}, Number: types.Bool{Value: plan.Number.Value}, - Numeric: types.Bool{Value: plan.Number.Value}, + Numeric: types.Bool{Value: plan.Numeric.Value}, MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, MinUpper: types.Int64{Value: plan.MinUpper.Value}, MinLower: types.Int64{Value: plan.MinLower.Value}, diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index 0c45498b..f9de6da6 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -79,7 +79,7 @@ func getStringSchemaV1() tfsdk.Schema { } func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan StringModel + var plan StringModelV1 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -93,7 +93,7 @@ func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tf minUpper: plan.MinUpper.Value, lower: plan.Lower.Value, minLower: plan.MinLower.Value, - number: plan.Number.Value, + numeric: plan.Numeric.Value, minNumeric: plan.MinNumeric.Value, special: plan.Special.Value, minSpecial: plan.MinSpecial.Value, @@ -106,7 +106,7 @@ func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tf return } - state := StringModel{ + state := StringModelV1{ ID: types.String{Value: string(result)}, Keepers: plan.Keepers, Length: types.Int64{Value: plan.Length.Value}, @@ -114,6 +114,7 @@ func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tf Upper: types.Bool{Value: plan.Upper.Value}, Lower: types.Bool{Value: plan.Lower.Value}, Number: types.Bool{Value: plan.Number.Value}, + Numeric: types.Bool{Value: plan.Numeric.Value}, MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, MinUpper: types.Int64{Value: plan.MinUpper.Value}, MinLower: types.Int64{Value: plan.MinLower.Value}, @@ -132,7 +133,7 @@ func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tf func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { id := req.ID - state := StringModel{ + state := StringModelV0{ ID: types.String{Value: id}, Result: types.String{Value: id}, } diff --git a/internal/provider/string.go b/internal/provider/string.go index a73732f3..cca3152d 100644 --- a/internal/provider/string.go +++ b/internal/provider/string.go @@ -165,7 +165,6 @@ type randomStringParams struct { minUpper int64 lower bool minLower int64 - number bool numeric bool minNumeric int64 special bool From 55ec3efc771228a82a3047475ed7e77af195e066 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 8 Jun 2022 15:40:20 +0100 Subject: [PATCH 69/96] Adding plan modifier for handling keeping number and numeric in-sync (#177) --- internal/provider/plan_modifiers.go | 73 +++++++++++++++++++++ internal/provider/resource_password.go | 15 ++++- internal/provider/resource_password_test.go | 3 +- 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/internal/provider/plan_modifiers.go b/internal/provider/plan_modifiers.go index f31a6dab..22b577d4 100644 --- a/internal/provider/plan_modifiers.go +++ b/internal/provider/plan_modifiers.go @@ -6,6 +6,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" ) // newDefaultValueAttributePlanModifier accepts an attr.Value and returns a struct that implements the @@ -63,3 +65,74 @@ func (d *defaultValueAttributePlanModifier) Modify(ctx context.Context, req tfsd resp.AttributePlan = d.val } + +func newNumberNumericAttributePlanModifier() tfsdk.AttributePlanModifier { + return &numberNumericAttributePlanModifier{} +} + +type numberNumericAttributePlanModifier struct { +} + +func (d *numberNumericAttributePlanModifier) Description(ctx context.Context) string { + return "" +} + +func (d *numberNumericAttributePlanModifier) MarkdownDescription(ctx context.Context) string { + return "" +} + +func (d *numberNumericAttributePlanModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + numberConfig := types.Bool{} + diags := req.Config.GetAttribute(ctx, tftypes.NewAttributePath().WithAttributeName("number"), &numberConfig) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + numericConfig := types.Bool{} + req.Config.GetAttribute(ctx, tftypes.NewAttributePath().WithAttributeName("numeric"), &numericConfig) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if !numberConfig.Null && !numericConfig.Null { + resp.Diagnostics.AddError( + "Number numeric attribute plan modifier failed", + "Cannot specify both number and numeric in config", + ) + return + } + + numberPlan := types.Bool{} + diags = req.Config.GetAttribute(ctx, tftypes.NewAttributePath().WithAttributeName("number"), &numberPlan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + numericPlan := types.Bool{} + req.Config.GetAttribute(ctx, tftypes.NewAttributePath().WithAttributeName("numeric"), &numericPlan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Default to true for both number and numeric when both are null. + if numberPlan.Null && numericPlan.Null { + resp.AttributePlan = types.Bool{Value: true} + return + } + + // Default to using value for numeric if number is null + if numberPlan.Null && !numericPlan.Null { + resp.AttributePlan = numericPlan + return + } + + // Default to using value for number if numeric is null + if !numberPlan.Null && numericPlan.Null { + resp.AttributePlan = numberPlan + return + } +} diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 509c18d4..954f0c85 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -66,6 +66,19 @@ func (r resourcePassword) UpgradeState(context.Context) map[int64]tfsdk.Resource func getPasswordSchemaV2() tfsdk.Schema { passwordSchema := getPasswordSchemaV1() + passwordSchema.Attributes["number"] = tfsdk.Attribute{ + Description: "Include numeric characters in the result. Default value is `true`. " + + "**NOTE**: This is deprecated, use `numeric` instead.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newNumberNumericAttributePlanModifier(), + }, + DeprecationMessage: "**NOTE**: This is deprecated, use `numeric` instead.", + } + passwordSchema.Attributes["numeric"] = tfsdk.Attribute{ Description: "Include numeric characters in the result. Default value is `true`.", Type: types.BoolType, @@ -73,7 +86,7 @@ func getPasswordSchemaV2() tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + newNumberNumericAttributePlanModifier(), }, } diff --git a/internal/provider/resource_password_test.go b/internal/provider/resource_password_test.go index c13be898..5e3e53f6 100644 --- a/internal/provider/resource_password_test.go +++ b/internal/provider/resource_password_test.go @@ -190,7 +190,8 @@ func TestAccResourcePassword_StateUpgraders(t *testing.T) { }, afterStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "false"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "false"), + //resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), }, }, { From af1d3091ac077b763ac1f3d93201746177f8b1c3 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 8 Jun 2022 16:37:54 +0100 Subject: [PATCH 70/96] Altering plan modifiers used by random_string (#177) --- internal/provider/resource_string.go | 15 ++++++++++++++- internal/provider/resource_string_test.go | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index f9de6da6..18bb8fbf 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -64,6 +64,19 @@ func getStringSchemaV1() tfsdk.Schema { stringSchema.Attributes["id"] = id } + stringSchema.Attributes["number"] = tfsdk.Attribute{ + Description: "Include numeric characters in the result. Default value is `true`. " + + "**NOTE**: This is deprecated, use `numeric` instead.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newNumberNumericAttributePlanModifier(), + }, + DeprecationMessage: "**NOTE**: This is deprecated, use `numeric` instead.", + } + stringSchema.Attributes["numeric"] = tfsdk.Attribute{ Description: "Include numeric characters in the result. Default value is `true`.", Type: types.BoolType, @@ -71,7 +84,7 @@ func getStringSchemaV1() tfsdk.Schema { Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + newNumberNumericAttributePlanModifier(), }, } diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index 70647489..1f79230f 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -97,8 +97,8 @@ func TestAccResourceString_StateUpgraders(t *testing.T) { resource.TestCheckNoResourceAttr("random_string.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - //resource.TestCheckResourceAttr("random_string.default", "number", "true"), - //resource.TestCheckResourceAttrPair("random_string.default", "number", "random_string.default", "numeric"), + resource.TestCheckResourceAttr("random_string.default", "number", "true"), + resource.TestCheckResourceAttrPair("random_string.default", "number", "random_string.default", "numeric"), }, }, { From a5b86bf7c4ceb1c0f6a6c2c9ad661bc8170cf523 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 8 Jun 2022 16:47:51 +0100 Subject: [PATCH 71/96] Fix random_string import --- internal/provider/resource_password_test.go | 3 +-- internal/provider/resource_string.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/provider/resource_password_test.go b/internal/provider/resource_password_test.go index 5e3e53f6..c13be898 100644 --- a/internal/provider/resource_password_test.go +++ b/internal/provider/resource_password_test.go @@ -190,8 +190,7 @@ func TestAccResourcePassword_StateUpgraders(t *testing.T) { }, afterStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "false"), - resource.TestCheckResourceAttr("random_password.default", "numeric", "false"), - //resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), }, }, { diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index 18bb8fbf..ac3fc2c1 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -146,7 +146,7 @@ func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tf func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { id := req.ID - state := StringModelV0{ + state := StringModelV1{ ID: types.String{Value: id}, Result: types.String{Value: id}, } From 01fe7890558c78ffa934c2fd6684917214458139 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 9 Jun 2022 11:43:25 +0100 Subject: [PATCH 72/96] Adding state upgrader to string (#177) --- internal/provider/models.go | 4 +- internal/provider/resource_string.go | 87 ++++++++++++++++++++++------ 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/internal/provider/models.go b/internal/provider/models.go index c9da0e7f..7f9c4bfb 100644 --- a/internal/provider/models.go +++ b/internal/provider/models.go @@ -90,7 +90,7 @@ type ShuffleModel struct { Result types.List `tfsdk:"result"` } -type StringModelV1 struct { +type StringModelV2 struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` Length types.Int64 `tfsdk:"length"` @@ -107,7 +107,7 @@ type StringModelV1 struct { Result types.String `tfsdk:"result"` } -type StringModelV0 struct { +type StringModelV1 struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` Length types.Int64 `tfsdk:"length"` diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index ac3fc2c1..63376fcd 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -11,7 +11,7 @@ import ( type resourceStringType struct{} func (r resourceStringType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { - return getStringSchemaV1(), nil + return getStringSchemaV2(), nil } func (r resourceStringType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { @@ -46,23 +46,19 @@ func (r resourceString) ImportState(ctx context.Context, req tfsdk.ImportResourc importString(ctx, req, resp) } -func getStringSchemaV1() tfsdk.Schema { - stringSchema := passwordStringSchema() +func (r resourceString) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { + stringSchemaV1 := getStringSchemaV1() - stringSchema.Description = "The resource `random_string` generates a random permutation of alphanumeric " + - "characters and optionally special characters.\n" + - "\n" + - "This resource *does* use a cryptographic random number generator.\n" + - "\n" + - "Historically this resource's intended usage has been ambiguous as the original example used " + - "it in a password. For backwards compatibility it will continue to exist. For unique ids please " + - "use [random_id](id.html), for sensitive random values please use [random_password](password.html)." - - id, ok := stringSchema.Attributes["id"] - if ok { - id.Description = "The generated random string." - stringSchema.Attributes["id"] = id + return map[int64]tfsdk.ResourceStateUpgrader{ + 1: { + PriorSchema: &stringSchemaV1, + StateUpgrader: upgradeStringStateV1toV2, + }, } +} + +func getStringSchemaV2() tfsdk.Schema { + stringSchema := getStringSchemaV1() stringSchema.Attributes["number"] = tfsdk.Attribute{ Description: "Include numeric characters in the result. Default value is `true`. " + @@ -88,11 +84,36 @@ func getStringSchemaV1() tfsdk.Schema { }, } + stringSchema.Version = 2 + + return stringSchema +} + +func getStringSchemaV1() tfsdk.Schema { + stringSchema := passwordStringSchema() + + stringSchema.Description = "The resource `random_string` generates a random permutation of alphanumeric " + + "characters and optionally special characters.\n" + + "\n" + + "This resource *does* use a cryptographic random number generator.\n" + + "\n" + + "Historically this resource's intended usage has been ambiguous as the original example used " + + "it in a password. For backwards compatibility it will continue to exist. For unique ids please " + + "use [random_id](id.html), for sensitive random values please use [random_password](password.html)." + + id, ok := stringSchema.Attributes["id"] + if ok { + id.Description = "The generated random string." + stringSchema.Attributes["id"] = id + } + + stringSchema.Version = 1 + return stringSchema } func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan StringModelV1 + var plan StringModelV2 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -119,7 +140,7 @@ func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tf return } - state := StringModelV1{ + state := StringModelV2{ ID: types.String{Value: string(result)}, Keepers: plan.Keepers, Length: types.Int64{Value: plan.Length.Value}, @@ -146,7 +167,7 @@ func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tf func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { id := req.ID - state := StringModelV1{ + state := StringModelV2{ ID: types.String{Value: id}, Result: types.String{Value: id}, } @@ -159,3 +180,31 @@ func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, res return } } + +func upgradeStringStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { + var stringDataV1 StringModelV1 + + resp.Diagnostics.Append(req.State.Get(ctx, &stringDataV1)...) + if resp.Diagnostics.HasError() { + return + } + + stringDataV2 := StringModelV2{ + Keepers: stringDataV1.Keepers, + Length: stringDataV1.Length, + Special: stringDataV1.Special, + Upper: stringDataV1.Upper, + Lower: stringDataV1.Lower, + Number: stringDataV1.Number, + Numeric: stringDataV1.Number, + MinNumeric: stringDataV1.MinNumeric, + MinLower: stringDataV1.MinLower, + MinSpecial: stringDataV1.MinSpecial, + OverrideSpecial: stringDataV1.OverrideSpecial, + Result: stringDataV1.Result, + ID: stringDataV1.ID, + } + + diags := resp.State.Set(ctx, stringDataV2) + resp.Diagnostics.Append(diags...) +} From cdc7b5a7354bda420cf24c95a97ad28292f12e69 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 9 Jun 2022 12:07:46 +0100 Subject: [PATCH 73/96] Extracting full password schema to individual functions (#177) --- internal/provider/resource_password.go | 544 +++++++++++++++++--- internal/provider/resource_password_test.go | 8 +- internal/provider/string.go | 2 +- internal/provider/validators_test.go | 4 +- 4 files changed, 485 insertions(+), 73 deletions(-) diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 954f0c85..1f01b4cc 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -6,13 +6,14 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" "golang.org/x/crypto/bcrypt" ) type resourcePasswordType struct{} func (r resourcePasswordType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { - return getPasswordSchemaV2(), nil + return passwordSchemaV2(), nil } func (r resourcePasswordType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { @@ -48,90 +49,501 @@ func (r resourcePassword) ImportState(ctx context.Context, req tfsdk.ImportResou } func (r resourcePassword) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { - passwordSchemaV0 := getPasswordSchemaV0() - passwordSchemaV1 := getPasswordSchemaV1() + schemaV0 := passwordSchemaV0() + schemaV1 := passwordSchemaV1() return map[int64]tfsdk.ResourceStateUpgrader{ 0: { - PriorSchema: &passwordSchemaV0, + PriorSchema: &schemaV0, StateUpgrader: migratePasswordStateV0toV2, }, 1: { - PriorSchema: &passwordSchemaV1, + PriorSchema: &schemaV1, StateUpgrader: migratePasswordStateV1toV2, }, } } -func getPasswordSchemaV2() tfsdk.Schema { - passwordSchema := getPasswordSchemaV1() - - passwordSchema.Attributes["number"] = tfsdk.Attribute{ - Description: "Include numeric characters in the result. Default value is `true`. " + - "**NOTE**: This is deprecated, use `numeric` instead.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newNumberNumericAttributePlanModifier(), +func passwordSchemaV2() tfsdk.Schema { + return tfsdk.Schema{ + Version: 2, + Description: "Identical to [random_string](string.html) with the exception that the result is " + + "treated as sensitive and, thus, _not_ displayed in console output. Read more about sensitive " + + "data handling in the " + + "[Terraform documentation](https://www.terraform.io/docs/language/state/sensitive-data.html).\n\n" + + "This resource *does* use a cryptographic random number generator.", + Attributes: map[string]tfsdk.Attribute{ + "keepers": { + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + Type: types.MapType{ + ElemType: types.StringType, + }, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + + "length": { + Description: "The length of the string desired. The minimum value for length is 1 and, length " + + "must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`).", + Type: types.Int64Type, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Validators: []tfsdk.AttributeValidator{ + NewIntAtLeastValidator(1), + NewIntIsAtLeastSumOfValidator( + tftypes.NewAttributePath().WithAttributeName("min_upper"), + tftypes.NewAttributePath().WithAttributeName("min_lower"), + tftypes.NewAttributePath().WithAttributeName("min_numeric"), + tftypes.NewAttributePath().WithAttributeName("min_special"), + ), + }, + }, + + "special": { + Description: "Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "upper": { + Description: "Include uppercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "lower": { + Description: "Include lowercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "number": { + Description: "Include numeric characters in the result. Default value is `true`. " + + "**NOTE**: This is deprecated, use `numeric` instead.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newNumberNumericAttributePlanModifier(), + }, + DeprecationMessage: "**NOTE**: This is deprecated, use `numeric` instead.", + }, + + "numeric": { + Description: "Include numeric characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newNumberNumericAttributePlanModifier(), + }, + }, + + "min_numeric": { + Description: "Minimum number of numeric characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_upper": { + Description: "Minimum number of uppercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_lower": { + Description: "Minimum number of lowercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_special": { + Description: "Minimum number of special characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "override_special": { + Description: "Supply your own list of special characters to use for string generation. This " + + "overrides the default character list in the special argument. The `special` argument must " + + "still be set to true for any overwritten characters to be used in generation.", + Type: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, + }, + + "result": { + Description: "The generated random string.", + Type: types.StringType, + Computed: true, + Sensitive: true, + }, + + "bcrypt_hash": { + Description: "A bcrypt hash of the generated random string.", + Type: types.StringType, + Computed: true, + Sensitive: true, + }, + + "id": { + Description: "A static value used internally by Terraform, this should not be referenced in configurations.", + Computed: true, + Type: types.StringType, + }, }, - DeprecationMessage: "**NOTE**: This is deprecated, use `numeric` instead.", } +} - passwordSchema.Attributes["numeric"] = tfsdk.Attribute{ - Description: "Include numeric characters in the result. Default value is `true`.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newNumberNumericAttributePlanModifier(), +func passwordSchemaV1() tfsdk.Schema { + return tfsdk.Schema{ + Version: 1, + Description: "Identical to [random_string](string.html) with the exception that the result is " + + "treated as sensitive and, thus, _not_ displayed in console output. Read more about sensitive " + + "data handling in the " + + "[Terraform documentation](https://www.terraform.io/docs/language/state/sensitive-data.html).\n\n" + + "This resource *does* use a cryptographic random number generator.", + Attributes: map[string]tfsdk.Attribute{ + "keepers": { + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + Type: types.MapType{ + ElemType: types.StringType, + }, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + + "length": { + Description: "The length of the string desired. The minimum value for length is 1 and, length " + + "must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`).", + Type: types.Int64Type, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Validators: []tfsdk.AttributeValidator{ + NewIntAtLeastValidator(1), + NewIntIsAtLeastSumOfValidator( + tftypes.NewAttributePath().WithAttributeName("min_upper"), + tftypes.NewAttributePath().WithAttributeName("min_lower"), + tftypes.NewAttributePath().WithAttributeName("min_numeric"), + tftypes.NewAttributePath().WithAttributeName("min_special"), + ), + }, + }, + + "special": { + Description: "Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "upper": { + Description: "Include uppercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "lower": { + Description: "Include lowercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "number": { + Description: "Include numeric characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "min_numeric": { + Description: "Minimum number of numeric characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_upper": { + Description: "Minimum number of uppercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_lower": { + Description: "Minimum number of lowercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_special": { + Description: "Minimum number of special characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "override_special": { + Description: "Supply your own list of special characters to use for string generation. This " + + "overrides the default character list in the special argument. The `special` argument must " + + "still be set to true for any overwritten characters to be used in generation.", + Type: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, + }, + + "result": { + Description: "The generated random string.", + Type: types.StringType, + Computed: true, + Sensitive: true, + }, + + "bcrypt_hash": { + Description: "A bcrypt hash of the generated random string.", + Type: types.StringType, + Computed: true, + Sensitive: true, + }, + + "id": { + Description: "A static value used internally by Terraform, this should not be referenced in configurations.", + Computed: true, + Type: types.StringType, + }, }, } - - passwordSchema.Version = 2 - - return passwordSchema } -func getPasswordSchemaV1() tfsdk.Schema { - passwordSchema := getPasswordSchemaV0() - - passwordSchema.Attributes["bcrypt_hash"] = tfsdk.Attribute{ - Description: "A bcrypt hash of the generated random string.", - Type: types.StringType, - Computed: true, - Sensitive: true, - } - - passwordSchema.Version = 1 - - return passwordSchema -} - -func getPasswordSchemaV0() tfsdk.Schema { - passwordSchema := passwordStringSchema() - - passwordSchema.Description = "Identical to [random_string](string.html) with the exception that the result is " + - "treated as sensitive and, thus, _not_ displayed in console output. Read more about sensitive " + - "data handling in the " + - "[Terraform documentation](https://www.terraform.io/docs/language/state/sensitive-data.html).\n\n" + - "This resource *does* use a cryptographic random number generator." - - id, ok := passwordSchema.Attributes["id"] - if ok { - id.Description = "A static value used internally by Terraform, this should not be referenced in configurations." - passwordSchema.Attributes["id"] = id - } - - result, ok := passwordSchema.Attributes["result"] - if ok { - result.Sensitive = true - passwordSchema.Attributes["result"] = result +func passwordSchemaV0() tfsdk.Schema { + return tfsdk.Schema{ + Description: "Identical to [random_string](string.html) with the exception that the result is " + + "treated as sensitive and, thus, _not_ displayed in console output. Read more about sensitive " + + "data handling in the " + + "[Terraform documentation](https://www.terraform.io/docs/language/state/sensitive-data.html).\n\n" + + "This resource *does* use a cryptographic random number generator.", + Attributes: map[string]tfsdk.Attribute{ + "keepers": { + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + Type: types.MapType{ + ElemType: types.StringType, + }, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + + "length": { + Description: "The length of the string desired. The minimum value for length is 1 and, length " + + "must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`).", + Type: types.Int64Type, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Validators: []tfsdk.AttributeValidator{ + NewIntAtLeastValidator(1), + NewIntIsAtLeastSumOfValidator( + tftypes.NewAttributePath().WithAttributeName("min_upper"), + tftypes.NewAttributePath().WithAttributeName("min_lower"), + tftypes.NewAttributePath().WithAttributeName("min_numeric"), + tftypes.NewAttributePath().WithAttributeName("min_special"), + ), + }, + }, + + "special": { + Description: "Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "upper": { + Description: "Include uppercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "lower": { + Description: "Include lowercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "number": { + Description: "Include numeric characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "min_numeric": { + Description: "Minimum number of numeric characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_upper": { + Description: "Minimum number of uppercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_lower": { + Description: "Minimum number of lowercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_special": { + Description: "Minimum number of special characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "override_special": { + Description: "Supply your own list of special characters to use for string generation. This " + + "overrides the default character list in the special argument. The `special` argument must " + + "still be set to true for any overwritten characters to be used in generation.", + Type: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, + }, + + "result": { + Description: "The generated random string.", + Type: types.StringType, + Computed: true, + Sensitive: true, + }, + + "id": { + Description: "A static value used internally by Terraform, this should not be referenced in configurations.", + Computed: true, + Type: types.StringType, + }, + }, } - - return passwordSchema } // createPassword currently uses plan.Number.Value for both number and numeric. diff --git a/internal/provider/resource_password_test.go b/internal/provider/resource_password_test.go index c13be898..40272a11 100644 --- a/internal/provider/resource_password_test.go +++ b/internal/provider/resource_password_test.go @@ -356,13 +356,13 @@ func TestMigratePasswordStateV0toV2(t *testing.T) { req := tfsdk.UpgradeResourceStateRequest{ State: &tfsdk.State{ Raw: raw, - Schema: getPasswordSchemaV0(), + Schema: passwordSchemaV0(), }, } resp := &tfsdk.UpgradeResourceStateResponse{ State: tfsdk.State{ - Schema: getPasswordSchemaV2(), + Schema: passwordSchemaV2(), }, } @@ -425,13 +425,13 @@ func TestMigratePasswordStateV1toV2(t *testing.T) { req := tfsdk.UpgradeResourceStateRequest{ State: &tfsdk.State{ Raw: raw, - Schema: getPasswordSchemaV1(), + Schema: passwordSchemaV1(), }, } resp := &tfsdk.UpgradeResourceStateResponse{ State: tfsdk.State{ - Schema: getPasswordSchemaV2(), + Schema: passwordSchemaV2(), }, } diff --git a/internal/provider/string.go b/internal/provider/string.go index cca3152d..d4fcbab7 100644 --- a/internal/provider/string.go +++ b/internal/provider/string.go @@ -12,7 +12,7 @@ import ( // passwordStringSchema contains the common set of Attributes for both password and string resources. // Specific Schema descriptions, result sensitive, id descriptions and additional attributes (e.g., bcrypt_hash) -// are added in getStringSchemaV1 and getPasswordSchemaV1. +// are added in getStringSchemaV1 and passwordSchemaV1. func passwordStringSchema() tfsdk.Schema { return tfsdk.Schema{ Attributes: map[string]tfsdk.Attribute{ diff --git a/internal/provider/validators_test.go b/internal/provider/validators_test.go index bde0ef1a..c9f835e9 100644 --- a/internal/provider/validators_test.go +++ b/internal/provider/validators_test.go @@ -18,7 +18,7 @@ func TestIntAtLeastValidator_Validate(t *testing.T) { req := tfsdk.ValidateAttributeRequest{ AttributePath: tftypes.NewAttributePath().WithAttributeName("length"), Config: tfsdk.Config{ - Schema: getPasswordSchemaV1(), + Schema: passwordSchemaV1(), }, } @@ -85,7 +85,7 @@ func TestIsAtLeastSumOfValidator_Validate(t *testing.T) { AttributePath: tftypes.NewAttributePath().WithAttributeName("length"), AttributeConfig: types.Int64{Value: 16}, Config: tfsdk.Config{ - Schema: getPasswordSchemaV1(), + Schema: passwordSchemaV1(), }, } From 7b45bdcf55dbfffa3812aaee88c5b5d2bd9d0513 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 9 Jun 2022 12:19:31 +0100 Subject: [PATCH 74/96] Extracting full string schema to individual functions (#177) --- internal/provider/resource_string.go | 369 +++++++++++++++++++++++---- internal/provider/string.go | 153 ----------- 2 files changed, 318 insertions(+), 204 deletions(-) diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index 63376fcd..c210907e 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -6,12 +6,13 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" ) type resourceStringType struct{} func (r resourceStringType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { - return getStringSchemaV2(), nil + return stringSchemaV2(), nil } func (r resourceStringType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { @@ -47,69 +48,335 @@ func (r resourceString) ImportState(ctx context.Context, req tfsdk.ImportResourc } func (r resourceString) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { - stringSchemaV1 := getStringSchemaV1() + schemaV1 := stringSchemaV1() return map[int64]tfsdk.ResourceStateUpgrader{ 1: { - PriorSchema: &stringSchemaV1, + PriorSchema: &schemaV1, StateUpgrader: upgradeStringStateV1toV2, }, } } -func getStringSchemaV2() tfsdk.Schema { - stringSchema := getStringSchemaV1() - - stringSchema.Attributes["number"] = tfsdk.Attribute{ - Description: "Include numeric characters in the result. Default value is `true`. " + - "**NOTE**: This is deprecated, use `numeric` instead.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newNumberNumericAttributePlanModifier(), +func stringSchemaV2() tfsdk.Schema { + return tfsdk.Schema{ + Version: 2, + Description: "The resource `random_string` generates a random permutation of alphanumeric " + + "characters and optionally special characters.\n" + + "\n" + + "This resource *does* use a cryptographic random number generator.\n" + + "\n" + + "Historically this resource's intended usage has been ambiguous as the original example used " + + "it in a password. For backwards compatibility it will continue to exist. For unique ids please " + + "use [random_id](id.html), for sensitive random values please use [random_password](password.html).", + Attributes: map[string]tfsdk.Attribute{ + "keepers": { + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + Type: types.MapType{ + ElemType: types.StringType, + }, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + + "length": { + Description: "The length of the string desired. The minimum value for length is 1 and, length " + + "must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`).", + Type: types.Int64Type, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Validators: []tfsdk.AttributeValidator{ + NewIntAtLeastValidator(1), + NewIntIsAtLeastSumOfValidator( + tftypes.NewAttributePath().WithAttributeName("min_upper"), + tftypes.NewAttributePath().WithAttributeName("min_lower"), + tftypes.NewAttributePath().WithAttributeName("min_numeric"), + tftypes.NewAttributePath().WithAttributeName("min_special"), + ), + }, + }, + + "special": { + Description: "Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "upper": { + Description: "Include uppercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "lower": { + Description: "Include lowercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "number": tfsdk.Attribute{ + Description: "Include numeric characters in the result. Default value is `true`. " + + "**NOTE**: This is deprecated, use `numeric` instead.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newNumberNumericAttributePlanModifier(), + }, + DeprecationMessage: "**NOTE**: This is deprecated, use `numeric` instead.", + }, + + "numeric": tfsdk.Attribute{ + Description: "Include numeric characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newNumberNumericAttributePlanModifier(), + }, + }, + + "min_numeric": { + Description: "Minimum number of numeric characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_upper": { + Description: "Minimum number of uppercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_lower": { + Description: "Minimum number of lowercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_special": { + Description: "Minimum number of special characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "override_special": { + Description: "Supply your own list of special characters to use for string generation. This " + + "overrides the default character list in the special argument. The `special` argument must " + + "still be set to true for any overwritten characters to be used in generation.", + Type: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, + }, + + "result": { + Description: "The generated random string.", + Type: types.StringType, + Computed: true, + }, + + "id": { + Description: "The generated random string.", + Computed: true, + Type: types.StringType, + }, }, - DeprecationMessage: "**NOTE**: This is deprecated, use `numeric` instead.", } - - stringSchema.Attributes["numeric"] = tfsdk.Attribute{ - Description: "Include numeric characters in the result. Default value is `true`.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newNumberNumericAttributePlanModifier(), - }, - } - - stringSchema.Version = 2 - - return stringSchema } -func getStringSchemaV1() tfsdk.Schema { - stringSchema := passwordStringSchema() - - stringSchema.Description = "The resource `random_string` generates a random permutation of alphanumeric " + - "characters and optionally special characters.\n" + - "\n" + - "This resource *does* use a cryptographic random number generator.\n" + - "\n" + - "Historically this resource's intended usage has been ambiguous as the original example used " + - "it in a password. For backwards compatibility it will continue to exist. For unique ids please " + - "use [random_id](id.html), for sensitive random values please use [random_password](password.html)." - - id, ok := stringSchema.Attributes["id"] - if ok { - id.Description = "The generated random string." - stringSchema.Attributes["id"] = id +func stringSchemaV1() tfsdk.Schema { + return tfsdk.Schema{ + Version: 1, + Description: "The resource `random_string` generates a random permutation of alphanumeric " + + "characters and optionally special characters.\n" + + "\n" + + "This resource *does* use a cryptographic random number generator.\n" + + "\n" + + "Historically this resource's intended usage has been ambiguous as the original example used " + + "it in a password. For backwards compatibility it will continue to exist. For unique ids please " + + "use [random_id](id.html), for sensitive random values please use [random_password](password.html).", + Attributes: map[string]tfsdk.Attribute{ + "keepers": { + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + Type: types.MapType{ + ElemType: types.StringType, + }, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + }, + + "length": { + Description: "The length of the string desired. The minimum value for length is 1 and, length " + + "must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`).", + Type: types.Int64Type, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Validators: []tfsdk.AttributeValidator{ + NewIntAtLeastValidator(1), + NewIntIsAtLeastSumOfValidator( + tftypes.NewAttributePath().WithAttributeName("min_upper"), + tftypes.NewAttributePath().WithAttributeName("min_lower"), + tftypes.NewAttributePath().WithAttributeName("min_numeric"), + tftypes.NewAttributePath().WithAttributeName("min_special"), + ), + }, + }, + + "special": { + Description: "Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "upper": { + Description: "Include uppercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "lower": { + Description: "Include lowercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "number": { + Description: "Include numeric characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + }, + }, + + "min_numeric": { + Description: "Minimum number of numeric characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_upper": { + Description: "Minimum number of uppercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_lower": { + Description: "Minimum number of lowercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "min_special": { + Description: "Minimum number of special characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + }, + }, + + "override_special": { + Description: "Supply your own list of special characters to use for string generation. This " + + "overrides the default character list in the special argument. The `special` argument must " + + "still be set to true for any overwritten characters to be used in generation.", + Type: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, + }, + + "result": { + Description: "The generated random string.", + Type: types.StringType, + Computed: true, + }, + + "id": { + Description: "The generated random string.", + Computed: true, + Type: types.StringType, + }, + }, } - - stringSchema.Version = 1 - - return stringSchema } func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { diff --git a/internal/provider/string.go b/internal/provider/string.go index d4fcbab7..93e911f7 100644 --- a/internal/provider/string.go +++ b/internal/provider/string.go @@ -4,161 +4,8 @@ import ( "crypto/rand" "math/big" "sort" - - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) -// passwordStringSchema contains the common set of Attributes for both password and string resources. -// Specific Schema descriptions, result sensitive, id descriptions and additional attributes (e.g., bcrypt_hash) -// are added in getStringSchemaV1 and passwordSchemaV1. -func passwordStringSchema() tfsdk.Schema { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "keepers": { - Description: "Arbitrary map of values that, when changed, will trigger recreation of " + - "resource. See [the main provider documentation](../index.html) for more information.", - Type: types.MapType{ - ElemType: types.StringType, - }, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - }, - - "length": { - Description: "The length of the string desired. The minimum value for length is 1 and, length " + - "must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`).", - Type: types.Int64Type, - Required: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Validators: []tfsdk.AttributeValidator{ - NewIntAtLeastValidator(1), - NewIntIsAtLeastSumOfValidator( - tftypes.NewAttributePath().WithAttributeName("min_upper"), - tftypes.NewAttributePath().WithAttributeName("min_lower"), - tftypes.NewAttributePath().WithAttributeName("min_numeric"), - tftypes.NewAttributePath().WithAttributeName("min_special"), - ), - }, - }, - - "special": { - Description: "Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), - }, - }, - - "upper": { - Description: "Include uppercase alphabet characters in the result. Default value is `true`.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), - }, - }, - - "lower": { - Description: "Include lowercase alphabet characters in the result. Default value is `true`.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), - }, - }, - - "number": { - Description: "Include numeric characters in the result. Default value is `true`. " + - "**NOTE**: This is deprecated, use `numeric` instead.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), - }, - DeprecationMessage: "**NOTE**: This is deprecated, use `numeric` instead.", - }, - - "min_numeric": { - Description: "Minimum number of numeric characters in the result. Default value is `0`.", - Type: types.Int64Type, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), - }, - }, - - "min_upper": { - Description: "Minimum number of uppercase alphabet characters in the result. Default value is `0`.", - Type: types.Int64Type, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), - }, - }, - - "min_lower": { - Description: "Minimum number of lowercase alphabet characters in the result. Default value is `0`.", - Type: types.Int64Type, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), - }, - }, - - "min_special": { - Description: "Minimum number of special characters in the result. Default value is `0`.", - Type: types.Int64Type, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), - }, - }, - - "override_special": { - Description: "Supply your own list of special characters to use for string generation. This " + - "overrides the default character list in the special argument. The `special` argument must " + - "still be set to true for any overwritten characters to be used in generation.", - Type: types.StringType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - }, - }, - - "result": { - Description: "The generated random string.", - Type: types.StringType, - Computed: true, - }, - - "id": { - Computed: true, - Type: types.StringType, - }, - }, - } -} - type randomStringParams struct { length int64 upper bool From 20533f1b37a7754a5f5fdd37dd07389a5ba4912a Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 9 Jun 2022 12:21:12 +0100 Subject: [PATCH 75/96] Renaming password state upgrade functions (#177) --- internal/provider/resource_password.go | 8 ++++---- internal/provider/resource_password_test.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 1f01b4cc..8a3b140f 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -55,11 +55,11 @@ func (r resourcePassword) UpgradeState(context.Context) map[int64]tfsdk.Resource return map[int64]tfsdk.ResourceStateUpgrader{ 0: { PriorSchema: &schemaV0, - StateUpgrader: migratePasswordStateV0toV2, + StateUpgrader: upgradePasswordStateV0toV2, }, 1: { PriorSchema: &schemaV1, - StateUpgrader: migratePasswordStateV1toV2, + StateUpgrader: upgradePasswordStateV1toV2, }, } } @@ -631,7 +631,7 @@ func importPassword(ctx context.Context, req tfsdk.ImportResourceStateRequest, r } } -func migratePasswordStateV0toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { +func upgradePasswordStateV0toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { var passwordDataV0 PasswordModelV0 resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV0)...) @@ -667,7 +667,7 @@ func migratePasswordStateV0toV2(ctx context.Context, req tfsdk.UpgradeResourceSt resp.Diagnostics.Append(diags...) } -func migratePasswordStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { +func upgradePasswordStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { var passwordDataV1 PasswordModelV1 resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV1)...) diff --git a/internal/provider/resource_password_test.go b/internal/provider/resource_password_test.go index 40272a11..321632be 100644 --- a/internal/provider/resource_password_test.go +++ b/internal/provider/resource_password_test.go @@ -366,7 +366,7 @@ func TestMigratePasswordStateV0toV2(t *testing.T) { }, } - migratePasswordStateV0toV2(context.Background(), req, resp) + upgradePasswordStateV0toV2(context.Background(), req, resp) expected := PasswordModelV2{ ID: types.String{Value: "none"}, @@ -435,7 +435,7 @@ func TestMigratePasswordStateV1toV2(t *testing.T) { }, } - migratePasswordStateV1toV2(context.Background(), req, resp) + upgradePasswordStateV1toV2(context.Background(), req, resp) expected := PasswordModelV2{ ID: types.String{Value: "none"}, From 5cf02dfcbc20954d0b0f4f811dc1f17449aa032f Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 9 Jun 2022 12:37:50 +0100 Subject: [PATCH 76/96] Separating out state upgrade tests (#177) --- internal/provider/resource_password_test.go | 288 ++++++++++++++++---- internal/provider/resource_string_test.go | 81 +++--- 2 files changed, 267 insertions(+), 102 deletions(-) diff --git a/internal/provider/resource_password_test.go b/internal/provider/resource_password_test.go index 321632be..757d5ef4 100644 --- a/internal/provider/resource_password_test.go +++ b/internal/provider/resource_password_test.go @@ -79,12 +79,13 @@ func TestAccResourcePasswordOverride(t *testing.T) { }) } -// TestAccResourcePassword_StateUpgraders covers the state upgrades from v0 to V2 and V1 to V2. -// This includes the addition of bcrypt_hash and numeric attributes. -func TestAccResourcePassword_StateUpgraders(t *testing.T) { +// TestAccResourcePassword_StateUpgrade_V0toV2 covers the state upgrades from V0 to V2. +// This includes the deprecation of `number` and the addition of `numeric` and `bcrypt_hash` attributes. +// v3.1.3 is used as this is last version before `bcrypt_hash` attributed was added. +func TestAccResourcePassword_StateUpgrade_V0toV2(t *testing.T) { t.Parallel() - v1Cases := []struct { + cases := []struct { name string configBeforeUpgrade string configDuringUpgrade string @@ -92,7 +93,19 @@ func TestAccResourcePassword_StateUpgraders(t *testing.T) { afterStateUpgrade []resource.TestCheckFunc }{ { - name: "%s number is absent", + name: "bcrypt_hash", + configBeforeUpgrade: `resource "random_password" "default" { + length = 12 + }`, + beforeStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckNoResourceAttr("random_password.default", "bcrypt_hash"), + }, + afterStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrSet("random_password.default", "bcrypt_hash"), + }, + }, + { + name: "number is absent", configBeforeUpgrade: `resource "random_password" "default" { length = 12 }`, @@ -106,7 +119,7 @@ func TestAccResourcePassword_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is absent then true", + name: "number is absent then true", configBeforeUpgrade: `resource "random_password" "default" { length = 12 }`, @@ -124,7 +137,7 @@ func TestAccResourcePassword_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is absent then false", + name: "number is absent then false", configBeforeUpgrade: `resource "random_password" "default" { length = 12 }`, @@ -142,7 +155,7 @@ func TestAccResourcePassword_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is true", + name: "number is true", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = true @@ -157,7 +170,7 @@ func TestAccResourcePassword_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is true then absent", + name: "number is true then absent", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = true @@ -175,7 +188,7 @@ func TestAccResourcePassword_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is true then false", + name: "number is true then false", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = true @@ -194,7 +207,7 @@ func TestAccResourcePassword_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is false", + name: "number is false", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = false @@ -209,7 +222,7 @@ func TestAccResourcePassword_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is false then absent", + name: "number is false then absent", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = false @@ -227,7 +240,7 @@ func TestAccResourcePassword_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is false then true", + name: "number is false then true", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = false @@ -247,64 +260,227 @@ func TestAccResourcePassword_StateUpgraders(t *testing.T) { }, } - v0Cases := v1Cases - v0Cases = append(v0Cases, struct { + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if c.configDuringUpgrade == "" { + c.configDuringUpgrade = c.configBeforeUpgrade + } + + resource.UnitTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{"random": { + VersionConstraint: "3.1.3", + Source: "hashicorp/random", + }}, + Config: c.configBeforeUpgrade, + Check: resource.ComposeTestCheckFunc(c.beforeStateUpgrade...), + }, + { + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Config: c.configDuringUpgrade, + Check: resource.ComposeTestCheckFunc(c.afterStateUpgrade...), + }, + }, + }) + }) + } +} + +// TestAccResourcePassword_StateUpgrade_V1toV2 covers the state upgrades from V1 to V2. +// This includes the deprecation of `number` and the addition of `numeric` attributes. +// v3.2.0 was used as this is the last version before `number` was deprecated and `numeric` attribute +// was added. +func TestAccResourcePassword_StateUpgrade_V1toV2(t *testing.T) { + t.Parallel() + + cases := []struct { name string configBeforeUpgrade string configDuringUpgrade string beforeStateUpgrade []resource.TestCheckFunc afterStateUpgrade []resource.TestCheckFunc }{ - name: "%s bcrypt_hash", - configBeforeUpgrade: `resource "random_password" "default" { + { + name: "number is absent", + configBeforeUpgrade: `resource "random_password" "default" { length = 12 }`, - beforeStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckNoResourceAttr("random_password.default", "bcrypt_hash"), + beforeStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "numeric"), + }, + afterStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "true"), + resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + }, }, - afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttrSet("random_password.default", "bcrypt_hash"), + { + name: "number is absent then true", + configBeforeUpgrade: `resource "random_password" "default" { + length = 12 + }`, + configDuringUpgrade: `resource "random_password" "default" { + length = 12 + number = true + }`, + beforeStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "numeric"), + }, + afterStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "true"), + resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + }, + }, + { + name: "number is absent then false", + configBeforeUpgrade: `resource "random_password" "default" { + length = 12 + }`, + configDuringUpgrade: `resource "random_password" "default" { + length = 12 + number = false + }`, + beforeStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "numeric"), + }, + afterStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "false"), + resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + }, + }, + { + name: "number is true", + configBeforeUpgrade: `resource "random_password" "default" { + length = 12 + number = true + }`, + beforeStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "numeric"), + }, + afterStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "true"), + resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + }, + }, + { + name: "number is true then absent", + configBeforeUpgrade: `resource "random_password" "default" { + length = 12 + number = true + }`, + configDuringUpgrade: `resource "random_password" "default" { + length = 12 + }`, + beforeStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "numeric"), + }, + afterStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "true"), + resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + }, + }, + { + name: "number is true then false", + configBeforeUpgrade: `resource "random_password" "default" { + length = 12 + number = true + }`, + configDuringUpgrade: `resource "random_password" "default" { + length = 12 + number = false + }`, + beforeStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "numeric"), + }, + afterStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "false"), + resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + }, + }, + { + name: "number is false", + configBeforeUpgrade: `resource "random_password" "default" { + length = 12 + number = false + }`, + beforeStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "false"), + resource.TestCheckNoResourceAttr("random_password.default", "numeric"), + }, + afterStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "false"), + resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + }, + }, + { + name: "number is false then absent", + configBeforeUpgrade: `resource "random_password" "default" { + length = 12 + number = false + }`, + configDuringUpgrade: `resource "random_password" "default" { + length = 12 + }`, + beforeStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "false"), + resource.TestCheckNoResourceAttr("random_password.default", "numeric"), + }, + afterStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "true"), + resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + }, + }, + { + name: "number is false then true", + configBeforeUpgrade: `resource "random_password" "default" { + length = 12 + number = false + }`, + configDuringUpgrade: `resource "random_password" "default" { + length = 12 + number = true + }`, + beforeStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "false"), + resource.TestCheckNoResourceAttr("random_password.default", "numeric"), + }, + afterStateUpgrade: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("random_password.default", "number", "true"), + resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + }, }, - }) - - cases := map[string][]struct { - name string - configBeforeUpgrade string - configDuringUpgrade string - beforeStateUpgrade []resource.TestCheckFunc - afterStateUpgrade []resource.TestCheckFunc - }{ - "3.1.3": v0Cases, - "3.2.0": v1Cases, } - for providerVersion, v := range cases { - for _, c := range v { - name := fmt.Sprintf(c.name, providerVersion) - t.Run(name, func(t *testing.T) { - if c.configDuringUpgrade == "" { - c.configDuringUpgrade = c.configBeforeUpgrade - } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if c.configDuringUpgrade == "" { + c.configDuringUpgrade = c.configBeforeUpgrade + } - resource.UnitTest(t, resource.TestCase{ - Steps: []resource.TestStep{ - { - ExternalProviders: map[string]resource.ExternalProvider{"random": { - VersionConstraint: providerVersion, - Source: "hashicorp/random", - }}, - Config: c.configBeforeUpgrade, - Check: resource.ComposeTestCheckFunc(c.beforeStateUpgrade...), - }, - { - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), - Config: c.configDuringUpgrade, - Check: resource.ComposeTestCheckFunc(c.afterStateUpgrade...), - }, + resource.UnitTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{"random": { + VersionConstraint: "3.2.0", + Source: "hashicorp/random", + }}, + Config: c.configBeforeUpgrade, + Check: resource.ComposeTestCheckFunc(c.beforeStateUpgrade...), }, - }) + { + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Config: c.configDuringUpgrade, + Check: resource.ComposeTestCheckFunc(c.afterStateUpgrade...), + }, + }, }) - } + }) } } diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index 1f79230f..c3cabe79 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -75,12 +75,14 @@ func TestAccResourceStringMin(t *testing.T) { }) } -// TestAccResourceString_StateUpgraders covers the state upgrade from V1 to V2. -// This includes the addition of numeric attribute. +// TestAccResourceString_StateUpgrade_V1toV2 covers the state upgrade from V1 to V2. +// This includes the deprecation of `number` and the addition of `numeric` attributes. +// v3.2.0 was used as this is the last version before `number` was deprecated and `numeric` attribute +// was added. func TestAccResourceString_StateUpgraders(t *testing.T) { t.Parallel() - v1Cases := []struct { + cases := []struct { name string configBeforeUpgrade string configDuringUpgrade string @@ -88,7 +90,7 @@ func TestAccResourceString_StateUpgraders(t *testing.T) { afterStateUpgrade []resource.TestCheckFunc }{ { - name: "%s number is absent", + name: "number is absent", configBeforeUpgrade: `resource "random_string" "default" { length = 12 }`, @@ -102,7 +104,7 @@ func TestAccResourceString_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is absent then true", + name: "number is absent then true", configBeforeUpgrade: `resource "random_string" "default" { length = 12 }`, @@ -120,7 +122,7 @@ func TestAccResourceString_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is absent then false", + name: "number is absent then false", configBeforeUpgrade: `resource "random_string" "default" { length = 12 }`, @@ -138,7 +140,7 @@ func TestAccResourceString_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is true", + name: "number is true", configBeforeUpgrade: `resource "random_string" "default" { length = 12 number = true @@ -153,7 +155,7 @@ func TestAccResourceString_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is true then absent", + name: "number is true then absent", configBeforeUpgrade: `resource "random_string" "default" { length = 12 number = true @@ -171,7 +173,7 @@ func TestAccResourceString_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is true then false", + name: "number is true then false", configBeforeUpgrade: `resource "random_string" "default" { length = 12 number = true @@ -190,7 +192,7 @@ func TestAccResourceString_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is false", + name: "number is false", configBeforeUpgrade: `resource "random_string" "default" { length = 12 number = false @@ -205,7 +207,7 @@ func TestAccResourceString_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is false then absent", + name: "number is false then absent", configBeforeUpgrade: `resource "random_string" "default" { length = 12 number = false @@ -223,7 +225,7 @@ func TestAccResourceString_StateUpgraders(t *testing.T) { }, }, { - name: "%s number is false then true", + name: "number is false then true", configBeforeUpgrade: `resource "random_string" "default" { length = 12 number = false @@ -243,43 +245,30 @@ func TestAccResourceString_StateUpgraders(t *testing.T) { }, } - cases := map[string][]struct { - name string - configBeforeUpgrade string - configDuringUpgrade string - beforeStateUpgrade []resource.TestCheckFunc - afterStateUpgrade []resource.TestCheckFunc - }{ - "3.2.0": v1Cases, - } - - for providerVersion, v := range cases { - for _, c := range v { - name := fmt.Sprintf(c.name, providerVersion) - t.Run(name, func(t *testing.T) { - if c.configDuringUpgrade == "" { - c.configDuringUpgrade = c.configBeforeUpgrade - } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if c.configDuringUpgrade == "" { + c.configDuringUpgrade = c.configBeforeUpgrade + } - resource.UnitTest(t, resource.TestCase{ - Steps: []resource.TestStep{ - { - ExternalProviders: map[string]resource.ExternalProvider{"random": { - VersionConstraint: providerVersion, - Source: "hashicorp/random", - }}, - Config: c.configBeforeUpgrade, - Check: resource.ComposeTestCheckFunc(c.beforeStateUpgrade...), - }, - { - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), - Config: c.configDuringUpgrade, - Check: resource.ComposeTestCheckFunc(c.afterStateUpgrade...), - }, + resource.UnitTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{"random": { + VersionConstraint: "3.2.0", + Source: "hashicorp/random", + }}, + Config: c.configBeforeUpgrade, + Check: resource.ComposeTestCheckFunc(c.beforeStateUpgrade...), }, - }) + { + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Config: c.configDuringUpgrade, + Check: resource.ComposeTestCheckFunc(c.afterStateUpgrade...), + }, + }, }) - } + }) } } From da8224928eb99c7f4b05566039633d200ae2e47f Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 9 Jun 2022 14:10:25 +0100 Subject: [PATCH 77/96] Lint (#177) --- internal/provider/resource_string.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index c210907e..2c311ac1 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -130,7 +130,7 @@ func stringSchemaV2() tfsdk.Schema { }, }, - "number": tfsdk.Attribute{ + "number": { Description: "Include numeric characters in the result. Default value is `true`. " + "**NOTE**: This is deprecated, use `numeric` instead.", Type: types.BoolType, @@ -143,7 +143,7 @@ func stringSchemaV2() tfsdk.Schema { DeprecationMessage: "**NOTE**: This is deprecated, use `numeric` instead.", }, - "numeric": tfsdk.Attribute{ + "numeric": { Description: "Include numeric characters in the result. Default value is `true`.", Type: types.BoolType, Optional: true, From 6eb39fa2f3ee23a6bc834b1145cba8c698782189 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 13 Jun 2022 13:56:12 +0100 Subject: [PATCH 78/96] Refactoring tests to use helper functions (#177) --- internal/provider/resource_password_test.go | 31 ++-- internal/provider/resource_string_test.go | 158 ++++++-------------- 2 files changed, 65 insertions(+), 124 deletions(-) diff --git a/internal/provider/resource_password_test.go b/internal/provider/resource_password_test.go index 757d5ef4..10d5f953 100644 --- a/internal/provider/resource_password_test.go +++ b/internal/provider/resource_password_test.go @@ -25,8 +25,11 @@ func TestAccResourcePasswordBasic(t *testing.T) { length = 12 }`, Check: resource.ComposeTestCheckFunc( - testAccResourceStringCheck("random_password.basic", &customLens{ - customLen: 12, + resource.TestCheckResourceAttrWith("random_password.basic", "result", func(result string) error { + if len(result) != 12 { + return fmt.Errorf("expected length 12, actual length %d", len(result)) + } + return nil }), ), }, @@ -69,10 +72,13 @@ func TestAccResourcePasswordOverride(t *testing.T) { numeric = false }`, Check: resource.ComposeTestCheckFunc( - testAccResourceStringCheck("random_password.override", &customLens{ - customLen: 4, + resource.TestCheckResourceAttrWith("random_password.override", "result", func(result string) error { + if len(result) != 4 { + return fmt.Errorf("expected length 4, actual length %d", len(result)) + } + return nil }), - patternMatch("random_password.override", "!!!!"), + resource.TestCheckResourceAttr("random_password.override", "result", "!!!!"), ), }, }, @@ -499,13 +505,16 @@ func TestAccResourcePasswordMin(t *testing.T) { min_numeric = 4 }`, Check: resource.ComposeTestCheckFunc( - testAccResourceStringCheck("random_password.min", &customLens{ - customLen: 12, + resource.TestCheckResourceAttrWith("random_password.min", "result", func(result string) error { + if len(result) != 12 { + return fmt.Errorf("expected length 12, actual length %d", len(result)) + } + return nil }), - regexMatch("random_password.min", regexp.MustCompile(`([a-z])`), 2), - regexMatch("random_password.min", regexp.MustCompile(`([A-Z])`), 3), - regexMatch("random_password.min", regexp.MustCompile(`([0-9])`), 4), - regexMatch("random_password.min", regexp.MustCompile(`([!#@])`), 1), + resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([a-z].*){2,}`)), + resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([A-Z].*){3,}`)), + resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([0-9].*){4,}`)), + resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([!#@])`)), ), }, }, diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index c3cabe79..123572c9 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -6,23 +6,23 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -type customLens struct { - customLen int -} - func TestAccResourceString(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourceStringBasic, + Config: `resource "random_string" "basic" { + length = 12 + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceStringCheck("random_string.basic", &customLens{ - customLen: 12, + resource.TestCheckResourceAttrWith("random_string.basic", "result", func(result string) error { + if len(result) != 12 { + return fmt.Errorf("expected length 12, actual length %d", len(result)) + } + return nil }), ), }, @@ -42,12 +42,21 @@ func TestAccResourceStringOverride(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourceStringOverride, + Config: `resource "random_string" "override" { + length = 4 + override_special = "!" + lower = false + upper = false + number = false + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceStringCheck("random_string.override", &customLens{ - customLen: 4, + resource.TestCheckResourceAttrWith("random_string.override", "result", func(result string) error { + if len(result) != 4 { + return fmt.Errorf("expected length 12, actual length %d", len(result)) + } + return nil }), - patternMatch("random_string.override", "!!!!"), + resource.TestCheckResourceAttr("random_string.override", "result", "!!!!"), ), }, }, @@ -60,15 +69,25 @@ func TestAccResourceStringMin(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourceStringMin, + Config: `resource "random_string" "min" { + length = 12 + override_special = "!#@" + min_lower = 2 + min_upper = 3 + min_special = 1 + min_numeric = 4 + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceStringCheck("random_string.min", &customLens{ - customLen: 12, + resource.TestCheckResourceAttrWith("random_string.min", "result", func(result string) error { + if len(result) != 12 { + return fmt.Errorf("expected length 12, actual length %d", len(result)) + } + return nil }), - regexMatch("random_string.min", regexp.MustCompile(`([a-z])`), 2), - regexMatch("random_string.min", regexp.MustCompile(`([A-Z])`), 3), - regexMatch("random_string.min", regexp.MustCompile(`([0-9])`), 4), - regexMatch("random_string.min", regexp.MustCompile(`([!#@])`), 1), + resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([a-z].*){2,}`)), + resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([A-Z].*){3,}`)), + resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([0-9].*){4,}`)), + resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([!#@].*)`)), ), }, }, @@ -278,105 +297,18 @@ func TestAccResourceStringErrors(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourceStringInvalidConfig, + Config: `resource "random_string" "invalid_length" { + length = 2 + min_lower = 3 + }`, ExpectError: regexp.MustCompile(`.*Attribute "length" \(2\) cannot be less than min_upper \+ min_lower \+\nmin_numeric \+ min_special \(3\).`), }, { - Config: testAccResourceStringLengthTooShortConfig, + Config: `resource "random_string" "invalid_length" { + length = 0 + }`, ExpectError: regexp.MustCompile(`.*Attribute "length" \(0\) must be at least 1`), }, }, }) } - -const ( - testAccResourceStringBasic = ` -resource "random_string" "basic" { - length = 12 -}` - testAccResourceStringOverride = ` -resource "random_string" "override" { -length = 4 -override_special = "!" -lower = false -upper = false -number = false -} -` - testAccResourceStringMin = ` -resource "random_string" "min" { -length = 12 -override_special = "!#@" -min_lower = 2 -min_upper = 3 -min_special = 1 -min_numeric = 4 -}` - testAccResourceStringInvalidConfig = ` -resource "random_string" "invalid_length" { - length = 2 - min_lower = 3 -}` - testAccResourceStringLengthTooShortConfig = ` -resource "random_string" "invalid_length" { - length = 0 -}` -) - -func testAccResourceStringCheck(id string, want *customLens) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[id] - if !ok { - return fmt.Errorf("Not found: %s", id) - } - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - customStr := rs.Primary.Attributes["result"] - - if got, want := len(customStr), want.customLen; got != want { - return fmt.Errorf("custom string length is %d; want %d", got, want) - } - - return nil - } -} - -func regexMatch(id string, exp *regexp.Regexp, requiredMatches int) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[id] - if !ok { - return fmt.Errorf("Not found: %s", id) - } - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - customStr := rs.Primary.Attributes["result"] - - if matches := exp.FindAllStringSubmatchIndex(customStr, -1); len(matches) < requiredMatches { - return fmt.Errorf("custom string is %s; did not match %s", customStr, exp) - } - - return nil - } -} -func patternMatch(id string, want string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[id] - if !ok { - return fmt.Errorf("Not found: %s", id) - } - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - customStr := rs.Primary.Attributes["result"] - - if got, want := customStr, want; got != want { - return fmt.Errorf("custom string is %s; want %s", got, want) - } - - return nil - } -} From 74ec49fa3724d0775e8422e14cf8f34d12d3a2bd Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 13 Jun 2022 14:34:37 +0100 Subject: [PATCH 79/96] Refactoring tests to use helper functions (#177) --- internal/provider/resource_id_test.go | 81 ++++----------------- internal/provider/resource_password_test.go | 21 +----- internal/provider/resource_string_test.go | 41 ++++++----- 3 files changed, 41 insertions(+), 102 deletions(-) diff --git a/internal/provider/resource_id_test.go b/internal/provider/resource_id_test.go index 7caf073c..a1ca08b4 100644 --- a/internal/provider/resource_id_test.go +++ b/internal/provider/resource_id_test.go @@ -1,32 +1,25 @@ package provider import ( - "fmt" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -type idLens struct { - b64UrlLen int - b64StdLen int - hexLen int -} - func TestAccResourceID(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourceIDConfig, + Config: `resource "random_id" "foo" { + byte_length = 4 + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceIDCheck("random_id.foo", &idLens{ - b64UrlLen: 6, - b64StdLen: 8, - hexLen: 8, - }), + resource.TestCheckResourceAttrWith("random_id.foo", "b64_url", testCheckLen(6)), + resource.TestCheckResourceAttrWith("random_id.foo", "b64_std", testCheckLen(8)), + resource.TestCheckResourceAttrWith("random_id.foo", "hex", testCheckLen(8)), + resource.TestCheckResourceAttrWith("random_id.foo", "dec", testCheckMinLen(1)), ), }, { @@ -44,13 +37,15 @@ func TestAccResourceID_importWithPrefix(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourceIDConfigWithPrefix, + Config: `resource "random_id" "bar" { + byte_length = 4 + prefix = "cloud-" + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceIDCheck("random_id.bar", &idLens{ - b64UrlLen: 12, - b64StdLen: 14, - hexLen: 14, - }), + resource.TestCheckResourceAttrWith("random_id.bar", "b64_url", testCheckLen(12)), + resource.TestCheckResourceAttrWith("random_id.bar", "b64_std", testCheckLen(14)), + resource.TestCheckResourceAttrWith("random_id.bar", "hex", testCheckLen(14)), + resource.TestCheckResourceAttrWith("random_id.bar", "dec", testCheckMinLen(1)), ), }, { @@ -62,49 +57,3 @@ func TestAccResourceID_importWithPrefix(t *testing.T) { }, }) } - -func testAccResourceIDCheck(id string, want *idLens) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[id] - if !ok { - return fmt.Errorf("Not found: %s", id) - } - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - b64UrlStr := rs.Primary.Attributes["b64_url"] - b64StdStr := rs.Primary.Attributes["b64_std"] - hexStr := rs.Primary.Attributes["hex"] - decStr := rs.Primary.Attributes["dec"] - - if got, want := len(b64UrlStr), want.b64UrlLen; got != want { - return fmt.Errorf("base64 URL string length is %d; want %d", got, want) - } - if got, want := len(b64StdStr), want.b64StdLen; got != want { - return fmt.Errorf("base64 STD string length is %d; want %d", got, want) - } - if got, want := len(hexStr), want.hexLen; got != want { - return fmt.Errorf("hex string length is %d; want %d", got, want) - } - if len(decStr) < 1 { - return fmt.Errorf("decimal string is empty; want at least one digit") - } - - return nil - } -} - -const ( - testAccResourceIDConfig = ` -resource "random_id" "foo" { - byte_length = 4 -}` - - testAccResourceIDConfigWithPrefix = ` -resource "random_id" "bar" { - byte_length = 4 - prefix = "cloud-" -} -` -) diff --git a/internal/provider/resource_password_test.go b/internal/provider/resource_password_test.go index 10d5f953..808185b9 100644 --- a/internal/provider/resource_password_test.go +++ b/internal/provider/resource_password_test.go @@ -25,12 +25,7 @@ func TestAccResourcePasswordBasic(t *testing.T) { length = 12 }`, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith("random_password.basic", "result", func(result string) error { - if len(result) != 12 { - return fmt.Errorf("expected length 12, actual length %d", len(result)) - } - return nil - }), + resource.TestCheckResourceAttrWith("random_password.basic", "result", testCheckLen(12)), ), }, { @@ -72,12 +67,7 @@ func TestAccResourcePasswordOverride(t *testing.T) { numeric = false }`, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith("random_password.override", "result", func(result string) error { - if len(result) != 4 { - return fmt.Errorf("expected length 4, actual length %d", len(result)) - } - return nil - }), + resource.TestCheckResourceAttrWith("random_password.override", "result", testCheckLen(12)), resource.TestCheckResourceAttr("random_password.override", "result", "!!!!"), ), }, @@ -505,12 +495,7 @@ func TestAccResourcePasswordMin(t *testing.T) { min_numeric = 4 }`, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith("random_password.min", "result", func(result string) error { - if len(result) != 12 { - return fmt.Errorf("expected length 12, actual length %d", len(result)) - } - return nil - }), + resource.TestCheckResourceAttrWith("random_password.min", "result", testCheckLen(12)), resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([a-z].*){2,}`)), resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([A-Z].*){3,}`)), resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([0-9].*){4,}`)), diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index 123572c9..e2db220d 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -18,12 +18,7 @@ func TestAccResourceString(t *testing.T) { length = 12 }`, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith("random_string.basic", "result", func(result string) error { - if len(result) != 12 { - return fmt.Errorf("expected length 12, actual length %d", len(result)) - } - return nil - }), + resource.TestCheckResourceAttrWith("random_string.basic", "result", testCheckLen(12)), ), }, { @@ -50,12 +45,7 @@ func TestAccResourceStringOverride(t *testing.T) { number = false }`, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith("random_string.override", "result", func(result string) error { - if len(result) != 4 { - return fmt.Errorf("expected length 12, actual length %d", len(result)) - } - return nil - }), + resource.TestCheckResourceAttrWith("random_string.override", "result", testCheckLen(12)), resource.TestCheckResourceAttr("random_string.override", "result", "!!!!"), ), }, @@ -78,12 +68,7 @@ func TestAccResourceStringMin(t *testing.T) { min_numeric = 4 }`, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith("random_string.min", "result", func(result string) error { - if len(result) != 12 { - return fmt.Errorf("expected length 12, actual length %d", len(result)) - } - return nil - }), + resource.TestCheckResourceAttrWith("random_string.min", "result", testCheckLen(12)), resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([a-z].*){2,}`)), resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([A-Z].*){3,}`)), resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([0-9].*){4,}`)), @@ -312,3 +297,23 @@ func TestAccResourceStringErrors(t *testing.T) { }, }) } + +func testCheckLen(expectedLen int) func(input string) error { + return func(input string) error { + if len(input) != expectedLen { + return fmt.Errorf("expected length %d, actual length %d", expectedLen, len(input)) + } + + return nil + } +} + +func testCheckMinLen(minLen int) func(input string) error { + return func(input string) error { + if len(input) < minLen { + return fmt.Errorf("minimum length %d, actual length %d", minLen, len(input)) + } + + return nil + } +} From 561a325373dc9d359d4a7a645bb7983e17f1d467 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 13 Jun 2022 16:15:08 +0100 Subject: [PATCH 80/96] Refactoring tests to use helper functions (#177) --- internal/provider/resource_integer_test.go | 158 +++++++-------------- internal/provider/resource_pet_test.go | 69 +++------ 2 files changed, 71 insertions(+), 156 deletions(-) diff --git a/internal/provider/resource_integer_test.go b/internal/provider/resource_integer_test.go index 49d0295a..0bbaa24c 100644 --- a/internal/provider/resource_integer_test.go +++ b/internal/provider/resource_integer_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccResourceIntegerBasic(t *testing.T) { @@ -15,9 +14,13 @@ func TestAccResourceIntegerBasic(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testRandomIntegerBasic, + Config: `resource "random_integer" "integer_1" { + min = 1 + max = 3 + seed = "12345" + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceIntegerBasic("random_integer.integer_1"), + resource.TestCheckResourceAttr("random_integer.integer_1", "result", "3"), ), }, { @@ -37,15 +40,23 @@ func TestAccResourceIntegerUpdate(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testRandomIntegerBasic, + Config: `resource "random_integer" "integer_1" { + min = 1 + max = 3 + seed = "12345" + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceIntegerBasic("random_integer.integer_1"), + resource.TestCheckResourceAttr("random_integer.integer_1", "result", "3"), ), }, { - Config: testRandomIntegerUpdate, + Config: `resource "random_integer" "integer_1" { + min = 1 + max = 3 + seed = "123456" + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceIntegerUpdate("random_integer.integer_1"), + resource.TestCheckResourceAttr("random_integer.integer_1", "result", "2"), ), }, }, @@ -59,15 +70,22 @@ func TestAccResourceIntegerSeedless_to_seeded(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testRandomIntegerSeedless, + Config: `resource "random_integer" "integer_1" { + min = 1 + max = 3 + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceIntegerSeedless("random_integer.integer_1"), + resource.TestCheckResourceAttrWith("random_integer.integer_1", "result", testCheckNotEmptyString("result")), ), }, { - Config: testRandomIntegerUpdate, + Config: `resource "random_integer" "integer_1" { + min = 1 + max = 3 + seed = "123456" + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceIntegerUpdate("random_integer.integer_1"), + resource.TestCheckResourceAttr("random_integer.integer_1", "result", "2"), ), }, }, @@ -81,15 +99,22 @@ func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testRandomIntegerBasic, + Config: `resource "random_integer" "integer_1" { + min = 1 + max = 3 + seed = "12345" + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceIntegerBasic("random_integer.integer_1"), + resource.TestCheckResourceAttr("random_integer.integer_1", "result", "3"), ), }, { - Config: testRandomIntegerSeedless, + Config: `resource "random_integer" "integer_1" { + min = 1 + max = 3 + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceIntegerSeedless("random_integer.integer_1"), + resource.TestCheckResourceAttrWith("random_integer.integer_1", "result", testCheckNotEmptyString("result")), ), }, }, @@ -103,7 +128,11 @@ func TestAccResourceIntegerBig(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testRandomIntegerBig, + Config: `resource "random_integer" "integer_1" { + max = 7227701560655103598 + min = 7227701560655103597 + seed = 12345 + }`, }, { ResourceName: "random_integer.integer_1", @@ -115,101 +144,12 @@ func TestAccResourceIntegerBig(t *testing.T) { }) } -func testAccResourceIntegerBasic(id string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[id] - if !ok { - return fmt.Errorf("Not found: %s", id) - } - result := rs.Primary.Attributes["result"] - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - if result == "" { - return fmt.Errorf("Result not found") - } - - if result != "3" { - return fmt.Errorf("Invalid result %s. Seed does not result in correct value", result) - } - return nil - } -} - -func testAccResourceIntegerUpdate(id string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[id] - if !ok { - return fmt.Errorf("Not found: %s", id) - } - result := rs.Primary.Attributes["result"] - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - if result == "" { - return fmt.Errorf("Result not found") - } - - if result != "2" { - return fmt.Errorf("Invalid result %s. Seed does not result in correct value", result) - } - return nil - } -} - -// testAccResourceIntegerSeedless only checks that some result was returned, and does not validate the value. -func testAccResourceIntegerSeedless(id string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[id] - if !ok { - return fmt.Errorf("Not found: %s", id) - } - result := rs.Primary.Attributes["result"] - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - if result == "" { - return fmt.Errorf("Result not found") +func testCheckNotEmptyString(field string) func(input string) error { + return func(input string) error { + if input == "" { + return fmt.Errorf("%s is empty string", field) } return nil } } - -const ( - testRandomIntegerBasic = ` -resource "random_integer" "integer_1" { - min = 1 - max = 3 - seed = "12345" -} -` - - testRandomIntegerUpdate = ` -resource "random_integer" "integer_1" { - min = 1 - max = 3 - seed = "123456" -} -` - - testRandomIntegerSeedless = ` -resource "random_integer" "integer_1" { - min = 1 - max = 3 -} -` - - testRandomIntegerBig = ` -resource "random_integer" "integer_1" { - max = 7227701560655103598 - min = 7227701560655103597 - seed = 12345 -}` -) diff --git a/internal/provider/resource_pet_test.go b/internal/provider/resource_pet_test.go index f0e2b3e8..2a78cffd 100644 --- a/internal/provider/resource_pet_test.go +++ b/internal/provider/resource_pet_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccResourcePet_basic(t *testing.T) { @@ -16,9 +15,10 @@ func TestAccResourcePet_basic(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourcePetBasic, + Config: `resource "random_pet" "pet_1" { + }`, Check: resource.ComposeTestCheckFunc( - testAccResourcePetLength("random_pet.pet_1", "-", 2), + resource.TestCheckResourceAttrWith("random_pet.pet_1", "id", testCheckPetLen("-", 2)), ), }, }, @@ -31,9 +31,11 @@ func TestAccResourcePet_length(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourcePetLengthSet, + Config: `resource "random_pet" "pet_1" { + length = 4 + }`, Check: resource.ComposeTestCheckFunc( - testAccResourcePetLength("random_pet.pet_1", "-", 4), + resource.TestCheckResourceAttrWith("random_pet.pet_1", "id", testCheckPetLen("-", 4)), ), }, }, @@ -46,11 +48,12 @@ func TestAccResourcePet_prefix(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourcePetPrefix, + Config: `resource "random_pet" "pet_1" { + prefix = "consul" + }`, Check: resource.ComposeTestCheckFunc( - testAccResourcePetLength("random_pet.pet_1", "-", 3), - resource.TestMatchResourceAttr( - "random_pet.pet_1", "id", regexp.MustCompile("^consul-")), + resource.TestCheckResourceAttrWith("random_pet.pet_1", "id", testCheckPetLen("-", 3)), + resource.TestMatchResourceAttr("random_pet.pet_1", "id", regexp.MustCompile("^consul-")), ), }, }, @@ -63,54 +66,26 @@ func TestAccResourcePet_separator(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourcePetSeparator, + Config: `resource "random_pet" "pet_1" { + length = 3 + separator = "_" + }`, Check: resource.ComposeTestCheckFunc( - testAccResourcePetLength("random_pet.pet_1", "_", 3), + resource.TestCheckResourceAttrWith("random_pet.pet_1", "id", testCheckPetLen("_", 3)), ), }, }, }) } -// nolint:unparam -func testAccResourcePetLength(id string, separator string, length int) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[id] - if !ok { - return fmt.Errorf("Not found: %s", id) - } - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } +func testCheckPetLen(separator string, expectedLen int) func(input string) error { + return func(input string) error { + petNameParts := strings.Split(input, separator) - petParts := strings.Split(rs.Primary.ID, separator) - if len(petParts) != length { - return fmt.Errorf("Length does not match") + if len(petNameParts) != expectedLen { + return fmt.Errorf("expected length %d, actual length %d", expectedLen, len(petNameParts)) } return nil } } - -const testAccResourcePetBasic = ` -resource "random_pet" "pet_1" { -} -` - -const testAccResourcePetLengthSet = ` -resource "random_pet" "pet_1" { - length = 4 -} -` -const testAccResourcePetPrefix = ` -resource "random_pet" "pet_1" { - prefix = "consul" -} -` - -const testAccResourcePetSeparator = ` -resource "random_pet" "pet_1" { - length = 3 - separator = "_" -} -` From a1aaabfa92e06dfe91c1d10b0bce0fdade7945ad Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 16 Jun 2022 10:01:05 +0100 Subject: [PATCH 81/96] Using modified version of RequiresReplace (#177) --- internal/provider/plan_modifiers.go | 47 +++++++++++++++++++++ internal/provider/resource_password.go | 24 ++++++----- internal/provider/resource_password_test.go | 2 +- internal/provider/resource_string.go | 24 ++++++----- internal/provider/resource_string_test.go | 4 +- 5 files changed, 76 insertions(+), 25 deletions(-) diff --git a/internal/provider/plan_modifiers.go b/internal/provider/plan_modifiers.go index 22b577d4..f4f1059c 100644 --- a/internal/provider/plan_modifiers.go +++ b/internal/provider/plan_modifiers.go @@ -136,3 +136,50 @@ func (d *numberNumericAttributePlanModifier) Modify(ctx context.Context, req tfs return } } + +// RequiresReplace returns an attribute plan modifier that is identical to +// tfsdk.RequiresReplace() with the exception that there is no check for +// configRaw.IsNull && attrSchema.Computed because the defaults that we +// need to assign are computed. +func RequiresReplace() tfsdk.AttributePlanModifier { + return RequiresReplaceModifier{} +} + +type RequiresReplaceModifier struct{} + +func (r RequiresReplaceModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + if req.AttributeConfig == nil || req.AttributePlan == nil || req.AttributeState == nil { + // shouldn't happen, but let's not panic if it does + return + } + + if req.State.Raw.IsNull() { + // if we're creating the resource, no need to delete and + // recreate it + return + } + + if req.Plan.Raw.IsNull() { + // if we're deleting the resource, no need to delete and + // recreate it + return + } + + if req.AttributePlan.Equal(req.AttributeState) { + // if the plan and the state are in agreement, this attribute + // isn't changing, don't require replace + return + } + + resp.RequiresReplace = true +} + +// Description returns a human-readable description of the plan modifier. +func (r RequiresReplaceModifier) Description(ctx context.Context) string { + return "If the value of this attribute changes, Terraform will destroy and recreate the resource." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (r RequiresReplaceModifier) MarkdownDescription(ctx context.Context) string { + return "If the value of this attribute changes, Terraform will destroy and recreate the resource." +} diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 8a3b140f..d22f28b3 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -79,8 +79,10 @@ func passwordSchemaV2() tfsdk.Schema { Type: types.MapType{ ElemType: types.StringType, }, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, }, "length": { @@ -106,8 +108,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + RequiresReplace(), }, }, @@ -117,8 +119,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + RequiresReplace(), }, }, @@ -128,8 +130,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + RequiresReplace(), }, }, @@ -140,8 +142,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newNumberNumericAttributePlanModifier(), + RequiresReplace(), }, DeprecationMessage: "**NOTE**: This is deprecated, use `numeric` instead.", }, @@ -152,8 +154,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newNumberNumericAttributePlanModifier(), + RequiresReplace(), }, }, @@ -163,8 +165,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + RequiresReplace(), }, }, @@ -174,8 +176,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + RequiresReplace(), }, }, @@ -185,8 +187,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + RequiresReplace(), }, }, @@ -196,8 +198,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + RequiresReplace(), }, }, diff --git a/internal/provider/resource_password_test.go b/internal/provider/resource_password_test.go index 808185b9..54173b1b 100644 --- a/internal/provider/resource_password_test.go +++ b/internal/provider/resource_password_test.go @@ -67,7 +67,7 @@ func TestAccResourcePasswordOverride(t *testing.T) { numeric = false }`, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith("random_password.override", "result", testCheckLen(12)), + resource.TestCheckResourceAttrWith("random_password.override", "result", testCheckLen(4)), resource.TestCheckResourceAttr("random_password.override", "result", "!!!!"), ), }, diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index 2c311ac1..bff42339 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -76,8 +76,10 @@ func stringSchemaV2() tfsdk.Schema { Type: types.MapType{ ElemType: types.StringType, }, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, }, "length": { @@ -103,8 +105,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + RequiresReplace(), }, }, @@ -114,8 +116,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + RequiresReplace(), }, }, @@ -125,8 +127,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + RequiresReplace(), }, }, @@ -137,8 +139,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newNumberNumericAttributePlanModifier(), + RequiresReplace(), }, DeprecationMessage: "**NOTE**: This is deprecated, use `numeric` instead.", }, @@ -149,8 +151,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newNumberNumericAttributePlanModifier(), + RequiresReplace(), }, }, @@ -160,8 +162,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + RequiresReplace(), }, }, @@ -171,8 +173,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + RequiresReplace(), }, }, @@ -182,8 +184,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + RequiresReplace(), }, }, @@ -193,8 +195,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + RequiresReplace(), }, }, diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index e2db220d..91acf5b3 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -45,7 +45,7 @@ func TestAccResourceStringOverride(t *testing.T) { number = false }`, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith("random_string.override", "result", testCheckLen(12)), + resource.TestCheckResourceAttrWith("random_string.override", "result", testCheckLen(4)), resource.TestCheckResourceAttr("random_string.override", "result", "!!!!"), ), }, @@ -83,7 +83,7 @@ func TestAccResourceStringMin(t *testing.T) { // This includes the deprecation of `number` and the addition of `numeric` attributes. // v3.2.0 was used as this is the last version before `number` was deprecated and `numeric` attribute // was added. -func TestAccResourceString_StateUpgraders(t *testing.T) { +func TestAccResourceString_StateUpgrade_V1toV2(t *testing.T) { t.Parallel() cases := []struct { From 01a15580020f2e7bb08c5834dcc6f895a941a2f7 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 16 Jun 2022 10:50:17 +0100 Subject: [PATCH 82/96] Using terraform-plugin-framework-validators (#177) --- go.mod | 7 +-- go.sum | 18 +++++-- internal/provider/resource_password.go | 7 +-- internal/provider/resource_string.go | 5 +- internal/provider/validators.go | 56 ---------------------- internal/provider/validators_test.go | 66 -------------------------- 6 files changed, 24 insertions(+), 135 deletions(-) diff --git a/go.mod b/go.mod index 27b80840..65113b5f 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,10 @@ require ( github.com/google/go-cmp v0.5.8 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/terraform-plugin-docs v0.10.1 - github.com/hashicorp/terraform-plugin-framework v0.8.0 + github.com/hashicorp/terraform-plugin-framework v0.9.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.2.0 github.com/hashicorp/terraform-plugin-go v0.9.1 - github.com/hashicorp/terraform-plugin-log v0.4.0 + github.com/hashicorp/terraform-plugin-log v0.4.1 github.com/hashicorp/terraform-plugin-sdk/v2 v2.17.0 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e ) @@ -30,7 +31,7 @@ require ( github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect - github.com/hashicorp/go-hclog v1.2.0 // indirect + github.com/hashicorp/go-hclog v1.2.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.4 // indirect github.com/hashicorp/go-version v1.5.0 // indirect diff --git a/go.sum b/go.sum index 7d2f0788..41285d2e 100644 --- a/go.sum +++ b/go.sum @@ -125,8 +125,9 @@ github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/S github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= +github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 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= @@ -155,14 +156,18 @@ github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= github.com/hashicorp/terraform-plugin-docs v0.10.1 h1:jiVYfhJ/hVXDAQN2XjLK3WH1A/YHgFCrFXPpxibvmjc= github.com/hashicorp/terraform-plugin-docs v0.10.1/go.mod h1:47ZcsxMUJxAjGzHf+dZ9q78oYf4PeJxO1N+i5XDtXBc= -github.com/hashicorp/terraform-plugin-framework v0.8.0 h1:2nxk+5qAKlGWOrpWZbAZNkO+AoC87l4+9d/rjtQd6Wo= github.com/hashicorp/terraform-plugin-framework v0.8.0/go.mod h1:jUhqrbeI48gAleP8LXzg9jtRH07EAcpwEGQlYmKNIVg= +github.com/hashicorp/terraform-plugin-framework v0.9.0 h1:vOKG9+keJv062zGhXFgfOFEuGcfgV6LHciwleFTSek0= +github.com/hashicorp/terraform-plugin-framework v0.9.0/go.mod h1:ActelD2V6yt2m0MwIX4jESGDYJ573rAvZswGjSGm1rY= +github.com/hashicorp/terraform-plugin-framework-validators v0.2.0 h1:+bmU82GBrmImRfP7jq9g9hHKWrsvWc8xOFJiojPxiEY= +github.com/hashicorp/terraform-plugin-framework-validators v0.2.0/go.mod h1:1h4uI89dby5g+/esMKVVys5Xju4NpnSYisGcLZxoFeU= github.com/hashicorp/terraform-plugin-go v0.9.0/go.mod h1:EawBkgjBWNf7jiKnVoyDyF39OSV+u6KUX+Y73EPj3oM= github.com/hashicorp/terraform-plugin-go v0.9.1 h1:vXdHaQ6aqL+OF076nMSBV+JKPdmXlzG5mzVDD04WyPs= github.com/hashicorp/terraform-plugin-go v0.9.1/go.mod h1:ItjVSlQs70otlzcCwlPcU8FRXLdO973oYFRZwAOxy8M= github.com/hashicorp/terraform-plugin-log v0.3.0/go.mod h1:EjueSP/HjlyFAsDqt+okpCPjkT4NDynAe32AeDC4vps= -github.com/hashicorp/terraform-plugin-log v0.4.0 h1:F3eVnm8r2EfQCe2k9blPIiF/r2TT01SHijXnS7bujvc= github.com/hashicorp/terraform-plugin-log v0.4.0/go.mod h1:9KclxdunFownr4pIm1jdmwKRmE4d6HVG2c9XDq47rpg= +github.com/hashicorp/terraform-plugin-log v0.4.1 h1:xpbmVhvuU3mgHzLetOmx9pkOL2rmgpu302XxddON6eo= +github.com/hashicorp/terraform-plugin-log v0.4.1/go.mod h1:p4R1jWBXRTvL4odmEkFfDdhUjHf9zcs/BCoNHAc7IK4= github.com/hashicorp/terraform-plugin-sdk/v2 v2.17.0 h1:Qr5fWNg1SPSfCRMtou67Y6Kcy9UnMYRNlIJTKRuUvXU= github.com/hashicorp/terraform-plugin-sdk/v2 v2.17.0/go.mod h1:b+LFg8WpYgFgvEBP/6Htk5H9/pJp1V1E8NJAekfH2Ws= github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896/go.mod h1:bzBPnUIkI0RxauU8Dqo+2KrZZ28Cf48s8V6IHt3p4co= @@ -269,8 +274,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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/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= @@ -363,6 +369,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -440,7 +447,8 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index d22f28b3..70907066 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -3,6 +3,7 @@ package provider import ( "context" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" @@ -92,7 +93,7 @@ func passwordSchemaV2() tfsdk.Schema { Required: true, PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, Validators: []tfsdk.AttributeValidator{ - NewIntAtLeastValidator(1), + int64validator.AtLeast(1), NewIntIsAtLeastSumOfValidator( tftypes.NewAttributePath().WithAttributeName("min_upper"), tftypes.NewAttributePath().WithAttributeName("min_lower"), @@ -264,7 +265,7 @@ func passwordSchemaV1() tfsdk.Schema { Required: true, PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, Validators: []tfsdk.AttributeValidator{ - NewIntAtLeastValidator(1), + int64validator.AtLeast(1), NewIntIsAtLeastSumOfValidator( tftypes.NewAttributePath().WithAttributeName("min_upper"), tftypes.NewAttributePath().WithAttributeName("min_lower"), @@ -422,7 +423,7 @@ func passwordSchemaV0() tfsdk.Schema { Required: true, PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, Validators: []tfsdk.AttributeValidator{ - NewIntAtLeastValidator(1), + int64validator.AtLeast(1), NewIntIsAtLeastSumOfValidator( tftypes.NewAttributePath().WithAttributeName("min_upper"), tftypes.NewAttributePath().WithAttributeName("min_lower"), diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index bff42339..de121206 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -3,6 +3,7 @@ package provider import ( "context" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" @@ -89,7 +90,7 @@ func stringSchemaV2() tfsdk.Schema { Required: true, PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, Validators: []tfsdk.AttributeValidator{ - NewIntAtLeastValidator(1), + int64validator.AtLeast(1), NewIntIsAtLeastSumOfValidator( tftypes.NewAttributePath().WithAttributeName("min_upper"), tftypes.NewAttributePath().WithAttributeName("min_lower"), @@ -256,7 +257,7 @@ func stringSchemaV1() tfsdk.Schema { Required: true, PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, Validators: []tfsdk.AttributeValidator{ - NewIntAtLeastValidator(1), + int64validator.AtLeast(1), NewIntIsAtLeastSumOfValidator( tftypes.NewAttributePath().WithAttributeName("min_upper"), tftypes.NewAttributePath().WithAttributeName("min_lower"), diff --git a/internal/provider/validators.go b/internal/provider/validators.go index 0f0cd697..8e0e5a22 100644 --- a/internal/provider/validators.go +++ b/internal/provider/validators.go @@ -12,62 +12,6 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" ) -// intAtLeastValidator checks that the value of the attribute in the configuration -// (i.e., AttributeConfig in ValidateAttributeRequest) is greater than or, equal to minVal. -type intAtLeastValidator struct { - minVal int64 -} - -var _ tfsdk.AttributeValidator = (*intAtLeastValidator)(nil) - -func NewIntAtLeastValidator(min int64) tfsdk.AttributeValidator { - return &intAtLeastValidator{min} -} - -func (av *intAtLeastValidator) Description(ctx context.Context) string { - return "intAtLeastValidator ensures that attribute is at least minVal" -} - -func (av *intAtLeastValidator) MarkdownDescription(context.Context) string { - return "intAtLeastValidator ensures that attribute is at least `minVal`" -} - -// Validate runs the following checks: -// 1. Determines if AttributeConfig can be reflected into types.Int64. -// 2. Checks that the value is >= minVal. -func (av *intAtLeastValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { - // TODO: Remove once attr.Value interface includes IsNull. - attribConfigValue, err := req.AttributeConfig.ToTerraformValue(ctx) - if err != nil { - resp.Diagnostics.AddError( - "Int at least validator failed", - fmt.Sprintf("Unable to convert attribute config (%s) to terraform value: %s", req.AttributeConfig.Type(ctx).String(), err), - ) - return - } - - if attribConfigValue.IsNull() || !attribConfigValue.IsKnown() { - return - } - - var attrib types.Int64 - - resp.Diagnostics.Append(tfsdk.ValueAs(ctx, req.AttributeConfig, &attrib)...) - if resp.Diagnostics.HasError() { - return - } - - if attrib.Value < av.minVal { - pathStr := attrPathToString(req.AttributePath) - - resp.Diagnostics.AddAttributeError( - req.AttributePath, - fmt.Sprintf("Attribute %q is less than minimum required.", pathStr), - fmt.Sprintf("Attribute %q (%d) must be at least %d.", attrPathToString(req.AttributePath), attrib.Value, av.minVal), - ) - } -} - // intIsAtLeastValidator checks that the value of the attribute in the configuration // (i.e., AttributeConfig in ValidateAttributeRequest) is greater than or, equal to the sum of the values of the // attributes in the slice of AttributePath. diff --git a/internal/provider/validators_test.go b/internal/provider/validators_test.go index c9f835e9..7981aea8 100644 --- a/internal/provider/validators_test.go +++ b/internal/provider/validators_test.go @@ -12,72 +12,6 @@ import ( "github.com/hashicorp/terraform-plugin-go/tftypes" ) -func TestIntAtLeastValidator_Validate(t *testing.T) { - t.Parallel() - - req := tfsdk.ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("length"), - Config: tfsdk.Config{ - Schema: passwordSchemaV1(), - }, - } - - cases := []struct { - name string - reqAttribConfig attr.Value - expectDiag bool - expectedValidatorDiags diag.Diagnostics - }{ - { - name: "attribute wrong type", - reqAttribConfig: types.String{Value: "16"}, - expectDiag: true, - }, - { - name: "attribute less than min val", - reqAttribConfig: types.Int64{Value: 5}, - expectDiag: true, - expectedValidatorDiags: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("length"), - `Attribute "length" is less than minimum required.`, - `Attribute "length" (5) must be at least 10.`, - ), - }, - }, - { - name: "attribute equal to min val", - reqAttribConfig: types.Int64{Value: 10}, - expectDiag: false, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - req.AttributeConfig = c.reqAttribConfig - resp := tfsdk.ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{}, - } - - validator := NewIntAtLeastValidator(10) - validator.Validate(context.Background(), req, &resp) - - if c.expectDiag { - if len(resp.Diagnostics) != 1 { - t.Errorf("expecting resp diags len: 1, actual resp diags len: %d", len(resp.Diagnostics)) - } - } - - // Only test the contents of diags that are explicitly under the control of the validator. - if c.expectedValidatorDiags != nil { - if !cmp.Equal(c.expectedValidatorDiags, resp.Diagnostics) { - t.Errorf("expecting resp diags: %s, actual resp diags: %s", c.expectedValidatorDiags, resp.Diagnostics) - } - } - }) - } -} - func TestIsAtLeastSumOfValidator_Validate(t *testing.T) { t.Parallel() From 1251d078813b2a14a16148a15ed225ce67e700a6 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 16 Jun 2022 11:15:18 +0100 Subject: [PATCH 83/96] Fixing test (#177) --- internal/provider/resource_string_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index 91acf5b3..0d2c6ca3 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -290,9 +290,9 @@ func TestAccResourceStringErrors(t *testing.T) { }, { Config: `resource "random_string" "invalid_length" { - length = 0 + length = 0 }`, - ExpectError: regexp.MustCompile(`.*Attribute "length" \(0\) must be at least 1`), + ExpectError: regexp.MustCompile(`.*Value must be at least 1, got: 0`), }, }, }) From 818a27bfee10478a8ed6b4f17b1d2d1ba29c4d56 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 23 Jun 2022 15:54:54 +0100 Subject: [PATCH 84/96] Porting setting default values during import password and string (#177) --- internal/provider/resource_password.go | 22 ++++++++++++++++----- internal/provider/resource_password_test.go | 2 +- internal/provider/resource_string.go | 14 +++++++++++-- internal/provider/resource_string_test.go | 7 +++---- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 70907066..57812db3 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -89,9 +89,11 @@ func passwordSchemaV2() tfsdk.Schema { "length": { Description: "The length of the string desired. The minimum value for length is 1 and, length " + "must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`).", - Type: types.Int64Type, - Required: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Type: types.Int64Type, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, Validators: []tfsdk.AttributeValidator{ int64validator.AtLeast(1), NewIntIsAtLeastSumOfValidator( @@ -614,8 +616,18 @@ func importPassword(ctx context.Context, req tfsdk.ImportResourceStateRequest, r id := req.ID state := PasswordModelV2{ - ID: types.String{Value: "none"}, - Result: types.String{Value: id}, + ID: types.String{Value: "none"}, + Result: types.String{Value: id}, + Length: types.Int64{Value: int64(len(id))}, + Special: types.Bool{Value: true}, + Upper: types.Bool{Value: true}, + Lower: types.Bool{Value: true}, + Number: types.Bool{Value: true}, + Numeric: types.Bool{Value: true}, + MinSpecial: types.Int64{Value: 0}, + MinUpper: types.Int64{Value: 0}, + MinLower: types.Int64{Value: 0}, + MinNumeric: types.Int64{Value: 0}, } state.Keepers.ElemType = types.StringType diff --git a/internal/provider/resource_password_test.go b/internal/provider/resource_password_test.go index 54173b1b..09346e8e 100644 --- a/internal/provider/resource_password_test.go +++ b/internal/provider/resource_password_test.go @@ -47,7 +47,7 @@ func TestAccResourcePasswordBasic(t *testing.T) { }, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"bcrypt_hash", "length", "lower", "number", "numeric", "special", "upper", "min_lower", "min_numeric", "min_special", "min_upper", "override_special"}, + ImportStateVerifyIgnore: []string{"bcrypt_hash"}, }, }, }) diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index de121206..674a1321 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -438,8 +438,18 @@ func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, res id := req.ID state := StringModelV2{ - ID: types.String{Value: id}, - Result: types.String{Value: id}, + ID: types.String{Value: id}, + Result: types.String{Value: id}, + Length: types.Int64{Value: int64(len(id))}, + Special: types.Bool{Value: true}, + Upper: types.Bool{Value: true}, + Lower: types.Bool{Value: true}, + Number: types.Bool{Value: true}, + Numeric: types.Bool{Value: true}, + MinSpecial: types.Int64{Value: 0}, + MinUpper: types.Int64{Value: 0}, + MinLower: types.Int64{Value: 0}, + MinNumeric: types.Int64{Value: 0}, } state.Keepers.ElemType = types.StringType diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index 0d2c6ca3..c23b0165 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -22,10 +22,9 @@ func TestAccResourceString(t *testing.T) { ), }, { - ResourceName: "random_string.basic", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"length", "lower", "number", "numeric", "special", "upper", "min_lower", "min_numeric", "min_special", "min_upper", "override_special"}, + ResourceName: "random_string.basic", + ImportState: true, + ImportStateVerify: true, }, }, }) From 643151bd347eb2fae24a59af0f0bfa2bd80987e3 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 24 Jun 2022 13:14:53 +0100 Subject: [PATCH 85/96] Removing deprecated number attribute (#177) --- internal/planmodifiers/attribute.go | 86 +++++++++ internal/provider/models.go | 2 - internal/provider/plan_modifiers.go | 185 -------------------- internal/provider/resource_password.go | 123 ++++++------- internal/provider/resource_password_test.go | 159 +++++++++-------- internal/provider/resource_pet.go | 6 +- internal/provider/resource_string.go | 82 ++++----- internal/provider/resource_string_test.go | 81 +++++---- 8 files changed, 311 insertions(+), 413 deletions(-) create mode 100644 internal/planmodifiers/attribute.go delete mode 100644 internal/provider/plan_modifiers.go diff --git a/internal/planmodifiers/attribute.go b/internal/planmodifiers/attribute.go new file mode 100644 index 00000000..8ba10059 --- /dev/null +++ b/internal/planmodifiers/attribute.go @@ -0,0 +1,86 @@ +package planmodifiers + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +// DefaultValue accepts an attr.Value and uses the supplied value to set a default if the config for +// the attribute is null. +func DefaultValue(val attr.Value) tfsdk.AttributePlanModifier { + return &defaultValueAttributePlanModifier{val} +} + +type defaultValueAttributePlanModifier struct { + val attr.Value +} + +func (d *defaultValueAttributePlanModifier) Description(ctx context.Context) string { + return "If the config does not contain a value, a default will be set using val." +} + +func (d *defaultValueAttributePlanModifier) MarkdownDescription(ctx context.Context) string { + return d.Description(ctx) +} + +// Modify checks that the value of the attribute in the configuration and assigns the default value if +// the value in the config is null. This is a destructive operation in that it will overwrite any value +// present in the plan. +func (d *defaultValueAttributePlanModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + // Do not set default if the attribute configuration has been set. + if !req.AttributeConfig.IsNull() { + return + } + + resp.AttributePlan = d.val +} + +// RequiresReplace returns an attribute plan modifier that is identical to tfsdk.RequiresReplace() with +// the exception that there is no check for `configRaw.IsNull && attrSchema.Computed` as a replacement +// needs to be triggered when the attribute has been removed from the config. +func RequiresReplace() tfsdk.AttributePlanModifier { + return RequiresReplaceModifier{} +} + +type RequiresReplaceModifier struct{} + +// Description returns a human-readable description of the plan modifier. +func (r RequiresReplaceModifier) Description(ctx context.Context) string { + return "If the value of this attribute changes, Terraform will destroy and recreate the resource." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (r RequiresReplaceModifier) MarkdownDescription(ctx context.Context) string { + return r.Description(ctx) +} + +// Modify will trigger replacement (i.e., destroy-create) when `configRaw.IsNull && attrSchema.Computed`, +// which differs from the behaviour of `tfsdk.RequiresReplace()`. +func (r RequiresReplaceModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + if req.AttributeConfig == nil || req.AttributePlan == nil || req.AttributeState == nil { + // shouldn't happen, but let's not panic if it does + return + } + + if req.State.Raw.IsNull() { + // if we're creating the resource, no need to delete and + // recreate it + return + } + + if req.Plan.Raw.IsNull() { + // if we're deleting the resource, no need to delete and + // recreate it + return + } + + if req.AttributePlan.Equal(req.AttributeState) { + // if the plan and the state are in agreement, this attribute + // isn't changing, don't require replace + return + } + + resp.RequiresReplace = true +} diff --git a/internal/provider/models.go b/internal/provider/models.go index 7f9c4bfb..611bf687 100644 --- a/internal/provider/models.go +++ b/internal/provider/models.go @@ -29,7 +29,6 @@ type PasswordModelV2 struct { Special types.Bool `tfsdk:"special"` Upper types.Bool `tfsdk:"upper"` Lower types.Bool `tfsdk:"lower"` - Number types.Bool `tfsdk:"number"` Numeric types.Bool `tfsdk:"numeric"` MinNumeric types.Int64 `tfsdk:"min_numeric"` MinUpper types.Int64 `tfsdk:"min_upper"` @@ -97,7 +96,6 @@ type StringModelV2 struct { Special types.Bool `tfsdk:"special"` Upper types.Bool `tfsdk:"upper"` Lower types.Bool `tfsdk:"lower"` - Number types.Bool `tfsdk:"number"` Numeric types.Bool `tfsdk:"numeric"` MinNumeric types.Int64 `tfsdk:"min_numeric"` MinUpper types.Int64 `tfsdk:"min_upper"` diff --git a/internal/provider/plan_modifiers.go b/internal/provider/plan_modifiers.go deleted file mode 100644 index f4f1059c..00000000 --- a/internal/provider/plan_modifiers.go +++ /dev/null @@ -1,185 +0,0 @@ -package provider - -import ( - "context" - "fmt" - - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" -) - -// newDefaultValueAttributePlanModifier accepts an attr.Value and returns a struct that implements the -// AttributePlanModifier interface. -//nolint:unparam // val is always true -func newDefaultValueAttributePlanModifier(val attr.Value) tfsdk.AttributePlanModifier { - return &defaultValueAttributePlanModifier{val} -} - -type defaultValueAttributePlanModifier struct { - val attr.Value -} - -func (d *defaultValueAttributePlanModifier) Description(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set using val." -} - -func (d *defaultValueAttributePlanModifier) MarkdownDescription(ctx context.Context) string { - return "If the plan does not contain a value, a default will be set using `val`." -} - -// Modify checks that the value of the attribute in the configuration and the plan and only assigns the default value if -// the value in the config is null or the value in the plan is not known, or is known but is null. -func (d *defaultValueAttributePlanModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { - // TODO: Remove once attr.Value interface includes IsNull. - attribConfigValue, err := req.AttributeConfig.ToTerraformValue(ctx) - if err != nil { - resp.Diagnostics.AddError( - "Default value attribute plan modifier failed", - fmt.Sprintf("Unable to convert attribute config (%s) to terraform value: %s", req.AttributeConfig.Type(ctx).String(), err), - ) - return - } - - // Do not set default if the attribute configuration has been set. - if !attribConfigValue.IsNull() { - return - } - - // TODO: Remove once attr.Value interface includes IsUnknown. - attribPlanValue, err := req.AttributePlan.ToTerraformValue(ctx) - if err != nil { - resp.Diagnostics.AddError( - "Default value attribute plan modifier failed", - fmt.Sprintf("Unable to convert attribute plan %s to terraform value: %s", req.AttributePlan.Type(ctx).String(), err), - ) - return - } - - // If the attribute plan is "known" and "not null", then a previous plan modifier in the sequence has already been - // applied and, we don't want to overwrite. - if attribPlanValue.IsKnown() && !attribPlanValue.IsNull() { - return - } - - resp.AttributePlan = d.val -} - -func newNumberNumericAttributePlanModifier() tfsdk.AttributePlanModifier { - return &numberNumericAttributePlanModifier{} -} - -type numberNumericAttributePlanModifier struct { -} - -func (d *numberNumericAttributePlanModifier) Description(ctx context.Context) string { - return "" -} - -func (d *numberNumericAttributePlanModifier) MarkdownDescription(ctx context.Context) string { - return "" -} - -func (d *numberNumericAttributePlanModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { - numberConfig := types.Bool{} - diags := req.Config.GetAttribute(ctx, tftypes.NewAttributePath().WithAttributeName("number"), &numberConfig) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - numericConfig := types.Bool{} - req.Config.GetAttribute(ctx, tftypes.NewAttributePath().WithAttributeName("numeric"), &numericConfig) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - if !numberConfig.Null && !numericConfig.Null { - resp.Diagnostics.AddError( - "Number numeric attribute plan modifier failed", - "Cannot specify both number and numeric in config", - ) - return - } - - numberPlan := types.Bool{} - diags = req.Config.GetAttribute(ctx, tftypes.NewAttributePath().WithAttributeName("number"), &numberPlan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - numericPlan := types.Bool{} - req.Config.GetAttribute(ctx, tftypes.NewAttributePath().WithAttributeName("numeric"), &numericPlan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - // Default to true for both number and numeric when both are null. - if numberPlan.Null && numericPlan.Null { - resp.AttributePlan = types.Bool{Value: true} - return - } - - // Default to using value for numeric if number is null - if numberPlan.Null && !numericPlan.Null { - resp.AttributePlan = numericPlan - return - } - - // Default to using value for number if numeric is null - if !numberPlan.Null && numericPlan.Null { - resp.AttributePlan = numberPlan - return - } -} - -// RequiresReplace returns an attribute plan modifier that is identical to -// tfsdk.RequiresReplace() with the exception that there is no check for -// configRaw.IsNull && attrSchema.Computed because the defaults that we -// need to assign are computed. -func RequiresReplace() tfsdk.AttributePlanModifier { - return RequiresReplaceModifier{} -} - -type RequiresReplaceModifier struct{} - -func (r RequiresReplaceModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { - if req.AttributeConfig == nil || req.AttributePlan == nil || req.AttributeState == nil { - // shouldn't happen, but let's not panic if it does - return - } - - if req.State.Raw.IsNull() { - // if we're creating the resource, no need to delete and - // recreate it - return - } - - if req.Plan.Raw.IsNull() { - // if we're deleting the resource, no need to delete and - // recreate it - return - } - - if req.AttributePlan.Equal(req.AttributeState) { - // if the plan and the state are in agreement, this attribute - // isn't changing, don't require replace - return - } - - resp.RequiresReplace = true -} - -// Description returns a human-readable description of the plan modifier. -func (r RequiresReplaceModifier) Description(ctx context.Context) string { - return "If the value of this attribute changes, Terraform will destroy and recreate the resource." -} - -// MarkdownDescription returns a markdown description of the plan modifier. -func (r RequiresReplaceModifier) MarkdownDescription(ctx context.Context) string { - return "If the value of this attribute changes, Terraform will destroy and recreate the resource." -} diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 57812db3..12a3f626 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -9,6 +9,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" "golang.org/x/crypto/bcrypt" + + "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers" ) type resourcePasswordType struct{} @@ -111,8 +113,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), - RequiresReplace(), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -122,8 +124,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), - RequiresReplace(), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -133,22 +135,9 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), - RequiresReplace(), - }, - }, - - "number": { - Description: "Include numeric characters in the result. Default value is `true`. " + - "**NOTE**: This is deprecated, use `numeric` instead.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - newNumberNumericAttributePlanModifier(), - RequiresReplace(), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, - DeprecationMessage: "**NOTE**: This is deprecated, use `numeric` instead.", }, "numeric": { @@ -157,8 +146,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newNumberNumericAttributePlanModifier(), - RequiresReplace(), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -168,8 +157,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), - RequiresReplace(), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -179,8 +168,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), - RequiresReplace(), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -190,8 +179,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), - RequiresReplace(), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -201,8 +190,8 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), - RequiresReplace(), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -256,8 +245,10 @@ func passwordSchemaV1() tfsdk.Schema { Type: types.MapType{ ElemType: types.StringType, }, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, }, "length": { @@ -283,8 +274,8 @@ func passwordSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -294,8 +285,8 @@ func passwordSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -305,8 +296,8 @@ func passwordSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -316,8 +307,8 @@ func passwordSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -327,8 +318,8 @@ func passwordSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -338,8 +329,8 @@ func passwordSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -349,8 +340,8 @@ func passwordSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -360,8 +351,8 @@ func passwordSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -441,8 +432,8 @@ func passwordSchemaV0() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -452,8 +443,8 @@ func passwordSchemaV0() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -463,8 +454,8 @@ func passwordSchemaV0() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -474,8 +465,8 @@ func passwordSchemaV0() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -485,8 +476,8 @@ func passwordSchemaV0() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -496,8 +487,8 @@ func passwordSchemaV0() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -507,8 +498,8 @@ func passwordSchemaV0() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -518,8 +509,8 @@ func passwordSchemaV0() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -551,8 +542,6 @@ func passwordSchemaV0() tfsdk.Schema { } } -// createPassword currently uses plan.Number.Value for both number and numeric. -// TODO: Remove usage of number before release. func createPassword(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { var plan PasswordModelV2 @@ -588,7 +577,6 @@ func createPassword(ctx context.Context, req tfsdk.CreateResourceRequest, resp * Special: types.Bool{Value: plan.Special.Value}, Upper: types.Bool{Value: plan.Upper.Value}, Lower: types.Bool{Value: plan.Lower.Value}, - Number: types.Bool{Value: plan.Number.Value}, Numeric: types.Bool{Value: plan.Numeric.Value}, MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, MinUpper: types.Int64{Value: plan.MinUpper.Value}, @@ -622,7 +610,6 @@ func importPassword(ctx context.Context, req tfsdk.ImportResourceStateRequest, r Special: types.Bool{Value: true}, Upper: types.Bool{Value: true}, Lower: types.Bool{Value: true}, - Number: types.Bool{Value: true}, Numeric: types.Bool{Value: true}, MinSpecial: types.Int64{Value: 0}, MinUpper: types.Int64{Value: 0}, @@ -660,7 +647,6 @@ func upgradePasswordStateV0toV2(ctx context.Context, req tfsdk.UpgradeResourceSt Special: passwordDataV0.Special, Upper: passwordDataV0.Upper, Lower: passwordDataV0.Lower, - Number: passwordDataV0.Number, Numeric: passwordDataV0.Number, MinNumeric: passwordDataV0.MinNumeric, MinLower: passwordDataV0.MinLower, @@ -696,7 +682,6 @@ func upgradePasswordStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceSt Special: passwordDataV1.Special, Upper: passwordDataV1.Upper, Lower: passwordDataV1.Lower, - Number: passwordDataV1.Number, Numeric: passwordDataV1.Number, MinNumeric: passwordDataV1.MinNumeric, MinLower: passwordDataV1.MinLower, diff --git a/internal/provider/resource_password_test.go b/internal/provider/resource_password_test.go index 09346e8e..b688a22f 100644 --- a/internal/provider/resource_password_test.go +++ b/internal/provider/resource_password_test.go @@ -76,7 +76,8 @@ func TestAccResourcePasswordOverride(t *testing.T) { } // TestAccResourcePassword_StateUpgrade_V0toV2 covers the state upgrades from V0 to V2. -// This includes the deprecation of `number` and the addition of `numeric` and `bcrypt_hash` attributes. +// This includes the deprecation and removal of `number` and the addition of `numeric` +// and `bcrypt_hash` attributes. // v3.1.3 is used as this is last version before `bcrypt_hash` attributed was added. func TestAccResourcePassword_StateUpgrade_V0toV2(t *testing.T) { t.Parallel() @@ -93,6 +94,9 @@ func TestAccResourcePassword_StateUpgrade_V0toV2(t *testing.T) { configBeforeUpgrade: `resource "random_password" "default" { length = 12 }`, + configDuringUpgrade: `resource "random_password" "default" { + length = 12 + }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckNoResourceAttr("random_password.default", "bcrypt_hash"), }, @@ -101,72 +105,78 @@ func TestAccResourcePassword_StateUpgrade_V0toV2(t *testing.T) { }, }, { - name: "number is absent", + name: "number is absent before numeric is absent during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 }`, + configDuringUpgrade: `resource "random_password" "default" { + length = 12 + }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "true"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is absent then true", + name: "number is absent before numeric is true during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 }`, configDuringUpgrade: `resource "random_password" "default" { length = 12 - number = true + numeric = true }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "true"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is absent then false", + name: "number is absent before numeric is false during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 }`, configDuringUpgrade: `resource "random_password" "default" { length = 12 - number = false + numeric = false }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "true"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "false"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "false"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is true", + name: "number is true before numeric is absent during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = true }`, + configDuringUpgrade: `resource "random_password" "default" { + length = 12 + }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "true"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is true then absent", + name: "number is true before numeric is absent during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = true @@ -179,46 +189,50 @@ func TestAccResourcePassword_StateUpgrade_V0toV2(t *testing.T) { resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is true then false", + name: "number is true before numeric is false during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = true }`, configDuringUpgrade: `resource "random_password" "default" { length = 12 - number = false + numeric = false }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "true"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "false"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "false"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is false", + name: "number is false before numeric is false during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = false }`, + configDuringUpgrade: `resource "random_password" "default" { + length = 12 + numeric = false + }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "false"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "false"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "false"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is false then absent", + name: "number is false before numeric is absent during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = false @@ -231,37 +245,33 @@ func TestAccResourcePassword_StateUpgrade_V0toV2(t *testing.T) { resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is false then true", + name: "number is false before numeric is true during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = false }`, configDuringUpgrade: `resource "random_password" "default" { length = 12 - number = true + numeric = true }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "false"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - if c.configDuringUpgrade == "" { - c.configDuringUpgrade = c.configBeforeUpgrade - } - resource.UnitTest(t, resource.TestCase{ Steps: []resource.TestStep{ { @@ -284,7 +294,7 @@ func TestAccResourcePassword_StateUpgrade_V0toV2(t *testing.T) { } // TestAccResourcePassword_StateUpgrade_V1toV2 covers the state upgrades from V1 to V2. -// This includes the deprecation of `number` and the addition of `numeric` attributes. +// This includes the deprecation and removal of `number` and the addition of `numeric` attributes. // v3.2.0 was used as this is the last version before `number` was deprecated and `numeric` attribute // was added. func TestAccResourcePassword_StateUpgrade_V1toV2(t *testing.T) { @@ -298,72 +308,79 @@ func TestAccResourcePassword_StateUpgrade_V1toV2(t *testing.T) { afterStateUpgrade []resource.TestCheckFunc }{ { - name: "number is absent", + name: "number is absent before numeric is absent during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 }`, + configDuringUpgrade: `resource "random_password" "default" { + length = 12 + }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "true"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is absent then true", + name: "number is absent before numeric is true during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 }`, configDuringUpgrade: `resource "random_password" "default" { length = 12 - number = true + numeric = true }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "true"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is absent then false", + name: "number is absent before numeric is false during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 }`, configDuringUpgrade: `resource "random_password" "default" { length = 12 - number = false + numeric = false }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "true"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "false"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "false"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is true", + name: "number is true before numeric is true during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = true }`, + configDuringUpgrade: `resource "random_password" "default" { + length = 12 + numeric = true + }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "true"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is true then absent", + name: "number is true before numeric is absent during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = true @@ -376,46 +393,50 @@ func TestAccResourcePassword_StateUpgrade_V1toV2(t *testing.T) { resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is true then false", + name: "number is true before numeric is false during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = true }`, configDuringUpgrade: `resource "random_password" "default" { length = 12 - number = false + numeric = false }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "true"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "false"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "false"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is false", + name: "number is false before numeric is false during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = false }`, + configDuringUpgrade: `resource "random_password" "default" { + length = 12 + numeric = false + }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "false"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "false"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "false"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is false then absent", + name: "number is false before numeric is absent during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = false @@ -428,27 +449,27 @@ func TestAccResourcePassword_StateUpgrade_V1toV2(t *testing.T) { resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, { - name: "number is false then true", + name: "number is false before numeric is true during", configBeforeUpgrade: `resource "random_password" "default" { length = 12 number = false }`, configDuringUpgrade: `resource "random_password" "default" { length = 12 - number = true + numeric = true }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_password.default", "number", "false"), resource.TestCheckNoResourceAttr("random_password.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_password.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_password.default", "number", "random_password.default", "numeric"), + resource.TestCheckResourceAttr("random_password.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_password.default", "number"), }, }, } @@ -545,7 +566,6 @@ func TestMigratePasswordStateV0toV2(t *testing.T) { Special: types.Bool{Value: true}, Upper: types.Bool{Value: true}, Lower: types.Bool{Value: true}, - Number: types.Bool{Value: true}, Numeric: types.Bool{Value: true}, MinNumeric: types.Int64{Value: 0}, MinUpper: types.Int64{Value: 0}, @@ -614,7 +634,6 @@ func TestMigratePasswordStateV1toV2(t *testing.T) { Special: types.Bool{Value: true}, Upper: types.Bool{Value: true}, Lower: types.Bool{Value: true}, - Number: types.Bool{Value: true}, Numeric: types.Bool{Value: true}, MinNumeric: types.Int64{Value: 0}, MinUpper: types.Int64{Value: 0}, diff --git a/internal/provider/resource_pet.go b/internal/provider/resource_pet.go index 0e2cf8d7..113ecdb0 100644 --- a/internal/provider/resource_pet.go +++ b/internal/provider/resource_pet.go @@ -9,6 +9,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers" ) type resourcePetType struct{} @@ -42,8 +44,8 @@ func (r resourcePetType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnost Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifiers.DefaultValue(types.Int64{Value: 2}), tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 2}), }, }, "prefix": { @@ -58,8 +60,8 @@ func (r resourcePetType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnost Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifiers.DefaultValue(types.String{Value: "-"}), tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.String{Value: "-"}), }, }, "id": { diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index 674a1321..91329dcc 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -8,6 +8,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers" ) type resourceStringType struct{} @@ -106,8 +108,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), - RequiresReplace(), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -117,8 +119,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), - RequiresReplace(), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -128,22 +130,9 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), - RequiresReplace(), - }, - }, - - "number": { - Description: "Include numeric characters in the result. Default value is `true`. " + - "**NOTE**: This is deprecated, use `numeric` instead.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - newNumberNumericAttributePlanModifier(), - RequiresReplace(), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, - DeprecationMessage: "**NOTE**: This is deprecated, use `numeric` instead.", }, "numeric": { @@ -152,8 +141,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newNumberNumericAttributePlanModifier(), - RequiresReplace(), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -163,8 +152,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), - RequiresReplace(), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -174,8 +163,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), - RequiresReplace(), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -185,8 +174,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), - RequiresReplace(), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -196,8 +185,8 @@ func stringSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), - RequiresReplace(), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -273,8 +262,8 @@ func stringSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -284,8 +273,8 @@ func stringSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -295,8 +284,8 @@ func stringSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -306,8 +295,8 @@ func stringSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Bool{Value: true}), + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), }, }, @@ -317,8 +306,8 @@ func stringSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -328,8 +317,8 @@ func stringSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -339,8 +328,8 @@ func stringSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -350,8 +339,8 @@ func stringSchemaV1() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - newDefaultValueAttributePlanModifier(types.Int64{Value: 0}), + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), }, }, @@ -417,7 +406,6 @@ func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tf Special: types.Bool{Value: plan.Special.Value}, Upper: types.Bool{Value: plan.Upper.Value}, Lower: types.Bool{Value: plan.Lower.Value}, - Number: types.Bool{Value: plan.Number.Value}, Numeric: types.Bool{Value: plan.Numeric.Value}, MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, MinUpper: types.Int64{Value: plan.MinUpper.Value}, @@ -444,7 +432,6 @@ func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, res Special: types.Bool{Value: true}, Upper: types.Bool{Value: true}, Lower: types.Bool{Value: true}, - Number: types.Bool{Value: true}, Numeric: types.Bool{Value: true}, MinSpecial: types.Int64{Value: 0}, MinUpper: types.Int64{Value: 0}, @@ -475,7 +462,6 @@ func upgradeStringStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStat Special: stringDataV1.Special, Upper: stringDataV1.Upper, Lower: stringDataV1.Lower, - Number: stringDataV1.Number, Numeric: stringDataV1.Number, MinNumeric: stringDataV1.MinNumeric, MinLower: stringDataV1.MinLower, diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index c23b0165..ac77350c 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -41,7 +41,7 @@ func TestAccResourceStringOverride(t *testing.T) { override_special = "!" lower = false upper = false - number = false + numeric = false }`, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrWith("random_string.override", "result", testCheckLen(4)), @@ -79,7 +79,7 @@ func TestAccResourceStringMin(t *testing.T) { } // TestAccResourceString_StateUpgrade_V1toV2 covers the state upgrade from V1 to V2. -// This includes the deprecation of `number` and the addition of `numeric` attributes. +// This includes the deprecation and removal of `number` and the addition of `numeric` attributes. // v3.2.0 was used as this is the last version before `number` was deprecated and `numeric` attribute // was added. func TestAccResourceString_StateUpgrade_V1toV2(t *testing.T) { @@ -93,72 +93,79 @@ func TestAccResourceString_StateUpgrade_V1toV2(t *testing.T) { afterStateUpgrade []resource.TestCheckFunc }{ { - name: "number is absent", + name: "number is absent before numeric is absent during", configBeforeUpgrade: `resource "random_string" "default" { length = 12 }`, + configDuringUpgrade: `resource "random_string" "default" { + length = 12 + }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_string.default", "number", "true"), resource.TestCheckNoResourceAttr("random_string.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_string.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_string.default", "number", "random_string.default", "numeric"), + resource.TestCheckResourceAttr("random_string.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_string.default", "number"), }, }, { - name: "number is absent then true", + name: "number is absent before numeric is true during", configBeforeUpgrade: `resource "random_string" "default" { length = 12 }`, configDuringUpgrade: `resource "random_string" "default" { length = 12 - number = true + numeric = true }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_string.default", "number", "true"), resource.TestCheckNoResourceAttr("random_string.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_string.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_string.default", "number", "random_string.default", "numeric"), + resource.TestCheckResourceAttr("random_string.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_string.default", "number"), }, }, { - name: "number is absent then false", + name: "number is absent before numeric is false during", configBeforeUpgrade: `resource "random_string" "default" { length = 12 }`, configDuringUpgrade: `resource "random_string" "default" { length = 12 - number = false + numeric = false }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_string.default", "number", "true"), resource.TestCheckNoResourceAttr("random_string.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_string.default", "number", "false"), - resource.TestCheckResourceAttrPair("random_string.default", "number", "random_string.default", "numeric"), + resource.TestCheckResourceAttr("random_string.default", "numeric", "false"), + resource.TestCheckNoResourceAttr("random_string.default", "number"), }, }, { - name: "number is true", + name: "number is true before numeric is true during", configBeforeUpgrade: `resource "random_string" "default" { length = 12 number = true }`, + configDuringUpgrade: `resource "random_string" "default" { + length = 12 + numeric = true + }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_string.default", "number", "true"), resource.TestCheckNoResourceAttr("random_string.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_string.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_string.default", "number", "random_string.default", "numeric"), + resource.TestCheckResourceAttr("random_string.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_string.default", "number"), }, }, { - name: "number is true then absent", + name: "number is true before numeric is absent during", configBeforeUpgrade: `resource "random_string" "default" { length = 12 number = true @@ -171,46 +178,50 @@ func TestAccResourceString_StateUpgrade_V1toV2(t *testing.T) { resource.TestCheckNoResourceAttr("random_string.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_string.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_string.default", "number", "random_string.default", "numeric"), + resource.TestCheckResourceAttr("random_string.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_string.default", "number"), }, }, { - name: "number is true then false", + name: "number is true before numeric is false during", configBeforeUpgrade: `resource "random_string" "default" { length = 12 number = true }`, configDuringUpgrade: `resource "random_string" "default" { length = 12 - number = false + numeric = false }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_string.default", "number", "true"), resource.TestCheckNoResourceAttr("random_string.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_string.default", "number", "false"), - resource.TestCheckResourceAttrPair("random_string.default", "number", "random_string.default", "numeric"), + resource.TestCheckResourceAttr("random_string.default", "numeric", "false"), + resource.TestCheckNoResourceAttr("random_string.default", "number"), }, }, { - name: "number is false", + name: "number is false before numeric is false during", configBeforeUpgrade: `resource "random_string" "default" { length = 12 number = false }`, + configDuringUpgrade: `resource "random_string" "default" { + length = 12 + numeric = false + }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_string.default", "number", "false"), resource.TestCheckNoResourceAttr("random_string.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_string.default", "number", "false"), - resource.TestCheckResourceAttrPair("random_string.default", "number", "random_string.default", "numeric"), + resource.TestCheckResourceAttr("random_string.default", "numeric", "false"), + resource.TestCheckNoResourceAttr("random_string.default", "number"), }, }, { - name: "number is false then absent", + name: "number is false before numeric is absent during", configBeforeUpgrade: `resource "random_string" "default" { length = 12 number = false @@ -223,37 +234,33 @@ func TestAccResourceString_StateUpgrade_V1toV2(t *testing.T) { resource.TestCheckNoResourceAttr("random_string.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_string.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_string.default", "number", "random_string.default", "numeric"), + resource.TestCheckResourceAttr("random_string.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_string.default", "number"), }, }, { - name: "number is false then true", + name: "number is false before numeric is true during", configBeforeUpgrade: `resource "random_string" "default" { length = 12 number = false }`, configDuringUpgrade: `resource "random_string" "default" { length = 12 - number = true + numeric = true }`, beforeStateUpgrade: []resource.TestCheckFunc{ resource.TestCheckResourceAttr("random_string.default", "number", "false"), resource.TestCheckNoResourceAttr("random_string.default", "numeric"), }, afterStateUpgrade: []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("random_string.default", "number", "true"), - resource.TestCheckResourceAttrPair("random_string.default", "number", "random_string.default", "numeric"), + resource.TestCheckResourceAttr("random_string.default", "numeric", "true"), + resource.TestCheckNoResourceAttr("random_string.default", "number"), }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - if c.configDuringUpgrade == "" { - c.configDuringUpgrade = c.configBeforeUpgrade - } - resource.UnitTest(t, resource.TestCase{ Steps: []resource.TestStep{ { From 8a35a9eba5a808a26db5c6d2a223d1973a42768b Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 24 Jun 2022 14:14:48 +0100 Subject: [PATCH 86/96] Updating CHANGELOG.md (#177) --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b12ec803..55d21c36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## 4.0.0-rc.1 (unreleased) + +NOTES: + +* Provider has been re-written using the new [`terraform-plugin-framework`](https://www.terraform.io/plugin/framework) ([#177](https://github.com/hashicorp/terraform-provider-random/pull/177)). + +BREAKING CHANGES: + +* [Terraform `>=1.0`](https://www.terraform.io/language/upgrade-guides/1-0) is now required to use this provider. + +* resource/random_password: Deprecated attribute `number` has been removed ([266](https://github.com/hashicorp/terraform-provider-random/issues/266)). +* resource/random_string: Deprecated attribute `number` has been removed ([266](https://github.com/hashicorp/terraform-provider-random/issues/266)). + ## 3.3.1 (June 07, 2022) BUG FIXES: From aaeab93cfc65255278b6cb138f69135921f7d948 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 24 Jun 2022 14:27:32 +0100 Subject: [PATCH 87/96] Updating CHANGELOG.md (#177) --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e2b2423..fb1e7900 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,6 @@ BUG FIXES: * resource/random_password: When importing set defaults for all attributes that have a default defined ([256](https://github.com/hashicorp/terraform-provider-random/pull/256)). * resource/random_string: When importing set defaults for all attributes that have a default defined ([256](https://github.com/hashicorp/terraform-provider-random/pull/256)). ->>>>>>> main ## 3.3.1 (June 07, 2022) From d12f1ad05018097dd8b698cfa55a06896c12084c Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 24 Jun 2022 14:30:41 +0100 Subject: [PATCH 88/96] Updating docs (#177) --- docs/resources/password.md | 1 - docs/resources/string.md | 1 - 2 files changed, 2 deletions(-) diff --git a/docs/resources/password.md b/docs/resources/password.md index 22bd8a16..1a214204 100644 --- a/docs/resources/password.md +++ b/docs/resources/password.md @@ -45,7 +45,6 @@ resource "aws_db_instance" "example" { - `min_numeric` (Number) Minimum number of numeric characters in the result. Default value is `0`. - `min_special` (Number) Minimum number of special characters in the result. Default value is `0`. - `min_upper` (Number) Minimum number of uppercase alphabet characters in the result. Default value is `0`. -- `number` (Boolean, Deprecated) Include numeric characters in the result. Default value is `true`. **NOTE**: This is deprecated, use `numeric` instead. - `numeric` (Boolean) Include numeric characters in the result. Default value is `true`. - `override_special` (String) Supply your own list of special characters to use for string generation. This overrides the default character list in the special argument. The `special` argument must still be set to true for any overwritten characters to be used in generation. - `special` (Boolean) Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`. diff --git a/docs/resources/string.md b/docs/resources/string.md index 2ad2427f..e6ba9c37 100644 --- a/docs/resources/string.md +++ b/docs/resources/string.md @@ -40,7 +40,6 @@ resource "random_string" "random" { - `min_numeric` (Number) Minimum number of numeric characters in the result. Default value is `0`. - `min_special` (Number) Minimum number of special characters in the result. Default value is `0`. - `min_upper` (Number) Minimum number of uppercase alphabet characters in the result. Default value is `0`. -- `number` (Boolean, Deprecated) Include numeric characters in the result. Default value is `true`. **NOTE**: This is deprecated, use `numeric` instead. - `numeric` (Boolean) Include numeric characters in the result. Default value is `true`. - `override_special` (String) Supply your own list of special characters to use for string generation. This overrides the default character list in the special argument. The `special` argument must still be set to true for any overwritten characters to be used in generation. - `special` (Boolean) Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`. From f88891778a2c0e91b3855cedcd6d3ca9168323f3 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 27 Jun 2022 08:24:22 +0100 Subject: [PATCH 89/96] Refactoring (#177) --- .../{provider => diagnostics}/diagnostics.go | 12 +- internal/provider/models.go | 128 ------ internal/provider/provider.go | 39 +- ...d_test.go => provider_resource_id_test.go} | 2 - ...t.go => provider_resource_integer_test.go} | 5 - ....go => provider_resource_password_test.go} | 138 +----- ..._test.go => provider_resource_pet_test.go} | 4 - ...t.go => provider_resource_shuffle_test.go} | 5 - ...st.go => provider_resource_string_test.go} | 4 - ...test.go => provider_resource_uuid_test.go} | 1 - internal/provider/provider_test.go | 7 +- .../{provider/string.go => random/random.go} | 50 +-- internal/{provider => random}/seed.go | 2 +- .../id/resource.go} | 83 ++-- .../integer/resource.go} | 56 ++- internal/resources/password/resource.go | 277 ++++++++++++ internal/resources/password/resource_test.go | 140 ++++++ .../password/schema.go} | 233 +--------- .../pet/resource.go} | 62 +-- .../shuffle/resource.go} | 77 ++-- .../string/resource.go} | 397 ++++++------------ .../uuid}/resource_uuid.go | 62 ++- .../{provider => validators}/validators.go | 4 +- .../validators_test.go | 14 +- main.go | 2 +- 25 files changed, 854 insertions(+), 950 deletions(-) rename internal/{provider => diagnostics}/diagnostics.go (70%) delete mode 100644 internal/provider/models.go rename internal/provider/{resource_id_test.go => provider_resource_id_test.go} (93%) rename internal/provider/{resource_integer_test.go => provider_resource_integer_test.go} (92%) rename internal/provider/{resource_password_test.go => provider_resource_password_test.go} (77%) rename internal/provider/{resource_pet_test.go => provider_resource_pet_test.go} (90%) rename internal/provider/{resource_shuffle_test.go => provider_resource_shuffle_test.go} (93%) rename internal/provider/{resource_string_test.go => provider_resource_string_test.go} (97%) rename internal/provider/{resource_uuid_test.go => provider_resource_uuid_test.go} (92%) rename internal/{provider/string.go => random/random.go} (61%) rename internal/{provider => random}/seed.go (96%) rename internal/{provider/resource_id.go => resources/id/resource.go} (73%) rename internal/{provider/resource_integer.go => resources/integer/resource.go} (75%) create mode 100644 internal/resources/password/resource.go create mode 100644 internal/resources/password/resource_test.go rename internal/{provider/resource_password.go => resources/password/schema.go} (69%) rename internal/{provider/resource_pet.go => resources/pet/resource.go} (72%) rename internal/{provider/resource_shuffle.go => resources/shuffle/resource.go} (66%) rename internal/{provider/resource_string.go => resources/string/resource.go} (53%) rename internal/{provider => resources/uuid}/resource_uuid.go (65%) rename internal/{provider => validators}/validators.go (97%) rename internal/{provider => validators}/validators_test.go (95%) diff --git a/internal/provider/diagnostics.go b/internal/diagnostics/diagnostics.go similarity index 70% rename from internal/provider/diagnostics.go rename to internal/diagnostics/diagnostics.go index ef87d2eb..35ab13da 100644 --- a/internal/provider/diagnostics.go +++ b/internal/diagnostics/diagnostics.go @@ -1,4 +1,4 @@ -package provider +package diagnostics import ( "fmt" @@ -6,27 +6,27 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" ) -const retryMsg = "Retry the Terraform operation. If the error still occurs or happens regularly, please contact the provider developer with hardware and operating system information.\n\n" +const RetryMsg = "Retry the Terraform operation. If the error still occurs or happens regularly, please contact the provider developer with hardware and operating system information.\n\n" -func randomReadError(errMsg string) diag.Diagnostics { +func RandomReadError(errMsg string) diag.Diagnostics { var diags diag.Diagnostics diags.AddError( "Random Read Error", "While attempting to generate a random value for this resource, a read error was generated.\n\n"+ - retryMsg+ + RetryMsg+ fmt.Sprintf("Original Error: %s", errMsg), ) return diags } -func hashGenerationError(errMsg string) diag.Diagnostics { +func HashGenerationError(errMsg string) diag.Diagnostics { var diags diag.Diagnostics diags.AddError( "Hash Generation Error", - "While attempting to generate a hash from of the password an error occurred.\n\n"+ + "While attempting to generate a hash from the password an error occurred.\n\n"+ "Verify that the state contains a populated 'result' field, using 'terraform state show', and retry the operation\n\n"+ fmt.Sprintf("Original Error: %s", errMsg), ) diff --git a/internal/provider/models.go b/internal/provider/models.go deleted file mode 100644 index 611bf687..00000000 --- a/internal/provider/models.go +++ /dev/null @@ -1,128 +0,0 @@ -package provider - -import "github.com/hashicorp/terraform-plugin-framework/types" - -type IDModel struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - ByteLength types.Int64 `tfsdk:"byte_length"` - Prefix types.String `tfsdk:"prefix"` - B64URL types.String `tfsdk:"b64_url"` - B64Std types.String `tfsdk:"b64_std"` - Hex types.String `tfsdk:"hex"` - Dec types.String `tfsdk:"dec"` -} - -type IntegerModel struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Min types.Int64 `tfsdk:"min"` - Max types.Int64 `tfsdk:"max"` - Seed types.String `tfsdk:"seed"` - Result types.Int64 `tfsdk:"result"` -} - -type PasswordModelV2 struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Length types.Int64 `tfsdk:"length"` - Special types.Bool `tfsdk:"special"` - Upper types.Bool `tfsdk:"upper"` - Lower types.Bool `tfsdk:"lower"` - Numeric types.Bool `tfsdk:"numeric"` - MinNumeric types.Int64 `tfsdk:"min_numeric"` - MinUpper types.Int64 `tfsdk:"min_upper"` - MinLower types.Int64 `tfsdk:"min_lower"` - MinSpecial types.Int64 `tfsdk:"min_special"` - OverrideSpecial types.String `tfsdk:"override_special"` - Result types.String `tfsdk:"result"` - BcryptHash types.String `tfsdk:"bcrypt_hash"` -} - -type PasswordModelV1 struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Length types.Int64 `tfsdk:"length"` - Special types.Bool `tfsdk:"special"` - Upper types.Bool `tfsdk:"upper"` - Lower types.Bool `tfsdk:"lower"` - Number types.Bool `tfsdk:"number"` - MinNumeric types.Int64 `tfsdk:"min_numeric"` - MinUpper types.Int64 `tfsdk:"min_upper"` - MinLower types.Int64 `tfsdk:"min_lower"` - MinSpecial types.Int64 `tfsdk:"min_special"` - OverrideSpecial types.String `tfsdk:"override_special"` - Result types.String `tfsdk:"result"` - BcryptHash types.String `tfsdk:"bcrypt_hash"` -} - -type PasswordModelV0 struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Length types.Int64 `tfsdk:"length"` - Special types.Bool `tfsdk:"special"` - Upper types.Bool `tfsdk:"upper"` - Lower types.Bool `tfsdk:"lower"` - Number types.Bool `tfsdk:"number"` - MinNumeric types.Int64 `tfsdk:"min_numeric"` - MinUpper types.Int64 `tfsdk:"min_upper"` - MinLower types.Int64 `tfsdk:"min_lower"` - MinSpecial types.Int64 `tfsdk:"min_special"` - OverrideSpecial types.String `tfsdk:"override_special"` - Result types.String `tfsdk:"result"` -} - -type PetNameModel struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Length types.Int64 `tfsdk:"length"` - Prefix types.String `tfsdk:"prefix"` - Separator types.String `tfsdk:"separator"` -} - -type ShuffleModel struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Seed types.String `tfsdk:"seed"` - Input types.List `tfsdk:"input"` - ResultCount types.Int64 `tfsdk:"result_count"` - Result types.List `tfsdk:"result"` -} - -type StringModelV2 struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Length types.Int64 `tfsdk:"length"` - Special types.Bool `tfsdk:"special"` - Upper types.Bool `tfsdk:"upper"` - Lower types.Bool `tfsdk:"lower"` - Numeric types.Bool `tfsdk:"numeric"` - MinNumeric types.Int64 `tfsdk:"min_numeric"` - MinUpper types.Int64 `tfsdk:"min_upper"` - MinLower types.Int64 `tfsdk:"min_lower"` - MinSpecial types.Int64 `tfsdk:"min_special"` - OverrideSpecial types.String `tfsdk:"override_special"` - Result types.String `tfsdk:"result"` -} - -type StringModelV1 struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Length types.Int64 `tfsdk:"length"` - Special types.Bool `tfsdk:"special"` - Upper types.Bool `tfsdk:"upper"` - Lower types.Bool `tfsdk:"lower"` - Number types.Bool `tfsdk:"number"` - MinNumeric types.Int64 `tfsdk:"min_numeric"` - MinUpper types.Int64 `tfsdk:"min_upper"` - MinLower types.Int64 `tfsdk:"min_lower"` - MinSpecial types.Int64 `tfsdk:"min_special"` - OverrideSpecial types.String `tfsdk:"override_special"` - Result types.String `tfsdk:"result"` -} - -type UUIDModel struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Result types.String `tfsdk:"result"` -} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 6986b38e..8d3d39a4 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -5,34 +5,43 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" -) -type provider struct { -} + "github.com/terraform-providers/terraform-provider-random/internal/resources/id" + "github.com/terraform-providers/terraform-provider-random/internal/resources/integer" + "github.com/terraform-providers/terraform-provider-random/internal/resources/password" + "github.com/terraform-providers/terraform-provider-random/internal/resources/pet" + "github.com/terraform-providers/terraform-provider-random/internal/resources/shuffle" + stringresource "github.com/terraform-providers/terraform-provider-random/internal/resources/string" + "github.com/terraform-providers/terraform-provider-random/internal/resources/uuid" +) -func NewFramework() tfsdk.Provider { +func NewProvider() tfsdk.Provider { return &provider{} } -func (p *provider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { +var _ tfsdk.Provider = (*provider)(nil) + +type provider struct{} + +func (p *provider) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{}, nil } -func (p *provider) Configure(ctx context.Context, req tfsdk.ConfigureProviderRequest, resp *tfsdk.ConfigureProviderResponse) { +func (p *provider) Configure(context.Context, tfsdk.ConfigureProviderRequest, *tfsdk.ConfigureProviderResponse) { } -func (p *provider) GetResources(ctx context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { +func (p *provider) GetResources(context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { return map[string]tfsdk.ResourceType{ - "random_id": resourceIDType{}, - "random_integer": resourceIntegerType{}, - "random_password": resourcePasswordType{}, - "random_pet": resourcePetType{}, - "random_shuffle": resourceShuffleType{}, - "random_string": resourceStringType{}, - "random_uuid": resourceUUIDType{}, + "random_id": id.NewResourceType(), + "random_integer": integer.NewResourceType(), + "random_password": password.NewResourceType(), + "random_pet": pet.NewResourceType(), + "random_shuffle": shuffle.NewResourceType(), + "random_string": stringresource.NewResourceType(), + "random_uuid": uuid.NewResourceType(), }, nil } -func (p *provider) GetDataSources(ctx context.Context) (map[string]tfsdk.DataSourceType, diag.Diagnostics) { +func (p *provider) GetDataSources(context.Context) (map[string]tfsdk.DataSourceType, diag.Diagnostics) { return map[string]tfsdk.DataSourceType{}, nil } diff --git a/internal/provider/resource_id_test.go b/internal/provider/provider_resource_id_test.go similarity index 93% rename from internal/provider/resource_id_test.go rename to internal/provider/provider_resource_id_test.go index a1ca08b4..82106148 100644 --- a/internal/provider/resource_id_test.go +++ b/internal/provider/provider_resource_id_test.go @@ -8,7 +8,6 @@ import ( func TestAccResourceID(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -33,7 +32,6 @@ func TestAccResourceID(t *testing.T) { func TestAccResourceID_importWithPrefix(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { diff --git a/internal/provider/resource_integer_test.go b/internal/provider/provider_resource_integer_test.go similarity index 92% rename from internal/provider/resource_integer_test.go rename to internal/provider/provider_resource_integer_test.go index 0bbaa24c..9878fd05 100644 --- a/internal/provider/resource_integer_test.go +++ b/internal/provider/provider_resource_integer_test.go @@ -10,7 +10,6 @@ import ( func TestAccResourceIntegerBasic(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -36,7 +35,6 @@ func TestAccResourceIntegerBasic(t *testing.T) { func TestAccResourceIntegerUpdate(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -66,7 +64,6 @@ func TestAccResourceIntegerUpdate(t *testing.T) { func TestAccResourceIntegerSeedless_to_seeded(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -95,7 +92,6 @@ func TestAccResourceIntegerSeedless_to_seeded(t *testing.T) { func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -124,7 +120,6 @@ func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) { func TestAccResourceIntegerBig(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { diff --git a/internal/provider/resource_password_test.go b/internal/provider/provider_resource_password_test.go similarity index 77% rename from internal/provider/resource_password_test.go rename to internal/provider/provider_resource_password_test.go index b688a22f..c3f909a5 100644 --- a/internal/provider/resource_password_test.go +++ b/internal/provider/provider_resource_password_test.go @@ -1,23 +1,16 @@ package provider import ( - "context" "fmt" "regexp" "testing" - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - "golang.org/x/crypto/bcrypt" ) func TestAccResourcePasswordBasic(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -55,7 +48,6 @@ func TestAccResourcePasswordBasic(t *testing.T) { func TestAccResourcePasswordOverride(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -480,6 +472,7 @@ func TestAccResourcePassword_StateUpgrade_V1toV2(t *testing.T) { c.configDuringUpgrade = c.configBeforeUpgrade } + // TODO: Why is resource.Test not being used here resource.UnitTest(t, resource.TestCase{ Steps: []resource.TestStep{ { @@ -503,7 +496,6 @@ func TestAccResourcePassword_StateUpgrade_V1toV2(t *testing.T) { func TestAccResourcePasswordMin(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -526,131 +518,3 @@ func TestAccResourcePasswordMin(t *testing.T) { }, }) } - -func TestMigratePasswordStateV0toV2(t *testing.T) { - raw := tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "none"), - "keepers": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), - "length": tftypes.NewValue(tftypes.Number, 16), - "lower": tftypes.NewValue(tftypes.Bool, true), - "min_lower": tftypes.NewValue(tftypes.Number, 0), - "min_numeric": tftypes.NewValue(tftypes.Number, 0), - "min_special": tftypes.NewValue(tftypes.Number, 0), - "min_upper": tftypes.NewValue(tftypes.Number, 0), - "number": tftypes.NewValue(tftypes.Bool, true), - "override_special": tftypes.NewValue(tftypes.String, "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"), - "result": tftypes.NewValue(tftypes.String, "DZy_3*tnonj%Q%Yx"), - "special": tftypes.NewValue(tftypes.Bool, true), - "upper": tftypes.NewValue(tftypes.Bool, true), - }) - - req := tfsdk.UpgradeResourceStateRequest{ - State: &tfsdk.State{ - Raw: raw, - Schema: passwordSchemaV0(), - }, - } - - resp := &tfsdk.UpgradeResourceStateResponse{ - State: tfsdk.State{ - Schema: passwordSchemaV2(), - }, - } - - upgradePasswordStateV0toV2(context.Background(), req, resp) - - expected := PasswordModelV2{ - ID: types.String{Value: "none"}, - Keepers: types.Map{Null: true, ElemType: types.StringType}, - Length: types.Int64{Value: 16}, - Special: types.Bool{Value: true}, - Upper: types.Bool{Value: true}, - Lower: types.Bool{Value: true}, - Numeric: types.Bool{Value: true}, - MinNumeric: types.Int64{Value: 0}, - MinUpper: types.Int64{Value: 0}, - MinLower: types.Int64{Value: 0}, - MinSpecial: types.Int64{Value: 0}, - OverrideSpecial: types.String{Value: "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"}, - Result: types.String{Value: "DZy_3*tnonj%Q%Yx"}, - } - - actual := PasswordModelV2{} - diags := resp.State.Get(context.Background(), &actual) - if diags.HasError() { - t.Errorf("error getting state: %v", diags) - } - - err := bcrypt.CompareHashAndPassword([]byte(actual.BcryptHash.Value), []byte(actual.Result.Value)) - if err != nil { - t.Errorf("unexpected bcrypt comparison error: %s", err) - } - - // Setting actual.BcryptHash to zero value to allow direct comparison of expected and actual. - actual.BcryptHash = types.String{} - - if !cmp.Equal(expected, actual) { - t.Errorf("expected: %+v, got: %+v", expected, actual) - } -} - -func TestMigratePasswordStateV1toV2(t *testing.T) { - raw := tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "none"), - "keepers": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), - "length": tftypes.NewValue(tftypes.Number, 16), - "lower": tftypes.NewValue(tftypes.Bool, true), - "min_lower": tftypes.NewValue(tftypes.Number, 0), - "min_numeric": tftypes.NewValue(tftypes.Number, 0), - "min_special": tftypes.NewValue(tftypes.Number, 0), - "min_upper": tftypes.NewValue(tftypes.Number, 0), - "number": tftypes.NewValue(tftypes.Bool, true), - "override_special": tftypes.NewValue(tftypes.String, "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"), - "result": tftypes.NewValue(tftypes.String, "DZy_3*tnonj%Q%Yx"), - "special": tftypes.NewValue(tftypes.Bool, true), - "upper": tftypes.NewValue(tftypes.Bool, true), - "bcrypt_hash": tftypes.NewValue(tftypes.String, "bcrypt_hash"), - }) - - req := tfsdk.UpgradeResourceStateRequest{ - State: &tfsdk.State{ - Raw: raw, - Schema: passwordSchemaV1(), - }, - } - - resp := &tfsdk.UpgradeResourceStateResponse{ - State: tfsdk.State{ - Schema: passwordSchemaV2(), - }, - } - - upgradePasswordStateV1toV2(context.Background(), req, resp) - - expected := PasswordModelV2{ - ID: types.String{Value: "none"}, - Keepers: types.Map{Null: true, ElemType: types.StringType}, - Length: types.Int64{Value: 16}, - Special: types.Bool{Value: true}, - Upper: types.Bool{Value: true}, - Lower: types.Bool{Value: true}, - Numeric: types.Bool{Value: true}, - MinNumeric: types.Int64{Value: 0}, - MinUpper: types.Int64{Value: 0}, - MinLower: types.Int64{Value: 0}, - MinSpecial: types.Int64{Value: 0}, - OverrideSpecial: types.String{Value: "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"}, - BcryptHash: types.String{Value: "bcrypt_hash"}, - Result: types.String{Value: "DZy_3*tnonj%Q%Yx"}, - } - - actual := PasswordModelV2{} - diags := resp.State.Get(context.Background(), &actual) - if diags.HasError() { - t.Errorf("error getting state: %v", diags) - } - - if !cmp.Equal(expected, actual) { - t.Errorf("expected: %+v, got: %+v", expected, actual) - } -} diff --git a/internal/provider/resource_pet_test.go b/internal/provider/provider_resource_pet_test.go similarity index 90% rename from internal/provider/resource_pet_test.go rename to internal/provider/provider_resource_pet_test.go index 2a78cffd..a2ab2867 100644 --- a/internal/provider/resource_pet_test.go +++ b/internal/provider/provider_resource_pet_test.go @@ -11,7 +11,6 @@ import ( func TestAccResourcePet_basic(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -27,7 +26,6 @@ func TestAccResourcePet_basic(t *testing.T) { func TestAccResourcePet_length(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -44,7 +42,6 @@ func TestAccResourcePet_length(t *testing.T) { func TestAccResourcePet_prefix(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -62,7 +59,6 @@ func TestAccResourcePet_prefix(t *testing.T) { func TestAccResourcePet_separator(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { diff --git a/internal/provider/resource_shuffle_test.go b/internal/provider/provider_resource_shuffle_test.go similarity index 93% rename from internal/provider/resource_shuffle_test.go rename to internal/provider/provider_resource_shuffle_test.go index 14899cf5..79ddfa34 100644 --- a/internal/provider/resource_shuffle_test.go +++ b/internal/provider/provider_resource_shuffle_test.go @@ -20,7 +20,6 @@ import ( // guaranteed consistent across Terraform releases. func TestAccResourceShuffleDefault(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -38,7 +37,6 @@ func TestAccResourceShuffleDefault(t *testing.T) { func TestAccResourceShuffleShorter(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -56,7 +54,6 @@ func TestAccResourceShuffleShorter(t *testing.T) { func TestAccResourceShuffleLonger(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -74,7 +71,6 @@ func TestAccResourceShuffleLonger(t *testing.T) { func TestAccResourceShuffleEmpty(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -92,7 +88,6 @@ func TestAccResourceShuffleEmpty(t *testing.T) { func TestAccResourceShuffleOne(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { diff --git a/internal/provider/resource_string_test.go b/internal/provider/provider_resource_string_test.go similarity index 97% rename from internal/provider/resource_string_test.go rename to internal/provider/provider_resource_string_test.go index ac77350c..8bb5c0be 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/provider_resource_string_test.go @@ -10,7 +10,6 @@ import ( func TestAccResourceString(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -32,7 +31,6 @@ func TestAccResourceString(t *testing.T) { func TestAccResourceStringOverride(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -54,7 +52,6 @@ func TestAccResourceStringOverride(t *testing.T) { func TestAccResourceStringMin(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -284,7 +281,6 @@ func TestAccResourceString_StateUpgrade_V1toV2(t *testing.T) { func TestAccResourceStringErrors(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { diff --git a/internal/provider/resource_uuid_test.go b/internal/provider/provider_resource_uuid_test.go similarity index 92% rename from internal/provider/resource_uuid_test.go rename to internal/provider/provider_resource_uuid_test.go index 8ddfa1cd..9643d7fd 100644 --- a/internal/provider/resource_uuid_test.go +++ b/internal/provider/provider_resource_uuid_test.go @@ -9,7 +9,6 @@ import ( func TestAccResourceUUID(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), Steps: []resource.TestStep{ { diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index bafa1be2..46d1e872 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -1,20 +1,15 @@ package provider import ( - "testing" - "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov6" ) -func testAccPreCheck(t *testing.T) { -} - //nolint:unparam func testAccProtoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { return map[string]func() (tfprotov6.ProviderServer, error){ "random": func() (tfprotov6.ProviderServer, error) { - return providerserver.NewProtocol6(NewFramework())(), nil + return providerserver.NewProtocol6(NewProvider())(), nil }, } } diff --git a/internal/provider/string.go b/internal/random/random.go similarity index 61% rename from internal/provider/string.go rename to internal/random/random.go index 93e911f7..3bf98bc3 100644 --- a/internal/provider/string.go +++ b/internal/random/random.go @@ -1,4 +1,4 @@ -package provider +package random import ( "crypto/rand" @@ -6,52 +6,52 @@ import ( "sort" ) -type randomStringParams struct { - length int64 - upper bool - minUpper int64 - lower bool - minLower int64 - numeric bool - minNumeric int64 - special bool - minSpecial int64 - overrideSpecial string +type RandomStringParams struct { + Length int64 + Upper bool + MinUpper int64 + Lower bool + MinLower int64 + Numeric bool + MinNumeric int64 + Special bool + MinSpecial int64 + OverrideSpecial string } -func createRandomString(input randomStringParams) ([]byte, error) { +func CreateRandomString(input RandomStringParams) ([]byte, error) { const numChars = "0123456789" const lowerChars = "abcdefghijklmnopqrstuvwxyz" const upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" var specialChars = "!@#$%&*()-_=+[]{}<>:?" var result []byte - if input.overrideSpecial != "" { - specialChars = input.overrideSpecial + if input.OverrideSpecial != "" { + specialChars = input.OverrideSpecial } var chars = "" - if input.upper { + if input.Upper { chars += upperChars } - if input.lower { + if input.Lower { chars += lowerChars } - if input.numeric { + if input.Numeric { chars += numChars } - if input.special { + if input.Special { chars += specialChars } minMapping := map[string]int64{ - numChars: input.minNumeric, - lowerChars: input.minLower, - upperChars: input.minUpper, - specialChars: input.minSpecial, + numChars: input.MinNumeric, + lowerChars: input.MinLower, + upperChars: input.MinUpper, + specialChars: input.MinSpecial, } - result = make([]byte, 0, input.length) + result = make([]byte, 0, input.Length) for k, v := range minMapping { s, err := generateRandomBytes(&k, v) @@ -61,7 +61,7 @@ func createRandomString(input randomStringParams) ([]byte, error) { result = append(result, s...) } - s, err := generateRandomBytes(&chars, input.length-int64(len(result))) + s, err := generateRandomBytes(&chars, input.Length-int64(len(result))) if err != nil { return nil, err } diff --git a/internal/provider/seed.go b/internal/random/seed.go similarity index 96% rename from internal/provider/seed.go rename to internal/random/seed.go index 4d3c5265..b0bcf8bb 100644 --- a/internal/provider/seed.go +++ b/internal/random/seed.go @@ -1,4 +1,4 @@ -package provider +package random import ( "hash/crc64" diff --git a/internal/provider/resource_id.go b/internal/resources/id/resource.go similarity index 73% rename from internal/provider/resource_id.go rename to internal/resources/id/resource.go index 87059669..03d49929 100644 --- a/internal/provider/resource_id.go +++ b/internal/resources/id/resource.go @@ -1,4 +1,4 @@ -package provider +package id import ( "context" @@ -12,11 +12,19 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" ) -type resourceIDType struct{} +func NewResourceType() *resourceType { + return &resourceType{} +} + +var _ tfsdk.ResourceType = (*resourceType)(nil) + +type resourceType struct{} -func (r resourceIDType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { +func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Description: ` The resource ` + "`random_id`" + ` generates random numbers that are intended to be @@ -39,22 +47,28 @@ exist concurrently. Type: types.MapType{ ElemType: types.StringType, }, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, }, "byte_length": { Description: "The number of random bytes to produce. The minimum value is 1, which produces " + "eight bits of randomness.", - Type: types.Int64Type, - Required: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Type: types.Int64Type, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, }, "prefix": { Description: "Arbitrary string to prefix the output value with. This string is supplied as-is, " + "meaning it is not guaranteed to be URL-safe or base64 encoded.", - Type: types.StringType, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Type: types.StringType, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, }, "b64_url": { Description: "The generated id presented in base64, using the URL-friendly character set: " + @@ -87,18 +101,19 @@ exist concurrently. }, nil } -func (r resourceIDType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceID{ - p: *(p.(*provider)), - }, nil +func (r *resourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &resource{}, nil } -type resourceID struct { - p provider -} +var ( + _ tfsdk.Resource = (*resource)(nil) + _ tfsdk.ResourceWithImportState = (*resource)(nil) +) -func (r resourceID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan IDModel +type resource struct{} + +func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan modelV0 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -120,7 +135,7 @@ func (r resourceID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, return } if err != nil { - resp.Diagnostics.Append(randomReadError(err.Error())...) + resp.Diagnostics.Append(diagnostics.RandomReadError(err.Error())...) return } @@ -133,7 +148,7 @@ func (r resourceID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, bigInt.SetBytes(bytes) dec := bigInt.String() - i := IDModel{ + i := modelV0{ ID: types.String{Value: id}, Keepers: plan.Keepers, ByteLength: types.Int64{Value: plan.ByteLength.Value}, @@ -152,21 +167,20 @@ func (r resourceID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r resourceID) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +func (r *resource) Read(context.Context, tfsdk.ReadResourceRequest, *tfsdk.ReadResourceResponse) { } // Update is intentionally left blank as all required and optional attributes force replacement of the resource // through the RequiresReplace AttributePlanModifier. -func (r resourceID) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - // Intentionally left blank. +func (r *resource) Update(context.Context, tfsdk.UpdateResourceRequest, *tfsdk.UpdateResourceResponse) { } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the // [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r resourceID) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +func (r *resource) Delete(context.Context, tfsdk.DeleteResourceRequest, *tfsdk.DeleteResourceResponse) { } -func (r resourceID) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { +func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { id := req.ID var prefix string @@ -181,7 +195,7 @@ func (r resourceID) ImportState(ctx context.Context, req tfsdk.ImportResourceSta resp.Diagnostics.AddError( "Import Random ID Error", "While attempting to import a random id there was a decoding error.\n\n+"+ - retryMsg+ + diagnostics.RetryMsg+ fmt.Sprintf("Original Error: %s", err), ) return @@ -194,7 +208,7 @@ func (r resourceID) ImportState(ctx context.Context, req tfsdk.ImportResourceSta bigInt.SetBytes(bytes) dec := bigInt.String() - var state IDModel + var state modelV0 state.ID.Value = id state.ByteLength.Value = int64(len(bytes)) @@ -216,3 +230,14 @@ func (r resourceID) ImportState(ctx context.Context, req tfsdk.ImportResourceSta return } } + +type modelV0 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + ByteLength types.Int64 `tfsdk:"byte_length"` + Prefix types.String `tfsdk:"prefix"` + B64URL types.String `tfsdk:"b64_url"` + B64Std types.String `tfsdk:"b64_std"` + Hex types.String `tfsdk:"hex"` + Dec types.String `tfsdk:"dec"` +} diff --git a/internal/provider/resource_integer.go b/internal/resources/integer/resource.go similarity index 75% rename from internal/provider/resource_integer.go rename to internal/resources/integer/resource.go index bc6b511b..a81b7f8b 100644 --- a/internal/provider/resource_integer.go +++ b/internal/resources/integer/resource.go @@ -1,4 +1,4 @@ -package provider +package integer import ( "context" @@ -9,11 +9,19 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/terraform-providers/terraform-provider-random/internal/random" ) -type resourceIntegerType struct{} +func NewResourceType() *resourceType { + return &resourceType{} +} + +var _ tfsdk.ResourceType = (*resourceType)(nil) -func (r resourceIntegerType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { +type resourceType struct{} + +func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Description: "The resource `random_integer` generates random values from a given range, described " + "by the `min` and `max` attributes of a given resource.\n" + @@ -63,18 +71,19 @@ func (r resourceIntegerType) GetSchema(context.Context) (tfsdk.Schema, diag.Diag }, nil } -func (r resourceIntegerType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceInteger{ - p: *(p.(*provider)), - }, nil +func (r *resourceType) NewResource(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &resource{}, nil } -type resourceInteger struct { - p provider -} +var ( + _ tfsdk.Resource = (*resource)(nil) + _ tfsdk.ResourceWithImportState = (*resource)(nil) +) + +type resource struct{} -func (r resourceInteger) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan IntegerModel +func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan modelV0 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -94,10 +103,10 @@ func (r resourceInteger) Create(ctx context.Context, req tfsdk.CreateResourceReq return } - rand := NewRand(seed) + rand := random.NewRand(seed) number := rand.Intn((max+1)-min) + min - u := &IntegerModel{ + u := &modelV0{ ID: types.String{Value: strconv.Itoa(number)}, Keepers: plan.Keepers, Min: types.Int64{Value: int64(min)}, @@ -119,20 +128,20 @@ func (r resourceInteger) Create(ctx context.Context, req tfsdk.CreateResourceReq } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r resourceInteger) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +func (r *resource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { } // Update is intentionally left blank as all required and optional attributes force replacement of the resource // through the RequiresReplace AttributePlanModifier. -func (r resourceInteger) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { +func (r *resource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the // [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r resourceInteger) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +func (r *resource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { } -func (r resourceInteger) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { +func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { parts := strings.Split(req.ID, ",") if len(parts) != 3 && len(parts) != 4 { resp.Diagnostics.AddError( @@ -172,7 +181,7 @@ func (r resourceInteger) ImportState(ctx context.Context, req tfsdk.ImportResour return } - var state IntegerModel + var state modelV0 state.ID.Value = parts[0] state.Keepers.ElemType = types.StringType @@ -190,3 +199,12 @@ func (r resourceInteger) ImportState(ctx context.Context, req tfsdk.ImportResour return } } + +type modelV0 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Min types.Int64 `tfsdk:"min"` + Max types.Int64 `tfsdk:"max"` + Seed types.String `tfsdk:"seed"` + Result types.Int64 `tfsdk:"result"` +} diff --git a/internal/resources/password/resource.go b/internal/resources/password/resource.go new file mode 100644 index 00000000..f874bbf8 --- /dev/null +++ b/internal/resources/password/resource.go @@ -0,0 +1,277 @@ +package password + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "golang.org/x/crypto/bcrypt" + + "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" + "github.com/terraform-providers/terraform-provider-random/internal/random" +) + +var _ tfsdk.ResourceType = (*resourceType)(nil) + +func NewResourceType() *resourceType { + return &resourceType{} +} + +type resourceType struct{} + +func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { + return schemaV2(), nil +} + +func (r *resourceType) NewResource(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &resource{}, nil +} + +var ( + _ tfsdk.Resource = (*resource)(nil) + _ tfsdk.ResourceWithImportState = (*resource)(nil) + _ tfsdk.ResourceWithUpgradeState = (*resource)(nil) +) + +type resource struct{} + +func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan modelV2 + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + params := random.RandomStringParams{ + Length: plan.Length.Value, + Upper: plan.Upper.Value, + MinUpper: plan.MinUpper.Value, + Lower: plan.Lower.Value, + MinLower: plan.MinLower.Value, + Numeric: plan.Numeric.Value, + MinNumeric: plan.MinNumeric.Value, + Special: plan.Special.Value, + MinSpecial: plan.MinSpecial.Value, + OverrideSpecial: plan.OverrideSpecial.Value, + } + + result, err := random.CreateRandomString(params) + if err != nil { + resp.Diagnostics.Append(diagnostics.RandomReadError(err.Error())...) + return + } + + state := modelV2{ + ID: types.String{Value: "none"}, + Keepers: plan.Keepers, + Length: types.Int64{Value: plan.Length.Value}, + Special: types.Bool{Value: plan.Special.Value}, + Upper: types.Bool{Value: plan.Upper.Value}, + Lower: types.Bool{Value: plan.Lower.Value}, + Numeric: types.Bool{Value: plan.Numeric.Value}, + MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, + MinUpper: types.Int64{Value: plan.MinUpper.Value}, + MinLower: types.Int64{Value: plan.MinLower.Value}, + MinSpecial: types.Int64{Value: plan.MinSpecial.Value}, + OverrideSpecial: types.String{Value: plan.OverrideSpecial.Value}, + Result: types.String{Value: string(result)}, + } + + hash, err := generateHash(plan.Result.Value) + if err != nil { + resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...) + } + + state.BcryptHash = types.String{Value: hash} + + diags = resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. +func (r *resource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +} + +// Update is intentionally left blank as all required and optional attributes force replacement of the resource +// through the RequiresReplace AttributePlanModifier. +func (r *resource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { +} + +// Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the +// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). +func (r *resource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +} + +func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + id := req.ID + + state := modelV2{ + ID: types.String{Value: "none"}, + Result: types.String{Value: id}, + Length: types.Int64{Value: int64(len(id))}, + Special: types.Bool{Value: true}, + Upper: types.Bool{Value: true}, + Lower: types.Bool{Value: true}, + Numeric: types.Bool{Value: true}, + MinSpecial: types.Int64{Value: 0}, + MinUpper: types.Int64{Value: 0}, + MinLower: types.Int64{Value: 0}, + MinNumeric: types.Int64{Value: 0}, + } + + state.Keepers.ElemType = types.StringType + + hash, err := generateHash(id) + if err != nil { + resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...) + } + + state.BcryptHash = types.String{Value: hash} + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *resource) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { + schemaV0 := schemaV0() + schemaV1 := schemaV1() + + return map[int64]tfsdk.ResourceStateUpgrader{ + 0: { + PriorSchema: &schemaV0, + StateUpgrader: upgradePasswordStateV0toV2, + }, + 1: { + PriorSchema: &schemaV1, + StateUpgrader: upgradePasswordStateV1toV2, + }, + } +} + +func upgradePasswordStateV0toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { + type modelV0 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Number types.Bool `tfsdk:"number"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Result types.String `tfsdk:"result"` + } + + var passwordDataV0 modelV0 + + resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV0)...) + if resp.Diagnostics.HasError() { + return + } + + passwordDataV2 := modelV2{ + Keepers: passwordDataV0.Keepers, + Length: passwordDataV0.Length, + Special: passwordDataV0.Special, + Upper: passwordDataV0.Upper, + Lower: passwordDataV0.Lower, + Numeric: passwordDataV0.Number, + MinNumeric: passwordDataV0.MinNumeric, + MinLower: passwordDataV0.MinLower, + MinSpecial: passwordDataV0.MinSpecial, + OverrideSpecial: passwordDataV0.OverrideSpecial, + Result: passwordDataV0.Result, + ID: passwordDataV0.ID, + } + + hash, err := generateHash(passwordDataV2.Result.Value) + if err != nil { + resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...) + return + } + + passwordDataV2.BcryptHash.Value = hash + + diags := resp.State.Set(ctx, passwordDataV2) + resp.Diagnostics.Append(diags...) +} + +func upgradePasswordStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { + type modelV1 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Number types.Bool `tfsdk:"number"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Result types.String `tfsdk:"result"` + BcryptHash types.String `tfsdk:"bcrypt_hash"` + } + + var passwordDataV1 modelV1 + + resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV1)...) + if resp.Diagnostics.HasError() { + return + } + + passwordDataV2 := modelV2{ + Keepers: passwordDataV1.Keepers, + Length: passwordDataV1.Length, + Special: passwordDataV1.Special, + Upper: passwordDataV1.Upper, + Lower: passwordDataV1.Lower, + Numeric: passwordDataV1.Number, + MinNumeric: passwordDataV1.MinNumeric, + MinLower: passwordDataV1.MinLower, + MinSpecial: passwordDataV1.MinSpecial, + OverrideSpecial: passwordDataV1.OverrideSpecial, + BcryptHash: passwordDataV1.BcryptHash, + Result: passwordDataV1.Result, + ID: passwordDataV1.ID, + } + + diags := resp.State.Set(ctx, passwordDataV2) + resp.Diagnostics.Append(diags...) +} + +func generateHash(toHash string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(toHash), bcrypt.DefaultCost) + + return string(hash), err +} + +type modelV2 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Numeric types.Bool `tfsdk:"numeric"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Result types.String `tfsdk:"result"` + BcryptHash types.String `tfsdk:"bcrypt_hash"` +} diff --git a/internal/resources/password/resource_test.go b/internal/resources/password/resource_test.go new file mode 100644 index 00000000..e8637b41 --- /dev/null +++ b/internal/resources/password/resource_test.go @@ -0,0 +1,140 @@ +package password + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "golang.org/x/crypto/bcrypt" +) + +func TestUpgradePasswordStateV0toV2(t *testing.T) { + raw := tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "none"), + "keepers": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), + "length": tftypes.NewValue(tftypes.Number, 16), + "lower": tftypes.NewValue(tftypes.Bool, true), + "min_lower": tftypes.NewValue(tftypes.Number, 0), + "min_numeric": tftypes.NewValue(tftypes.Number, 0), + "min_special": tftypes.NewValue(tftypes.Number, 0), + "min_upper": tftypes.NewValue(tftypes.Number, 0), + "number": tftypes.NewValue(tftypes.Bool, true), + "override_special": tftypes.NewValue(tftypes.String, "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"), + "result": tftypes.NewValue(tftypes.String, "DZy_3*tnonj%Q%Yx"), + "special": tftypes.NewValue(tftypes.Bool, true), + "upper": tftypes.NewValue(tftypes.Bool, true), + }) + + req := tfsdk.UpgradeResourceStateRequest{ + State: &tfsdk.State{ + Raw: raw, + Schema: schemaV0(), + }, + } + + resp := &tfsdk.UpgradeResourceStateResponse{ + State: tfsdk.State{ + Schema: schemaV2(), + }, + } + + upgradePasswordStateV0toV2(context.Background(), req, resp) + + expected := modelV2{ + ID: types.String{Value: "none"}, + Keepers: types.Map{Null: true, ElemType: types.StringType}, + Length: types.Int64{Value: 16}, + Special: types.Bool{Value: true}, + Upper: types.Bool{Value: true}, + Lower: types.Bool{Value: true}, + Numeric: types.Bool{Value: true}, + MinNumeric: types.Int64{Value: 0}, + MinUpper: types.Int64{Value: 0}, + MinLower: types.Int64{Value: 0}, + MinSpecial: types.Int64{Value: 0}, + OverrideSpecial: types.String{Value: "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"}, + Result: types.String{Value: "DZy_3*tnonj%Q%Yx"}, + } + + actual := modelV2{} + diags := resp.State.Get(context.Background(), &actual) + if diags.HasError() { + t.Errorf("error getting state: %v", diags) + } + + err := bcrypt.CompareHashAndPassword([]byte(actual.BcryptHash.Value), []byte(actual.Result.Value)) + if err != nil { + t.Errorf("unexpected bcrypt comparison error: %s", err) + } + + // Setting actual.BcryptHash to zero value to allow direct comparison of expected and actual. + actual.BcryptHash = types.String{} + + if !cmp.Equal(expected, actual) { + t.Errorf("expected: %+v, got: %+v", expected, actual) + } +} + +func TestUpgradePasswordStateV1toV2(t *testing.T) { + raw := tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "none"), + "keepers": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), + "length": tftypes.NewValue(tftypes.Number, 16), + "lower": tftypes.NewValue(tftypes.Bool, true), + "min_lower": tftypes.NewValue(tftypes.Number, 0), + "min_numeric": tftypes.NewValue(tftypes.Number, 0), + "min_special": tftypes.NewValue(tftypes.Number, 0), + "min_upper": tftypes.NewValue(tftypes.Number, 0), + "number": tftypes.NewValue(tftypes.Bool, true), + "override_special": tftypes.NewValue(tftypes.String, "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"), + "result": tftypes.NewValue(tftypes.String, "DZy_3*tnonj%Q%Yx"), + "special": tftypes.NewValue(tftypes.Bool, true), + "upper": tftypes.NewValue(tftypes.Bool, true), + "bcrypt_hash": tftypes.NewValue(tftypes.String, "bcrypt_hash"), + }) + + req := tfsdk.UpgradeResourceStateRequest{ + State: &tfsdk.State{ + Raw: raw, + Schema: schemaV1(), + }, + } + + resp := &tfsdk.UpgradeResourceStateResponse{ + State: tfsdk.State{ + Schema: schemaV2(), + }, + } + + upgradePasswordStateV1toV2(context.Background(), req, resp) + + expected := modelV2{ + ID: types.String{Value: "none"}, + Keepers: types.Map{Null: true, ElemType: types.StringType}, + Length: types.Int64{Value: 16}, + Special: types.Bool{Value: true}, + Upper: types.Bool{Value: true}, + Lower: types.Bool{Value: true}, + Numeric: types.Bool{Value: true}, + MinNumeric: types.Int64{Value: 0}, + MinUpper: types.Int64{Value: 0}, + MinLower: types.Int64{Value: 0}, + MinSpecial: types.Int64{Value: 0}, + OverrideSpecial: types.String{Value: "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"}, + BcryptHash: types.String{Value: "bcrypt_hash"}, + Result: types.String{Value: "DZy_3*tnonj%Q%Yx"}, + } + + actual := modelV2{} + diags := resp.State.Get(context.Background(), &actual) + if diags.HasError() { + t.Errorf("error getting state: %v", diags) + } + + if !cmp.Equal(expected, actual) { + t.Errorf("expected: %+v, got: %+v", expected, actual) + } +} diff --git a/internal/provider/resource_password.go b/internal/resources/password/schema.go similarity index 69% rename from internal/provider/resource_password.go rename to internal/resources/password/schema.go index 12a3f626..aeaef2cd 100644 --- a/internal/provider/resource_password.go +++ b/internal/resources/password/schema.go @@ -1,73 +1,16 @@ -package provider +package password import ( - "context" - "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" - "golang.org/x/crypto/bcrypt" "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers" + "github.com/terraform-providers/terraform-provider-random/internal/validators" ) -type resourcePasswordType struct{} - -func (r resourcePasswordType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { - return passwordSchemaV2(), nil -} - -func (r resourcePasswordType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourcePassword{ - p: *(p.(*provider)), - }, nil -} - -type resourcePassword struct { - p provider -} - -func (r resourcePassword) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - createPassword(ctx, req, resp) -} - -// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r resourcePassword) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { -} - -// Update is intentionally left blank as all required and optional attributes force replacement of the resource -// through the RequiresReplace AttributePlanModifier. -func (r resourcePassword) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { -} - -// Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the -// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r resourcePassword) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { -} - -func (r resourcePassword) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - importPassword(ctx, req, resp) -} - -func (r resourcePassword) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { - schemaV0 := passwordSchemaV0() - schemaV1 := passwordSchemaV1() - - return map[int64]tfsdk.ResourceStateUpgrader{ - 0: { - PriorSchema: &schemaV0, - StateUpgrader: upgradePasswordStateV0toV2, - }, - 1: { - PriorSchema: &schemaV1, - StateUpgrader: upgradePasswordStateV1toV2, - }, - } -} - -func passwordSchemaV2() tfsdk.Schema { +func schemaV2() tfsdk.Schema { return tfsdk.Schema{ Version: 2, Description: "Identical to [random_string](string.html) with the exception that the result is " + @@ -98,7 +41,7 @@ func passwordSchemaV2() tfsdk.Schema { }, Validators: []tfsdk.AttributeValidator{ int64validator.AtLeast(1), - NewIntIsAtLeastSumOfValidator( + validators.NewIntIsAtLeastSumOfValidator( tftypes.NewAttributePath().WithAttributeName("min_upper"), tftypes.NewAttributePath().WithAttributeName("min_lower"), tftypes.NewAttributePath().WithAttributeName("min_numeric"), @@ -230,7 +173,7 @@ func passwordSchemaV2() tfsdk.Schema { } } -func passwordSchemaV1() tfsdk.Schema { +func schemaV1() tfsdk.Schema { return tfsdk.Schema{ Version: 1, Description: "Identical to [random_string](string.html) with the exception that the result is " + @@ -259,7 +202,7 @@ func passwordSchemaV1() tfsdk.Schema { PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, Validators: []tfsdk.AttributeValidator{ int64validator.AtLeast(1), - NewIntIsAtLeastSumOfValidator( + validators.NewIntIsAtLeastSumOfValidator( tftypes.NewAttributePath().WithAttributeName("min_upper"), tftypes.NewAttributePath().WithAttributeName("min_lower"), tftypes.NewAttributePath().WithAttributeName("min_numeric"), @@ -391,7 +334,7 @@ func passwordSchemaV1() tfsdk.Schema { } } -func passwordSchemaV0() tfsdk.Schema { +func schemaV0() tfsdk.Schema { return tfsdk.Schema{ Description: "Identical to [random_string](string.html) with the exception that the result is " + "treated as sensitive and, thus, _not_ displayed in console output. Read more about sensitive " + @@ -417,7 +360,7 @@ func passwordSchemaV0() tfsdk.Schema { PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, Validators: []tfsdk.AttributeValidator{ int64validator.AtLeast(1), - NewIntIsAtLeastSumOfValidator( + validators.NewIntIsAtLeastSumOfValidator( tftypes.NewAttributePath().WithAttributeName("min_upper"), tftypes.NewAttributePath().WithAttributeName("min_lower"), tftypes.NewAttributePath().WithAttributeName("min_numeric"), @@ -541,163 +484,3 @@ func passwordSchemaV0() tfsdk.Schema { }, } } - -func createPassword(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan PasswordModelV2 - - diags := req.Plan.Get(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - params := randomStringParams{ - length: plan.Length.Value, - upper: plan.Upper.Value, - minUpper: plan.MinUpper.Value, - lower: plan.Lower.Value, - minLower: plan.MinLower.Value, - numeric: plan.Numeric.Value, - minNumeric: plan.MinNumeric.Value, - special: plan.Special.Value, - minSpecial: plan.MinSpecial.Value, - overrideSpecial: plan.OverrideSpecial.Value, - } - - result, err := createRandomString(params) - if err != nil { - resp.Diagnostics.Append(randomReadError(err.Error())...) - return - } - - state := PasswordModelV2{ - ID: types.String{Value: "none"}, - Keepers: plan.Keepers, - Length: types.Int64{Value: plan.Length.Value}, - Special: types.Bool{Value: plan.Special.Value}, - Upper: types.Bool{Value: plan.Upper.Value}, - Lower: types.Bool{Value: plan.Lower.Value}, - Numeric: types.Bool{Value: plan.Numeric.Value}, - MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, - MinUpper: types.Int64{Value: plan.MinUpper.Value}, - MinLower: types.Int64{Value: plan.MinLower.Value}, - MinSpecial: types.Int64{Value: plan.MinSpecial.Value}, - OverrideSpecial: types.String{Value: plan.OverrideSpecial.Value}, - Result: types.String{Value: string(result)}, - } - - hash, err := generateHash(plan.Result.Value) - if err != nil { - resp.Diagnostics.Append(hashGenerationError(err.Error())...) - } - - state.BcryptHash = types.String{Value: hash} - - diags = resp.State.Set(ctx, state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -func importPassword(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - id := req.ID - - state := PasswordModelV2{ - ID: types.String{Value: "none"}, - Result: types.String{Value: id}, - Length: types.Int64{Value: int64(len(id))}, - Special: types.Bool{Value: true}, - Upper: types.Bool{Value: true}, - Lower: types.Bool{Value: true}, - Numeric: types.Bool{Value: true}, - MinSpecial: types.Int64{Value: 0}, - MinUpper: types.Int64{Value: 0}, - MinLower: types.Int64{Value: 0}, - MinNumeric: types.Int64{Value: 0}, - } - - state.Keepers.ElemType = types.StringType - - hash, err := generateHash(id) - if err != nil { - resp.Diagnostics.Append(hashGenerationError(err.Error())...) - } - - state.BcryptHash = types.String{Value: hash} - - diags := resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -func upgradePasswordStateV0toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { - var passwordDataV0 PasswordModelV0 - - resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV0)...) - if resp.Diagnostics.HasError() { - return - } - - passwordDataV2 := PasswordModelV2{ - Keepers: passwordDataV0.Keepers, - Length: passwordDataV0.Length, - Special: passwordDataV0.Special, - Upper: passwordDataV0.Upper, - Lower: passwordDataV0.Lower, - Numeric: passwordDataV0.Number, - MinNumeric: passwordDataV0.MinNumeric, - MinLower: passwordDataV0.MinLower, - MinSpecial: passwordDataV0.MinSpecial, - OverrideSpecial: passwordDataV0.OverrideSpecial, - Result: passwordDataV0.Result, - ID: passwordDataV0.ID, - } - - hash, err := generateHash(passwordDataV2.Result.Value) - if err != nil { - resp.Diagnostics.Append(hashGenerationError(err.Error())...) - return - } - - passwordDataV2.BcryptHash.Value = hash - - diags := resp.State.Set(ctx, passwordDataV2) - resp.Diagnostics.Append(diags...) -} - -func upgradePasswordStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { - var passwordDataV1 PasswordModelV1 - - resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV1)...) - if resp.Diagnostics.HasError() { - return - } - - passwordDataV2 := PasswordModelV2{ - Keepers: passwordDataV1.Keepers, - Length: passwordDataV1.Length, - Special: passwordDataV1.Special, - Upper: passwordDataV1.Upper, - Lower: passwordDataV1.Lower, - Numeric: passwordDataV1.Number, - MinNumeric: passwordDataV1.MinNumeric, - MinLower: passwordDataV1.MinLower, - MinSpecial: passwordDataV1.MinSpecial, - OverrideSpecial: passwordDataV1.OverrideSpecial, - BcryptHash: passwordDataV1.BcryptHash, - Result: passwordDataV1.Result, - ID: passwordDataV1.ID, - } - - diags := resp.State.Set(ctx, passwordDataV2) - resp.Diagnostics.Append(diags...) -} - -func generateHash(toHash string) (string, error) { - hash, err := bcrypt.GenerateFromPassword([]byte(toHash), bcrypt.DefaultCost) - - return string(hash), err -} diff --git a/internal/provider/resource_pet.go b/internal/resources/pet/resource.go similarity index 72% rename from internal/provider/resource_pet.go rename to internal/resources/pet/resource.go index 113ecdb0..69740f0e 100644 --- a/internal/provider/resource_pet.go +++ b/internal/resources/pet/resource.go @@ -1,4 +1,4 @@ -package provider +package pet import ( "context" @@ -13,14 +13,15 @@ import ( "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers" ) -type resourcePetType struct{} +func NewResourceType() *resourceType { + return &resourceType{} +} -func (r resourcePetType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { - // This is necessary to ensure each call to petname is properly randomised: - // the library uses `rand.Intn()` and does NOT seed `rand.Seed()` by default, - // so this call takes care of that. - petname.NonDeterministicMode() +var _ tfsdk.ResourceType = (*resourceType)(nil) + +type resourceType struct{} +func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Description: "The resource `random_pet` generates random pet names that are intended to be used as " + "unique identifiers for other resources.\n" + @@ -35,8 +36,10 @@ func (r resourcePetType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnost Type: types.MapType{ ElemType: types.StringType, }, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, }, "length": { Description: "The length (in words) of the pet name. Defaults to 2", @@ -45,7 +48,7 @@ func (r resourcePetType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnost Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ planmodifiers.DefaultValue(types.Int64{Value: 2}), - tfsdk.RequiresReplace(), + planmodifiers.RequiresReplace(), }, }, "prefix": { @@ -61,7 +64,7 @@ func (r resourcePetType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnost Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ planmodifiers.DefaultValue(types.String{Value: "-"}), - tfsdk.RequiresReplace(), + planmodifiers.RequiresReplace(), }, }, "id": { @@ -73,18 +76,21 @@ func (r resourcePetType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnost }, nil } -func (r resourcePetType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourcePet{ - p: *(p.(*provider)), - }, nil +func (r *resourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &resource{}, nil } -type resourcePet struct { - p provider -} +var _ tfsdk.Resource = (*resource)(nil) -func (r resourcePet) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan PetNameModel +type resource struct{} + +func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + // This is necessary to ensure each call to petname is properly randomised: + // the library uses `rand.Intn()` and does NOT seed `rand.Seed()` by default, + // so this call takes care of that. + petname.NonDeterministicMode() + + var plan modelV0 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -98,7 +104,7 @@ func (r resourcePet) Create(ctx context.Context, req tfsdk.CreateResourceRequest pet := strings.ToLower(petname.Generate(int(length), separator)) - pn := PetNameModel{ + pn := modelV0{ Keepers: plan.Keepers, Length: types.Int64{Value: length}, Separator: types.String{Value: separator}, @@ -121,15 +127,23 @@ func (r resourcePet) Create(ctx context.Context, req tfsdk.CreateResourceRequest } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r resourcePet) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +func (r *resource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { } // Update is intentionally left blank as all required and optional attributes force replacement of the resource // through the RequiresReplace AttributePlanModifier. -func (r resourcePet) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { +func (r *resource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the // [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r resourcePet) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +func (r *resource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +} + +type modelV0 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Prefix types.String `tfsdk:"prefix"` + Separator types.String `tfsdk:"separator"` } diff --git a/internal/provider/resource_shuffle.go b/internal/resources/shuffle/resource.go similarity index 66% rename from internal/provider/resource_shuffle.go rename to internal/resources/shuffle/resource.go index 325a194d..fb90aa93 100644 --- a/internal/provider/resource_shuffle.go +++ b/internal/resources/shuffle/resource.go @@ -1,4 +1,4 @@ -package provider +package shuffle import ( "context" @@ -7,11 +7,19 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/terraform-providers/terraform-provider-random/internal/random" ) -type resourceShuffleType struct{} +func NewResourceType() *resourceType { + return &resourceType{} +} + +var _ tfsdk.ResourceType = (*resourceType)(nil) + +type resourceType struct{} -func (r resourceShuffleType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { +func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Description: "The resource `random_shuffle` generates a random permutation of a list of strings " + "given as an argument.", @@ -22,8 +30,10 @@ func (r resourceShuffleType) GetSchema(context.Context) (tfsdk.Schema, diag.Diag Type: types.MapType{ ElemType: types.StringType, }, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, }, "seed": { Description: "Arbitrary string with which to seed the random number generator, in order to " + @@ -32,26 +42,32 @@ func (r resourceShuffleType) GetSchema(context.Context) (tfsdk.Schema, diag.Diag "**Important:** Even with an identical seed, it is not guaranteed that the same permutation " + "will be produced across different versions of Terraform. This argument causes the " + "result to be *less volatile*, but not fixed for all time.", - Type: types.StringType, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Type: types.StringType, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, }, "input": { Description: "The list of strings to shuffle.", Type: types.ListType{ ElemType: types.StringType, }, - Required: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, }, "result_count": { Description: "The number of results to return. Defaults to the number of items in the " + "`input` list. If fewer items are requested, some elements will be excluded from the " + "result. If more items are requested, items will be repeated in the result but not more " + "frequently than the number of items in the input list.", - Type: types.Int64Type, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Type: types.Int64Type, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, }, "result": { Description: "Random permutation of the list of strings given in `input`.", @@ -69,18 +85,16 @@ func (r resourceShuffleType) GetSchema(context.Context) (tfsdk.Schema, diag.Diag }, nil } -func (r resourceShuffleType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceShuffle{ - p: *(p.(*provider)), - }, nil +func (r *resourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &resource{}, nil } -type resourceShuffle struct { - p provider -} +var _ tfsdk.Resource = (*resource)(nil) -func (r resourceShuffle) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan ShuffleModel +type resource struct{} + +func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan modelV0 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -98,7 +112,7 @@ func (r resourceShuffle) Create(ctx context.Context, req tfsdk.CreateResourceReq result := make([]attr.Value, 0, resultCount) if len(input.Elems) > 0 { - rand := NewRand(seed) + rand := random.NewRand(seed) // Keep producing permutations until we fill our result Batches: @@ -115,7 +129,7 @@ func (r resourceShuffle) Create(ctx context.Context, req tfsdk.CreateResourceReq } } - s := ShuffleModel{ + s := modelV0{ ID: types.String{Value: "-"}, Keepers: plan.Keepers, Input: plan.Input, @@ -147,15 +161,24 @@ func (r resourceShuffle) Create(ctx context.Context, req tfsdk.CreateResourceReq } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r resourceShuffle) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +func (r *resource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { } // Update is intentionally left blank as all required and optional attributes force replacement of the resource // through the RequiresReplace AttributePlanModifier. -func (r resourceShuffle) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { +func (r *resource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the // [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r resourceShuffle) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +func (r *resource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +} + +type modelV0 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Seed types.String `tfsdk:"seed"` + Input types.List `tfsdk:"input"` + ResultCount types.Int64 `tfsdk:"result_count"` + Result types.List `tfsdk:"result"` } diff --git a/internal/provider/resource_string.go b/internal/resources/string/resource.go similarity index 53% rename from internal/provider/resource_string.go rename to internal/resources/string/resource.go index 91329dcc..d56bf199 100644 --- a/internal/provider/resource_string.go +++ b/internal/resources/string/resource.go @@ -1,4 +1,4 @@ -package provider +package string import ( "context" @@ -9,61 +9,130 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers" + "github.com/terraform-providers/terraform-provider-random/internal/random" + "github.com/terraform-providers/terraform-provider-random/internal/validators" ) -type resourceStringType struct{} - -func (r resourceStringType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { - return stringSchemaV2(), nil +func NewResourceType() *resourceType { + return &resourceType{} } -func (r resourceStringType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceString{ - p: *(p.(*provider)), - }, nil +var _ tfsdk.ResourceType = (*resourceType)(nil) + +type resourceType struct{} + +func (r resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { + return schemaV2(), nil } -type resourceString struct { - p provider +func (r resourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &resource{}, nil } -func (r resourceString) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - createString(ctx, req, resp) +var ( + _ tfsdk.Resource = (*resource)(nil) + _ tfsdk.ResourceWithImportState = (*resource)(nil) + _ tfsdk.ResourceWithUpgradeState = (*resource)(nil) +) + +type resource struct{} + +func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan modelV2 + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + params := random.RandomStringParams{ + Length: plan.Length.Value, + Upper: plan.Upper.Value, + MinUpper: plan.MinUpper.Value, + Lower: plan.Lower.Value, + MinLower: plan.MinLower.Value, + Numeric: plan.Numeric.Value, + MinNumeric: plan.MinNumeric.Value, + Special: plan.Special.Value, + MinSpecial: plan.MinSpecial.Value, + OverrideSpecial: plan.OverrideSpecial.Value, + } + + result, err := random.CreateRandomString(params) + if err != nil { + resp.Diagnostics.Append(diagnostics.RandomReadError(err.Error())...) + return + } + + state := modelV2{ + ID: types.String{Value: string(result)}, + Keepers: plan.Keepers, + Length: types.Int64{Value: plan.Length.Value}, + Special: types.Bool{Value: plan.Special.Value}, + Upper: types.Bool{Value: plan.Upper.Value}, + Lower: types.Bool{Value: plan.Lower.Value}, + Numeric: types.Bool{Value: plan.Numeric.Value}, + MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, + MinUpper: types.Int64{Value: plan.MinUpper.Value}, + MinLower: types.Int64{Value: plan.MinLower.Value}, + MinSpecial: types.Int64{Value: plan.MinSpecial.Value}, + OverrideSpecial: types.String{Value: plan.OverrideSpecial.Value}, + Result: types.String{Value: string(result)}, + } + + diags = resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r resourceString) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +func (r *resource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { } // Update is intentionally left blank as all required and optional attributes force replacement of the resource // through the RequiresReplace AttributePlanModifier. -func (r resourceString) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { +func (r *resource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the // [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r resourceString) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +func (r *resource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { } -func (r resourceString) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - importString(ctx, req, resp) -} +func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + id := req.ID -func (r resourceString) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { - schemaV1 := stringSchemaV1() + state := modelV2{ + ID: types.String{Value: id}, + Result: types.String{Value: id}, + Length: types.Int64{Value: int64(len(id))}, + Special: types.Bool{Value: true}, + Upper: types.Bool{Value: true}, + Lower: types.Bool{Value: true}, + Numeric: types.Bool{Value: true}, + MinSpecial: types.Int64{Value: 0}, + MinUpper: types.Int64{Value: 0}, + MinLower: types.Int64{Value: 0}, + MinNumeric: types.Int64{Value: 0}, + } - return map[int64]tfsdk.ResourceStateUpgrader{ - 1: { - PriorSchema: &schemaV1, - StateUpgrader: upgradeStringStateV1toV2, - }, + state.Keepers.ElemType = types.StringType + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } } -func stringSchemaV2() tfsdk.Schema { - return tfsdk.Schema{ - Version: 2, +func (r *resource) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { + schemaV1 := tfsdk.Schema{ + Version: 1, Description: "The resource `random_string` generates a random permutation of alphanumeric " + "characters and optionally special characters.\n" + "\n" + @@ -93,161 +162,7 @@ func stringSchemaV2() tfsdk.Schema { PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, Validators: []tfsdk.AttributeValidator{ int64validator.AtLeast(1), - NewIntIsAtLeastSumOfValidator( - tftypes.NewAttributePath().WithAttributeName("min_upper"), - tftypes.NewAttributePath().WithAttributeName("min_lower"), - tftypes.NewAttributePath().WithAttributeName("min_numeric"), - tftypes.NewAttributePath().WithAttributeName("min_special"), - ), - }, - }, - - "special": { - Description: "Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - planmodifiers.DefaultValue(types.Bool{Value: true}), - planmodifiers.RequiresReplace(), - }, - }, - - "upper": { - Description: "Include uppercase alphabet characters in the result. Default value is `true`.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - planmodifiers.DefaultValue(types.Bool{Value: true}), - planmodifiers.RequiresReplace(), - }, - }, - - "lower": { - Description: "Include lowercase alphabet characters in the result. Default value is `true`.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - planmodifiers.DefaultValue(types.Bool{Value: true}), - planmodifiers.RequiresReplace(), - }, - }, - - "numeric": { - Description: "Include numeric characters in the result. Default value is `true`.", - Type: types.BoolType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - planmodifiers.DefaultValue(types.Bool{Value: true}), - planmodifiers.RequiresReplace(), - }, - }, - - "min_numeric": { - Description: "Minimum number of numeric characters in the result. Default value is `0`.", - Type: types.Int64Type, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - planmodifiers.DefaultValue(types.Int64{Value: 0}), - planmodifiers.RequiresReplace(), - }, - }, - - "min_upper": { - Description: "Minimum number of uppercase alphabet characters in the result. Default value is `0`.", - Type: types.Int64Type, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - planmodifiers.DefaultValue(types.Int64{Value: 0}), - planmodifiers.RequiresReplace(), - }, - }, - - "min_lower": { - Description: "Minimum number of lowercase alphabet characters in the result. Default value is `0`.", - Type: types.Int64Type, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - planmodifiers.DefaultValue(types.Int64{Value: 0}), - planmodifiers.RequiresReplace(), - }, - }, - - "min_special": { - Description: "Minimum number of special characters in the result. Default value is `0`.", - Type: types.Int64Type, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - planmodifiers.DefaultValue(types.Int64{Value: 0}), - planmodifiers.RequiresReplace(), - }, - }, - - "override_special": { - Description: "Supply your own list of special characters to use for string generation. This " + - "overrides the default character list in the special argument. The `special` argument must " + - "still be set to true for any overwritten characters to be used in generation.", - Type: types.StringType, - Optional: true, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - }, - }, - - "result": { - Description: "The generated random string.", - Type: types.StringType, - Computed: true, - }, - - "id": { - Description: "The generated random string.", - Computed: true, - Type: types.StringType, - }, - }, - } -} - -func stringSchemaV1() tfsdk.Schema { - return tfsdk.Schema{ - Version: 1, - Description: "The resource `random_string` generates a random permutation of alphanumeric " + - "characters and optionally special characters.\n" + - "\n" + - "This resource *does* use a cryptographic random number generator.\n" + - "\n" + - "Historically this resource's intended usage has been ambiguous as the original example used " + - "it in a password. For backwards compatibility it will continue to exist. For unique ids please " + - "use [random_id](id.html), for sensitive random values please use [random_password](password.html).", - Attributes: map[string]tfsdk.Attribute{ - "keepers": { - Description: "Arbitrary map of values that, when changed, will trigger recreation of " + - "resource. See [the main provider documentation](../index.html) for more information.", - Type: types.MapType{ - ElemType: types.StringType, - }, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - }, - - "length": { - Description: "The length of the string desired. The minimum value for length is 1 and, length " + - "must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`).", - Type: types.Int64Type, - Required: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Validators: []tfsdk.AttributeValidator{ - int64validator.AtLeast(1), - NewIntIsAtLeastSumOfValidator( + validators.NewIntIsAtLeastSumOfValidator( tftypes.NewAttributePath().WithAttributeName("min_upper"), tftypes.NewAttributePath().WithAttributeName("min_lower"), tftypes.NewAttributePath().WithAttributeName("min_numeric"), @@ -369,94 +284,40 @@ func stringSchemaV1() tfsdk.Schema { }, }, } -} - -func createString(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan StringModelV2 - - diags := req.Plan.Get(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - params := randomStringParams{ - length: plan.Length.Value, - upper: plan.Upper.Value, - minUpper: plan.MinUpper.Value, - lower: plan.Lower.Value, - minLower: plan.MinLower.Value, - numeric: plan.Numeric.Value, - minNumeric: plan.MinNumeric.Value, - special: plan.Special.Value, - minSpecial: plan.MinSpecial.Value, - overrideSpecial: plan.OverrideSpecial.Value, - } - - result, err := createRandomString(params) - if err != nil { - resp.Diagnostics.Append(randomReadError(err.Error())...) - return - } - - state := StringModelV2{ - ID: types.String{Value: string(result)}, - Keepers: plan.Keepers, - Length: types.Int64{Value: plan.Length.Value}, - Special: types.Bool{Value: plan.Special.Value}, - Upper: types.Bool{Value: plan.Upper.Value}, - Lower: types.Bool{Value: plan.Lower.Value}, - Numeric: types.Bool{Value: plan.Numeric.Value}, - MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, - MinUpper: types.Int64{Value: plan.MinUpper.Value}, - MinLower: types.Int64{Value: plan.MinLower.Value}, - MinSpecial: types.Int64{Value: plan.MinSpecial.Value}, - OverrideSpecial: types.String{Value: plan.OverrideSpecial.Value}, - Result: types.String{Value: string(result)}, - } - - diags = resp.State.Set(ctx, state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return + return map[int64]tfsdk.ResourceStateUpgrader{ + 1: { + PriorSchema: &schemaV1, + StateUpgrader: upgradeStringStateV1toV2, + }, } } -func importString(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - id := req.ID - - state := StringModelV2{ - ID: types.String{Value: id}, - Result: types.String{Value: id}, - Length: types.Int64{Value: int64(len(id))}, - Special: types.Bool{Value: true}, - Upper: types.Bool{Value: true}, - Lower: types.Bool{Value: true}, - Numeric: types.Bool{Value: true}, - MinSpecial: types.Int64{Value: 0}, - MinUpper: types.Int64{Value: 0}, - MinLower: types.Int64{Value: 0}, - MinNumeric: types.Int64{Value: 0}, - } - - state.Keepers.ElemType = types.StringType - - diags := resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return +func upgradeStringStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { + type modelV1 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Number types.Bool `tfsdk:"number"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Result types.String `tfsdk:"result"` } -} -func upgradeStringStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { - var stringDataV1 StringModelV1 + var stringDataV1 modelV1 resp.Diagnostics.Append(req.State.Get(ctx, &stringDataV1)...) if resp.Diagnostics.HasError() { return } - stringDataV2 := StringModelV2{ + stringDataV2 := modelV2{ Keepers: stringDataV1.Keepers, Length: stringDataV1.Length, Special: stringDataV1.Special, @@ -474,3 +335,19 @@ func upgradeStringStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStat diags := resp.State.Set(ctx, stringDataV2) resp.Diagnostics.Append(diags...) } + +type modelV2 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Numeric types.Bool `tfsdk:"numeric"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Result types.String `tfsdk:"result"` +} diff --git a/internal/provider/resource_uuid.go b/internal/resources/uuid/resource_uuid.go similarity index 65% rename from internal/provider/resource_uuid.go rename to internal/resources/uuid/resource_uuid.go index 77bd8832..fe238a27 100644 --- a/internal/provider/resource_uuid.go +++ b/internal/resources/uuid/resource_uuid.go @@ -1,4 +1,4 @@ -package provider +package uuid import ( "context" @@ -8,11 +8,19 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" ) -type resourceUUIDType struct{} +func NewResourceType() *resourceType { + return &resourceType{} +} + +var _ tfsdk.ResourceType = (*resourceType)(nil) -func (r resourceUUIDType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { +type resourceType struct{} + +func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Description: "The resource `random_uuid` generates random uuid string that is intended to be " + "used as unique identifiers for other resources.\n" + @@ -26,8 +34,10 @@ func (r resourceUUIDType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnos Type: types.MapType{ ElemType: types.StringType, }, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, }, "result": { Description: "The generated uuid presented in string format.", @@ -43,29 +53,31 @@ func (r resourceUUIDType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnos }, nil } -func (r resourceUUIDType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceUUID{ - p: *(p.(*provider)), - }, nil +func (r resourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &resource{}, nil } -type resourceUUID struct { - p provider +var ( + _ tfsdk.Resource = (*resource)(nil) + _ tfsdk.ResourceWithImportState = (*resource)(nil) +) + +type resource struct { } -func (r resourceUUID) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { +func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { result, err := uuid.GenerateUUID() if err != nil { resp.Diagnostics.AddError( "Create Random UUID error", "There was an error during generation of a UUID.\n\n"+ - retryMsg+ + diagnostics.RetryMsg+ fmt.Sprintf("Original Error: %s", err), ) return } - var plan UUIDModel + var plan modelV0 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -73,7 +85,7 @@ func (r resourceUUID) Create(ctx context.Context, req tfsdk.CreateResourceReques return } - u := &UUIDModel{ + u := &modelV0{ ID: types.String{Value: result}, Result: types.String{Value: result}, Keepers: plan.Keepers, @@ -87,26 +99,26 @@ func (r resourceUUID) Create(ctx context.Context, req tfsdk.CreateResourceReques } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r resourceUUID) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +func (r *resource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { } // Update is intentionally left blank as all required and optional attributes force replacement of the resource // through the RequiresReplace AttributePlanModifier. -func (r resourceUUID) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { +func (r *resource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the // [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r resourceUUID) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +func (r *resource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { } -func (r resourceUUID) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { +func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { bytes, err := uuid.ParseUUID(req.ID) if err != nil { resp.Diagnostics.AddError( "Import Random UUID Error", "There was an error during the parsing of the UUID.\n\n"+ - retryMsg+ + diagnostics.RetryMsg+ fmt.Sprintf("Original Error: %s", err), ) return @@ -117,13 +129,13 @@ func (r resourceUUID) ImportState(ctx context.Context, req tfsdk.ImportResourceS resp.Diagnostics.AddError( "Import Random UUID Error", "There was an error during the formatting of the UUID.\n\n"+ - retryMsg+ + diagnostics.RetryMsg+ fmt.Sprintf("Original Error: %s", err), ) return } - var state UUIDModel + var state modelV0 state.ID.Value = result state.Result.Value = result @@ -135,3 +147,9 @@ func (r resourceUUID) ImportState(ctx context.Context, req tfsdk.ImportResourceS return } } + +type modelV0 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Result types.String `tfsdk:"result"` +} diff --git a/internal/provider/validators.go b/internal/validators/validators.go similarity index 97% rename from internal/provider/validators.go rename to internal/validators/validators.go index 8e0e5a22..2ec1d118 100644 --- a/internal/provider/validators.go +++ b/internal/validators/validators.go @@ -1,4 +1,4 @@ -package provider +package validators import ( "context" @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" ) -// intIsAtLeastValidator checks that the value of the attribute in the configuration +// intIsAtLeastSumOfValidator checks that the value of the attribute in the configuration // (i.e., AttributeConfig in ValidateAttributeRequest) is greater than or, equal to the sum of the values of the // attributes in the slice of AttributePath. type intIsAtLeastSumOfValidator struct { diff --git a/internal/provider/validators_test.go b/internal/validators/validators_test.go similarity index 95% rename from internal/provider/validators_test.go rename to internal/validators/validators_test.go index 7981aea8..58d18e0e 100644 --- a/internal/provider/validators_test.go +++ b/internal/validators/validators_test.go @@ -1,4 +1,4 @@ -package provider +package validators import ( "context" @@ -19,7 +19,17 @@ func TestIsAtLeastSumOfValidator_Validate(t *testing.T) { AttributePath: tftypes.NewAttributePath().WithAttributeName("length"), AttributeConfig: types.Int64{Value: 16}, Config: tfsdk.Config{ - Schema: passwordSchemaV1(), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "min_upper": { + Type: types.Int64Type, + }, + + "min_lower": { + Type: types.Int64Type, + }, + }, + }, }, } diff --git a/main.go b/main.go index 9dc3fff8..4ac97cb2 100644 --- a/main.go +++ b/main.go @@ -20,7 +20,7 @@ import ( //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs func main() { - err := providerserver.Serve(context.Background(), provider.NewFramework, providerserver.ServeOpts{ + err := providerserver.Serve(context.Background(), provider.NewProvider, providerserver.ServeOpts{ Address: "registry.terraform.io/hashicorp/random", }) if err != nil { From f29402fbd1d6dfef7e087f1e639d534858cba24a Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 27 Jun 2022 11:36:21 +0100 Subject: [PATCH 90/96] Adding acceptance tests to verify upgrading to framework does not trigger unexpected replace (#177) --- examples/resources/random_string/import.sh | 2 +- internal/provider/provider.go | 2 +- .../provider/provider_resource_id_test.go | 47 +++- .../provider_resource_integer_test.go | 58 ++++- .../provider_resource_password_test.go | 95 +++++++- .../provider/provider_resource_pet_test.go | 50 ++++- .../provider_resource_shuffle_test.go | 207 +++++++++--------- .../provider/provider_resource_string_test.go | 92 +++++++- .../provider/provider_resource_uuid_test.go | 43 +++- internal/provider/provider_test.go | 12 +- internal/random/random.go | 4 +- internal/resources/password/resource.go | 4 +- .../{string => stringresource}/resource.go | 160 +++++++++++++- .../uuid/{resource_uuid.go => resource.go} | 0 14 files changed, 612 insertions(+), 164 deletions(-) rename internal/resources/{string => stringresource}/resource.go (69%) rename internal/resources/uuid/{resource_uuid.go => resource.go} (100%) diff --git a/examples/resources/random_string/import.sh b/examples/resources/random_string/import.sh index cf09ad6c..da101043 100644 --- a/examples/resources/random_string/import.sh +++ b/examples/resources/random_string/import.sh @@ -1,2 +1,2 @@ -# Random String can be imported by specifying the value of the string. +# Random String can be imported by specifying the value of the string. terraform import random_string.test test \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 8d3d39a4..66e3ff75 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -11,7 +11,7 @@ import ( "github.com/terraform-providers/terraform-provider-random/internal/resources/password" "github.com/terraform-providers/terraform-provider-random/internal/resources/pet" "github.com/terraform-providers/terraform-provider-random/internal/resources/shuffle" - stringresource "github.com/terraform-providers/terraform-provider-random/internal/resources/string" + "github.com/terraform-providers/terraform-provider-random/internal/resources/stringresource" "github.com/terraform-providers/terraform-provider-random/internal/resources/uuid" ) diff --git a/internal/provider/provider_resource_id_test.go b/internal/provider/provider_resource_id_test.go index 82106148..be04a020 100644 --- a/internal/provider/provider_resource_id_test.go +++ b/internal/provider/provider_resource_id_test.go @@ -8,7 +8,7 @@ import ( func TestAccResourceID(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_id" "foo" { @@ -30,9 +30,9 @@ func TestAccResourceID(t *testing.T) { }) } -func TestAccResourceID_importWithPrefix(t *testing.T) { +func TestAccResourceID_ImportWithPrefix(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_id" "bar" { @@ -55,3 +55,44 @@ func TestAccResourceID_importWithPrefix(t *testing.T) { }, }) } + +func TestAccResourceID_UpgradeFromVersion3_3_2(t *testing.T) { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_id" "bar" { + byte_length = 4 + prefix = "cloud-" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrWith("random_id.bar", "b64_url", testCheckLen(12)), + resource.TestCheckResourceAttrWith("random_id.bar", "b64_std", testCheckLen(14)), + resource.TestCheckResourceAttrWith("random_id.bar", "hex", testCheckLen(14)), + resource.TestCheckResourceAttrWith("random_id.bar", "dec", testCheckMinLen(1)), + ), + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_id" "bar" { + byte_length = 4 + prefix = "cloud-" + }`, + PlanOnly: true, + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_id" "bar" { + byte_length = 4 + prefix = "cloud-" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrWith("random_id.bar", "b64_url", testCheckLen(12)), + resource.TestCheckResourceAttrWith("random_id.bar", "b64_std", testCheckLen(14)), + resource.TestCheckResourceAttrWith("random_id.bar", "hex", testCheckLen(14)), + resource.TestCheckResourceAttrWith("random_id.bar", "dec", testCheckMinLen(1)), + ), + }, + }, + }) +} diff --git a/internal/provider/provider_resource_integer_test.go b/internal/provider/provider_resource_integer_test.go index 9878fd05..e04edf46 100644 --- a/internal/provider/provider_resource_integer_test.go +++ b/internal/provider/provider_resource_integer_test.go @@ -7,10 +7,10 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestAccResourceIntegerBasic(t *testing.T) { +func TestAccResourceInteger(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_integer" "integer_1" { @@ -32,10 +32,10 @@ func TestAccResourceIntegerBasic(t *testing.T) { }) } -func TestAccResourceIntegerUpdate(t *testing.T) { +func TestAccResourceInteger_ChangeSeed(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_integer" "integer_1" { @@ -61,10 +61,10 @@ func TestAccResourceIntegerUpdate(t *testing.T) { }) } -func TestAccResourceIntegerSeedless_to_seeded(t *testing.T) { +func TestAccResourceInteger_SeedlessToSeeded(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_integer" "integer_1" { @@ -89,10 +89,10 @@ func TestAccResourceIntegerSeedless_to_seeded(t *testing.T) { }) } -func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) { +func TestAccResourceInteger_SeededToSeedless(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_integer" "integer_1" { @@ -117,10 +117,10 @@ func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) { }) } -func TestAccResourceIntegerBig(t *testing.T) { +func TestAccResourceInteger_Big(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_integer" "integer_1" { @@ -139,6 +139,44 @@ func TestAccResourceIntegerBig(t *testing.T) { }) } +func TestAccResourceInteger_UpgradeFromVersion3_3_2(t *testing.T) { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_integer" "integer_1" { + min = 1 + max = 3 + seed = "12345" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("random_integer.integer_1", "result", "3"), + ), + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_integer" "integer_1" { + min = 1 + max = 3 + seed = "12345" + }`, + PlanOnly: true, + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_integer" "integer_1" { + min = 1 + max = 3 + seed = "12345" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("random_integer.integer_1", "result", "3"), + ), + }, + }, + }) +} + func testCheckNotEmptyString(field string) func(input string) error { return func(input string) error { if input == "" { diff --git a/internal/provider/provider_resource_password_test.go b/internal/provider/provider_resource_password_test.go index c3f909a5..91599ba1 100644 --- a/internal/provider/provider_resource_password_test.go +++ b/internal/provider/provider_resource_password_test.go @@ -9,9 +9,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -func TestAccResourcePasswordBasic(t *testing.T) { +func TestAccResourcePassword(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_password" "basic" { @@ -46,9 +46,9 @@ func TestAccResourcePasswordBasic(t *testing.T) { }) } -func TestAccResourcePasswordOverride(t *testing.T) { +func TestAccResourcePassword_Override(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_password" "override" { @@ -67,11 +67,11 @@ func TestAccResourcePasswordOverride(t *testing.T) { }) } -// TestAccResourcePassword_StateUpgrade_V0toV2 covers the state upgrades from V0 to V2. +// TestAccResourcePassword_StateUpgradeV0toV2 covers the state upgrades from V0 to V2. // This includes the deprecation and removal of `number` and the addition of `numeric` // and `bcrypt_hash` attributes. // v3.1.3 is used as this is last version before `bcrypt_hash` attributed was added. -func TestAccResourcePassword_StateUpgrade_V0toV2(t *testing.T) { +func TestAccResourcePassword_StateUpgradeV0toV2(t *testing.T) { t.Parallel() cases := []struct { @@ -275,7 +275,7 @@ func TestAccResourcePassword_StateUpgrade_V0toV2(t *testing.T) { Check: resource.ComposeTestCheckFunc(c.beforeStateUpgrade...), }, { - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Config: c.configDuringUpgrade, Check: resource.ComposeTestCheckFunc(c.afterStateUpgrade...), }, @@ -289,7 +289,7 @@ func TestAccResourcePassword_StateUpgrade_V0toV2(t *testing.T) { // This includes the deprecation and removal of `number` and the addition of `numeric` attributes. // v3.2.0 was used as this is the last version before `number` was deprecated and `numeric` attribute // was added. -func TestAccResourcePassword_StateUpgrade_V1toV2(t *testing.T) { +func TestAccResourcePassword_StateUpgradeV1toV2(t *testing.T) { t.Parallel() cases := []struct { @@ -484,7 +484,7 @@ func TestAccResourcePassword_StateUpgrade_V1toV2(t *testing.T) { Check: resource.ComposeTestCheckFunc(c.beforeStateUpgrade...), }, { - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Config: c.configDuringUpgrade, Check: resource.ComposeTestCheckFunc(c.afterStateUpgrade...), }, @@ -494,9 +494,9 @@ func TestAccResourcePassword_StateUpgrade_V1toV2(t *testing.T) { } } -func TestAccResourcePasswordMin(t *testing.T) { +func TestAccResourcePassword_Min(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_password" "min" { @@ -518,3 +518,76 @@ func TestAccResourcePasswordMin(t *testing.T) { }, }) } + +func TestAccResourcePassword_UpgradeFromVersion3_3_2(t *testing.T) { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_password" "min" { + length = 12 + override_special = "!#@" + min_lower = 2 + min_upper = 3 + min_special = 1 + min_numeric = 4 + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrWith("random_password.min", "result", testCheckLen(12)), + resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([a-z].*){2,}`)), + resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([A-Z].*){3,}`)), + resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([0-9].*){4,}`)), + resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([!#@])`)), + resource.TestCheckResourceAttr("random_password.min", "special", "true"), + resource.TestCheckResourceAttr("random_password.min", "upper", "true"), + resource.TestCheckResourceAttr("random_password.min", "lower", "true"), + resource.TestCheckResourceAttr("random_password.min", "numeric", "true"), + resource.TestCheckResourceAttr("random_password.min", "min_special", "1"), + resource.TestCheckResourceAttr("random_password.min", "min_upper", "3"), + resource.TestCheckResourceAttr("random_password.min", "min_lower", "2"), + resource.TestCheckResourceAttr("random_password.min", "min_numeric", "4"), + resource.TestCheckResourceAttrSet("random_password.min", "bcrypt_hash"), + ), + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_password" "min" { + length = 12 + override_special = "!#@" + min_lower = 2 + min_upper = 3 + min_special = 1 + min_numeric = 4 + }`, + PlanOnly: true, + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_password" "min" { + length = 12 + override_special = "!#@" + min_lower = 2 + min_upper = 3 + min_special = 1 + min_numeric = 4 + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrWith("random_password.min", "result", testCheckLen(12)), + resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([a-z].*){2,}`)), + resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([A-Z].*){3,}`)), + resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([0-9].*){4,}`)), + resource.TestMatchResourceAttr("random_password.min", "result", regexp.MustCompile(`([!#@])`)), + resource.TestCheckResourceAttr("random_password.min", "special", "true"), + resource.TestCheckResourceAttr("random_password.min", "upper", "true"), + resource.TestCheckResourceAttr("random_password.min", "lower", "true"), + resource.TestCheckResourceAttr("random_password.min", "numeric", "true"), + resource.TestCheckResourceAttr("random_password.min", "min_special", "1"), + resource.TestCheckResourceAttr("random_password.min", "min_upper", "3"), + resource.TestCheckResourceAttr("random_password.min", "min_lower", "2"), + resource.TestCheckResourceAttr("random_password.min", "min_numeric", "4"), + resource.TestCheckResourceAttrSet("random_password.min", "bcrypt_hash"), + ), + }, + }, + }) +} diff --git a/internal/provider/provider_resource_pet_test.go b/internal/provider/provider_resource_pet_test.go index a2ab2867..3a5fc832 100644 --- a/internal/provider/provider_resource_pet_test.go +++ b/internal/provider/provider_resource_pet_test.go @@ -9,9 +9,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestAccResourcePet_basic(t *testing.T) { +func TestAccResourcePet(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_pet" "pet_1" { @@ -24,9 +24,9 @@ func TestAccResourcePet_basic(t *testing.T) { }) } -func TestAccResourcePet_length(t *testing.T) { +func TestAccResourcePet_Length(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_pet" "pet_1" { @@ -40,9 +40,9 @@ func TestAccResourcePet_length(t *testing.T) { }) } -func TestAccResourcePet_prefix(t *testing.T) { +func TestAccResourcePet_Prefix(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_pet" "pet_1" { @@ -57,9 +57,9 @@ func TestAccResourcePet_prefix(t *testing.T) { }) } -func TestAccResourcePet_separator(t *testing.T) { +func TestAccResourcePet_Separator(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_pet" "pet_1" { @@ -74,6 +74,40 @@ func TestAccResourcePet_separator(t *testing.T) { }) } +func TestAccResourcePet_UpgradeFromVersion3_3_2(t *testing.T) { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_pet" "pet_1" { + prefix = "consul" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrWith("random_pet.pet_1", "id", testCheckPetLen("-", 3)), + resource.TestMatchResourceAttr("random_pet.pet_1", "id", regexp.MustCompile("^consul-")), + ), + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_pet" "pet_1" { + prefix = "consul" + }`, + PlanOnly: true, + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_pet" "pet_1" { + prefix = "consul" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrWith("random_pet.pet_1", "id", testCheckPetLen("-", 3)), + resource.TestMatchResourceAttr("random_pet.pet_1", "id", regexp.MustCompile("^consul-")), + ), + }, + }, + }) +} + func testCheckPetLen(separator string, expectedLen int) func(input string) error { return func(input string) error { petNameParts := strings.Split(input, separator) diff --git a/internal/provider/provider_resource_shuffle_test.go b/internal/provider/provider_resource_shuffle_test.go index 79ddfa34..a5670e5d 100644 --- a/internal/provider/provider_resource_shuffle_test.go +++ b/internal/provider/provider_resource_shuffle_test.go @@ -2,11 +2,9 @@ package provider import ( "fmt" - "strconv" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) // These results are current as of Go 1.6. The Go @@ -18,156 +16,167 @@ import ( // document them when they arise, but the docs for this // resource specifically warn that results are not // guaranteed consistent across Terraform releases. -func TestAccResourceShuffleDefault(t *testing.T) { +func TestAccResourceShuffle(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourceShuffleConfigDefault, + Config: `resource "random_shuffle" "default_length" { + input = ["a", "b", "c", "d", "e"] + seed = "-" + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceShuffleCheck( - "random_shuffle.default_length", - []string{"a", "c", "b", "e", "d"}, - ), + resource.TestCheckResourceAttrWith("random_shuffle.default_length", "result.#", testAccResourceShuffleCheckLength("5")), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.0", "a"), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.1", "c"), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.2", "b"), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.3", "e"), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.4", "d"), ), }, }, }) } -func TestAccResourceShuffleShorter(t *testing.T) { +func TestAccResourceShuffle_Shorter(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourceShuffleConfigShorter, + Config: `resource "random_shuffle" "shorter_length" { + input = ["a", "b", "c", "d", "e"] + seed = "-" + result_count = 3 + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceShuffleCheck( - "random_shuffle.shorter_length", - []string{"a", "c", "b"}, - ), + resource.TestCheckResourceAttrWith("random_shuffle.shorter_length", "result.#", testAccResourceShuffleCheckLength("3")), + resource.TestCheckResourceAttr("random_shuffle.shorter_length", "result.0", "a"), + resource.TestCheckResourceAttr("random_shuffle.shorter_length", "result.1", "c"), + resource.TestCheckResourceAttr("random_shuffle.shorter_length", "result.2", "b"), ), }, }, }) } -func TestAccResourceShuffleLonger(t *testing.T) { +func TestAccResourceShuffle_Longer(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourceShuffleConfigLonger, + Config: `resource "random_shuffle" "longer_length" { + input = ["a", "b", "c", "d", "e"] + seed = "-" + result_count = 12 + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceShuffleCheck( - "random_shuffle.longer_length", - []string{"a", "c", "b", "e", "d", "a", "e", "d", "c", "b", "a", "b"}, - ), + resource.TestCheckResourceAttrWith("random_shuffle.longer_length", "result.#", testAccResourceShuffleCheckLength("12")), + resource.TestCheckResourceAttr("random_shuffle.longer_length", "result.0", "a"), + resource.TestCheckResourceAttr("random_shuffle.longer_length", "result.1", "c"), + resource.TestCheckResourceAttr("random_shuffle.longer_length", "result.2", "b"), + resource.TestCheckResourceAttr("random_shuffle.longer_length", "result.3", "e"), + resource.TestCheckResourceAttr("random_shuffle.longer_length", "result.4", "d"), + resource.TestCheckResourceAttr("random_shuffle.longer_length", "result.5", "a"), + resource.TestCheckResourceAttr("random_shuffle.longer_length", "result.6", "e"), + resource.TestCheckResourceAttr("random_shuffle.longer_length", "result.7", "d"), + resource.TestCheckResourceAttr("random_shuffle.longer_length", "result.8", "c"), + resource.TestCheckResourceAttr("random_shuffle.longer_length", "result.9", "b"), + resource.TestCheckResourceAttr("random_shuffle.longer_length", "result.10", "a"), + resource.TestCheckResourceAttr("random_shuffle.longer_length", "result.11", "b"), ), }, }, }) } -func TestAccResourceShuffleEmpty(t *testing.T) { +func TestAccResourceShuffle_Empty(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourceShuffleConfigEmpty, + Config: `resource "random_shuffle" "empty_length" { + input = [] + seed = "-" + result_count = 12 + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceShuffleCheck( - "random_shuffle.empty_length", - []string{}, - ), + resource.TestCheckResourceAttrWith("random_shuffle.empty_length", "result.#", testAccResourceShuffleCheckLength("0")), ), }, }, }) } -func TestAccResourceShuffleOne(t *testing.T) { +func TestAccResourceShuffle_One(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourceShuffleConfigOne, + Config: `resource "random_shuffle" "one_length" { + input = ["a"] + seed = "-" + result_count = 1 + }`, Check: resource.ComposeTestCheckFunc( - testAccResourceShuffleCheck( - "random_shuffle.one_length", - []string{"a"}, - ), + resource.TestCheckResourceAttrWith("random_shuffle.one_length", "result.#", testAccResourceShuffleCheckLength("1")), + resource.TestCheckResourceAttr("random_shuffle.one_length", "result.0", "a"), ), }, }, }) } -func testAccResourceShuffleCheck(id string, wants []string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[id] - if !ok { - return fmt.Errorf("Not found: %s", id) - } - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - attrs := rs.Primary.Attributes - - gotLen := attrs["result.#"] - wantLen := strconv.Itoa(len(wants)) - if gotLen != wantLen { - return fmt.Errorf("got %s result items; want %s", gotLen, wantLen) - } +func TestAccResourceShuffle_UpgradeFromVersion3_3_2(t *testing.T) { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_shuffle" "default_length" { + input = ["a", "b", "c", "d", "e"] + seed = "-" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrWith("random_shuffle.default_length", "result.#", testAccResourceShuffleCheckLength("5")), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.0", "a"), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.1", "c"), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.2", "b"), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.3", "e"), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.4", "d"), + ), + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_shuffle" "default_length" { + input = ["a", "b", "c", "d", "e"] + seed = "-" + }`, + PlanOnly: true, + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_shuffle" "default_length" { + input = ["a", "b", "c", "d", "e"] + seed = "-" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrWith("random_shuffle.default_length", "result.#", testAccResourceShuffleCheckLength("5")), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.0", "a"), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.1", "c"), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.2", "b"), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.3", "e"), + resource.TestCheckResourceAttr("random_shuffle.default_length", "result.4", "d"), + ), + }, + }, + }) +} - for i, want := range wants { - key := fmt.Sprintf("result.%d", i) - if got := attrs[key]; got != want { - return fmt.Errorf("index %d is %q; want %q", i, got, want) - } +func testAccResourceShuffleCheckLength(expectedLength string) func(input string) error { + return func(input string) error { + if input != expectedLength { + return fmt.Errorf("got length %s; expected length %s", input, expectedLength) } return nil } } - -const ( - testAccResourceShuffleConfigDefault = ` -resource "random_shuffle" "default_length" { - input = ["a", "b", "c", "d", "e"] - seed = "-" -}` - - testAccResourceShuffleConfigShorter = ` -resource "random_shuffle" "shorter_length" { - input = ["a", "b", "c", "d", "e"] - seed = "-" - result_count = 3 -} -` - - testAccResourceShuffleConfigLonger = ` -resource "random_shuffle" "longer_length" { - input = ["a", "b", "c", "d", "e"] - seed = "-" - result_count = 12 -} -` - - testAccResourceShuffleConfigEmpty = ` -resource "random_shuffle" "empty_length" { - input = [] - seed = "-" - result_count = 12 -} -` - - testAccResourceShuffleConfigOne = ` -resource "random_shuffle" "one_length" { - input = ["a"] - seed = "-" - result_count = 1 -} -` -) diff --git a/internal/provider/provider_resource_string_test.go b/internal/provider/provider_resource_string_test.go index 8bb5c0be..19fcdb36 100644 --- a/internal/provider/provider_resource_string_test.go +++ b/internal/provider/provider_resource_string_test.go @@ -10,7 +10,7 @@ import ( func TestAccResourceString(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_string" "basic" { @@ -29,9 +29,9 @@ func TestAccResourceString(t *testing.T) { }) } -func TestAccResourceStringOverride(t *testing.T) { +func TestAccResourceString_Override(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_string" "override" { @@ -50,9 +50,9 @@ func TestAccResourceStringOverride(t *testing.T) { }) } -func TestAccResourceStringMin(t *testing.T) { +func TestAccResourceString_Min(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_string" "min" { @@ -75,11 +75,11 @@ func TestAccResourceStringMin(t *testing.T) { }) } -// TestAccResourceString_StateUpgrade_V1toV2 covers the state upgrade from V1 to V2. +// TestAccResourceString_StateUpgradeV1toV2 covers the state upgrade from V1 to V2. // This includes the deprecation and removal of `number` and the addition of `numeric` attributes. // v3.2.0 was used as this is the last version before `number` was deprecated and `numeric` attribute // was added. -func TestAccResourceString_StateUpgrade_V1toV2(t *testing.T) { +func TestAccResourceString_StateUpgradeV1toV2(t *testing.T) { t.Parallel() cases := []struct { @@ -269,7 +269,7 @@ func TestAccResourceString_StateUpgrade_V1toV2(t *testing.T) { Check: resource.ComposeTestCheckFunc(c.beforeStateUpgrade...), }, { - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Config: c.configDuringUpgrade, Check: resource.ComposeTestCheckFunc(c.afterStateUpgrade...), }, @@ -279,9 +279,9 @@ func TestAccResourceString_StateUpgrade_V1toV2(t *testing.T) { } } -func TestAccResourceStringErrors(t *testing.T) { +func TestAccResourceString_LengthErrors(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { Config: `resource "random_string" "invalid_length" { @@ -300,6 +300,77 @@ func TestAccResourceStringErrors(t *testing.T) { }) } +func TestAccResourceString_UpgradeFromVersion3_3_2(t *testing.T) { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_string" "min" { + length = 12 + override_special = "!#@" + min_lower = 2 + min_upper = 3 + min_special = 1 + min_numeric = 4 + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrWith("random_string.min", "result", testCheckLen(12)), + resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([a-z].*){2,}`)), + resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([A-Z].*){3,}`)), + resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([0-9].*){4,}`)), + resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([!#@])`)), + resource.TestCheckResourceAttr("random_string.min", "special", "true"), + resource.TestCheckResourceAttr("random_string.min", "upper", "true"), + resource.TestCheckResourceAttr("random_string.min", "lower", "true"), + resource.TestCheckResourceAttr("random_string.min", "numeric", "true"), + resource.TestCheckResourceAttr("random_string.min", "min_special", "1"), + resource.TestCheckResourceAttr("random_string.min", "min_upper", "3"), + resource.TestCheckResourceAttr("random_string.min", "min_lower", "2"), + resource.TestCheckResourceAttr("random_string.min", "min_numeric", "4"), + ), + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_string" "min" { + length = 12 + override_special = "!#@" + min_lower = 2 + min_upper = 3 + min_special = 1 + min_numeric = 4 + }`, + PlanOnly: true, + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_string" "min" { + length = 12 + override_special = "!#@" + min_lower = 2 + min_upper = 3 + min_special = 1 + min_numeric = 4 + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrWith("random_string.min", "result", testCheckLen(12)), + resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([a-z].*){2,}`)), + resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([A-Z].*){3,}`)), + resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([0-9].*){4,}`)), + resource.TestMatchResourceAttr("random_string.min", "result", regexp.MustCompile(`([!#@])`)), + resource.TestCheckResourceAttr("random_string.min", "special", "true"), + resource.TestCheckResourceAttr("random_string.min", "upper", "true"), + resource.TestCheckResourceAttr("random_string.min", "lower", "true"), + resource.TestCheckResourceAttr("random_string.min", "numeric", "true"), + resource.TestCheckResourceAttr("random_string.min", "min_special", "1"), + resource.TestCheckResourceAttr("random_string.min", "min_upper", "3"), + resource.TestCheckResourceAttr("random_string.min", "min_lower", "2"), + resource.TestCheckResourceAttr("random_string.min", "min_numeric", "4"), + ), + }, + }, + }) +} + func testCheckLen(expectedLen int) func(input string) error { return func(input string) error { if len(input) != expectedLen { @@ -310,6 +381,7 @@ func testCheckLen(expectedLen int) func(input string) error { } } +//nolint:unparam func testCheckMinLen(minLen int) func(input string) error { return func(input string) error { if len(input) < minLen { diff --git a/internal/provider/provider_resource_uuid_test.go b/internal/provider/provider_resource_uuid_test.go index 9643d7fd..94ec0577 100644 --- a/internal/provider/provider_resource_uuid_test.go +++ b/internal/provider/provider_resource_uuid_test.go @@ -9,16 +9,13 @@ import ( func TestAccResourceUUID(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccResourceUUIDConfig, + Config: `resource "random_uuid" "basic" { + }`, Check: resource.ComposeTestCheckFunc( - resource.TestMatchResourceAttr( - "random_uuid.basic", - "result", - regexp.MustCompile(`[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}`), - ), + resource.TestMatchResourceAttr("random_uuid.basic", "result", regexp.MustCompile(`[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}`)), ), }, { @@ -30,9 +27,31 @@ func TestAccResourceUUID(t *testing.T) { }) } -const ( - testAccResourceUUIDConfig = ` -resource "random_uuid" "basic" { +func TestAccResourceUUID_UpgradeFromVersion3_3_2(t *testing.T) { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_uuid" "basic" { + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr("random_uuid.basic", "result", regexp.MustCompile(`[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}`)), + ), + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_uuid" "basic" { + }`, + PlanOnly: true, + }, + { + ProtoV6ProviderFactories: protoV6ProviderFactories(), + Config: `resource "random_uuid" "basic" { + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr("random_uuid.basic", "result", regexp.MustCompile(`[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}`)), + ), + }, + }, + }) } -` -) diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index 46d1e872..90bdd00a 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -3,13 +3,23 @@ package provider import ( "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) //nolint:unparam -func testAccProtoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { +func protoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { return map[string]func() (tfprotov6.ProviderServer, error){ "random": func() (tfprotov6.ProviderServer, error) { return providerserver.NewProtocol6(NewProvider())(), nil }, } } + +func providerVersion332() map[string]resource.ExternalProvider { + return map[string]resource.ExternalProvider{ + "tls": { + VersionConstraint: "3.3.2", + Source: "hashicorp/random", + }, + } +} diff --git a/internal/random/random.go b/internal/random/random.go index 3bf98bc3..f3494050 100644 --- a/internal/random/random.go +++ b/internal/random/random.go @@ -6,7 +6,7 @@ import ( "sort" ) -type RandomStringParams struct { +type StringParams struct { Length int64 Upper bool MinUpper int64 @@ -19,7 +19,7 @@ type RandomStringParams struct { OverrideSpecial string } -func CreateRandomString(input RandomStringParams) ([]byte, error) { +func CreateString(input StringParams) ([]byte, error) { const numChars = "0123456789" const lowerChars = "abcdefghijklmnopqrstuvwxyz" const upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" diff --git a/internal/resources/password/resource.go b/internal/resources/password/resource.go index f874bbf8..55fa930c 100644 --- a/internal/resources/password/resource.go +++ b/internal/resources/password/resource.go @@ -45,7 +45,7 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, return } - params := random.RandomStringParams{ + params := random.StringParams{ Length: plan.Length.Value, Upper: plan.Upper.Value, MinUpper: plan.MinUpper.Value, @@ -58,7 +58,7 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, OverrideSpecial: plan.OverrideSpecial.Value, } - result, err := random.CreateRandomString(params) + result, err := random.CreateString(params) if err != nil { resp.Diagnostics.Append(diagnostics.RandomReadError(err.Error())...) return diff --git a/internal/resources/string/resource.go b/internal/resources/stringresource/resource.go similarity index 69% rename from internal/resources/string/resource.go rename to internal/resources/stringresource/resource.go index d56bf199..c329e068 100644 --- a/internal/resources/string/resource.go +++ b/internal/resources/stringresource/resource.go @@ -1,4 +1,4 @@ -package string +package stringresource import ( "context" @@ -24,7 +24,159 @@ var _ tfsdk.ResourceType = (*resourceType)(nil) type resourceType struct{} func (r resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { - return schemaV2(), nil + return tfsdk.Schema{ + Version: 2, + Description: "The resource `random_string` generates a random permutation of alphanumeric " + + "characters and optionally special characters.\n" + + "\n" + + "This resource *does* use a cryptographic random number generator.\n" + + "\n" + + "Historically this resource's intended usage has been ambiguous as the original example used " + + "it in a password. For backwards compatibility it will continue to exist. For unique ids please " + + "use [random_id](id.html), for sensitive random values please use [random_password](password.html).", + Attributes: map[string]tfsdk.Attribute{ + "keepers": { + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + Type: types.MapType{ + ElemType: types.StringType, + }, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, + }, + + "length": { + Description: "The length of the string desired. The minimum value for length is 1 and, length " + + "must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`).", + Type: types.Int64Type, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, + Validators: []tfsdk.AttributeValidator{ + int64validator.AtLeast(1), + validators.NewIntIsAtLeastSumOfValidator( + tftypes.NewAttributePath().WithAttributeName("min_upper"), + tftypes.NewAttributePath().WithAttributeName("min_lower"), + tftypes.NewAttributePath().WithAttributeName("min_numeric"), + tftypes.NewAttributePath().WithAttributeName("min_special"), + ), + }, + }, + + "special": { + Description: "Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), + }, + }, + + "upper": { + Description: "Include uppercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), + }, + }, + + "lower": { + Description: "Include lowercase alphabet characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), + }, + }, + + "numeric": { + Description: "Include numeric characters in the result. Default value is `true`.", + Type: types.BoolType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifiers.DefaultValue(types.Bool{Value: true}), + planmodifiers.RequiresReplace(), + }, + }, + + "min_numeric": { + Description: "Minimum number of numeric characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), + }, + }, + + "min_upper": { + Description: "Minimum number of uppercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), + }, + }, + + "min_lower": { + Description: "Minimum number of lowercase alphabet characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), + }, + }, + + "min_special": { + Description: "Minimum number of special characters in the result. Default value is `0`.", + Type: types.Int64Type, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifiers.DefaultValue(types.Int64{Value: 0}), + planmodifiers.RequiresReplace(), + }, + }, + + "override_special": { + Description: "Supply your own list of special characters to use for string generation. This " + + "overrides the default character list in the special argument. The `special` argument must " + + "still be set to true for any overwritten characters to be used in generation.", + Type: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, + }, + + "result": { + Description: "The generated random string.", + Type: types.StringType, + Computed: true, + }, + + "id": { + Description: "The generated random string.", + Computed: true, + Type: types.StringType, + }, + }, + }, nil } func (r resourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { @@ -48,7 +200,7 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, return } - params := random.RandomStringParams{ + params := random.StringParams{ Length: plan.Length.Value, Upper: plan.Upper.Value, MinUpper: plan.MinUpper.Value, @@ -61,7 +213,7 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, OverrideSpecial: plan.OverrideSpecial.Value, } - result, err := random.CreateRandomString(params) + result, err := random.CreateString(params) if err != nil { resp.Diagnostics.Append(diagnostics.RandomReadError(err.Error())...) return diff --git a/internal/resources/uuid/resource_uuid.go b/internal/resources/uuid/resource.go similarity index 100% rename from internal/resources/uuid/resource_uuid.go rename to internal/resources/uuid/resource.go From 6c94c96209e02845f1858050828f3bccfda441a3 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 27 Jun 2022 13:48:59 +0100 Subject: [PATCH 91/96] Renaming files (#177) --- internal/provider/provider.go | 2 +- internal/provider/provider_test.go | 2 +- internal/resources/id/{resource.go => resource_id.go} | 0 internal/resources/integer/{resource.go => resource_integer.go} | 0 .../resources/password/{resource.go => resource_password.go} | 0 .../password/{resource_test.go => resource_password_test.go} | 0 internal/resources/pet/{resource.go => resource_pet.go} | 0 internal/resources/shuffle/{resource.go => resource_shuffle.go} | 0 .../stringresource/{resource.go => resource_string.go} | 0 internal/resources/uuid/{resource.go => resource_uuid.go} | 0 internal/validators/validators.go | 1 + main.go | 2 +- 12 files changed, 4 insertions(+), 3 deletions(-) rename internal/resources/id/{resource.go => resource_id.go} (100%) rename internal/resources/integer/{resource.go => resource_integer.go} (100%) rename internal/resources/password/{resource.go => resource_password.go} (100%) rename internal/resources/password/{resource_test.go => resource_password_test.go} (100%) rename internal/resources/pet/{resource.go => resource_pet.go} (100%) rename internal/resources/shuffle/{resource.go => resource_shuffle.go} (100%) rename internal/resources/stringresource/{resource.go => resource_string.go} (100%) rename internal/resources/uuid/{resource.go => resource_uuid.go} (100%) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 66e3ff75..1141f959 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -15,7 +15,7 @@ import ( "github.com/terraform-providers/terraform-provider-random/internal/resources/uuid" ) -func NewProvider() tfsdk.Provider { +func New() tfsdk.Provider { return &provider{} } diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index 90bdd00a..a36b3726 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -10,7 +10,7 @@ import ( func protoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { return map[string]func() (tfprotov6.ProviderServer, error){ "random": func() (tfprotov6.ProviderServer, error) { - return providerserver.NewProtocol6(NewProvider())(), nil + return providerserver.NewProtocol6(New())(), nil }, } } diff --git a/internal/resources/id/resource.go b/internal/resources/id/resource_id.go similarity index 100% rename from internal/resources/id/resource.go rename to internal/resources/id/resource_id.go diff --git a/internal/resources/integer/resource.go b/internal/resources/integer/resource_integer.go similarity index 100% rename from internal/resources/integer/resource.go rename to internal/resources/integer/resource_integer.go diff --git a/internal/resources/password/resource.go b/internal/resources/password/resource_password.go similarity index 100% rename from internal/resources/password/resource.go rename to internal/resources/password/resource_password.go diff --git a/internal/resources/password/resource_test.go b/internal/resources/password/resource_password_test.go similarity index 100% rename from internal/resources/password/resource_test.go rename to internal/resources/password/resource_password_test.go diff --git a/internal/resources/pet/resource.go b/internal/resources/pet/resource_pet.go similarity index 100% rename from internal/resources/pet/resource.go rename to internal/resources/pet/resource_pet.go diff --git a/internal/resources/shuffle/resource.go b/internal/resources/shuffle/resource_shuffle.go similarity index 100% rename from internal/resources/shuffle/resource.go rename to internal/resources/shuffle/resource_shuffle.go diff --git a/internal/resources/stringresource/resource.go b/internal/resources/stringresource/resource_string.go similarity index 100% rename from internal/resources/stringresource/resource.go rename to internal/resources/stringresource/resource_string.go diff --git a/internal/resources/uuid/resource.go b/internal/resources/uuid/resource_uuid.go similarity index 100% rename from internal/resources/uuid/resource.go rename to internal/resources/uuid/resource_uuid.go diff --git a/internal/validators/validators.go b/internal/validators/validators.go index 2ec1d118..20f86eb4 100644 --- a/internal/validators/validators.go +++ b/internal/validators/validators.go @@ -15,6 +15,7 @@ import ( // intIsAtLeastSumOfValidator checks that the value of the attribute in the configuration // (i.e., AttributeConfig in ValidateAttributeRequest) is greater than or, equal to the sum of the values of the // attributes in the slice of AttributePath. +// TODO: Remove once https://github.com/hashicorp/terraform-plugin-framework-validators/pull/29 is merged. type intIsAtLeastSumOfValidator struct { attributesToSum []*tftypes.AttributePath } diff --git a/main.go b/main.go index 4ac97cb2..1671f7f0 100644 --- a/main.go +++ b/main.go @@ -20,7 +20,7 @@ import ( //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs func main() { - err := providerserver.Serve(context.Background(), provider.NewProvider, providerserver.ServeOpts{ + err := providerserver.Serve(context.Background(), provider.New, providerserver.ServeOpts{ Address: "registry.terraform.io/hashicorp/random", }) if err != nil { From c75de5608c5d6b246af30036ba089b67097505aa Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 28 Jun 2022 11:36:41 +0100 Subject: [PATCH 92/96] Adding support for debug flag (#177) --- main.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/main.go b/main.go index 1671f7f0..196115a4 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "flag" "log" "github.com/hashicorp/terraform-plugin-framework/providerserver" @@ -20,8 +21,14 @@ import ( //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs func main() { + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + err := providerserver.Serve(context.Background(), provider.New, providerserver.ServeOpts{ Address: "registry.terraform.io/hashicorp/random", + Debug: debug, }) if err != nil { log.Fatal(err) From 85657999c80a81415a38e263d0d8e8defac26fb5 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 28 Jun 2022 13:39:15 +0100 Subject: [PATCH 93/96] Bumping protocol version (#177) --- terraform-registry-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform-registry-manifest.json b/terraform-registry-manifest.json index a8286e38..6e86c621 100644 --- a/terraform-registry-manifest.json +++ b/terraform-registry-manifest.json @@ -1,6 +1,6 @@ { "version": 1, "metadata": { - "protocol_versions": ["5.0"] + "protocol_versions": ["6.0"] } } \ No newline at end of file From 62729335d16b5d89192f8d3b0c08d0ac3c058418 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 30 Jun 2022 10:39:08 +0100 Subject: [PATCH 94/96] Refactoring (#177) --- internal/provider/provider.go | 22 +- .../{resources/id => provider}/resource_id.go | 40 ++- ...esource_id_test.go => resource_id_test.go} | 0 .../integer => provider}/resource_integer.go | 40 ++- ...teger_test.go => resource_integer_test.go} | 0 .../resource_password.go} | 274 ++++++++++++++++- ...word_test.go => resource_password_test.go} | 134 +++++++++ .../pet => provider}/resource_pet.go | 34 +-- ...ource_pet_test.go => resource_pet_test.go} | 0 .../shuffle => provider}/resource_shuffle.go | 34 +-- ...uffle_test.go => resource_shuffle_test.go} | 0 .../resource_string.go | 46 ++- ...string_test.go => resource_string_test.go} | 0 .../uuid => provider}/resource_uuid.go | 40 ++- ...rce_uuid_test.go => resource_uuid_test.go} | 0 internal/random/{random.go => string.go} | 0 .../resources/password/resource_password.go | 277 ------------------ .../password/resource_password_test.go | 140 --------- 18 files changed, 516 insertions(+), 565 deletions(-) rename internal/{resources/id => provider}/resource_id.go (85%) rename internal/provider/{provider_resource_id_test.go => resource_id_test.go} (100%) rename internal/{resources/integer => provider}/resource_integer.go (80%) rename internal/provider/{provider_resource_integer_test.go => resource_integer_test.go} (100%) rename internal/{resources/password/schema.go => provider/resource_password.go} (63%) rename internal/provider/{provider_resource_password_test.go => resource_password_test.go} (81%) rename internal/{resources/pet => provider}/resource_pet.go (79%) rename internal/provider/{provider_resource_pet_test.go => resource_pet_test.go} (100%) rename internal/{resources/shuffle => provider}/resource_shuffle.go (82%) rename internal/provider/{provider_resource_shuffle_test.go => resource_shuffle_test.go} (100%) rename internal/{resources/stringresource => provider}/resource_string.go (92%) rename internal/provider/{provider_resource_string_test.go => resource_string_test.go} (100%) rename internal/{resources/uuid => provider}/resource_uuid.go (74%) rename internal/provider/{provider_resource_uuid_test.go => resource_uuid_test.go} (100%) rename internal/random/{random.go => string.go} (100%) delete mode 100644 internal/resources/password/resource_password.go delete mode 100644 internal/resources/password/resource_password_test.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 1141f959..d0af56b1 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -5,14 +5,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" - - "github.com/terraform-providers/terraform-provider-random/internal/resources/id" - "github.com/terraform-providers/terraform-provider-random/internal/resources/integer" - "github.com/terraform-providers/terraform-provider-random/internal/resources/password" - "github.com/terraform-providers/terraform-provider-random/internal/resources/pet" - "github.com/terraform-providers/terraform-provider-random/internal/resources/shuffle" - "github.com/terraform-providers/terraform-provider-random/internal/resources/stringresource" - "github.com/terraform-providers/terraform-provider-random/internal/resources/uuid" ) func New() tfsdk.Provider { @@ -32,13 +24,13 @@ func (p *provider) Configure(context.Context, tfsdk.ConfigureProviderRequest, *t func (p *provider) GetResources(context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { return map[string]tfsdk.ResourceType{ - "random_id": id.NewResourceType(), - "random_integer": integer.NewResourceType(), - "random_password": password.NewResourceType(), - "random_pet": pet.NewResourceType(), - "random_shuffle": shuffle.NewResourceType(), - "random_string": stringresource.NewResourceType(), - "random_uuid": uuid.NewResourceType(), + "random_id": &idResourceType{}, + "random_integer": &integerResourceType{}, + "random_password": &passwordResourceType{}, + "random_pet": &petResourceType{}, + "random_shuffle": &shuffleResourceType{}, + "random_string": &stringResourceType{}, + "random_uuid": &uuidResourceType{}, }, nil } diff --git a/internal/resources/id/resource_id.go b/internal/provider/resource_id.go similarity index 85% rename from internal/resources/id/resource_id.go rename to internal/provider/resource_id.go index 03d49929..2459d1c3 100644 --- a/internal/resources/id/resource_id.go +++ b/internal/provider/resource_id.go @@ -1,4 +1,4 @@ -package id +package provider import ( "context" @@ -16,15 +16,11 @@ import ( "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" ) -func NewResourceType() *resourceType { - return &resourceType{} -} - -var _ tfsdk.ResourceType = (*resourceType)(nil) +var _ tfsdk.ResourceType = (*idResourceType)(nil) -type resourceType struct{} +type idResourceType struct{} -func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { +func (r *idResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Description: ` The resource ` + "`random_id`" + ` generates random numbers that are intended to be @@ -101,19 +97,19 @@ exist concurrently. }, nil } -func (r *resourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return &resource{}, nil +func (r *idResourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &idResource{}, nil } var ( - _ tfsdk.Resource = (*resource)(nil) - _ tfsdk.ResourceWithImportState = (*resource)(nil) + _ tfsdk.Resource = (*idResource)(nil) + _ tfsdk.ResourceWithImportState = (*idResource)(nil) ) -type resource struct{} +type idResource struct{} -func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan modelV0 +func (r *idResource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan idModelV0 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -148,7 +144,7 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, bigInt.SetBytes(bytes) dec := bigInt.String() - i := modelV0{ + i := idModelV0{ ID: types.String{Value: id}, Keepers: plan.Keepers, ByteLength: types.Int64{Value: plan.ByteLength.Value}, @@ -167,20 +163,20 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r *resource) Read(context.Context, tfsdk.ReadResourceRequest, *tfsdk.ReadResourceResponse) { +func (r *idResource) Read(context.Context, tfsdk.ReadResourceRequest, *tfsdk.ReadResourceResponse) { } // Update is intentionally left blank as all required and optional attributes force replacement of the resource // through the RequiresReplace AttributePlanModifier. -func (r *resource) Update(context.Context, tfsdk.UpdateResourceRequest, *tfsdk.UpdateResourceResponse) { +func (r *idResource) Update(context.Context, tfsdk.UpdateResourceRequest, *tfsdk.UpdateResourceResponse) { } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the // [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r *resource) Delete(context.Context, tfsdk.DeleteResourceRequest, *tfsdk.DeleteResourceResponse) { +func (r *idResource) Delete(context.Context, tfsdk.DeleteResourceRequest, *tfsdk.DeleteResourceResponse) { } -func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { +func (r *idResource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { id := req.ID var prefix string @@ -208,7 +204,7 @@ func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStat bigInt.SetBytes(bytes) dec := bigInt.String() - var state modelV0 + var state idModelV0 state.ID.Value = id state.ByteLength.Value = int64(len(bytes)) @@ -231,7 +227,7 @@ func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStat } } -type modelV0 struct { +type idModelV0 struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` ByteLength types.Int64 `tfsdk:"byte_length"` diff --git a/internal/provider/provider_resource_id_test.go b/internal/provider/resource_id_test.go similarity index 100% rename from internal/provider/provider_resource_id_test.go rename to internal/provider/resource_id_test.go diff --git a/internal/resources/integer/resource_integer.go b/internal/provider/resource_integer.go similarity index 80% rename from internal/resources/integer/resource_integer.go rename to internal/provider/resource_integer.go index a81b7f8b..cf145280 100644 --- a/internal/resources/integer/resource_integer.go +++ b/internal/provider/resource_integer.go @@ -1,4 +1,4 @@ -package integer +package provider import ( "context" @@ -13,15 +13,11 @@ import ( "github.com/terraform-providers/terraform-provider-random/internal/random" ) -func NewResourceType() *resourceType { - return &resourceType{} -} - -var _ tfsdk.ResourceType = (*resourceType)(nil) +var _ tfsdk.ResourceType = (*integerResourceType)(nil) -type resourceType struct{} +type integerResourceType struct{} -func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { +func (r *integerResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Description: "The resource `random_integer` generates random values from a given range, described " + "by the `min` and `max` attributes of a given resource.\n" + @@ -71,19 +67,19 @@ func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostic }, nil } -func (r *resourceType) NewResource(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return &resource{}, nil +func (r *integerResourceType) NewResource(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &integerResource{}, nil } var ( - _ tfsdk.Resource = (*resource)(nil) - _ tfsdk.ResourceWithImportState = (*resource)(nil) + _ tfsdk.Resource = (*integerResource)(nil) + _ tfsdk.ResourceWithImportState = (*integerResource)(nil) ) -type resource struct{} +type integerResource struct{} -func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan modelV0 +func (r *integerResource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan integerModelV0 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -106,7 +102,7 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, rand := random.NewRand(seed) number := rand.Intn((max+1)-min) + min - u := &modelV0{ + u := &integerModelV0{ ID: types.String{Value: strconv.Itoa(number)}, Keepers: plan.Keepers, Min: types.Int64{Value: int64(min)}, @@ -128,20 +124,20 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r *resource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +func (r *integerResource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { } // Update is intentionally left blank as all required and optional attributes force replacement of the resource // through the RequiresReplace AttributePlanModifier. -func (r *resource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { +func (r *integerResource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the // [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r *resource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +func (r *integerResource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { } -func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { +func (r *integerResource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { parts := strings.Split(req.ID, ",") if len(parts) != 3 && len(parts) != 4 { resp.Diagnostics.AddError( @@ -181,7 +177,7 @@ func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStat return } - var state modelV0 + var state integerModelV0 state.ID.Value = parts[0] state.Keepers.ElemType = types.StringType @@ -200,7 +196,7 @@ func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStat } } -type modelV0 struct { +type integerModelV0 struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` Min types.Int64 `tfsdk:"min"` diff --git a/internal/provider/provider_resource_integer_test.go b/internal/provider/resource_integer_test.go similarity index 100% rename from internal/provider/provider_resource_integer_test.go rename to internal/provider/resource_integer_test.go diff --git a/internal/resources/password/schema.go b/internal/provider/resource_password.go similarity index 63% rename from internal/resources/password/schema.go rename to internal/provider/resource_password.go index aeaef2cd..8780989b 100644 --- a/internal/resources/password/schema.go +++ b/internal/provider/resource_password.go @@ -1,16 +1,265 @@ -package password +package provider import ( + "context" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" + "golang.org/x/crypto/bcrypt" + "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers" + "github.com/terraform-providers/terraform-provider-random/internal/random" "github.com/terraform-providers/terraform-provider-random/internal/validators" ) -func schemaV2() tfsdk.Schema { +var _ tfsdk.ResourceType = (*passwordResourceType)(nil) + +type passwordResourceType struct{} + +func (r *passwordResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { + return passwordSchemaV2(), nil +} + +func (r *passwordResourceType) NewResource(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &passwordResource{}, nil +} + +var ( + _ tfsdk.Resource = (*passwordResource)(nil) + _ tfsdk.ResourceWithImportState = (*passwordResource)(nil) + _ tfsdk.ResourceWithUpgradeState = (*passwordResource)(nil) +) + +type passwordResource struct{} + +func (r *passwordResource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan passwordModelV2 + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + params := random.StringParams{ + Length: plan.Length.Value, + Upper: plan.Upper.Value, + MinUpper: plan.MinUpper.Value, + Lower: plan.Lower.Value, + MinLower: plan.MinLower.Value, + Numeric: plan.Numeric.Value, + MinNumeric: plan.MinNumeric.Value, + Special: plan.Special.Value, + MinSpecial: plan.MinSpecial.Value, + OverrideSpecial: plan.OverrideSpecial.Value, + } + + result, err := random.CreateString(params) + if err != nil { + resp.Diagnostics.Append(diagnostics.RandomReadError(err.Error())...) + return + } + + state := passwordModelV2{ + ID: types.String{Value: "none"}, + Keepers: plan.Keepers, + Length: types.Int64{Value: plan.Length.Value}, + Special: types.Bool{Value: plan.Special.Value}, + Upper: types.Bool{Value: plan.Upper.Value}, + Lower: types.Bool{Value: plan.Lower.Value}, + Numeric: types.Bool{Value: plan.Numeric.Value}, + MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, + MinUpper: types.Int64{Value: plan.MinUpper.Value}, + MinLower: types.Int64{Value: plan.MinLower.Value}, + MinSpecial: types.Int64{Value: plan.MinSpecial.Value}, + OverrideSpecial: types.String{Value: plan.OverrideSpecial.Value}, + Result: types.String{Value: string(result)}, + } + + hash, err := generateHash(plan.Result.Value) + if err != nil { + resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...) + } + + state.BcryptHash = types.String{Value: hash} + + diags = resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. +func (r *passwordResource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +} + +// Update is intentionally left blank as all required and optional attributes force replacement of the resource +// through the RequiresReplace AttributePlanModifier. +func (r *passwordResource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { +} + +// Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the +// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). +func (r *passwordResource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +} + +func (r *passwordResource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + id := req.ID + + state := passwordModelV2{ + ID: types.String{Value: "none"}, + Result: types.String{Value: id}, + Length: types.Int64{Value: int64(len(id))}, + Special: types.Bool{Value: true}, + Upper: types.Bool{Value: true}, + Lower: types.Bool{Value: true}, + Numeric: types.Bool{Value: true}, + MinSpecial: types.Int64{Value: 0}, + MinUpper: types.Int64{Value: 0}, + MinLower: types.Int64{Value: 0}, + MinNumeric: types.Int64{Value: 0}, + } + + state.Keepers.ElemType = types.StringType + + hash, err := generateHash(id) + if err != nil { + resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...) + } + + state.BcryptHash = types.String{Value: hash} + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *passwordResource) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { + schemaV0 := passwordSchemaV0() + schemaV1 := passwordSchemaV1() + + return map[int64]tfsdk.ResourceStateUpgrader{ + 0: { + PriorSchema: &schemaV0, + StateUpgrader: upgradePasswordStateV0toV2, + }, + 1: { + PriorSchema: &schemaV1, + StateUpgrader: upgradePasswordStateV1toV2, + }, + } +} + +func upgradePasswordStateV0toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { + type modelV0 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Number types.Bool `tfsdk:"number"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Result types.String `tfsdk:"result"` + } + + var passwordDataV0 modelV0 + + resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV0)...) + if resp.Diagnostics.HasError() { + return + } + + passwordDataV2 := passwordModelV2{ + Keepers: passwordDataV0.Keepers, + Length: passwordDataV0.Length, + Special: passwordDataV0.Special, + Upper: passwordDataV0.Upper, + Lower: passwordDataV0.Lower, + Numeric: passwordDataV0.Number, + MinNumeric: passwordDataV0.MinNumeric, + MinLower: passwordDataV0.MinLower, + MinSpecial: passwordDataV0.MinSpecial, + OverrideSpecial: passwordDataV0.OverrideSpecial, + Result: passwordDataV0.Result, + ID: passwordDataV0.ID, + } + + hash, err := generateHash(passwordDataV2.Result.Value) + if err != nil { + resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...) + return + } + + passwordDataV2.BcryptHash.Value = hash + + diags := resp.State.Set(ctx, passwordDataV2) + resp.Diagnostics.Append(diags...) +} + +func upgradePasswordStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { + type modelV1 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Number types.Bool `tfsdk:"number"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Result types.String `tfsdk:"result"` + BcryptHash types.String `tfsdk:"bcrypt_hash"` + } + + var passwordDataV1 modelV1 + + resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV1)...) + if resp.Diagnostics.HasError() { + return + } + + passwordDataV2 := passwordModelV2{ + Keepers: passwordDataV1.Keepers, + Length: passwordDataV1.Length, + Special: passwordDataV1.Special, + Upper: passwordDataV1.Upper, + Lower: passwordDataV1.Lower, + Numeric: passwordDataV1.Number, + MinNumeric: passwordDataV1.MinNumeric, + MinLower: passwordDataV1.MinLower, + MinSpecial: passwordDataV1.MinSpecial, + OverrideSpecial: passwordDataV1.OverrideSpecial, + BcryptHash: passwordDataV1.BcryptHash, + Result: passwordDataV1.Result, + ID: passwordDataV1.ID, + } + + diags := resp.State.Set(ctx, passwordDataV2) + resp.Diagnostics.Append(diags...) +} + +func generateHash(toHash string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(toHash), bcrypt.DefaultCost) + + return string(hash), err +} + +func passwordSchemaV2() tfsdk.Schema { return tfsdk.Schema{ Version: 2, Description: "Identical to [random_string](string.html) with the exception that the result is " + @@ -173,7 +422,7 @@ func schemaV2() tfsdk.Schema { } } -func schemaV1() tfsdk.Schema { +func passwordSchemaV1() tfsdk.Schema { return tfsdk.Schema{ Version: 1, Description: "Identical to [random_string](string.html) with the exception that the result is " + @@ -334,7 +583,7 @@ func schemaV1() tfsdk.Schema { } } -func schemaV0() tfsdk.Schema { +func passwordSchemaV0() tfsdk.Schema { return tfsdk.Schema{ Description: "Identical to [random_string](string.html) with the exception that the result is " + "treated as sensitive and, thus, _not_ displayed in console output. Read more about sensitive " + @@ -484,3 +733,20 @@ func schemaV0() tfsdk.Schema { }, } } + +type passwordModelV2 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Numeric types.Bool `tfsdk:"numeric"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Result types.String `tfsdk:"result"` + BcryptHash types.String `tfsdk:"bcrypt_hash"` +} diff --git a/internal/provider/provider_resource_password_test.go b/internal/provider/resource_password_test.go similarity index 81% rename from internal/provider/provider_resource_password_test.go rename to internal/provider/resource_password_test.go index 91599ba1..11b4fae1 100644 --- a/internal/provider/provider_resource_password_test.go +++ b/internal/provider/resource_password_test.go @@ -1,12 +1,18 @@ package provider import ( + "context" "fmt" "regexp" "testing" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "golang.org/x/crypto/bcrypt" ) func TestAccResourcePassword(t *testing.T) { @@ -591,3 +597,131 @@ func TestAccResourcePassword_UpgradeFromVersion3_3_2(t *testing.T) { }, }) } + +func TestUpgradePasswordStateV0toV2(t *testing.T) { + raw := tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "none"), + "keepers": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), + "length": tftypes.NewValue(tftypes.Number, 16), + "lower": tftypes.NewValue(tftypes.Bool, true), + "min_lower": tftypes.NewValue(tftypes.Number, 0), + "min_numeric": tftypes.NewValue(tftypes.Number, 0), + "min_special": tftypes.NewValue(tftypes.Number, 0), + "min_upper": tftypes.NewValue(tftypes.Number, 0), + "number": tftypes.NewValue(tftypes.Bool, true), + "override_special": tftypes.NewValue(tftypes.String, "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"), + "result": tftypes.NewValue(tftypes.String, "DZy_3*tnonj%Q%Yx"), + "special": tftypes.NewValue(tftypes.Bool, true), + "upper": tftypes.NewValue(tftypes.Bool, true), + }) + + req := tfsdk.UpgradeResourceStateRequest{ + State: &tfsdk.State{ + Raw: raw, + Schema: passwordSchemaV0(), + }, + } + + resp := &tfsdk.UpgradeResourceStateResponse{ + State: tfsdk.State{ + Schema: passwordSchemaV2(), + }, + } + + upgradePasswordStateV0toV2(context.Background(), req, resp) + + expected := passwordModelV2{ + ID: types.String{Value: "none"}, + Keepers: types.Map{Null: true, ElemType: types.StringType}, + Length: types.Int64{Value: 16}, + Special: types.Bool{Value: true}, + Upper: types.Bool{Value: true}, + Lower: types.Bool{Value: true}, + Numeric: types.Bool{Value: true}, + MinNumeric: types.Int64{Value: 0}, + MinUpper: types.Int64{Value: 0}, + MinLower: types.Int64{Value: 0}, + MinSpecial: types.Int64{Value: 0}, + OverrideSpecial: types.String{Value: "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"}, + Result: types.String{Value: "DZy_3*tnonj%Q%Yx"}, + } + + actual := passwordModelV2{} + diags := resp.State.Get(context.Background(), &actual) + if diags.HasError() { + t.Errorf("error getting state: %v", diags) + } + + err := bcrypt.CompareHashAndPassword([]byte(actual.BcryptHash.Value), []byte(actual.Result.Value)) + if err != nil { + t.Errorf("unexpected bcrypt comparison error: %s", err) + } + + // Setting actual.BcryptHash to zero value to allow direct comparison of expected and actual. + actual.BcryptHash = types.String{} + + if !cmp.Equal(expected, actual) { + t.Errorf("expected: %+v, got: %+v", expected, actual) + } +} + +func TestUpgradePasswordStateV1toV2(t *testing.T) { + raw := tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "none"), + "keepers": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), + "length": tftypes.NewValue(tftypes.Number, 16), + "lower": tftypes.NewValue(tftypes.Bool, true), + "min_lower": tftypes.NewValue(tftypes.Number, 0), + "min_numeric": tftypes.NewValue(tftypes.Number, 0), + "min_special": tftypes.NewValue(tftypes.Number, 0), + "min_upper": tftypes.NewValue(tftypes.Number, 0), + "number": tftypes.NewValue(tftypes.Bool, true), + "override_special": tftypes.NewValue(tftypes.String, "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"), + "result": tftypes.NewValue(tftypes.String, "DZy_3*tnonj%Q%Yx"), + "special": tftypes.NewValue(tftypes.Bool, true), + "upper": tftypes.NewValue(tftypes.Bool, true), + "bcrypt_hash": tftypes.NewValue(tftypes.String, "bcrypt_hash"), + }) + + req := tfsdk.UpgradeResourceStateRequest{ + State: &tfsdk.State{ + Raw: raw, + Schema: passwordSchemaV1(), + }, + } + + resp := &tfsdk.UpgradeResourceStateResponse{ + State: tfsdk.State{ + Schema: passwordSchemaV2(), + }, + } + + upgradePasswordStateV1toV2(context.Background(), req, resp) + + expected := passwordModelV2{ + ID: types.String{Value: "none"}, + Keepers: types.Map{Null: true, ElemType: types.StringType}, + Length: types.Int64{Value: 16}, + Special: types.Bool{Value: true}, + Upper: types.Bool{Value: true}, + Lower: types.Bool{Value: true}, + Numeric: types.Bool{Value: true}, + MinNumeric: types.Int64{Value: 0}, + MinUpper: types.Int64{Value: 0}, + MinLower: types.Int64{Value: 0}, + MinSpecial: types.Int64{Value: 0}, + OverrideSpecial: types.String{Value: "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"}, + BcryptHash: types.String{Value: "bcrypt_hash"}, + Result: types.String{Value: "DZy_3*tnonj%Q%Yx"}, + } + + actual := passwordModelV2{} + diags := resp.State.Get(context.Background(), &actual) + if diags.HasError() { + t.Errorf("error getting state: %v", diags) + } + + if !cmp.Equal(expected, actual) { + t.Errorf("expected: %+v, got: %+v", expected, actual) + } +} diff --git a/internal/resources/pet/resource_pet.go b/internal/provider/resource_pet.go similarity index 79% rename from internal/resources/pet/resource_pet.go rename to internal/provider/resource_pet.go index 69740f0e..2e0ba2e7 100644 --- a/internal/resources/pet/resource_pet.go +++ b/internal/provider/resource_pet.go @@ -1,4 +1,4 @@ -package pet +package provider import ( "context" @@ -13,15 +13,11 @@ import ( "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers" ) -func NewResourceType() *resourceType { - return &resourceType{} -} - -var _ tfsdk.ResourceType = (*resourceType)(nil) +var _ tfsdk.ResourceType = (*petResourceType)(nil) -type resourceType struct{} +type petResourceType struct{} -func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { +func (r *petResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Description: "The resource `random_pet` generates random pet names that are intended to be used as " + "unique identifiers for other resources.\n" + @@ -76,21 +72,21 @@ func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostic }, nil } -func (r *resourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return &resource{}, nil +func (r *petResourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &petResource{}, nil } -var _ tfsdk.Resource = (*resource)(nil) +var _ tfsdk.Resource = (*petResource)(nil) -type resource struct{} +type petResource struct{} -func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { +func (r *petResource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { // This is necessary to ensure each call to petname is properly randomised: // the library uses `rand.Intn()` and does NOT seed `rand.Seed()` by default, // so this call takes care of that. petname.NonDeterministicMode() - var plan modelV0 + var plan petModelV0 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -104,7 +100,7 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, pet := strings.ToLower(petname.Generate(int(length), separator)) - pn := modelV0{ + pn := petModelV0{ Keepers: plan.Keepers, Length: types.Int64{Value: length}, Separator: types.String{Value: separator}, @@ -127,20 +123,20 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r *resource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +func (r *petResource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { } // Update is intentionally left blank as all required and optional attributes force replacement of the resource // through the RequiresReplace AttributePlanModifier. -func (r *resource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { +func (r *petResource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the // [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r *resource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +func (r *petResource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { } -type modelV0 struct { +type petModelV0 struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` Length types.Int64 `tfsdk:"length"` diff --git a/internal/provider/provider_resource_pet_test.go b/internal/provider/resource_pet_test.go similarity index 100% rename from internal/provider/provider_resource_pet_test.go rename to internal/provider/resource_pet_test.go diff --git a/internal/resources/shuffle/resource_shuffle.go b/internal/provider/resource_shuffle.go similarity index 82% rename from internal/resources/shuffle/resource_shuffle.go rename to internal/provider/resource_shuffle.go index fb90aa93..433cc0d1 100644 --- a/internal/resources/shuffle/resource_shuffle.go +++ b/internal/provider/resource_shuffle.go @@ -1,4 +1,4 @@ -package shuffle +package provider import ( "context" @@ -11,15 +11,11 @@ import ( "github.com/terraform-providers/terraform-provider-random/internal/random" ) -func NewResourceType() *resourceType { - return &resourceType{} -} - -var _ tfsdk.ResourceType = (*resourceType)(nil) +var _ tfsdk.ResourceType = (*shuffleResourceType)(nil) -type resourceType struct{} +type shuffleResourceType struct{} -func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { +func (r *shuffleResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Description: "The resource `random_shuffle` generates a random permutation of a list of strings " + "given as an argument.", @@ -85,16 +81,16 @@ func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostic }, nil } -func (r *resourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return &resource{}, nil +func (r *shuffleResourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &shuffleResource{}, nil } -var _ tfsdk.Resource = (*resource)(nil) +var _ tfsdk.Resource = (*shuffleResource)(nil) -type resource struct{} +type shuffleResource struct{} -func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan modelV0 +func (r *shuffleResource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan shuffleModelV0 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -129,7 +125,7 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, } } - s := modelV0{ + s := shuffleModelV0{ ID: types.String{Value: "-"}, Keepers: plan.Keepers, Input: plan.Input, @@ -161,20 +157,20 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r *resource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +func (r *shuffleResource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { } // Update is intentionally left blank as all required and optional attributes force replacement of the resource // through the RequiresReplace AttributePlanModifier. -func (r *resource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { +func (r *shuffleResource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the // [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r *resource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +func (r *shuffleResource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { } -type modelV0 struct { +type shuffleModelV0 struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` Seed types.String `tfsdk:"seed"` diff --git a/internal/provider/provider_resource_shuffle_test.go b/internal/provider/resource_shuffle_test.go similarity index 100% rename from internal/provider/provider_resource_shuffle_test.go rename to internal/provider/resource_shuffle_test.go diff --git a/internal/resources/stringresource/resource_string.go b/internal/provider/resource_string.go similarity index 92% rename from internal/resources/stringresource/resource_string.go rename to internal/provider/resource_string.go index c329e068..74028153 100644 --- a/internal/resources/stringresource/resource_string.go +++ b/internal/provider/resource_string.go @@ -1,4 +1,4 @@ -package stringresource +package provider import ( "context" @@ -15,15 +15,11 @@ import ( "github.com/terraform-providers/terraform-provider-random/internal/validators" ) -func NewResourceType() *resourceType { - return &resourceType{} -} - -var _ tfsdk.ResourceType = (*resourceType)(nil) +var _ tfsdk.ResourceType = (*stringResourceType)(nil) -type resourceType struct{} +type stringResourceType struct{} -func (r resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { +func (r stringResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Version: 2, Description: "The resource `random_string` generates a random permutation of alphanumeric " + @@ -179,20 +175,20 @@ func (r resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics }, nil } -func (r resourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return &resource{}, nil +func (r stringResourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &stringResource{}, nil } var ( - _ tfsdk.Resource = (*resource)(nil) - _ tfsdk.ResourceWithImportState = (*resource)(nil) - _ tfsdk.ResourceWithUpgradeState = (*resource)(nil) + _ tfsdk.Resource = (*stringResource)(nil) + _ tfsdk.ResourceWithImportState = (*stringResource)(nil) + _ tfsdk.ResourceWithUpgradeState = (*stringResource)(nil) ) -type resource struct{} +type stringResource struct{} -func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan modelV2 +func (r *stringResource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var plan stringModelV2 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -219,7 +215,7 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, return } - state := modelV2{ + state := stringModelV2{ ID: types.String{Value: string(result)}, Keepers: plan.Keepers, Length: types.Int64{Value: plan.Length.Value}, @@ -243,23 +239,23 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r *resource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +func (r *stringResource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { } // Update is intentionally left blank as all required and optional attributes force replacement of the resource // through the RequiresReplace AttributePlanModifier. -func (r *resource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { +func (r *stringResource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the // [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r *resource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +func (r *stringResource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { } -func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { +func (r *stringResource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { id := req.ID - state := modelV2{ + state := stringModelV2{ ID: types.String{Value: id}, Result: types.String{Value: id}, Length: types.Int64{Value: int64(len(id))}, @@ -282,7 +278,7 @@ func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStat } } -func (r *resource) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { +func (r *stringResource) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { schemaV1 := tfsdk.Schema{ Version: 1, Description: "The resource `random_string` generates a random permutation of alphanumeric " + @@ -469,7 +465,7 @@ func upgradeStringStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStat return } - stringDataV2 := modelV2{ + stringDataV2 := stringModelV2{ Keepers: stringDataV1.Keepers, Length: stringDataV1.Length, Special: stringDataV1.Special, @@ -488,7 +484,7 @@ func upgradeStringStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStat resp.Diagnostics.Append(diags...) } -type modelV2 struct { +type stringModelV2 struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` Length types.Int64 `tfsdk:"length"` diff --git a/internal/provider/provider_resource_string_test.go b/internal/provider/resource_string_test.go similarity index 100% rename from internal/provider/provider_resource_string_test.go rename to internal/provider/resource_string_test.go diff --git a/internal/resources/uuid/resource_uuid.go b/internal/provider/resource_uuid.go similarity index 74% rename from internal/resources/uuid/resource_uuid.go rename to internal/provider/resource_uuid.go index fe238a27..b03f1e04 100644 --- a/internal/resources/uuid/resource_uuid.go +++ b/internal/provider/resource_uuid.go @@ -1,4 +1,4 @@ -package uuid +package provider import ( "context" @@ -12,15 +12,11 @@ import ( "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" ) -func NewResourceType() *resourceType { - return &resourceType{} -} - -var _ tfsdk.ResourceType = (*resourceType)(nil) +var _ tfsdk.ResourceType = (*uuidResourceType)(nil) -type resourceType struct{} +type uuidResourceType struct{} -func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { +func (r *uuidResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Description: "The resource `random_uuid` generates random uuid string that is intended to be " + "used as unique identifiers for other resources.\n" + @@ -53,19 +49,19 @@ func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostic }, nil } -func (r resourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return &resource{}, nil +func (r uuidResourceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &uuidResource{}, nil } var ( - _ tfsdk.Resource = (*resource)(nil) - _ tfsdk.ResourceWithImportState = (*resource)(nil) + _ tfsdk.Resource = (*uuidResource)(nil) + _ tfsdk.ResourceWithImportState = (*uuidResource)(nil) ) -type resource struct { +type uuidResource struct { } -func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { +func (r *uuidResource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { result, err := uuid.GenerateUUID() if err != nil { resp.Diagnostics.AddError( @@ -77,7 +73,7 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, return } - var plan modelV0 + var plan uuidModelV0 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -85,7 +81,7 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, return } - u := &modelV0{ + u := &uuidModelV0{ ID: types.String{Value: result}, Result: types.String{Value: result}, Keepers: plan.Keepers, @@ -99,20 +95,20 @@ func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, } // Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r *resource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +func (r *uuidResource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { } // Update is intentionally left blank as all required and optional attributes force replacement of the resource // through the RequiresReplace AttributePlanModifier. -func (r *resource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { +func (r *uuidResource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the // [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r *resource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +func (r *uuidResource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { } -func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { +func (r *uuidResource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { bytes, err := uuid.ParseUUID(req.ID) if err != nil { resp.Diagnostics.AddError( @@ -135,7 +131,7 @@ func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStat return } - var state modelV0 + var state uuidModelV0 state.ID.Value = result state.Result.Value = result @@ -148,7 +144,7 @@ func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStat } } -type modelV0 struct { +type uuidModelV0 struct { ID types.String `tfsdk:"id"` Keepers types.Map `tfsdk:"keepers"` Result types.String `tfsdk:"result"` diff --git a/internal/provider/provider_resource_uuid_test.go b/internal/provider/resource_uuid_test.go similarity index 100% rename from internal/provider/provider_resource_uuid_test.go rename to internal/provider/resource_uuid_test.go diff --git a/internal/random/random.go b/internal/random/string.go similarity index 100% rename from internal/random/random.go rename to internal/random/string.go diff --git a/internal/resources/password/resource_password.go b/internal/resources/password/resource_password.go deleted file mode 100644 index 55fa930c..00000000 --- a/internal/resources/password/resource_password.go +++ /dev/null @@ -1,277 +0,0 @@ -package password - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "golang.org/x/crypto/bcrypt" - - "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" - "github.com/terraform-providers/terraform-provider-random/internal/random" -) - -var _ tfsdk.ResourceType = (*resourceType)(nil) - -func NewResourceType() *resourceType { - return &resourceType{} -} - -type resourceType struct{} - -func (r *resourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { - return schemaV2(), nil -} - -func (r *resourceType) NewResource(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return &resource{}, nil -} - -var ( - _ tfsdk.Resource = (*resource)(nil) - _ tfsdk.ResourceWithImportState = (*resource)(nil) - _ tfsdk.ResourceWithUpgradeState = (*resource)(nil) -) - -type resource struct{} - -func (r *resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - var plan modelV2 - - diags := req.Plan.Get(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - params := random.StringParams{ - Length: plan.Length.Value, - Upper: plan.Upper.Value, - MinUpper: plan.MinUpper.Value, - Lower: plan.Lower.Value, - MinLower: plan.MinLower.Value, - Numeric: plan.Numeric.Value, - MinNumeric: plan.MinNumeric.Value, - Special: plan.Special.Value, - MinSpecial: plan.MinSpecial.Value, - OverrideSpecial: plan.OverrideSpecial.Value, - } - - result, err := random.CreateString(params) - if err != nil { - resp.Diagnostics.Append(diagnostics.RandomReadError(err.Error())...) - return - } - - state := modelV2{ - ID: types.String{Value: "none"}, - Keepers: plan.Keepers, - Length: types.Int64{Value: plan.Length.Value}, - Special: types.Bool{Value: plan.Special.Value}, - Upper: types.Bool{Value: plan.Upper.Value}, - Lower: types.Bool{Value: plan.Lower.Value}, - Numeric: types.Bool{Value: plan.Numeric.Value}, - MinNumeric: types.Int64{Value: plan.MinNumeric.Value}, - MinUpper: types.Int64{Value: plan.MinUpper.Value}, - MinLower: types.Int64{Value: plan.MinLower.Value}, - MinSpecial: types.Int64{Value: plan.MinSpecial.Value}, - OverrideSpecial: types.String{Value: plan.OverrideSpecial.Value}, - Result: types.String{Value: string(result)}, - } - - hash, err := generateHash(plan.Result.Value) - if err != nil { - resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...) - } - - state.BcryptHash = types.String{Value: hash} - - diags = resp.State.Set(ctx, state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. -func (r *resource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { -} - -// Update is intentionally left blank as all required and optional attributes force replacement of the resource -// through the RequiresReplace AttributePlanModifier. -func (r *resource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { -} - -// Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the -// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). -func (r *resource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { -} - -func (r *resource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - id := req.ID - - state := modelV2{ - ID: types.String{Value: "none"}, - Result: types.String{Value: id}, - Length: types.Int64{Value: int64(len(id))}, - Special: types.Bool{Value: true}, - Upper: types.Bool{Value: true}, - Lower: types.Bool{Value: true}, - Numeric: types.Bool{Value: true}, - MinSpecial: types.Int64{Value: 0}, - MinUpper: types.Int64{Value: 0}, - MinLower: types.Int64{Value: 0}, - MinNumeric: types.Int64{Value: 0}, - } - - state.Keepers.ElemType = types.StringType - - hash, err := generateHash(id) - if err != nil { - resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...) - } - - state.BcryptHash = types.String{Value: hash} - - diags := resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -func (r *resource) UpgradeState(context.Context) map[int64]tfsdk.ResourceStateUpgrader { - schemaV0 := schemaV0() - schemaV1 := schemaV1() - - return map[int64]tfsdk.ResourceStateUpgrader{ - 0: { - PriorSchema: &schemaV0, - StateUpgrader: upgradePasswordStateV0toV2, - }, - 1: { - PriorSchema: &schemaV1, - StateUpgrader: upgradePasswordStateV1toV2, - }, - } -} - -func upgradePasswordStateV0toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { - type modelV0 struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Length types.Int64 `tfsdk:"length"` - Special types.Bool `tfsdk:"special"` - Upper types.Bool `tfsdk:"upper"` - Lower types.Bool `tfsdk:"lower"` - Number types.Bool `tfsdk:"number"` - MinNumeric types.Int64 `tfsdk:"min_numeric"` - MinUpper types.Int64 `tfsdk:"min_upper"` - MinLower types.Int64 `tfsdk:"min_lower"` - MinSpecial types.Int64 `tfsdk:"min_special"` - OverrideSpecial types.String `tfsdk:"override_special"` - Result types.String `tfsdk:"result"` - } - - var passwordDataV0 modelV0 - - resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV0)...) - if resp.Diagnostics.HasError() { - return - } - - passwordDataV2 := modelV2{ - Keepers: passwordDataV0.Keepers, - Length: passwordDataV0.Length, - Special: passwordDataV0.Special, - Upper: passwordDataV0.Upper, - Lower: passwordDataV0.Lower, - Numeric: passwordDataV0.Number, - MinNumeric: passwordDataV0.MinNumeric, - MinLower: passwordDataV0.MinLower, - MinSpecial: passwordDataV0.MinSpecial, - OverrideSpecial: passwordDataV0.OverrideSpecial, - Result: passwordDataV0.Result, - ID: passwordDataV0.ID, - } - - hash, err := generateHash(passwordDataV2.Result.Value) - if err != nil { - resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...) - return - } - - passwordDataV2.BcryptHash.Value = hash - - diags := resp.State.Set(ctx, passwordDataV2) - resp.Diagnostics.Append(diags...) -} - -func upgradePasswordStateV1toV2(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { - type modelV1 struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Length types.Int64 `tfsdk:"length"` - Special types.Bool `tfsdk:"special"` - Upper types.Bool `tfsdk:"upper"` - Lower types.Bool `tfsdk:"lower"` - Number types.Bool `tfsdk:"number"` - MinNumeric types.Int64 `tfsdk:"min_numeric"` - MinUpper types.Int64 `tfsdk:"min_upper"` - MinLower types.Int64 `tfsdk:"min_lower"` - MinSpecial types.Int64 `tfsdk:"min_special"` - OverrideSpecial types.String `tfsdk:"override_special"` - Result types.String `tfsdk:"result"` - BcryptHash types.String `tfsdk:"bcrypt_hash"` - } - - var passwordDataV1 modelV1 - - resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV1)...) - if resp.Diagnostics.HasError() { - return - } - - passwordDataV2 := modelV2{ - Keepers: passwordDataV1.Keepers, - Length: passwordDataV1.Length, - Special: passwordDataV1.Special, - Upper: passwordDataV1.Upper, - Lower: passwordDataV1.Lower, - Numeric: passwordDataV1.Number, - MinNumeric: passwordDataV1.MinNumeric, - MinLower: passwordDataV1.MinLower, - MinSpecial: passwordDataV1.MinSpecial, - OverrideSpecial: passwordDataV1.OverrideSpecial, - BcryptHash: passwordDataV1.BcryptHash, - Result: passwordDataV1.Result, - ID: passwordDataV1.ID, - } - - diags := resp.State.Set(ctx, passwordDataV2) - resp.Diagnostics.Append(diags...) -} - -func generateHash(toHash string) (string, error) { - hash, err := bcrypt.GenerateFromPassword([]byte(toHash), bcrypt.DefaultCost) - - return string(hash), err -} - -type modelV2 struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Length types.Int64 `tfsdk:"length"` - Special types.Bool `tfsdk:"special"` - Upper types.Bool `tfsdk:"upper"` - Lower types.Bool `tfsdk:"lower"` - Numeric types.Bool `tfsdk:"numeric"` - MinNumeric types.Int64 `tfsdk:"min_numeric"` - MinUpper types.Int64 `tfsdk:"min_upper"` - MinLower types.Int64 `tfsdk:"min_lower"` - MinSpecial types.Int64 `tfsdk:"min_special"` - OverrideSpecial types.String `tfsdk:"override_special"` - Result types.String `tfsdk:"result"` - BcryptHash types.String `tfsdk:"bcrypt_hash"` -} diff --git a/internal/resources/password/resource_password_test.go b/internal/resources/password/resource_password_test.go deleted file mode 100644 index e8637b41..00000000 --- a/internal/resources/password/resource_password_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package password - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" - "golang.org/x/crypto/bcrypt" -) - -func TestUpgradePasswordStateV0toV2(t *testing.T) { - raw := tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "none"), - "keepers": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), - "length": tftypes.NewValue(tftypes.Number, 16), - "lower": tftypes.NewValue(tftypes.Bool, true), - "min_lower": tftypes.NewValue(tftypes.Number, 0), - "min_numeric": tftypes.NewValue(tftypes.Number, 0), - "min_special": tftypes.NewValue(tftypes.Number, 0), - "min_upper": tftypes.NewValue(tftypes.Number, 0), - "number": tftypes.NewValue(tftypes.Bool, true), - "override_special": tftypes.NewValue(tftypes.String, "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"), - "result": tftypes.NewValue(tftypes.String, "DZy_3*tnonj%Q%Yx"), - "special": tftypes.NewValue(tftypes.Bool, true), - "upper": tftypes.NewValue(tftypes.Bool, true), - }) - - req := tfsdk.UpgradeResourceStateRequest{ - State: &tfsdk.State{ - Raw: raw, - Schema: schemaV0(), - }, - } - - resp := &tfsdk.UpgradeResourceStateResponse{ - State: tfsdk.State{ - Schema: schemaV2(), - }, - } - - upgradePasswordStateV0toV2(context.Background(), req, resp) - - expected := modelV2{ - ID: types.String{Value: "none"}, - Keepers: types.Map{Null: true, ElemType: types.StringType}, - Length: types.Int64{Value: 16}, - Special: types.Bool{Value: true}, - Upper: types.Bool{Value: true}, - Lower: types.Bool{Value: true}, - Numeric: types.Bool{Value: true}, - MinNumeric: types.Int64{Value: 0}, - MinUpper: types.Int64{Value: 0}, - MinLower: types.Int64{Value: 0}, - MinSpecial: types.Int64{Value: 0}, - OverrideSpecial: types.String{Value: "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"}, - Result: types.String{Value: "DZy_3*tnonj%Q%Yx"}, - } - - actual := modelV2{} - diags := resp.State.Get(context.Background(), &actual) - if diags.HasError() { - t.Errorf("error getting state: %v", diags) - } - - err := bcrypt.CompareHashAndPassword([]byte(actual.BcryptHash.Value), []byte(actual.Result.Value)) - if err != nil { - t.Errorf("unexpected bcrypt comparison error: %s", err) - } - - // Setting actual.BcryptHash to zero value to allow direct comparison of expected and actual. - actual.BcryptHash = types.String{} - - if !cmp.Equal(expected, actual) { - t.Errorf("expected: %+v, got: %+v", expected, actual) - } -} - -func TestUpgradePasswordStateV1toV2(t *testing.T) { - raw := tftypes.NewValue(tftypes.Object{}, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "none"), - "keepers": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), - "length": tftypes.NewValue(tftypes.Number, 16), - "lower": tftypes.NewValue(tftypes.Bool, true), - "min_lower": tftypes.NewValue(tftypes.Number, 0), - "min_numeric": tftypes.NewValue(tftypes.Number, 0), - "min_special": tftypes.NewValue(tftypes.Number, 0), - "min_upper": tftypes.NewValue(tftypes.Number, 0), - "number": tftypes.NewValue(tftypes.Bool, true), - "override_special": tftypes.NewValue(tftypes.String, "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"), - "result": tftypes.NewValue(tftypes.String, "DZy_3*tnonj%Q%Yx"), - "special": tftypes.NewValue(tftypes.Bool, true), - "upper": tftypes.NewValue(tftypes.Bool, true), - "bcrypt_hash": tftypes.NewValue(tftypes.String, "bcrypt_hash"), - }) - - req := tfsdk.UpgradeResourceStateRequest{ - State: &tfsdk.State{ - Raw: raw, - Schema: schemaV1(), - }, - } - - resp := &tfsdk.UpgradeResourceStateResponse{ - State: tfsdk.State{ - Schema: schemaV2(), - }, - } - - upgradePasswordStateV1toV2(context.Background(), req, resp) - - expected := modelV2{ - ID: types.String{Value: "none"}, - Keepers: types.Map{Null: true, ElemType: types.StringType}, - Length: types.Int64{Value: 16}, - Special: types.Bool{Value: true}, - Upper: types.Bool{Value: true}, - Lower: types.Bool{Value: true}, - Numeric: types.Bool{Value: true}, - MinNumeric: types.Int64{Value: 0}, - MinUpper: types.Int64{Value: 0}, - MinLower: types.Int64{Value: 0}, - MinSpecial: types.Int64{Value: 0}, - OverrideSpecial: types.String{Value: "!#$%\u0026*()-_=+[]{}\u003c\u003e:?"}, - BcryptHash: types.String{Value: "bcrypt_hash"}, - Result: types.String{Value: "DZy_3*tnonj%Q%Yx"}, - } - - actual := modelV2{} - diags := resp.State.Get(context.Background(), &actual) - if diags.HasError() { - t.Errorf("error getting state: %v", diags) - } - - if !cmp.Equal(expected, actual) { - t.Errorf("expected: %+v, got: %+v", expected, actual) - } -} From a53a4509eb9016a5d226bde8492aef851fbf4047 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 30 Jun 2022 10:54:28 +0100 Subject: [PATCH 95/96] Updating tests to use NewProtocol6WithError (#177) --- internal/provider/provider_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index a36b3726..a4e0a945 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -10,7 +10,7 @@ import ( func protoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { return map[string]func() (tfprotov6.ProviderServer, error){ "random": func() (tfprotov6.ProviderServer, error) { - return providerserver.NewProtocol6(New())(), nil + return providerserver.NewProtocol6WithError(New())() }, } } From dd331c53658f9c5a17d27c4c64a526ac21619371 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 1 Jul 2022 17:01:53 +0100 Subject: [PATCH 96/96] Updates following code review (#177) --- internal/diagnostics/diagnostics.go | 13 +++++++++++++ internal/provider/provider_test.go | 4 +--- internal/provider/resource_id.go | 7 +------ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/internal/diagnostics/diagnostics.go b/internal/diagnostics/diagnostics.go index 35ab13da..2930a009 100644 --- a/internal/diagnostics/diagnostics.go +++ b/internal/diagnostics/diagnostics.go @@ -33,3 +33,16 @@ func HashGenerationError(errMsg string) diag.Diagnostics { return diags } + +func RandomnessGenerationError(errMsg string) diag.Diagnostics { + var diags diag.Diagnostics + + diags.AddError( + "Randomness Generation Error", + "While attempting to generate a random value for this resource, an insufficient number of random bytes were generated.\n\n"+ + RetryMsg+ + fmt.Sprintf("Original Error: %s", errMsg), + ) + + return diags +} diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index a4e0a945..020fa511 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -9,9 +9,7 @@ import ( //nolint:unparam func protoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { return map[string]func() (tfprotov6.ProviderServer, error){ - "random": func() (tfprotov6.ProviderServer, error) { - return providerserver.NewProtocol6WithError(New())() - }, + "random": providerserver.NewProtocol6WithError(New()), } } diff --git a/internal/provider/resource_id.go b/internal/provider/resource_id.go index 2459d1c3..4492c6cc 100644 --- a/internal/provider/resource_id.go +++ b/internal/provider/resource_id.go @@ -122,12 +122,7 @@ func (r *idResource) Create(ctx context.Context, req tfsdk.CreateResourceRequest n, err := rand.Reader.Read(bytes) if int64(n) != byteLength { - resp.Diagnostics.AddError( - "Randomness Generation Error", - "While attempting to generate a random value for this resource, an insufficient number of random bytes were generated. Most commonly, this is a hardware or operating system issue where their random number generator does not provide a sufficient randomness source. Otherwise, it may represent an issue in the randomness handling of the provider.\n\n"+ - "Retry the Terraform operation. If the error still occurs or happens regularly, please contact the provider developer with hardware and operating system information.\n\n"+ - fmt.Sprintf("Original Error: %s", err), - ) + resp.Diagnostics.Append(diagnostics.RandomnessGenerationError(err.Error())...) return } if err != nil {