diff --git a/postgresql/resource_postgresql_role.go b/postgresql/resource_postgresql_role.go index 98c34f04..7799b87a 100644 --- a/postgresql/resource_postgresql_role.go +++ b/postgresql/resource_postgresql_role.go @@ -24,6 +24,7 @@ const ( roleEncryptedPassAttr = "encrypted_password" roleInheritAttr = "inherit" roleLoginAttr = "login" + roleLogStatementAttr = "log_statement" roleNameAttr = "name" rolePasswordAttr = "password" roleReplicationAttr = "replication" @@ -39,6 +40,13 @@ const ( roleDepEncryptedAttr = "encrypted" ) +var allowedLogStatementOpts = []string{ + "none", + "ddl", + "mod", + "all", +} + func resourcePostgreSQLRole() *schema.Resource { return &schema.Resource{ Create: resourcePostgreSQLRoleCreate, @@ -131,6 +139,13 @@ func resourcePostgreSQLRole() *schema.Resource { Default: false, Description: "Determine whether a role is allowed to log in", }, + roleLogStatementAttr: { + Type: schema.TypeString, + Optional: true, + Default: "none", + ValidateFunc: validation.StringInSlice(allowedLogStatementOpts, false), + Description: "Sets the log level for SQL statements", + }, roleReplicationAttr: { Type: schema.TypeBool, Optional: true, @@ -295,6 +310,10 @@ func resourcePostgreSQLRoleCreate(d *schema.ResourceData, meta interface{}) erro return err } + if err = setRoleLogStatement(txn, d); err != nil { + return err + } + if err = txn.Commit(); err != nil { return errwrap.Wrapf("could not commit transaction: {{err}}", err) } @@ -449,6 +468,7 @@ func resourcePostgreSQLRoleReadImpl(c *Client, d *schema.ResourceData) error { d.Set(roleReplicationAttr, roleBypassRLS) d.Set(roleRolesAttr, pgArrayToSet(roleRoles)) d.Set(roleSearchPathAttr, readSearchPath(roleConfig)) + d.Set(roleLogStatementAttr, readLogStatement(roleConfig, d.Get(roleLogStatementAttr).(string))) statementTimeout, err := readStatementTimeout(roleConfig) if err != nil { @@ -481,6 +501,19 @@ func readSearchPath(roleConfig pq.ByteaArray) []string { return nil } +// readLogStatement searches for a log_statement entry in the rolconfig array. +// In case no such value is present, it returns empty. +func readLogStatement(roleConfig pq.ByteaArray, defaultValue string) string { + for _, v := range roleConfig { + config := string(v) + if strings.HasPrefix(config, roleLogStatementAttr) { + var result = strings.TrimPrefix(config, roleLogStatementAttr+"=") + return result + } + } + return defaultValue +} + // readStatementTimeout searches for a statement_timeout entry in the rolconfig array. // In case no such value is present, it returns nil. func readStatementTimeout(roleConfig pq.ByteaArray) (int, error) { @@ -600,6 +633,10 @@ func resourcePostgreSQLRoleUpdate(d *schema.ResourceData, meta interface{}) erro return err } + if err := setRoleLogStatement(txn, d); err != nil { + return err + } + if err := setRoleReplication(txn, d); err != nil { return err } @@ -789,6 +826,22 @@ func setRoleLogin(txn *sql.Tx, d *schema.ResourceData) error { return nil } +func setRoleLogStatement(txn *sql.Tx, d *schema.ResourceData) error { + if !d.HasChange(roleLogStatementAttr) { + return nil + } + + level := d.Get(roleLogStatementAttr).(string) + + roleName := d.Get(roleNameAttr).(string) + sql := fmt.Sprintf("ALTER ROLE %s SET log_statement TO %s", pq.QuoteIdentifier(roleName), pq.QuoteIdentifier(level)) + if _, err := txn.Exec(sql); err != nil { + return errwrap.Wrapf("Error updating role log_statement: {{err}}", err) + } + + return nil +} + func setRoleReplication(txn *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(roleReplicationAttr) { return nil diff --git a/postgresql/resource_postgresql_role_test.go b/postgresql/resource_postgresql_role_test.go index 9b361c06..2ab55c91 100644 --- a/postgresql/resource_postgresql_role_test.go +++ b/postgresql/resource_postgresql_role_test.go @@ -37,6 +37,7 @@ func TestAccPostgresqlRole_Basic(t *testing.T) { resource.TestCheckResourceAttr("postgresql_role.role_with_defaults", "inherit", "false"), resource.TestCheckResourceAttr("postgresql_role.role_with_defaults", "replication", "false"), resource.TestCheckResourceAttr("postgresql_role.role_with_defaults", "bypass_row_level_security", "false"), + resource.TestCheckResourceAttr("postgresql_role.role_with_defaults", "log_statement", "none"), resource.TestCheckResourceAttr("postgresql_role.role_with_defaults", "connection_limit", "-1"), resource.TestCheckResourceAttr("postgresql_role.role_with_defaults", "encrypted_password", "true"), resource.TestCheckResourceAttr("postgresql_role.role_with_defaults", "password", ""), @@ -90,6 +91,7 @@ resource "postgresql_role" "update_role" { roles = ["${postgresql_role.group_role.name}"] search_path = ["mysearchpath"] statement_timeout = 30000 + log_statement = "all" } ` resource.Test(t, resource.TestCase{ @@ -112,6 +114,7 @@ resource "postgresql_role" "update_role" { resource.TestCheckResourceAttr("postgresql_role.update_role", "roles.#", "0"), resource.TestCheckResourceAttr("postgresql_role.update_role", "search_path.#", "0"), resource.TestCheckResourceAttr("postgresql_role.update_role", "statement_timeout", "0"), + resource.TestCheckResourceAttr("postgresql_role.update_role", "log_statement", "none"), testAccCheckRoleCanLogin(t, "update_role", "toto"), ), }, @@ -134,6 +137,7 @@ resource "postgresql_role" "update_role" { resource.TestCheckResourceAttr("postgresql_role.update_role", "search_path.#", "1"), resource.TestCheckResourceAttr("postgresql_role.update_role", "search_path.0", "mysearchpath"), resource.TestCheckResourceAttr("postgresql_role.update_role", "statement_timeout", "30000"), + resource.TestCheckResourceAttr("postgresql_role.update_role", "log_statement", "all"), testAccCheckRoleCanLogin(t, "update_role2", "titi"), ), }, @@ -150,6 +154,7 @@ resource "postgresql_role" "update_role" { resource.TestCheckResourceAttr("postgresql_role.update_role", "roles.#", "0"), resource.TestCheckResourceAttr("postgresql_role.update_role", "search_path.#", "0"), resource.TestCheckResourceAttr("postgresql_role.update_role", "statement_timeout", "0"), + resource.TestCheckResourceAttr("postgresql_role.update_role", "log_statement", "none"), testAccCheckRoleCanLogin(t, "update_role", "toto"), ), }, @@ -322,6 +327,7 @@ resource "postgresql_role" "role_with_defaults" { login = false replication = false bypass_row_level_security = false + log_statement = "none" connection_limit = -1 encrypted_password = true password = "" diff --git a/website/docs/r/postgresql_role.html.markdown b/website/docs/r/postgresql_role.html.markdown index 97df8cb8..c034f047 100644 --- a/website/docs/r/postgresql_role.html.markdown +++ b/website/docs/r/postgresql_role.html.markdown @@ -116,6 +116,8 @@ resource "postgresql_role" "my_replication_role" { * `statement_timeout` - (Optional) Defines [`statement_timeout`](https://www.postgresql.org/docs/current/runtime-config-client.html#RUNTIME-CONFIG-CLIENT-STATEMENT) setting for this role which allows to abort any statement that takes more than the specified amount of time. +* `log_statement` - (Optional) Defines [`log_statement`](https://www.postgresql.org/docs/current/runtime-config-logging.html) setting for this role which controls the type of SQL statements that are logged. Default is `none` + ## Import Example `postgresql_role` supports importing resources. Supposing the following