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