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

Commit

Permalink
Create new resource: postgresql_grant
Browse files Browse the repository at this point in the history
This resource allow to grant privileges on all existing tables or sequences
for a specified role in a specified schema.
  • Loading branch information
cyrilgdn authored and Cyril Gaudin committed Nov 23, 2018
1 parent 47597c8 commit 735c78f
Show file tree
Hide file tree
Showing 14 changed files with 863 additions and 20 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ BUG FIXES:
* Parse Azure PostgreSQL version
([#40](https://github.com/terraform-providers/terraform-provider-postgresql/pull/40))

FEATURES:

* New resource: postgresql_grant. This resource allows to grant privileges on all existing tables or sequences for a specified role in a specified schema.

## 0.1.2 (July 06, 2018)

FEATURES:
Expand Down
26 changes: 15 additions & 11 deletions postgresql/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ var (
type Config struct {
Host string
Port int
Database string
Username string
Password string
SSLMode string
Expand All @@ -80,6 +79,8 @@ type Client struct {
// Configuration for the client
config Config

databaseName string

// db is a pointer to the DB connection. Callers are responsible for
// releasing their connections.
db *sql.DB
Expand All @@ -96,21 +97,23 @@ type Client struct {
catalogLock sync.RWMutex
}

// NewClient returns new client config
func (c *Config) NewClient() (*Client, error) {
// NewClient returns client config for the specified database.
func (c *Config) NewClient(database string) (*Client, error) {
dbRegistryLock.Lock()
defer dbRegistryLock.Unlock()

dsn := c.connStr()
dsn := c.connStr(database)
dbEntry, found := dbRegistry[dsn]
if !found {
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, errwrap.Wrapf("Error connecting to PostgreSQL server: {{err}}", err)
}

// only one connection
db.SetMaxIdleConns(1)
// We don't want to retain connection
// So when we connect on a specific database which might be managed by terraform,
// we don't keep opened connection in case of the db has to be dopped in the plan.
db.SetMaxIdleConns(0)
db.SetMaxOpenConns(c.MaxConns)

version, err := fingerprintCapabilities(db)
Expand All @@ -127,8 +130,9 @@ func (c *Config) NewClient() (*Client, error) {
}

client := Client{
config: *c,
db: dbEntry.db,
config: *c,
databaseName: database,
db: dbEntry.db,
}

return &client, nil
Expand All @@ -147,7 +151,7 @@ func (c *Config) featureSupported(name featureName) bool {
return fn(c.ExpectedVersion)
}

func (c *Config) connStr() string {
func (c *Config) connStr(database string) string {
// NOTE: dbname must come before user otherwise dbname will be set to
// user.
var dsnFmt string
Expand Down Expand Up @@ -202,7 +206,7 @@ func (c *Config) connStr() string {
logValues := []interface{}{
quote(c.Host),
c.Port,
quote(c.Database),
quote(database),
quote(c.Username),
quote("<redacted>"),
quote(c.SSLMode),
Expand All @@ -221,7 +225,7 @@ func (c *Config) connStr() string {
connValues := []interface{}{
quote(c.Host),
c.Port,
quote(c.Database),
quote(database),
quote(c.Username),
quote(c.Password),
quote(c.SSLMode),
Expand Down
100 changes: 100 additions & 0 deletions postgresql/helpers.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package postgresql

import (
"database/sql"
"fmt"
"strings"

"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
"github.com/lib/pq"
)

// pqQuoteLiteral returns a string literal safe for inclusion in a PostgreSQL
Expand All @@ -22,3 +27,98 @@ func validateConnLimit(v interface{}, key string) (warnings []string, errors []e
}
return
}

func sliceContainsStr(haystack []string, needle string) bool {
for _, s := range haystack {
if s == needle {
return true
}
}
return false
}

// allowedPrivileges is the list of privileges allowed per object types in Postgres.
// see: https://www.postgresql.org/docs/current/sql-grant.html
var allowedPrivileges = map[string][]string{
"table": []string{"ALL", "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER"},
"sequence": []string{"ALL", "USAGE", "SELECT", "UPDATE"},
}

// validatePrivileges checks that privileges to apply are allowed for this object type.
func validatePrivileges(objectType string, privileges []interface{}) error {
allowed, ok := allowedPrivileges[objectType]
if !ok {
return fmt.Errorf("unknown object type %s", objectType)
}

for _, priv := range privileges {
if !sliceContainsStr(allowed, priv.(string)) {
return fmt.Errorf("%s is not an allowed privilege for object type %s", priv, objectType)
}
}
return nil
}

func pgArrayToSet(arr pq.ByteaArray) *schema.Set {
s := make([]interface{}, len(arr))
for i, v := range arr {
s[i] = string(v)
}
return schema.NewSet(schema.HashString, s)
}

// startTransaction starts a new DB transaction on the specified database.
// If the database is specified and different from the one configured in the provider,
// it will create a new connection pool if needed.
func startTransaction(client *Client, database string) (*sql.Tx, error) {
if database != "" && database != client.databaseName {
var err error
client, err = client.config.NewClient(database)
if err != nil {
return nil, err
}
}
db := client.DB()
txn, err := db.Begin()
if err != nil {
return nil, errwrap.Wrapf("could not start transaction: {{err}}", err)
}

return txn, nil
}

func dbExists(txn *sql.Tx, dbname string) (bool, error) {
err := txn.QueryRow("SELECT datname FROM pg_database WHERE datname=$1", dbname).Scan(&dbname)
switch {
case err == sql.ErrNoRows:
return false, nil
case err != nil:
return false, errwrap.Wrapf("could not check if database exists: {{err}}", err)
}

return true, nil
}

func roleExists(txn *sql.Tx, rolname string) (bool, error) {
err := txn.QueryRow("SELECT 1 FROM pg_roles WHERE rolname=$1", rolname).Scan(&rolname)
switch {
case err == sql.ErrNoRows:
return false, nil
case err != nil:
return false, errwrap.Wrapf("could not check if role exists: {{err}}", err)
}

return true, nil
}

func schemaExists(txn *sql.Tx, schemaname string) (bool, error) {
err := txn.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, errwrap.Wrapf("could not check if schema exists: {{err}}", err)
}

return true, nil
}
4 changes: 2 additions & 2 deletions postgresql/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func Provider() terraform.ResourceProvider {
"postgresql_extension": resourcePostgreSQLExtension(),
"postgresql_schema": resourcePostgreSQLSchema(),
"postgresql_role": resourcePostgreSQLRole(),
"postgresql_grant": resourcePostgreSQLGrant(),
},

ConfigureFunc: providerConfigure,
Expand Down Expand Up @@ -133,7 +134,6 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := Config{
Host: d.Get("host").(string),
Port: d.Get("port").(int),
Database: d.Get("database").(string),
Username: d.Get("username").(string),
Password: d.Get("password").(string),
SSLMode: sslMode,
Expand All @@ -143,7 +143,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
ExpectedVersion: version,
}

client, err := config.NewClient()
client, err := config.NewClient(d.Get("database").(string))
if err != nil {
return nil, errwrap.Wrapf("Error initializing PostgreSQL client: {{err}}", err)
}
Expand Down
11 changes: 4 additions & 7 deletions postgresql/resource_postgresql_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,16 +263,13 @@ func resourcePostgreSQLDatabaseExists(d *schema.ResourceData, meta interface{})
c.catalogLock.RLock()
defer c.catalogLock.RUnlock()

var dbName string
err := c.DB().QueryRow("SELECT d.datname from pg_database d WHERE datname=$1", d.Id()).Scan(&dbName)
switch {
case err == sql.ErrNoRows:
return false, nil
case err != nil:
txn, err := startTransaction(c, "")
if err != nil {
return false, err
}
defer txn.Rollback()

return true, nil
return dbExists(txn, d.Id())
}

func resourcePostgreSQLDatabaseRead(d *schema.ResourceData, meta interface{}) error {
Expand Down
Loading

0 comments on commit 735c78f

Please sign in to comment.