From e439c2a18dd41018a336046fd374571fbccc6f0c Mon Sep 17 00:00:00 2001 From: Ansgar Mertens Date: Tue, 15 Apr 2025 13:17:56 +0200 Subject: [PATCH 1/9] support importing by resource identity for sdkv2 resource --- .../sdkv2provider/resource_user_identity.go | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/internal/sdkv2provider/resource_user_identity.go b/internal/sdkv2provider/resource_user_identity.go index 3fb59b6..8491684 100644 --- a/internal/sdkv2provider/resource_user_identity.go +++ b/internal/sdkv2provider/resource_user_identity.go @@ -6,6 +6,7 @@ package sdkv2 import ( "context" + "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -46,6 +47,41 @@ func resourceUserIdentity() *schema.Resource { } }, }, + + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { + if rd.Id() != "" { + return []*schema.ResourceData{rd}, nil // just return the resource data, since the string id is used + } + + identity, err := rd.Identity() + if err != nil { + return nil, err + } + + emailRaw, ok := identity.GetOk("email") + if !ok { + return nil, fmt.Errorf("error getting email from identity: %w", err) + } + + email, ok := emailRaw.(string) + if !ok { + return nil, fmt.Errorf("error converting email to string") + } + + if email == "" { + return nil, fmt.Errorf("email cannot be empty") + } + + err = rd.Set("email", email) + rd.SetId(email) // TODO: document that this is still require with resource identity + if err != nil { + return nil, fmt.Errorf("error setting email: %w", err) + } + + return []*schema.ResourceData{rd}, nil + }, + }, } } From 6b77bce1cfbb0e249f710e7edc5bc2620ebc2286 Mon Sep 17 00:00:00 2001 From: Ansgar Mertens Date: Thu, 17 Apr 2025 16:02:26 +0200 Subject: [PATCH 2/9] add steps to test import from identity --- internal/sdkv2provider/resource_user_identity_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/sdkv2provider/resource_user_identity_test.go b/internal/sdkv2provider/resource_user_identity_test.go index 0e132bc..5658803 100644 --- a/internal/sdkv2provider/resource_user_identity_test.go +++ b/internal/sdkv2provider/resource_user_identity_test.go @@ -31,6 +31,14 @@ func testAccResourceUserIdentity(t *testing.T) resource.TestCase { }), }, }, + { + RefreshState: true, + }, + { + ResourceName: "corner_user_identity.foo", + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + }, }, } } From 1cf238ed12554fee06b2ab0b04d9650855fd0196 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Fri, 18 Apr 2025 15:34:04 -0400 Subject: [PATCH 3/9] updated the version --- internal/sdkv2provider/resource_user_identity_test.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/internal/sdkv2provider/resource_user_identity_test.go b/internal/sdkv2provider/resource_user_identity_test.go index 5658803..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,9 +28,6 @@ func testAccResourceUserIdentity(t *testing.T) resource.TestCase { }), }, }, - { - RefreshState: true, - }, { ResourceName: "corner_user_identity.foo", ImportState: true, From 15f5185dcb4adcea718031e028b2caeb7fe3e203 Mon Sep 17 00:00:00 2001 From: Ansgar Mertens Date: Wed, 30 Apr 2025 13:32:32 +0200 Subject: [PATCH 4/9] tested resource identity upgrade by manually changing identity version --- .../sdkv2provider/resource_user_identity.go | 83 ++++++++++++++++--- .../resource_user_identity_test.go | 3 +- 2 files changed, 73 insertions(+), 13 deletions(-) diff --git a/internal/sdkv2provider/resource_user_identity.go b/internal/sdkv2provider/resource_user_identity.go index 8491684..6a0264a 100644 --- a/internal/sdkv2provider/resource_user_identity.go +++ b/internal/sdkv2provider/resource_user_identity.go @@ -7,7 +7,9 @@ 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" @@ -37,18 +39,52 @@ func resourceUserIdentity() *schema.Resource { }, Identity: &schema.ResourceIdentity{ - Version: 1, + Version: 2, SchemaFunc: func() map[string]*schema.Schema { return map[string]*schema.Schema{ - "email": { + // previous version of the identity (version 1) + // "email": { + // Type: schema.TypeString, + // RequiredForImport: true, + // }, + // The second version of the identity splits the email into local part and domain + // (for no good reason, just for one of testing of upgraders) + "local_part": { + Type: schema.TypeString, + RequiredForImport: true, + }, + "domain": { Type: schema.TypeString, RequiredForImport: true, }, } }, + IdentityUpgraders: []schema.IdentityUpgrader{ + { + Version: 1, + 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 + }, + }, + }, }, Importer: &schema.ResourceImporter{ + // for state version 1, this could have been used: + // StateContext: schema.ImportStatePassthroughWithIdentity("email"), StateContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { if rd.Id() != "" { return []*schema.ResourceData{rd}, nil // just return the resource data, since the string id is used @@ -59,24 +95,38 @@ func resourceUserIdentity() *schema.Resource { return nil, err } - emailRaw, ok := identity.GetOk("email") + localPartRaw, ok := identity.GetOk("local_part") if !ok { - return nil, fmt.Errorf("error getting email from identity: %w", err) + return nil, fmt.Errorf("error getting local_part from identity: %w", err) } - email, ok := emailRaw.(string) + localPart, ok := localPartRaw.(string) if !ok { - return nil, fmt.Errorf("error converting email to string") + return nil, fmt.Errorf("error converting local_part to string") + } + + if localPart == "" { + return nil, fmt.Errorf("local_part cannot be empty") } - if email == "" { - return nil, fmt.Errorf("email cannot be empty") + domainRaw, ok := identity.GetOk("domain") + if !ok { + return nil, fmt.Errorf("error getting domain from identity: %w", err) + } + domain, ok := domainRaw.(string) + if !ok { + return nil, fmt.Errorf("error converting domain to string") + } + if domain == "" { + return nil, fmt.Errorf("domain cannot be empty") } - err = rd.Set("email", email) - rd.SetId(email) // TODO: document that this is still require with resource identity + email := fmt.Sprintf("%s@%s", localPart, domain) + + rd.SetId(email) + err = rd.Set("email", email) // required for import because else it requires a replace if err != nil { - return nil, fmt.Errorf("error setting email: %w", err) + return nil, fmt.Errorf("error setting email in resource data: %w", err) } return []*schema.ResourceData{rd}, nil @@ -130,7 +180,16 @@ func resourceUserIdentityRead(ctx context.Context, d *schema.ResourceData, meta if err != nil { return diag.FromErr(err) } - err = identity.Set("email", email) + 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) } diff --git a/internal/sdkv2provider/resource_user_identity_test.go b/internal/sdkv2provider/resource_user_identity_test.go index f5a8936..7dba75f 100644 --- a/internal/sdkv2provider/resource_user_identity_test.go +++ b/internal/sdkv2provider/resource_user_identity_test.go @@ -24,7 +24,8 @@ func testAccResourceUserIdentity(t *testing.T) resource.TestCase { Config: configResourceBasicIdentity, ConfigStateChecks: []statecheck.StateCheck{ statecheck.ExpectIdentity("corner_user_identity.foo", map[string]knownvalue.Check{ - "email": knownvalue.StringExact("ford@prefect.co"), + "local_part": knownvalue.StringExact("ford"), + "domain": knownvalue.StringExact("prefect.co"), }), }, }, From 98c6806c7c3fc7e9b4db672729433d863cd2a2cc Mon Sep 17 00:00:00 2001 From: Ansgar Mertens Date: Fri, 2 May 2025 13:48:38 +0200 Subject: [PATCH 5/9] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/sdkv2provider/resource_user_identity.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/sdkv2provider/resource_user_identity.go b/internal/sdkv2provider/resource_user_identity.go index 6a0264a..b60bc7e 100644 --- a/internal/sdkv2provider/resource_user_identity.go +++ b/internal/sdkv2provider/resource_user_identity.go @@ -97,7 +97,7 @@ func resourceUserIdentity() *schema.Resource { localPartRaw, ok := identity.GetOk("local_part") if !ok { - return nil, fmt.Errorf("error getting local_part from identity: %w", err) + return nil, fmt.Errorf("error getting local_part from identity") } localPart, ok := localPartRaw.(string) @@ -111,7 +111,7 @@ func resourceUserIdentity() *schema.Resource { domainRaw, ok := identity.GetOk("domain") if !ok { - return nil, fmt.Errorf("error getting domain from identity: %w", err) + return nil, fmt.Errorf("error getting domain from identity: domain value is missing or invalid") } domain, ok := domainRaw.(string) if !ok { From 22cec498f8640afe2c1d7a13f9b1d72323f5ae0d Mon Sep 17 00:00:00 2001 From: Ansgar Mertens Date: Wed, 7 May 2025 11:03:27 +0200 Subject: [PATCH 6/9] split identity schema upgrade test into it's own resource and test case --- internal/sdkv2provider/provider.go | 8 + internal/sdkv2provider/provider_test.go | 1 + .../sdkv2provider/resource_user_identity.go | 100 ++------- .../resource_user_identity_test.go | 3 +- .../resource_user_identity_upgrade.go | 201 ++++++++++++++++++ .../resource_user_identity_upgrade_test.go | 60 ++++++ 6 files changed, 283 insertions(+), 90 deletions(-) create mode 100644 internal/sdkv2provider/resource_user_identity_upgrade.go create mode 100644 internal/sdkv2provider/resource_user_identity_upgrade_test.go 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 b60bc7e..089caf9 100644 --- a/internal/sdkv2provider/resource_user_identity.go +++ b/internal/sdkv2provider/resource_user_identity.go @@ -6,8 +6,6 @@ package sdkv2 import ( "context" - "fmt" - "strings" "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -39,21 +37,10 @@ func resourceUserIdentity() *schema.Resource { }, Identity: &schema.ResourceIdentity{ - Version: 2, + Version: 1, SchemaFunc: func() map[string]*schema.Schema { return map[string]*schema.Schema{ - // previous version of the identity (version 1) - // "email": { - // Type: schema.TypeString, - // RequiredForImport: true, - // }, - // The second version of the identity splits the email into local part and domain - // (for no good reason, just for one of testing of upgraders) - "local_part": { - Type: schema.TypeString, - RequiredForImport: true, - }, - "domain": { + "email": { Type: schema.TypeString, RequiredForImport: true, }, @@ -67,78 +54,21 @@ func resourceUserIdentity() *schema.Resource { "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 - }, }, }, }, Importer: &schema.ResourceImporter{ - // for state version 1, this could have been used: - // StateContext: schema.ImportStatePassthroughWithIdentity("email"), - StateContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { - if rd.Id() != "" { - return []*schema.ResourceData{rd}, nil // just return the resource data, since the string id is used - } - - identity, err := rd.Identity() - if err != nil { - return nil, err - } - - localPartRaw, ok := identity.GetOk("local_part") - if !ok { - return nil, fmt.Errorf("error getting local_part from identity") - } - - localPart, ok := localPartRaw.(string) - if !ok { - return nil, fmt.Errorf("error converting local_part to string") - } - - if localPart == "" { - return nil, fmt.Errorf("local_part cannot be empty") - } - - domainRaw, ok := identity.GetOk("domain") - if !ok { - return nil, fmt.Errorf("error getting domain from identity: domain value is missing or invalid") - } - domain, ok := domainRaw.(string) - if !ok { - return nil, fmt.Errorf("error converting domain to string") - } - if domain == "" { - return nil, fmt.Errorf("domain cannot be empty") - } - - email := fmt.Sprintf("%s@%s", localPart, domain) - - rd.SetId(email) - err = rd.Set("email", email) // required for import because else it requires a replace - if err != nil { - return nil, fmt.Errorf("error setting email in resource data: %w", err) - } - - return []*schema.ResourceData{rd}, nil - }, + 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), } @@ -147,6 +77,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) } @@ -154,7 +85,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 { @@ -165,31 +96,24 @@ func resourceUserIdentityRead(ctx context.Context, d *schema.ResourceData, meta return nil } - d.SetId(email) - - err = d.Set("name", p.Name) + err = d.Set("email", email) if err != nil { return diag.FromErr(err) } - err = d.Set("age", p.Age) + err = d.Set("name", p.Name) if err != nil { return diag.FromErr(err) } - - identity, err := d.Identity() + err = d.Set("age", p.Age) if err != nil { return diag.FromErr(err) } - 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]) + identity, err := d.Identity() if err != nil { return diag.FromErr(err) } - err = identity.Set("domain", parts[1]) + err = identity.Set("email", email) 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 7dba75f..f5a8936 100644 --- a/internal/sdkv2provider/resource_user_identity_test.go +++ b/internal/sdkv2provider/resource_user_identity_test.go @@ -24,8 +24,7 @@ func testAccResourceUserIdentity(t *testing.T) resource.TestCase { Config: configResourceBasicIdentity, ConfigStateChecks: []statecheck.StateCheck{ statecheck.ExpectIdentity("corner_user_identity.foo", map[string]knownvalue.Check{ - "local_part": knownvalue.StringExact("ford"), - "domain": knownvalue.StringExact("prefect.co"), + "email": knownvalue.StringExact("ford@prefect.co"), }), }, }, diff --git a/internal/sdkv2provider/resource_user_identity_upgrade.go b/internal/sdkv2provider/resource_user_identity_upgrade.go new file mode 100644 index 0000000..bbb5d1c --- /dev/null +++ b/internal/sdkv2provider/resource_user_identity_upgrade.go @@ -0,0 +1,201 @@ +// 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: 1, + 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) + } + + if version == 0 { + err = identity.Set("email", email) + if err != nil { + return diag.FromErr(err) + } + } else if version == 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) + } + } + + 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"), + }), + }, + }, + }, + } +} From 699bf428e43e2a1a3211a50cae42f5b7a30e1811 Mon Sep 17 00:00:00 2001 From: Ansgar Mertens Date: Wed, 7 May 2025 15:20:19 +0200 Subject: [PATCH 7/9] fix identity upgrader version --- internal/sdkv2provider/resource_user_identity_upgrade.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/sdkv2provider/resource_user_identity_upgrade.go b/internal/sdkv2provider/resource_user_identity_upgrade.go index bbb5d1c..ededa4b 100644 --- a/internal/sdkv2provider/resource_user_identity_upgrade.go +++ b/internal/sdkv2provider/resource_user_identity_upgrade.go @@ -65,7 +65,7 @@ func resourceUserIdentityUpgrade(version int) *schema.Resource { }, IdentityUpgraders: []schema.IdentityUpgrader{ { - Version: 1, + Version: 0, Type: tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ "email": tftypes.String, From 7b30e43aa368779e0d6ba6e60c049457f738903e Mon Sep 17 00:00:00 2001 From: Ansgar Mertens Date: Thu, 8 May 2025 09:00:02 +0200 Subject: [PATCH 8/9] update to latest main of plugin-sdk, drop unused upgrade function, tidy go mod --- go.mod | 10 +++++----- go.sum | 20 +++++++++---------- .../sdkv2provider/resource_user_identity.go | 11 ---------- 3 files changed, 15 insertions(+), 26 deletions(-) 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/resource_user_identity.go b/internal/sdkv2provider/resource_user_identity.go index 089caf9..e709603 100644 --- a/internal/sdkv2provider/resource_user_identity.go +++ b/internal/sdkv2provider/resource_user_identity.go @@ -7,7 +7,6 @@ package sdkv2 import ( "context" - "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" @@ -46,16 +45,6 @@ func resourceUserIdentity() *schema.Resource { }, } }, - IdentityUpgraders: []schema.IdentityUpgrader{ - { - Version: 1, - Type: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "email": tftypes.String, - }, - }, - }, - }, }, Importer: &schema.ResourceImporter{ From bf7d463b044c19b466fb1949501174b1411fec06 Mon Sep 17 00:00:00 2001 From: Ansgar Mertens Date: Thu, 8 May 2025 10:53:36 +0200 Subject: [PATCH 9/9] use tagged switch to please linter --- internal/sdkv2provider/resource_user_identity_upgrade.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/sdkv2provider/resource_user_identity_upgrade.go b/internal/sdkv2provider/resource_user_identity_upgrade.go index ededa4b..d179268 100644 --- a/internal/sdkv2provider/resource_user_identity_upgrade.go +++ b/internal/sdkv2provider/resource_user_identity_upgrade.go @@ -137,12 +137,13 @@ func resourceUserIdentityUpgradeRead(version int) schema.ReadContextFunc { return diag.FromErr(err) } - if version == 0 { + switch version { + case 0: err = identity.Set("email", email) if err != nil { return diag.FromErr(err) } - } else if version == 1 { + case 1: parts := strings.Split(email, "@") if len(parts) != 2 { return diag.FromErr(fmt.Errorf("invalid email format: %s", email)) @@ -156,6 +157,8 @@ func resourceUserIdentityUpgradeRead(version int) schema.ReadContextFunc { if err != nil { return diag.FromErr(err) } + default: + return diag.FromErr(fmt.Errorf("unknown version: %d", version)) } return nil