github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/postgresql/resource_postgresql_schema.go (about) 1 package postgresql 2 3 import ( 4 "bytes" 5 "database/sql" 6 "errors" 7 "fmt" 8 "log" 9 "reflect" 10 "strings" 11 12 "github.com/hashicorp/errwrap" 13 "github.com/hashicorp/terraform/helper/schema" 14 "github.com/lib/pq" 15 "github.com/sean-/postgresql-acl" 16 ) 17 18 const ( 19 schemaNameAttr = "name" 20 schemaOwnerAttr = "owner" 21 schemaPolicyAttr = "policy" 22 schemaIfNotExists = "if_not_exists" 23 24 schemaPolicyCreateAttr = "create" 25 schemaPolicyCreateWithGrantAttr = "create_with_grant" 26 schemaPolicyRoleAttr = "role" 27 schemaPolicyUsageAttr = "usage" 28 schemaPolicyUsageWithGrantAttr = "usage_with_grant" 29 ) 30 31 func resourcePostgreSQLSchema() *schema.Resource { 32 return &schema.Resource{ 33 Create: resourcePostgreSQLSchemaCreate, 34 Read: resourcePostgreSQLSchemaRead, 35 Update: resourcePostgreSQLSchemaUpdate, 36 Delete: resourcePostgreSQLSchemaDelete, 37 Exists: resourcePostgreSQLSchemaExists, 38 Importer: &schema.ResourceImporter{ 39 State: schema.ImportStatePassthrough, 40 }, 41 42 Schema: map[string]*schema.Schema{ 43 schemaNameAttr: { 44 Type: schema.TypeString, 45 Required: true, 46 Description: "The name of the schema", 47 }, 48 schemaOwnerAttr: { 49 Type: schema.TypeString, 50 Optional: true, 51 Computed: true, 52 Description: "The ROLE name who owns the schema", 53 }, 54 schemaIfNotExists: { 55 Type: schema.TypeBool, 56 Optional: true, 57 Default: true, 58 Description: "When true, use the existing schema if it exsts", 59 }, 60 schemaPolicyAttr: &schema.Schema{ 61 Type: schema.TypeSet, 62 Optional: true, 63 Computed: true, 64 Elem: &schema.Resource{ 65 Schema: map[string]*schema.Schema{ 66 schemaPolicyCreateAttr: { 67 Type: schema.TypeBool, 68 Optional: true, 69 Default: false, 70 Description: "If true, allow the specified ROLEs to CREATE new objects within the schema(s)", 71 ConflictsWith: []string{schemaPolicyAttr + "." + schemaPolicyCreateWithGrantAttr}, 72 }, 73 schemaPolicyCreateWithGrantAttr: { 74 Type: schema.TypeBool, 75 Optional: true, 76 Default: false, 77 Description: "If true, allow the specified ROLEs to CREATE new objects within the schema(s) and GRANT the same CREATE privilege to different ROLEs", 78 ConflictsWith: []string{schemaPolicyAttr + "." + schemaPolicyCreateAttr}, 79 }, 80 schemaPolicyRoleAttr: { 81 Type: schema.TypeString, 82 Elem: &schema.Schema{Type: schema.TypeString}, 83 Optional: true, 84 Default: "", 85 Description: "ROLE who will receive this policy (default: PUBLIC)", 86 }, 87 schemaPolicyUsageAttr: { 88 Type: schema.TypeBool, 89 Optional: true, 90 Default: false, 91 Description: "If true, allow the specified ROLEs to use objects within the schema(s)", 92 ConflictsWith: []string{schemaPolicyAttr + "." + schemaPolicyUsageWithGrantAttr}, 93 }, 94 schemaPolicyUsageWithGrantAttr: { 95 Type: schema.TypeBool, 96 Optional: true, 97 Default: false, 98 Description: "If true, allow the specified ROLEs to use objects within the schema(s) and GRANT the same USAGE privilege to different ROLEs", 99 ConflictsWith: []string{schemaPolicyAttr + "." + schemaPolicyUsageAttr}, 100 }, 101 }, 102 }, 103 }, 104 }, 105 } 106 } 107 108 func resourcePostgreSQLSchemaCreate(d *schema.ResourceData, meta interface{}) error { 109 c := meta.(*Client) 110 111 queries := []string{} 112 113 schemaName := d.Get(schemaNameAttr).(string) 114 { 115 b := bytes.NewBufferString("CREATE SCHEMA ") 116 if v := d.Get(schemaIfNotExists); v.(bool) { 117 fmt.Fprint(b, "IF NOT EXISTS ") 118 } 119 fmt.Fprint(b, pq.QuoteIdentifier(schemaName)) 120 121 switch v, ok := d.GetOk(schemaOwnerAttr); { 122 case ok: 123 fmt.Fprint(b, " AUTHORIZATION ", pq.QuoteIdentifier(v.(string))) 124 } 125 queries = append(queries, b.String()) 126 } 127 128 // ACL objects that can generate the necessary SQL 129 type RoleKey string 130 var schemaPolicies map[RoleKey]acl.Schema 131 132 if policiesRaw, ok := d.GetOk(schemaPolicyAttr); ok { 133 policiesList := policiesRaw.(*schema.Set).List() 134 135 // NOTE: len(policiesList) doesn't take into account multiple 136 // roles per policy. 137 schemaPolicies = make(map[RoleKey]acl.Schema, len(policiesList)) 138 139 for _, policyRaw := range policiesList { 140 policyMap := policyRaw.(map[string]interface{}) 141 rolePolicy := schemaPolicyToACL(policyMap) 142 143 roleKey := RoleKey(strings.ToLower(rolePolicy.Role)) 144 if existingRolePolicy, ok := schemaPolicies[roleKey]; ok { 145 schemaPolicies[roleKey] = existingRolePolicy.Merge(rolePolicy) 146 } else { 147 schemaPolicies[roleKey] = rolePolicy 148 } 149 } 150 } 151 152 for _, policy := range schemaPolicies { 153 queries = append(queries, policy.Grants(schemaName)...) 154 } 155 156 c.catalogLock.Lock() 157 defer c.catalogLock.Unlock() 158 159 conn, err := c.Connect() 160 if err != nil { 161 return errwrap.Wrapf("Error connecting to PostgreSQL: {{err}}", err) 162 } 163 defer conn.Close() 164 165 txn, err := conn.Begin() 166 if err != nil { 167 return err 168 } 169 defer txn.Rollback() 170 171 for _, query := range queries { 172 _, err = txn.Query(query) 173 if err != nil { 174 return errwrap.Wrapf(fmt.Sprintf("Error creating schema %s: {{err}}", schemaName), err) 175 } 176 } 177 178 if err := txn.Commit(); err != nil { 179 return errwrap.Wrapf("Error committing schema: {{err}}", err) 180 } 181 182 d.SetId(schemaName) 183 184 return resourcePostgreSQLSchemaReadImpl(d, meta) 185 } 186 187 func resourcePostgreSQLSchemaDelete(d *schema.ResourceData, meta interface{}) error { 188 c := meta.(*Client) 189 c.catalogLock.Lock() 190 defer c.catalogLock.Unlock() 191 192 conn, err := c.Connect() 193 if err != nil { 194 return err 195 } 196 defer conn.Close() 197 198 txn, err := conn.Begin() 199 if err != nil { 200 return err 201 } 202 defer txn.Rollback() 203 204 schemaName := d.Get(schemaNameAttr).(string) 205 206 // NOTE(sean@): Deliberately not performing a cascading drop. 207 query := fmt.Sprintf("DROP SCHEMA %s", pq.QuoteIdentifier(schemaName)) 208 _, err = txn.Query(query) 209 if err != nil { 210 return errwrap.Wrapf("Error deleting schema: {{err}}", err) 211 } 212 213 if err := txn.Commit(); err != nil { 214 return errwrap.Wrapf("Error committing schema: {{err}}", err) 215 } 216 217 d.SetId("") 218 219 return nil 220 } 221 222 func resourcePostgreSQLSchemaExists(d *schema.ResourceData, meta interface{}) (bool, error) { 223 c := meta.(*Client) 224 c.catalogLock.RLock() 225 defer c.catalogLock.RUnlock() 226 227 conn, err := c.Connect() 228 if err != nil { 229 return false, err 230 } 231 defer conn.Close() 232 233 var schemaName string 234 err = conn.QueryRow("SELECT n.nspname FROM pg_catalog.pg_namespace n WHERE n.nspname=$1", d.Id()).Scan(&schemaName) 235 switch { 236 case err == sql.ErrNoRows: 237 return false, nil 238 case err != nil: 239 return false, errwrap.Wrapf("Error reading schema: {{err}}", err) 240 } 241 242 return true, nil 243 } 244 245 func resourcePostgreSQLSchemaRead(d *schema.ResourceData, meta interface{}) error { 246 c := meta.(*Client) 247 c.catalogLock.RLock() 248 defer c.catalogLock.RUnlock() 249 250 return resourcePostgreSQLSchemaReadImpl(d, meta) 251 } 252 253 func resourcePostgreSQLSchemaReadImpl(d *schema.ResourceData, meta interface{}) error { 254 c := meta.(*Client) 255 conn, err := c.Connect() 256 if err != nil { 257 return err 258 } 259 defer conn.Close() 260 261 schemaId := d.Id() 262 var schemaName, schemaOwner string 263 var schemaACLs []string 264 err = conn.QueryRow("SELECT n.nspname, pg_catalog.pg_get_userbyid(n.nspowner), COALESCE(n.nspacl, '{}'::aclitem[])::TEXT[] FROM pg_catalog.pg_namespace n WHERE n.nspname=$1", schemaId).Scan(&schemaName, &schemaOwner, pq.Array(&schemaACLs)) 265 switch { 266 case err == sql.ErrNoRows: 267 log.Printf("[WARN] PostgreSQL schema (%s) not found", schemaId) 268 d.SetId("") 269 return nil 270 case err != nil: 271 return errwrap.Wrapf("Error reading schema: {{err}}", err) 272 default: 273 type RoleKey string 274 schemaPolicies := make(map[RoleKey]acl.Schema, len(schemaACLs)) 275 for _, aclStr := range schemaACLs { 276 aclItem, err := acl.Parse(aclStr) 277 if err != nil { 278 return errwrap.Wrapf("Error parsing aclitem: {{err}}", err) 279 } 280 281 schemaACL, err := acl.NewSchema(aclItem) 282 if err != nil { 283 return errwrap.Wrapf("invalid perms for schema: {{err}}", err) 284 } 285 286 roleKey := RoleKey(strings.ToLower(schemaACL.Role)) 287 var mergedPolicy acl.Schema 288 if existingRolePolicy, ok := schemaPolicies[roleKey]; ok { 289 mergedPolicy = existingRolePolicy.Merge(schemaACL) 290 } else { 291 mergedPolicy = schemaACL 292 } 293 schemaPolicies[roleKey] = mergedPolicy 294 } 295 296 d.Set(schemaNameAttr, schemaName) 297 d.Set(schemaOwnerAttr, schemaOwner) 298 d.SetId(schemaName) 299 return nil 300 } 301 } 302 303 func resourcePostgreSQLSchemaUpdate(d *schema.ResourceData, meta interface{}) error { 304 c := meta.(*Client) 305 c.catalogLock.Lock() 306 defer c.catalogLock.Unlock() 307 308 conn, err := c.Connect() 309 if err != nil { 310 return err 311 } 312 defer conn.Close() 313 314 txn, err := conn.Begin() 315 if err != nil { 316 return err 317 } 318 defer txn.Rollback() 319 320 if err := setSchemaName(txn, d); err != nil { 321 return err 322 } 323 324 if err := setSchemaOwner(txn, d); err != nil { 325 return err 326 } 327 328 if err := setSchemaPolicy(txn, d); err != nil { 329 return err 330 } 331 332 if err := txn.Commit(); err != nil { 333 return errwrap.Wrapf("Error committing schema: {{err}}", err) 334 } 335 336 return resourcePostgreSQLSchemaReadImpl(d, meta) 337 } 338 339 func setSchemaName(txn *sql.Tx, d *schema.ResourceData) error { 340 if !d.HasChange(schemaNameAttr) { 341 return nil 342 } 343 344 oraw, nraw := d.GetChange(schemaNameAttr) 345 o := oraw.(string) 346 n := nraw.(string) 347 if n == "" { 348 return errors.New("Error setting schema name to an empty string") 349 } 350 351 query := fmt.Sprintf("ALTER SCHEMA %s RENAME TO %s", pq.QuoteIdentifier(o), pq.QuoteIdentifier(n)) 352 if _, err := txn.Query(query); err != nil { 353 return errwrap.Wrapf("Error updating schema NAME: {{err}}", err) 354 } 355 d.SetId(n) 356 357 return nil 358 } 359 360 func setSchemaOwner(txn *sql.Tx, d *schema.ResourceData) error { 361 if !d.HasChange(schemaOwnerAttr) { 362 return nil 363 } 364 365 oraw, nraw := d.GetChange(schemaOwnerAttr) 366 o := oraw.(string) 367 n := nraw.(string) 368 if n == "" { 369 return errors.New("Error setting schema owner to an empty string") 370 } 371 372 query := fmt.Sprintf("ALTER SCHEMA %s OWNER TO %s", pq.QuoteIdentifier(o), pq.QuoteIdentifier(n)) 373 if _, err := txn.Query(query); err != nil { 374 return errwrap.Wrapf("Error updating schema OWNER: {{err}}", err) 375 } 376 377 return nil 378 } 379 380 func setSchemaPolicy(txn *sql.Tx, d *schema.ResourceData) error { 381 if !d.HasChange(schemaPolicyAttr) { 382 return nil 383 } 384 385 schemaName := d.Get(schemaNameAttr).(string) 386 387 oraw, nraw := d.GetChange(schemaPolicyAttr) 388 oldList := oraw.(*schema.Set).List() 389 newList := nraw.(*schema.Set).List() 390 queries := make([]string, 0, len(oldList)+len(newList)) 391 dropped, added, updated, _ := schemaChangedPolicies(oldList, newList) 392 393 for _, p := range dropped { 394 pMap := p.(map[string]interface{}) 395 rolePolicy := schemaPolicyToACL(pMap) 396 397 // The PUBLIC role can not be DROP'ed, therefore we do not need 398 // to prevent revoking against it not existing. 399 if rolePolicy.Role != "" { 400 var foundUser bool 401 err := txn.QueryRow(`SELECT TRUE FROM pg_catalog.pg_user WHERE usename = $1`, rolePolicy.Role).Scan(&foundUser) 402 switch { 403 case err == sql.ErrNoRows: 404 // Don't execute this role's REVOKEs because the role 405 // was dropped first and therefore doesn't exist. 406 case err != nil: 407 return errwrap.Wrapf("Error reading schema: {{err}}", err) 408 default: 409 queries = append(queries, rolePolicy.Revokes(schemaName)...) 410 } 411 } 412 } 413 414 for _, p := range added { 415 pMap := p.(map[string]interface{}) 416 rolePolicy := schemaPolicyToACL(pMap) 417 queries = append(queries, rolePolicy.Grants(schemaName)...) 418 } 419 420 for _, p := range updated { 421 policies := p.([]interface{}) 422 if len(policies) != 2 { 423 panic("expected 2 policies, old and new") 424 } 425 426 { 427 oldPolicies := policies[0].(map[string]interface{}) 428 rolePolicy := schemaPolicyToACL(oldPolicies) 429 queries = append(queries, rolePolicy.Revokes(schemaName)...) 430 } 431 432 { 433 newPolicies := policies[1].(map[string]interface{}) 434 rolePolicy := schemaPolicyToACL(newPolicies) 435 queries = append(queries, rolePolicy.Grants(schemaName)...) 436 } 437 } 438 439 for _, query := range queries { 440 if _, err := txn.Query(query); err != nil { 441 return errwrap.Wrapf("Error updating schema DCL: {{err}}", err) 442 } 443 } 444 445 return nil 446 } 447 448 // schemaChangedPolicies walks old and new to create a set of queries that can 449 // be executed to enact each type of state change (roles that have been dropped 450 // from the policy, added to a policy, have updated privilges, or are 451 // unchanged). 452 func schemaChangedPolicies(old, new []interface{}) (dropped, added, update, unchanged map[string]interface{}) { 453 type RoleKey string 454 oldLookupMap := make(map[RoleKey]interface{}, len(old)) 455 for idx, _ := range old { 456 v := old[idx] 457 schemaPolicy := v.(map[string]interface{}) 458 if roleRaw, ok := schemaPolicy[schemaPolicyRoleAttr]; ok { 459 role := roleRaw.(string) 460 roleKey := strings.ToLower(role) 461 oldLookupMap[RoleKey(roleKey)] = schemaPolicy 462 } 463 } 464 465 newLookupMap := make(map[RoleKey]interface{}, len(new)) 466 for idx, _ := range new { 467 v := new[idx] 468 schemaPolicy := v.(map[string]interface{}) 469 if roleRaw, ok := schemaPolicy[schemaPolicyRoleAttr]; ok { 470 role := roleRaw.(string) 471 roleKey := strings.ToLower(role) 472 newLookupMap[RoleKey(roleKey)] = schemaPolicy 473 } 474 } 475 476 droppedRoles := make(map[string]interface{}, len(old)) 477 for kOld, vOld := range oldLookupMap { 478 if _, ok := newLookupMap[kOld]; !ok { 479 droppedRoles[string(kOld)] = vOld 480 } 481 } 482 483 addedRoles := make(map[string]interface{}, len(new)) 484 for kNew, vNew := range newLookupMap { 485 if _, ok := oldLookupMap[kNew]; !ok { 486 addedRoles[string(kNew)] = vNew 487 } 488 } 489 490 updatedRoles := make(map[string]interface{}, len(new)) 491 unchangedRoles := make(map[string]interface{}, len(new)) 492 for kOld, vOld := range oldLookupMap { 493 if vNew, ok := newLookupMap[kOld]; ok { 494 if reflect.DeepEqual(vOld, vNew) { 495 unchangedRoles[string(kOld)] = vOld 496 } else { 497 updatedRoles[string(kOld)] = []interface{}{vOld, vNew} 498 } 499 } 500 } 501 502 return droppedRoles, addedRoles, updatedRoles, unchangedRoles 503 } 504 505 func schemaPolicyToHCL(s *acl.Schema) map[string]interface{} { 506 return map[string]interface{}{ 507 schemaPolicyRoleAttr: s.Role, 508 schemaPolicyCreateAttr: s.GetPrivilege(acl.Create), 509 schemaPolicyCreateWithGrantAttr: s.GetGrantOption(acl.Create), 510 schemaPolicyUsageAttr: s.GetPrivilege(acl.Usage), 511 schemaPolicyUsageWithGrantAttr: s.GetGrantOption(acl.Usage), 512 } 513 } 514 515 func schemaPolicyToACL(policyMap map[string]interface{}) acl.Schema { 516 var rolePolicy acl.Schema 517 518 if policyMap[schemaPolicyCreateAttr].(bool) { 519 rolePolicy.Privileges |= acl.Create 520 } 521 522 if policyMap[schemaPolicyCreateWithGrantAttr].(bool) { 523 rolePolicy.Privileges |= acl.Create 524 rolePolicy.GrantOptions |= acl.Create 525 } 526 527 if policyMap[schemaPolicyUsageAttr].(bool) { 528 rolePolicy.Privileges |= acl.Usage 529 } 530 531 if policyMap[schemaPolicyUsageWithGrantAttr].(bool) { 532 rolePolicy.Privileges |= acl.Usage 533 rolePolicy.GrantOptions |= acl.Usage 534 } 535 536 if roleRaw, ok := policyMap[schemaPolicyRoleAttr]; ok { 537 rolePolicy.Role = roleRaw.(string) 538 } 539 540 return rolePolicy 541 }