diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 77c4d5b3..1ec3320b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - pgversion: [14, 13, 12, 11] + pgversion: [15, 14, 13, 12, 11] env: PGVERSION: ${{ matrix.pgversion }} diff --git a/postgresql/config.go b/postgresql/config.go index 10094128..8ab56d3e 100644 --- a/postgresql/config.go +++ b/postgresql/config.go @@ -21,6 +21,7 @@ type featureName uint const ( featureCreateRoleWith featureName = iota + featureDatabaseOwnerRole featureDBAllowConnections featureDBIsTemplate featureFallbackApplicationName @@ -109,6 +110,8 @@ var ( featureFunction: semver.MustParseRange(">=8.4.0"), // CREATE SERVER support featureServer: semver.MustParseRange(">=10.0.0"), + + featureDatabaseOwnerRole: semver.MustParseRange(">=15.0.0"), } ) diff --git a/postgresql/helpers.go b/postgresql/helpers.go index e2281398..1cc0cd1d 100644 --- a/postgresql/helpers.go +++ b/postgresql/helpers.go @@ -420,15 +420,24 @@ func getDatabase(d *schema.ResourceData, databaseName string) string { } func getDatabaseOwner(db QueryAble, database string) (string, error) { - query := ` + dbQueryString := "$1" + dbQueryValues := []interface{}{database} + + // Empty means current DB + if database == "" { + dbQueryString = "current_database()" + dbQueryValues = []interface{}{} + + } + query := fmt.Sprintf(` SELECT rolname FROM pg_database JOIN pg_roles ON datdba = pg_roles.oid - WHERE datname = $1 -` + WHERE datname = %s +`, dbQueryString) var owner string - err := db.QueryRow(query, database).Scan(&owner) + err := db.QueryRow(query, dbQueryValues...).Scan(&owner) switch { case err == sql.ErrNoRows: return "", fmt.Errorf("could not find database '%s' while looking for owner", database) @@ -479,6 +488,22 @@ func getTablesOwner(db QueryAble, schemaName string) ([]string, error) { return owners, nil } +func resolveOwners(db QueryAble, owners []string) ([]string, error) { + resolvedOwners := []string{} + for _, owner := range owners { + if owner == "pg_database_owner" { + var err error + owner, err = getDatabaseOwner(db, "") + if err != nil { + return nil, err + } + } + resolvedOwners = append(resolvedOwners, owner) + } + + return resolvedOwners, nil +} + func isSuperuser(db QueryAble, role string) (bool, error) { var superuser bool diff --git a/postgresql/resource_postgresql_default_privileges_test.go b/postgresql/resource_postgresql_default_privileges_test.go index fa03093b..5c779d06 100644 --- a/postgresql/resource_postgresql_default_privileges_test.go +++ b/postgresql/resource_postgresql_default_privileges_test.go @@ -145,6 +145,15 @@ resource postgresql_role "test_owner" { name = "test_owner" } +// From PostgreSQL 15, schema public is not wild open anymore +resource "postgresql_grant" "public_usage" { + database = "%s" + schema = "public" + role = postgresql_role.test_owner.name + object_type = "schema" + privileges = ["CREATE", "USAGE"] +} + resource "postgresql_default_privileges" "test_ro" { database = "%s" owner = postgresql_role.test_owner.name @@ -153,7 +162,7 @@ resource "postgresql_default_privileges" "test_ro" { object_type = "table" privileges = ["SELECT"] } - `, dbName, roleName) + `, dbName, dbName, roleName) resource.Test(t, resource.TestCase{ PreCheck: func() { diff --git a/postgresql/resource_postgresql_grant.go b/postgresql/resource_postgresql_grant.go index 8e0b315f..f4a5a6cb 100644 --- a/postgresql/resource_postgresql_grant.go +++ b/postgresql/resource_postgresql_grant.go @@ -803,6 +803,11 @@ func getRolesToGrant(txn *sql.Tx, d *schema.ResourceData) ([]string, error) { owners = append(owners, schemaOwner) } + owners, err = resolveOwners(txn, owners) + if err != nil { + return nil, err + } + return owners, nil } diff --git a/postgresql/resource_postgresql_grant_test.go b/postgresql/resource_postgresql_grant_test.go index a31aabb4..a81c95bc 100644 --- a/postgresql/resource_postgresql_grant_test.go +++ b/postgresql/resource_postgresql_grant_test.go @@ -1,6 +1,7 @@ package postgresql import ( + "database/sql" "fmt" "regexp" "strings" @@ -1287,6 +1288,15 @@ resource "postgresql_role" "test" { login = true } +// From PostgreSQL 15, schema public is not wild open anymore +resource "postgresql_grant" "public_usage" { + database = "postgres" + schema = "public" + role = postgresql_role.test.name + object_type = "schema" + privileges = ["CREATE", "USAGE"] +} + resource "postgresql_grant" "test" { depends_on = [postgresql_role.test] database = "postgres" @@ -1327,6 +1337,73 @@ resource "postgresql_grant" "test" { }) } +func TestAccPostgresqlGrantOwnerPG15(t *testing.T) { + skipIfNotAcc(t) + + dbSuffix, teardown := setupTestDatabase(t, true, true) + defer teardown() + + testTables := []string{"test_schema.test_table"} + createTestTables(t, dbSuffix, testTables, "") + + dbName, roleName := getTestDBNames(dbSuffix) + + var tfConfig = fmt.Sprintf(` + resource "postgresql_grant" "test" { + database = "%s" + role = "%s" + schema = "test_schema" + object_type = "table" + privileges = ["SELECT"] + }`, dbName, roleName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testCheckCompatibleVersion(t, featureDatabaseOwnerRole) + + // Change the owner to the new pg_database_owner role + func() { + config := getTestConfig(t) + db, err := sql.Open("postgres", config.connStr(dbName)) + if err != nil { + t.Fatalf("could not connect to database %s: %v", dbName, err) + } + + defer db.Close() + + if _, err := db.Exec(` + ALTER SCHEMA test_schema OWNER TO pg_database_owner; + ALTER TABLE test_schema.test_table OWNER TO pg_database_owner; + `); err != nil { + t.Fatalf("could not alter owner of test_table (as %s): %v", config.Username, err) + } + + }() + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: tfConfig, + Check: resource.ComposeTestCheckFunc( + func(*terraform.State) error { + return testCheckTablesPrivileges(t, dbName, roleName, []string{"test_schema.test_table"}, []string{"SELECT"}) + }, + ), + }, + { + Config: tfConfig, + Destroy: true, + Check: resource.ComposeTestCheckFunc( + func(*terraform.State) error { + return testCheckTablesPrivileges(t, dbName, roleName, []string{"test_schema.test_table"}, []string{}) + }, + ), + }, + }, + }) +} + func testCheckDatabasesPrivileges(t *testing.T, canCreate bool) func(*terraform.State) error { return func(*terraform.State) error { db := connectAsTestRole(t, "test_grant_role", "test_grant_db")