github.com/skyscape-cloud-services/terraform@v0.9.2-0.20170609144644-7ece028a1747/builtin/providers/postgresql/resource_postgresql_role.go (about) 1 package postgresql 2 3 import ( 4 "database/sql" 5 "errors" 6 "fmt" 7 "log" 8 "strings" 9 10 "github.com/hashicorp/errwrap" 11 "github.com/hashicorp/terraform/helper/schema" 12 "github.com/lib/pq" 13 ) 14 15 const ( 16 roleBypassRLSAttr = "bypass_row_level_security" 17 roleConnLimitAttr = "connection_limit" 18 roleCreateDBAttr = "create_database" 19 roleCreateRoleAttr = "create_role" 20 roleEncryptedPassAttr = "encrypted_password" 21 roleInheritAttr = "inherit" 22 roleLoginAttr = "login" 23 roleNameAttr = "name" 24 rolePasswordAttr = "password" 25 roleReplicationAttr = "replication" 26 roleSkipDropRoleAttr = "skip_drop_role" 27 roleSkipReassignOwnedAttr = "skip_reassign_owned" 28 roleSuperuserAttr = "superuser" 29 roleValidUntilAttr = "valid_until" 30 31 // Deprecated options 32 roleDepEncryptedAttr = "encrypted" 33 ) 34 35 func resourcePostgreSQLRole() *schema.Resource { 36 return &schema.Resource{ 37 Create: resourcePostgreSQLRoleCreate, 38 Read: resourcePostgreSQLRoleRead, 39 Update: resourcePostgreSQLRoleUpdate, 40 Delete: resourcePostgreSQLRoleDelete, 41 Exists: resourcePostgreSQLRoleExists, 42 Importer: &schema.ResourceImporter{ 43 State: schema.ImportStatePassthrough, 44 }, 45 46 Schema: map[string]*schema.Schema{ 47 roleNameAttr: { 48 Type: schema.TypeString, 49 Required: true, 50 Description: "The name of the role", 51 }, 52 rolePasswordAttr: { 53 Type: schema.TypeString, 54 Optional: true, 55 Computed: true, 56 Sensitive: true, 57 DefaultFunc: schema.EnvDefaultFunc("PGPASSWORD", nil), 58 Description: "Sets the role's password", 59 }, 60 roleDepEncryptedAttr: { 61 Type: schema.TypeString, 62 Optional: true, 63 Deprecated: fmt.Sprintf("Rename PostgreSQL role resource attribute %q to %q", roleDepEncryptedAttr, roleEncryptedPassAttr), 64 }, 65 roleEncryptedPassAttr: { 66 Type: schema.TypeBool, 67 Optional: true, 68 Default: true, 69 Description: "Control whether the password is stored encrypted in the system catalogs", 70 }, 71 roleValidUntilAttr: { 72 Type: schema.TypeString, 73 Optional: true, 74 Default: "infinity", 75 Description: "Sets a date and time after which the role's password is no longer valid", 76 }, 77 roleConnLimitAttr: { 78 Type: schema.TypeInt, 79 Optional: true, 80 Default: -1, 81 Description: "How many concurrent connections can be made with this role", 82 ValidateFunc: validateConnLimit, 83 }, 84 roleSuperuserAttr: { 85 Type: schema.TypeBool, 86 Optional: true, 87 Default: false, 88 Description: `Determine whether the new role is a "superuser"`, 89 }, 90 roleCreateDBAttr: { 91 Type: schema.TypeBool, 92 Optional: true, 93 Default: false, 94 Description: "Define a role's ability to create databases", 95 }, 96 roleCreateRoleAttr: { 97 Type: schema.TypeBool, 98 Optional: true, 99 Default: false, 100 Description: "Determine whether this role will be permitted to create new roles", 101 }, 102 roleInheritAttr: { 103 Type: schema.TypeBool, 104 Optional: true, 105 Default: true, 106 Description: `Determine whether a role "inherits" the privileges of roles it is a member of`, 107 }, 108 roleLoginAttr: { 109 Type: schema.TypeBool, 110 Optional: true, 111 Default: false, 112 Description: "Determine whether a role is allowed to log in", 113 }, 114 roleReplicationAttr: { 115 Type: schema.TypeBool, 116 Optional: true, 117 Default: false, 118 Description: "Determine whether a role is allowed to initiate streaming replication or put the system in and out of backup mode", 119 }, 120 roleBypassRLSAttr: { 121 Type: schema.TypeBool, 122 Optional: true, 123 Default: false, 124 Description: "Determine whether a role bypasses every row-level security (RLS) policy", 125 }, 126 roleSkipDropRoleAttr: { 127 Type: schema.TypeBool, 128 Optional: true, 129 Default: false, 130 Description: "Skip actually running the DROP ROLE command when removing a ROLE from PostgreSQL", 131 }, 132 roleSkipReassignOwnedAttr: { 133 Type: schema.TypeBool, 134 Optional: true, 135 Default: false, 136 Description: "Skip actually running the REASSIGN OWNED command when removing a role from PostgreSQL", 137 }, 138 }, 139 } 140 } 141 142 func resourcePostgreSQLRoleCreate(d *schema.ResourceData, meta interface{}) error { 143 c := meta.(*Client) 144 c.catalogLock.Lock() 145 defer c.catalogLock.Unlock() 146 147 conn, err := c.Connect() 148 if err != nil { 149 return errwrap.Wrapf("Error connecting to PostgreSQL: {{err}}", err) 150 } 151 defer conn.Close() 152 153 stringOpts := []struct { 154 hclKey string 155 sqlKey string 156 }{ 157 {rolePasswordAttr, "PASSWORD"}, 158 {roleValidUntilAttr, "VALID UNTIL"}, 159 } 160 intOpts := []struct { 161 hclKey string 162 sqlKey string 163 }{ 164 {roleConnLimitAttr, "CONNECTION LIMIT"}, 165 } 166 boolOpts := []struct { 167 hclKey string 168 sqlKeyEnable string 169 sqlKeyDisable string 170 }{ 171 {roleSuperuserAttr, "CREATEDB", "NOCREATEDB"}, 172 {roleCreateRoleAttr, "CREATEROLE", "NOCREATEROLE"}, 173 {roleInheritAttr, "INHERIT", "NOINHERIT"}, 174 {roleLoginAttr, "LOGIN", "NOLOGIN"}, 175 {roleReplicationAttr, "REPLICATION", "NOREPLICATION"}, 176 {roleBypassRLSAttr, "BYPASSRLS", "NOBYPASSRLS"}, 177 178 // roleEncryptedPassAttr is used only when rolePasswordAttr is set. 179 // {roleEncryptedPassAttr, "ENCRYPTED", "UNENCRYPTED"}, 180 } 181 182 createOpts := make([]string, 0, len(stringOpts)+len(intOpts)+len(boolOpts)) 183 184 for _, opt := range stringOpts { 185 v, ok := d.GetOk(opt.hclKey) 186 if !ok { 187 continue 188 } 189 190 val := v.(string) 191 if val != "" { 192 switch { 193 case opt.hclKey == rolePasswordAttr: 194 if strings.ToUpper(v.(string)) == "NULL" { 195 createOpts = append(createOpts, "PASSWORD NULL") 196 } else { 197 if d.Get(roleEncryptedPassAttr).(bool) { 198 createOpts = append(createOpts, "ENCRYPTED") 199 } else { 200 createOpts = append(createOpts, "UNENCRYPTED") 201 } 202 createOpts = append(createOpts, fmt.Sprintf("%s '%s'", opt.sqlKey, pqQuoteLiteral(val))) 203 } 204 case opt.hclKey == roleValidUntilAttr: 205 switch { 206 case v.(string) == "", strings.ToLower(v.(string)) == "infinity": 207 createOpts = append(createOpts, fmt.Sprintf("%s '%s'", opt.sqlKey, "infinity")) 208 default: 209 createOpts = append(createOpts, fmt.Sprintf("%s %s", opt.sqlKey, pq.QuoteIdentifier(val))) 210 } 211 default: 212 createOpts = append(createOpts, fmt.Sprintf("%s %s", opt.sqlKey, pq.QuoteIdentifier(val))) 213 } 214 } 215 } 216 217 for _, opt := range intOpts { 218 val := d.Get(opt.hclKey).(int) 219 createOpts = append(createOpts, fmt.Sprintf("%s %d", opt.sqlKey, val)) 220 } 221 222 for _, opt := range boolOpts { 223 if opt.hclKey == roleEncryptedPassAttr { 224 // This attribute is handled above in the stringOpts 225 // loop. 226 continue 227 } 228 val := d.Get(opt.hclKey).(bool) 229 valStr := opt.sqlKeyDisable 230 if val { 231 valStr = opt.sqlKeyEnable 232 } 233 createOpts = append(createOpts, valStr) 234 } 235 236 roleName := d.Get(roleNameAttr).(string) 237 createStr := strings.Join(createOpts, " ") 238 if len(createOpts) > 0 { 239 // FIXME(seanc@): Work around ParAccel/AWS RedShift's ancient fork of PostgreSQL 240 // createStr = " WITH " + createStr 241 createStr = " " + createStr 242 } 243 244 query := fmt.Sprintf("CREATE ROLE %s%s", pq.QuoteIdentifier(roleName), createStr) 245 _, err = conn.Query(query) 246 if err != nil { 247 return errwrap.Wrapf(fmt.Sprintf("Error creating role %s: {{err}}", roleName), err) 248 } 249 250 d.SetId(roleName) 251 252 return resourcePostgreSQLRoleReadImpl(d, meta) 253 } 254 255 func resourcePostgreSQLRoleDelete(d *schema.ResourceData, meta interface{}) error { 256 c := meta.(*Client) 257 c.catalogLock.Lock() 258 defer c.catalogLock.Unlock() 259 260 conn, err := c.Connect() 261 if err != nil { 262 return err 263 } 264 defer conn.Close() 265 266 txn, err := conn.Begin() 267 if err != nil { 268 return err 269 } 270 defer txn.Rollback() 271 272 roleName := d.Get(roleNameAttr).(string) 273 274 queries := make([]string, 0, 3) 275 if !d.Get(roleSkipReassignOwnedAttr).(bool) { 276 queries = append(queries, fmt.Sprintf("REASSIGN OWNED BY %s TO CURRENT_USER", pq.QuoteIdentifier(roleName))) 277 queries = append(queries, fmt.Sprintf("DROP OWNED BY %s", pq.QuoteIdentifier(roleName))) 278 } 279 280 if !d.Get(roleSkipDropRoleAttr).(bool) { 281 queries = append(queries, fmt.Sprintf("DROP ROLE %s", pq.QuoteIdentifier(roleName))) 282 } 283 284 if len(queries) > 0 { 285 for _, query := range queries { 286 _, err = conn.Query(query) 287 if err != nil { 288 return errwrap.Wrapf("Error deleting role: {{err}}", err) 289 } 290 } 291 292 if err := txn.Commit(); err != nil { 293 return errwrap.Wrapf("Error committing schema: {{err}}", err) 294 } 295 } 296 297 d.SetId("") 298 299 return nil 300 } 301 302 func resourcePostgreSQLRoleExists(d *schema.ResourceData, meta interface{}) (bool, error) { 303 c := meta.(*Client) 304 c.catalogLock.RLock() 305 defer c.catalogLock.RUnlock() 306 307 conn, err := c.Connect() 308 if err != nil { 309 return false, err 310 } 311 defer conn.Close() 312 313 var roleName string 314 err = conn.QueryRow("SELECT rolname FROM pg_catalog.pg_roles WHERE rolname=$1", d.Id()).Scan(&roleName) 315 switch { 316 case err == sql.ErrNoRows: 317 return false, nil 318 case err != nil: 319 return false, err 320 } 321 322 return true, nil 323 } 324 325 func resourcePostgreSQLRoleRead(d *schema.ResourceData, meta interface{}) error { 326 c := meta.(*Client) 327 c.catalogLock.RLock() 328 defer c.catalogLock.RUnlock() 329 330 return resourcePostgreSQLRoleReadImpl(d, meta) 331 } 332 333 func resourcePostgreSQLRoleReadImpl(d *schema.ResourceData, meta interface{}) error { 334 c := meta.(*Client) 335 conn, err := c.Connect() 336 if err != nil { 337 return err 338 } 339 defer conn.Close() 340 341 roleId := d.Id() 342 var roleSuperuser, roleInherit, roleCreateRole, roleCreateDB, roleCanLogin, roleReplication, roleBypassRLS bool 343 var roleConnLimit int 344 var roleName, roleValidUntil string 345 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) 346 switch { 347 case err == sql.ErrNoRows: 348 log.Printf("[WARN] PostgreSQL role (%s) not found", roleId) 349 d.SetId("") 350 return nil 351 case err != nil: 352 return errwrap.Wrapf("Error reading role: {{err}}", err) 353 default: 354 d.Set(roleNameAttr, roleName) 355 d.Set(roleBypassRLSAttr, roleBypassRLS) 356 d.Set(roleConnLimitAttr, roleConnLimit) 357 d.Set(roleCreateDBAttr, roleCreateDB) 358 d.Set(roleCreateRoleAttr, roleCreateRole) 359 d.Set(roleEncryptedPassAttr, true) 360 d.Set(roleInheritAttr, roleInherit) 361 d.Set(roleLoginAttr, roleCanLogin) 362 d.Set(roleReplicationAttr, roleReplication) 363 d.Set(roleSkipDropRoleAttr, d.Get(roleSkipDropRoleAttr).(bool)) 364 d.Set(roleSkipReassignOwnedAttr, d.Get(roleSkipReassignOwnedAttr).(bool)) 365 d.Set(roleSuperuserAttr, roleSuperuser) 366 d.Set(roleValidUntilAttr, roleValidUntil) 367 d.SetId(roleName) 368 } 369 370 if !roleSuperuser { 371 // Return early if not superuser user 372 return nil 373 } 374 375 var rolePassword string 376 err = conn.QueryRow("SELECT COALESCE(passwd, '') FROM pg_catalog.pg_shadow AS s WHERE s.usename = $1", roleId).Scan(&rolePassword) 377 switch { 378 case err == sql.ErrNoRows: 379 return errwrap.Wrapf(fmt.Sprintf("PostgreSQL role (%s) not found in shadow database: {{err}}", roleId), err) 380 case err != nil: 381 return errwrap.Wrapf("Error reading role: {{err}}", err) 382 default: 383 d.Set(rolePasswordAttr, rolePassword) 384 return nil 385 } 386 } 387 388 func resourcePostgreSQLRoleUpdate(d *schema.ResourceData, meta interface{}) error { 389 c := meta.(*Client) 390 c.catalogLock.Lock() 391 defer c.catalogLock.Unlock() 392 393 conn, err := c.Connect() 394 if err != nil { 395 return err 396 } 397 defer conn.Close() 398 399 if err := setRoleName(conn, d); err != nil { 400 return err 401 } 402 403 if err := setRoleBypassRLS(conn, d); err != nil { 404 return err 405 } 406 407 if err := setRoleConnLimit(conn, d); err != nil { 408 return err 409 } 410 411 if err := setRoleCreateDB(conn, d); err != nil { 412 return err 413 } 414 415 if err := setRoleCreateRole(conn, d); err != nil { 416 return err 417 } 418 419 if err := setRoleInherit(conn, d); err != nil { 420 return err 421 } 422 423 if err := setRoleLogin(conn, d); err != nil { 424 return err 425 } 426 427 if err := setRoleReplication(conn, d); err != nil { 428 return err 429 } 430 431 if err := setRoleSuperuser(conn, d); err != nil { 432 return err 433 } 434 435 if err := setRoleValidUntil(conn, d); err != nil { 436 return err 437 } 438 439 return resourcePostgreSQLRoleReadImpl(d, meta) 440 } 441 442 func setRoleName(conn *sql.DB, d *schema.ResourceData) error { 443 if !d.HasChange(roleNameAttr) { 444 return nil 445 } 446 447 oraw, nraw := d.GetChange(roleNameAttr) 448 o := oraw.(string) 449 n := nraw.(string) 450 if n == "" { 451 return errors.New("Error setting role name to an empty string") 452 } 453 454 query := fmt.Sprintf("ALTER ROLE %s RENAME TO %s", pq.QuoteIdentifier(o), pq.QuoteIdentifier(n)) 455 if _, err := conn.Query(query); err != nil { 456 return errwrap.Wrapf("Error updating role NAME: {{err}}", err) 457 } 458 d.SetId(n) 459 460 return nil 461 } 462 463 func setRoleBypassRLS(conn *sql.DB, d *schema.ResourceData) error { 464 if !d.HasChange(roleBypassRLSAttr) { 465 return nil 466 } 467 468 bypassRLS := d.Get(roleBypassRLSAttr).(bool) 469 tok := "NOBYPASSRLS" 470 if bypassRLS { 471 tok = "BYPASSRLS" 472 } 473 roleName := d.Get(roleNameAttr).(string) 474 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 475 if _, err := conn.Query(query); err != nil { 476 return errwrap.Wrapf("Error updating role BYPASSRLS: {{err}}", err) 477 } 478 479 return nil 480 } 481 482 func setRoleConnLimit(conn *sql.DB, d *schema.ResourceData) error { 483 if !d.HasChange(roleConnLimitAttr) { 484 return nil 485 } 486 487 connLimit := d.Get(roleConnLimitAttr).(int) 488 roleName := d.Get(roleNameAttr).(string) 489 query := fmt.Sprintf("ALTER ROLE %s CONNECTION LIMIT %d", pq.QuoteIdentifier(roleName), connLimit) 490 if _, err := conn.Query(query); err != nil { 491 return errwrap.Wrapf("Error updating role CONNECTION LIMIT: {{err}}", err) 492 } 493 494 return nil 495 } 496 497 func setRoleCreateDB(conn *sql.DB, d *schema.ResourceData) error { 498 if !d.HasChange(roleCreateDBAttr) { 499 return nil 500 } 501 502 createDB := d.Get(roleCreateDBAttr).(bool) 503 tok := "NOCREATEDB" 504 if createDB { 505 tok = "CREATEDB" 506 } 507 roleName := d.Get(roleNameAttr).(string) 508 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 509 if _, err := conn.Query(query); err != nil { 510 return errwrap.Wrapf("Error updating role CREATEDB: {{err}}", err) 511 } 512 513 return nil 514 } 515 516 func setRoleCreateRole(conn *sql.DB, d *schema.ResourceData) error { 517 if !d.HasChange(roleCreateRoleAttr) { 518 return nil 519 } 520 521 createRole := d.Get(roleCreateRoleAttr).(bool) 522 tok := "NOCREATEROLE" 523 if createRole { 524 tok = "CREATEROLE" 525 } 526 roleName := d.Get(roleNameAttr).(string) 527 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 528 if _, err := conn.Query(query); err != nil { 529 return errwrap.Wrapf("Error updating role CREATEROLE: {{err}}", err) 530 } 531 532 return nil 533 } 534 535 func setRoleInherit(conn *sql.DB, d *schema.ResourceData) error { 536 if !d.HasChange(roleInheritAttr) { 537 return nil 538 } 539 540 inherit := d.Get(roleInheritAttr).(bool) 541 tok := "NOINHERIT" 542 if inherit { 543 tok = "INHERIT" 544 } 545 roleName := d.Get(roleNameAttr).(string) 546 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 547 if _, err := conn.Query(query); err != nil { 548 return errwrap.Wrapf("Error updating role INHERIT: {{err}}", err) 549 } 550 551 return nil 552 } 553 554 func setRoleLogin(conn *sql.DB, d *schema.ResourceData) error { 555 if !d.HasChange(roleLoginAttr) { 556 return nil 557 } 558 559 login := d.Get(roleLoginAttr).(bool) 560 tok := "NOLOGIN" 561 if login { 562 tok = "LOGIN" 563 } 564 roleName := d.Get(roleNameAttr).(string) 565 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 566 if _, err := conn.Query(query); err != nil { 567 return errwrap.Wrapf("Error updating role LOGIN: {{err}}", err) 568 } 569 570 return nil 571 } 572 573 func setRoleReplication(conn *sql.DB, d *schema.ResourceData) error { 574 if !d.HasChange(roleReplicationAttr) { 575 return nil 576 } 577 578 replication := d.Get(roleReplicationAttr).(bool) 579 tok := "NOREPLICATION" 580 if replication { 581 tok = "REPLICATION" 582 } 583 roleName := d.Get(roleNameAttr).(string) 584 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 585 if _, err := conn.Query(query); err != nil { 586 return errwrap.Wrapf("Error updating role REPLICATION: {{err}}", err) 587 } 588 589 return nil 590 } 591 592 func setRoleSuperuser(conn *sql.DB, d *schema.ResourceData) error { 593 if !d.HasChange(roleSuperuserAttr) { 594 return nil 595 } 596 597 superuser := d.Get(roleSuperuserAttr).(bool) 598 tok := "NOSUPERUSER" 599 if superuser { 600 tok = "SUPERUSER" 601 } 602 roleName := d.Get(roleNameAttr).(string) 603 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 604 if _, err := conn.Query(query); err != nil { 605 return errwrap.Wrapf("Error updating role SUPERUSER: {{err}}", err) 606 } 607 608 return nil 609 } 610 611 func setRoleValidUntil(conn *sql.DB, d *schema.ResourceData) error { 612 if !d.HasChange(roleValidUntilAttr) { 613 return nil 614 } 615 616 validUntil := d.Get(roleValidUntilAttr).(string) 617 if validUntil == "" { 618 return nil 619 } else if strings.ToLower(validUntil) == "infinity" { 620 validUntil = "infinity" 621 } 622 623 roleName := d.Get(roleNameAttr).(string) 624 query := fmt.Sprintf("ALTER ROLE %s VALID UNTIL '%s'", pq.QuoteIdentifier(roleName), pqQuoteLiteral(validUntil)) 625 626 if _, err := conn.Query(query); err != nil { 627 return errwrap.Wrapf("Error updating role VALID UNTIL: {{err}}", err) 628 } 629 630 return nil 631 }