Add the postgresql_schema_policy resource. This is a WIP due to
issues with cycles when an object is destroyed.
This commit is contained in:
parent
de6dcbd8cd
commit
ebc81727da
|
@ -64,10 +64,11 @@ func Provider() terraform.ResourceProvider {
|
|||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"postgresql_database": resourcePostgreSQLDatabase(),
|
||||
"postgresql_extension": resourcePostgreSQLExtension(),
|
||||
"postgresql_schema": resourcePostgreSQLSchema(),
|
||||
"postgresql_role": resourcePostgreSQLRole(),
|
||||
"postgresql_database": resourcePostgreSQLDatabase(),
|
||||
"postgresql_extension": resourcePostgreSQLExtension(),
|
||||
"postgresql_schema": resourcePostgreSQLSchema(),
|
||||
"postgresql_schema_policy": resourcePostgreSQLSchemaPolicy(),
|
||||
"postgresql_role": resourcePostgreSQLRole(),
|
||||
},
|
||||
|
||||
ConfigureFunc: providerConfigure,
|
||||
|
|
|
@ -0,0 +1,442 @@
|
|||
package postgresql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/lib/pq"
|
||||
"github.com/sean-/pgacl"
|
||||
)
|
||||
|
||||
const (
|
||||
schemaPolicyCreateAttr = "create"
|
||||
schemaPolicyCreateWithGrantAttr = "create_with_grant"
|
||||
schemaPolicyRoleAttr = "role"
|
||||
schemaPolicySchemaAttr = "schema"
|
||||
schemaPolicyUsageAttr = "usage"
|
||||
schemaPolicyUsageWithGrantAttr = "usage_with_grant"
|
||||
// schemaPolicyRolesAttr = "roles"
|
||||
// schemaPolicySchemasAttr = "schemas"
|
||||
)
|
||||
|
||||
func resourcePostgreSQLSchemaPolicy() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourcePostgreSQLSchemaPolicyCreate,
|
||||
Read: resourcePostgreSQLSchemaPolicyRead,
|
||||
Update: resourcePostgreSQLSchemaPolicyUpdate,
|
||||
Delete: resourcePostgreSQLSchemaPolicyDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
State: schema.ImportStatePassthrough,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
schemaPolicyCreateAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: "If true, allow the specified ROLEs to CREATE new objects within the schema(s)",
|
||||
ConflictsWith: []string{schemaPolicyCreateWithGrantAttr},
|
||||
},
|
||||
schemaPolicyCreateWithGrantAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: "If true, allow the specified ROLEs to CREATE new objects within the schema(s) and GRANT the same CREATE privilege to different ROLEs",
|
||||
ConflictsWith: []string{schemaPolicyCreateAttr},
|
||||
},
|
||||
// schemaPolicyRolesAttr: {
|
||||
// Type: schema.TypeList,
|
||||
// Elem: &schema.Schema{Type: schema.TypeString},
|
||||
// Description: "List of ROLEs who will receive this policy",
|
||||
// },
|
||||
schemaPolicyRoleAttr: {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "ROLE who will receive this policy",
|
||||
},
|
||||
schemaPolicySchemaAttr: {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Target SCHEMA who will have this policy applied to it",
|
||||
},
|
||||
schemaPolicyUsageAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: "If true, allow the specified ROLEs to use objects within the schema(s)",
|
||||
ConflictsWith: []string{schemaPolicyUsageWithGrantAttr},
|
||||
},
|
||||
schemaPolicyUsageWithGrantAttr: {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: "If true, allow the specified ROLEs to use objects within the schema(s) and GRANT the same USAGE privilege to different ROLEs",
|
||||
ConflictsWith: []string{schemaPolicyUsageAttr},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourcePostgreSQLSchemaPolicyCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
id, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Unable to generate schema policy ID: {{err}}", err)
|
||||
}
|
||||
d.SetId(id)
|
||||
|
||||
acl := pgacl.Schema{}
|
||||
role := d.Get(schemaPolicyRoleAttr).(string)
|
||||
if strings.ToUpper(role) != "PUBLIC" {
|
||||
role = pq.QuoteIdentifier(role)
|
||||
}
|
||||
|
||||
acl.CreateGrant = d.Get(schemaPolicyCreateWithGrantAttr).(bool)
|
||||
if acl.CreateGrant {
|
||||
acl.Create = true
|
||||
} else {
|
||||
acl.Create = d.Get(schemaPolicyCreateAttr).(bool)
|
||||
}
|
||||
|
||||
acl.UsageGrant = d.Get(schemaPolicyUsageWithGrantAttr).(bool)
|
||||
if acl.UsageGrant {
|
||||
acl.Usage = true
|
||||
} else {
|
||||
acl.Usage = d.Get(schemaPolicyUsageAttr).(bool)
|
||||
}
|
||||
schema := d.Get(schemaPolicySchemaAttr).(string)
|
||||
|
||||
queries := make([]string, 0, 2)
|
||||
if acl.Create {
|
||||
b := bytes.NewBufferString("GRANT CREATE ON SCHEMA ")
|
||||
fmt.Fprintf(b, "%s TO %s", pq.QuoteIdentifier(schema), role)
|
||||
|
||||
if acl.CreateGrant {
|
||||
fmt.Fprint(b, " WITH GRANT OPTION")
|
||||
}
|
||||
queries = append(queries, b.String())
|
||||
}
|
||||
|
||||
if acl.Usage {
|
||||
b := bytes.NewBufferString("GRANT USAGE ON SCHEMA ")
|
||||
fmt.Fprintf(b, "%s TO %s", pq.QuoteIdentifier(schema), role)
|
||||
if acl.UsageGrant {
|
||||
fmt.Fprint(b, " WITH GRANT OPTION")
|
||||
}
|
||||
queries = append(queries, b.String())
|
||||
}
|
||||
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
txn, err := conn.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
for _, query := range queries {
|
||||
_, err = txn.Query(query)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(fmt.Sprintf("Error applying policy on schema (%+q): {{err}}", query), err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := txn.Commit(); err != nil {
|
||||
return errwrap.Wrapf("Error committing schema policy: {{err}}", err)
|
||||
}
|
||||
|
||||
return resourcePostgreSQLSchemaPolicyRead(d, meta)
|
||||
}
|
||||
|
||||
func resourcePostgreSQLSchemaPolicyDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
role := d.Get(schemaPolicyRoleAttr).(string)
|
||||
schema := d.Get(schemaPolicySchemaAttr).(string)
|
||||
|
||||
acl := pgacl.Schema{}
|
||||
if strings.ToUpper(role) != "PUBLIC" {
|
||||
role = pq.QuoteIdentifier(role)
|
||||
}
|
||||
|
||||
acl.CreateGrant = d.Get(schemaPolicyCreateWithGrantAttr).(bool)
|
||||
if acl.CreateGrant {
|
||||
acl.Create = true
|
||||
} else {
|
||||
acl.Create = d.Get(schemaPolicyCreateAttr).(bool)
|
||||
}
|
||||
|
||||
acl.UsageGrant = d.Get(schemaPolicyUsageWithGrantAttr).(bool)
|
||||
if acl.UsageGrant {
|
||||
acl.Usage = true
|
||||
} else {
|
||||
acl.Usage = d.Get(schemaPolicyUsageAttr).(bool)
|
||||
}
|
||||
|
||||
queries := make([]string, 0, 2)
|
||||
switch {
|
||||
case !acl.Create:
|
||||
b := bytes.NewBufferString("REVOKE CREATE ON SCHEMA ")
|
||||
fmt.Fprintf(b, "%s FROM %s", pq.QuoteIdentifier(schema), role)
|
||||
queries = append(queries, b.String())
|
||||
case acl.Create && !acl.CreateGrant:
|
||||
b := bytes.NewBufferString("REVOKE GRANT OPTION FOR CREATE ON SCHEMA ")
|
||||
fmt.Fprintf(b, "%s FROM %s", pq.QuoteIdentifier(schema), role)
|
||||
queries = append(queries, b.String())
|
||||
}
|
||||
|
||||
switch {
|
||||
case !acl.Usage:
|
||||
b := bytes.NewBufferString("REVOKE USAGE ON SCHEMA ")
|
||||
fmt.Fprintf(b, "%s FROM %s", pq.QuoteIdentifier(schema), role)
|
||||
queries = append(queries, b.String())
|
||||
case acl.Usage && !acl.UsageGrant:
|
||||
b := bytes.NewBufferString("REVOKE GRANT OPTION FOR USAGE ON SCHEMA ")
|
||||
fmt.Fprintf(b, "%s FROM %s", pq.QuoteIdentifier(schema), role)
|
||||
queries = append(queries, b.String())
|
||||
}
|
||||
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
txn, err := conn.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
for _, query := range queries {
|
||||
_, err = txn.Query(query)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(fmt.Sprintf("Error removing policy on schema (%+q): {{err}}", query), err)
|
||||
}
|
||||
}
|
||||
txn.Commit()
|
||||
|
||||
d.SetId("")
|
||||
|
||||
return resourcePostgreSQLSchemaPolicyRead(d, meta)
|
||||
}
|
||||
|
||||
func resourcePostgreSQLSchemaPolicyRead(d *schema.ResourceData, meta interface{}) error {
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
oraw, _ := d.GetChange(schemaPolicySchemaAttr)
|
||||
o := oraw.(string)
|
||||
|
||||
var schemaName, schemaOwner, schemaACL string
|
||||
err = conn.QueryRow("SELECT n.nspname, pg_catalog.pg_get_userbyid(n.nspowner), n.nspacl FROM pg_catalog.pg_namespace n WHERE n.nspname = $1", o).Scan(&schemaName, &schemaOwner, &schemaACL)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
log.Printf("[WARN] PostgreSQL schema (%s) not found", o)
|
||||
d.SetId("")
|
||||
return nil
|
||||
case err != nil:
|
||||
return errwrap.Wrapf("Error reading schema ACLs: {{err}}", err)
|
||||
default:
|
||||
acl, err := pgacl.NewSchema(schemaACL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case acl.CreateGrant:
|
||||
d.Set(schemaPolicyCreateWithGrantAttr, true)
|
||||
d.Set(schemaPolicyCreateAttr, false)
|
||||
case acl.Create:
|
||||
d.Set(schemaPolicyCreateAttr, true)
|
||||
d.Set(schemaPolicyCreateWithGrantAttr, false)
|
||||
default:
|
||||
d.Set(schemaPolicyCreateWithGrantAttr, false)
|
||||
d.Set(schemaPolicyCreateAttr, false)
|
||||
}
|
||||
|
||||
switch {
|
||||
case acl.UsageGrant:
|
||||
d.Set(schemaPolicyUsageWithGrantAttr, true)
|
||||
d.Set(schemaPolicyUsageAttr, false)
|
||||
case acl.Usage:
|
||||
d.Set(schemaPolicyUsageAttr, true)
|
||||
d.Set(schemaPolicyUsageWithGrantAttr, false)
|
||||
default:
|
||||
d.Set(schemaPolicyUsageWithGrantAttr, false)
|
||||
d.Set(schemaPolicyUsageAttr, false)
|
||||
}
|
||||
d.Set(schemaPolicySchemaAttr, acl.Role)
|
||||
d.Set(schemaPolicySchemaAttr, schemaName)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func resourcePostgreSQLSchemaPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
c := meta.(*Client)
|
||||
conn, err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
txn, err := conn.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
if err := setSchemaPolicyCreate(txn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setSchemaPolicyUsage(txn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txn.Commit()
|
||||
|
||||
return resourcePostgreSQLSchemaRead(d, meta)
|
||||
}
|
||||
|
||||
func setSchemaPolicyCreate(txn *sql.Tx, d *schema.ResourceData) error {
|
||||
if !d.HasChange(schemaPolicyCreateAttr) && !d.HasChange(schemaPolicyCreateWithGrantAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
oldCreateRaw, newCreateRaw := d.GetChange(schemaPolicyCreateAttr)
|
||||
oldCreate := oldCreateRaw.(bool)
|
||||
newCreate := newCreateRaw.(bool)
|
||||
|
||||
oldGrantRaw, newGrantRaw := d.GetChange(schemaPolicyCreateWithGrantAttr)
|
||||
oldGrant := oldGrantRaw.(bool)
|
||||
newGrant := newGrantRaw.(bool)
|
||||
|
||||
var grant, revoke, withGrant bool
|
||||
switch {
|
||||
case oldCreate == newCreate:
|
||||
// nothing changed
|
||||
case oldCreate && !newCreate:
|
||||
// Lost create privs
|
||||
revoke = true
|
||||
case !oldCreate && newCreate:
|
||||
// Gaining create privs
|
||||
grant = true
|
||||
}
|
||||
|
||||
switch {
|
||||
case newGrant == oldGrant:
|
||||
// Nothing changed
|
||||
case newGrant && !oldGrant, // Getting WITH GRANT OPTION priv
|
||||
!newGrant && oldGrant: // Loosing WITH GRANT OPTION priv
|
||||
withGrant = true
|
||||
}
|
||||
|
||||
role := d.Get(schemaPolicyRoleAttr).(string)
|
||||
if strings.ToUpper(role) != "PUBLIC" {
|
||||
role = pq.QuoteIdentifier(role)
|
||||
}
|
||||
|
||||
schema := d.Get(schemaPolicySchemaAttr).(string)
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
switch {
|
||||
case grant:
|
||||
b = bytes.NewBufferString("GRANT CREATE ON SCHEMA ")
|
||||
fmt.Fprintf(b, "%s TO %s", pq.QuoteIdentifier(schema), role)
|
||||
if withGrant {
|
||||
fmt.Fprint(b, " WITH GRANT OPTION")
|
||||
}
|
||||
case revoke:
|
||||
b = bytes.NewBufferString("REVOKE")
|
||||
if withGrant {
|
||||
fmt.Fprint(b, " GRANT OPTION FOR")
|
||||
}
|
||||
|
||||
fmt.Fprintf(b, " CREATE ON SCHEMA %s FROM %s", pq.QuoteIdentifier(schema), role)
|
||||
}
|
||||
|
||||
query := b.String()
|
||||
if _, err := txn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating schema create privileges: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSchemaPolicyUsage(txn *sql.Tx, d *schema.ResourceData) error {
|
||||
if !d.HasChange(schemaPolicyUsageAttr) && !d.HasChange(schemaPolicyUsageWithGrantAttr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
oldUsageRaw, newUsageRaw := d.GetChange(schemaPolicyUsageAttr)
|
||||
oldUsage := oldUsageRaw.(bool)
|
||||
newUsage := newUsageRaw.(bool)
|
||||
|
||||
oldGrantRaw, newGrantRaw := d.GetChange(schemaPolicyUsageWithGrantAttr)
|
||||
oldGrant := oldGrantRaw.(bool)
|
||||
newGrant := newGrantRaw.(bool)
|
||||
|
||||
var grant, revoke, withGrant bool
|
||||
switch {
|
||||
case oldUsage == newUsage:
|
||||
// nothing changed
|
||||
case oldUsage && !newUsage:
|
||||
// Lost usage privs
|
||||
revoke = true
|
||||
case !oldUsage && newUsage:
|
||||
// Gaining usage privs
|
||||
grant = true
|
||||
}
|
||||
|
||||
switch {
|
||||
case newGrant == oldGrant:
|
||||
// Nothing changed
|
||||
case newGrant && !oldGrant, // Getting WITH GRANT OPTION priv
|
||||
!newGrant && oldGrant: // Loosing WITH GRANT OPTION priv
|
||||
withGrant = true
|
||||
}
|
||||
|
||||
role := d.Get(schemaPolicyRoleAttr).(string)
|
||||
if strings.ToUpper(role) != "PUBLIC" {
|
||||
role = pq.QuoteIdentifier(role)
|
||||
}
|
||||
|
||||
schema := d.Get(schemaPolicySchemaAttr).(string)
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
switch {
|
||||
case grant:
|
||||
b = bytes.NewBufferString("GRANT USAGE ON SCHEMA ")
|
||||
fmt.Fprintf(b, "%s TO %s", pq.QuoteIdentifier(schema), role)
|
||||
if withGrant {
|
||||
fmt.Fprint(b, " WITH GRANT OPTION")
|
||||
}
|
||||
case revoke:
|
||||
b = bytes.NewBufferString("REVOKE")
|
||||
if withGrant {
|
||||
fmt.Fprint(b, " GRANT OPTION FOR")
|
||||
}
|
||||
|
||||
fmt.Fprintf(b, " USAGE ON SCHEMA %s FROM %s", pq.QuoteIdentifier(schema), role)
|
||||
}
|
||||
|
||||
query := b.String()
|
||||
if _, err := txn.Query(query); err != nil {
|
||||
return errwrap.Wrapf("Error updating schema usage privileges: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
package postgresql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccPostgreSQLSchemaPolicy_Basic(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckPostgreSQLSchemaPolicyDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccPostgreSQLSchemaPolicyConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckPostgreSQLSchemaPolicyExists("postgresql_schema.test1", "foo"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.dba", "name", "dba"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.app1", "name", "app1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_role.app2", "name", "app2"),
|
||||
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema.foo", "name", "foo"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema.owner", "name", "dba"),
|
||||
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema_policy.foo_allow", "create", "false"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema_policy.foo_allow", "create_with_grant", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema_policy.foo_allow", "usage", "false"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema_policy.foo_allow", "usage_with_grant", "true"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema_policy.foo_allow", "schema", "foo"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema_policy.foo_allow", "role", "app1"),
|
||||
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema_policy.foo_deny", "schema", "foo"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"postgresql_schema_policy.foo_deny", "role", "app2"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckPostgreSQLSchemaPolicyDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*Client)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "postgresql_schema" {
|
||||
continue
|
||||
}
|
||||
|
||||
exists, err := checkSchemaExists(client, rs.Primary.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error checking schema %s", err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
return fmt.Errorf("Schema still exists after destroy")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckPostgreSQLSchemaPolicyExists(n string, schemaName string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Resource not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
actualSchemaName := rs.Primary.Attributes["name"]
|
||||
if actualSchemaName != schemaName {
|
||||
return fmt.Errorf("Wrong value for schema name expected %s got %s", schemaName, actualSchemaName)
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*Client)
|
||||
exists, err := checkSchemaExists(client, rs.Primary.ID)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error checking schema %s", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("Schema not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkSchemaPolicyExists(client *Client, schemaName string) (bool, error) {
|
||||
conn, err := client.Connect()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var _rez string
|
||||
err = conn.QueryRow("SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname=$1", schemaName).Scan(&_rez)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return false, nil
|
||||
case err != nil:
|
||||
return false, fmt.Errorf("Error reading info about schema: %s", err)
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccPostgreSQLSchemaPolicyConfig = `
|
||||
resource "postgresql_role" "dba" {
|
||||
name = "dba"
|
||||
}
|
||||
|
||||
resource "postgresql_role" "app1" {
|
||||
name = "app1"
|
||||
# depends_on = ["postgresql_schema_policy.foo_allow"]
|
||||
}
|
||||
|
||||
resource "postgresql_role" "app2" {
|
||||
name = "app2"
|
||||
}
|
||||
|
||||
resource "postgresql_schema" "foo" {
|
||||
name = "foo"
|
||||
owner = "${postgresql_role.dba.name}"
|
||||
}
|
||||
|
||||
resource "postgresql_schema_policy" "foo_allow" {
|
||||
create_with_grant = true
|
||||
usage_with_grant = true
|
||||
|
||||
schema = "${postgresql_schema.foo.name}"
|
||||
role = "${postgresql_role.app1.name}"
|
||||
}
|
||||
|
||||
resource "postgresql_schema_policy" "foo_deny" {
|
||||
schema = "${postgresql_schema.foo.name}"
|
||||
role = "${postgresql_role.app2.name}"
|
||||
}
|
||||
`
|
|
@ -0,0 +1,25 @@
|
|||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2016, Sean Chittenden
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,7 @@
|
|||
package pgacl
|
||||
|
||||
// ACL is a generic interface that all pgacl types must adhere to
|
||||
type ACL interface {
|
||||
// String creates a PostgreSQL compatible ACL string
|
||||
String() string
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package pgacl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Schema models the privileges of a schema
|
||||
type Schema struct {
|
||||
Role string
|
||||
Create bool
|
||||
CreateGrant bool
|
||||
Usage bool
|
||||
UsageGrant bool
|
||||
}
|
||||
|
||||
const numSchemaOpts = 4
|
||||
|
||||
// NewSchema parses a PostgreSQL ACL string for a schema and returns a Schema
|
||||
// object
|
||||
func NewSchema(aclStr string) (Schema, error) {
|
||||
acl := Schema{}
|
||||
idx := strings.IndexByte(aclStr, '=')
|
||||
if idx == -1 {
|
||||
return Schema{}, fmt.Errorf("invalid aclStr format: %+q", aclStr)
|
||||
}
|
||||
|
||||
acl.Role = aclStr[:idx]
|
||||
|
||||
aclLen := len(aclStr)
|
||||
var i int
|
||||
withGrant := func() bool {
|
||||
if i+1 >= aclLen {
|
||||
return false
|
||||
}
|
||||
|
||||
if aclStr[i+1] == '*' {
|
||||
i++
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
for i = idx + 1; i < aclLen; i++ {
|
||||
switch aclStr[i] {
|
||||
case 'C':
|
||||
acl.Create = true
|
||||
if withGrant() {
|
||||
acl.CreateGrant = true
|
||||
}
|
||||
case 'U':
|
||||
acl.Usage = true
|
||||
if withGrant() {
|
||||
acl.UsageGrant = true
|
||||
}
|
||||
default:
|
||||
return Schema{}, fmt.Errorf("invalid byte %c in schema ACL at %d: %+q", aclStr[i], i, aclStr)
|
||||
}
|
||||
}
|
||||
|
||||
return acl, nil
|
||||
}
|
||||
|
||||
// String creates a PostgreSQL native output for the ACLs that apply to a
|
||||
// schema.
|
||||
func (s Schema) String() string {
|
||||
b := new(bytes.Buffer)
|
||||
b.Grow(len(s.Role) + numSchemaOpts + 1)
|
||||
|
||||
fmt.Fprint(b, s.Role, "=")
|
||||
|
||||
if s.Usage {
|
||||
fmt.Fprint(b, "U")
|
||||
if s.UsageGrant {
|
||||
fmt.Fprint(b, "*")
|
||||
}
|
||||
}
|
||||
|
||||
if s.Create {
|
||||
fmt.Fprint(b, "C")
|
||||
if s.CreateGrant {
|
||||
fmt.Fprint(b, "*")
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
|
@ -2268,6 +2268,12 @@
|
|||
"revision": "88132ecdd39da62f7c73c5a8e1a383d7da5e0e09",
|
||||
"revisionTime": "2016-10-27T15:40:24Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "fCtp8mJPTtEMkpDz8TgRtJzMYw0=",
|
||||
"path": "github.com/sean-/pgacl",
|
||||
"revision": "3d9f301f1bf3d5b590119b28132d2e8d5d1f1a62",
|
||||
"revisionTime": "2016-12-15T16:51:43Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "BqtlwAjgFuHsVVdnw+dGSe+CKLM=",
|
||||
"path": "github.com/sethvargo/go-fastly",
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
---
|
||||
layout: "postgresql"
|
||||
page_title: "PostgreSQL: postgresql_schema_policy"
|
||||
sidebar_current: "docs-postgresql-resource-postgresql_schema_policy"
|
||||
description: |-
|
||||
Manages the permissions of PostgreSQL schemas.
|
||||
---
|
||||
|
||||
# postgresql\_schema\_policy
|
||||
|
||||
The ``postgresql_schema_policy`` resource applies the necessary SQL DCL
|
||||
(`GRANT`s and `REVOKE`s) necessary to ensure access compliance to a particular
|
||||
SCHEMA within a PostgreSQL database.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
resource "postgresql_role" "my_app" {
|
||||
name = "my_app"
|
||||
}
|
||||
|
||||
resource "postgresql_schema" "my_schema" {
|
||||
name = "my_schema"
|
||||
}
|
||||
|
||||
resource "postgresql_schema_policy" "my_schema" {
|
||||
create = true
|
||||
usage = true
|
||||
schema = "${postgresql_schema.my_schema.name}"
|
||||
role = "${postgresql_role.my_app.name}"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
* `create` - (Optional) Should the specified ROLE have CREATE privileges to the specified SCHEMA.
|
||||
|
||||
* `create_with_grant` - (Optional) Should the specified ROLE have CREATE privileges to the specified SCHEMA and the ability to GRANT the CREATE privilege to other ROLEs.
|
||||
|
||||
* `usage` - (Optional) Should the specified ROLE have USAGE privileges to the specified SCHEMA.
|
||||
|
||||
* `usage_with_grant` - (Optional) Should the specified ROLE have USAGE privileges to the specified SCHEMA and the ability to GRANT the USAGE privilege to other ROLEs.
|
||||
|
||||
* `role` - (Required) The ROLE who is receiving the policy.
|
||||
|
||||
* `schema` - (Required) The SCHEMA that is the target of the policy.
|
||||
|
||||
## Import Example
|
||||
|
||||
`postgresql_schema_policy` supports importing resources. Supposing the
|
||||
following Terraform:
|
||||
|
||||
```
|
||||
resource "postgresql_schema" "public" {
|
||||
name = "public"
|
||||
}
|
||||
|
||||
resource "postgresql_schema_policy" "public" {
|
||||
create = true
|
||||
usage = true
|
||||
schema = "${postgresql_schema.public.name}"
|
||||
role = "${postgresql_role.my_app.name}"
|
||||
}
|
||||
```
|
||||
|
||||
It is possible to import a `postgresql_schema_policy` resource with the
|
||||
following command:
|
||||
|
||||
```
|
||||
$ terraform import postgresql_schema_policy.public public
|
||||
```
|
||||
|
||||
Where `public` is the name of the schema in the PostgreSQL database and
|
||||
`postgresql_schema_policy.public` is the name of the resource whose state will
|
||||
be populated as a result of the command.
|
|
@ -25,6 +25,9 @@
|
|||
<li<%= sidebar_current("docs-postgresql-resource-postgresql_schema") %>>
|
||||
<a href="/docs/providers/postgresql/r/postgresql_schema.html">postgresql_schema</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-postgresql-resource-postgresql_schema_policy") %>>
|
||||
<a href="/docs/providers/postgresql/r/postgresql_schema_policy.html">postgresql_schema_policy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
Loading…
Reference in New Issue