From 8fc9ce90992c3c0188bc69e150dfed2e3c12f137 Mon Sep 17 00:00:00 2001 From: Benjamin Medlock Date: Thu, 12 May 2022 13:39:34 +0100 Subject: [PATCH 1/3] add alterrole resource --- postgresql/provider.go | 1 + postgresql/resource_postgresql_alter_role.go | 210 ++++++++++++++++++ .../resource_postgresql_alter_role_test.go | 144 ++++++++++++ .../r/postgresql_alter_role.html.markdown | 45 ++++ website/postgresql.erb | 3 + 5 files changed, 403 insertions(+) create mode 100644 postgresql/resource_postgresql_alter_role.go create mode 100644 postgresql/resource_postgresql_alter_role_test.go create mode 100644 website/docs/r/postgresql_alter_role.html.markdown diff --git a/postgresql/provider.go b/postgresql/provider.go index 9647026c..2bbc9098 100644 --- a/postgresql/provider.go +++ b/postgresql/provider.go @@ -200,6 +200,7 @@ func Provider() *schema.Provider { "postgresql_function": resourcePostgreSQLFunction(), "postgresql_server": resourcePostgreSQLServer(), "postgresql_user_mapping": resourcePostgreSQLUserMapping(), + "postgresql_alter_role": resourcePostgreSQLAlterRole(), }, DataSourcesMap: map[string]*schema.Resource{ diff --git a/postgresql/resource_postgresql_alter_role.go b/postgresql/resource_postgresql_alter_role.go new file mode 100644 index 00000000..e5434ece --- /dev/null +++ b/postgresql/resource_postgresql_alter_role.go @@ -0,0 +1,210 @@ +package postgresql + +import ( + "database/sql" + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/lib/pq" +) + +const ( + getAlterRoleQuery = ` +SELECT + rolname as role, + rolconfig as role_parameters +FROM + pg_catalog.pg_roles +WHERE + rolname = $1 +` +) + +func resourcePostgreSQLAlterRole() *schema.Resource { + return &schema.Resource{ + Create: PGResourceFunc(resourcePostgreSQLAlterRoleCreate), + Read: PGResourceFunc(resourcePostgreSQLAlterRoleRead), + Delete: PGResourceFunc(resourcePostgreSQLAlterRoleDelete), + + Schema: map[string]*schema.Schema{ + "role_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The name of the role to alter the attributes of", + }, + "parameter_key": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The name of the parameter to alter on the role", + }, + "parameter_value": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The value of the parameter which is being set", + }, + }, + } +} + +func resourcePostgreSQLAlterRoleRead(db *DBConnection, d *schema.ResourceData) error { + if !db.featureSupported(featurePrivileges) { + return fmt.Errorf( + "postgresql_alter_role resource is not supported for this Postgres version (%s)", + db.version, + ) + } + + return readAlterRole(db, d) +} + +func resourcePostgreSQLAlterRoleCreate(db *DBConnection, d *schema.ResourceData) error { + if !db.featureSupported(featurePrivileges) { + return fmt.Errorf( + "postgresql_alter_role resource is not supported for this Postgres version (%s)", + db.version, + ) + } + + txn, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(txn) + + // Reset the role alterations before altering them again. + if err = resetAlterRole(txn, d); err != nil { + return err + } + + if err = alterRole(txn, d); err != nil { + return err + } + + if err = txn.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + + d.SetId(generateAlterRoleID(d)) + + return readAlterRole(db, d) +} + +func resourcePostgreSQLAlterRoleDelete(db *DBConnection, d *schema.ResourceData) error { + if !db.featureSupported(featurePrivileges) { + return fmt.Errorf( + "postgresql_alter_role resource is not supported for this Postgres version (%s)", + db.version, + ) + } + + txn, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(txn) + + if err = resetAlterRole(txn, d); err != nil { + return err + } + + if err = txn.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + + return nil +} + +func readAlterRole(db QueryAble, d *schema.ResourceData) error { + var ( + roleName string + roleParameters pq.ByteaArray + ) + + alterRoleID := d.Id() + alterParameterKey := d.Get("parameter_key") + + values := []interface{}{ + &roleName, + &roleParameters, + } + + err := db.QueryRow(getAlterRoleQuery, d.Get("role_name")).Scan(values...) + switch { + case err == sql.ErrNoRows: + log.Printf("[WARN] PostgreSQL alter role (%q) not found", alterRoleID) + d.SetId("") + return nil + case err != nil: + return fmt.Errorf("error reading alter role: %w", err) + } + + d.Set("parameter_key", alterParameterKey) + d.Set("parameter_value", "") + d.Set("role_name", roleName) + d.SetId(generateAlterRoleID(d)) + + for _, v := range roleParameters { + parameter := string(v) + parameterKey := strings.Split(parameter, "=")[0] + parameterValue := strings.Split(parameter, "=")[1] + if parameterKey == alterParameterKey { + d.Set("parameter_key", parameterKey) + d.Set("parameter_value", parameterValue) + } + } + + return nil +} + +func createAlterRoleQuery(d *schema.ResourceData) string { + alterRole, _ := d.Get("role_name").(string) + alterParameterKey, _ := d.Get("parameter_key").(string) + alterParameterValue, _ := d.Get("parameter_value").(string) + + query := fmt.Sprintf( + "ALTER ROLE %s SET %s TO %s", + pq.QuoteIdentifier(alterRole), + pq.QuoteIdentifier(alterParameterKey), + pq.QuoteIdentifier(alterParameterValue), + ) + + return query +} + +func createResetAlterRoleQuery(d *schema.ResourceData) string { + alterRole, _ := d.Get("role_name").(string) + alterParameterKey, _ := d.Get("parameter_key").(string) + + return fmt.Sprintf( + "ALTER ROLE %s RESET %s", + pq.QuoteIdentifier(alterRole), + pq.QuoteIdentifier(alterParameterKey), + ) +} + +func alterRole(txn *sql.Tx, d *schema.ResourceData) error { + query := createAlterRoleQuery(d) + log.Println(query) + if _, err := txn.Exec(query); err != nil { + return fmt.Errorf("could not execute alter query: (%s): %w", query, err) + } + return nil +} + +func resetAlterRole(txn *sql.Tx, d *schema.ResourceData) error { + query := createResetAlterRoleQuery(d) + fmt.Println(query) + if _, err := txn.Exec(query); err != nil { + return fmt.Errorf("could not execute alter reset query (%s): %w", query, err) + } + return nil +} + +func generateAlterRoleID(d *schema.ResourceData) string { + return strings.Join([]string{d.Get("role_name").(string), d.Get("parameter_key").(string), d.Get("parameter_value").(string)}, "_") +} diff --git a/postgresql/resource_postgresql_alter_role_test.go b/postgresql/resource_postgresql_alter_role_test.go new file mode 100644 index 00000000..33f93e82 --- /dev/null +++ b/postgresql/resource_postgresql_alter_role_test.go @@ -0,0 +1,144 @@ +package postgresql + +import ( + "database/sql" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/lib/pq" +) + +func TestCreateAlterRoleQuery(t *testing.T) { + var roleName = "foo" + var parameterKey = "log_statement" + var parameterValue = "ALL" + + cases := []struct { + resource map[string]interface{} + expected string + }{ + { + resource: map[string]interface{}{ + "role_name": roleName, + "parameter_key": parameterKey, + "parameter_value": parameterValue, + }, + expected: fmt.Sprintf("ALTER ROLE %s SET %s TO %s", + pq.QuoteIdentifier(roleName), + pq.QuoteIdentifier(parameterKey), + pq.QuoteIdentifier(parameterValue)), + }, + } + + for _, c := range cases { + out := createAlterRoleQuery(schema.TestResourceDataRaw(t, resourcePostgreSQLAlterRole().Schema, c.resource)) + if out != c.expected { + t.Fatalf("Error matching output and expected: %#v vs %#v", out, c.expected) + } + } +} + +func TestResetRoleQuery(t *testing.T) { + var roleName = "foo" + var parameterKey = "log_statement" + + expected := fmt.Sprintf("ALTER ROLE %s RESET %s", pq.QuoteIdentifier(roleName), pq.QuoteIdentifier(parameterKey)) + + cases := []struct { + resource map[string]interface{} + }{ + { + resource: map[string]interface{}{ + "role_name": roleName, + "parameter_key": parameterKey, + }, + }, + } + + for _, c := range cases { + out := createResetAlterRoleQuery(schema.TestResourceDataRaw(t, resourcePostgreSQLAlterRole().Schema, c.resource)) + if out != expected { + t.Fatalf("Error matching output and expected: %#v vs %#v", out, expected) + } + } +} + +func TestAccPostgresqlAlterRole(t *testing.T) { + skipIfNotAcc(t) + + config := getTestConfig(t) + dsn := config.connStr("postgres") + + dbSuffix, teardown := setupTestDatabase(t, false, true) + defer teardown() + + _, roleName := getTestDBNames(dbSuffix) + + parameterKey := "log_statement" + parameterValue := "ALL" + + testAccPostgresqlAlterRoleResources := fmt.Sprintf(` + resource "postgresql_alter_role" "alter_role" { + role_name = "%s" + parameter_key = "%s" + parameter_value = "%s" + } + `, roleName, parameterKey, parameterValue) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testCheckCompatibleVersion(t, featurePrivileges) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccPostgresqlAlterRoleResources, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "postgresql_alter_role.alter_role", "role_name", roleName), + resource.TestCheckResourceAttr( + "postgresql_alter_role.alter_role", "parameter_key", parameterKey), + resource.TestCheckResourceAttr( + "postgresql_alter_role.alter_role", "parameter_value", parameterValue), + checkAlterRole(t, dsn, roleName, parameterKey, parameterValue), + ), + }, + }, + }) +} + +func checkAlterRole(t *testing.T, dsn, role string, parameterKey string, parameterValue string) resource.TestCheckFunc { + return func(s *terraform.State) error { + db, err := sql.Open("postgres", dsn) + if err != nil { + t.Fatalf("could to create connection pool: %v", err) + } + defer db.Close() + + roleParameter := fmt.Sprintf("%s=%s", parameterKey, parameterValue) + var _rez int + err = db.QueryRow(` + SELECT 1 + FROM pg_catalog.pg_roles + WHERE rolname = $1 + AND $2=ANY(rolconfig) + `, role, roleParameter).Scan(&_rez) + + switch { + case err == sql.ErrNoRows: + return fmt.Errorf( + "Role %s does not have the following attribute assigned %s", + role, roleParameter, + ) + + case err != nil: + t.Fatalf("could not check role attributes: %v", err) + } + + return nil + } +} diff --git a/website/docs/r/postgresql_alter_role.html.markdown b/website/docs/r/postgresql_alter_role.html.markdown new file mode 100644 index 00000000..8e192fea --- /dev/null +++ b/website/docs/r/postgresql_alter_role.html.markdown @@ -0,0 +1,45 @@ +--- +layout: "postgresql" +page_title: "PostgreSQL: postgresql_alter_role" +sidebar_current: "docs-postgresql-resource-postgresql_alter_role" +description: |- + Creates and manages the attributes of a role. +--- + +# postgresql\_alter\_role + +The ``postgresql_alter_role`` resource creates and manages the attributes of a role. + +See [PostgreSQL documentation](https://www.postgresql.org/docs/current/sql-alterrole.html) + +~> **Note:** This resource needs Postgresql version 10 or above. + +## Usage + +```hcl +resource "postgresql_alter_role" "set_pgaudit_logging" { + role = "test_role" + parameter_key = "pgaudit.log" + parameter_value = "ALL" +} +``` + +## Argument Reference + +* `role` - (Required) The name of the role to alter the attributes of. +* `parameter_key` - (Required) The configuration parameter to be set. +* `parameter_value` - (Required) The value for the configuration parameter. + +## Example + +```hcl +resource "postgresql_role" "pgaudit_role" { + name = "test_pgaudit_role" +} + +resource "postgresql_alter_role" "set_pgaudit_logging" { + role = postgresql_role.pgaudit_role.name + parameter_key = "pgaudit.log" + parameter_value = "ALL" +} +``` \ No newline at end of file diff --git a/website/postgresql.erb b/website/postgresql.erb index a4478779..816b158b 100644 --- a/website/postgresql.erb +++ b/website/postgresql.erb @@ -13,6 +13,9 @@ > Resources