diff --git a/go.mod b/go.mod index d161d7d..4a00016 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 github.com/hashicorp/terraform-plugin-go v0.27.0-alpha.1.0.20250325210248-fa8d1fe4306b github.com/hashicorp/terraform-plugin-mux v0.19.0-alpha.1 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0-beta.1 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0-beta.1.0.20250506133545-a8969de4a3fb github.com/hashicorp/terraform-plugin-testing v1.13.0-beta.1 github.com/zclconf/go-cty v1.16.2 ) @@ -57,12 +57,12 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - golang.org/x/crypto v0.37.0 // indirect + golang.org/x/crypto v0.38.0 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/sync v0.13.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.24.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect diff --git a/go.sum b/go.sum index cbc5677..c1542e0 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,8 @@ github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9T github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/hashicorp/terraform-plugin-mux v0.19.0-alpha.1 h1:WCzSBsp719WKEV/+j+4/o742paM0twYm7B84y7x8pOM= github.com/hashicorp/terraform-plugin-mux v0.19.0-alpha.1/go.mod h1:iKph9LFBiD4a33AJLgqg7IKSVg2kdlYvx0IRd+ys3Ig= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0-beta.1 h1:Ia0jU/ZLzyfReSg4TMHq6ffYGCNCREzpSMBqswM71a0= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0-beta.1/go.mod h1:fVJWDD6/eNOK0aG55CK5g8vTv3Ph9UD/dZztPPvFDgw= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0-beta.1.0.20250506133545-a8969de4a3fb h1:VrU0z5vk50b0488uU6gfm4YR5CgbqssleG5yU13coak= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0-beta.1.0.20250506133545-a8969de4a3fb/go.mod h1:7cnES2/7I2Kt/LT0b0tqQPgOrxAbKRtO/2hdye5VbEE= github.com/hashicorp/terraform-plugin-testing v1.13.0-beta.1 h1:YpdITO9pgpSVSBoxL9DqiOG/2/rUQtcnP6encYAtKd0= github.com/hashicorp/terraform-plugin-testing v1.13.0-beta.1/go.mod h1:2fJBV6Eim03FqxyaPbPW2qZadDbfD1+yj/tRnDHBjjI= github.com/hashicorp/terraform-registry-address v0.2.4 h1:JXu/zHB2Ymg/TGVCRu10XqNa4Sh2bWcqCNyKWjnCPJA= @@ -187,8 +187,8 @@ go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= @@ -201,8 +201,8 @@ golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -215,16 +215,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/internal/sdkv2provider/provider.go b/internal/sdkv2provider/provider.go index 4f27a4f..f5c2152 100644 --- a/internal/sdkv2provider/provider.go +++ b/internal/sdkv2provider/provider.go @@ -28,6 +28,7 @@ func New() *schema.Provider { ResourcesMap: map[string]*schema.Resource{ "corner_user": resourceUser(), "corner_user_identity": resourceUserIdentity(), + "corner_user_identity_upgrade": resourceUserIdentityUpgrade(0), "corner_writeonly": resourceWriteOnly(), "corner_writeonly_import": resourceWriteOnlyImport(), "corner_writeonly_upgrade": resourceWriteOnlyUpgrade(0), @@ -63,3 +64,10 @@ func NewWithUpgradeVersion(version int) *schema.Provider { return p } + +func NewWithIdentityUpgradeVersion(version int) *schema.Provider { + p := New() + p.ResourcesMap["corner_user_identity_upgrade"] = resourceUserIdentityUpgrade(version) + + return p +} diff --git a/internal/sdkv2provider/provider_test.go b/internal/sdkv2provider/provider_test.go index f6c8e81..c40df8a 100644 --- a/internal/sdkv2provider/provider_test.go +++ b/internal/sdkv2provider/provider_test.go @@ -46,6 +46,7 @@ func TestAccTests(t *testing.T) { var TestCases = map[string]func(*testing.T) resource.TestCase{ "corner_user": testAccResourceUser, "corner_user_identity": testAccResourceUserIdentity, + "corner_user_identity_upgrade": testAccResourceUserIdentityUpgrade, "corner_regions": testAccDataSourceRegions, "corner_bigint_data": testAccDataSourceBigint, "corner_bigint": testAccResourceBigint, diff --git a/internal/sdkv2provider/resource_user_identity.go b/internal/sdkv2provider/resource_user_identity.go index 3fb59b6..e709603 100644 --- a/internal/sdkv2provider/resource_user_identity.go +++ b/internal/sdkv2provider/resource_user_identity.go @@ -46,13 +46,18 @@ func resourceUserIdentity() *schema.Resource { } }, }, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughWithIdentity("email"), + }, } } func resourceUserIdentityCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*backend.Client) + email := d.Get("email").(string) newUser := &backend.User{ - Email: d.Get("email").(string), + Email: email, Name: d.Get("name").(string), Age: d.Get("age").(int), } @@ -61,6 +66,7 @@ func resourceUserIdentityCreate(ctx context.Context, d *schema.ResourceData, met if err != nil { return diag.FromErr(err) } + d.SetId(email) return resourceUserIdentityRead(ctx, d, meta) } @@ -68,7 +74,7 @@ func resourceUserIdentityCreate(ctx context.Context, d *schema.ResourceData, met func resourceUserIdentityRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*backend.Client) - email := d.Get("email").(string) + email := d.Id() p, err := client.ReadUser(email) if err != nil { @@ -79,8 +85,10 @@ func resourceUserIdentityRead(ctx context.Context, d *schema.ResourceData, meta return nil } - d.SetId(email) - + err = d.Set("email", email) + if err != nil { + return diag.FromErr(err) + } err = d.Set("name", p.Name) if err != nil { return diag.FromErr(err) diff --git a/internal/sdkv2provider/resource_user_identity_test.go b/internal/sdkv2provider/resource_user_identity_test.go index 0e132bc..f5a8936 100644 --- a/internal/sdkv2provider/resource_user_identity_test.go +++ b/internal/sdkv2provider/resource_user_identity_test.go @@ -6,7 +6,6 @@ package sdkv2 import ( "testing" - "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/knownvalue" "github.com/hashicorp/terraform-plugin-testing/statecheck" @@ -16,10 +15,8 @@ import ( func testAccResourceUserIdentity(t *testing.T) resource.TestCase { return resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - // Latest alpha version that this JSON data is available in - // https://github.com/hashicorp/terraform/releases/tag/v1.12.0-alpha20250319 TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(version.Must(version.NewVersion("1.12.0-alpha20250319"))), + tfversion.SkipBelow(tfversion.Version1_12_0), }, Providers: testAccProviders, Steps: []resource.TestStep{ @@ -31,6 +28,11 @@ func testAccResourceUserIdentity(t *testing.T) resource.TestCase { }), }, }, + { + ResourceName: "corner_user_identity.foo", + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + }, }, } } diff --git a/internal/sdkv2provider/resource_user_identity_upgrade.go b/internal/sdkv2provider/resource_user_identity_upgrade.go new file mode 100644 index 0000000..d179268 --- /dev/null +++ b/internal/sdkv2provider/resource_user_identity_upgrade.go @@ -0,0 +1,204 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//nolint:forcetypeassert // Test SDK provider +package sdkv2 + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-corner/internal/backend" +) + +func resourceUserIdentityUpgrade(version int) *schema.Resource { + return &schema.Resource{ + CreateContext: resourceUserIdentityUpgradeCreate(version), + ReadContext: resourceUserIdentityUpgradeRead(version), + UpdateContext: resourceUserIdentityUpgradeUpdate(version), + DeleteContext: resourceUserIdentityUpgradeDelete(version), + + Schema: map[string]*schema.Schema{ + "email": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "age": { + Type: schema.TypeInt, + Required: true, + }, + }, + + Identity: &schema.ResourceIdentity{ + Version: int64(version), + SchemaFunc: func() map[string]*schema.Schema { + if version == 0 { + return map[string]*schema.Schema{ + "email": { + Type: schema.TypeString, + RequiredForImport: true, + }, + } + } + if version == 1 { + return map[string]*schema.Schema{ + "local_part": { + Type: schema.TypeString, + RequiredForImport: true, + }, + "domain": { + Type: schema.TypeString, + RequiredForImport: true, + }, + } + } + panic(fmt.Sprintf("unknown version %d", version)) + }, + IdentityUpgraders: []schema.IdentityUpgrader{ + { + Version: 0, + Type: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "email": tftypes.String, + }, + }, + Upgrade: func(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + email := rawState["email"].(string) + parts := strings.Split(email, "@") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid email format: %s", email) + } + return map[string]interface{}{ + "local_part": parts[0], + "domain": parts[1], + }, nil + }, + }, + }, + }, + } +} + +func resourceUserIdentityUpgradeCreate(version int) schema.CreateContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*backend.Client) + newUser := &backend.User{ + Email: d.Get("email").(string), + Name: d.Get("name").(string), + Age: d.Get("age").(int), + } + + err := client.CreateUser(newUser) + if err != nil { + return diag.FromErr(err) + } + + return resourceUserIdentityUpgradeRead(version)(ctx, d, meta) + } +} + +func resourceUserIdentityUpgradeRead(version int) schema.ReadContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*backend.Client) + + email := d.Get("email").(string) + + p, err := client.ReadUser(email) + if err != nil { + return diag.FromErr(err) + } + + if p == nil { + return nil + } + + d.SetId(email) + + err = d.Set("name", p.Name) + if err != nil { + return diag.FromErr(err) + } + err = d.Set("age", p.Age) + if err != nil { + return diag.FromErr(err) + } + + identity, err := d.Identity() + if err != nil { + return diag.FromErr(err) + } + + switch version { + case 0: + err = identity.Set("email", email) + if err != nil { + return diag.FromErr(err) + } + case 1: + parts := strings.Split(email, "@") + if len(parts) != 2 { + return diag.FromErr(fmt.Errorf("invalid email format: %s", email)) + } + + err = identity.Set("local_part", parts[0]) + if err != nil { + return diag.FromErr(err) + } + err = identity.Set("domain", parts[1]) + if err != nil { + return diag.FromErr(err) + } + default: + return diag.FromErr(fmt.Errorf("unknown version: %d", version)) + } + + return nil + } +} + +func resourceUserIdentityUpgradeUpdate(version int) schema.UpdateContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*backend.Client) + + user := &backend.User{ + Email: d.Get("email").(string), + Name: d.Get("name").(string), + Age: d.Get("age").(int), + } + + err := client.UpdateUser(user) + if err != nil { + return diag.FromErr(err) + } + + return nil + } +} + +func resourceUserIdentityUpgradeDelete(version int) schema.DeleteContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*backend.Client) + + user := &backend.User{ + Email: d.Get("email").(string), + Name: d.Get("name").(string), + Age: d.Get("age").(int), + } + + err := client.DeleteUser(user) + if err != nil { + return diag.FromErr(err) + } + + return nil + } +} diff --git a/internal/sdkv2provider/resource_user_identity_upgrade_test.go b/internal/sdkv2provider/resource_user_identity_upgrade_test.go new file mode 100644 index 0000000..e4d396a --- /dev/null +++ b/internal/sdkv2provider/resource_user_identity_upgrade_test.go @@ -0,0 +1,60 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package sdkv2 + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func testAccResourceUserIdentityUpgrade(t *testing.T) resource.TestCase { + return resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "corner": func() (tfprotov5.ProviderServer, error) { //nolint + return NewWithIdentityUpgradeVersion(0).GRPCProvider(), nil + }, + }, + Config: `resource "corner_user_identity_upgrade" "foo" { + email = "ford@prefect.co" + name = "Ford Prefect" + age = 200 + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity("corner_user_identity_upgrade.foo", map[string]knownvalue.Check{ + "email": knownvalue.StringExact("ford@prefect.co"), + }), + }, + }, + { + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "corner": func() (tfprotov5.ProviderServer, error) { //nolint + return NewWithIdentityUpgradeVersion(1).GRPCProvider(), nil + }, + }, + Config: `resource "corner_user_identity_upgrade" "foo" { + email = "ford@prefect.co" + name = "Ford Prefect" + age = 200 + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity("corner_user_identity_upgrade.foo", map[string]knownvalue.Check{ + "local_part": knownvalue.StringExact("ford"), + "domain": knownvalue.StringExact("prefect.co"), + }), + }, + }, + }, + } +}