2015-10-27 11:04:19 +01:00
package postgresql
import (
2016-11-07 19:32:51 +01:00
"bytes"
2015-10-27 11:04:19 +01:00
"database/sql"
2016-11-06 09:57:59 +01:00
"errors"
2015-10-27 11:04:19 +01:00
"fmt"
2016-11-06 09:57:59 +01:00
"log"
2015-10-27 11:04:19 +01:00
"strings"
2016-09-05 23:46:40 +02:00
"github.com/hashicorp/errwrap"
2015-10-27 11:04:19 +01:00
"github.com/hashicorp/terraform/helper/schema"
"github.com/lib/pq"
)
2016-11-06 09:57:59 +01:00
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"
)
2016-09-06 00:04:48 +02:00
func resourcePostgreSQLDatabase ( ) * schema . Resource {
2015-10-27 11:04:19 +01:00
return & schema . Resource {
2016-09-06 00:04:48 +02:00
Create : resourcePostgreSQLDatabaseCreate ,
Read : resourcePostgreSQLDatabaseRead ,
Update : resourcePostgreSQLDatabaseUpdate ,
Delete : resourcePostgreSQLDatabaseDelete ,
2016-11-06 09:57:59 +01:00
Importer : & schema . ResourceImporter {
State : schema . ImportStatePassthrough ,
} ,
2015-10-27 11:04:19 +01:00
Schema : map [ string ] * schema . Schema {
2016-11-06 09:57:59 +01:00
dbNameAttr : {
2016-09-06 08:37:39 +02:00
Type : schema . TypeString ,
Required : true ,
Description : "The PostgreSQL database name to connect to" ,
2015-10-27 11:04:19 +01:00
} ,
2016-11-06 09:57:59 +01:00
dbOwnerAttr : {
2016-09-06 08:37:39 +02:00
Type : schema . TypeString ,
Optional : true ,
2016-09-06 08:40:13 +02:00
Computed : true ,
2016-09-06 08:37:39 +02:00
Description : "The role name of the user who will own the new database" ,
2015-10-27 11:04:19 +01:00
} ,
2016-11-06 09:57:59 +01:00
dbTemplateAttr : {
2016-09-06 03:40:35 +02:00
Type : schema . TypeString ,
Optional : true ,
2016-11-06 09:57:59 +01:00
ForceNew : true ,
Computed : true ,
2016-09-06 08:40:13 +02:00
Description : "The name of the template from which to create the new database" ,
2016-09-06 03:40:35 +02:00
} ,
2016-11-06 09:57:59 +01:00
dbEncodingAttr : {
2016-09-06 03:40:35 +02:00
Type : schema . TypeString ,
Optional : true ,
2016-09-06 08:40:13 +02:00
Computed : true ,
2016-09-06 08:40:28 +02:00
ForceNew : true ,
2016-09-06 08:40:13 +02:00
Description : "Character set encoding to use in the new database" ,
2016-09-06 03:40:35 +02:00
} ,
2016-11-06 09:57:59 +01:00
dbCollationAttr : {
2016-09-06 03:40:35 +02:00
Type : schema . TypeString ,
Optional : true ,
2016-09-06 08:40:13 +02:00
Computed : true ,
2016-09-06 08:40:28 +02:00
ForceNew : true ,
2016-09-06 08:40:13 +02:00
Description : "Collation order (LC_COLLATE) to use in the new database" ,
2016-09-06 03:40:35 +02:00
} ,
2016-11-06 09:57:59 +01:00
dbCTypeAttr : {
2016-09-06 03:40:35 +02:00
Type : schema . TypeString ,
Optional : true ,
2016-09-06 08:40:13 +02:00
Computed : true ,
2016-09-06 08:40:28 +02:00
ForceNew : true ,
2016-09-06 08:40:13 +02:00
Description : "Character classification (LC_CTYPE) to use in the new database" ,
2016-09-06 03:40:35 +02:00
} ,
2016-11-06 09:57:59 +01:00
dbTablespaceAttr : {
2016-09-06 03:40:35 +02:00
Type : schema . TypeString ,
Optional : true ,
2016-09-06 08:40:13 +02:00
Computed : true ,
Description : "The name of the tablespace that will be associated with the new database" ,
2016-09-06 03:40:35 +02:00
} ,
2016-11-06 09:57:59 +01:00
dbConnLimitAttr : {
2016-09-06 08:39:22 +02:00
Type : schema . TypeInt ,
Optional : true ,
Computed : true ,
Description : "How many concurrent connections can be made to this database" ,
ValidateFunc : validateConnLimit ,
2016-09-06 03:40:35 +02:00
} ,
2016-11-06 09:57:59 +01:00
dbAllowConnsAttr : {
2016-09-06 03:40:35 +02:00
Type : schema . TypeBool ,
Optional : true ,
2016-11-06 17:46:40 +01:00
Default : true ,
2016-09-06 08:40:13 +02:00
Description : "If false then no one can connect to this database" ,
2016-09-06 03:40:35 +02:00
} ,
2016-11-06 09:57:59 +01:00
dbIsTemplateAttr : {
2016-09-06 03:40:35 +02:00
Type : schema . TypeBool ,
Optional : true ,
2016-09-06 08:40:13 +02:00
Computed : true ,
Description : "If true, then this database can be cloned by any user with CREATEDB privileges" ,
2016-09-06 03:40:35 +02:00
} ,
2015-10-27 11:04:19 +01:00
} ,
}
}
2016-09-06 00:04:48 +02:00
func resourcePostgreSQLDatabaseCreate ( d * schema . ResourceData , meta interface { } ) error {
2016-11-06 09:57:59 +01:00
c := meta . ( * Client )
conn , err := c . Connect ( )
2015-10-27 11:04:19 +01:00
if err != nil {
2016-09-05 23:46:40 +02:00
return errwrap . Wrapf ( "Error connecting to PostgreSQL: {{err}}" , err )
2015-10-27 11:04:19 +01:00
}
defer conn . Close ( )
2016-11-07 19:32:51 +01:00
dbName := d . Get ( dbNameAttr ) . ( string )
b := bytes . NewBufferString ( "CREATE DATABASE " )
fmt . Fprint ( b , pq . QuoteIdentifier ( dbName ) )
2016-11-06 09:57:59 +01:00
2016-11-07 19:32:51 +01:00
// Handle each option individually and stream results into the query
// buffer.
2016-11-06 09:57:59 +01:00
2016-11-07 19:32:51 +01:00
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 ) )
}
2016-11-06 09:57:59 +01:00
2016-11-07 19:32:51 +01:00
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" )
}
2015-10-27 11:04:19 +01:00
2016-11-07 19:32:51 +01:00
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" ` )
}
2016-09-06 03:40:35 +02:00
2016-11-07 19:32:51 +01:00
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" ` )
}
2016-09-06 03:40:35 +02:00
2016-11-07 19:32:51 +01:00
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" ` )
2016-09-06 03:40:35 +02:00
}
2016-11-07 19:32:51 +01:00
if v , ok := d . GetOk ( dbTablespaceAttr ) ; ok {
fmt . Fprint ( b , " TABLESPACE " , pq . QuoteIdentifier ( v . ( string ) ) )
2015-10-27 11:04:19 +01:00
}
2016-11-07 19:32:51 +01:00
{
val := d . Get ( dbAllowConnsAttr ) . ( bool )
fmt . Fprint ( b , " ALLOW_CONNECTIONS " , val )
}
2016-09-06 03:40:35 +02:00
2016-11-07 19:32:51 +01:00
{
val := d . Get ( dbConnLimitAttr ) . ( int )
fmt . Fprint ( b , " CONNECTION LIMIT " , val )
2016-09-06 03:40:35 +02:00
}
2016-11-07 19:32:51 +01:00
{
val := d . Get ( dbIsTemplateAttr ) . ( bool )
fmt . Fprint ( b , " IS_TEMPLATE " , val )
2016-09-06 03:40:35 +02:00
}
2016-11-07 19:32:51 +01:00
query := b . String ( )
2015-10-27 11:04:19 +01:00
_ , err = conn . Query ( query )
if err != nil {
2016-09-06 00:04:48 +02:00
return errwrap . Wrapf ( fmt . Sprintf ( "Error creating database %s: {{err}}" , dbName ) , err )
2015-10-27 11:04:19 +01:00
}
d . SetId ( dbName )
2016-09-06 00:04:48 +02:00
return resourcePostgreSQLDatabaseRead ( d , meta )
2015-10-27 11:04:19 +01:00
}
2016-09-06 00:04:48 +02:00
func resourcePostgreSQLDatabaseDelete ( d * schema . ResourceData , meta interface { } ) error {
2016-11-06 09:57:59 +01:00
c := meta . ( * Client )
conn , err := c . Connect ( )
2015-10-27 11:04:19 +01:00
if err != nil {
2016-09-05 23:46:40 +02:00
return errwrap . Wrapf ( "Error connecting to PostgreSQL: {{err}}" , err )
2015-10-27 11:04:19 +01:00
}
defer conn . Close ( )
2016-11-06 09:57:59 +01:00
dbName := d . Get ( dbNameAttr ) . ( string )
2016-11-06 17:46:40 +01:00
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
}
2015-10-27 11:04:19 +01:00
query := fmt . Sprintf ( "DROP DATABASE %s" , pq . QuoteIdentifier ( dbName ) )
_ , err = conn . Query ( query )
if err != nil {
2016-09-05 23:46:40 +02:00
return errwrap . Wrapf ( "Error dropping database: {{err}}" , err )
2015-10-27 11:04:19 +01:00
}
d . SetId ( "" )
return nil
}
2016-09-06 00:04:48 +02:00
func resourcePostgreSQLDatabaseRead ( d * schema . ResourceData , meta interface { } ) error {
2016-11-06 09:57:59 +01:00
c := meta . ( * Client )
conn , err := c . Connect ( )
2015-10-27 11:04:19 +01:00
if err != nil {
return err
}
defer conn . Close ( )
2016-11-06 09:57:59 +01:00
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 :
2016-11-07 19:32:51 +01:00
log . Printf ( "[WARN] PostgreSQL database (%s) not found" , dbId )
2016-11-06 09:57:59 +01:00
d . SetId ( "" )
return nil
case err != nil :
return errwrap . Wrapf ( "Error reading database: {{err}}" , err )
}
2015-10-27 11:04:19 +01:00
2016-11-06 09:57:59 +01:00
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 ,
)
2015-10-27 11:04:19 +01:00
switch {
case err == sql . ErrNoRows :
2016-11-07 19:32:51 +01:00
log . Printf ( "[WARN] PostgreSQL database (%s) not found" , dbId )
2015-10-27 11:04:19 +01:00
d . SetId ( "" )
return nil
case err != nil :
2016-09-06 00:04:48 +02:00
return errwrap . Wrapf ( "Error reading database: {{err}}" , err )
2015-10-27 11:04:19 +01:00
default :
2016-11-06 09:57:59 +01:00
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 )
2015-10-27 11:04:19 +01:00
return nil
}
}
2016-09-06 00:04:48 +02:00
func resourcePostgreSQLDatabaseUpdate ( d * schema . ResourceData , meta interface { } ) error {
2016-11-06 09:57:59 +01:00
c := meta . ( * Client )
conn , err := c . Connect ( )
2015-10-27 11:04:19 +01:00
if err != nil {
return err
}
defer conn . Close ( )
2016-11-06 09:57:59 +01:00
if err := setDBName ( conn , d ) ; err != nil {
return err
}
2015-10-27 11:04:19 +01:00
2016-11-06 09:57:59 +01:00
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
2015-10-27 11:04:19 +01:00
}
2016-11-06 09:57:59 +01:00
if err := setDBIsTemplate ( conn , d ) ; err != nil {
return err
}
// Empty values: ALTER DATABASE name RESET configuration_parameter;
2016-09-06 00:04:48 +02:00
return resourcePostgreSQLDatabaseRead ( d , meta )
2015-10-27 11:04:19 +01:00
}
2016-11-06 09:57:59 +01:00
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
}
2016-11-06 17:46:40 +01:00
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 {
2016-11-06 09:57:59 +01:00
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
}