@@ -3,64 +3,232 @@ package postgresql
33import (
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+
1233func 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
46127func 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
92260func 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