github.com/ffrizzo/terraform@v0.8.2-0.20161219200057-992e12335f3d/builtin/providers/postgresql/resource_postgresql_database.go (about)

     1  package postgresql
     2  
     3  import (
     4  	"bytes"
     5  	"database/sql"
     6  	"errors"
     7  	"fmt"
     8  	"log"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/errwrap"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  	"github.com/lib/pq"
    14  )
    15  
    16  const (
    17  	dbAllowConnsAttr = "allow_connections"
    18  	dbCTypeAttr      = "lc_ctype"
    19  	dbCollationAttr  = "lc_collate"
    20  	dbConnLimitAttr  = "connection_limit"
    21  	dbEncodingAttr   = "encoding"
    22  	dbIsTemplateAttr = "is_template"
    23  	dbNameAttr       = "name"
    24  	dbOwnerAttr      = "owner"
    25  	dbTablespaceAttr = "tablespace_name"
    26  	dbTemplateAttr   = "template"
    27  )
    28  
    29  func resourcePostgreSQLDatabase() *schema.Resource {
    30  	return &schema.Resource{
    31  		Create: resourcePostgreSQLDatabaseCreate,
    32  		Read:   resourcePostgreSQLDatabaseRead,
    33  		Update: resourcePostgreSQLDatabaseUpdate,
    34  		Delete: resourcePostgreSQLDatabaseDelete,
    35  		Importer: &schema.ResourceImporter{
    36  			State: schema.ImportStatePassthrough,
    37  		},
    38  
    39  		Schema: map[string]*schema.Schema{
    40  			dbNameAttr: {
    41  				Type:        schema.TypeString,
    42  				Required:    true,
    43  				Description: "The PostgreSQL database name to connect to",
    44  			},
    45  			dbOwnerAttr: {
    46  				Type:        schema.TypeString,
    47  				Optional:    true,
    48  				Computed:    true,
    49  				Description: "The role name of the user who will own the new database",
    50  			},
    51  			dbTemplateAttr: {
    52  				Type:        schema.TypeString,
    53  				Optional:    true,
    54  				ForceNew:    true,
    55  				Computed:    true,
    56  				Description: "The name of the template from which to create the new database",
    57  			},
    58  			dbEncodingAttr: {
    59  				Type:        schema.TypeString,
    60  				Optional:    true,
    61  				Computed:    true,
    62  				ForceNew:    true,
    63  				Description: "Character set encoding to use in the new database",
    64  			},
    65  			dbCollationAttr: {
    66  				Type:        schema.TypeString,
    67  				Optional:    true,
    68  				Computed:    true,
    69  				ForceNew:    true,
    70  				Description: "Collation order (LC_COLLATE) to use in the new database",
    71  			},
    72  			dbCTypeAttr: {
    73  				Type:        schema.TypeString,
    74  				Optional:    true,
    75  				Computed:    true,
    76  				ForceNew:    true,
    77  				Description: "Character classification (LC_CTYPE) to use in the new database",
    78  			},
    79  			dbTablespaceAttr: {
    80  				Type:        schema.TypeString,
    81  				Optional:    true,
    82  				Computed:    true,
    83  				Description: "The name of the tablespace that will be associated with the new database",
    84  			},
    85  			dbConnLimitAttr: {
    86  				Type:         schema.TypeInt,
    87  				Optional:     true,
    88  				Computed:     true,
    89  				Description:  "How many concurrent connections can be made to this database",
    90  				ValidateFunc: validateConnLimit,
    91  			},
    92  			dbAllowConnsAttr: {
    93  				Type:        schema.TypeBool,
    94  				Optional:    true,
    95  				Default:     true,
    96  				Description: "If false then no one can connect to this database",
    97  			},
    98  			dbIsTemplateAttr: {
    99  				Type:        schema.TypeBool,
   100  				Optional:    true,
   101  				Computed:    true,
   102  				Description: "If true, then this database can be cloned by any user with CREATEDB privileges",
   103  			},
   104  		},
   105  	}
   106  }
   107  
   108  func resourcePostgreSQLDatabaseCreate(d *schema.ResourceData, meta interface{}) error {
   109  	c := meta.(*Client)
   110  	conn, err := c.Connect()
   111  	if err != nil {
   112  		return errwrap.Wrapf("Error connecting to PostgreSQL: {{err}}", err)
   113  	}
   114  	defer conn.Close()
   115  
   116  	dbName := d.Get(dbNameAttr).(string)
   117  	b := bytes.NewBufferString("CREATE DATABASE ")
   118  	fmt.Fprint(b, pq.QuoteIdentifier(dbName))
   119  
   120  	// Handle each option individually and stream results into the query
   121  	// buffer.
   122  
   123  	switch v, ok := d.GetOk(dbOwnerAttr); {
   124  	case ok:
   125  		fmt.Fprint(b, " OWNER ", pq.QuoteIdentifier(v.(string)))
   126  	default:
   127  		// No owner specified in the config, default to using
   128  		// the connecting username.
   129  		fmt.Fprint(b, " OWNER ", pq.QuoteIdentifier(c.username))
   130  	}
   131  
   132  	switch v, ok := d.GetOk(dbTemplateAttr); {
   133  	case ok:
   134  		fmt.Fprint(b, " TEMPLATE ", pq.QuoteIdentifier(v.(string)))
   135  	case v.(string) == "", strings.ToUpper(v.(string)) != "DEFAULT":
   136  		fmt.Fprint(b, " TEMPLATE template0")
   137  	}
   138  
   139  	switch v, ok := d.GetOk(dbEncodingAttr); {
   140  	case ok:
   141  		fmt.Fprint(b, " ENCODING ", pq.QuoteIdentifier(v.(string)))
   142  	case v.(string) == "", strings.ToUpper(v.(string)) != "DEFAULT":
   143  		fmt.Fprint(b, ` ENCODING "UTF8"`)
   144  	}
   145  
   146  	switch v, ok := d.GetOk(dbCollationAttr); {
   147  	case ok:
   148  		fmt.Fprint(b, " LC_COLLATE ", pq.QuoteIdentifier(v.(string)))
   149  	case v.(string) == "", strings.ToUpper(v.(string)) != "DEFAULT":
   150  		fmt.Fprint(b, ` LC_COLLATE "C"`)
   151  	}
   152  
   153  	switch v, ok := d.GetOk(dbCTypeAttr); {
   154  	case ok:
   155  		fmt.Fprint(b, " LC_CTYPE ", pq.QuoteIdentifier(v.(string)))
   156  	case v.(string) == "", strings.ToUpper(v.(string)) != "DEFAULT":
   157  		fmt.Fprint(b, ` LC_CTYPE "C"`)
   158  	}
   159  
   160  	if v, ok := d.GetOk(dbTablespaceAttr); ok {
   161  		fmt.Fprint(b, " TABLESPACE ", pq.QuoteIdentifier(v.(string)))
   162  	}
   163  
   164  	{
   165  		val := d.Get(dbAllowConnsAttr).(bool)
   166  		fmt.Fprint(b, " ALLOW_CONNECTIONS ", val)
   167  	}
   168  
   169  	{
   170  		val := d.Get(dbConnLimitAttr).(int)
   171  		fmt.Fprint(b, " CONNECTION LIMIT ", val)
   172  	}
   173  
   174  	{
   175  		val := d.Get(dbIsTemplateAttr).(bool)
   176  		fmt.Fprint(b, " IS_TEMPLATE ", val)
   177  	}
   178  
   179  	query := b.String()
   180  	_, err = conn.Query(query)
   181  	if err != nil {
   182  		return errwrap.Wrapf(fmt.Sprintf("Error creating database %s: {{err}}", dbName), err)
   183  	}
   184  
   185  	d.SetId(dbName)
   186  
   187  	return resourcePostgreSQLDatabaseRead(d, meta)
   188  }
   189  
   190  func resourcePostgreSQLDatabaseDelete(d *schema.ResourceData, meta interface{}) error {
   191  	c := meta.(*Client)
   192  	conn, err := c.Connect()
   193  	if err != nil {
   194  		return errwrap.Wrapf("Error connecting to PostgreSQL: {{err}}", err)
   195  	}
   196  	defer conn.Close()
   197  
   198  	dbName := d.Get(dbNameAttr).(string)
   199  
   200  	if isTemplate := d.Get(dbIsTemplateAttr).(bool); isTemplate {
   201  		// Template databases must have this attribute cleared before
   202  		// they can be dropped.
   203  		if err := doSetDBIsTemplate(conn, dbName, false); err != nil {
   204  			return errwrap.Wrapf("Error updating database IS_TEMPLATE during DROP DATABASE: {{err}}", err)
   205  		}
   206  	}
   207  
   208  	if err := setDBIsTemplate(conn, d); err != nil {
   209  		return err
   210  	}
   211  
   212  	query := fmt.Sprintf("DROP DATABASE %s", pq.QuoteIdentifier(dbName))
   213  	_, err = conn.Query(query)
   214  	if err != nil {
   215  		return errwrap.Wrapf("Error dropping database: {{err}}", err)
   216  	}
   217  
   218  	d.SetId("")
   219  
   220  	return nil
   221  }
   222  
   223  func resourcePostgreSQLDatabaseRead(d *schema.ResourceData, meta interface{}) error {
   224  	c := meta.(*Client)
   225  	conn, err := c.Connect()
   226  	if err != nil {
   227  		return err
   228  	}
   229  	defer conn.Close()
   230  
   231  	dbId := d.Id()
   232  	var dbName, ownerName string
   233  	err = conn.QueryRow("SELECT d.datname, pg_catalog.pg_get_userbyid(d.datdba) from pg_database d WHERE datname=$1", dbId).Scan(&dbName, &ownerName)
   234  	switch {
   235  	case err == sql.ErrNoRows:
   236  		log.Printf("[WARN] PostgreSQL database (%s) not found", dbId)
   237  		d.SetId("")
   238  		return nil
   239  	case err != nil:
   240  		return errwrap.Wrapf("Error reading database: {{err}}", err)
   241  	}
   242  
   243  	var dbEncoding, dbCollation, dbCType, dbTablespaceName string
   244  	var dbConnLimit int
   245  	var dbAllowConns, dbIsTemplate bool
   246  	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).
   247  		Scan(
   248  			&dbEncoding, &dbCollation, &dbCType, &dbTablespaceName,
   249  			&dbConnLimit, &dbAllowConns, &dbIsTemplate,
   250  		)
   251  	switch {
   252  	case err == sql.ErrNoRows:
   253  		log.Printf("[WARN] PostgreSQL database (%s) not found", dbId)
   254  		d.SetId("")
   255  		return nil
   256  	case err != nil:
   257  		return errwrap.Wrapf("Error reading database: {{err}}", err)
   258  	default:
   259  		d.Set(dbNameAttr, dbName)
   260  		d.Set(dbOwnerAttr, ownerName)
   261  		d.Set(dbEncodingAttr, dbEncoding)
   262  		d.Set(dbCollationAttr, dbCollation)
   263  		d.Set(dbCTypeAttr, dbCType)
   264  		d.Set(dbTablespaceAttr, dbTablespaceName)
   265  		d.Set(dbConnLimitAttr, dbConnLimit)
   266  		d.Set(dbAllowConnsAttr, dbAllowConns)
   267  		d.Set(dbIsTemplateAttr, dbIsTemplate)
   268  		dbTemplate := d.Get(dbTemplateAttr).(string)
   269  		if dbTemplate == "" {
   270  			dbTemplate = "template0"
   271  		}
   272  		d.Set(dbTemplateAttr, dbTemplate)
   273  		return nil
   274  	}
   275  }
   276  
   277  func resourcePostgreSQLDatabaseUpdate(d *schema.ResourceData, meta interface{}) error {
   278  	c := meta.(*Client)
   279  	conn, err := c.Connect()
   280  	if err != nil {
   281  		return err
   282  	}
   283  	defer conn.Close()
   284  
   285  	if err := setDBName(conn, d); err != nil {
   286  		return err
   287  	}
   288  
   289  	if err := setDBOwner(conn, d); err != nil {
   290  		return err
   291  	}
   292  
   293  	if err := setDBTablespace(conn, d); err != nil {
   294  		return err
   295  	}
   296  
   297  	if err := setDBConnLimit(conn, d); err != nil {
   298  		return err
   299  	}
   300  
   301  	if err := setDBAllowConns(conn, d); err != nil {
   302  		return err
   303  	}
   304  
   305  	if err := setDBIsTemplate(conn, d); err != nil {
   306  		return err
   307  	}
   308  
   309  	// Empty values: ALTER DATABASE name RESET configuration_parameter;
   310  
   311  	return resourcePostgreSQLDatabaseRead(d, meta)
   312  }
   313  
   314  func setDBName(conn *sql.DB, d *schema.ResourceData) error {
   315  	if !d.HasChange(dbNameAttr) {
   316  		return nil
   317  	}
   318  
   319  	oraw, nraw := d.GetChange(dbNameAttr)
   320  	o := oraw.(string)
   321  	n := nraw.(string)
   322  	if n == "" {
   323  		return errors.New("Error setting database name to an empty string")
   324  	}
   325  
   326  	query := fmt.Sprintf("ALTER DATABASE %s RENAME TO %s", pq.QuoteIdentifier(o), pq.QuoteIdentifier(n))
   327  	if _, err := conn.Query(query); err != nil {
   328  		return errwrap.Wrapf("Error updating database name: {{err}}", err)
   329  	}
   330  	d.SetId(n)
   331  
   332  	return nil
   333  }
   334  
   335  func setDBOwner(conn *sql.DB, d *schema.ResourceData) error {
   336  	if !d.HasChange(dbOwnerAttr) {
   337  		return nil
   338  	}
   339  
   340  	owner := d.Get(dbOwnerAttr).(string)
   341  	if owner == "" {
   342  		return nil
   343  	}
   344  
   345  	dbName := d.Get(dbNameAttr).(string)
   346  	query := fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", pq.QuoteIdentifier(dbName), pq.QuoteIdentifier(owner))
   347  	if _, err := conn.Query(query); err != nil {
   348  		return errwrap.Wrapf("Error updating database OWNER: {{err}}", err)
   349  	}
   350  
   351  	return nil
   352  }
   353  
   354  func setDBTablespace(conn *sql.DB, d *schema.ResourceData) error {
   355  	if !d.HasChange(dbTablespaceAttr) {
   356  		return nil
   357  	}
   358  
   359  	tbspName := d.Get(dbTablespaceAttr).(string)
   360  	dbName := d.Get(dbNameAttr).(string)
   361  	var query string
   362  	if tbspName == "" || strings.ToUpper(tbspName) == "DEFAULT" {
   363  		query = fmt.Sprintf("ALTER DATABASE %s RESET TABLESPACE", pq.QuoteIdentifier(dbName))
   364  	} else {
   365  		query = fmt.Sprintf("ALTER DATABASE %s SET TABLESPACE %s", pq.QuoteIdentifier(dbName), pq.QuoteIdentifier(tbspName))
   366  	}
   367  
   368  	if _, err := conn.Query(query); err != nil {
   369  		return errwrap.Wrapf("Error updating database TABLESPACE: {{err}}", err)
   370  	}
   371  
   372  	return nil
   373  }
   374  
   375  func setDBConnLimit(conn *sql.DB, d *schema.ResourceData) error {
   376  	if !d.HasChange(dbConnLimitAttr) {
   377  		return nil
   378  	}
   379  
   380  	connLimit := d.Get(dbConnLimitAttr).(int)
   381  	dbName := d.Get(dbNameAttr).(string)
   382  	query := fmt.Sprintf("ALTER DATABASE %s CONNECTION LIMIT = %d", pq.QuoteIdentifier(dbName), connLimit)
   383  	if _, err := conn.Query(query); err != nil {
   384  		return errwrap.Wrapf("Error updating database CONNECTION LIMIT: {{err}}", err)
   385  	}
   386  
   387  	return nil
   388  }
   389  
   390  func setDBAllowConns(conn *sql.DB, d *schema.ResourceData) error {
   391  	if !d.HasChange(dbAllowConnsAttr) {
   392  		return nil
   393  	}
   394  
   395  	allowConns := d.Get(dbAllowConnsAttr).(bool)
   396  	dbName := d.Get(dbNameAttr).(string)
   397  	query := fmt.Sprintf("ALTER DATABASE %s ALLOW_CONNECTIONS %t", pq.QuoteIdentifier(dbName), allowConns)
   398  	if _, err := conn.Query(query); err != nil {
   399  		return errwrap.Wrapf("Error updating database ALLOW_CONNECTIONS: {{err}}", err)
   400  	}
   401  
   402  	return nil
   403  }
   404  
   405  func setDBIsTemplate(conn *sql.DB, d *schema.ResourceData) error {
   406  	if !d.HasChange(dbIsTemplateAttr) {
   407  		return nil
   408  	}
   409  
   410  	if err := doSetDBIsTemplate(conn, d.Get(dbNameAttr).(string), d.Get(dbIsTemplateAttr).(bool)); err != nil {
   411  		return errwrap.Wrapf("Error updating database IS_TEMPLATE: {{err}}", err)
   412  	}
   413  
   414  	return nil
   415  }
   416  
   417  func doSetDBIsTemplate(conn *sql.DB, dbName string, isTemplate bool) error {
   418  	query := fmt.Sprintf("ALTER DATABASE %s IS_TEMPLATE %t", pq.QuoteIdentifier(dbName), isTemplate)
   419  	if _, err := conn.Query(query); err != nil {
   420  		return errwrap.Wrapf("Error updating database IS_TEMPLATE: {{err}}", err)
   421  	}
   422  
   423  	return nil
   424  }