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
109 changes: 109 additions & 0 deletions postgresql/resource_postgresql_role_attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import (
"github.com/lib/pq"
)

const (
roleExtensionAttrsAttr = "extension_attrs"
)

func resourcePostgreSQLRoleAttribute() *schema.Resource {
return &schema.Resource{
Create: PGResourceFunc(resourcePostgreSQLRoleAttributeCreate),
Expand Down Expand Up @@ -111,6 +115,12 @@ func resourcePostgreSQLRoleAttribute() *schema.Resource {
Optional: true,
Description: "Role to switch to at login",
},
roleExtensionAttrsAttr: {
Type: schema.TypeMap,
Optional: true,
Description: "Map of arbitrary GUC (Grand Unified Configuration) key-value pairs to set for the role. Supports all PostgreSQL custom variables.",
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
Expand Down Expand Up @@ -271,6 +281,10 @@ func resourcePostgreSQLRoleAttributeReadImpl(db *DBConnection, d *schema.Resourc
d.Set(rolePasswordAttr, password)
}

if _, ok := d.GetOk(roleExtensionAttrsAttr); ok {
d.Set(roleExtensionAttrsAttr, readExtensionAttrs(roleConfig))
}

return nil
}

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

if d.HasChange(roleExtensionAttrsAttr) {
if err := setExtensionAttrs(txn, d); err != nil {
return err
}
}

return nil
}

// readExtensionAttrs extracts arbitrary GUC parameters from roleConfig array,
// excluding well-known parameters that are handled by specific attributes.
func readExtensionAttrs(roleConfig pq.ByteaArray) map[string]string {
extensionAttrs := make(map[string]string)

// Well-known parameters that should be excluded from extension_attrs
excludedParams := map[string]bool{
"search_path": true,
"statement_timeout": true,
"idle_in_transaction_session_timeout": true,
"role": true, // assume_role
}

for _, v := range roleConfig {
config := string(v)

// Split on first '=' to get key=value
parts := strings.SplitN(config, "=", 2)
if len(parts) != 2 {
continue
}

key := parts[0]
value := parts[1]

// Skip well-known parameters
if excludedParams[key] {
continue
}

extensionAttrs[key] = value
}

return extensionAttrs
}

// setExtensionAttrs sets arbitrary GUC parameters for a role
func setExtensionAttrs(txn *sql.Tx, d *schema.ResourceData) error {
roleName := d.Get(roleNameAttr).(string)
extensionAttrsRaw := d.Get(roleExtensionAttrsAttr).(map[string]interface{})

// Convert interface{} map to string map
extensionAttrs := make(map[string]string)
for k, v := range extensionAttrsRaw {
extensionAttrs[k] = fmt.Sprintf("%v", v)
}

// Get old and new values to determine what needs to be changed
oldRaw, _ := d.GetChange(roleExtensionAttrsAttr)
oldAttrs := make(map[string]string)
newAttrs := extensionAttrs

if oldRaw != nil {
oldAttrsRaw := oldRaw.(map[string]interface{})
for k, v := range oldAttrsRaw {
oldAttrs[k] = fmt.Sprintf("%v", v)
}
}

// Reset parameters that were removed
for key := range oldAttrs {
if _, exists := newAttrs[key]; !exists {
sql := fmt.Sprintf("ALTER ROLE %s RESET %s", pq.QuoteIdentifier(roleName), pq.QuoteIdentifier(key))
if _, err := txn.Exec(sql); err != nil {
return fmt.Errorf("could not reset %s for role %s: %w", key, roleName, err)
}
}
}

// Set new or changed parameters
for key, value := range newAttrs {
if oldValue, exists := oldAttrs[key]; !exists || oldValue != value {
var sql string
if value == "" {
// Reset parameter if value is empty
sql = fmt.Sprintf("ALTER ROLE %s RESET %s", pq.QuoteIdentifier(roleName), pq.QuoteIdentifier(key))
} else {
sql = fmt.Sprintf("ALTER ROLE %s SET %s = '%s'", pq.QuoteIdentifier(roleName), pq.QuoteIdentifier(key), pqQuoteLiteral(value))
}

if _, err := txn.Exec(sql); err != nil {
return fmt.Errorf("could not set %s for role %s: %w", key, roleName, err)
}
}
}

return nil
}
9 changes: 9 additions & 0 deletions website/docs/r/role_attribute.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ resource "postgresql_role_attribute" "app_iam_service_account_role_attrs" {
name = postgresql_role.app_iam_service_account.name
bypass_row_level_security = true
}

# Configure pgAudit settings for role
resource "postgresql_role_attribute" "dba_audit_attrs" {
name = "dba_role"
extension_attrs = {
"pgaudit.log" = "all"
}
}
```

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

## Import

Expand Down
Loading