Skip to content
Open
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
45 changes: 44 additions & 1 deletion docs/resources/rdb_privilege.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The following arguments are supported:

- `database_name` - (Required) Name of the database (e.g. `my-db-name`).

- `permission` - (Required) Permission to set. Valid values are `readonly`, `readwrite`, `all`, `custom` and `none`.
- `permission` - (Required) Desired permission level. Valid values are `readonly`, `readwrite`, `all`, `custom` and `none`.

- `region` - (Defaults to [provider](../index.md#region) `region`) The [region](../guides/regions_and_zones.md#regions) in which the resource exists.

Expand All @@ -61,6 +61,49 @@ In addition to all arguments above, the following attributes are exported:

- `id` - The ID of the user privileges, which is of the form `{region}/{instance_id}/{database_name}/{user_name}`, e.g. `fr-par/11111111-1111-1111-1111-111111111111/database_name/foo`

- `effective_permission` - The actual permission currently set in Scaleway. May differ from `permission` after database schema changes (new tables, views, or sequences created).

- `permission_status` - Permission synchronization status. Possible values:
- `synced`: The effective permission matches the desired permission
- `drifted`: The effective permission differs from the desired permission (requires `terraform apply` to resync)

## Permission Drift Management

### Understanding Permission Drift

When you configure a privilege (e.g., `readwrite`), Scaleway applies it to **database objects that exist at that moment**. If new tables, views, or sequences are created later, they won't automatically inherit these permissions. In that case, the API may return `custom`.

**Example:**

```terraform
resource "scaleway_rdb_privilege" "app" {
instance_id = scaleway_rdb_instance.main.id
user_name = "app_user"
database_name = "mydb"
permission = "readwrite"

# Later, after new objects are created externally:
# effective_permission = "custom" (computed)
# permission_status = "drifted" (computed)
}
```

### Handling Permission Drift

Run `terraform apply` to reapply the configured permission to all objects (existing and new):

```bash
terraform apply
```

The plan will typically show:

```diff
~ resource "scaleway_rdb_privilege" "app" {
~ permission = "custom" -> "readwrite"
}
```

## Import

The user privileges can be imported using the `{region}/{instance_id}/{database_name}/{user_name}`, e.g.
Expand Down
55 changes: 52 additions & 3 deletions internal/services/rdb/privilege.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"strings"

"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -57,10 +58,20 @@ func ResourcePrivilege() *schema.Resource {
},
"permission": {
Type: schema.TypeString,
Description: "Privilege",
Description: "Desired permission (readonly, readwrite, all, custom, none)",
ValidateDiagFunc: verify.ValidateEnum[rdb.Permission](),
Required: true,
},
"effective_permission": {
Type: schema.TypeString,
Description: "Actual permission currently set in Scaleway. May differ from 'permission' after database schema changes",
Computed: true,
},
"permission_status": {
Type: schema.TypeString,
Description: "Permission synchronization status: 'synced' if effective matches desired, 'drifted' if they differ",
Computed: true,
},
// Common
"region": regional.Schema(),
},
Expand Down Expand Up @@ -120,6 +131,10 @@ func ResourceRdbPrivilegeCreate(ctx context.Context, d *schema.ResourceData, m a

d.SetId(ResourceRdbUserPrivilegeID(region, locality.ExpandID(instanceID), databaseName, userName))

configuredPermission := d.Get("permission").(string)
_ = d.Set("effective_permission", configuredPermission)
_ = d.Set("permission_status", "synced")

return ResourceRdbPrivilegeRead(ctx, d, m)
}

Expand Down Expand Up @@ -184,13 +199,47 @@ func ResourceRdbPrivilegeRead(ctx context.Context, d *schema.ResourceData, m any
}

privilege := res.Privileges[0]
effectivePermission := string(privilege.Permission)
configuredPermission := d.Get("permission").(string)

_ = d.Set("database_name", privilege.DatabaseName)
_ = d.Set("user_name", privilege.UserName)
_ = d.Set("permission", privilege.Permission)
_ = d.Set("instance_id", regional.NewIDString(region, instanceID))
_ = d.Set("region", region)
_ = d.Set("permission", privilege.Permission)
_ = d.Set("effective_permission", effectivePermission)

var diags diag.Diagnostics

if effectivePermission != configuredPermission {
Copy link
Member

Choose a reason for hiding this comment

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

Is there a way to add a test for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Testing drift requires SQL executed outside Terraform, which is tricky to automate in acceptance tests :(

_ = d.Set("permission_status", "drifted")

diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "Database privilege drift detected",
Detail: fmt.Sprintf(
"The privilege for user '%s' on database '%s' has drifted:\n"+
" • Configured permission: '%s'\n"+
" • Effective permission: '%s'\n\n"+
"This usually happens after database schema changes (new tables, views, or sequences created).\n"+
"The configured permission was applied to objects existing at the time, but new objects created "+
"afterward don't automatically inherit these permissions.\n\n"+
"To fix this:\n"+
" 1. Run 'terraform apply' to reapply the configured permission to all objects\n"+
" 2. Or use PostgreSQL default privileges to automatically grant permissions to future objects\n"+
" 3. Or set 'permission = \"%s\"' if you want to keep the current state\n\n"+
"See: https://www.scaleway.com/en/docs/managed-databases/postgresql-and-mysql/how-to/manage-users/",
userName, databaseName,
configuredPermission, effectivePermission,
effectivePermission,
),
AttributePath: cty.GetAttrPath("permission"),
})
} else {
_ = d.Set("permission_status", "synced")
}

return nil
return diags
}

func ResourceRdbPrivilegeUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
Expand Down
45 changes: 44 additions & 1 deletion templates/resources/rdb_privilege.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The following arguments are supported:

- `database_name` - (Required) Name of the database (e.g. `my-db-name`).

- `permission` - (Required) Permission to set. Valid values are `readonly`, `readwrite`, `all`, `custom` and `none`.
- `permission` - (Required) Desired permission level. Valid values are `readonly`, `readwrite`, `all`, `custom` and `none`.

- `region` - (Defaults to [provider](../index.md#region) `region`) The [region](../guides/regions_and_zones.md#regions) in which the resource exists.

Expand All @@ -62,6 +62,49 @@ In addition to all arguments above, the following attributes are exported:

- `id` - The ID of the user privileges, which is of the form `{region}/{instance_id}/{database_name}/{user_name}`, e.g. `fr-par/11111111-1111-1111-1111-111111111111/database_name/foo`

- `effective_permission` - The actual permission currently set in Scaleway. May differ from `permission` after database schema changes (new tables, views, or sequences created).

- `permission_status` - Permission synchronization status. Possible values:
- `synced`: The effective permission matches the desired permission
- `drifted`: The effective permission differs from the desired permission (requires `terraform apply` to resync)

## Permission Drift Management

### Understanding Permission Drift

When you configure a privilege (e.g., `readwrite`), Scaleway applies it to **database objects that exist at that moment**. If new tables, views, or sequences are created later, they won't automatically inherit these permissions. In that case, the API may return `custom`.

**Example:**

```terraform
resource "scaleway_rdb_privilege" "app" {
instance_id = scaleway_rdb_instance.main.id
user_name = "app_user"
database_name = "mydb"
permission = "readwrite"

# Later, after new objects are created externally:
# effective_permission = "custom" (computed)
# permission_status = "drifted" (computed)
}
```

### Handling Permission Drift

Run `terraform apply` to reapply the configured permission to all objects (existing and new):

```bash
terraform apply
```

The plan will typically show:

```diff
~ resource "scaleway_rdb_privilege" "app" {
~ permission = "custom" -> "readwrite"
}
```

## Import

The user privileges can be imported using the `{region}/{instance_id}/{database_name}/{user_name}`, e.g.
Expand Down
Loading