Skip to content

Commit 1caee37

Browse files
authored
Fix cyrilgdn#321: Always use a single transaction when changing grant (#9)
Fix cyrilgdn#321
1 parent 2a13d82 commit 1caee37

File tree

2 files changed

+76
-54
lines changed

2 files changed

+76
-54
lines changed

postgresql/resource_postgresql_grant.go

Lines changed: 72 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ var objectTypes = map[string]string{
3737
func resourcePostgreSQLGrant() *schema.Resource {
3838
return &schema.Resource{
3939
Create: PGResourceFunc(resourcePostgreSQLGrantCreate),
40-
// Since all of this resource's arguments force a recreation
41-
// there's no need for an Update function
42-
// Update:
40+
Update: PGResourceFunc(resourcePostgreSQLGrantUpdate),
4341
Read: PGResourceFunc(resourcePostgreSQLGrantRead),
4442
Delete: PGResourceFunc(resourcePostgreSQLGrantDelete),
4543

@@ -57,46 +55,46 @@ func resourcePostgreSQLGrant() *schema.Resource {
5755
Description: "The database to grant privileges on for this role",
5856
},
5957
"schema": {
60-
Type: schema.TypeString,
61-
Optional: true,
62-
ForceNew: true,
58+
Type: schema.TypeString,
59+
Optional: true,
60+
// ForceNew: true,
6361
Description: "The database schema to grant privileges on for this role",
6462
},
6563
"object_type": {
66-
Type: schema.TypeString,
67-
Required: true,
68-
ForceNew: true,
64+
Type: schema.TypeString,
65+
Required: true,
66+
// ForceNew: true,
6967
ValidateFunc: validation.StringInSlice(allowedObjectTypes, false),
7068
Description: "The PostgreSQL object type to grant the privileges on (one of: " + strings.Join(allowedObjectTypes, ", ") + ")",
7169
},
7270
"objects": {
73-
Type: schema.TypeSet,
74-
Optional: true,
75-
ForceNew: true,
71+
Type: schema.TypeSet,
72+
Optional: true,
73+
// ForceNew: true,
7674
Elem: &schema.Schema{Type: schema.TypeString},
7775
Set: schema.HashString,
7876
Description: "The specific objects to grant privileges on for this role (empty means all objects of the requested type)",
7977
},
8078
"columns": {
81-
Type: schema.TypeSet,
82-
Optional: true,
83-
ForceNew: true,
79+
Type: schema.TypeSet,
80+
Optional: true,
81+
// ForceNew: true,
8482
Elem: &schema.Schema{Type: schema.TypeString},
8583
Set: schema.HashString,
8684
Description: "The specific columns to grant privileges on for this role",
8785
},
8886
"privileges": {
89-
Type: schema.TypeSet,
90-
Required: true,
91-
ForceNew: true,
87+
Type: schema.TypeSet,
88+
Required: true,
89+
// ForceNew: true,
9290
Elem: &schema.Schema{Type: schema.TypeString},
9391
Set: schema.HashString,
9492
Description: "The list of privileges to grant",
9593
},
9694
"with_grant_option": {
97-
Type: schema.TypeBool,
98-
Optional: true,
99-
ForceNew: true,
95+
Type: schema.TypeBool,
96+
Optional: true,
97+
// ForceNew: true,
10098
Default: false,
10199
Description: "Permit the grant recipient to grant it to others",
102100
},
@@ -129,6 +127,10 @@ func resourcePostgreSQLGrantRead(db *DBConnection, d *schema.ResourceData) error
129127
}
130128

131129
func resourcePostgreSQLGrantCreate(db *DBConnection, d *schema.ResourceData) error {
130+
return resourcePostgreSQLGrantCreateOrUpdate(db, d, false)
131+
}
132+
133+
func resourcePostgreSQLGrantCreateOrUpdate(db *DBConnection, d *schema.ResourceData, usePreviousForRevoke bool) error {
132134
if err := validateFeatureSupport(db, d); err != nil {
133135
return fmt.Errorf("feature is not supported: %v", err)
134136
}
@@ -187,7 +189,7 @@ func resourcePostgreSQLGrantCreate(db *DBConnection, d *schema.ResourceData) err
187189
// Revoke all privileges before granting otherwise reducing privileges will not work.
188190
// We just have to revoke them in the same transaction so the role will not lost its
189191
// privileges between the revoke and grant statements.
190-
if err := revokeRolePrivileges(txn, d); err != nil {
192+
if err := revokeRolePrivileges(txn, d, usePreviousForRevoke); err != nil {
191193
return err
192194
}
193195
if err := grantRolePrivileges(txn, d); err != nil {
@@ -213,6 +215,10 @@ func resourcePostgreSQLGrantCreate(db *DBConnection, d *schema.ResourceData) err
213215
return readRolePrivileges(txn, d)
214216
}
215217

218+
func resourcePostgreSQLGrantUpdate(db *DBConnection, d *schema.ResourceData) error {
219+
return resourcePostgreSQLGrantCreateOrUpdate(db, d, true)
220+
}
221+
216222
func resourcePostgreSQLGrantDelete(db *DBConnection, d *schema.ResourceData) error {
217223
if err := validateFeatureSupport(db, d); err != nil {
218224
return fmt.Errorf("feature is not supported: %v", err)
@@ -243,7 +249,7 @@ func resourcePostgreSQLGrantDelete(db *DBConnection, d *schema.ResourceData) err
243249
}
244250

245251
if err := withRolesGranted(txn, owners, func() error {
246-
return revokeRolePrivileges(txn, d)
252+
return revokeRolePrivileges(txn, d, false)
247253
}); err != nil {
248254
return err
249255
}
@@ -589,40 +595,42 @@ func createGrantQuery(d *schema.ResourceData, privileges []string) string {
589595
return query
590596
}
591597

592-
func createRevokeQuery(d *schema.ResourceData) string {
598+
type ResourceSchemGetter func(string) interface{}
599+
600+
func createRevokeQuery(getter ResourceSchemGetter) string {
593601
var query string
594602

595-
switch strings.ToUpper(d.Get("object_type").(string)) {
603+
switch strings.ToUpper(getter("object_type").(string)) {
596604
case "DATABASE":
597605
query = fmt.Sprintf(
598606
"REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s",
599-
pq.QuoteIdentifier(d.Get("database").(string)),
600-
pq.QuoteIdentifier(d.Get("role").(string)),
607+
pq.QuoteIdentifier(getter("database").(string)),
608+
pq.QuoteIdentifier(getter("role").(string)),
601609
)
602610
case "SCHEMA":
603611
query = fmt.Sprintf(
604612
"REVOKE ALL PRIVILEGES ON SCHEMA %s FROM %s",
605-
pq.QuoteIdentifier(d.Get("schema").(string)),
606-
pq.QuoteIdentifier(d.Get("role").(string)),
613+
pq.QuoteIdentifier(getter("schema").(string)),
614+
pq.QuoteIdentifier(getter("role").(string)),
607615
)
608616
case "FOREIGN_DATA_WRAPPER":
609-
fdwName := d.Get("objects").(*schema.Set).List()[0]
617+
fdwName := getter("objects").(*schema.Set).List()[0]
610618
query = fmt.Sprintf(
611619
"REVOKE ALL PRIVILEGES ON FOREIGN DATA WRAPPER %s FROM %s",
612620
pq.QuoteIdentifier(fdwName.(string)),
613-
pq.QuoteIdentifier(d.Get("role").(string)),
621+
pq.QuoteIdentifier(getter("role").(string)),
614622
)
615623
case "FOREIGN_SERVER":
616-
srvName := d.Get("objects").(*schema.Set).List()[0]
624+
srvName := getter("objects").(*schema.Set).List()[0]
617625
query = fmt.Sprintf(
618626
"REVOKE ALL PRIVILEGES ON FOREIGN SERVER %s FROM %s",
619627
pq.QuoteIdentifier(srvName.(string)),
620-
pq.QuoteIdentifier(d.Get("role").(string)),
628+
pq.QuoteIdentifier(getter("role").(string)),
621629
)
622630
case "COLUMN":
623-
objects := d.Get("objects").(*schema.Set)
624-
columns := d.Get("columns").(*schema.Set)
625-
privileges := d.Get("privileges").(*schema.Set)
631+
objects := getter("objects").(*schema.Set)
632+
columns := getter("columns").(*schema.Set)
633+
privileges := getter("privileges").(*schema.Set)
626634
if privileges.Len() == 0 || columns.Len() == 0 {
627635
// No privileges to revoke, so don't revoke anything
628636
query = "SELECT NULL"
@@ -631,38 +639,38 @@ func createRevokeQuery(d *schema.ResourceData) string {
631639
"REVOKE %s (%s) ON TABLE %s FROM %s",
632640
setToPgIdentSimpleList(privileges),
633641
setToPgIdentListWithoutSchema(columns),
634-
setToPgIdentList(d.Get("schema").(string), objects),
635-
pq.QuoteIdentifier(d.Get("role").(string)),
642+
setToPgIdentList(getter("schema").(string), objects),
643+
pq.QuoteIdentifier(getter("role").(string)),
636644
)
637645
}
638646
case "TABLE", "SEQUENCE", "FUNCTION", "PROCEDURE", "ROUTINE":
639-
objects := d.Get("objects").(*schema.Set)
640-
privileges := d.Get("privileges").(*schema.Set)
647+
objects := getter("objects").(*schema.Set)
648+
privileges := getter("privileges").(*schema.Set)
641649
if objects.Len() > 0 {
642650
if privileges.Len() > 0 {
643651
// Revoking specific privileges instead of all privileges
644652
// to avoid messing with column level grants
645653
query = fmt.Sprintf(
646654
"REVOKE %s ON %s %s FROM %s",
647655
setToPgIdentSimpleList(privileges),
648-
strings.ToUpper(d.Get("object_type").(string)),
649-
setToPgIdentList(d.Get("schema").(string), objects),
650-
pq.QuoteIdentifier(d.Get("role").(string)),
656+
strings.ToUpper(getter("object_type").(string)),
657+
setToPgIdentList(getter("schema").(string), objects),
658+
pq.QuoteIdentifier(getter("role").(string)),
651659
)
652660
} else {
653661
query = fmt.Sprintf(
654662
"REVOKE ALL PRIVILEGES ON %s %s FROM %s",
655-
strings.ToUpper(d.Get("object_type").(string)),
656-
setToPgIdentList(d.Get("schema").(string), objects),
657-
pq.QuoteIdentifier(d.Get("role").(string)),
663+
strings.ToUpper(getter("object_type").(string)),
664+
setToPgIdentList(getter("schema").(string), objects),
665+
pq.QuoteIdentifier(getter("role").(string)),
658666
)
659667
}
660668
} else {
661669
query = fmt.Sprintf(
662670
"REVOKE ALL PRIVILEGES ON ALL %sS IN SCHEMA %s FROM %s",
663-
strings.ToUpper(d.Get("object_type").(string)),
664-
pq.QuoteIdentifier(d.Get("schema").(string)),
665-
pq.QuoteIdentifier(d.Get("role").(string)),
671+
strings.ToUpper(getter("object_type").(string)),
672+
pq.QuoteIdentifier(getter("schema").(string)),
673+
pq.QuoteIdentifier(getter("role").(string)),
666674
)
667675
}
668676
}
@@ -675,24 +683,35 @@ func grantRolePrivileges(txn *sql.Tx, d *schema.ResourceData) error {
675683
for _, priv := range d.Get("privileges").(*schema.Set).List() {
676684
privileges = append(privileges, priv.(string))
677685
}
678-
679686
if len(privileges) == 0 {
680687
log.Printf("[DEBUG] no privileges to grant for role %s in database: %s,", d.Get("role").(string), d.Get("database"))
681688
return nil
682689
}
683-
684690
query := createGrantQuery(d, privileges)
691+
log.Printf("[INFO] executing %s", query)
685692

686693
_, err := txn.Exec(query)
687694
return err
688695
}
689696

690-
func revokeRolePrivileges(txn *sql.Tx, d *schema.ResourceData) error {
691-
query := createRevokeQuery(d)
697+
func revokeRolePrivileges(txn *sql.Tx, d *schema.ResourceData, usePrevious bool) error {
698+
var getter ResourceSchemGetter
699+
if usePrevious {
700+
getter = func(name string) interface{} {
701+
old, _ := d.GetChange(name)
702+
return old
703+
}
704+
} else {
705+
getter = func(name string) interface{} {
706+
return d.Get(name)
707+
}
708+
}
709+
query := createRevokeQuery(getter)
692710
if len(query) == 0 {
693711
// Query is empty, don't run anything
694712
return nil
695713
}
714+
log.Printf("[INFO] executing %s", query)
696715
if _, err := txn.Exec(query); err != nil {
697716
return fmt.Errorf("could not execute revoke query: %w", err)
698717
}

postgresql/resource_postgresql_grant_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,10 @@ func TestCreateRevokeQuery(t *testing.T) {
293293
}
294294

295295
for _, c := range cases {
296-
out := createRevokeQuery(c.resource)
296+
getter := func(name string) interface{} {
297+
return c.resource.Get(name)
298+
}
299+
out := createRevokeQuery(getter)
297300
if out != c.expected {
298301
t.Fatalf("Error matching output and expected: %#v vs %#v", out, c.expected)
299302
}

0 commit comments

Comments
 (0)