Skip to content

Commit 9c28441

Browse files
postgresql_default_privileges: support of schema object type (#126)
* postgresql_default_privileges: support of schema object type * Add schema test steps to TestAccPostgresqlDefaultPrivileges * Fix id attr value * Move schema tests to TestAccPostgresqlDefaultPrivileges_NoSchema * Check supported PG version * Dedicated acc test for acls on schemas * Fix comment in acc test
1 parent fb106ea commit 9c28441

File tree

5 files changed

+204
-3
lines changed

5 files changed

+204
-3
lines changed

postgresql/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const (
2929
featureReplication
3030
featureExtension
3131
featurePrivileges
32+
featurePrivilegesOnSchemas
3233
featureForceDropDatabase
3334
featurePid
3435
)
@@ -67,6 +68,10 @@ var (
6768
// for Postgresql < 9.
6869
featurePrivileges: semver.MustParseRange(">=9.0.0"),
6970

71+
// ALTER DEFAULT PRIVILEGES has ON SCHEMAS support
72+
// for Postgresql >= 10
73+
featurePrivilegesOnSchemas: semver.MustParseRange(">=10.0.0"),
74+
7075
// DROP DATABASE WITH FORCE
7176
// for Postgresql >= 13
7277
featureForceDropDatabase: semver.MustParseRange(">=13.0.0"),

postgresql/resource_postgresql_default_privileges.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func resourcePostgreSQLDefaultPrivileges() *schema.Resource {
5454
"sequence",
5555
"function",
5656
"type",
57+
"schema",
5758
}, false),
5859
Description: "The PostgreSQL object type to set the default privileges on (one of: table, sequence, function, type)",
5960
},
@@ -76,6 +77,16 @@ func resourcePostgreSQLDefaultPrivileges() *schema.Resource {
7677
}
7778

7879
func resourcePostgreSQLDefaultPrivilegesRead(db *DBConnection, d *schema.ResourceData) error {
80+
pgSchema := d.Get("schema").(string)
81+
objectType := d.Get("object_type").(string)
82+
83+
if pgSchema != "" && objectType == "schema" && !db.featureSupported(featurePrivilegesOnSchemas) {
84+
return fmt.Errorf(
85+
"changing default privileges for schemas is not supported for this Postgres version (%s)",
86+
db.version,
87+
)
88+
}
89+
7990
exists, err := checkRoleDBSchemaExists(db.client, d)
8091
if err != nil {
8192
return err
@@ -95,6 +106,18 @@ func resourcePostgreSQLDefaultPrivilegesRead(db *DBConnection, d *schema.Resourc
95106
}
96107

97108
func resourcePostgreSQLDefaultPrivilegesCreate(db *DBConnection, d *schema.ResourceData) error {
109+
pgSchema := d.Get("schema").(string)
110+
objectType := d.Get("object_type").(string)
111+
112+
if pgSchema != "" && objectType == "schema" {
113+
if !db.featureSupported(featurePrivilegesOnSchemas) {
114+
return fmt.Errorf(
115+
"changing default privileges for schemas is not supported for this Postgres version (%s)",
116+
db.version,
117+
)
118+
}
119+
return fmt.Errorf("cannot specify `schema` when `object_type` is `schema`")
120+
}
98121

99122
if d.Get("with_grant_option").(bool) && strings.ToLower(d.Get("role").(string)) == "public" {
100123
return fmt.Errorf("with_grant_option cannot be true for role 'public'")
@@ -152,6 +175,15 @@ func resourcePostgreSQLDefaultPrivilegesCreate(db *DBConnection, d *schema.Resou
152175

153176
func resourcePostgreSQLDefaultPrivilegesDelete(db *DBConnection, d *schema.ResourceData) error {
154177
owner := d.Get("owner").(string)
178+
pgSchema := d.Get("schema").(string)
179+
objectType := d.Get("object_type").(string)
180+
181+
if pgSchema != "" && objectType == "schema" && !db.featureSupported(featurePrivilegesOnSchemas) {
182+
return fmt.Errorf(
183+
"changing default privileges for schemas is not supported for this Postgres version (%s)",
184+
db.version,
185+
)
186+
}
155187

156188
txn, err := startTransaction(db.client, d.Get("database").(string))
157189
if err != nil {

postgresql/resource_postgresql_default_privileges_test.go

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func TestAccPostgresqlDefaultPrivileges_GrantOwner(t *testing.T) {
142142
var stateConfig = fmt.Sprintf(`
143143
144144
resource postgresql_role "test_owner" {
145-
name = "test_owner"
145+
name = "test_owner"
146146
}
147147
148148
resource "postgresql_default_privileges" "test_ro" {
@@ -151,7 +151,7 @@ resource "postgresql_default_privileges" "test_ro" {
151151
role = "%s"
152152
schema = "public"
153153
object_type = "table"
154-
privileges = ["SELECT"]
154+
privileges = ["SELECT"]
155155
}
156156
`, dbName, roleName)
157157

@@ -210,7 +210,7 @@ resource "postgresql_default_privileges" "test_ro" {
210210
owner = "%s"
211211
role = "%s"
212212
object_type = "table"
213-
privileges = %%s
213+
privileges = %%s
214214
}
215215
`
216216
// We set PGUSER as owner as he will create the test table
@@ -262,3 +262,82 @@ resource "postgresql_default_privileges" "test_ro" {
262262
})
263263
}
264264
}
265+
266+
// Test defaults privileges on schemas
267+
func TestAccPostgresqlDefaultPrivilegesOnSchemas(t *testing.T) {
268+
skipIfNotAcc(t)
269+
270+
// We have to create the database outside of resource.Test
271+
// because we need to create schemas to assert that grant are correctly applied
272+
// and we don't have this resource yet
273+
dbSuffix, teardown := setupTestDatabase(t, true, true)
274+
defer teardown()
275+
276+
config := getTestConfig(t)
277+
dbName, roleName := getTestDBNames(dbSuffix)
278+
279+
// Set default privileges to the test role then to public (i.e.: everyone)
280+
for _, role := range []string{roleName, "public"} {
281+
t.Run(role, func(t *testing.T) {
282+
283+
hclText := `
284+
resource "postgresql_default_privileges" "test_ro" {
285+
database = "%s"
286+
owner = "%s"
287+
role = "%s"
288+
object_type = "schema"
289+
privileges = %%s
290+
}
291+
`
292+
// We set PGUSER as owner as he will create the test schemas
293+
var tfConfig = fmt.Sprintf(hclText, dbName, config.Username, role)
294+
295+
resource.Test(t, resource.TestCase{
296+
PreCheck: func() {
297+
testAccPreCheck(t)
298+
testCheckCompatibleVersion(t, featurePrivileges)
299+
testCheckCompatibleVersion(t, featurePrivilegesOnSchemas)
300+
},
301+
Providers: testAccProviders,
302+
Steps: []resource.TestStep{
303+
{
304+
Config: fmt.Sprintf(tfConfig, `[]`),
305+
Check: resource.ComposeTestCheckFunc(
306+
func(*terraform.State) error {
307+
schemas := []string{"test_schema2", "dev_schema2"}
308+
// To test default privileges, we need to create a schema
309+
// after having apply the state.
310+
dropFunc := createTestSchemas(t, dbSuffix, schemas, "")
311+
defer dropFunc()
312+
313+
return testCheckSchemasPrivileges(t, dbName, roleName, schemas, []string{})
314+
},
315+
resource.TestCheckResourceAttr("postgresql_default_privileges.test_ro", "object_type", "schema"),
316+
resource.TestCheckResourceAttr("postgresql_default_privileges.test_ro", "privileges.#", "0"),
317+
),
318+
},
319+
{
320+
Config: fmt.Sprintf(tfConfig, `["CREATE", "USAGE"]`),
321+
Check: resource.ComposeTestCheckFunc(
322+
func(*terraform.State) error {
323+
schemas := []string{"test_schema2", "dev_schema2"}
324+
// To test default privileges, we need to create a schema
325+
// after having apply the state.
326+
dropFunc := createTestSchemas(t, dbSuffix, schemas, "")
327+
defer dropFunc()
328+
329+
return testCheckSchemasPrivileges(t, dbName, roleName, schemas, []string{"CREATE", "USAGE"})
330+
},
331+
resource.TestCheckResourceAttr(
332+
"postgresql_default_privileges.test_ro", "id", fmt.Sprintf("%s_%s_noschema_%s_schema", role, dbName, config.Username),
333+
),
334+
resource.TestCheckResourceAttr("postgresql_default_privileges.test_ro", "privileges.#", "2"),
335+
resource.TestCheckResourceAttr("postgresql_default_privileges.test_ro", "privileges.2133731197", "CREATE"),
336+
resource.TestCheckResourceAttr("postgresql_default_privileges.test_ro", "privileges.666868928", "USAGE"),
337+
),
338+
},
339+
},
340+
})
341+
})
342+
}
343+
}

postgresql/resource_postgresql_grant.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ var objectTypes = map[string]string{
2828
"sequence": "S",
2929
"function": "f",
3030
"type": "T",
31+
"schema": "n",
3132
}
3233

3334
func resourcePostgreSQLGrant() *schema.Resource {

postgresql/utils_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,71 @@ func createTestTables(t *testing.T, dbSuffix string, tables []string, owner stri
209209
}
210210
}
211211

212+
func createTestSchemas(t *testing.T, dbSuffix string, schemas []string, owner string) func() {
213+
config := getTestConfig(t)
214+
dbName, _ := getTestDBNames(dbSuffix)
215+
adminUser := config.getDatabaseUsername()
216+
217+
db, err := sql.Open("postgres", config.connStr(dbName))
218+
if err != nil {
219+
t.Fatalf("could not open connection pool for db %s: %v", dbName, err)
220+
}
221+
defer db.Close()
222+
223+
if owner != "" {
224+
if !config.Superuser {
225+
if _, err := db.Exec(fmt.Sprintf("GRANT %s TO CURRENT_USER", owner)); err != nil {
226+
t.Fatalf("could not grant role %s to current user: %v", owner, err)
227+
}
228+
}
229+
if _, err := db.Exec(fmt.Sprintf("SET ROLE %s", owner)); err != nil {
230+
t.Fatalf("could not set role to %s: %v", owner, err)
231+
}
232+
}
233+
234+
for _, schema := range schemas {
235+
if _, err := db.Exec(fmt.Sprintf("CREATE SCHEMA %s", schema)); err != nil {
236+
t.Fatalf("could not create test schema in db %s: %v", dbName, err)
237+
}
238+
if owner != "" {
239+
if _, err := db.Exec(fmt.Sprintf("ALTER SCHEMA %s OWNER TO %s", schema, owner)); err != nil {
240+
t.Fatalf("could not set test schema owner to %s: %v", owner, err)
241+
}
242+
}
243+
}
244+
if owner != "" && !config.Superuser {
245+
if _, err := db.Exec(fmt.Sprintf("SET ROLE %s; REVOKE %s FROM %s", adminUser, owner, adminUser)); err != nil {
246+
t.Fatalf("could not revoke role %s from %s: %v", owner, adminUser, err)
247+
}
248+
}
249+
250+
// In this case we need to drop schema after each test.
251+
return func() {
252+
db, err := sql.Open("postgres", config.connStr(dbName))
253+
if err != nil {
254+
t.Fatalf("could not open connection pool for db %s: %v", dbName, err)
255+
}
256+
defer db.Close()
257+
258+
if owner != "" && !config.Superuser {
259+
if _, err := db.Exec(fmt.Sprintf("GRANT %s TO CURRENT_USER", owner)); err != nil {
260+
t.Fatalf("could not grant role %s to current user: %v", owner, err)
261+
}
262+
}
263+
264+
for _, schema := range schemas {
265+
if _, err := db.Exec(fmt.Sprintf("DROP SCHEMA %s CASCADE", schema)); err != nil {
266+
t.Fatalf("could not drop schema %s: %v", schema, err)
267+
}
268+
}
269+
if owner != "" && !config.Superuser {
270+
if _, err := db.Exec(fmt.Sprintf("SET ROLE %s; REVOKE %s FROM %s", adminUser, owner, adminUser)); err != nil {
271+
t.Fatalf("could not revoke role %s from %s: %v", owner, adminUser, err)
272+
}
273+
}
274+
}
275+
}
276+
212277
// testHasGrantForQuery executes a query and checks that it fails if
213278
// we were not allowed or succeses if we're allowed.
214279
func testHasGrantForQuery(db *sql.DB, query string, allowed bool) error {
@@ -260,3 +325,22 @@ func testCheckTablesPrivileges(t *testing.T, dbName, roleName string, tables []s
260325
}
261326
return nil
262327
}
328+
329+
func testCheckSchemasPrivileges(t *testing.T, dbName, roleName string, schemas []string, allowedPrivileges []string) error {
330+
db := connectAsTestRole(t, roleName, dbName)
331+
defer db.Close()
332+
333+
for _, schema := range schemas {
334+
queries := map[string]string{
335+
"USAGE": fmt.Sprintf("DROP TABLE IF EXISTS %s.test_table", schema),
336+
"CREATE": fmt.Sprintf("CREATE TABLE %s.test_table()", schema),
337+
}
338+
339+
for queryType, query := range queries {
340+
if err := testHasGrantForQuery(db, query, sliceContainsStr(allowedPrivileges, queryType)); err != nil {
341+
return err
342+
}
343+
}
344+
}
345+
return nil
346+
}

0 commit comments

Comments
 (0)