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

Commit 450634a

Browse files
committed
Fix grant on database and write an integration test.
1 parent c05cf9e commit 450634a

5 files changed

+159
-99
lines changed

postgresql/helpers.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package postgresql
22

33
import (
4+
"database/sql"
45
"fmt"
56
"log"
67
"strings"
78

8-
"database/sql"
9-
109
"github.com/hashicorp/errwrap"
1110
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
1211
"github.com/lib/pq"
@@ -111,7 +110,15 @@ var allowedPrivileges = map[string][]string{
111110
}
112111

113112
// validatePrivileges checks that privileges to apply are allowed for this object type.
114-
func validatePrivileges(objectType string, privileges []interface{}) error {
113+
func validatePrivileges(d *schema.ResourceData) error {
114+
objectType := d.Get("object_type").(string)
115+
privileges := d.Get("privileges").(*schema.Set).List()
116+
117+
// Verify fields that are mandatory for specific object types
118+
if objectType != "database" && d.Get("schema").(string) == "" {
119+
return fmt.Errorf("parameter 'schema' is mandatory for object_type %s", objectType)
120+
}
121+
115122
allowed, ok := allowedPrivileges[objectType]
116123
if !ok {
117124
return fmt.Errorf("unknown object type %s", objectType)

postgresql/resource_postgresql_default_privileges.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func resourcePostgreSQLDefaultPrivilegesRead(d *schema.ResourceData, meta interf
9393
}
9494

9595
func resourcePostgreSQLDefaultPrivilegesCreate(d *schema.ResourceData, meta interface{}) error {
96-
if err := validatePrivileges(d.Get("object_type").(string), d.Get("privileges").(*schema.Set).List()); err != nil {
96+
if err := validatePrivileges(d); err != nil {
9797
return err
9898
}
9999

@@ -271,7 +271,6 @@ func revokeRoleDefaultPrivileges(txn *sql.Tx, d *schema.ResourceData) error {
271271
return errwrap.Wrapf("could not revoke default privileges: {{err}}", err)
272272
}
273273
return nil
274-
275274
}
276275

277276
func generateDefaultPrivilegesID(d *schema.ResourceData) string {

postgresql/resource_postgresql_grant.go

+69-31
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,18 @@ import (
1414
"github.com/lib/pq"
1515
)
1616

17+
var allowedObjectTypes = []string{
18+
"database",
19+
"table",
20+
"sequence",
21+
}
22+
1723
var objectTypes = map[string]string{
18-
"database": "d",
1924
"table": "r",
2025
"sequence": "S",
2126
}
2227

2328
func resourcePostgreSQLGrant() *schema.Resource {
24-
25-
allowedObjectTypes := make([]string, 0, len(objectTypes))
26-
27-
for k := range objectTypes {
28-
allowedObjectTypes = append(allowedObjectTypes, k)
29-
}
30-
3129
return &schema.Resource{
3230
Create: resourcePostgreSQLGrantCreate,
3331
// As create revokes and grants we can use it to update too
@@ -50,7 +48,7 @@ func resourcePostgreSQLGrant() *schema.Resource {
5048
},
5149
"schema": {
5250
Type: schema.TypeString,
53-
Required: true,
51+
Optional: true,
5452
ForceNew: true,
5553
Description: "The database schema to grant privileges on for this role",
5654
},
@@ -122,7 +120,7 @@ func resourcePostgreSQLGrantCreate(d *schema.ResourceData, meta interface{}) err
122120
)
123121
}
124122

125-
if err := validatePrivileges(d.Get("object_type").(string), d.Get("privileges").(*schema.Set).List()); err != nil {
123+
if err := validatePrivileges(d); err != nil {
126124
return err
127125
}
128126

@@ -193,7 +191,38 @@ func resourcePostgreSQLGrantDelete(d *schema.ResourceData, meta interface{}) err
193191
return nil
194192
}
195193

194+
func readDatabaseRolePriviges(txn *sql.Tx, d *schema.ResourceData) error {
195+
query := `
196+
SELECT privilege_type
197+
FROM (
198+
SELECT (aclexplode(datacl)).* FROM pg_database WHERE datname=$1
199+
) as privileges
200+
JOIN pg_roles ON grantee = pg_roles.oid WHERE rolname = $2
201+
`
202+
203+
privileges := []string{}
204+
rows, err := txn.Query(query, d.Get("database"), d.Get("role"))
205+
if err != nil {
206+
return errwrap.Wrapf("could not read database privileges: {{err}}", err)
207+
}
208+
209+
for rows.Next() {
210+
var privilegeType string
211+
if err := rows.Scan(&privilegeType); err != nil {
212+
return errwrap.Wrapf("could not scan database privilege: {{err}}", err)
213+
}
214+
privileges = append(privileges, privilegeType)
215+
}
216+
217+
d.Set("privileges", privileges)
218+
return nil
219+
}
220+
196221
func readRolePrivileges(txn *sql.Tx, d *schema.ResourceData) error {
222+
if d.Get("object_type").(string) == "database" {
223+
return readDatabaseRolePriviges(txn, d)
224+
}
225+
197226
// This returns, for the specified role (rolname),
198227
// the list of all object of the specified type (relkind) in the specified schema (namespace)
199228
// with the list of the currently applied privileges (aggregation of privilege_type)
@@ -312,8 +341,10 @@ func grantRolePrivileges(txn *sql.Tx, d *schema.ResourceData) error {
312341

313342
func revokeRolePrivileges(txn *sql.Tx, d *schema.ResourceData) error {
314343
query := createRevokeQuery(d)
315-
_, err := txn.Exec(query)
316-
return err
344+
if _, err := txn.Exec(query); err != nil {
345+
return errwrap.Wrapf("could not execute revoke query: {{err}}", err)
346+
}
347+
return nil
317348
}
318349

319350
func checkRoleDBSchemaExists(client *Client, d *schema.ResourceData) (bool, error) {
@@ -345,30 +376,37 @@ func checkRoleDBSchemaExists(client *Client, d *schema.ResourceData) (bool, erro
345376
return false, nil
346377
}
347378

348-
// Connect on this database to check if schema exists
349-
dbTxn, err := startTransaction(client, database)
350-
if err != nil {
351-
return false, err
352-
}
353-
defer dbTxn.Rollback()
379+
if d.Get("object_type").(string) != "database" {
380+
// Connect on this database to check if schema exists
381+
dbTxn, err := startTransaction(client, database)
382+
if err != nil {
383+
return false, err
384+
}
385+
defer dbTxn.Rollback()
354386

355-
// Check the schema exists (the SQL connection needs to be on the right database)
356-
pgSchema := d.Get("schema").(string)
357-
exists, err = schemaExists(dbTxn, pgSchema)
358-
if err != nil {
359-
return false, err
360-
}
361-
if !exists {
362-
log.Printf("[DEBUG] schema %s does not exists", pgSchema)
363-
return false, nil
387+
// Check the schema exists (the SQL connection needs to be on the right database)
388+
pgSchema := d.Get("schema").(string)
389+
exists, err = schemaExists(dbTxn, pgSchema)
390+
if err != nil {
391+
return false, err
392+
}
393+
if !exists {
394+
log.Printf("[DEBUG] schema %s does not exists", pgSchema)
395+
return false, nil
396+
}
364397
}
365398

366399
return true, nil
367400
}
368401

369402
func generateGrantID(d *schema.ResourceData) string {
370-
return strings.Join([]string{
371-
d.Get("role").(string), d.Get("database").(string),
372-
d.Get("schema").(string), d.Get("object_type").(string),
373-
}, "_")
403+
parts := []string{d.Get("role").(string), d.Get("database").(string)}
404+
405+
objectType := d.Get("object_type").(string)
406+
if objectType != "database" {
407+
parts = append(parts, d.Get("schema").(string))
408+
}
409+
parts = append(parts, objectType)
410+
411+
return strings.Join(parts, "_")
374412
}

postgresql/resource_postgresql_grant_test.go

+47-22
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ func TestAccPostgresqlGrant(t *testing.T) {
178178
{
179179
Config: testGrantSelect,
180180
Check: resource.ComposeTestCheckFunc(
181+
resource.TestCheckResourceAttr(
182+
"postgresql_grant.test", "id", fmt.Sprintf("%s_%s_test_schema_table", roleName, dbName),
183+
),
181184
resource.TestCheckResourceAttr("postgresql_grant.test", "privileges.#", "1"),
182185
resource.TestCheckResourceAttr("postgresql_grant.test", "privileges.3138006342", "SELECT"),
183186
func(*terraform.State) error {
@@ -213,25 +216,27 @@ func TestAccPostgresqlGrant(t *testing.T) {
213216
}
214217

215218
func TestAccPostgresqlGrantDatabase(t *testing.T) {
216-
skipIfNotAcc(t)
219+
// create a TF config with placeholder for privileges
220+
// it will be filled in each step.
221+
config := fmt.Sprintf(`
222+
resource "postgresql_role" "test" {
223+
name = "test_grant_role"
224+
password = "%s"
225+
login = true
226+
}
217227
218-
// We have to create the database outside of resource.Test
219-
// because we need to create tables to assert that grant are correctly applied
220-
// and we don't have this resource yet
221-
dbSuffix, teardown := setupTestDatabase(t, true, true)
222-
defer teardown()
228+
resource "postgresql_database" "test_db" {
229+
depends_on = [postgresql_role.test]
230+
name = "test_grant_db"
231+
}
223232
224-
dbName, roleName := getTestDBNames(dbSuffix)
225-
var testGrantSelect = fmt.Sprintf(`
226-
resource "postgresql_grant" "test" {
227-
database = "%s"
228-
role = "%s"
229-
schema = "test_schema"
230-
object_type = "database"
231-
privileges = ["CONNECT", "CREATE"]
232-
with_grant_option = true
233-
}
234-
`, dbName, roleName)
233+
resource "postgresql_grant" "test" {
234+
database = postgresql_database.test_db.name
235+
role = postgresql_role.test.name
236+
object_type = "database"
237+
privileges = %%s
238+
}
239+
`, testRolePassword)
235240

236241
resource.Test(t, resource.TestCase{
237242
PreCheck: func() {
@@ -240,16 +245,36 @@ func TestAccPostgresqlGrantDatabase(t *testing.T) {
240245
},
241246
Providers: testAccProviders,
242247
Steps: []resource.TestStep{
248+
// Not allowed to create
243249
{
244-
Config: testGrantSelect,
250+
Config: fmt.Sprintf(config, "[\"CONNECT\"]"),
251+
Check: resource.ComposeTestCheckFunc(
252+
resource.TestCheckResourceAttr("postgresql_grant.test", "id", "test_grant_role_test_grant_db_database"),
253+
resource.TestCheckResourceAttr("postgresql_grant.test", "privileges.#", "1"),
254+
resource.TestCheckResourceAttr("postgresql_grant.test", "with_grant_option", "false"),
255+
testCheckDatabasesPrivileges(t, false),
256+
),
257+
},
258+
// Can create but not grant
259+
{
260+
Config: fmt.Sprintf(config, "[\"CONNECT\", \"CREATE\"]"),
245261
Check: resource.ComposeTestCheckFunc(
246262
resource.TestCheckResourceAttr("postgresql_grant.test", "privileges.#", "2"),
247-
resource.TestCheckResourceAttr("postgresql_grant.test", "with_grant_option", "true"),
248-
func(*terraform.State) error {
249-
return testCheckDatabasesPrivileges(t, dbSuffix, []string{"CONNECT"})
250-
},
263+
testCheckDatabasesPrivileges(t, true),
251264
),
252265
},
253266
},
254267
})
255268
}
269+
270+
func testCheckDatabasesPrivileges(t *testing.T, canCreate bool) func(*terraform.State) error {
271+
return func(*terraform.State) error {
272+
db := connectAsTestRole(t, "test_grant_role", "test_grant_db")
273+
defer db.Close()
274+
275+
if err := testHasGrantForQuery(db, "CREATE SCHEMA plop", canCreate); err != nil {
276+
return err
277+
}
278+
return nil
279+
}
280+
}

postgresql/utils_test.go

+32-41
Original file line numberDiff line numberDiff line change
@@ -151,18 +151,45 @@ func createTestTables(t *testing.T, dbSuffix string, tables []string, owner stri
151151
}
152152
}
153153

154-
func testCheckTablesPrivileges(t *testing.T, dbSuffix string, tables []string, allowedPrivileges []string) error {
154+
// testHasGrantForQuery executes a query and checks that it fails if
155+
// we were not allowed or succeses if we're allowed.
156+
func testHasGrantForQuery(db *sql.DB, query string, allowed bool) error {
157+
_, err := db.Exec(query)
158+
if err != nil {
159+
if allowed {
160+
return errwrap.Wrapf(
161+
fmt.Sprintf(
162+
"could not execute %s as expected: {{err}}", query,
163+
), err,
164+
)
165+
}
166+
return nil
167+
}
168+
169+
if !allowed {
170+
return fmt.Errorf("did not fail as expected when executing query '%s'", query)
171+
}
172+
return nil
173+
}
174+
175+
func connectAsTestRole(t *testing.T, role, dbName string) *sql.DB {
155176
config := getTestConfig(t)
156-
dbName, roleName := getTestDBNames(dbSuffix)
157177

158178
// Connect as the test role
159-
config.Username = roleName
179+
config.Username = role
160180
config.Password = testRolePassword
161181

162182
db, err := sql.Open("postgres", config.connStr(dbName))
163183
if err != nil {
164184
t.Fatalf("could not open connection pool for db %s: %v", dbName, err)
165185
}
186+
return db
187+
}
188+
189+
func testCheckTablesPrivileges(t *testing.T, dbSuffix string, tables []string, allowedPrivileges []string) error {
190+
dbName, roleName := getTestDBNames(dbSuffix)
191+
192+
db := connectAsTestRole(t, roleName, dbName)
166193
defer db.Close()
167194

168195
for _, table := range tables {
@@ -174,46 +201,10 @@ func testCheckTablesPrivileges(t *testing.T, dbSuffix string, tables []string, a
174201
}
175202

176203
for queryType, query := range queries {
177-
_, err := db.Exec(query)
178-
179-
if err != nil && sliceContainsStr(allowedPrivileges, queryType) {
180-
return errwrap.Wrapf(
181-
fmt.Sprintf("could not %s on test table %s: {{err}}", queryType, table),
182-
err,
183-
)
184-
185-
} else if err == nil && !sliceContainsStr(allowedPrivileges, queryType) {
186-
return errwrap.Wrapf(
187-
fmt.Sprintf("%s did not failed as expected for table %s: {{err}}", queryType, table),
188-
err,
189-
)
204+
if err := testHasGrantForQuery(db, query, sliceContainsStr(allowedPrivileges, queryType)); err != nil {
205+
return err
190206
}
191207
}
192208
}
193209
return nil
194210
}
195-
196-
func testCheckDatabasesPrivileges(t *testing.T, dbSuffix string, allowedPrivileges []string) error {
197-
config := getTestConfig(t)
198-
dbName, roleName := getTestDBNames(dbSuffix)
199-
200-
// Connect as the test role
201-
config.Username = roleName
202-
config.Password = testRolePassword
203-
204-
db, err := sql.Open("postgres", config.connStr(dbName))
205-
if err != nil {
206-
t.Fatalf("could not open connection pool for db %s: %v", dbName, err)
207-
}
208-
defer db.Close()
209-
210-
queries := map[string]string{
211-
"CREATE": fmt.Sprintf("CREATE DATABASE foo"),
212-
}
213-
214-
for _, query := range queries {
215-
db.Exec(query)
216-
}
217-
218-
return nil
219-
}

0 commit comments

Comments
 (0)