Skip to content
This repository was archived by the owner on Nov 14, 2020. It is now read-only.

Making possible to specify default 'role' parameter for the user #150

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
49 changes: 49 additions & 0 deletions postgresql/resource_postgresql_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
roleSuperuserAttr = "superuser"
roleValidUntilAttr = "valid_until"
roleRolesAttr = "roles"
roleRoleParameterAttr = "role_parameter"
roleSearchPathAttr = "search_path"
roleStatementTimeoutAttr = "statement_timeout"

Expand Down Expand Up @@ -74,6 +75,11 @@ func resourcePostgreSQLRole() *schema.Resource {
MinItems: 0,
Description: "Role(s) to grant to this new role",
},
roleRoleParameterAttr: {
Type: schema.TypeString,
Optional: true,
Description: "Sets a ROLE parameter on this role",
},
roleSearchPathAttr: {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -290,6 +296,10 @@ func resourcePostgreSQLRoleCreate(d *schema.ResourceData, meta interface{}) erro
return err
}

if err = alterRoleParameter(txn, d); err != nil {
return err
}

if err = setStatementTimeout(txn, d); err != nil {
return err
}
Expand Down Expand Up @@ -448,6 +458,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(roleRoleParameterAttr, readRoleParameter(roleConfig))

statementTimeout, err := readStatementTimeout(roleConfig)
if err != nil {
Expand Down Expand Up @@ -480,6 +491,19 @@ func readSearchPath(roleConfig pq.ByteaArray) []string {
return nil
}

// readRoleParameter searches for a role entry in the rolconfig array.
// In case no such value is present, it returns empty string.
func readRoleParameter(roleConfig pq.ByteaArray) string {
for _, v := range roleConfig {
config := string(v)
if strings.HasPrefix(config, "role=") {
var result = strings.TrimPrefix(config, "role=")
return result
}
}
return ""
}

// 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) {
Expand Down Expand Up @@ -624,6 +648,10 @@ func resourcePostgreSQLRoleUpdate(d *schema.ResourceData, meta interface{}) erro
return err
}

if err = alterRoleParameter(txn, d); err != nil {
return err
}

if err = setStatementTimeout(txn, d); err != nil {
return err
}
Expand Down Expand Up @@ -951,3 +979,24 @@ func setStatementTimeout(txn *sql.Tx, d *schema.ResourceData) error {
}
return nil
}

func alterRoleParameter(txn *sql.Tx, d *schema.ResourceData) error {
roleName := d.Get(roleNameAttr).(string)
roleParameter := d.Get(roleRoleParameterAttr).(string)
if len(roleParameter) > 0 {
sql := fmt.Sprintf(
"ALTER ROLE %s SET role TO %s", pq.QuoteIdentifier(roleName), pq.QuoteIdentifier(roleParameter),
)
if _, err := txn.Exec(sql); err != nil {
return fmt.Errorf("could not set role parameter %s for %s: %w", roleParameter, roleName, err)
}
} else {
sql := fmt.Sprintf(
"ALTER ROLE %s RESET role", pq.QuoteIdentifier(roleName),
)
if _, err := txn.Exec(sql); err != nil {
return fmt.Errorf("could not reset role parameter for %s: %w", roleName, err)
}
}
return nil
}
56 changes: 47 additions & 9 deletions postgresql/resource_postgresql_role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ func TestAccPostgresqlRole_Basic(t *testing.T) {
{
Config: testAccPostgresqlRoleConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckPostgresqlRoleExists("myrole2", nil, nil),
testAccCheckPostgresqlRoleExists("myrole2", nil, nil, ""),
resource.TestCheckResourceAttr("postgresql_role.myrole2", "name", "myrole2"),
resource.TestCheckResourceAttr("postgresql_role.myrole2", "login", "true"),
resource.TestCheckResourceAttr("postgresql_role.myrole2", "roles.#", "0"),

testAccCheckPostgresqlRoleExists("role_default", nil, nil),
testAccCheckPostgresqlRoleExists("role_default", nil, nil, ""),
resource.TestCheckResourceAttr("postgresql_role.role_with_defaults", "name", "role_default"),
resource.TestCheckResourceAttr("postgresql_role.role_with_defaults", "superuser", "false"),
resource.TestCheckResourceAttr("postgresql_role.role_with_defaults", "create_database", "false"),
Expand All @@ -51,11 +51,13 @@ func TestAccPostgresqlRole_Basic(t *testing.T) {
resource.TestCheckResourceAttr("postgresql_role.role_with_superuser", "superuser", "true"),
resource.TestCheckResourceAttr("postgresql_role.role_with_defaults", "roles.#", "0"),

testAccCheckPostgresqlRoleExists("sub_role", []string{"myrole2", "role_simple"}, nil),
testAccCheckPostgresqlRoleExists("sub_role", []string{"myrole2", "role_simple"}, nil, ""),
resource.TestCheckResourceAttr("postgresql_role.sub_role", "name", "sub_role"),
resource.TestCheckResourceAttr("postgresql_role.sub_role", "roles.#", "2"),

testAccCheckPostgresqlRoleExists("role_with_search_path", nil, []string{"bar", "foo"}),
testAccCheckPostgresqlRoleExists("role_with_search_path", nil, []string{"bar", "foo"}, ""),

testAccCheckPostgresqlRoleExists("role_with_role_parameter", nil, nil, "myrole2"),

// The int part in the attr name is the schema.HashString of the value.
resource.TestCheckResourceAttr("postgresql_role.sub_role", "roles.719783566", "myrole2"),
Expand Down Expand Up @@ -88,6 +90,7 @@ resource "postgresql_role" "update_role" {
connection_limit = 5
password = "titi"
roles = ["${postgresql_role.group_role.name}"]
role_parameter = "${postgresql_role.group_role.id}"
search_path = ["mysearchpath"]
statement_timeout = 30000
}
Expand All @@ -103,7 +106,7 @@ resource "postgresql_role" "update_role" {
{
Config: configCreate,
Check: resource.ComposeTestCheckFunc(
testAccCheckPostgresqlRoleExists("update_role", []string{}, nil),
testAccCheckPostgresqlRoleExists("update_role", []string{}, nil, ""),
resource.TestCheckResourceAttr("postgresql_role.update_role", "name", "update_role"),
resource.TestCheckResourceAttr("postgresql_role.update_role", "login", "true"),
resource.TestCheckResourceAttr("postgresql_role.update_role", "connection_limit", "-1"),
Expand All @@ -118,7 +121,7 @@ resource "postgresql_role" "update_role" {
{
Config: configUpdate,
Check: resource.ComposeTestCheckFunc(
testAccCheckPostgresqlRoleExists("update_role2", []string{"group_role"}, nil),
testAccCheckPostgresqlRoleExists("update_role2", []string{"group_role"}, nil, "group_role"),
resource.TestCheckResourceAttr(
"postgresql_role.update_role", "name", "update_role2",
),
Expand All @@ -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", "role_parameter", "group_role"),
testAccCheckRoleCanLogin(t, "update_role2", "titi"),
),
},
Expand All @@ -142,7 +146,7 @@ resource "postgresql_role" "update_role" {
{
Config: configCreate,
Check: resource.ComposeTestCheckFunc(
testAccCheckPostgresqlRoleExists("update_role", []string{}, nil),
testAccCheckPostgresqlRoleExists("update_role", []string{}, nil, ""),
resource.TestCheckResourceAttr("postgresql_role.update_role", "name", "update_role"),
resource.TestCheckResourceAttr("postgresql_role.update_role", "login", "true"),
resource.TestCheckResourceAttr("postgresql_role.update_role", "connection_limit", "-1"),
Expand Down Expand Up @@ -179,7 +183,7 @@ func testAccCheckPostgresqlRoleDestroy(s *terraform.State) error {
return nil
}

func testAccCheckPostgresqlRoleExists(roleName string, grantedRoles []string, searchPath []string) resource.TestCheckFunc {
func testAccCheckPostgresqlRoleExists(roleName string, grantedRoles []string, searchPath []string, roleParameter string) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*Client)

Expand All @@ -203,6 +207,11 @@ func testAccCheckPostgresqlRoleExists(roleName string, grantedRoles []string, se
return err
}
}

if err := checkRoleParameter(client, roleName, roleParameter); err != nil {
return err
}

return nil
}
}
Expand Down Expand Up @@ -268,7 +277,7 @@ func checkGrantedRoles(client *Client, roleName string, expectedRoles []string)
func checkSearchPath(client *Client, roleName string, expectedSearchPath []string) error {
var searchPathStr string
err := client.DB().QueryRow(
"SELECT (pg_options_to_table(rolconfig)).option_value FROM pg_roles WHERE rolname=$1;",
"SELECT option_value FROM pg_roles r, pg_options_to_table(r.rolconfig) WHERE option_name='search_path' AND rolname=$1;",
roleName,
).Scan(&searchPathStr)

Expand All @@ -290,6 +299,29 @@ func checkSearchPath(client *Client, roleName string, expectedSearchPath []strin
return nil
}

func checkRoleParameter(client *Client, roleName string, expectedRoleParameter string) error {
var roleParameterStr string
err := client.DB().QueryRow(
"SELECT option_value FROM pg_roles r, pg_options_to_table(r.rolconfig) WHERE option_name='role' AND rolname=$1;",
roleName,
).Scan(&roleParameterStr)

// The query returns ErrNoRows if the role parameter is not found.
if err != nil && err == sql.ErrNoRows {
roleParameterStr = ""
} else if err != nil {
return fmt.Errorf("Error reading role: %v", err)
}

if expectedRoleParameter != roleParameterStr {
return fmt.Errorf(
"role is not equal to expected value. expected %v - got %v",
expectedRoleParameter, roleParameterStr,
)
}
return nil
}

var testAccPostgresqlRoleConfig = `
resource "postgresql_role" "myrole2" {
name = "myrole2"
Expand Down Expand Up @@ -349,6 +381,12 @@ resource "postgresql_role" "role_with_search_path" {
search_path = ["bar", "foo"]
}

resource "postgresql_role" "role_with_role_parameter" {
name = "role_with_role_parameter"
role_parameter = "${postgresql_role.myrole2.id}"
roles = ["${postgresql_role.myrole2.id}"]
}

resource "postgresql_role" "role_with_superuser" {
name = "role_with_superuser"
superuser = true
Expand Down
3 changes: 3 additions & 0 deletions website/docs/r/postgresql_role.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ resource "postgresql_role" "my_replication_role" {
* `password` - (Optional) Sets the role's password. A password is only of use
for roles having the `login` attribute set to true.

* `role_parameter` - (Optional) Sets the `role` parameter for this role. The specified role
must be a part of `roles` list, unless this is a superuser.

* `roles` - (Optional) Defines list of roles which will be granted to this new role.

* `search_path` - (Optional) Alters the search path of this new role. Note that
Expand Down