From 3e33656217e610f206bd78235ff814b496fc6ea4 Mon Sep 17 00:00:00 2001 From: Wiktor Miszuris Date: Sat, 4 Jul 2020 03:08:44 +0200 Subject: [PATCH 1/4] Initial draft - WIP --- postgresql/resource_postgresql_role.go | 151 ++++++++++++++++++++ postgresql/resource_postgresql_role_test.go | 6 + 2 files changed, 157 insertions(+) diff --git a/postgresql/resource_postgresql_role.go b/postgresql/resource_postgresql_role.go index 30c31c31..bfbeb2bc 100644 --- a/postgresql/resource_postgresql_role.go +++ b/postgresql/resource_postgresql_role.go @@ -25,6 +25,7 @@ const ( roleLoginAttr = "login" roleNameAttr = "name" rolePasswordAttr = "password" + roleParametersAttr = "parameters" roleReplicationAttr = "replication" roleSkipDropRoleAttr = "skip_drop_role" roleSkipReassignOwnedAttr = "skip_reassign_owned" @@ -38,6 +39,8 @@ const ( roleDepEncryptedAttr = "encrypted" ) +var roleStandaloneParameters = [...]string{roleSearchPathAttr, roleStatementTimeoutAttr, roleValidUntilAttr} + func resourcePostgreSQLRole() *schema.Resource { return &schema.Resource{ Create: resourcePostgreSQLRoleCreate, @@ -160,6 +163,13 @@ func resourcePostgreSQLRole() *schema.Resource { Description: "Abort any statement that takes more than the specified number of milliseconds", ValidateFunc: validation.IntAtLeast(0), }, + roleParametersAttr: { + Type: schema.TypeMap, + Optional: true, + Description: "Specifies default parameters which will be set for the role", + Elem: &schema.Schema{Type: schema.TypeString}, + ValidateFunc: validateRoleParameters, + }, }, } } @@ -294,6 +304,10 @@ func resourcePostgreSQLRoleCreate(d *schema.ResourceData, meta interface{}) erro return err } + if err = setRoleParameters(txn, d); err != nil { + return err + } + if err = txn.Commit(); err != nil { return fmt.Errorf("could not commit transaction: %w", err) } @@ -458,6 +472,11 @@ func resourcePostgreSQLRoleReadImpl(c *Client, d *schema.ResourceData) error { d.SetId(roleName) + err = getRoleParameters(c.DB(), d) + if err != nil { + return err + } + password, err := readRolePassword(c, d, roleCanLogin) if err != nil { return err @@ -628,6 +647,10 @@ func resourcePostgreSQLRoleUpdate(d *schema.ResourceData, meta interface{}) erro return err } + if err = alterRoleParameters(txn, d); err != nil { + return err + } + if err = txn.Commit(); err != nil { return fmt.Errorf("could not commit transaction: %w", err) } @@ -900,6 +923,134 @@ func grantRoles(txn *sql.Tx, d *schema.ResourceData) error { return nil } +func isStandaloneRoleParameter(paramName string) bool { + for _, standaloneParameter := range roleStandaloneParameters { + if strings.ToLower(paramName) == strings.ToLower(standaloneParameter) { + return true + } + } + return false +} + +func validateRoleParameters(value interface{}, key string) (ws []string, es []error) { + paramValues := value.(map[string]interface{}) + + for p, v := range paramValues { + if len(v.(string)) == 0 { + es = append(es, fmt.Errorf("Parameter %s has no value", p)) + } + + if isStandaloneRoleParameter(p) { + es = append(es, fmt.Errorf("Parameter %s should be set by dedicated Role resource property", p)) + } + } + return +} + +func setRoleParameters(txn *sql.Tx, d *schema.ResourceData) error { + roleName := d.Get(roleNameAttr).(string) + roleParameters := d.Get(roleParametersAttr).(map[string]interface{}) + + for param, value := range roleParameters { + // We completely ignore parameters set by other properties + if isStandaloneRoleParameter(param) { + continue + } + + query := fmt.Sprintf( + "ALTER ROLE %s SET %s TO %s", pq.QuoteIdentifier(roleName), param, value.(string), + ) + + fmt.Printf("Setting: %s\n", query) + + if _, err := txn.Exec(query); err != nil { + return fmt.Errorf("Could not set parameter %s for %s: %w", param, roleName, err) + } + } + + return nil +} + +func getRoleParameters(db *sql.DB, d *schema.ResourceData) error { + var params map[string]string + var err error + + if params, err = getRoleParametersImpl(db, d); err != nil { + return err + } + + return d.Set(roleParametersAttr, params) +} + +func alterRoleParameters(txn *sql.Tx, d *schema.ResourceData) error { + roleName := d.Get(roleNameAttr).(string) + roleParameters := d.Get(roleParametersAttr).(map[string]interface{}) + + var existingParams map[string]string + var err error + + if existingParams, err = getRoleParametersImpl(txn, d); err != nil { + return err + } + + // Unset parameters that currently exist, but are not supposed to be set + for param := range existingParams { + // We completely ignore parameters set by other properties + if isStandaloneRoleParameter(param) { + continue + } + + if _, ok := roleParameters[param]; ok { + sql := fmt.Sprintf( + "ALTER ROLE %s RESET %s", pq.QuoteIdentifier(roleName), param, + ) + + fmt.Printf("Altering: %s\n", sql) + + if _, err := txn.Exec(sql); err != nil { + return fmt.Errorf("Could not reset %s parameter for %s: %w", param, roleName, err) + } + } + } + + // Now let's set actual parameters + return setRoleParameters(txn, d) +} + +func getRoleParametersImpl(db QueryAble, d *schema.ResourceData) (map[string]string, error) { + roleName := d.Get(roleNameAttr).(string) + + query := "SELECT option_name, option_value FROM pg_roles r, pg_options_to_table(r.rolconfig) WHERE rolname=$1" + rows, err := db.Query(query, roleName) + + if err != nil { + return nil, fmt.Errorf("Unable to read role paramaters for %s: %w", roleName, err) + } + defer rows.Close() + + dbParams := make(map[string]string) + + for rows.Next() { + var ( + paramName string + paramValue string + ) + if err := rows.Scan(¶mName, ¶mValue); err != nil { + return nil, fmt.Errorf("Unable to read role paramaters for %s: %w", roleName, err) + } + + // We completely ignore parameters set by other properties + if isStandaloneRoleParameter(paramName) { + fmt.Printf("Ignored: %s, %s\n", paramName, paramValue) + continue + } + + fmt.Printf("Getting: %s, %s\n", paramName, paramValue) + dbParams[paramName] = paramValue + } + return dbParams, nil +} + func alterSearchPath(txn *sql.Tx, d *schema.ResourceData) error { role := d.Get(roleNameAttr).(string) searchPathInterface := d.Get(roleSearchPathAttr).([]interface{}) diff --git a/postgresql/resource_postgresql_role_test.go b/postgresql/resource_postgresql_role_test.go index 9b361c06..eb037006 100644 --- a/postgresql/resource_postgresql_role_test.go +++ b/postgresql/resource_postgresql_role_test.go @@ -342,6 +342,12 @@ resource "postgresql_role" "sub_role" { "${postgresql_role.myrole2.id}", "${postgresql_role.role_simple.id}", ] + parameters = { + application_name = "aaa" + role = "${postgresql_role.myrole2.id}" + //setting this will raise validation error + //search_path = "aaa" + } } resource "postgresql_role" "role_with_search_path" { From baf27d03396a801af28400ba5516a4cef2e28898 Mon Sep 17 00:00:00 2001 From: Wiktor Miszuris Date: Sat, 4 Jul 2020 03:47:36 +0200 Subject: [PATCH 2/4] Adjusting for older PostgreSql --- postgresql/resource_postgresql_role.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postgresql/resource_postgresql_role.go b/postgresql/resource_postgresql_role.go index bfbeb2bc..b752ce7c 100644 --- a/postgresql/resource_postgresql_role.go +++ b/postgresql/resource_postgresql_role.go @@ -1020,7 +1020,7 @@ func alterRoleParameters(txn *sql.Tx, d *schema.ResourceData) error { func getRoleParametersImpl(db QueryAble, d *schema.ResourceData) (map[string]string, error) { roleName := d.Get(roleNameAttr).(string) - query := "SELECT option_name, option_value FROM pg_roles r, pg_options_to_table(r.rolconfig) WHERE rolname=$1" + query := "SELECT option_name, option_value FROM (SELECT (pg_options_to_table(r.rolconfig)).* FROM pg_roles r where rolname = $1) t" rows, err := db.Query(query, roleName) if err != nil { From ce7ce50aad7319a2e42d4b6252d524d96da6fbf1 Mon Sep 17 00:00:00 2001 From: Wiktor Miszuris Date: Sat, 4 Jul 2020 09:42:37 +0200 Subject: [PATCH 3/4] Fixing basic bugs, adding one more test --- postgresql/resource_postgresql_role.go | 6 +++--- postgresql/resource_postgresql_role_test.go | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/postgresql/resource_postgresql_role.go b/postgresql/resource_postgresql_role.go index b752ce7c..907eab3b 100644 --- a/postgresql/resource_postgresql_role.go +++ b/postgresql/resource_postgresql_role.go @@ -39,7 +39,7 @@ const ( roleDepEncryptedAttr = "encrypted" ) -var roleStandaloneParameters = [...]string{roleSearchPathAttr, roleStatementTimeoutAttr, roleValidUntilAttr} +var roleStandaloneParameters = [...]string{roleSearchPathAttr, roleStatementTimeoutAttr} func resourcePostgreSQLRole() *schema.Resource { return &schema.Resource{ @@ -958,7 +958,7 @@ func setRoleParameters(txn *sql.Tx, d *schema.ResourceData) error { } query := fmt.Sprintf( - "ALTER ROLE %s SET %s TO %s", pq.QuoteIdentifier(roleName), param, value.(string), + "ALTER ROLE %s SET %s TO %s", pq.QuoteIdentifier(roleName), param, pq.QuoteIdentifier(value.(string)), ) fmt.Printf("Setting: %s\n", query) @@ -1000,7 +1000,7 @@ func alterRoleParameters(txn *sql.Tx, d *schema.ResourceData) error { continue } - if _, ok := roleParameters[param]; ok { + if _, ok := roleParameters[param]; !ok { sql := fmt.Sprintf( "ALTER ROLE %s RESET %s", pq.QuoteIdentifier(roleName), param, ) diff --git a/postgresql/resource_postgresql_role_test.go b/postgresql/resource_postgresql_role_test.go index eb037006..eda320fc 100644 --- a/postgresql/resource_postgresql_role_test.go +++ b/postgresql/resource_postgresql_role_test.go @@ -74,6 +74,9 @@ resource "postgresql_role" "update_role" { login = true password = "toto" valid_until = "2099-05-04 12:00:00+00" + parameters = { + application_name = "First_app" + } } ` @@ -90,6 +93,11 @@ resource "postgresql_role" "update_role" { roles = ["${postgresql_role.group_role.name}"] search_path = ["mysearchpath"] statement_timeout = 30000 + parameters = { + application_name = "Final" + log_statement = "all" + role = "${postgresql_role.group_role.name}" + } } ` resource.Test(t, resource.TestCase{ From a2b6d897dcd872eb12b5863312d59ee446877f5c Mon Sep 17 00:00:00 2001 From: Wiktor Miszuris Date: Sat, 4 Jul 2020 09:50:08 +0200 Subject: [PATCH 4/4] proper quotation for literal --- postgresql/resource_postgresql_role.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postgresql/resource_postgresql_role.go b/postgresql/resource_postgresql_role.go index 907eab3b..3a12d839 100644 --- a/postgresql/resource_postgresql_role.go +++ b/postgresql/resource_postgresql_role.go @@ -958,7 +958,7 @@ func setRoleParameters(txn *sql.Tx, d *schema.ResourceData) error { } query := fmt.Sprintf( - "ALTER ROLE %s SET %s TO %s", pq.QuoteIdentifier(roleName), param, pq.QuoteIdentifier(value.(string)), + "ALTER ROLE %s SET %s TO %s", pq.QuoteIdentifier(roleName), param, pq.QuoteLiteral(value.(string)), ) fmt.Printf("Setting: %s\n", query)