diff --git a/docs/resources/rdb_privilege.md b/docs/resources/rdb_privilege.md index a387b98f8..1e36c27ef 100644 --- a/docs/resources/rdb_privilege.md +++ b/docs/resources/rdb_privilege.md @@ -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. @@ -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. diff --git a/internal/services/rdb/privilege.go b/internal/services/rdb/privilege.go index 5ca6a7b1f..5280fe44e 100644 --- a/internal/services/rdb/privilege.go +++ b/internal/services/rdb/privilege.go @@ -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" @@ -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(), }, @@ -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) } @@ -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 { + _ = 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 { diff --git a/templates/resources/rdb_privilege.md.tmpl b/templates/resources/rdb_privilege.md.tmpl index 3aea348c4..21f1eaefa 100644 --- a/templates/resources/rdb_privilege.md.tmpl +++ b/templates/resources/rdb_privilege.md.tmpl @@ -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. @@ -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.