Skip to content

Commit 75e0dd7

Browse files
committed
feat/postgresql_role_attribute: add support for extensions settings
1 parent 174355b commit 75e0dd7

File tree

2 files changed

+118
-0
lines changed

2 files changed

+118
-0
lines changed

postgresql/resource_postgresql_role_attribute.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import (
1111
"github.com/lib/pq"
1212
)
1313

14+
const (
15+
roleExtensionAttrsAttr = "extension_attrs"
16+
)
17+
1418
func resourcePostgreSQLRoleAttribute() *schema.Resource {
1519
return &schema.Resource{
1620
Create: PGResourceFunc(resourcePostgreSQLRoleAttributeCreate),
@@ -111,6 +115,12 @@ func resourcePostgreSQLRoleAttribute() *schema.Resource {
111115
Optional: true,
112116
Description: "Role to switch to at login",
113117
},
118+
roleExtensionAttrsAttr: {
119+
Type: schema.TypeMap,
120+
Optional: true,
121+
Description: "Map of arbitrary GUC (Grand Unified Configuration) key-value pairs to set for the role. Supports all PostgreSQL custom variables.",
122+
Elem: &schema.Schema{Type: schema.TypeString},
123+
},
114124
},
115125
}
116126
}
@@ -271,6 +281,10 @@ func resourcePostgreSQLRoleAttributeReadImpl(db *DBConnection, d *schema.Resourc
271281
d.Set(rolePasswordAttr, password)
272282
}
273283

284+
if _, ok := d.GetOk(roleExtensionAttrsAttr); ok {
285+
d.Set(roleExtensionAttrsAttr, readExtensionAttrs(roleConfig))
286+
}
287+
274288
return nil
275289
}
276290

@@ -401,5 +415,100 @@ func setRoleAttributes(txn *sql.Tx, db *DBConnection, d *schema.ResourceData) er
401415
}
402416
}
403417

418+
if d.HasChange(roleExtensionAttrsAttr) {
419+
if err := setExtensionAttrs(txn, d); err != nil {
420+
return err
421+
}
422+
}
423+
424+
return nil
425+
}
426+
427+
// readExtensionAttrs extracts arbitrary GUC parameters from roleConfig array,
428+
// excluding well-known parameters that are handled by specific attributes.
429+
func readExtensionAttrs(roleConfig pq.ByteaArray) map[string]string {
430+
extensionAttrs := make(map[string]string)
431+
432+
// Well-known parameters that should be excluded from extension_attrs
433+
excludedParams := map[string]bool{
434+
"search_path": true,
435+
"statement_timeout": true,
436+
"idle_in_transaction_session_timeout": true,
437+
"role": true, // assume_role
438+
}
439+
440+
for _, v := range roleConfig {
441+
config := string(v)
442+
443+
// Split on first '=' to get key=value
444+
parts := strings.SplitN(config, "=", 2)
445+
if len(parts) != 2 {
446+
continue
447+
}
448+
449+
key := parts[0]
450+
value := parts[1]
451+
452+
// Skip well-known parameters
453+
if excludedParams[key] {
454+
continue
455+
}
456+
457+
extensionAttrs[key] = value
458+
}
459+
460+
return extensionAttrs
461+
}
462+
463+
// setExtensionAttrs sets arbitrary GUC parameters for a role
464+
func setExtensionAttrs(txn *sql.Tx, d *schema.ResourceData) error {
465+
roleName := d.Get(roleNameAttr).(string)
466+
extensionAttrsRaw := d.Get(roleExtensionAttrsAttr).(map[string]interface{})
467+
468+
// Convert interface{} map to string map
469+
extensionAttrs := make(map[string]string)
470+
for k, v := range extensionAttrsRaw {
471+
extensionAttrs[k] = fmt.Sprintf("%v", v)
472+
}
473+
474+
// Get old and new values to determine what needs to be changed
475+
oldRaw, _ := d.GetChange(roleExtensionAttrsAttr)
476+
oldAttrs := make(map[string]string)
477+
newAttrs := extensionAttrs
478+
479+
if oldRaw != nil {
480+
oldAttrsRaw := oldRaw.(map[string]interface{})
481+
for k, v := range oldAttrsRaw {
482+
oldAttrs[k] = fmt.Sprintf("%v", v)
483+
}
484+
}
485+
486+
// Reset parameters that were removed
487+
for key := range oldAttrs {
488+
if _, exists := newAttrs[key]; !exists {
489+
sql := fmt.Sprintf("ALTER ROLE %s RESET %s", pq.QuoteIdentifier(roleName), pq.QuoteIdentifier(key))
490+
if _, err := txn.Exec(sql); err != nil {
491+
return fmt.Errorf("could not reset %s for role %s: %w", key, roleName, err)
492+
}
493+
}
494+
}
495+
496+
// Set new or changed parameters
497+
for key, value := range newAttrs {
498+
if oldValue, exists := oldAttrs[key]; !exists || oldValue != value {
499+
var sql string
500+
if value == "" {
501+
// Reset parameter if value is empty
502+
sql = fmt.Sprintf("ALTER ROLE %s RESET %s", pq.QuoteIdentifier(roleName), pq.QuoteIdentifier(key))
503+
} else {
504+
sql = fmt.Sprintf("ALTER ROLE %s SET %s = '%s'", pq.QuoteIdentifier(roleName), pq.QuoteIdentifier(key), pqQuoteLiteral(value))
505+
}
506+
507+
if _, err := txn.Exec(sql); err != nil {
508+
return fmt.Errorf("could not set %s for role %s: %w", key, roleName, err)
509+
}
510+
}
511+
}
512+
404513
return nil
405514
}

website/docs/r/role_attribute.html.markdown

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ resource "postgresql_role_attribute" "app_iam_service_account_role_attrs" {
3030
name = postgresql_role.app_iam_service_account.name
3131
bypass_row_level_security = true
3232
}
33+
34+
# Configure pgAudit settings for role
35+
resource "postgresql_role_attribute" "dba_audit_attrs" {
36+
name = "dba_role"
37+
extension_attrs = {
38+
"pgaudit.log" = "all"
39+
}
40+
}
3341
```
3442

3543
## Argument Reference
@@ -50,6 +58,7 @@ resource "postgresql_role_attribute" "app_iam_service_account_role_attrs" {
5058
* `search_path` - (Optional) Sets the role's search path.
5159
* `statement_timeout` - (Optional) Abort any statement that takes more than the specified number of milliseconds.
5260
* `assume_role` - (Optional) Role to switch to at login.
61+
* `extension_attrs` - (Optional) Map of arbitrary GUC (Grand Unified Configuration) key-value pairs to set for the role.
5362

5463
## Import
5564

0 commit comments

Comments
 (0)