Skip to content

Commit

Permalink
feat: Postgresql 15 support (#348)
Browse files Browse the repository at this point in the history
* feat: Postgresql 15 support

* Support of pg_database_owner new [predefined role](https://www.postgresql.org/docs/current/predefined-roles.html)
* Public schema doesn't have wide open permissions anymore

* Add version 15 in tests

* fixup! feat: Postgresql 15 support
  • Loading branch information
cyrilgdn authored Sep 10, 2023
1 parent 592f723 commit e3b877e
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:

strategy:
matrix:
pgversion: [14, 13, 12, 11]
pgversion: [15, 14, 13, 12, 11]

env:
PGVERSION: ${{ matrix.pgversion }}
Expand Down
3 changes: 3 additions & 0 deletions postgresql/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type featureName uint

const (
featureCreateRoleWith featureName = iota
featureDatabaseOwnerRole
featureDBAllowConnections
featureDBIsTemplate
featureFallbackApplicationName
Expand Down Expand Up @@ -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"),
}
)

Expand Down
33 changes: 29 additions & 4 deletions postgresql/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
11 changes: 10 additions & 1 deletion postgresql/resource_postgresql_default_privileges_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() {
Expand Down
5 changes: 5 additions & 0 deletions postgresql/resource_postgresql_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
77 changes: 77 additions & 0 deletions postgresql/resource_postgresql_grant_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package postgresql

import (
"database/sql"
"fmt"
"regexp"
"strings"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit e3b877e

Please sign in to comment.