github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/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 }