Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions postgresql/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,18 @@ func schemaExists(txn *sql.Tx, schemaname string) (bool, error) {
return true, nil
}

func schemaExistsWithDB(db *DBConnection, schemaname string) (bool, error) {
err := db.QueryRow("SELECT 1 FROM pg_namespace WHERE nspname=$1", schemaname).Scan(&schemaname)
switch {
case err == sql.ErrNoRows:
return false, nil
case err != nil:
return false, fmt.Errorf("could not check if schema exists: %w", err)
}

return true, nil
}

func getCurrentUser(db QueryAble) (string, error) {
var currentUser string
err := db.QueryRow("SELECT CURRENT_USER").Scan(&currentUser)
Expand Down
130 changes: 130 additions & 0 deletions postgresql/resource_postgresql_default_privileges.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ func resourcePostgreSQLDefaultPrivilegesRead(db *DBConnection, d *schema.Resourc
return nil
}

// CockroachDB does not support certain DDL operations within explicit transactions
// https://www.cockroachlabs.com/docs/v23.1/online-schema-changes
if db.dbType == dbTypeCockroachdb {
return readRoleDefaultPrivilegesWithDB(db, d)
}

txn, err := startTransaction(db.client, d.Get("database").(string))
if err != nil {
return err
Expand Down Expand Up @@ -130,6 +136,19 @@ func resourcePostgreSQLDefaultPrivilegesCreate(db *DBConnection, d *schema.Resou
database := d.Get("database").(string)
owner := d.Get("owner").(string)

// CockroachDB does not support certain DDL operations within explicit transactions
// https://www.cockroachlabs.com/docs/v23.1/online-schema-changes
if db.dbType == dbTypeCockroachdb {
if err := revokeRoleDefaultPrivilegesWithDB(db, d); err != nil {
return err
}
if err := grantRoleDefaultPrivilegesWithDB(db, d); err != nil {
return err
}
d.SetId(generateDefaultPrivilegesID(d))
return readRoleDefaultPrivilegesWithDB(db, d)
}

txn, err := startTransaction(db.client, database)
if err != nil {
return err
Expand Down Expand Up @@ -185,6 +204,15 @@ func resourcePostgreSQLDefaultPrivilegesDelete(db *DBConnection, d *schema.Resou
)
}

// CockroachDB does not support certain DDL operations within explicit transactions
// https://www.cockroachlabs.com/docs/v23.1/online-schema-changes
if db.dbType == dbTypeCockroachdb {
if err := revokeRoleDefaultPrivilegesWithDB(db, d); err != nil {
return err
}
return nil
}

txn, err := startTransaction(db.client, d.Get("database").(string))
if err != nil {
return err
Expand Down Expand Up @@ -356,3 +384,105 @@ func generateDefaultPrivilegesID(d *schema.ResourceData) string {
}, "_")

}

// grantRoleDefaultPrivilegesWithDB grants default privileges outside of a transaction for CockroachDB
func grantRoleDefaultPrivilegesWithDB(db *DBConnection, d *schema.ResourceData) error {
role := d.Get("role").(string)
pgSchema := d.Get("schema").(string)

privileges := []string{}
for _, priv := range d.Get("privileges").(*schema.Set).List() {
privileges = append(privileges, priv.(string))
}

if len(privileges) == 0 {
log.Printf("[DEBUG] no default privileges to grant for role %s, owner %s in database: %s,", d.Get("role").(string), d.Get("owner").(string), d.Get("database").(string))
return nil
}

var inSchema string

// If a schema is specified we need to build the part of the query string to action this
if pgSchema != "" {
inSchema = fmt.Sprintf("IN SCHEMA %s", pq.QuoteIdentifier(pgSchema))
}

query := fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR ROLE %s %s GRANT %s ON %sS TO %s",
pq.QuoteIdentifier(d.Get("owner").(string)),
inSchema,
strings.Join(privileges, ","),
strings.ToUpper(d.Get("object_type").(string)),
pq.QuoteIdentifier(role),
)

if d.Get("with_grant_option").(bool) {
query = query + " WITH GRANT OPTION"
}
_, err := db.Exec(query)
if err != nil {
return fmt.Errorf("could not alter default privileges: %w", err)
}

return nil
}

// revokeRoleDefaultPrivilegesWithDB revokes default privileges outside of a transaction for CockroachDB
func revokeRoleDefaultPrivilegesWithDB(db *DBConnection, d *schema.ResourceData) error {
pgSchema := d.Get("schema").(string)

var inSchema string

// If a schema is specified we need to build the part of the query string to action this
if pgSchema != "" {
inSchema = fmt.Sprintf("IN SCHEMA %s", pq.QuoteIdentifier(pgSchema))
}
query := fmt.Sprintf(
"ALTER DEFAULT PRIVILEGES FOR ROLE %s %s REVOKE ALL ON %sS FROM %s",
pq.QuoteIdentifier(d.Get("owner").(string)),
inSchema,
strings.ToUpper(d.Get("object_type").(string)),
pq.QuoteIdentifier(d.Get("role").(string)),
)
if _, err := db.Exec(query); err != nil {
return fmt.Errorf("could not revoke default privileges: %w", err)
}
return nil
}

// readRoleDefaultPrivilegesWithDB reads default privileges outside of a transaction for CockroachDB
func readRoleDefaultPrivilegesWithDB(db *DBConnection, d *schema.ResourceData) error {
role := d.Get("role").(string)
owner := d.Get("owner").(string)
pgSchema := d.Get("schema").(string)
objectType := d.Get("object_type").(string)
privilegesInput := d.Get("privileges").(*schema.Set).List()

var query string
var inSchema string
// If a schema is specified we need to build the part of the query string to action this
if pgSchema != "" {
inSchema = fmt.Sprintf("IN SCHEMA %s", pq.QuoteIdentifier(pgSchema))
}
// CockroachDB uses SHOW DEFAULT PRIVILEGES instead of aclexplode
query = fmt.Sprintf("with a as (show DEFAULT PRIVILEGES for role %s %s) select array_agg(privilege_type) from a where grantee = '%s' and object_type = '%ss';", owner, inSchema, role, objectType)

var privileges pq.ByteaArray
if err := db.QueryRow(query).Scan(&privileges); err != nil {
return fmt.Errorf("could not read default privileges: %w", err)
}

// We consider no privileges as "not exists" unless no privileges were provided as input
if len(privileges) == 0 {
log.Printf("[DEBUG] no default privileges for role %s in schema %s", role, pgSchema)
if len(privilegesInput) != 0 {
d.SetId("")
return nil
}
}

privilegesSet := pgArrayToSet(privileges)
d.Set("privileges", privilegesSet)
d.SetId(generateDefaultPrivilegesID(d))

return nil
}
40 changes: 32 additions & 8 deletions postgresql/resource_postgresql_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,16 @@ func resourcePostgreSQLRoleCreate(db *DBConnection, d *schema.ResourceData) erro
}

sql := fmt.Sprintf("CREATE ROLE %s%s", pq.QuoteIdentifier(roleName), createStr)
if _, err := txn.Exec(sql); err != nil {
return fmt.Errorf("error creating role %s: %w", roleName, err)
// CockroachDB does not support certain DDL operations within explicit transactions
// https://www.cockroachlabs.com/docs/v23.1/online-schema-changes
if db.dbType == dbTypeCockroachdb {
if _, err := db.Exec(sql); err != nil {
return fmt.Errorf("error creating role %s: %w", roleName, err)
}
} else {
if _, err := txn.Exec(sql); err != nil {
return fmt.Errorf("error creating role %s: %w", roleName, err)
}
}

if err = grantRoles(txn, d); err != nil {
Expand Down Expand Up @@ -347,8 +355,12 @@ func resourcePostgreSQLRoleCreate(db *DBConnection, d *schema.ResourceData) erro
}
}

if err = txn.Commit(); err != nil {
return fmt.Errorf("could not commit transaction: %w", err)
// CockroachDB does not support certain DDL operations within explicit transactions
// Skip commit for CockroachDB as DDL was executed outside the transaction
if db.dbType != dbTypeCockroachdb {
if err = txn.Commit(); err != nil {
return fmt.Errorf("could not commit transaction: %w", err)
}
}

d.SetId(roleName)
Expand Down Expand Up @@ -391,13 +403,25 @@ func resourcePostgreSQLRoleDelete(db *DBConnection, d *schema.ResourceData) erro
}
}
if !d.Get(roleSkipDropRoleAttr).(bool) {
if _, err := txn.Exec(fmt.Sprintf("DROP ROLE %s", pq.QuoteIdentifier(roleName))); err != nil {
return fmt.Errorf("could not delete role %s: %w", roleName, err)
// CockroachDB does not support certain DDL operations within explicit transactions
// https://www.cockroachlabs.com/docs/v23.1/online-schema-changes
if db.dbType == dbTypeCockroachdb {
if _, err := db.Exec(fmt.Sprintf("DROP ROLE %s", pq.QuoteIdentifier(roleName))); err != nil {
return fmt.Errorf("could not delete role %s: %w", roleName, err)
}
} else {
if _, err := txn.Exec(fmt.Sprintf("DROP ROLE %s", pq.QuoteIdentifier(roleName))); err != nil {
return fmt.Errorf("could not delete role %s: %w", roleName, err)
}
}
}

if err := txn.Commit(); err != nil {
return fmt.Errorf("Error committing schema: %w", err)
// CockroachDB does not support certain DDL operations within explicit transactions
// Skip commit for CockroachDB as DDL was executed outside the transaction
if db.dbType != dbTypeCockroachdb {
if err := txn.Commit(); err != nil {
return fmt.Errorf("Error committing schema: %w", err)
}
}

d.SetId("")
Expand Down
127 changes: 125 additions & 2 deletions postgresql/resource_postgresql_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,17 @@ func resourcePostgreSQLSchema() *schema.Resource {

func resourcePostgreSQLSchemaCreate(db *DBConnection, d *schema.ResourceData) error {
database := getDatabase(d, db.client.databaseName)

// CockroachDB does not support certain DDL operations within explicit transactions
// https://www.cockroachlabs.com/docs/v23.1/online-schema-changes
if db.dbType == dbTypeCockroachdb {
if err := createSchemaWithDB(db, d); err != nil {
return err
}
d.SetId(generateSchemaID(d, database))
return resourcePostgreSQLSchemaReadImpl(db, d)
}

txn, err := startTransaction(db.client, database)
if err != nil {
return err
Expand Down Expand Up @@ -228,17 +239,129 @@ func createSchema(db *DBConnection, txn *sql.Tx, d *schema.ResourceData) error {
return nil
}

// createSchemaWithDB creates a schema outside of a transaction for CockroachDB
func createSchemaWithDB(db *DBConnection, d *schema.ResourceData) error {
schemaName := d.Get(schemaNameAttr).(string)

// Check if previous tasks haven't already created schema
var foundSchema bool
err := db.QueryRow(`SELECT TRUE FROM pg_catalog.pg_namespace WHERE nspname = $1`, schemaName).Scan(&foundSchema)

queries := []string{}
switch {
case err == sql.ErrNoRows:
b := bytes.NewBufferString("CREATE SCHEMA ")
if db.featureSupported(featureSchemaCreateIfNotExist) {
if v := d.Get(schemaIfNotExists); v.(bool) {
fmt.Fprint(b, "IF NOT EXISTS ")
}
}
fmt.Fprint(b, pq.QuoteIdentifier(schemaName))

switch v, ok := d.GetOk(schemaOwnerAttr); {
case ok:
fmt.Fprint(b, " AUTHORIZATION ", pq.QuoteIdentifier(v.(string)))
}
queries = append(queries, b.String())

case err != nil:
return fmt.Errorf("Error looking for schema: %w", err)

default:
// The schema already exists, we just set the owner.
if err := setSchemaOwnerWithDB(db, d); err != nil {
return err
}
}

// ACL objects that can generate the necessary SQL
type RoleKey string
var schemaPolicies map[RoleKey]acl.Schema

if policiesRaw, ok := d.GetOk(schemaPolicyAttr); ok {
policiesList := policiesRaw.(*schema.Set).List()

schemaPolicies = make(map[RoleKey]acl.Schema, len(policiesList))

for _, policyRaw := range policiesList {
policyMap := policyRaw.(map[string]interface{})
rolePolicy := schemaPolicyToACL(policyMap)

roleKey := RoleKey(strings.ToLower(rolePolicy.Role))
if existingRolePolicy, ok := schemaPolicies[roleKey]; ok {
schemaPolicies[roleKey] = existingRolePolicy.Merge(rolePolicy)
} else {
schemaPolicies[roleKey] = rolePolicy
}
}
}

for _, policy := range schemaPolicies {
queries = append(queries, policy.Grants(schemaName)...)
}

for _, query := range queries {
if _, err = db.Exec(query); err != nil {
return fmt.Errorf("Error creating schema %s: %w", schemaName, err)
}
}

return nil
}

// setSchemaOwnerWithDB sets the schema owner outside of a transaction for CockroachDB
func setSchemaOwnerWithDB(db *DBConnection, d *schema.ResourceData) error {
schemaName := d.Get(schemaNameAttr).(string)
owner := d.Get(schemaOwnerAttr).(string)
if owner == "" {
return nil
}
sql := fmt.Sprintf("ALTER SCHEMA %s OWNER TO %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(owner))
if _, err := db.Exec(sql); err != nil {
return fmt.Errorf("Error setting schema owner: %w", err)
}
return nil
}

func resourcePostgreSQLSchemaDelete(db *DBConnection, d *schema.ResourceData) error {
database := getDatabase(d, db.client.databaseName)
schemaName := d.Get(schemaNameAttr).(string)

// CockroachDB does not support certain DDL operations within explicit transactions
// https://www.cockroachlabs.com/docs/v23.1/online-schema-changes
if db.dbType == dbTypeCockroachdb {
if schemaName != "public" {
exists, err := schemaExistsWithDB(db, schemaName)
if err != nil {
return err
}
if !exists {
d.SetId("")
return nil
}

dropMode := "RESTRICT"
if d.Get(schemaDropCascade).(bool) {
dropMode = "CASCADE"
}

sql := fmt.Sprintf("DROP SCHEMA %s %s", pq.QuoteIdentifier(schemaName), dropMode)
if _, err = db.Exec(sql); err != nil {
return fmt.Errorf("Error deleting schema: %w", err)
}
d.SetId("")
} else {
log.Printf("cannot delete schema %s", schemaName)
}
return nil
}

txn, err := startTransaction(db.client, database)
if err != nil {
return err
}
defer deferredRollback(txn)

schemaName := d.Get(schemaNameAttr).(string)

if schemaName != "public" {

exists, err := schemaExists(txn, schemaName)
Expand Down
Loading