Commit WIP re: updated postgresql_role provider.

*Read() and *Update() still need to be updated.
This commit is contained in:
Sean Chittenden 2016-11-06 13:16:12 -08:00
parent 2e529146a5
commit bfc2a2d42f
No known key found for this signature in database
GPG Key ID: 4EBC9DC16C2E5E16
6 changed files with 354 additions and 60 deletions

View File

@ -104,14 +104,6 @@ func resourcePostgreSQLDatabase() *schema.Resource {
}
}
func validateConnLimit(v interface{}, key string) (warnings []string, errors []error) {
value := v.(int)
if value < -1 {
errors = append(errors, fmt.Errorf("%d can not be less than -1", key))
}
return
}
func resourcePostgreSQLDatabaseCreate(d *schema.ResourceData, meta interface{}) error {
c := meta.(*Client)
conn, err := c.Connect()

View File

@ -3,64 +3,232 @@ package postgresql
import (
"database/sql"
"fmt"
"log"
"strconv"
"strings"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
"github.com/lib/pq"
)
const (
roleBypassRLSAttr = "bypass_row_level_security"
roleConnLimitAttr = "connection_limit"
roleCreateDBAttr = "create_database"
roleCreateRoleAttr = "create_role"
roleEncryptedPassAttr = "encrypted_password"
roleInheritAttr = "inherit"
roleLoginAttr = "login"
roleNameAttr = "name"
rolePasswordAttr = "password"
roleReplicationAttr = "replication"
roleSuperUserAttr = "superuser"
roleValidUntilAttr = "valid_until"
// Deprecated options
roleDepEncryptedAttr = "encrypted"
)
func resourcePostgreSQLRole() *schema.Resource {
return &schema.Resource{
Create: resourcePostgreSQLRoleCreate,
Read: resourcePostgreSQLRoleRead,
Update: resourcePostgreSQLRoleUpdate,
Delete: resourcePostgreSQLRoleDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
roleNameAttr: {
Type: schema.TypeString,
Required: true,
Description: "The name of the role",
},
"login": {
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
Default: false,
rolePasswordAttr: {
Type: schema.TypeString,
Optional: true,
Computed: true,
Sensitive: true,
DefaultFunc: schema.EnvDefaultFunc("PGPASSWORD", nil),
Description: "Sets the role's password",
},
"password": {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
roleDepEncryptedAttr: {
Type: schema.TypeString,
Optional: true,
Deprecated: fmt.Sprintf("Rename PostgreSQL role resource attribute %q to %q", roleDepEncryptedAttr, roleEncryptedPassAttr),
},
"encrypted": {
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
Default: false,
roleEncryptedPassAttr: {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Control whether the password is stored encrypted in the system catalogs",
},
roleValidUntilAttr: {
Type: schema.TypeString,
Optional: true,
Description: "Sets a date and time after which the role's password is no longer valid",
},
roleConnLimitAttr: {
Type: schema.TypeInt,
Optional: true,
Computed: true,
Description: "How many concurrent connections can be made with this role",
ValidateFunc: validateConnLimit,
},
roleSuperUserAttr: {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: `Determine whether the new role is a "superuser"`,
},
roleCreateDBAttr: {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Define a role's ability to create databases",
},
roleCreateRoleAttr: {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Determine whether this role will be permitted to create new roles",
},
roleInheritAttr: {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: `Determine whether a role "inherits" the privileges of roles it is a member of`,
},
roleLoginAttr: {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Determine whether a role is allowed to log in",
},
roleReplicationAttr: {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Determine whether a role is allowed to initiate streaming replication or put the system in and out of backup mode",
},
roleBypassRLSAttr: {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Determine whether a role bypasses every row-level security (RLS) policy",
},
},
}
}
func resourcePostgreSQLRoleCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Client)
conn, err := client.Connect()
c := meta.(*Client)
conn, err := c.Connect()
if err != nil {
return err
return errwrap.Wrapf("Error connecting to PostgreSQL: {{err}}", err)
}
defer conn.Close()
roleName := d.Get("name").(string)
loginAttr := getLoginStr(d.Get("login").(bool))
password := d.Get("password").(string)
stringOpts := []struct {
hclKey string
sqlKey string
}{
{rolePasswordAttr, "PASSWORD"},
{roleValidUntilAttr, "VALID UNTIL"},
}
intOpts := []struct {
hclKey string
sqlKey string
}{
{roleConnLimitAttr, "CONNECTION LIMIT"},
}
boolOpts := []struct {
hclKey string
sqlKeyEnable string
sqlKeyDisable string
}{
{roleSuperUserAttr, "CREATEDB", "NOCREATEDB"},
{roleCreateRoleAttr, "CREATEROLE", "NOCREATEROLE"},
{roleInheritAttr, "INHERIT", "NOINHERIT"},
{roleLoginAttr, "LOGIN", "NOLOGIN"},
{roleReplicationAttr, "REPLICATION", "NOREPLICATION"},
{roleBypassRLSAttr, "BYPASSRLS", "NOBYPASSRLS"},
encryptedCfg := getEncryptedStr(d.Get("encrypted").(bool))
// roleEncryptedPassAttr is used only when rolePasswordAttr is set.
// {roleEncryptedPassAttr, "ENCRYPTED", "UNENCRYPTED"},
}
query := fmt.Sprintf("CREATE ROLE %s %s %s PASSWORD '%s'", pq.QuoteIdentifier(roleName), loginAttr, encryptedCfg, password)
createOpts := make([]string, 0, len(stringOpts)+len(intOpts)+len(boolOpts))
for _, opt := range stringOpts {
v, ok := d.GetOk(opt.hclKey)
if !ok {
continue
}
val := v.(string)
if val != "" {
switch {
case opt.hclKey == rolePasswordAttr:
if strings.ToUpper(v.(string)) == "NULL" {
createOpts = append(createOpts, "PASSWORD NULL")
} else {
if d.Get(roleEncryptedPassAttr).(bool) {
createOpts = append(createOpts, "ENCRYPTED")
} else {
createOpts = append(createOpts, "UNENCRYPTED")
}
escapedPassword := strconv.Quote(val)
escapedPassword = strings.TrimLeft(escapedPassword, `"`)
escapedPassword = strings.TrimRight(escapedPassword, `"`)
createOpts = append(createOpts, fmt.Sprintf("%s '%s'", opt.sqlKey, escapedPassword))
}
case opt.hclKey == roleValidUntilAttr:
switch {
case v.(string) == "", strings.ToUpper(v.(string)) == "NULL":
createOpts = append(createOpts, fmt.Sprintf("%s %s", opt.sqlKey, "'infinity'"))
default:
createOpts = append(createOpts, fmt.Sprintf("%s %s", opt.sqlKey, pq.QuoteIdentifier(val)))
}
default:
createOpts = append(createOpts, fmt.Sprintf("%s %s", opt.sqlKey, pq.QuoteIdentifier(val)))
}
}
}
for _, opt := range intOpts {
val := d.Get(opt.hclKey).(int)
createOpts = append(createOpts, fmt.Sprintf("%s %d", opt.sqlKey, val))
}
for _, opt := range boolOpts {
if opt.hclKey == roleEncryptedPassAttr {
// This attribute is handled above in the stringOpts
// loop.
continue
}
val := d.Get(opt.hclKey).(bool)
valStr := opt.sqlKeyDisable
if val {
valStr = opt.sqlKeyEnable
}
createOpts = append(createOpts, valStr)
}
roleName := d.Get(roleNameAttr).(string)
createStr := strings.Join(createOpts, " ")
if len(createOpts) > 0 {
createStr = " WITH " + createStr
}
query := fmt.Sprintf("CREATE ROLE %s%s", pq.QuoteIdentifier(roleName), createStr)
_, err = conn.Query(query)
if err != nil {
return errwrap.Wrapf("Error creating role: {{err}}", err)
return errwrap.Wrapf(fmt.Sprintf("Error creating role %s: {{err}}", roleName), err)
}
d.SetId(roleName)
@ -76,7 +244,7 @@ func resourcePostgreSQLRoleDelete(d *schema.ResourceData, meta interface{}) erro
}
defer conn.Close()
roleName := d.Get("name").(string)
roleName := d.Get(roleNameAttr).(string)
query := fmt.Sprintf("DROP ROLE %s", pq.QuoteIdentifier(roleName))
_, err = conn.Query(query)
@ -90,25 +258,32 @@ func resourcePostgreSQLRoleDelete(d *schema.ResourceData, meta interface{}) erro
}
func resourcePostgreSQLRoleRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Client)
conn, err := client.Connect()
c := meta.(*Client)
conn, err := c.Connect()
if err != nil {
return err
}
defer conn.Close()
roleName := d.Get("name").(string)
roleName := d.Get(roleNameAttr).(string)
if roleName == "" {
roleName = d.Id()
}
var canLogin bool
err = conn.QueryRow("SELECT rolcanlogin FROM pg_roles WHERE rolname=$1", roleName).Scan(&canLogin)
var roleCanLogin bool
err = conn.QueryRow("SELECT rolcanlogin FROM pg_roles WHERE rolname=$1", roleName).Scan(&roleCanLogin)
switch {
case err == sql.ErrNoRows:
log.Printf("[WARN] PostgreSQL database (%s) not found", d.Id())
d.SetId("")
return nil
case err != nil:
return errwrap.Wrapf("Error reading role: {{err}}", err)
default:
d.Set("login", canLogin)
d.Set(roleNameAttr, roleName)
d.Set(roleLoginAttr, roleCanLogin)
d.Set("encrypted", true)
d.SetId(roleName)
return nil
}
}
@ -123,21 +298,21 @@ func resourcePostgreSQLRoleUpdate(d *schema.ResourceData, meta interface{}) erro
d.Partial(true)
roleName := d.Get("name").(string)
roleName := d.Get(roleNameAttr).(string)
if d.HasChange("login") {
loginAttr := getLoginStr(d.Get("login").(bool))
if d.HasChange(roleLoginAttr) {
loginAttr := getLoginStr(d.Get(roleLoginAttr).(bool))
query := fmt.Sprintf("ALTER ROLE %s %s", pq.QuoteIdentifier(roleName), pq.QuoteIdentifier(loginAttr))
_, err := conn.Query(query)
if err != nil {
return errwrap.Wrapf("Error updating login attribute for role: {{err}}", err)
}
d.SetPartial("login")
d.SetPartial(roleLoginAttr)
}
password := d.Get("password").(string)
if d.HasChange("password") {
password := d.Get(rolePasswordAttr).(string)
if d.HasChange(rolePasswordAttr) {
encryptedCfg := getEncryptedStr(d.Get("encrypted").(bool))
query := fmt.Sprintf("ALTER ROLE %s %s PASSWORD '%s'", pq.QuoteIdentifier(roleName), encryptedCfg, password)
@ -146,7 +321,7 @@ func resourcePostgreSQLRoleUpdate(d *schema.ResourceData, meta interface{}) erro
return errwrap.Wrapf("Error updating password attribute for role: {{err}}", err)
}
d.SetPartial("password")
d.SetPartial(rolePasswordAttr)
}
if d.HasChange("encrypted") {

View File

@ -24,6 +24,29 @@ func TestAccPostgresqlRole_Basic(t *testing.T) {
"postgresql_role.myrole2", "name", "myrole2"),
resource.TestCheckResourceAttr(
"postgresql_role.myrole2", "login", "true"),
resource.TestCheckResourceAttr(
"postgresql_role.role_with_defaults", "name", "testing_role_with_defaults"),
resource.TestCheckResourceAttr(
"postgresql_role.role_with_defaults", "superuser", "false"),
resource.TestCheckResourceAttr(
"postgresql_role.role_with_defaults", "create_database", "false"),
resource.TestCheckResourceAttr(
"postgresql_role.role_with_defaults", "create_role", "false"),
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", "connection_limit", "-1"),
resource.TestCheckResourceAttr(
"postgresql_role.role_with_defaults", "encrypted_password", "true"),
resource.TestCheckResourceAttr(
"postgresql_role.role_with_defaults", "password", ""),
resource.TestCheckResourceAttr(
"postgresql_role.role_with_defaults", "valid_until", "NULL"),
),
},
},
@ -129,4 +152,19 @@ resource "postgresql_role" "role_with_pwd_no_login" {
resource "postgresql_role" "role_simple" {
name = "role_simple"
}
resource "postgresql_role" "role_with_defaults" {
name = "testing_role_with_defaults"
superuser = false
create_database = false
create_role = false
inherit = false
login = false
replication = false
bypass_row_level_security = false
connection_limit = -1
encrypted_password = true
password = ""
valid_until = "NULL"
}
`

View File

@ -0,0 +1,11 @@
package postgresql
import "fmt"
func validateConnLimit(v interface{}, key string) (warnings []string, errors []error) {
value := v.(int)
if value < -1 {
errors = append(errors, fmt.Errorf("%d can not be less than -1", key))
}
return
}

View File

@ -42,8 +42,8 @@ resource "postgresql_database" "my_db" {
tablespace. This tablespace will be the default tablespace used for objects
created in this database.
* `connection_limit` - (Optional) How many concurrent connections can be made to
this database. `-1` (the default) means no limit.
* `connection_limit` - (Optional) How many concurrent connections can be
established to this database. `-1` (the default) means no limit.
* `allow_connections` - (Optional) If `false` then no one can connect to this
database. The default is `true`, allowing connections (except as restricted by
@ -95,7 +95,7 @@ provider "postgresql" {
resource "postgresql_database" "db1" {
provider = "postgresql.admindb"
name = "db1"
name = "testdb1"
}
```
@ -103,5 +103,9 @@ It is possible to import a `postgresql_database` resource with the following
command:
```
$ terraform import postgresql_database.testdb1 testdb1
$ terraform import postgresql_database.db1 testdb1
```
Where `testdb1` is the name of the database to import and
`postgresql_database.db1` is the name of the resource whose state will be
populated as a result of the command.

View File

@ -19,19 +19,93 @@ resource "postgresql_role" "my_role" {
name = "my_role"
login = true
password = "mypass"
encrypted = true
}
resource "postgresql_role" "my_replication_role" {
name = "replication_role"
replication = true
login = true
connection_limit = 5
password = "md5c98cbfeb6a347a47eb8e96cfb4c4b890"
}
```
## Argument Reference
* `name` - (Required) The name of the role. Must be unique on the PostgreSQL server instance
where it is configured.
* `name` - (Required) The name of the role. Must be unique on the PostgreSQL
server instance where it is configured.
* `login` - (Optional) Configures whether a role is allowed to log in; that is, whether the role can be given as the initial session authorization name during client connection. Corresponds to the LOGIN/NOLOGIN
clauses in 'CREATE ROLE'. Default value is false.
* `superuser` - (Optional) Defines whether the role is a "superuser", and
therefore can override all access restrictions within the database. Default
value is `false`.
* `password` - (Optional) Sets the role's password. (A password is only of use for roles having the LOGIN attribute, but you can nonetheless define one for roles without it.) If you do not plan to use password authentication you can omit this option. If no password is specified, the password will be set to null and password authentication will always fail for that user.
* `create_database` - (Optional) Defines a role's ability to execute `CREATE
DATABASE`. Default value is `false`.
* `encrypted` - (Optional) Corresponds to ENCRYPTED, UNENCRYPTED in PostgreSQL. This controls whether the password is stored encrypted in the system catalogs. Default is false.
* `create_role` - (Optional) Defines a role's ability to execute `CREATE ROLE`.
A role with this privilege can also alter and drop other roles. Default value
is `false`.
* `inherit` - (Optional) Defines whether a role "inherits" the privileges of
roles it is a member of. Default value is `false`.
* `login` - (Optional) Defines whether role is allowed to log in. Roles without
this attribute are useful for managing database privileges, but are not users
in the usual sense of the word. Default value is `false`.
* `replication` - (Optional) Defines whether a role is allowed to initiate
streaming replication or put the system in and out of backup mode. Default
value is `false`
* `bypass_row_level_security` - (Optional) Defines whether a role bypasses every
row-level security (RLS) policy. Default value is `false`.
* `connection_limit` - (Optional) If this role can log in, this specifies how
many concurrent connections the role can establish. `-1` (the default) means no
limit.
* `encrypted_password` - (Optional) Defines whether the password is stored
encrypted in the system catalogs. Default value is `true`. NOTE: this value
is always set (to the conservative and safe value), but may interfere with the
behavior of
[PostgreSQL's `password_encryption` setting](https://www.postgresql.org/docs/current/static/runtime-config-connection.html#GUC-PASSWORD-ENCRYPTION).
* `password` - (Optional) Sets the role's password. (A password is only of use
for roles having the `login` attribute set to true, but you can nonetheless
define one for roles without it.) Roles without a password explicitly set are
left alone. If the password is set to the magic value `NULL`, the password
will be always be cleared.
* `valid_until` - (Optional) Defines the date and time after which the role's
password is no longer valid. Established connections past this `valid_time`
will have to be manually terminated. This value corresponds to a PostgreSQL
datetime. If omitted or the magic value `NULL` is used, `valid_until` will be
set to `infinity`. Default is `NULL`, therefore `infinity`.
## Import Example
`postgresql_role` supports importing resources. Supposing the following
Terraform:
```
provider "postgresql" {
alias = "admindb"
}
resource "postgresql_role" "replication_role" {
provider = "postgresql.admindb"
name = "replication_name"
}
```
It is possible to import a `postgresql_role` resource with the following
command:
```
$ terraform import postgresql_role.replication_role replication_name
```
Where `replication_name` is the name of the role to import and
`postgresql_role.replication_role` is the name of the resource whose state will
be populated as a result of the command.