Skip to content

Commit fed0b94

Browse files
committed
rdb: surface effective_permission and permission_status for privileges; warn on drift; docs updated
1 parent c4ee92c commit fed0b94

File tree

3 files changed

+148
-13
lines changed

3 files changed

+148
-13
lines changed

docs/resources/rdb_privilege.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ The following arguments are supported:
5151

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

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

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

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

6262
- `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`
6363

64+
- `effective_permission` - The actual permission currently set in Scaleway. May differ from `permission` after database schema changes (new tables, views, or sequences created).
65+
66+
- `permission_status` - Permission synchronization status. Possible values:
67+
- `synced`: The effective permission matches the desired permission
68+
- `drifted`: The effective permission differs from the desired permission (requires `terraform apply` to resync)
69+
70+
## Permission Drift Management
71+
72+
### Understanding Permission Drift
73+
74+
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`.
75+
76+
**Example:**
77+
78+
```terraform
79+
resource "scaleway_rdb_privilege" "app" {
80+
instance_id = scaleway_rdb_instance.main.id
81+
user_name = "app_user"
82+
database_name = "mydb"
83+
permission = "readwrite"
84+
85+
# Later, after new objects are created externally:
86+
# effective_permission = "custom" (computed)
87+
# permission_status = "drifted" (computed)
88+
}
89+
```
90+
91+
### Handling Permission Drift
92+
93+
Run `terraform apply` to reapply the configured permission to all objects (existing and new):
94+
95+
```bash
96+
terraform apply
97+
```
98+
99+
The plan will typically show:
100+
101+
```diff
102+
~ resource "scaleway_rdb_privilege" "app" {
103+
~ permission = "custom" -> "readwrite"
104+
}
105+
```
106+
64107
## Import
65108

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

internal/services/rdb/privilege.go

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"strings"
77

8+
"github.com/hashicorp/go-cty/cty"
89
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
910
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
1011
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -32,14 +33,14 @@ func ResourcePrivilege() *schema.Resource {
3233
Update: schema.DefaultTimeout(defaultInstanceTimeout),
3334
Delete: schema.DefaultTimeout(defaultInstanceTimeout),
3435
Default: schema.DefaultTimeout(defaultInstanceTimeout),
35-
},
36-
SchemaVersion: 1,
37-
StateUpgraders: []schema.StateUpgrader{
38-
{Version: 0, Type: rdbPrivilegeUpgradeV1SchemaType(), Upgrade: PrivilegeV1SchemaUpgradeFunc},
39-
},
40-
SchemaFunc: privilegeSchema,
41-
CustomizeDiff: cdf.LocalityCheck("instance_id"),
42-
}
36+
},
37+
SchemaVersion: 1,
38+
StateUpgraders: []schema.StateUpgrader{
39+
{Version: 0, Type: rdbPrivilegeUpgradeV1SchemaType(), Upgrade: PrivilegeV1SchemaUpgradeFunc},
40+
},
41+
SchemaFunc: privilegeSchema,
42+
CustomizeDiff: cdf.LocalityCheck("instance_id"),
43+
}
4344
}
4445

4546
func privilegeSchema() map[string]*schema.Schema {
@@ -63,10 +64,20 @@ func privilegeSchema() map[string]*schema.Schema {
6364
},
6465
"permission": {
6566
Type: schema.TypeString,
66-
Description: "Privilege",
67+
Description: "Desired permission (readonly, readwrite, all, custom, none)",
6768
ValidateDiagFunc: verify.ValidateEnum[rdb.Permission](),
6869
Required: true,
6970
},
71+
"effective_permission": {
72+
Type: schema.TypeString,
73+
Description: "Actual permission currently set in Scaleway. May differ from 'permission' after database schema changes",
74+
Computed: true,
75+
},
76+
"permission_status": {
77+
Type: schema.TypeString,
78+
Description: "Permission synchronization status: 'synced' if effective matches desired, 'drifted' if they differ",
79+
Computed: true,
80+
},
7081
// Common
7182
"region": regional.Schema(),
7283
}
@@ -124,6 +135,10 @@ func ResourceRdbPrivilegeCreate(ctx context.Context, d *schema.ResourceData, m a
124135

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

138+
configuredPermission := d.Get("permission").(string)
139+
_ = d.Set("effective_permission", configuredPermission)
140+
_ = d.Set("permission_status", "synced")
141+
127142
return ResourceRdbPrivilegeRead(ctx, d, m)
128143
}
129144

@@ -188,13 +203,47 @@ func ResourceRdbPrivilegeRead(ctx context.Context, d *schema.ResourceData, m any
188203
}
189204

190205
privilege := res.Privileges[0]
206+
effectivePermission := string(privilege.Permission)
207+
configuredPermission := d.Get("permission").(string)
208+
191209
_ = d.Set("database_name", privilege.DatabaseName)
192210
_ = d.Set("user_name", privilege.UserName)
193-
_ = d.Set("permission", privilege.Permission)
194211
_ = d.Set("instance_id", regional.NewIDString(region, instanceID))
195212
_ = d.Set("region", region)
213+
_ = d.Set("permission", privilege.Permission)
214+
_ = d.Set("effective_permission", effectivePermission)
215+
216+
var diags diag.Diagnostics
217+
218+
if effectivePermission != configuredPermission {
219+
_ = d.Set("permission_status", "drifted")
220+
221+
diags = append(diags, diag.Diagnostic{
222+
Severity: diag.Warning,
223+
Summary: "Database privilege drift detected",
224+
Detail: fmt.Sprintf(
225+
"The privilege for user '%s' on database '%s' has drifted:\n"+
226+
" • Configured permission: '%s'\n"+
227+
" • Effective permission: '%s'\n\n"+
228+
"This usually happens after database schema changes (new tables, views, or sequences created).\n"+
229+
"The configured permission was applied to objects existing at the time, but new objects created "+
230+
"afterward don't automatically inherit these permissions.\n\n"+
231+
"To fix this:\n"+
232+
" 1. Run 'terraform apply' to reapply the configured permission to all objects\n"+
233+
" 2. Or use PostgreSQL default privileges to automatically grant permissions to future objects\n"+
234+
" 3. Or set 'permission = \"%s\"' if you want to keep the current state\n\n"+
235+
"See: https://www.scaleway.com/en/docs/managed-databases/postgresql-and-mysql/how-to/manage-users/",
236+
userName, databaseName,
237+
configuredPermission, effectivePermission,
238+
effectivePermission,
239+
),
240+
AttributePath: cty.GetAttrPath("permission"),
241+
})
242+
} else {
243+
_ = d.Set("permission_status", "synced")
244+
}
196245

197-
return nil
246+
return diags
198247
}
199248

200249
func ResourceRdbPrivilegeUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {

templates/resources/rdb_privilege.md.tmpl

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ The following arguments are supported:
5252

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

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

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

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

6363
- `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`
6464

65+
- `effective_permission` - The actual permission currently set in Scaleway. May differ from `permission` after database schema changes (new tables, views, or sequences created).
66+
67+
- `permission_status` - Permission synchronization status. Possible values:
68+
- `synced`: The effective permission matches the desired permission
69+
- `drifted`: The effective permission differs from the desired permission (requires `terraform apply` to resync)
70+
71+
## Permission Drift Management
72+
73+
### Understanding Permission Drift
74+
75+
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`.
76+
77+
**Example:**
78+
79+
```terraform
80+
resource "scaleway_rdb_privilege" "app" {
81+
instance_id = scaleway_rdb_instance.main.id
82+
user_name = "app_user"
83+
database_name = "mydb"
84+
permission = "readwrite"
85+
86+
# Later, after new objects are created externally:
87+
# effective_permission = "custom" (computed)
88+
# permission_status = "drifted" (computed)
89+
}
90+
```
91+
92+
### Handling Permission Drift
93+
94+
Run `terraform apply` to reapply the configured permission to all objects (existing and new):
95+
96+
```bash
97+
terraform apply
98+
```
99+
100+
The plan will typically show:
101+
102+
```diff
103+
~ resource "scaleway_rdb_privilege" "app" {
104+
~ permission = "custom" -> "readwrite"
105+
}
106+
```
107+
65108
## Import
66109

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

0 commit comments

Comments
 (0)