Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions internal/identity/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ func DefaultRegional() *schema.ResourceIdentity {
})
}

func FlatIdentity(key, description string) *schema.ResourceIdentity {
return WrapSchemaMap(map[string]*schema.Schema{
key: {
Type: schema.TypeString,
Description: description,
RequiredForImport: true,
},
})
}

func WrapSchemaMap(m map[string]*schema.Schema) *schema.ResourceIdentity {
return &schema.ResourceIdentity{
SchemaFunc: func() map[string]*schema.Schema {
Expand Down
41 changes: 25 additions & 16 deletions internal/services/iam/api_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/dsf"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/identity"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/types"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/verify"
Expand All @@ -25,6 +26,7 @@ func ResourceAPIKey() *schema.Resource {
},
SchemaVersion: 0,
SchemaFunc: apiKeySchema,
Identity: identity.FlatIdentity("id", "Access Key"),
}
}

Expand Down Expand Up @@ -109,15 +111,18 @@ func resourceIamAPIKeyCreate(ctx context.Context, d *schema.ResourceData, m any)

_ = d.Set("secret_key", res.SecretKey)

d.SetId(res.AccessKey)
err = identity.SetFlatIdentity(d, "id", res.AccessKey)
if err != nil {
return diag.FromErr(err)
}

return resourceIamAPIKeyRead(ctx, d, m)
}

func resourceIamAPIKeyRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
api := NewAPI(m)

res, err := api.GetAPIKey(&iam.GetAPIKeyRequest{
apiKey, err := api.GetAPIKey(&iam.GetAPIKeyRequest{
AccessKey: d.Id(),
}, scw.WithContext(ctx))
if err != nil {
Expand All @@ -130,25 +135,29 @@ func resourceIamAPIKeyRead(ctx context.Context, d *schema.ResourceData, m any) d
return diag.FromErr(err)
}

_ = d.Set("description", res.Description)
_ = d.Set("created_at", types.FlattenTime(res.CreatedAt))
_ = d.Set("updated_at", types.FlattenTime(res.UpdatedAt))
_ = d.Set("expires_at", types.FlattenTime(res.ExpiresAt))
_ = d.Set("access_key", res.AccessKey)
setAPIKeyState(d, apiKey)

Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResourceAPIKey now declares an Identity schema, but the Read path doesn't set it. To keep identity data consistent after refresh/import, set it from the API response (e.g., identity.SetFlatIdentity(d, "id", apiKey.AccessKey)) and handle any error.

Suggested change
if err := identity.SetFlatIdentity(d, "id", apiKey.AccessKey); err != nil {
return diag.FromErr(err)
}

Copilot uses AI. Check for mistakes.
if res.ApplicationID != nil {
_ = d.Set("application_id", res.ApplicationID)
}
return nil
}

func setAPIKeyState(d *schema.ResourceData, apiKey *iam.APIKey) {
_ = d.Set("description", apiKey.Description)
_ = d.Set("created_at", types.FlattenTime(apiKey.CreatedAt))
_ = d.Set("updated_at", types.FlattenTime(apiKey.UpdatedAt))
_ = d.Set("expires_at", types.FlattenTime(apiKey.ExpiresAt))
_ = d.Set("access_key", apiKey.AccessKey)

if res.UserID != nil {
_ = d.Set("user_id", res.UserID)
if apiKey.ApplicationID != nil {
_ = d.Set("application_id", apiKey.ApplicationID)
}

_ = d.Set("editable", res.Editable)
_ = d.Set("creation_ip", res.CreationIP)
_ = d.Set("default_project_id", res.DefaultProjectID)
if apiKey.UserID != nil {
_ = d.Set("user_id", apiKey.UserID)
}

return nil
_ = d.Set("editable", apiKey.Editable)
_ = d.Set("creation_ip", apiKey.CreationIP)
_ = d.Set("default_project_id", apiKey.DefaultProjectID)
}

func resourceIamAPIKeyUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
Expand Down
12 changes: 9 additions & 3 deletions internal/services/iam/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/identity"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/types"
)
Expand All @@ -23,6 +24,7 @@ func ResourceApplication() *schema.Resource {
},
SchemaVersion: 0,
SchemaFunc: applicationSchema,
Identity: identity.FlatIdentity("id", "Application UUID"),
}
}

Expand Down Expand Up @@ -79,7 +81,7 @@ func resourceIamApplicationCreate(ctx context.Context, d *schema.ResourceData, m
return diag.FromErr(err)
}

d.SetId(app.ID)
err = identity.SetFlatIdentity(d, "id", app.ID)
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

identity.SetFlatIdentity can return an error (e.g., if identity support isn't available for this resource data). The returned err is currently ignored, which can leave d.Id() unset and cause the subsequent Read call to query with an empty ID. Handle the error and return diagnostics if it occurs.

Suggested change
err = identity.SetFlatIdentity(d, "id", app.ID)
if err := identity.SetFlatIdentity(d, "id", app.ID); err != nil {
return diag.FromErr(err)
}

Copilot uses AI. Check for mistakes.

return resourceIamApplicationRead(ctx, d, m)
}
Expand All @@ -100,15 +102,19 @@ func resourceIamApplicationRead(ctx context.Context, d *schema.ResourceData, m a
return diag.FromErr(err)
}

setApplicationState(d, app)

return nil
Comment on lines 103 to +107
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This resource declares an Identity schema, but the Read path never populates it. Consider calling identity.SetFlatIdentity(d, "id", app.ID) (and handling the error) so imported/refreshed state keeps the identity fields consistent.

Copilot uses AI. Check for mistakes.
}

func setApplicationState(d *schema.ResourceData, app *iam.Application) {
_ = d.Set("name", app.Name)
_ = d.Set("description", app.Description)
_ = d.Set("created_at", types.FlattenTime(app.CreatedAt))
_ = d.Set("updated_at", types.FlattenTime(app.UpdatedAt))
_ = d.Set("organization_id", app.OrganizationID)
_ = d.Set("editable", app.Editable)
_ = d.Set("tags", types.FlattenSliceString(app.Tags))

return nil
}

func resourceIamApplicationUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
Expand Down
5 changes: 5 additions & 0 deletions internal/services/iam/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ func TestAccApplication_Basic(t *testing.T) {
resource.TestCheckResourceAttr("scaleway_iam_application.main", "tags.#", "0"),
),
},
{
ResourceName: "scaleway_iam_application.main",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
Expand Down
10 changes: 8 additions & 2 deletions internal/services/iam/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/identity"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/types"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/verify"
Expand All @@ -24,6 +25,7 @@ func ResourceGroup() *schema.Resource {
},
SchemaVersion: 0,
SchemaFunc: groupSchema,
Identity: identity.FlatIdentity("id", "Group UUID"),
}
Comment on lines 27 to 29
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResourceGroup declares an Identity schema, but the implementation still uses d.SetId(...) and does not populate the identity fields (in Create/Read). To actually support identity-based import/state, switch to identity.SetFlatIdentity(d, "id", group.ID) (and similarly in Read) and handle the returned error.

Copilot uses AI. Check for mistakes.
}

Expand Down Expand Up @@ -135,6 +137,12 @@ func resourceIamGroupRead(ctx context.Context, d *schema.ResourceData, m any) di
return diag.FromErr(err)
}

setGroupState(d, group)

return nil
}

func setGroupState(d *schema.ResourceData, group *iam.Group) {
_ = d.Set("name", group.Name)
_ = d.Set("description", group.Description)
_ = d.Set("created_at", types.FlattenTime(group.CreatedAt))
Expand All @@ -146,8 +154,6 @@ func resourceIamGroupRead(ctx context.Context, d *schema.ResourceData, m any) di
_ = d.Set("user_ids", group.UserIDs)
_ = d.Set("application_ids", group.ApplicationIDs)
}

return nil
}

func resourceIamGroupUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
Expand Down
20 changes: 20 additions & 0 deletions internal/services/iam/group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ func TestAccGroup_Basic(t *testing.T) {
resource.TestCheckResourceAttr("scaleway_iam_group.main_basic", "tags.#", "0"),
),
},
{
ResourceName: "scaleway_iam_group.main_basic",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
Expand Down Expand Up @@ -198,6 +203,11 @@ func TestAccGroup_Applications(t *testing.T) {
resource.TestCheckNoResourceAttr("scaleway_iam_group.main_app", "application_ids.0"),
),
},
{
ResourceName: "scaleway_iam_group.main_app",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
Expand Down Expand Up @@ -290,6 +300,11 @@ func TestAccGroup_Users(t *testing.T) {
resource.TestCheckNoResourceAttr("scaleway_iam_group.main_user", "user_ids.0"),
),
},
{
ResourceName: "scaleway_iam_group.main_user",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
Expand Down Expand Up @@ -459,6 +474,11 @@ func TestAccGroup_UsersAndApplications(t *testing.T) {
resource.TestCheckNoResourceAttr("scaleway_iam_group.main_mix", "user_ids.0"),
),
},
{
ResourceName: "scaleway_iam_group.main_mix",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
Expand Down
54 changes: 30 additions & 24 deletions internal/services/iam/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/identity"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/types"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/verify"
Expand All @@ -25,6 +26,7 @@ func ResourcePolicy() *schema.Resource {
},
SchemaVersion: 0,
SchemaFunc: policySchema,
Identity: identity.FlatIdentity("id", "Policy UUID"),
}
Comment on lines 27 to 30
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResourcePolicy declares an Identity schema, but the implementation still uses d.SetId(...) and never sets the identity fields (in Create/Read). To fully support identity-based import/state, use identity.SetFlatIdentity(d, "id", pol.ID) in Create and set it again in Read from the fetched policy, handling any error.

Copilot uses AI. Check for mistakes.
}

Expand Down Expand Up @@ -158,7 +160,7 @@ func resourceIamPolicyCreate(ctx context.Context, d *schema.ResourceData, m any)
func resourceIamPolicyRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
api := NewAPI(m)

pol, err := api.GetPolicy(&iam.GetPolicyRequest{
policy, err := api.GetPolicy(&iam.GetPolicyRequest{
PolicyID: d.Id(),
}, scw.WithContext(ctx))
if err != nil {
Expand All @@ -171,38 +173,42 @@ func resourceIamPolicyRead(ctx context.Context, d *schema.ResourceData, m any) d
return diag.FromErr(err)
}

_ = d.Set("name", pol.Name)
_ = d.Set("description", pol.Description)
_ = d.Set("created_at", types.FlattenTime(pol.CreatedAt))
_ = d.Set("updated_at", types.FlattenTime(pol.UpdatedAt))
_ = d.Set("organization_id", pol.OrganizationID)
_ = d.Set("editable", pol.Editable)
_ = d.Set("tags", types.FlattenSliceString(pol.Tags))

if pol.UserID != nil {
_ = d.Set("user_id", types.FlattenStringPtr(pol.UserID))
rules, err := api.ListRules(&iam.ListRulesRequest{
PolicyID: policy.ID,
})
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

api.ListRules is invoked without scw.WithContext(ctx), so request cancellation/timeouts from the Terraform context won't propagate to this API call. Pass the context as a request option, consistent with the other IAM API calls in this function.

Suggested change
})
}, scw.WithContext(ctx))

Copilot uses AI. Check for mistakes.
if err != nil {
return diag.FromErr(fmt.Errorf("failed to list policy's rules: %w", err))
}

if pol.GroupID != nil {
_ = d.Set("group_id", types.FlattenStringPtr(pol.GroupID))
}
setPolicyState(d, policy, rules)

if pol.ApplicationID != nil {
_ = d.Set("application_id", types.FlattenStringPtr(pol.ApplicationID))
return nil
}

func setPolicyState(d *schema.ResourceData, policy *iam.Policy, rules *iam.ListRulesResponse) {
_ = d.Set("name", policy.Name)
_ = d.Set("description", policy.Description)
_ = d.Set("created_at", types.FlattenTime(policy.CreatedAt))
_ = d.Set("updated_at", types.FlattenTime(policy.UpdatedAt))
_ = d.Set("organization_id", policy.OrganizationID)
_ = d.Set("editable", policy.Editable)
_ = d.Set("tags", types.FlattenSliceString(policy.Tags))

if policy.UserID != nil {
_ = d.Set("user_id", types.FlattenStringPtr(policy.UserID))
}

_ = d.Set("no_principal", types.FlattenBoolPtr(pol.NoPrincipal))
if policy.GroupID != nil {
_ = d.Set("group_id", types.FlattenStringPtr(policy.GroupID))
}

listRules, err := api.ListRules(&iam.ListRulesRequest{
PolicyID: pol.ID,
})
if err != nil {
return diag.FromErr(fmt.Errorf("failed to list policy's rules: %w", err))
if policy.ApplicationID != nil {
_ = d.Set("application_id", types.FlattenStringPtr(policy.ApplicationID))
}

_ = d.Set("rule", flattenPolicyRules(listRules.Rules))
_ = d.Set("no_principal", types.FlattenBoolPtr(policy.NoPrincipal))

return nil
_ = d.Set("rule", flattenPolicyRules(rules.Rules))
}

func resourceIamPolicyUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
Expand Down
5 changes: 5 additions & 0 deletions internal/services/iam/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ func TestAccPolicy_Basic(t *testing.T) {
resource.TestCheckResourceAttr("scaleway_iam_policy.main", "tags.#", "0"),
),
},
{
ResourceName: "scaleway_iam_policy.main",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
Expand Down
7 changes: 6 additions & 1 deletion internal/services/iam/ssh_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/identity"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/types"
"golang.org/x/crypto/ssh"
Expand All @@ -26,6 +27,7 @@ func ResourceSSKKey() *schema.Resource {
},
SchemaVersion: 0,
SchemaFunc: sshKeySchema,
Identity: identity.FlatIdentity("id", "SSH key UUID"),
}
Comment on lines 29 to 31
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResourceSSKKey now declares an Identity schema, but the Read path doesn't populate it. Consider setting the identity in Read (e.g., identity.SetFlatIdentity(d, "id", res.ID)) so the identity fields stay consistent after refresh/import.

Copilot uses AI. Check for mistakes.
}

Expand Down Expand Up @@ -110,7 +112,10 @@ func resourceIamSSKKeyCreate(ctx context.Context, d *schema.ResourceData, m any)
}
}

d.SetId(res.ID)
err = identity.SetFlatIdentity(d, "id", res.ID)
if err != nil {
return diag.FromErr(err)
}
Comment on lines +115 to +118
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When disabled is set during create, the code calls UpdateSSHKey using d.Id() before the ID is assigned (the ID is only set later via identity.SetFlatIdentity). This will send an empty SSHKeyID and fail. Set the ID/identity immediately after CreateSSHKey (or use res.ID directly in the update request) before attempting the update.

Copilot uses AI. Check for mistakes.

return resourceIamSSHKeyRead(ctx, d, m)
}
Expand Down
Loading
Loading