From cc6e1c64705801aae08c5f1394aed9f089b6d8bf Mon Sep 17 00:00:00 2001 From: Yacine FODIL Date: Thu, 5 Jun 2025 15:56:30 +0200 Subject: [PATCH 1/3] feat(vpc): add support for ResourceIdentity --- internal/services/vpc/acl.go | 53 ++++++++++++++++++- internal/services/vpc/private_network.go | 64 ++++++++++++++++++++++- internal/services/vpc/route.go | 53 ++++++++++++++++++- internal/services/vpc/vpc.go | 65 +++++++++++++++++++++++- 4 files changed, 231 insertions(+), 4 deletions(-) diff --git a/internal/services/vpc/acl.go b/internal/services/vpc/acl.go index 25c220b642..1fe261e397 100644 --- a/internal/services/vpc/acl.go +++ b/internal/services/vpc/acl.go @@ -2,6 +2,7 @@ package vpc import ( "context" + "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -10,6 +11,7 @@ import ( "github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors" "github.com/scaleway/terraform-provider-scaleway/v2/internal/locality" "github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/meta" "github.com/scaleway/terraform-provider-scaleway/v2/internal/verify" ) @@ -20,7 +22,56 @@ func ResourceACL() *schema.Resource { UpdateContext: ResourceVPCACLUpdate, DeleteContext: ResourceVPCACLDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: func( + ctx context.Context, + d *schema.ResourceData, + m interface{}, + ) ([]*schema.ResourceData, error) { + // If importing by ID (e.g. "fr-par/8cef…"), we just set the ID field to state, allowing the read to fill in the rest of the data + if d.Id() != "" { + return []*schema.ResourceData{d}, nil + } + + // Otherwise, we're importing by identity “identity = { id = ..., region = ... }” + identity, err := d.Identity() + if err != nil { + return nil, fmt.Errorf("error retrieving identity: %w", err) + } + + rawID := identity.Get("id").(string) + regionVal := identity.Get("region").(string) + if regionVal == "" { + region, err := meta.ExtractRegion(d, m) + if err != nil { + return nil, fmt.Errorf("identity.region was not set") + + } + regionVal = region.String() + } + + localizedID := fmt.Sprintf("%s/%s", regionVal, rawID) + + d.SetId(localizedID) + + return []*schema.ResourceData{d}, nil + }, + }, + Identity: &schema.ResourceIdentity{ + Version: 0, + SchemaFunc: func() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + RequiredForImport: true, + Description: "The ACL ID (e.g. `11111111-1111-1111-1111-111111111111`)", + }, + "region": { + Type: schema.TypeString, + OptionalForImport: true, + Description: "The region of the VPC. If omitted during import, defaults from provider", + }, + } + }, }, SchemaVersion: 0, Schema: map[string]*schema.Schema{ diff --git a/internal/services/vpc/private_network.go b/internal/services/vpc/private_network.go index b4b3e7dfe9..8847a12635 100644 --- a/internal/services/vpc/private_network.go +++ b/internal/services/vpc/private_network.go @@ -2,6 +2,7 @@ package vpc import ( "context" + "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" @@ -25,7 +26,56 @@ func ResourcePrivateNetwork() *schema.Resource { UpdateContext: ResourceVPCPrivateNetworkUpdate, DeleteContext: ResourceVPCPrivateNetworkDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: func( + ctx context.Context, + d *schema.ResourceData, + m interface{}, + ) ([]*schema.ResourceData, error) { + // If importing by ID (e.g. "fr-par/8cef…"), we just set the ID field to state, allowing the read to fill in the rest of the data + if d.Id() != "" { + return []*schema.ResourceData{d}, nil + } + + // Otherwise, we're importing by identity “identity = { id = ..., region = ... }” + identity, err := d.Identity() + if err != nil { + return nil, fmt.Errorf("error retrieving identity: %w", err) + } + + rawID := identity.Get("id").(string) + regionVal := identity.Get("region").(string) + if regionVal == "" { + region, err := meta.ExtractRegion(d, m) + if err != nil { + return nil, fmt.Errorf("identity.region was not set") + + } + regionVal = region.String() + } + + localizedID := fmt.Sprintf("%s/%s", regionVal, rawID) + + d.SetId(localizedID) + + return []*schema.ResourceData{d}, nil + }, + }, + Identity: &schema.ResourceIdentity{ + Version: 0, + SchemaFunc: func() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + RequiredForImport: true, + Description: "The Private Network ID (e.g. `11111111-1111-1111-1111-111111111111`)", + }, + "region": { + Type: schema.TypeString, + OptionalForImport: true, + Description: "The region of the VPC. If omitted during import, defaults from provider", + }, + } + }, }, SchemaVersion: 1, StateUpgraders: []schema.StateUpgrader{ @@ -266,6 +316,18 @@ func ResourceVPCPrivateNetworkRead(ctx context.Context, d *schema.ResourceData, _ = d.Set("ipv4_subnet", ipv4Subnet) _ = d.Set("ipv6_subnets", ipv6Subnets) + identity, err := d.Identity() + if err != nil { + return diag.FromErr(err) + } + + if err = identity.Set("id", pn.ID); err != nil { + return diag.FromErr(err) + } + if err = identity.Set("region", region); err != nil { + return diag.FromErr(err) + } + return nil } diff --git a/internal/services/vpc/route.go b/internal/services/vpc/route.go index 342ee1b494..c7d8d511f7 100644 --- a/internal/services/vpc/route.go +++ b/internal/services/vpc/route.go @@ -2,6 +2,7 @@ package vpc import ( "context" + "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -11,6 +12,7 @@ import ( "github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors" "github.com/scaleway/terraform-provider-scaleway/v2/internal/locality" "github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/meta" "github.com/scaleway/terraform-provider-scaleway/v2/internal/types" ) @@ -21,7 +23,56 @@ func ResourceRoute() *schema.Resource { UpdateContext: ResourceRouteUpdate, DeleteContext: ResourceRouteDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: func( + ctx context.Context, + d *schema.ResourceData, + m interface{}, + ) ([]*schema.ResourceData, error) { + // If importing by ID (e.g. "fr-par/8cef…"), we just set the ID field to state, allowing the read to fill in the rest of the data + if d.Id() != "" { + return []*schema.ResourceData{d}, nil + } + + // Otherwise, we're importing by identity “identity = { id = ..., region = ... }” + identity, err := d.Identity() + if err != nil { + return nil, fmt.Errorf("error retrieving identity: %w", err) + } + + rawID := identity.Get("id").(string) + regionVal := identity.Get("region").(string) + if regionVal == "" { + region, err := meta.ExtractRegion(d, m) + if err != nil { + return nil, fmt.Errorf("identity.region was not set") + + } + regionVal = region.String() + } + + localizedID := fmt.Sprintf("%s/%s", regionVal, rawID) + + d.SetId(localizedID) + + return []*schema.ResourceData{d}, nil + }, + }, + Identity: &schema.ResourceIdentity{ + Version: 0, + SchemaFunc: func() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + RequiredForImport: true, + Description: "The Route ID (e.g. `11111111-1111-1111-1111-111111111111`)", + }, + "region": { + Type: schema.TypeString, + OptionalForImport: true, + Description: "The region of the VPC. If omitted during import, defaults from provider", + }, + } + }, }, SchemaVersion: 0, Schema: map[string]*schema.Schema{ diff --git a/internal/services/vpc/vpc.go b/internal/services/vpc/vpc.go index 2b98fdb89f..a765d16709 100644 --- a/internal/services/vpc/vpc.go +++ b/internal/services/vpc/vpc.go @@ -3,6 +3,7 @@ package vpc import ( "context" "errors" + "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -10,6 +11,7 @@ import ( "github.com/scaleway/scaleway-sdk-go/scw" "github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors" "github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/meta" "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account" "github.com/scaleway/terraform-provider-scaleway/v2/internal/types" ) @@ -21,7 +23,56 @@ func ResourceVPC() *schema.Resource { UpdateContext: ResourceVPCUpdate, DeleteContext: ResourceVPCDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: func( + ctx context.Context, + d *schema.ResourceData, + m interface{}, + ) ([]*schema.ResourceData, error) { + // If importing by ID (e.g. "fr-par/8cef…"), we just set the ID field to state, allowing the read to fill in the rest of the data + if d.Id() != "" { + return []*schema.ResourceData{d}, nil + } + + // Otherwise, we're importing by identity “identity = { id = ..., region = ... }” + identity, err := d.Identity() + if err != nil { + return nil, fmt.Errorf("error retrieving identity: %w", err) + } + + rawID := identity.Get("id").(string) + regionVal := identity.Get("region").(string) + if regionVal == "" { + region, err := meta.ExtractRegion(d, m) + if err != nil { + return nil, fmt.Errorf("identity.region was not set") + + } + regionVal = region.String() + } + + localizedID := fmt.Sprintf("%s/%s", regionVal, rawID) + + d.SetId(localizedID) + + return []*schema.ResourceData{d}, nil + }, + }, + Identity: &schema.ResourceIdentity{ + Version: 0, + SchemaFunc: func() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + RequiredForImport: true, + Description: "The VPC ID (e.g. `11111111-1111-1111-1111-111111111111`)", + }, + "region": { + Type: schema.TypeString, + OptionalForImport: true, + Description: "The region of the VPC. If omitted during import, defaults from provider", + }, + } + }, }, SchemaVersion: 0, Schema: map[string]*schema.Schema{ @@ -131,6 +182,18 @@ func ResourceVPCRead(ctx context.Context, d *schema.ResourceData, m interface{}) _ = d.Set("tags", res.Tags) } + identity, err := d.Identity() + if err != nil { + return diag.FromErr(err) + } + + if err = identity.Set("id", res.ID); err != nil { + return diag.FromErr(err) + } + if err = identity.Set("region", region); err != nil { + return diag.FromErr(err) + } + return nil } From 92231f181b1f6435e6053bdb12261cdbfd2bb88e Mon Sep 17 00:00:00 2001 From: Yacine FODIL Date: Thu, 5 Jun 2025 16:06:09 +0200 Subject: [PATCH 2/3] lint --- internal/services/vpc/acl.go | 5 +++-- internal/services/vpc/private_network.go | 5 +++-- internal/services/vpc/route.go | 5 +++-- internal/services/vpc/vpc.go | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/internal/services/vpc/acl.go b/internal/services/vpc/acl.go index 1fe261e397..b6421b5679 100644 --- a/internal/services/vpc/acl.go +++ b/internal/services/vpc/acl.go @@ -2,6 +2,7 @@ package vpc import ( "context" + "errors" "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -39,12 +40,12 @@ func ResourceACL() *schema.Resource { } rawID := identity.Get("id").(string) + regionVal := identity.Get("region").(string) if regionVal == "" { region, err := meta.ExtractRegion(d, m) if err != nil { - return nil, fmt.Errorf("identity.region was not set") - + return nil, errors.New("identity.region was not set") } regionVal = region.String() } diff --git a/internal/services/vpc/private_network.go b/internal/services/vpc/private_network.go index 8847a12635..4f7b0c8d41 100644 --- a/internal/services/vpc/private_network.go +++ b/internal/services/vpc/private_network.go @@ -2,6 +2,7 @@ package vpc import ( "context" + "errors" "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -43,12 +44,12 @@ func ResourcePrivateNetwork() *schema.Resource { } rawID := identity.Get("id").(string) + regionVal := identity.Get("region").(string) if regionVal == "" { region, err := meta.ExtractRegion(d, m) if err != nil { - return nil, fmt.Errorf("identity.region was not set") - + return nil, errors.New("identity.region was not set") } regionVal = region.String() } diff --git a/internal/services/vpc/route.go b/internal/services/vpc/route.go index c7d8d511f7..80edecfdec 100644 --- a/internal/services/vpc/route.go +++ b/internal/services/vpc/route.go @@ -2,6 +2,7 @@ package vpc import ( "context" + "errors" "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -40,12 +41,12 @@ func ResourceRoute() *schema.Resource { } rawID := identity.Get("id").(string) + regionVal := identity.Get("region").(string) if regionVal == "" { region, err := meta.ExtractRegion(d, m) if err != nil { - return nil, fmt.Errorf("identity.region was not set") - + return nil, errors.New("identity.region was not set") } regionVal = region.String() } diff --git a/internal/services/vpc/vpc.go b/internal/services/vpc/vpc.go index a765d16709..b6098ea90b 100644 --- a/internal/services/vpc/vpc.go +++ b/internal/services/vpc/vpc.go @@ -40,12 +40,12 @@ func ResourceVPC() *schema.Resource { } rawID := identity.Get("id").(string) + regionVal := identity.Get("region").(string) if regionVal == "" { region, err := meta.ExtractRegion(d, m) if err != nil { - return nil, fmt.Errorf("identity.region was not set") - + return nil, errors.New("identity.region was not set") } regionVal = region.String() } From 7abc829e28ca2271262c2fb72fa8f8ac321daa4a Mon Sep 17 00:00:00 2001 From: Yacine FODIL Date: Tue, 10 Jun 2025 17:24:03 +0200 Subject: [PATCH 3/3] lint --- internal/services/vpc/acl.go | 1 + internal/services/vpc/private_network.go | 1 + internal/services/vpc/route.go | 1 + internal/services/vpc/vpc.go | 1 + 4 files changed, 4 insertions(+) diff --git a/internal/services/vpc/acl.go b/internal/services/vpc/acl.go index b6421b5679..e0ef4828cb 100644 --- a/internal/services/vpc/acl.go +++ b/internal/services/vpc/acl.go @@ -47,6 +47,7 @@ func ResourceACL() *schema.Resource { if err != nil { return nil, errors.New("identity.region was not set") } + regionVal = region.String() } diff --git a/internal/services/vpc/private_network.go b/internal/services/vpc/private_network.go index 4f7b0c8d41..0aaa8b46e2 100644 --- a/internal/services/vpc/private_network.go +++ b/internal/services/vpc/private_network.go @@ -51,6 +51,7 @@ func ResourcePrivateNetwork() *schema.Resource { if err != nil { return nil, errors.New("identity.region was not set") } + regionVal = region.String() } diff --git a/internal/services/vpc/route.go b/internal/services/vpc/route.go index 80edecfdec..d72044bf95 100644 --- a/internal/services/vpc/route.go +++ b/internal/services/vpc/route.go @@ -48,6 +48,7 @@ func ResourceRoute() *schema.Resource { if err != nil { return nil, errors.New("identity.region was not set") } + regionVal = region.String() } diff --git a/internal/services/vpc/vpc.go b/internal/services/vpc/vpc.go index b6098ea90b..54e24fd7b5 100644 --- a/internal/services/vpc/vpc.go +++ b/internal/services/vpc/vpc.go @@ -47,6 +47,7 @@ func ResourceVPC() *schema.Resource { if err != nil { return nil, errors.New("identity.region was not set") } + regionVal = region.String() }