Skip to content

Commit bfc2a2d

Browse files
committed
Commit WIP re: updated postgresql_role provider.
*Read() and *Update() still need to be updated.
1 parent 2e52914 commit bfc2a2d

6 files changed

Lines changed: 354 additions & 60 deletions

File tree

builtin/providers/postgresql/resource_postgresql_database.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,6 @@ func resourcePostgreSQLDatabase() *schema.Resource {
104104
}
105105
}
106106

107-
func validateConnLimit(v interface{}, key string) (warnings []string, errors []error) {
108-
value := v.(int)
109-
if value < -1 {
110-
errors = append(errors, fmt.Errorf("%d can not be less than -1", key))
111-
}
112-
return
113-
}
114-
115107
func resourcePostgreSQLDatabaseCreate(d *schema.ResourceData, meta interface{}) error {
116108
c := meta.(*Client)
117109
conn, err := c.Connect()

builtin/providers/postgresql/resource_postgresql_role.go

Lines changed: 216 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,64 +3,232 @@ package postgresql
33
import (
44
"database/sql"
55
"fmt"
6+
"log"
7+
"strconv"
8+
"strings"
69

710
"github.com/hashicorp/errwrap"
811
"github.com/hashicorp/terraform/helper/schema"
912
"github.com/lib/pq"
1013
)
1114

15+
const (
16+
roleBypassRLSAttr = "bypass_row_level_security"
17+
roleConnLimitAttr = "connection_limit"
18+
roleCreateDBAttr = "create_database"
19+
roleCreateRoleAttr = "create_role"
20+
roleEncryptedPassAttr = "encrypted_password"
21+
roleInheritAttr = "inherit"
22+
roleLoginAttr = "login"
23+
roleNameAttr = "name"
24+
rolePasswordAttr = "password"
25+
roleReplicationAttr = "replication"
26+
roleSuperUserAttr = "superuser"
27+
roleValidUntilAttr = "valid_until"
28+
29+
// Deprecated options
30+
roleDepEncryptedAttr = "encrypted"
31+
)
32+
1233
func resourcePostgreSQLRole() *schema.Resource {
1334
return &schema.Resource{
1435
Create: resourcePostgreSQLRoleCreate,
1536
Read: resourcePostgreSQLRoleRead,
1637
Update: resourcePostgreSQLRoleUpdate,
1738
Delete: resourcePostgreSQLRoleDelete,
39+
Importer: &schema.ResourceImporter{
40+
State: schema.ImportStatePassthrough,
41+
},
1842

1943
Schema: map[string]*schema.Schema{
20-
"name": {
21-
Type: schema.TypeString,
22-
Required: true,
23-
ForceNew: true,
44+
roleNameAttr: {
45+
Type: schema.TypeString,
46+
Required: true,
47+
Description: "The name of the role",
48+
},
49+
rolePasswordAttr: {
50+
Type: schema.TypeString,
51+
Optional: true,
52+
Computed: true,
53+
Sensitive: true,
54+
DefaultFunc: schema.EnvDefaultFunc("PGPASSWORD", nil),
55+
Description: "Sets the role's password",
56+
},
57+
roleDepEncryptedAttr: {
58+
Type: schema.TypeString,
59+
Optional: true,
60+
Deprecated: fmt.Sprintf("Rename PostgreSQL role resource attribute %q to %q", roleDepEncryptedAttr, roleEncryptedPassAttr),
61+
},
62+
roleEncryptedPassAttr: {
63+
Type: schema.TypeBool,
64+
Optional: true,
65+
Default: true,
66+
Description: "Control whether the password is stored encrypted in the system catalogs",
67+
},
68+
69+
roleValidUntilAttr: {
70+
Type: schema.TypeString,
71+
Optional: true,
72+
Description: "Sets a date and time after which the role's password is no longer valid",
73+
},
74+
roleConnLimitAttr: {
75+
Type: schema.TypeInt,
76+
Optional: true,
77+
Computed: true,
78+
Description: "How many concurrent connections can be made with this role",
79+
ValidateFunc: validateConnLimit,
2480
},
25-
"login": {
26-
Type: schema.TypeBool,
27-
Optional: true,
28-
ForceNew: false,
29-
Default: false,
81+
roleSuperUserAttr: {
82+
Type: schema.TypeBool,
83+
Optional: true,
84+
Default: false,
85+
Description: `Determine whether the new role is a "superuser"`,
3086
},
31-
"password": {
32-
Type: schema.TypeString,
33-
Optional: true,
34-
ForceNew: false,
87+
roleCreateDBAttr: {
88+
Type: schema.TypeBool,
89+
Optional: true,
90+
Default: false,
91+
Description: "Define a role's ability to create databases",
3592
},
36-
"encrypted": {
37-
Type: schema.TypeBool,
38-
Optional: true,
39-
ForceNew: false,
40-
Default: false,
93+
roleCreateRoleAttr: {
94+
Type: schema.TypeBool,
95+
Optional: true,
96+
Default: false,
97+
Description: "Determine whether this role will be permitted to create new roles",
98+
},
99+
roleInheritAttr: {
100+
Type: schema.TypeBool,
101+
Optional: true,
102+
Default: false,
103+
Description: `Determine whether a role "inherits" the privileges of roles it is a member of`,
104+
},
105+
roleLoginAttr: {
106+
Type: schema.TypeBool,
107+
Optional: true,
108+
Default: false,
109+
Description: "Determine whether a role is allowed to log in",
110+
},
111+
roleReplicationAttr: {
112+
Type: schema.TypeBool,
113+
Optional: true,
114+
Default: false,
115+
Description: "Determine whether a role is allowed to initiate streaming replication or put the system in and out of backup mode",
116+
},
117+
roleBypassRLSAttr: {
118+
Type: schema.TypeBool,
119+
Optional: true,
120+
Default: false,
121+
Description: "Determine whether a role bypasses every row-level security (RLS) policy",
41122
},
42123
},
43124
}
44125
}
45126

46127
func resourcePostgreSQLRoleCreate(d *schema.ResourceData, meta interface{}) error {
47-
client := meta.(*Client)
48-
conn, err := client.Connect()
128+
c := meta.(*Client)
129+
conn, err := c.Connect()
49130
if err != nil {
50-
return err
131+
return errwrap.Wrapf("Error connecting to PostgreSQL: {{err}}", err)
51132
}
52133
defer conn.Close()
53134

54-
roleName := d.Get("name").(string)
55-
loginAttr := getLoginStr(d.Get("login").(bool))
56-
password := d.Get("password").(string)
135+
stringOpts := []struct {
136+
hclKey string
137+
sqlKey string
138+
}{
139+
{rolePasswordAttr, "PASSWORD"},
140+
{roleValidUntilAttr, "VALID UNTIL"},
141+
}
142+
intOpts := []struct {
143+
hclKey string
144+
sqlKey string
145+
}{
146+
{roleConnLimitAttr, "CONNECTION LIMIT"},
147+
}
148+
boolOpts := []struct {
149+
hclKey string
150+
sqlKeyEnable string
151+
sqlKeyDisable string
152+
}{
153+
{roleSuperUserAttr, "CREATEDB", "NOCREATEDB"},
154+
{roleCreateRoleAttr, "CREATEROLE", "NOCREATEROLE"},
155+
{roleInheritAttr, "INHERIT", "NOINHERIT"},
156+
{roleLoginAttr, "LOGIN", "NOLOGIN"},
157+
{roleReplicationAttr, "REPLICATION", "NOREPLICATION"},
158+
{roleBypassRLSAttr, "BYPASSRLS", "NOBYPASSRLS"},
159+
160+
// roleEncryptedPassAttr is used only when rolePasswordAttr is set.
161+
// {roleEncryptedPassAttr, "ENCRYPTED", "UNENCRYPTED"},
162+
}
163+
164+
createOpts := make([]string, 0, len(stringOpts)+len(intOpts)+len(boolOpts))
165+
166+
for _, opt := range stringOpts {
167+
v, ok := d.GetOk(opt.hclKey)
168+
if !ok {
169+
continue
170+
}
171+
172+
val := v.(string)
173+
if val != "" {
174+
switch {
175+
case opt.hclKey == rolePasswordAttr:
176+
if strings.ToUpper(v.(string)) == "NULL" {
177+
createOpts = append(createOpts, "PASSWORD NULL")
178+
} else {
179+
if d.Get(roleEncryptedPassAttr).(bool) {
180+
createOpts = append(createOpts, "ENCRYPTED")
181+
} else {
182+
createOpts = append(createOpts, "UNENCRYPTED")
183+
}
184+
escapedPassword := strconv.Quote(val)
185+
escapedPassword = strings.TrimLeft(escapedPassword, `"`)
186+
escapedPassword = strings.TrimRight(escapedPassword, `"`)
187+
createOpts = append(createOpts, fmt.Sprintf("%s '%s'", opt.sqlKey, escapedPassword))
188+
}
189+
case opt.hclKey == roleValidUntilAttr:
190+
switch {
191+
case v.(string) == "", strings.ToUpper(v.(string)) == "NULL":
192+
createOpts = append(createOpts, fmt.Sprintf("%s %s", opt.sqlKey, "'infinity'"))
193+
default:
194+
createOpts = append(createOpts, fmt.Sprintf("%s %s", opt.sqlKey, pq.QuoteIdentifier(val)))
195+
}
196+
default:
197+
createOpts = append(createOpts, fmt.Sprintf("%s %s", opt.sqlKey, pq.QuoteIdentifier(val)))
198+
}
199+
}
200+
}
201+
202+
for _, opt := range intOpts {
203+
val := d.Get(opt.hclKey).(int)
204+
createOpts = append(createOpts, fmt.Sprintf("%s %d", opt.sqlKey, val))
205+
}
206+
207+
for _, opt := range boolOpts {
208+
if opt.hclKey == roleEncryptedPassAttr {
209+
// This attribute is handled above in the stringOpts
210+
// loop.
211+
continue
212+
}
213+
val := d.Get(opt.hclKey).(bool)
57214

58-
encryptedCfg := getEncryptedStr(d.Get("encrypted").(bool))
215+
valStr := opt.sqlKeyDisable
216+
if val {
217+
valStr = opt.sqlKeyEnable
218+
}
219+
createOpts = append(createOpts, valStr)
220+
}
59221

60-
query := fmt.Sprintf("CREATE ROLE %s %s %s PASSWORD '%s'", pq.QuoteIdentifier(roleName), loginAttr, encryptedCfg, password)
222+
roleName := d.Get(roleNameAttr).(string)
223+
createStr := strings.Join(createOpts, " ")
224+
if len(createOpts) > 0 {
225+
createStr = " WITH " + createStr
226+
}
227+
228+
query := fmt.Sprintf("CREATE ROLE %s%s", pq.QuoteIdentifier(roleName), createStr)
61229
_, err = conn.Query(query)
62230
if err != nil {
63-
return errwrap.Wrapf("Error creating role: {{err}}", err)
231+
return errwrap.Wrapf(fmt.Sprintf("Error creating role %s: {{err}}", roleName), err)
64232
}
65233

66234
d.SetId(roleName)
@@ -76,7 +244,7 @@ func resourcePostgreSQLRoleDelete(d *schema.ResourceData, meta interface{}) erro
76244
}
77245
defer conn.Close()
78246

79-
roleName := d.Get("name").(string)
247+
roleName := d.Get(roleNameAttr).(string)
80248

81249
query := fmt.Sprintf("DROP ROLE %s", pq.QuoteIdentifier(roleName))
82250
_, err = conn.Query(query)
@@ -90,25 +258,32 @@ func resourcePostgreSQLRoleDelete(d *schema.ResourceData, meta interface{}) erro
90258
}
91259

92260
func resourcePostgreSQLRoleRead(d *schema.ResourceData, meta interface{}) error {
93-
client := meta.(*Client)
94-
conn, err := client.Connect()
261+
c := meta.(*Client)
262+
conn, err := c.Connect()
95263
if err != nil {
96264
return err
97265
}
98266
defer conn.Close()
99267

100-
roleName := d.Get("name").(string)
268+
roleName := d.Get(roleNameAttr).(string)
269+
if roleName == "" {
270+
roleName = d.Id()
271+
}
101272

102-
var canLogin bool
103-
err = conn.QueryRow("SELECT rolcanlogin FROM pg_roles WHERE rolname=$1", roleName).Scan(&canLogin)
273+
var roleCanLogin bool
274+
err = conn.QueryRow("SELECT rolcanlogin FROM pg_roles WHERE rolname=$1", roleName).Scan(&roleCanLogin)
104275
switch {
105276
case err == sql.ErrNoRows:
277+
log.Printf("[WARN] PostgreSQL database (%s) not found", d.Id())
106278
d.SetId("")
107279
return nil
108280
case err != nil:
109281
return errwrap.Wrapf("Error reading role: {{err}}", err)
110282
default:
111-
d.Set("login", canLogin)
283+
d.Set(roleNameAttr, roleName)
284+
d.Set(roleLoginAttr, roleCanLogin)
285+
d.Set("encrypted", true)
286+
d.SetId(roleName)
112287
return nil
113288
}
114289
}
@@ -123,21 +298,21 @@ func resourcePostgreSQLRoleUpdate(d *schema.ResourceData, meta interface{}) erro
123298

124299
d.Partial(true)
125300

126-
roleName := d.Get("name").(string)
301+
roleName := d.Get(roleNameAttr).(string)
127302

128-
if d.HasChange("login") {
129-
loginAttr := getLoginStr(d.Get("login").(bool))
303+
if d.HasChange(roleLoginAttr) {
304+
loginAttr := getLoginStr(d.Get(roleLoginAttr).(bool))
130305
query := fmt.Sprintf("ALTER ROLE %s %s", pq.QuoteIdentifier(roleName), pq.QuoteIdentifier(loginAttr))
131306
_, err := conn.Query(query)
132307
if err != nil {
133308
return errwrap.Wrapf("Error updating login attribute for role: {{err}}", err)
134309
}
135310

136-
d.SetPartial("login")
311+
d.SetPartial(roleLoginAttr)
137312
}
138313

139-
password := d.Get("password").(string)
140-
if d.HasChange("password") {
314+
password := d.Get(rolePasswordAttr).(string)
315+
if d.HasChange(rolePasswordAttr) {
141316
encryptedCfg := getEncryptedStr(d.Get("encrypted").(bool))
142317

143318
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
146321
return errwrap.Wrapf("Error updating password attribute for role: {{err}}", err)
147322
}
148323

149-
d.SetPartial("password")
324+
d.SetPartial(rolePasswordAttr)
150325
}
151326

152327
if d.HasChange("encrypted") {

0 commit comments

Comments
 (0)