github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/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 createStr = " WITH " + createStr 240 } 241 242 query := fmt.Sprintf("CREATE ROLE %s%s", pq.QuoteIdentifier(roleName), createStr) 243 _, err = conn.Query(query) 244 if err != nil { 245 return errwrap.Wrapf(fmt.Sprintf("Error creating role %s: {{err}}", roleName), err) 246 } 247 248 d.SetId(roleName) 249 250 return resourcePostgreSQLRoleReadImpl(d, meta) 251 } 252 253 func resourcePostgreSQLRoleDelete(d *schema.ResourceData, meta interface{}) error { 254 c := meta.(*Client) 255 c.catalogLock.Lock() 256 defer c.catalogLock.Unlock() 257 258 conn, err := c.Connect() 259 if err != nil { 260 return err 261 } 262 defer conn.Close() 263 264 txn, err := conn.Begin() 265 if err != nil { 266 return err 267 } 268 defer txn.Rollback() 269 270 roleName := d.Get(roleNameAttr).(string) 271 272 queries := make([]string, 0, 3) 273 if !d.Get(roleSkipReassignOwnedAttr).(bool) { 274 queries = append(queries, fmt.Sprintf("REASSIGN OWNED BY %s TO CURRENT_USER", pq.QuoteIdentifier(roleName))) 275 queries = append(queries, fmt.Sprintf("DROP OWNED BY %s", pq.QuoteIdentifier(roleName))) 276 } 277 278 if !d.Get(roleSkipDropRoleAttr).(bool) { 279 queries = append(queries, fmt.Sprintf("DROP ROLE %s", pq.QuoteIdentifier(roleName))) 280 } 281 282 if len(queries) > 0 { 283 for _, query := range queries { 284 _, err = conn.Query(query) 285 if err != nil { 286 return errwrap.Wrapf("Error deleting role: {{err}}", err) 287 } 288 } 289 290 if err := txn.Commit(); err != nil { 291 return errwrap.Wrapf("Error committing schema: {{err}}", err) 292 } 293 } 294 295 d.SetId("") 296 297 return nil 298 } 299 300 func resourcePostgreSQLRoleExists(d *schema.ResourceData, meta interface{}) (bool, error) { 301 c := meta.(*Client) 302 c.catalogLock.RLock() 303 defer c.catalogLock.RUnlock() 304 305 conn, err := c.Connect() 306 if err != nil { 307 return false, err 308 } 309 defer conn.Close() 310 311 var roleName string 312 err = conn.QueryRow("SELECT rolname FROM pg_catalog.pg_roles WHERE rolname=$1", d.Id()).Scan(&roleName) 313 switch { 314 case err == sql.ErrNoRows: 315 return false, nil 316 case err != nil: 317 return false, err 318 } 319 320 return true, nil 321 } 322 323 func resourcePostgreSQLRoleRead(d *schema.ResourceData, meta interface{}) error { 324 c := meta.(*Client) 325 c.catalogLock.RLock() 326 defer c.catalogLock.RUnlock() 327 328 return resourcePostgreSQLRoleReadImpl(d, meta) 329 } 330 331 func resourcePostgreSQLRoleReadImpl(d *schema.ResourceData, meta interface{}) error { 332 c := meta.(*Client) 333 conn, err := c.Connect() 334 if err != nil { 335 return err 336 } 337 defer conn.Close() 338 339 roleId := d.Id() 340 var roleSuperuser, roleInherit, roleCreateRole, roleCreateDB, roleCanLogin, roleReplication, roleBypassRLS bool 341 var roleConnLimit int 342 var roleName, roleValidUntil string 343 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) 344 switch { 345 case err == sql.ErrNoRows: 346 log.Printf("[WARN] PostgreSQL role (%s) not found", roleId) 347 d.SetId("") 348 return nil 349 case err != nil: 350 return errwrap.Wrapf("Error reading role: {{err}}", err) 351 default: 352 d.Set(roleNameAttr, roleName) 353 d.Set(roleBypassRLSAttr, roleBypassRLS) 354 d.Set(roleConnLimitAttr, roleConnLimit) 355 d.Set(roleCreateDBAttr, roleCreateDB) 356 d.Set(roleCreateRoleAttr, roleCreateRole) 357 d.Set(roleEncryptedPassAttr, true) 358 d.Set(roleInheritAttr, roleInherit) 359 d.Set(roleLoginAttr, roleCanLogin) 360 d.Set(roleReplicationAttr, roleReplication) 361 d.Set(roleSkipDropRoleAttr, d.Get(roleSkipDropRoleAttr).(bool)) 362 d.Set(roleSkipReassignOwnedAttr, d.Get(roleSkipReassignOwnedAttr).(bool)) 363 d.Set(roleSuperuserAttr, roleSuperuser) 364 d.Set(roleValidUntilAttr, roleValidUntil) 365 d.SetId(roleName) 366 } 367 368 if !roleSuperuser { 369 // Return early if not superuser user 370 return nil 371 } 372 373 var rolePassword string 374 err = conn.QueryRow("SELECT COALESCE(passwd, '') FROM pg_catalog.pg_shadow AS s WHERE s.usename = $1", roleId).Scan(&rolePassword) 375 switch { 376 case err == sql.ErrNoRows: 377 return errwrap.Wrapf(fmt.Sprintf("PostgreSQL role (%s) not found in shadow database: {{err}}", roleId), err) 378 case err != nil: 379 return errwrap.Wrapf("Error reading role: {{err}}", err) 380 default: 381 d.Set(rolePasswordAttr, rolePassword) 382 return nil 383 } 384 } 385 386 func resourcePostgreSQLRoleUpdate(d *schema.ResourceData, meta interface{}) error { 387 c := meta.(*Client) 388 c.catalogLock.Lock() 389 defer c.catalogLock.Unlock() 390 391 conn, err := c.Connect() 392 if err != nil { 393 return err 394 } 395 defer conn.Close() 396 397 if err := setRoleName(conn, d); err != nil { 398 return err 399 } 400 401 if err := setRoleBypassRLS(conn, d); err != nil { 402 return err 403 } 404 405 if err := setRoleConnLimit(conn, d); err != nil { 406 return err 407 } 408 409 if err := setRoleCreateDB(conn, d); err != nil { 410 return err 411 } 412 413 if err := setRoleCreateRole(conn, d); err != nil { 414 return err 415 } 416 417 if err := setRoleInherit(conn, d); err != nil { 418 return err 419 } 420 421 if err := setRoleLogin(conn, d); err != nil { 422 return err 423 } 424 425 if err := setRoleReplication(conn, d); err != nil { 426 return err 427 } 428 429 if err := setRoleSuperuser(conn, d); err != nil { 430 return err 431 } 432 433 if err := setRoleValidUntil(conn, d); err != nil { 434 return err 435 } 436 437 return resourcePostgreSQLRoleReadImpl(d, meta) 438 } 439 440 func setRoleName(conn *sql.DB, d *schema.ResourceData) error { 441 if !d.HasChange(roleNameAttr) { 442 return nil 443 } 444 445 oraw, nraw := d.GetChange(roleNameAttr) 446 o := oraw.(string) 447 n := nraw.(string) 448 if n == "" { 449 return errors.New("Error setting role name to an empty string") 450 } 451 452 query := fmt.Sprintf("ALTER ROLE %s RENAME TO %s", pq.QuoteIdentifier(o), pq.QuoteIdentifier(n)) 453 if _, err := conn.Query(query); err != nil { 454 return errwrap.Wrapf("Error updating role NAME: {{err}}", err) 455 } 456 d.SetId(n) 457 458 return nil 459 } 460 461 func setRoleBypassRLS(conn *sql.DB, d *schema.ResourceData) error { 462 if !d.HasChange(roleBypassRLSAttr) { 463 return nil 464 } 465 466 bypassRLS := d.Get(roleBypassRLSAttr).(bool) 467 tok := "NOBYPASSRLS" 468 if bypassRLS { 469 tok = "BYPASSRLS" 470 } 471 roleName := d.Get(roleNameAttr).(string) 472 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 473 if _, err := conn.Query(query); err != nil { 474 return errwrap.Wrapf("Error updating role BYPASSRLS: {{err}}", err) 475 } 476 477 return nil 478 } 479 480 func setRoleConnLimit(conn *sql.DB, d *schema.ResourceData) error { 481 if !d.HasChange(roleConnLimitAttr) { 482 return nil 483 } 484 485 connLimit := d.Get(roleConnLimitAttr).(int) 486 roleName := d.Get(roleNameAttr).(string) 487 query := fmt.Sprintf("ALTER ROLE %s CONNECTION LIMIT %d", pq.QuoteIdentifier(roleName), connLimit) 488 if _, err := conn.Query(query); err != nil { 489 return errwrap.Wrapf("Error updating role CONNECTION LIMIT: {{err}}", err) 490 } 491 492 return nil 493 } 494 495 func setRoleCreateDB(conn *sql.DB, d *schema.ResourceData) error { 496 if !d.HasChange(roleCreateDBAttr) { 497 return nil 498 } 499 500 createDB := d.Get(roleCreateDBAttr).(bool) 501 tok := "NOCREATEDB" 502 if createDB { 503 tok = "CREATEDB" 504 } 505 roleName := d.Get(roleNameAttr).(string) 506 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 507 if _, err := conn.Query(query); err != nil { 508 return errwrap.Wrapf("Error updating role CREATEDB: {{err}}", err) 509 } 510 511 return nil 512 } 513 514 func setRoleCreateRole(conn *sql.DB, d *schema.ResourceData) error { 515 if !d.HasChange(roleCreateRoleAttr) { 516 return nil 517 } 518 519 createRole := d.Get(roleCreateRoleAttr).(bool) 520 tok := "NOCREATEROLE" 521 if createRole { 522 tok = "CREATEROLE" 523 } 524 roleName := d.Get(roleNameAttr).(string) 525 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 526 if _, err := conn.Query(query); err != nil { 527 return errwrap.Wrapf("Error updating role CREATEROLE: {{err}}", err) 528 } 529 530 return nil 531 } 532 533 func setRoleInherit(conn *sql.DB, d *schema.ResourceData) error { 534 if !d.HasChange(roleInheritAttr) { 535 return nil 536 } 537 538 inherit := d.Get(roleInheritAttr).(bool) 539 tok := "NOINHERIT" 540 if inherit { 541 tok = "INHERIT" 542 } 543 roleName := d.Get(roleNameAttr).(string) 544 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 545 if _, err := conn.Query(query); err != nil { 546 return errwrap.Wrapf("Error updating role INHERIT: {{err}}", err) 547 } 548 549 return nil 550 } 551 552 func setRoleLogin(conn *sql.DB, d *schema.ResourceData) error { 553 if !d.HasChange(roleLoginAttr) { 554 return nil 555 } 556 557 login := d.Get(roleLoginAttr).(bool) 558 tok := "NOLOGIN" 559 if login { 560 tok = "LOGIN" 561 } 562 roleName := d.Get(roleNameAttr).(string) 563 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 564 if _, err := conn.Query(query); err != nil { 565 return errwrap.Wrapf("Error updating role LOGIN: {{err}}", err) 566 } 567 568 return nil 569 } 570 571 func setRoleReplication(conn *sql.DB, d *schema.ResourceData) error { 572 if !d.HasChange(roleReplicationAttr) { 573 return nil 574 } 575 576 replication := d.Get(roleReplicationAttr).(bool) 577 tok := "NOREPLICATION" 578 if replication { 579 tok = "REPLICATION" 580 } 581 roleName := d.Get(roleNameAttr).(string) 582 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 583 if _, err := conn.Query(query); err != nil { 584 return errwrap.Wrapf("Error updating role REPLICATION: {{err}}", err) 585 } 586 587 return nil 588 } 589 590 func setRoleSuperuser(conn *sql.DB, d *schema.ResourceData) error { 591 if !d.HasChange(roleSuperuserAttr) { 592 return nil 593 } 594 595 superuser := d.Get(roleSuperuserAttr).(bool) 596 tok := "NOSUPERUSER" 597 if superuser { 598 tok = "SUPERUSER" 599 } 600 roleName := d.Get(roleNameAttr).(string) 601 query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok) 602 if _, err := conn.Query(query); err != nil { 603 return errwrap.Wrapf("Error updating role SUPERUSER: {{err}}", err) 604 } 605 606 return nil 607 } 608 609 func setRoleValidUntil(conn *sql.DB, d *schema.ResourceData) error { 610 if !d.HasChange(roleValidUntilAttr) { 611 return nil 612 } 613 614 validUntil := d.Get(roleValidUntilAttr).(string) 615 if validUntil == "" { 616 return nil 617 } else if strings.ToLower(validUntil) == "infinity" { 618 validUntil = "infinity" 619 } 620 621 roleName := d.Get(roleNameAttr).(string) 622 query := fmt.Sprintf("ALTER ROLE %s VALID UNTIL '%s'", pq.QuoteIdentifier(roleName), pqQuoteLiteral(validUntil)) 623 624 if _, err := conn.Query(query); err != nil { 625 return errwrap.Wrapf("Error updating role VALID UNTIL: {{err}}", err) 626 } 627 628 return nil 629 }