Merge pull request #10682 from hashicorp/f-fixup-postgresql
Various changes to the PostgreSQL provider
This commit is contained in:
commit
7cda9e8c74
|
@ -0,0 +1,28 @@
|
|||
POSTGRES?=/opt/local/lib/postgresql96/bin/postgres
|
||||
PSQL?=/opt/local/lib/postgresql96/bin/psql
|
||||
|
||||
PGDATA?=$(GOPATH)/src/github.com/hashicorp/terraform/builtin/providers/postgresql/data
|
||||
|
||||
initdb::
|
||||
/opt/local/lib/postgresql96/bin/initdb --no-locale -U postgres -D $(PGDATA)
|
||||
|
||||
startdb::
|
||||
2>&1 \
|
||||
$(POSTGRES) \
|
||||
-D $(PGDATA) \
|
||||
-c log_connections=on \
|
||||
-c log_disconnections=on \
|
||||
-c log_duration=on \
|
||||
-c log_statement=all \
|
||||
| tee postgresql.log
|
||||
|
||||
cleandb::
|
||||
rm -rf $(PGDATA)
|
||||
|
||||
freshdb:: cleandb initdb startdb
|
||||
|
||||
test::
|
||||
2>&1 PGSSLMODE=disable PGHOST=/tmp PGUSER=postgres make -C ../../.. testacc TEST=./builtin/providers/postgresql | tee test.log
|
||||
|
||||
psql::
|
||||
$(PSQL) -E postgres postgres
|
|
@ -3,18 +3,22 @@ package postgresql
|
|||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
_ "github.com/lib/pq" //PostgreSQL db
|
||||
)
|
||||
|
||||
// Config - provider config
|
||||
type Config struct {
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
SslMode string
|
||||
Timeout int
|
||||
Host string
|
||||
Port int
|
||||
Database string
|
||||
Username string
|
||||
Password string
|
||||
SSLMode string
|
||||
ApplicationName string
|
||||
Timeout int
|
||||
ConnectTimeoutSec int
|
||||
}
|
||||
|
||||
// Client struct holding connection string
|
||||
|
@ -25,8 +29,14 @@ type Client struct {
|
|||
|
||||
// NewClient returns new client config
|
||||
func (c *Config) NewClient() (*Client, error) {
|
||||
connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=postgres sslmode=%s connect_timeout=%d", c.Host, c.Port, c.Username, c.Password, c.SslMode, c.Timeout)
|
||||
// NOTE: dbname must come before user otherwise dbname will be set to
|
||||
// user.
|
||||
const dsnFmt = "host=%s port=%d dbname=%s user=%s password=%s sslmode=%s fallback_application_name=%s connect_timeout=%d"
|
||||
|
||||
logDSN := fmt.Sprintf(dsnFmt, c.Host, c.Port, c.Database, c.Username, "<redacted>", c.SSLMode, c.ApplicationName, c.ConnectTimeoutSec)
|
||||
log.Printf("[INFO] PostgreSQL DSN: `%s`", logDSN)
|
||||
|
||||
connStr := fmt.Sprintf(dsnFmt, c.Host, c.Port, c.Database, c.Username, c.Password, c.SSLMode, c.ApplicationName, c.ConnectTimeoutSec)
|
||||
client := Client{
|
||||
connStr: connStr,
|
||||
username: c.Username,
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package postgresql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// pqQuoteLiteral returns a string literal safe for inclusion in a PostgreSQL
|
||||
// query as a parameter. The resulting string still needs to be wrapped in
|
||||
// single quotes in SQL (i.e. fmt.Sprintf(`'%s'`, pqQuoteLiteral("str"))). See
|
||||
// quote_literal_internal() in postgresql/backend/utils/adt/quote.c:77.
|
||||
func pqQuoteLiteral(in string) string {
|
||||
in = strings.Replace(in, `\`, `\\`, -1)
|
||||
in = strings.Replace(in, `'`, `''`, -1)
|
||||
return in
|
||||
}
|
||||
|
||||
func validateConnLimit(v interface{}, key string) (warnings []string, errors []error) {
|
||||
value := v.(int)
|
||||
if value < -1 {
|
||||
errors = append(errors, fmt.Errorf("%s can not be less than -1", key))
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
package postgresql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
@ -12,61 +15,89 @@ func Provider() terraform.ResourceProvider {
|
|||
Schema: map[string]*schema.Schema{
|
||||
"host": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
DefaultFunc: schema.MultiEnvDefaultFunc([]string{"PGHOST", "POSTGRESQL_HOST"}, nil),
|
||||
Description: "The PostgreSQL server address",
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("PGHOST", nil),
|
||||
Description: "Name of PostgreSQL server address to connect to",
|
||||
},
|
||||
"port": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: 5432,
|
||||
Description: "The PostgreSQL server port",
|
||||
DefaultFunc: schema.EnvDefaultFunc("PGPORT", 5432),
|
||||
Description: "The PostgreSQL port number to connect to at the server host, or socket file name extension for Unix-domain connections",
|
||||
},
|
||||
"database": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "The name of the database to connect to in order to conenct to (defaults to `postgres`).",
|
||||
DefaultFunc: schema.EnvDefaultFunc("PGDATABASE", "postgres"),
|
||||
},
|
||||
"username": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
DefaultFunc: schema.MultiEnvDefaultFunc([]string{"PGUSER", "POSTGRESQL_USER"}, nil),
|
||||
Description: "Username for PostgreSQL server connection",
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("PGUSER", "postgres"),
|
||||
Description: "PostgreSQL user name to connect as",
|
||||
},
|
||||
"password": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.MultiEnvDefaultFunc([]string{"PGPASSWORD", "POSTGRESQL_PASSWORD"}, nil),
|
||||
Description: "Password for PostgreSQL server connection",
|
||||
DefaultFunc: schema.EnvDefaultFunc("PGPASSWORD", nil),
|
||||
Description: "Password to be used if the PostgreSQL server demands password authentication",
|
||||
},
|
||||
"ssl_mode": {
|
||||
"sslmode": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("PGSSLMODE", "require"),
|
||||
Description: "Connection mode for PostgreSQL server",
|
||||
DefaultFunc: schema.EnvDefaultFunc("PGSSLMODE", nil),
|
||||
Description: "This option determines whether or with what priority a secure SSL TCP/IP connection will be negotiated with the PostgreSQL server",
|
||||
},
|
||||
"ssl_mode": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Deprecated: "Rename PostgreSQL provider `ssl_mode` attribute to `sslmode`",
|
||||
},
|
||||
"connect_timeout": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: 15,
|
||||
DefaultFunc: schema.EnvDefaultFunc("PGCONNECT_TIMEOUT", nil),
|
||||
Description: "Maximum wait for connection, in seconds. Zero or not specified means wait indefinitely.",
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("PGCONNECT_TIMEOUT", 180),
|
||||
Description: "Maximum wait for connection, in seconds. Zero or not specified means wait indefinitely.",
|
||||
ValidateFunc: validateConnTimeout,
|
||||
},
|
||||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"postgresql_database": resourcePostgreSQLDatabase(),
|
||||
"postgresql_role": resourcePostgreSQLRole(),
|
||||
"postgresql_extension": resourcePostgreSQLExtension(),
|
||||
"postgresql_schema": resourcePostgreSQLSchema(),
|
||||
"postgresql_role": resourcePostgreSQLRole(),
|
||||
},
|
||||
|
||||
ConfigureFunc: providerConfigure,
|
||||
}
|
||||
}
|
||||
|
||||
func validateConnTimeout(v interface{}, key string) (warnings []string, errors []error) {
|
||||
value := v.(int)
|
||||
if value < 0 {
|
||||
errors = append(errors, fmt.Errorf("%s can not be less than 0", key))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
||||
var sslMode string
|
||||
if sslModeRaw, ok := d.GetOk("sslmode"); ok {
|
||||
sslMode = sslModeRaw.(string)
|
||||
} else {
|
||||
sslMode = d.Get("ssl_mode").(string)
|
||||
}
|
||||
config := Config{
|
||||
Host: d.Get("host").(string),
|
||||
Port: d.Get("port").(int),
|
||||
Username: d.Get("username").(string),
|
||||
Password: d.Get("password").(string),
|
||||
SslMode: d.Get("ssl_mode").(string),
|
||||
Timeout: d.Get("connect_timeout").(int),
|
||||
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,
|
||||
ApplicationName: tfAppName(),
|
||||
ConnectTimeoutSec: d.Get("connect_timeout").(int),
|
||||
}
|
||||
|
||||
client, err := config.NewClient()
|
||||
|
@ -76,3 +107,16 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
|||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func tfAppName() string {
|
||||
const VersionPrerelease = terraform.VersionPrerelease
|
||||
var versionString bytes.Buffer
|
||||
|
||||
fmt.Fprintf(&versionString, "'Terraform v%s", terraform.Version)
|
||||
if terraform.VersionPrerelease != "" {
|
||||
fmt.Fprintf(&versionString, "-%s", terraform.VersionPrerelease)
|
||||
}
|
||||
fmt.Fprintf(&versionString, "'")
|
||||
|
||||
return versionString.String()
|
||||
}
|
||||
|
|
|
@ -36,7 +36,4 @@ func testAccPreCheck(t *testing.T) {
|
|||
if v := os.Getenv("PGUSER"); v == "" {
|
||||
t.Fatal("PGUSER must be set for acceptance tests")
|
||||
}
|
||||
if v := os.Getenv("PGPASSWORD"); v == "" && host != "localhost" {
|
||||
t.Fatal("PGPASSWORD must be set for acceptance tests if PGHOST is not localhost")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package postgresql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
|
@ -10,54 +13,170 @@ import (
|
|||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
const (
|
||||
dbAllowConnsAttr = "allow_connections"
|
||||
dbCTypeAttr = "lc_ctype"
|
||||
dbCollationAttr = "lc_collate"
|
||||
dbConnLimitAttr = "connection_limit"
|
||||
dbEncodingAttr = "encoding"
|
||||
dbIsTemplateAttr = "is_template"
|
||||
dbNameAttr = "name"
|
||||
dbOwnerAttr = "owner"
|
||||
dbTablespaceAttr = "tablespace_name"
|
||||
dbTemplateAttr = "template"
|
||||
)
|
||||
|
||||
func resourcePostgreSQLDatabase() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourcePostgreSQLDatabaseCreate,
|
||||
Read: resourcePostgreSQLDatabaseRead,
|
||||
Update: resourcePostgreSQLDatabaseUpdate,
|
||||
Delete: resourcePostgreSQLDatabaseDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
State: schema.ImportStatePassthrough,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
dbNameAttr: {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "The PostgreSQL database name to connect to",
|
||||
},
|
||||
"owner": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
dbOwnerAttr: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "The role name of the user who will own the new database",
|
||||
},
|
||||
dbTemplateAttr: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Computed: true,
|
||||
Description: "The name of the template from which to create the new database",
|
||||
},
|
||||
dbEncodingAttr: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
Description: "Character set encoding to use in the new database",
|
||||
},
|
||||
dbCollationAttr: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
Description: "Collation order (LC_COLLATE) to use in the new database",
|
||||
},
|
||||
dbCTypeAttr: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
Description: "Character classification (LC_CTYPE) to use in the new database",
|
||||
},
|
||||
dbTablespaceAttr: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "The name of the tablespace that will be associated with the new database",
|
||||
},
|
||||
dbConnLimitAttr: {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "How many concurrent connections can be made to this database",
|
||||
ValidateFunc: validateConnLimit,
|
||||
},
|
||||
dbAllowConnsAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: true,
|
||||
Description: "If false then no one can connect to this database",
|
||||
},
|
||||
dbIsTemplateAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "If true, then this database can be cloned by any user with CREATEDB privileges",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourcePostgreSQLDatabaseCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*Client)
|
||||
conn, err := client.Connect()
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error connecting to PostgreSQL: {{err}}", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
dbName := d.Get("name").(string)
|
||||
dbOwner := d.Get("owner").(string)
|
||||
connUsername := client.username
|
||||
dbName := d.Get(dbNameAttr).(string)
|
||||
b := bytes.NewBufferString("CREATE DATABASE ")
|
||||
fmt.Fprint(b, pq.QuoteIdentifier(dbName))
|
||||
|
||||
var dbOwnerCfg string
|
||||
if dbOwner != "" {
|
||||
dbOwnerCfg = fmt.Sprintf("WITH OWNER=%s", pq.QuoteIdentifier(dbOwner))
|
||||
} else {
|
||||
dbOwnerCfg = ""
|
||||
// Handle each option individually and stream results into the query
|
||||
// buffer.
|
||||
|
||||
switch v, ok := d.GetOk(dbOwnerAttr); {
|
||||
case ok:
|
||||
fmt.Fprint(b, " OWNER ", pq.QuoteIdentifier(v.(string)))
|
||||
default:
|
||||
// No owner specified in the config, default to using
|
||||
// the connecting username.
|
||||
fmt.Fprint(b, " OWNER ", pq.QuoteIdentifier(c.username))
|
||||
}
|
||||
|
||||
//needed in order to set the owner of the db if the connection user is not a superuser
|
||||
err = grantRoleMembership(conn, dbOwner, connUsername)
|
||||
if err != nil {
|
||||
return err
|
||||
switch v, ok := d.GetOk(dbTemplateAttr); {
|
||||
case ok:
|
||||
fmt.Fprint(b, " TEMPLATE ", pq.QuoteIdentifier(v.(string)))
|
||||
case v.(string) == "", strings.ToUpper(v.(string)) != "DEFAULT":
|
||||
fmt.Fprint(b, " TEMPLATE template0")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("CREATE DATABASE %s %s", pq.QuoteIdentifier(dbName), dbOwnerCfg)
|
||||
switch v, ok := d.GetOk(dbEncodingAttr); {
|
||||
case ok:
|
||||
fmt.Fprint(b, " ENCODING ", pq.QuoteIdentifier(v.(string)))
|
||||
case v.(string) == "", strings.ToUpper(v.(string)) != "DEFAULT":
|
||||
fmt.Fprint(b, ` ENCODING "UTF8"`)
|
||||
}
|
||||
|
||||
switch v, ok := d.GetOk(dbCollationAttr); {
|
||||
case ok:
|
||||
fmt.Fprint(b, " LC_COLLATE ", pq.QuoteIdentifier(v.(string)))
|
||||
case v.(string) == "", strings.ToUpper(v.(string)) != "DEFAULT":
|
||||
fmt.Fprint(b, ` LC_COLLATE "C"`)
|
||||
}
|
||||
|
||||
switch v, ok := d.GetOk(dbCTypeAttr); {
|
||||
case ok:
|
||||
fmt.Fprint(b, " LC_CTYPE ", pq.QuoteIdentifier(v.(string)))
|
||||
case v.(string) == "", strings.ToUpper(v.(string)) != "DEFAULT":
|
||||
fmt.Fprint(b, ` LC_CTYPE "C"`)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(dbTablespaceAttr); ok {
|
||||
fmt.Fprint(b, " TABLESPACE ", pq.QuoteIdentifier(v.(string)))
|
||||
}
|
||||
|
||||
{
|
||||
val := d.Get(dbAllowConnsAttr).(bool)
|
||||
fmt.Fprint(b, " ALLOW_CONNECTIONS ", val)
|
||||
}
|
||||
|
||||
{
|
||||
val := d.Get(dbConnLimitAttr).(int)
|
||||
fmt.Fprint(b, " CONNECTION LIMIT ", val)
|
||||
}
|
||||
|
||||
{
|
||||
val := d.Get(dbIsTemplateAttr).(bool)
|
||||
fmt.Fprint(b, " IS_TEMPLATE ", val)
|
||||
}
|
||||
|
||||
query := b.String()
|
||||
_, err = conn.Query(query)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(fmt.Sprintf("Error creating database %s: {{err}}", dbName), err)
|
||||
|
@ -69,19 +188,24 @@ func resourcePostgreSQLDatabaseCreate(d *schema.ResourceData, meta interface{})
|
|||
}
|
||||
|
||||
func resourcePostgreSQLDatabaseDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*Client)
|
||||
conn, err := client.Connect()
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error connecting to PostgreSQL: {{err}}", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
dbName := d.Get("name").(string)
|
||||
connUsername := client.username
|
||||
dbOwner := d.Get("owner").(string)
|
||||
//needed in order to set the owner of the db if the connection user is not a superuser
|
||||
err = grantRoleMembership(conn, dbOwner, connUsername)
|
||||
if err != nil {
|
||||
dbName := d.Get(dbNameAttr).(string)
|
||||
|
||||
if isTemplate := d.Get(dbIsTemplateAttr).(bool); isTemplate {
|
||||
// Template databases must have this attribute cleared before
|
||||
// they can be dropped.
|
||||
if err := doSetDBIsTemplate(conn, dbName, false); err != nil {
|
||||
return errwrap.Wrapf("Error updating database IS_TEMPLATE during DROP DATABASE: {{err}}", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := setDBIsTemplate(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -97,64 +221,204 @@ func resourcePostgreSQLDatabaseDelete(d *schema.ResourceData, meta interface{})
|
|||
}
|
||||
|
||||
func resourcePostgreSQLDatabaseRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*Client)
|
||||
conn, err := client.Connect()
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
dbName := d.Get("name").(string)
|
||||
|
||||
var owner string
|
||||
err = conn.QueryRow("SELECT pg_catalog.pg_get_userbyid(d.datdba) from pg_database d WHERE datname=$1", dbName).Scan(&owner)
|
||||
dbId := d.Id()
|
||||
var dbName, ownerName string
|
||||
err = conn.QueryRow("SELECT d.datname, pg_catalog.pg_get_userbyid(d.datdba) from pg_database d WHERE datname=$1", dbId).Scan(&dbName, &ownerName)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
log.Printf("[WARN] PostgreSQL database (%s) not found", dbId)
|
||||
d.SetId("")
|
||||
return nil
|
||||
case err != nil:
|
||||
return errwrap.Wrapf("Error reading database: {{err}}", err)
|
||||
}
|
||||
|
||||
var dbEncoding, dbCollation, dbCType, dbTablespaceName string
|
||||
var dbConnLimit int
|
||||
var dbAllowConns, dbIsTemplate bool
|
||||
err = conn.QueryRow(`SELECT pg_catalog.pg_encoding_to_char(d.encoding), d.datcollate, d.datctype, ts.spcname, d.datconnlimit, d.datallowconn, d.datistemplate FROM pg_catalog.pg_database AS d, pg_catalog.pg_tablespace AS ts WHERE d.datname = $1 AND d.dattablespace = ts.oid`, dbId).
|
||||
Scan(
|
||||
&dbEncoding, &dbCollation, &dbCType, &dbTablespaceName,
|
||||
&dbConnLimit, &dbAllowConns, &dbIsTemplate,
|
||||
)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
log.Printf("[WARN] PostgreSQL database (%s) not found", dbId)
|
||||
d.SetId("")
|
||||
return nil
|
||||
case err != nil:
|
||||
return errwrap.Wrapf("Error reading database: {{err}}", err)
|
||||
default:
|
||||
d.Set("owner", owner)
|
||||
d.Set(dbNameAttr, dbName)
|
||||
d.Set(dbOwnerAttr, ownerName)
|
||||
d.Set(dbEncodingAttr, dbEncoding)
|
||||
d.Set(dbCollationAttr, dbCollation)
|
||||
d.Set(dbCTypeAttr, dbCType)
|
||||
d.Set(dbTablespaceAttr, dbTablespaceName)
|
||||
d.Set(dbConnLimitAttr, dbConnLimit)
|
||||
d.Set(dbAllowConnsAttr, dbAllowConns)
|
||||
d.Set(dbIsTemplateAttr, dbIsTemplate)
|
||||
dbTemplate := d.Get(dbTemplateAttr).(string)
|
||||
if dbTemplate == "" {
|
||||
dbTemplate = "template0"
|
||||
}
|
||||
d.Set(dbTemplateAttr, dbTemplate)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func resourcePostgreSQLDatabaseUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*Client)
|
||||
conn, err := client.Connect()
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
dbName := d.Get("name").(string)
|
||||
|
||||
if d.HasChange("owner") {
|
||||
owner := d.Get("owner").(string)
|
||||
if owner != "" {
|
||||
query := fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", pq.QuoteIdentifier(dbName), pq.QuoteIdentifier(owner))
|
||||
_, err := conn.Query(query)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error updating owner: {{err}}", err)
|
||||
}
|
||||
}
|
||||
if err := setDBName(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setDBOwner(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setDBTablespace(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setDBConnLimit(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setDBAllowConns(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setDBIsTemplate(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Empty values: ALTER DATABASE name RESET configuration_parameter;
|
||||
|
||||
return resourcePostgreSQLDatabaseRead(d, meta)
|
||||
}
|
||||
|
||||
func grantRoleMembership(conn *sql.DB, dbOwner string, connUsername string) error {
|
||||
if dbOwner != "" && dbOwner != connUsername {
|
||||
query := fmt.Sprintf("GRANT %s TO %s", pq.QuoteIdentifier(dbOwner), pq.QuoteIdentifier(connUsername))
|
||||
_, err := conn.Query(query)
|
||||
if err != nil {
|
||||
//is already member or role
|
||||
if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
|
||||
return nil
|
||||
}
|
||||
return errwrap.Wrapf("Error granting membership: {{err}}", err)
|
||||
}
|
||||
func setDBName(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(dbNameAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
oraw, nraw := d.GetChange(dbNameAttr)
|
||||
o := oraw.(string)
|
||||
n := nraw.(string)
|
||||
if n == "" {
|
||||
return errors.New("Error setting database name to an empty string")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("ALTER DATABASE %s RENAME TO %s", pq.QuoteIdentifier(o), pq.QuoteIdentifier(n))
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating database name: {{err}}", err)
|
||||
}
|
||||
d.SetId(n)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setDBOwner(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(dbOwnerAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
owner := d.Get(dbOwnerAttr).(string)
|
||||
if owner == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
dbName := d.Get(dbNameAttr).(string)
|
||||
query := fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", pq.QuoteIdentifier(dbName), pq.QuoteIdentifier(owner))
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating database OWNER: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setDBTablespace(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(dbTablespaceAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
tbspName := d.Get(dbTablespaceAttr).(string)
|
||||
dbName := d.Get(dbNameAttr).(string)
|
||||
var query string
|
||||
if tbspName == "" || strings.ToUpper(tbspName) == "DEFAULT" {
|
||||
query = fmt.Sprintf("ALTER DATABASE %s RESET TABLESPACE", pq.QuoteIdentifier(dbName))
|
||||
} else {
|
||||
query = fmt.Sprintf("ALTER DATABASE %s SET TABLESPACE %s", pq.QuoteIdentifier(dbName), pq.QuoteIdentifier(tbspName))
|
||||
}
|
||||
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating database TABLESPACE: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setDBConnLimit(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(dbConnLimitAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
connLimit := d.Get(dbConnLimitAttr).(int)
|
||||
dbName := d.Get(dbNameAttr).(string)
|
||||
query := fmt.Sprintf("ALTER DATABASE %s CONNECTION LIMIT = %d", pq.QuoteIdentifier(dbName), connLimit)
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating database CONNECTION LIMIT: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setDBAllowConns(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(dbAllowConnsAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
allowConns := d.Get(dbAllowConnsAttr).(bool)
|
||||
dbName := d.Get(dbNameAttr).(string)
|
||||
query := fmt.Sprintf("ALTER DATABASE %s ALLOW_CONNECTIONS %t", pq.QuoteIdentifier(dbName), allowConns)
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating database ALLOW_CONNECTIONS: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setDBIsTemplate(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(dbIsTemplateAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := doSetDBIsTemplate(conn, d.Get(dbNameAttr).(string), d.Get(dbIsTemplateAttr).(bool)); err != nil {
|
||||
return errwrap.Wrapf("Error updating database IS_TEMPLATE: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func doSetDBIsTemplate(conn *sql.DB, dbName string, isTemplate bool) error {
|
||||
query := fmt.Sprintf("ALTER DATABASE %s IS_TEMPLATE %t", pq.QuoteIdentifier(dbName), isTemplate)
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating database IS_TEMPLATE: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,29 +2,90 @@ package postgresql
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"errors"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccPostgresqlDatabase_Basic(t *testing.T) {
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckPostgresqlDatabaseDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccPostgresqlDatabaseConfig,
|
||||
Config: testAccPostgreSQLDatabaseConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckPostgresqlDatabaseExists("postgresql_database.mydb"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.mydb", "name", "mydb"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.mydb", "owner", "myrole"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.default_opts", "owner", "myrole"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.default_opts", "name", "default_opts_name"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.default_opts", "template", "template0"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.default_opts", "encoding", "UTF8"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.default_opts", "lc_collate", "C"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.default_opts", "lc_ctype", "C"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.default_opts", "tablespace_name", "pg_default"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.default_opts", "connection_limit", "-1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.default_opts", "allow_connections", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.default_opts", "is_template", "false"),
|
||||
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.modified_opts", "owner", "myrole"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.modified_opts", "name", "custom_template_db"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.modified_opts", "template", "template0"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.modified_opts", "encoding", "UTF8"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.modified_opts", "lc_collate", "en_US.UTF-8"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.modified_opts", "lc_ctype", "en_US.UTF-8"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.modified_opts", "tablespace_name", "pg_default"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.modified_opts", "connection_limit", "10"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.modified_opts", "allow_connections", "false"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.modified_opts", "is_template", "true"),
|
||||
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.pathological_opts", "owner", "myrole"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.pathological_opts", "name", "bad_template_db"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.pathological_opts", "template", "template0"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.pathological_opts", "encoding", "LATIN1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.pathological_opts", "lc_collate", "C"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.pathological_opts", "lc_ctype", "C"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.pathological_opts", "tablespace_name", "pg_default"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.pathological_opts", "connection_limit", "0"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.pathological_opts", "allow_connections", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_database.pathological_opts", "is_template", "true"),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
@ -32,14 +93,13 @@ func TestAccPostgresqlDatabase_Basic(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAccPostgresqlDatabase_DefaultOwner(t *testing.T) {
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckPostgresqlDatabaseDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccPostgresqlDatabaseConfig,
|
||||
Config: testAccPostgreSQLDatabaseConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckPostgresqlDatabaseExists("postgresql_database.mydb_default_owner"),
|
||||
resource.TestCheckResourceAttr(
|
||||
|
@ -119,7 +179,7 @@ func checkDatabaseExists(client *Client, dbName string) (bool, error) {
|
|||
}
|
||||
}
|
||||
|
||||
var testAccPostgresqlDatabaseConfig = `
|
||||
var testAccPostgreSQLDatabaseConfig = `
|
||||
resource "postgresql_role" "myrole" {
|
||||
name = "myrole"
|
||||
login = true
|
||||
|
@ -135,6 +195,45 @@ resource "postgresql_database" "mydb2" {
|
|||
owner = "${postgresql_role.myrole.name}"
|
||||
}
|
||||
|
||||
resource "postgresql_database" "default_opts" {
|
||||
name = "default_opts_name"
|
||||
owner = "${postgresql_role.myrole.name}"
|
||||
template = "template0"
|
||||
encoding = "UTF8"
|
||||
lc_collate = "C"
|
||||
lc_ctype = "C"
|
||||
tablespace_name = "pg_default"
|
||||
connection_limit = -1
|
||||
allow_connections = true
|
||||
is_template = false
|
||||
}
|
||||
|
||||
resource "postgresql_database" "modified_opts" {
|
||||
name = "custom_template_db"
|
||||
owner = "${postgresql_role.myrole.name}"
|
||||
template = "template0"
|
||||
encoding = "UTF8"
|
||||
lc_collate = "en_US.UTF-8"
|
||||
lc_ctype = "en_US.UTF-8"
|
||||
tablespace_name = "pg_default"
|
||||
connection_limit = 10
|
||||
allow_connections = false
|
||||
is_template = true
|
||||
}
|
||||
|
||||
resource "postgresql_database" "pathological_opts" {
|
||||
name = "bad_template_db"
|
||||
owner = "${postgresql_role.myrole.name}"
|
||||
template = "template0"
|
||||
encoding = "LATIN1"
|
||||
lc_collate = "C"
|
||||
lc_ctype = "C"
|
||||
tablespace_name = "pg_default"
|
||||
connection_limit = 0
|
||||
allow_connections = true
|
||||
is_template = true
|
||||
}
|
||||
|
||||
resource "postgresql_database" "mydb_default_owner" {
|
||||
name = "mydb_default_owner"
|
||||
}
|
||||
|
|
|
@ -1,86 +1,125 @@
|
|||
package postgresql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
const (
|
||||
extNameAttr = "name"
|
||||
extSchemaAttr = "schema"
|
||||
extVersionAttr = "version"
|
||||
)
|
||||
|
||||
func resourcePostgreSQLExtension() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourcePostgreSQLExtensionCreate,
|
||||
Read: resourcePostgreSQLExtensionRead,
|
||||
Update: resourcePostgreSQLExtensionUpdate,
|
||||
Delete: resourcePostgreSQLExtensionDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
State: schema.ImportStatePassthrough,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": {
|
||||
extNameAttr: {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
extSchemaAttr: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Sets the schema of an extension",
|
||||
},
|
||||
extVersionAttr: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Sets the version number of the extension",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourcePostgreSQLExtensionCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*Client)
|
||||
conn, err := client.Connect()
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
extensionName := d.Get("name").(string)
|
||||
extName := d.Get(extNameAttr).(string)
|
||||
|
||||
query := fmt.Sprintf("CREATE EXTENSION %s", pq.QuoteIdentifier(extensionName))
|
||||
b := bytes.NewBufferString("CREATE EXTENSION ")
|
||||
fmt.Fprintf(b, pq.QuoteIdentifier(extName))
|
||||
|
||||
if v, ok := d.GetOk(extSchemaAttr); ok {
|
||||
fmt.Fprint(b, " SCHEMA ", pq.QuoteIdentifier(v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(extVersionAttr); ok {
|
||||
fmt.Fprint(b, " VERSION ", pq.QuoteIdentifier(v.(string)))
|
||||
}
|
||||
|
||||
query := b.String()
|
||||
_, err = conn.Query(query)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error creating extension: {{err}}", err)
|
||||
}
|
||||
|
||||
d.SetId(extensionName)
|
||||
d.SetId(extName)
|
||||
|
||||
return resourcePostgreSQLExtensionRead(d, meta)
|
||||
}
|
||||
|
||||
func resourcePostgreSQLExtensionRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*Client)
|
||||
conn, err := client.Connect()
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
extensionName := d.Get("name").(string)
|
||||
|
||||
var hasExtension bool
|
||||
err = conn.QueryRow("SELECT 1 from pg_extension d WHERE extname=$1", extensionName).Scan(&hasExtension)
|
||||
extID := d.Id()
|
||||
var extName, extSchema, extVersion string
|
||||
err = conn.QueryRow("SELECT e.extname, n.nspname, e.extversion FROM pg_catalog.pg_extension e, pg_catalog.pg_namespace n WHERE n.oid = e.extnamespace AND e.extname = $1", extID).Scan(&extName, &extSchema, &extVersion)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
log.Printf("[WARN] PostgreSQL extension (%s) not found", d.Id())
|
||||
d.SetId("")
|
||||
return nil
|
||||
case err != nil:
|
||||
return errwrap.Wrapf("Error reading extension: {{err}}", err)
|
||||
default:
|
||||
d.Set("extension", hasExtension)
|
||||
d.Set(extNameAttr, extName)
|
||||
d.Set(extSchemaAttr, extSchema)
|
||||
d.Set(extVersionAttr, extVersion)
|
||||
d.SetId(extName)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func resourcePostgreSQLExtensionDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*Client)
|
||||
conn, err := client.Connect()
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
extensionName := d.Get("name").(string)
|
||||
extID := d.Id()
|
||||
|
||||
query := fmt.Sprintf("DROP EXTENSION %s", pq.QuoteIdentifier(extensionName))
|
||||
query := fmt.Sprintf("DROP EXTENSION %s", pq.QuoteIdentifier(extID))
|
||||
_, err = conn.Query(query)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error deleting extension: {{err}}", err)
|
||||
|
@ -90,3 +129,68 @@ func resourcePostgreSQLExtensionDelete(d *schema.ResourceData, meta interface{})
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourcePostgreSQLExtensionUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Can't rename a schema
|
||||
|
||||
if err := setExtSchema(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setExtVersion(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return resourcePostgreSQLExtensionRead(d, meta)
|
||||
}
|
||||
|
||||
func setExtSchema(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(extSchemaAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
extID := d.Id()
|
||||
_, nraw := d.GetChange(extSchemaAttr)
|
||||
n := nraw.(string)
|
||||
if n == "" {
|
||||
return errors.New("Error setting extension name to an empty string")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("ALTER EXTENSION %s SET SCHEMA %s", pq.QuoteIdentifier(extID), pq.QuoteIdentifier(n))
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating extension SCHEMA: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setExtVersion(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(extVersionAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
extID := d.Id()
|
||||
|
||||
b := bytes.NewBufferString("ALTER EXTENSION ")
|
||||
fmt.Fprintf(b, "%s UPDATE", pq.QuoteIdentifier(extID))
|
||||
|
||||
_, nraw := d.GetChange(extVersionAttr)
|
||||
n := nraw.(string)
|
||||
if n != "" {
|
||||
fmt.Fprintf(b, " TO %s", pq.QuoteIdentifier(n))
|
||||
}
|
||||
|
||||
query := b.String()
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating extension version: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
)
|
||||
|
||||
func TestAccPostgresqlExtension_Basic(t *testing.T) {
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
|
@ -22,6 +21,15 @@ func TestAccPostgresqlExtension_Basic(t *testing.T) {
|
|||
testAccCheckPostgresqlExtensionExists("postgresql_extension.myextension"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_extension.myextension", "name", "pg_trgm"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_extension.myextension", "schema", "public"),
|
||||
|
||||
// NOTE(sean): Version 1.3 is what's
|
||||
// shipped with PostgreSQL 9.6.1. This
|
||||
// version number may drift in the
|
||||
// future.
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_extension.myextension", "version", "1.3"),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
@ -76,6 +84,42 @@ func testAccCheckPostgresqlExtensionExists(n string) resource.TestCheckFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAccPostgresqlExtension_SchemaRename(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckPostgresqlExtensionDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccPostgresqlExtensionSchemaChange1,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckPostgresqlExtensionExists("postgresql_extension.ext1trgm"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema.ext1foo", "name", "foo"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_extension.ext1trgm", "name", "pg_trgm"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_extension.ext1trgm", "name", "pg_trgm"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_extension.ext1trgm", "schema", "foo"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccPostgresqlExtensionSchemaChange2,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckPostgresqlExtensionExists("postgresql_extension.ext1trgm"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema.ext1foo", "name", "bar"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_extension.ext1trgm", "name", "pg_trgm"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_extension.ext1trgm", "schema", "bar"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func checkExtensionExists(client *Client, extensionName string) (bool, error) {
|
||||
conn, err := client.Connect()
|
||||
if err != nil {
|
||||
|
@ -83,8 +127,8 @@ func checkExtensionExists(client *Client, extensionName string) (bool, error) {
|
|||
}
|
||||
defer conn.Close()
|
||||
|
||||
var _rez int
|
||||
err = conn.QueryRow("SELECT 1 from pg_extension d WHERE extname=$1", extensionName).Scan(&_rez)
|
||||
var _rez bool
|
||||
err = conn.QueryRow("SELECT TRUE from pg_catalog.pg_extension d WHERE extname=$1", extensionName).Scan(&_rez)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return false, nil
|
||||
|
@ -100,3 +144,25 @@ resource "postgresql_extension" "myextension" {
|
|||
name = "pg_trgm"
|
||||
}
|
||||
`
|
||||
|
||||
var testAccPostgresqlExtensionSchemaChange1 = `
|
||||
resource "postgresql_schema" "ext1foo" {
|
||||
name = "foo"
|
||||
}
|
||||
|
||||
resource "postgresql_extension" "ext1trgm" {
|
||||
name = "pg_trgm"
|
||||
schema = "${postgresql_schema.ext1foo.name}"
|
||||
}
|
||||
`
|
||||
|
||||
var testAccPostgresqlExtensionSchemaChange2 = `
|
||||
resource "postgresql_schema" "ext1foo" {
|
||||
name = "bar"
|
||||
}
|
||||
|
||||
resource "postgresql_extension" "ext1trgm" {
|
||||
name = "pg_trgm"
|
||||
schema = "${postgresql_schema.ext1foo.name}"
|
||||
}
|
||||
`
|
||||
|
|
|
@ -2,65 +2,229 @@ package postgresql
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
const (
|
||||
roleBypassRLSAttr = "bypass_row_level_security"
|
||||
roleConnLimitAttr = "connection_limit"
|
||||
roleCreateDBAttr = "create_database"
|
||||
roleCreateRoleAttr = "create_role"
|
||||
roleEncryptedPassAttr = "encrypted_password"
|
||||
roleInheritAttr = "inherit"
|
||||
roleLoginAttr = "login"
|
||||
roleNameAttr = "name"
|
||||
rolePasswordAttr = "password"
|
||||
roleReplicationAttr = "replication"
|
||||
roleSuperuserAttr = "superuser"
|
||||
roleValidUntilAttr = "valid_until"
|
||||
|
||||
// Deprecated options
|
||||
roleDepEncryptedAttr = "encrypted"
|
||||
)
|
||||
|
||||
func resourcePostgreSQLRole() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourcePostgreSQLRoleCreate,
|
||||
Read: resourcePostgreSQLRoleRead,
|
||||
Update: resourcePostgreSQLRoleUpdate,
|
||||
Delete: resourcePostgreSQLRoleDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
State: schema.ImportStatePassthrough,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
roleNameAttr: {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "The name of the role",
|
||||
},
|
||||
"login": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
ForceNew: false,
|
||||
Default: false,
|
||||
rolePasswordAttr: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Sensitive: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("PGPASSWORD", nil),
|
||||
Description: "Sets the role's password",
|
||||
},
|
||||
"password": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: false,
|
||||
roleDepEncryptedAttr: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Deprecated: fmt.Sprintf("Rename PostgreSQL role resource attribute %q to %q", roleDepEncryptedAttr, roleEncryptedPassAttr),
|
||||
},
|
||||
"encrypted": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
ForceNew: false,
|
||||
Default: false,
|
||||
roleEncryptedPassAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: true,
|
||||
Description: "Control whether the password is stored encrypted in the system catalogs",
|
||||
},
|
||||
roleValidUntilAttr: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "infinity",
|
||||
Description: "Sets a date and time after which the role's password is no longer valid",
|
||||
},
|
||||
roleConnLimitAttr: {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "How many concurrent connections can be made with this role",
|
||||
ValidateFunc: validateConnLimit,
|
||||
},
|
||||
roleSuperuserAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: `Determine whether the new role is a "superuser"`,
|
||||
},
|
||||
roleCreateDBAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: "Define a role's ability to create databases",
|
||||
},
|
||||
roleCreateRoleAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: "Determine whether this role will be permitted to create new roles",
|
||||
},
|
||||
roleInheritAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: true,
|
||||
Description: `Determine whether a role "inherits" the privileges of roles it is a member of`,
|
||||
},
|
||||
roleLoginAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: "Determine whether a role is allowed to log in",
|
||||
},
|
||||
roleReplicationAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: "Determine whether a role is allowed to initiate streaming replication or put the system in and out of backup mode",
|
||||
},
|
||||
roleBypassRLSAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: "Determine whether a role bypasses every row-level security (RLS) policy",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourcePostgreSQLRoleCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*Client)
|
||||
conn, err := client.Connect()
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
return errwrap.Wrapf("Error connecting to PostgreSQL: {{err}}", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
roleName := d.Get("name").(string)
|
||||
loginAttr := getLoginStr(d.Get("login").(bool))
|
||||
password := d.Get("password").(string)
|
||||
stringOpts := []struct {
|
||||
hclKey string
|
||||
sqlKey string
|
||||
}{
|
||||
{rolePasswordAttr, "PASSWORD"},
|
||||
{roleValidUntilAttr, "VALID UNTIL"},
|
||||
}
|
||||
intOpts := []struct {
|
||||
hclKey string
|
||||
sqlKey string
|
||||
}{
|
||||
{roleConnLimitAttr, "CONNECTION LIMIT"},
|
||||
}
|
||||
boolOpts := []struct {
|
||||
hclKey string
|
||||
sqlKeyEnable string
|
||||
sqlKeyDisable string
|
||||
}{
|
||||
{roleSuperuserAttr, "CREATEDB", "NOCREATEDB"},
|
||||
{roleCreateRoleAttr, "CREATEROLE", "NOCREATEROLE"},
|
||||
{roleInheritAttr, "INHERIT", "NOINHERIT"},
|
||||
{roleLoginAttr, "LOGIN", "NOLOGIN"},
|
||||
{roleReplicationAttr, "REPLICATION", "NOREPLICATION"},
|
||||
{roleBypassRLSAttr, "BYPASSRLS", "NOBYPASSRLS"},
|
||||
|
||||
encryptedCfg := getEncryptedStr(d.Get("encrypted").(bool))
|
||||
// roleEncryptedPassAttr is used only when rolePasswordAttr is set.
|
||||
// {roleEncryptedPassAttr, "ENCRYPTED", "UNENCRYPTED"},
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("CREATE ROLE %s %s %s PASSWORD '%s'", pq.QuoteIdentifier(roleName), loginAttr, encryptedCfg, password)
|
||||
createOpts := make([]string, 0, len(stringOpts)+len(intOpts)+len(boolOpts))
|
||||
|
||||
for _, opt := range stringOpts {
|
||||
v, ok := d.GetOk(opt.hclKey)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
val := v.(string)
|
||||
if val != "" {
|
||||
switch {
|
||||
case opt.hclKey == rolePasswordAttr:
|
||||
if strings.ToUpper(v.(string)) == "NULL" {
|
||||
createOpts = append(createOpts, "PASSWORD NULL")
|
||||
} else {
|
||||
if d.Get(roleEncryptedPassAttr).(bool) {
|
||||
createOpts = append(createOpts, "ENCRYPTED")
|
||||
} else {
|
||||
createOpts = append(createOpts, "UNENCRYPTED")
|
||||
}
|
||||
createOpts = append(createOpts, fmt.Sprintf("%s '%s'", opt.sqlKey, pqQuoteLiteral(val)))
|
||||
}
|
||||
case opt.hclKey == roleValidUntilAttr:
|
||||
switch {
|
||||
case v.(string) == "", strings.ToLower(v.(string)) == "infinity":
|
||||
createOpts = append(createOpts, fmt.Sprintf("%s '%s'", opt.sqlKey, "infinity"))
|
||||
default:
|
||||
createOpts = append(createOpts, fmt.Sprintf("%s %s", opt.sqlKey, pq.QuoteIdentifier(val)))
|
||||
}
|
||||
default:
|
||||
createOpts = append(createOpts, fmt.Sprintf("%s %s", opt.sqlKey, pq.QuoteIdentifier(val)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, opt := range intOpts {
|
||||
val := d.Get(opt.hclKey).(int)
|
||||
createOpts = append(createOpts, fmt.Sprintf("%s %d", opt.sqlKey, val))
|
||||
}
|
||||
|
||||
for _, opt := range boolOpts {
|
||||
if opt.hclKey == roleEncryptedPassAttr {
|
||||
// This attribute is handled above in the stringOpts
|
||||
// loop.
|
||||
continue
|
||||
}
|
||||
val := d.Get(opt.hclKey).(bool)
|
||||
valStr := opt.sqlKeyDisable
|
||||
if val {
|
||||
valStr = opt.sqlKeyEnable
|
||||
}
|
||||
createOpts = append(createOpts, valStr)
|
||||
}
|
||||
|
||||
roleName := d.Get(roleNameAttr).(string)
|
||||
createStr := strings.Join(createOpts, " ")
|
||||
if len(createOpts) > 0 {
|
||||
createStr = " WITH " + createStr
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("CREATE ROLE %s%s", pq.QuoteIdentifier(roleName), createStr)
|
||||
_, err = conn.Query(query)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error creating role: {{err}}", err)
|
||||
return errwrap.Wrapf(fmt.Sprintf("Error creating role %s: {{err}}", roleName), err)
|
||||
}
|
||||
|
||||
d.SetId(roleName)
|
||||
|
@ -76,8 +240,7 @@ func resourcePostgreSQLRoleDelete(d *schema.ResourceData, meta interface{}) erro
|
|||
}
|
||||
defer conn.Close()
|
||||
|
||||
roleName := d.Get("name").(string)
|
||||
|
||||
roleName := d.Get(roleNameAttr).(string)
|
||||
query := fmt.Sprintf("DROP ROLE %s", pq.QuoteIdentifier(roleName))
|
||||
_, err = conn.Query(query)
|
||||
if err != nil {
|
||||
|
@ -90,91 +253,296 @@ func resourcePostgreSQLRoleDelete(d *schema.ResourceData, meta interface{}) erro
|
|||
}
|
||||
|
||||
func resourcePostgreSQLRoleRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*Client)
|
||||
conn, err := client.Connect()
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
roleName := d.Get("name").(string)
|
||||
|
||||
var canLogin bool
|
||||
err = conn.QueryRow("SELECT rolcanlogin FROM pg_roles WHERE rolname=$1", roleName).Scan(&canLogin)
|
||||
roleId := d.Id()
|
||||
var roleSuperuser, roleInherit, roleCreateRole, roleCreateDB, roleCanLogin, roleReplication, roleBypassRLS bool
|
||||
var roleConnLimit int
|
||||
var roleName, roleValidUntil string
|
||||
err = conn.QueryRow("SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolconnlimit, COALESCE(rolvaliduntil::TEXT, 'infinity'), rolbypassrls FROM pg_catalog.pg_roles WHERE rolname=$1", roleId).Scan(&roleName, &roleSuperuser, &roleInherit, &roleCreateRole, &roleCreateDB, &roleCanLogin, &roleReplication, &roleConnLimit, &roleValidUntil, &roleBypassRLS)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
log.Printf("[WARN] PostgreSQL role (%s) not found", roleId)
|
||||
d.SetId("")
|
||||
return nil
|
||||
case err != nil:
|
||||
return errwrap.Wrapf("Error reading role: {{err}}", err)
|
||||
default:
|
||||
d.Set("login", canLogin)
|
||||
d.Set(roleNameAttr, roleName)
|
||||
d.Set(roleBypassRLSAttr, roleBypassRLS)
|
||||
d.Set(roleConnLimitAttr, roleConnLimit)
|
||||
d.Set(roleCreateDBAttr, roleCreateDB)
|
||||
d.Set(roleCreateRoleAttr, roleCreateRole)
|
||||
d.Set(roleEncryptedPassAttr, true)
|
||||
d.Set(roleInheritAttr, roleInherit)
|
||||
d.Set(roleLoginAttr, roleCanLogin)
|
||||
d.Set(roleReplicationAttr, roleReplication)
|
||||
d.Set(roleSuperuserAttr, roleSuperuser)
|
||||
d.Set(roleValidUntilAttr, roleValidUntil)
|
||||
d.SetId(roleName)
|
||||
}
|
||||
|
||||
if !roleSuperuser {
|
||||
// Return early if not superuser user
|
||||
return nil
|
||||
}
|
||||
|
||||
var rolePassword string
|
||||
err = conn.QueryRow("SELECT COALESCE(passwd, '') FROM pg_catalog.pg_shadow AS s WHERE s.usename = $1", roleId).Scan(&rolePassword)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return fmt.Errorf("PostgreSQL role (%s) not found in shadow database: {{err}}", roleId)
|
||||
case err != nil:
|
||||
return errwrap.Wrapf("Error reading role: {{err}}", err)
|
||||
default:
|
||||
d.Set(rolePasswordAttr, rolePassword)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func resourcePostgreSQLRoleUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*Client)
|
||||
conn, err := client.Connect()
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
d.Partial(true)
|
||||
|
||||
roleName := d.Get("name").(string)
|
||||
|
||||
if d.HasChange("login") {
|
||||
loginAttr := getLoginStr(d.Get("login").(bool))
|
||||
query := fmt.Sprintf("ALTER ROLE %s %s", pq.QuoteIdentifier(roleName), pq.QuoteIdentifier(loginAttr))
|
||||
_, err := conn.Query(query)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error updating login attribute for role: {{err}}", err)
|
||||
}
|
||||
|
||||
d.SetPartial("login")
|
||||
if err := setRoleName(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
password := d.Get("password").(string)
|
||||
if d.HasChange("password") {
|
||||
encryptedCfg := getEncryptedStr(d.Get("encrypted").(bool))
|
||||
|
||||
query := fmt.Sprintf("ALTER ROLE %s %s PASSWORD '%s'", pq.QuoteIdentifier(roleName), encryptedCfg, password)
|
||||
_, err := conn.Query(query)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error updating password attribute for role: {{err}}", err)
|
||||
}
|
||||
|
||||
d.SetPartial("password")
|
||||
if err := setRoleBypassRLS(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.HasChange("encrypted") {
|
||||
encryptedCfg := getEncryptedStr(d.Get("encrypted").(bool))
|
||||
|
||||
query := fmt.Sprintf("ALTER ROLE %s %s PASSWORD '%s'", pq.QuoteIdentifier(roleName), encryptedCfg, password)
|
||||
_, err := conn.Query(query)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error updating encrypted attribute for role: {{err}}", err)
|
||||
}
|
||||
|
||||
d.SetPartial("encrypted")
|
||||
if err := setRoleConnLimit(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setRoleCreateDB(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setRoleCreateRole(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setRoleInherit(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setRoleLogin(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setRoleReplication(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setRoleSuperuser(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setRoleValidUntil(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.Partial(false)
|
||||
return resourcePostgreSQLRoleRead(d, meta)
|
||||
}
|
||||
|
||||
func getLoginStr(canLogin bool) string {
|
||||
if canLogin {
|
||||
return "login"
|
||||
func setRoleName(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(roleNameAttr) {
|
||||
return nil
|
||||
}
|
||||
return "nologin"
|
||||
|
||||
oraw, nraw := d.GetChange(roleNameAttr)
|
||||
o := oraw.(string)
|
||||
n := nraw.(string)
|
||||
if n == "" {
|
||||
return errors.New("Error setting role name to an empty string")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("ALTER ROLE %s RENAME TO %s", pq.QuoteIdentifier(o), pq.QuoteIdentifier(n))
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating role NAME: {{err}}", err)
|
||||
}
|
||||
d.SetId(n)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEncryptedStr(isEncrypted bool) string {
|
||||
if isEncrypted {
|
||||
return "encrypted"
|
||||
func setRoleBypassRLS(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(roleBypassRLSAttr) {
|
||||
return nil
|
||||
}
|
||||
return "unencrypted"
|
||||
|
||||
bypassRLS := d.Get(roleBypassRLSAttr).(bool)
|
||||
tok := "NOBYPASSRLS"
|
||||
if bypassRLS {
|
||||
tok = "BYPASSRLS"
|
||||
}
|
||||
roleName := d.Get(roleNameAttr).(string)
|
||||
query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating role BYPASSRLS: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setRoleConnLimit(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(roleConnLimitAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
connLimit := d.Get(roleConnLimitAttr).(int)
|
||||
roleName := d.Get(roleNameAttr).(string)
|
||||
query := fmt.Sprintf("ALTER ROLE %s CONNECTION LIMIT = %d", pq.QuoteIdentifier(roleName), connLimit)
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating role CONNECTION LIMIT: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setRoleCreateDB(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(roleCreateDBAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
createDB := d.Get(roleCreateDBAttr).(bool)
|
||||
tok := "NOCREATEDB"
|
||||
if createDB {
|
||||
tok = "CREATEDB"
|
||||
}
|
||||
roleName := d.Get(roleNameAttr).(string)
|
||||
query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating role CREATEDB: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setRoleCreateRole(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(roleCreateRoleAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
createRole := d.Get(roleCreateRoleAttr).(bool)
|
||||
tok := "NOCREATEROLE"
|
||||
if createRole {
|
||||
tok = "CREATEROLE"
|
||||
}
|
||||
roleName := d.Get(roleNameAttr).(string)
|
||||
query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating role CREATEROLE: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setRoleInherit(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(roleInheritAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
inherit := d.Get(roleInheritAttr).(bool)
|
||||
tok := "NOINHERIT"
|
||||
if inherit {
|
||||
tok = "INHERIT"
|
||||
}
|
||||
roleName := d.Get(roleNameAttr).(string)
|
||||
query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating role INHERIT: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setRoleLogin(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(roleLoginAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
login := d.Get(roleLoginAttr).(bool)
|
||||
tok := "NOLOGIN"
|
||||
if login {
|
||||
tok = "LOGIN"
|
||||
}
|
||||
roleName := d.Get(roleNameAttr).(string)
|
||||
query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating role LOGIN: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setRoleReplication(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(roleReplicationAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
replication := d.Get(roleReplicationAttr).(bool)
|
||||
tok := "NOREPLICATION"
|
||||
if replication {
|
||||
tok = "REPLICATION"
|
||||
}
|
||||
roleName := d.Get(roleNameAttr).(string)
|
||||
query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating role REPLICATION: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setRoleSuperuser(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(roleSuperuserAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
superuser := d.Get(roleSuperuserAttr).(bool)
|
||||
tok := "NOSUPERUSER"
|
||||
if superuser {
|
||||
tok = "SUPERUSER"
|
||||
}
|
||||
roleName := d.Get(roleNameAttr).(string)
|
||||
query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating role SUPERUSER: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setRoleValidUntil(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(roleValidUntilAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
validUntil := d.Get(roleValidUntilAttr).(string)
|
||||
if validUntil == "" {
|
||||
return nil
|
||||
} else if strings.ToLower(validUntil) == "infinity" {
|
||||
validUntil = "infinity"
|
||||
}
|
||||
|
||||
roleName := d.Get(roleNameAttr).(string)
|
||||
query := fmt.Sprintf("ALTER ROLE %s VALID UNTIL '%s'", pq.QuoteIdentifier(roleName), pqQuoteLiteral(validUntil))
|
||||
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating role VALID UNTIL: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
)
|
||||
|
||||
func TestAccPostgresqlRole_Basic(t *testing.T) {
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
|
@ -24,6 +23,29 @@ func TestAccPostgresqlRole_Basic(t *testing.T) {
|
|||
"postgresql_role.myrole2", "name", "myrole2"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.myrole2", "login", "true"),
|
||||
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.role_with_defaults", "name", "testing_role_with_defaults"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.role_with_defaults", "superuser", "false"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.role_with_defaults", "create_database", "false"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.role_with_defaults", "create_role", "false"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.role_with_defaults", "inherit", "false"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.role_with_defaults", "replication", "false"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.role_with_defaults", "bypass_row_level_security", "false"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.role_with_defaults", "connection_limit", "-1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.role_with_defaults", "encrypted_password", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.role_with_defaults", "password", ""),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.role_with_defaults", "valid_until", "infinity"),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
@ -129,4 +151,19 @@ resource "postgresql_role" "role_with_pwd_no_login" {
|
|||
resource "postgresql_role" "role_simple" {
|
||||
name = "role_simple"
|
||||
}
|
||||
|
||||
resource "postgresql_role" "role_with_defaults" {
|
||||
name = "testing_role_with_defaults"
|
||||
superuser = false
|
||||
create_database = false
|
||||
create_role = false
|
||||
inherit = false
|
||||
login = false
|
||||
replication = false
|
||||
bypass_row_level_security = false
|
||||
connection_limit = -1
|
||||
encrypted_password = true
|
||||
password = ""
|
||||
valid_until = "infinity"
|
||||
}
|
||||
`
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
package postgresql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
const (
|
||||
schemaNameAttr = "name"
|
||||
schemaAuthorizationAttr = "authorization"
|
||||
)
|
||||
|
||||
func resourcePostgreSQLSchema() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourcePostgreSQLSchemaCreate,
|
||||
Read: resourcePostgreSQLSchemaRead,
|
||||
Update: resourcePostgreSQLSchemaUpdate,
|
||||
Delete: resourcePostgreSQLSchemaDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
State: schema.ImportStatePassthrough,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
schemaNameAttr: {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "The name of the schema",
|
||||
},
|
||||
schemaAuthorizationAttr: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "The role name of the owner of the schema",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourcePostgreSQLSchemaCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error connecting to PostgreSQL: {{err}}", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
schemaName := d.Get(schemaNameAttr).(string)
|
||||
b := bytes.NewBufferString("CREATE SCHEMA ")
|
||||
fmt.Fprintf(b, pq.QuoteIdentifier(schemaName))
|
||||
|
||||
if v, ok := d.GetOk(schemaAuthorizationAttr); ok {
|
||||
fmt.Fprint(b, " AUTHORIZATION ", pq.QuoteIdentifier(v.(string)))
|
||||
}
|
||||
|
||||
query := b.String()
|
||||
_, err = conn.Query(query)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(fmt.Sprintf("Error creating schema %s: {{err}}", schemaName), err)
|
||||
}
|
||||
|
||||
d.SetId(schemaName)
|
||||
|
||||
return resourcePostgreSQLSchemaRead(d, meta)
|
||||
}
|
||||
|
||||
func resourcePostgreSQLSchemaDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*Client)
|
||||
conn, err := client.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
schemaName := d.Get(schemaNameAttr).(string)
|
||||
query := fmt.Sprintf("DROP SCHEMA %s", pq.QuoteIdentifier(schemaName))
|
||||
_, err = conn.Query(query)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error deleting schema: {{err}}", err)
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourcePostgreSQLSchemaRead(d *schema.ResourceData, meta interface{}) error {
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
schemaId := d.Id()
|
||||
var schemaName, schemaAuthorization string
|
||||
err = conn.QueryRow("SELECT nspname, pg_catalog.pg_get_userbyid(nspowner) FROM pg_catalog.pg_namespace WHERE nspname=$1", schemaId).Scan(&schemaName, &schemaAuthorization)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
log.Printf("[WARN] PostgreSQL schema (%s) not found", schemaId)
|
||||
d.SetId("")
|
||||
return nil
|
||||
case err != nil:
|
||||
return errwrap.Wrapf("Error reading schema: {{err}}", err)
|
||||
default:
|
||||
d.Set(schemaNameAttr, schemaName)
|
||||
d.Set(schemaAuthorizationAttr, schemaAuthorization)
|
||||
d.SetId(schemaName)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func resourcePostgreSQLSchemaUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err := setSchemaName(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setSchemaAuthorization(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return resourcePostgreSQLSchemaRead(d, meta)
|
||||
}
|
||||
|
||||
func setSchemaName(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(schemaNameAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
oraw, nraw := d.GetChange(schemaNameAttr)
|
||||
o := oraw.(string)
|
||||
n := nraw.(string)
|
||||
if n == "" {
|
||||
return errors.New("Error setting schema name to an empty string")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("ALTER SCHEMA %s RENAME TO %s", pq.QuoteIdentifier(o), pq.QuoteIdentifier(n))
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating schema NAME: {{err}}", err)
|
||||
}
|
||||
d.SetId(n)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSchemaAuthorization(conn *sql.DB, d *schema.ResourceData) error {
|
||||
if !d.HasChange(schemaAuthorizationAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
schemaAuthorization := d.Get(schemaAuthorizationAttr).(string)
|
||||
if schemaAuthorization == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
schemaName := d.Get(schemaNameAttr).(string)
|
||||
query := fmt.Sprintf("ALTER SCHEMA %s OWNER TO %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(schemaAuthorization))
|
||||
|
||||
if _, err := conn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating schema AUTHORIZATION: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package postgresql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccPostgresqlSchema_Basic(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckPostgresqlSchemaDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccPostgresqlSchemaConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckPostgresqlSchemaExists("postgresql_schema.test1", "foo"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.myrole3", "name", "myrole3"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.myrole3", "login", "true"),
|
||||
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema.test1", "name", "foo"),
|
||||
// `postgres` is a calculated value
|
||||
// based on the username used in the
|
||||
// provider
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema.test1", "authorization", "postgres"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckPostgresqlSchemaDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccPostgresqlSchemaAuthConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckPostgresqlSchemaExists("postgresql_schema.test2", "foo2"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.myrole4", "name", "myrole4"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.myrole4", "login", "true"),
|
||||
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema.test2", "name", "foo2"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema.test2", "authorization", "myrole4"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckPostgresqlSchemaDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*Client)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "postgresql_schema" {
|
||||
continue
|
||||
}
|
||||
|
||||
exists, err := checkSchemaExists(client, rs.Primary.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error checking schema %s", err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
return fmt.Errorf("Schema still exists after destroy")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckPostgresqlSchemaExists(n string, schemaName string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Resource not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
actualSchemaName := rs.Primary.Attributes["name"]
|
||||
if actualSchemaName != schemaName {
|
||||
return fmt.Errorf("Wrong value for schema name expected %s got %s", schemaName, actualSchemaName)
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*Client)
|
||||
exists, err := checkSchemaExists(client, rs.Primary.ID)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error checking schema %s", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("Schema not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkSchemaExists(client *Client, schemaName string) (bool, error) {
|
||||
conn, err := client.Connect()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var _rez string
|
||||
err = conn.QueryRow("SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname=$1", schemaName).Scan(&_rez)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return false, nil
|
||||
case err != nil:
|
||||
return false, fmt.Errorf("Error reading info about schema: %s", err)
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccPostgresqlSchemaConfig = `
|
||||
resource "postgresql_role" "myrole3" {
|
||||
name = "myrole3"
|
||||
login = true
|
||||
}
|
||||
|
||||
resource "postgresql_schema" "test1" {
|
||||
name = "foo"
|
||||
}
|
||||
`
|
||||
|
||||
var testAccPostgresqlSchemaAuthConfig = `
|
||||
resource "postgresql_role" "myrole4" {
|
||||
name = "myrole4"
|
||||
login = true
|
||||
}
|
||||
|
||||
resource "postgresql_schema" "test2" {
|
||||
name = "foo2"
|
||||
authorization = "${postgresql_role.myrole4.name}"
|
||||
}
|
||||
`
|
|
@ -159,6 +159,9 @@ To make a resource importable, please see the
|
|||
* openstack_networking_secgroup_v2
|
||||
* openstack_networking_subnet_v2
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
* postgresql_database
|
||||
|
||||
### Triton
|
||||
|
||||
|
|
|
@ -18,9 +18,10 @@ Use the navigation to the left to read about the available resources.
|
|||
provider "postgresql" {
|
||||
host = "postgres_server_ip"
|
||||
port = 5432
|
||||
database = "postgres"
|
||||
username = "postgres_user"
|
||||
password = "postgres_password"
|
||||
ssl_mode = "require"
|
||||
sslmode = "require"
|
||||
connect_timeout = 15
|
||||
}
|
||||
|
||||
|
@ -61,9 +62,17 @@ The following arguments are supported:
|
|||
|
||||
* `host` - (Required) The address for the postgresql server connection.
|
||||
* `port` - (Optional) The port for the postgresql server connection. The default is `5432`.
|
||||
* `database` - (Optional) Database to connect to. The default is `postgres`.
|
||||
* `username` - (Required) Username for the server connection.
|
||||
* `password` - (Optional) Password for the server connection.
|
||||
* `ssl_mode` - (Optional) Set the priority for an SSL connection to the server.
|
||||
* `connect_timeout` - (Optional) Maximum wait for connection, in seconds. Zero means wait indefinitely, the default is `15`.
|
||||
The default is `prefer`; the full set of options and their implications
|
||||
can be seen [in the libpq SSL guide](http://www.postgresql.org/docs/9.4/static/libpq-ssl.html#LIBPQ-SSL-PROTECTION).
|
||||
* `sslmode` - (Optional) Set the priority for an SSL connection to the server.
|
||||
Valid values for `sslmode` are (note: `prefer` is not supported by Go's
|
||||
[`lib/pq`](https://godoc.org/github.com/lib/pq)):
|
||||
* disable - No SSL
|
||||
* require - Always SSL (the default, also skip verification)
|
||||
* verify-ca - Always SSL (verify that the certificate presented by the server was signed by a trusted CA)
|
||||
* verify-full - Always SSL (verify that the certification presented by the server was signed by a trusted CA and the server host name matches the one in the certificate)
|
||||
Additional information on the options and their implications can be seen
|
||||
[in the `libpq(3)` SSL guide](http://www.postgresql.org/docs/current/static/libpq-ssl.html#LIBPQ-SSL-PROTECTION).
|
||||
* `connect_timeout` - (Optional) Maximum wait for connection, in seconds. The
|
||||
default is `180s`. Zero or not specified means wait indefinitely.
|
||||
|
|
|
@ -8,8 +8,8 @@ description: |-
|
|||
|
||||
# postgresql\_database
|
||||
|
||||
The ``postgresql_database`` resource creates and manages a database on a PostgreSQL
|
||||
server.
|
||||
The ``postgresql_database`` resource creates and manages a database instance on
|
||||
a PostgreSQL server.
|
||||
|
||||
|
||||
## Usage
|
||||
|
@ -18,13 +18,94 @@ server.
|
|||
resource "postgresql_database" "my_db" {
|
||||
name = "my_db"
|
||||
owner = "my_role"
|
||||
template = "template0"
|
||||
collation = "C"
|
||||
connection_limit = -1
|
||||
allow_connections = true
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
* `name` - (Required) The name of the database. Must be unique on the PostgreSQL server instance
|
||||
where it is configured.
|
||||
* `name` - (Required) The name of the database. Must be unique on the PostgreSQL
|
||||
server instance where it is configured.
|
||||
|
||||
* `owner` - (Optional) The owner role of the database. If not specified the default is the user executing the command. To create a database owned by another role, you must be a direct or indirect member of that role, or be a superuser.
|
||||
* `owner` - (Optional) The role name of the user who will own the database, or
|
||||
`DEFAULT` to use the default (namely, the user executing the command). To
|
||||
create a database owned by another role or to change the owner of an existing
|
||||
database, you must be a direct or indirect member of the specified role, or
|
||||
the username in the provider is a superuser.
|
||||
|
||||
* `tablespace_name` - (Optional) The name of the tablespace that will be
|
||||
associated with the database, or `DEFAULT` to use the template database's
|
||||
tablespace. This tablespace will be the default tablespace used for objects
|
||||
created in this database.
|
||||
|
||||
* `connection_limit` - (Optional) How many concurrent connections can be
|
||||
established to this database. `-1` (the default) means no limit.
|
||||
|
||||
* `allow_connections` - (Optional) If `false` then no one can connect to this
|
||||
database. The default is `true`, allowing connections (except as restricted by
|
||||
other mechanisms, such as `GRANT` or `REVOKE CONNECT`).
|
||||
|
||||
* `is_template` - (Optional) If `true`, then this database can be cloned by any
|
||||
user with `CREATEDB` privileges; if `false` (the default), then only
|
||||
superusers or the owner of the database can clone it.
|
||||
|
||||
* `template` - (Optional) The name of the template database from which to create
|
||||
the database, or `DEFAULT` to use the default template (`template0`). NOTE:
|
||||
the default in Terraform is `template0`, not `template1`. Changing this value
|
||||
will force the creation of a new resource as this value can only be changed
|
||||
when a database is created.
|
||||
|
||||
* `encoding` - (Optional) Character set encoding to use in the database.
|
||||
Specify a string constant (e.g. `UTF8` or `SQL_ASCII`), or an integer encoding
|
||||
number. If unset or set to an empty string the default encoding is set to
|
||||
`UTF8`. If set to `DEFAULT` Terraform will use the same encoding as the
|
||||
template database. Changing this value will force the creation of a new
|
||||
resource as this value can only be changed when a database is created.
|
||||
|
||||
* `lc_collate` - (Optional) Collation order (`LC_COLLATE`) to use in the
|
||||
database. This affects the sort order applied to strings, e.g. in queries
|
||||
with `ORDER BY`, as well as the order used in indexes on text columns. If
|
||||
unset or set to an empty string the default collation is set to `C`. If set
|
||||
to `DEFAULT` Terraform will use the same collation order as the specified
|
||||
`template` database. Changing this value will force the creation of a new
|
||||
resource as this value can only be changed when a database is created.
|
||||
|
||||
* `lc_ctype` - (Optional) Character classification (`LC_CTYPE`) to use in the
|
||||
database. This affects the categorization of characters, e.g. lower, upper and
|
||||
digit. If unset or set to an empty string the default character classification
|
||||
is set to `C`. If set to `DEFAULT` Terraform will use the character
|
||||
classification of the specified `template` database. Changing this value will
|
||||
force the creation of a new resource as this value can only be changed when a
|
||||
database is created.
|
||||
|
||||
## Import Example
|
||||
|
||||
`postgresql_database` supports importing resources. Supposing the following
|
||||
Terraform:
|
||||
|
||||
```
|
||||
provider "postgresql" {
|
||||
alias = "admindb"
|
||||
}
|
||||
|
||||
resource "postgresql_database" "db1" {
|
||||
provider = "postgresql.admindb"
|
||||
|
||||
name = "testdb1"
|
||||
}
|
||||
```
|
||||
|
||||
It is possible to import a `postgresql_database` resource with the following
|
||||
command:
|
||||
|
||||
```
|
||||
$ terraform import postgresql_database.db1 testdb1
|
||||
```
|
||||
|
||||
Where `testdb1` is the name of the database to import and
|
||||
`postgresql_database.db1` is the name of the resource whose state will be
|
||||
populated as a result of the command.
|
||||
|
|
|
@ -3,7 +3,7 @@ layout: "postgresql"
|
|||
page_title: "PostgreSQL: postgresql_role"
|
||||
sidebar_current: "docs-postgresql-resource-postgresql_role"
|
||||
description: |-
|
||||
Creates and manages a database on a PostgreSQL server.
|
||||
Creates and manages a role on a PostgreSQL server.
|
||||
---
|
||||
|
||||
# postgresql\_role
|
||||
|
@ -19,19 +19,93 @@ resource "postgresql_role" "my_role" {
|
|||
name = "my_role"
|
||||
login = true
|
||||
password = "mypass"
|
||||
encrypted = true
|
||||
}
|
||||
|
||||
resource "postgresql_role" "my_replication_role" {
|
||||
name = "replication_role"
|
||||
replication = true
|
||||
login = true
|
||||
connection_limit = 5
|
||||
password = "md5c98cbfeb6a347a47eb8e96cfb4c4b890"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
* `name` - (Required) The name of the role. Must be unique on the PostgreSQL server instance
|
||||
where it is configured.
|
||||
* `name` - (Required) The name of the role. Must be unique on the PostgreSQL
|
||||
server instance where it is configured.
|
||||
|
||||
* `login` - (Optional) Configures whether a role is allowed to log in; that is, whether the role can be given as the initial session authorization name during client connection. Corresponds to the LOGIN/NOLOGIN
|
||||
clauses in 'CREATE ROLE'. Default value is false.
|
||||
* `superuser` - (Optional) Defines whether the role is a "superuser", and
|
||||
therefore can override all access restrictions within the database. Default
|
||||
value is `false`.
|
||||
|
||||
* `password` - (Optional) Sets the role's password. (A password is only of use for roles having the LOGIN attribute, but you can nonetheless define one for roles without it.) If you do not plan to use password authentication you can omit this option. If no password is specified, the password will be set to null and password authentication will always fail for that user.
|
||||
* `create_database` - (Optional) Defines a role's ability to execute `CREATE
|
||||
DATABASE`. Default value is `false`.
|
||||
|
||||
* `encrypted` - (Optional) Corresponds to ENCRYPTED, UNENCRYPTED in PostgreSQL. This controls whether the password is stored encrypted in the system catalogs. Default is false.
|
||||
* `create_role` - (Optional) Defines a role's ability to execute `CREATE ROLE`.
|
||||
A role with this privilege can also alter and drop other roles. Default value
|
||||
is `false`.
|
||||
|
||||
* `inherit` - (Optional) Defines whether a role "inherits" the privileges of
|
||||
roles it is a member of. Default value is `true`.
|
||||
|
||||
* `login` - (Optional) Defines whether role is allowed to log in. Roles without
|
||||
this attribute are useful for managing database privileges, but are not users
|
||||
in the usual sense of the word. Default value is `false`.
|
||||
|
||||
* `replication` - (Optional) Defines whether a role is allowed to initiate
|
||||
streaming replication or put the system in and out of backup mode. Default
|
||||
value is `false`
|
||||
|
||||
* `bypass_row_level_security` - (Optional) Defines whether a role bypasses every
|
||||
row-level security (RLS) policy. Default value is `false`.
|
||||
|
||||
* `connection_limit` - (Optional) If this role can log in, this specifies how
|
||||
many concurrent connections the role can establish. `-1` (the default) means no
|
||||
limit.
|
||||
|
||||
* `encrypted_password` - (Optional) Defines whether the password is stored
|
||||
encrypted in the system catalogs. Default value is `true`. NOTE: this value
|
||||
is always set (to the conservative and safe value), but may interfere with the
|
||||
behavior of
|
||||
[PostgreSQL's `password_encryption` setting](https://www.postgresql.org/docs/current/static/runtime-config-connection.html#GUC-PASSWORD-ENCRYPTION).
|
||||
|
||||
* `password` - (Optional) Sets the role's password. (A password is only of use
|
||||
for roles having the `login` attribute set to true, but you can nonetheless
|
||||
define one for roles without it.) Roles without a password explicitly set are
|
||||
left alone. If the password is set to the magic value `NULL`, the password
|
||||
will be always be cleared.
|
||||
|
||||
* `valid_until` - (Optional) Defines the date and time after which the role's
|
||||
password is no longer valid. Established connections past this `valid_time`
|
||||
will have to be manually terminated. This value corresponds to a PostgreSQL
|
||||
datetime. If omitted or the magic value `NULL` is used, `valid_until` will be
|
||||
set to `infinity`. Default is `NULL`, therefore `infinity`.
|
||||
|
||||
## Import Example
|
||||
|
||||
`postgresql_role` supports importing resources. Supposing the following
|
||||
Terraform:
|
||||
|
||||
```
|
||||
provider "postgresql" {
|
||||
alias = "admindb"
|
||||
}
|
||||
|
||||
resource "postgresql_role" "replication_role" {
|
||||
provider = "postgresql.admindb"
|
||||
|
||||
name = "replication_name"
|
||||
}
|
||||
```
|
||||
|
||||
It is possible to import a `postgresql_role` resource with the following
|
||||
command:
|
||||
|
||||
```
|
||||
$ terraform import postgresql_role.replication_role replication_name
|
||||
```
|
||||
|
||||
Where `replication_name` is the name of the role to import and
|
||||
`postgresql_role.replication_role` is the name of the resource whose state will
|
||||
be populated as a result of the command.
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
layout: "postgresql"
|
||||
page_title: "PostgreSQL: postgresql_schema"
|
||||
sidebar_current: "docs-postgresql-resource-postgresql_schema"
|
||||
description: |-
|
||||
Creates and manages a schema within a PostgreSQL database.
|
||||
---
|
||||
|
||||
# postgresql\_schema
|
||||
|
||||
The ``postgresql_schema`` resource creates and manages a schema within a
|
||||
PostgreSQL database.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
resource "postgresql_schema" "my_schema" {
|
||||
name = "my_schema"
|
||||
authorization = "my_role"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
* `name` - (Required) The name of the schema. Must be unique in the PostgreSQL
|
||||
database instance where it is configured.
|
||||
|
||||
* `authorization` - (Optional) The owner of the schema. Defaults to the
|
||||
username configured in the schema's provider.
|
||||
|
||||
## Import Example
|
||||
|
||||
`postgresql_schema` supports importing resources. Supposing the following
|
||||
Terraform:
|
||||
|
||||
```
|
||||
provider "postgresql" {
|
||||
alias = "admindb"
|
||||
}
|
||||
|
||||
resource "postgresql_schema" "schema_foo" {
|
||||
provider = "postgresql.admindb"
|
||||
|
||||
name = "my_schema"
|
||||
}
|
||||
```
|
||||
|
||||
It is possible to import a `postgresql_schema` resource with the following
|
||||
command:
|
||||
|
||||
```
|
||||
$ terraform import postgresql_schema.schema_foo my_schema
|
||||
```
|
||||
|
||||
Where `my_schema` is the name of the schema in the PostgreSQL database and
|
||||
`postgresql_schema.schema_foo` is the name of the resource whose state will be
|
||||
populated as a result of the command.
|
Loading…
Reference in New Issue