diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..9776db75
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,47 @@
+# This GitHub action can publish assets for release when a tag is created.
+# Currently its setup to run on any tag that matches the pattern "v*" (ie. v0.1.0).
+#
+# This uses an action (paultyng/ghaction-import-gpg) that assumes you set your
+# private key in the `GPG_PRIVATE_KEY` secret and passphrase in the `PASSPHRASE`
+# secret. If you would rather own your own GPG handling, please fork this action
+# or use an alternative one for key handling.
+#
+# You will need to pass the `--batch` flag to `gpg` in your signing step
+# in `goreleaser` to indicate this is being used in a non-interactive mode.
+#
+name: release
+on:
+ push:
+ tags:
+ - 'v*'
+jobs:
+ goreleaser:
+ runs-on: ubuntu-latest
+ steps:
+ -
+ name: Checkout
+ uses: actions/checkout@v2
+ -
+ name: Unshallow
+ run: git fetch --prune --unshallow
+ -
+ name: Set up Go
+ uses: actions/setup-go@v2
+ with:
+ go-version: 1.14
+ -
+ name: Import GPG key
+ id: import_gpg
+ uses: paultyng/ghaction-import-gpg@v2.1.0
+ env:
+ GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
+ PASSPHRASE: ${{ secrets.PASSPHRASE }}
+ -
+ name: Run GoReleaser
+ uses: goreleaser/goreleaser-action@v2
+ with:
+ version: latest
+ args: release --rm-dist
+ env:
+ GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.travis.yml b/.travis.yml
index aca93180..8e3959c2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,7 +23,6 @@ install:
script:
- make test
- make vet
-- make website-test
- make testacc
branches:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7bf4f1c5..6e684410 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,45 @@
-## 1.8.0 (Unreleased)
-## 1.7.2 (unreleased)
+
+## 1.9.0 (Unreleased)
+
+FEATURES:
+* `postgresql_grant_role`: Non-authoritative. Grant role to another role.
+
+## 1.8.1 (November 26, 2020)
BUG FIXES:
-* `postgresql_grant` : fix grant on function by removing prokind column selection,
- as is it not available on postgresql version < 11 and its not to
- expect to have a window(w) or aggregate function(a) with the same name as a normal function(f)
-
+* Revert "Use lazy connections" [#199](https://github.com/terraform-providers/terraform-provider-postgresql/pull/199)
+ Plugin panics if not able to connect to the database.
+
+## 1.8.0 (November 26, 2020)
+
+FEATURES:
+
+* `postgresql_extension`: Support drop cascade.
+ ([#162](https://github.com/terraform-providers/terraform-provider-postgresql/pull/162) - @multani)
+
+* ~~Use lazy connections.
+ ([#199](https://github.com/terraform-providers/terraform-provider-postgresql/pull/199) - @estahn)~~ (Reverted in 1.8.1)
+
+BUG FIXES:
+
+* `postgresql_grant`: Fix grant on function by removing `prokind` column selection.
+ ([#171](https://github.com/terraform-providers/terraform-provider-postgresql/pull/171) - @Tommi2Day)
+
+DEV IMPROVEMENTS:
+
+* Set up Github Workflows to create releases.
+ ([#3](https://github.com/cyrilgdn/terraform-provider-postgresql/pull/3) - @thenonameguy)
+
+## 1.7.2 (July 30, 2020)
+
+This is the first release on [Terraform registry](https://registry.terraform.io/providers/cyrilgdn/postgresql/latest)
+
+DEV IMPROVEMENTS:
+
+* Add goreleaser config
+* Pusblish on Terraform registry: https://registry.terraform.io/providers/cyrilgdn/postgresql/latest
+
## 1.7.1 (July 30, 2020)
BUG FIXES:
diff --git a/README.md b/README.md
index d71cb0b6..3448d17a 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,18 @@
-Terraform Provider
-==================
+Terraform Provider for PostgreSQL
+=================================
-- Website: https://www.terraform.io
-- [![Gitter chat](https://badges.gitter.im/hashicorp-terraform/Lobby.png)](https://gitter.im/hashicorp-terraform/Lobby)
-- Mailing list: [Google Groups](http://groups.google.com/group/terraform-tool)
+This provider allows to manage with Terraform [Postgresql](https://www.postgresql.org/) objects like databases, extensions, roles, etc..
-
+It's published on the [Terraform registry](https://registry.terraform.io/providers/cyrilgdn/postgresql/latest/docs).
+It replaces https://github.com/hashicorp/terraform-provider-postgresql since Hashicorp stopped hosting community providers in favor of the Terraform registry.
+
+- Documentation: https://registry.terraform.io/providers/cyrilgdn/postgresql/latest/docs
Requirements
------------
-- [Terraform](https://www.terraform.io/downloads.html) 0.10.x
-- [Go](https://golang.org/doc/install) 1.11 (to build the provider plugin)
+- [Terraform](https://www.terraform.io/downloads.html) 0.12.x
+- [Go](https://golang.org/doc/install) 1.14 (to build the provider plugin)
Building The Provider
---------------------
diff --git a/postgresql/config.go b/postgresql/config.go
index aae9c9d1..371831b9 100644
--- a/postgresql/config.go
+++ b/postgresql/config.go
@@ -119,9 +119,43 @@ func (c *Config) NewClient(database string) (*Client, error) {
dbRegistryLock.Lock()
defer dbRegistryLock.Unlock()
+ dsn := c.connStr(database)
+ dbEntry, found := dbRegistry[dsn]
+ if !found {
+ db, err := sql.Open("postgres", dsn)
+ if err != nil {
+ return nil, fmt.Errorf("Error connecting to PostgreSQL server: %w", err)
+ }
+
+ // We don't want to retain connection
+ // So when we connect on a specific database which might be managed by terraform,
+ // we don't keep opened connection in case of the db has to be dopped in the plan.
+ db.SetMaxIdleConns(0)
+ db.SetMaxOpenConns(c.MaxConns)
+
+ defaultVersion, _ := semver.Parse(defaultExpectedPostgreSQLVersion)
+ version := &c.ExpectedVersion
+ if defaultVersion.Equals(c.ExpectedVersion) {
+ // Version hint not set by user, need to fingerprint
+ version, err = fingerprintCapabilities(db)
+ if err != nil {
+ db.Close()
+ return nil, fmt.Errorf("error detecting capabilities: %w", err)
+ }
+ }
+
+ dbEntry = dbRegistryEntry{
+ db: db,
+ version: *version,
+ }
+ dbRegistry[dsn] = dbEntry
+ }
+
client := Client{
config: *c,
databaseName: database,
+ db: dbEntry.db,
+ version: dbEntry.version,
}
return &client, nil
@@ -271,52 +305,9 @@ func (c *Config) getDatabaseUsername() string {
// return their database resources. Use of QueryRow() or Exec() is encouraged.
// Query() must have their rows.Close()'ed.
func (c *Client) DB() *sql.DB {
- c.connectDB()
return c.db
}
-func (c *Client) connectDB() (*Client, error) {
- dbRegistryLock.Lock()
- defer dbRegistryLock.Unlock()
-
- dsn := c.config.connStr(c.databaseName)
- dbEntry, found := dbRegistry[dsn]
- if !found {
- db, err := sql.Open("postgres", dsn)
- if err != nil {
- return nil, fmt.Errorf("Error connecting to PostgreSQL server: %w", err)
- }
-
- // We don't want to retain connection
- // So when we connect on a specific database which might be managed by terraform,
- // we don't keep opened connection in case of the db has to be dopped in the plan.
- db.SetMaxIdleConns(0)
- db.SetMaxOpenConns(c.config.MaxConns)
-
- defaultVersion, _ := semver.Parse(defaultExpectedPostgreSQLVersion)
- version := &c.config.ExpectedVersion
- if defaultVersion.Equals(c.config.ExpectedVersion) {
- // Version hint not set by user, need to fingerprint
- version, err = fingerprintCapabilities(db)
- if err != nil {
- db.Close()
- return nil, fmt.Errorf("error detecting capabilities: %w", err)
- }
- }
-
- dbEntry = dbRegistryEntry{
- db: db,
- version: *version,
- }
- dbRegistry[dsn] = dbEntry
- }
-
- c.db = dbEntry.db
- c.version = dbEntry.version
-
- return nil, nil
-}
-
// fingerprintCapabilities queries PostgreSQL to populate a local catalog of
// capabilities. This is only run once per Client.
func fingerprintCapabilities(db *sql.DB) (*semver.Version, error) {
diff --git a/postgresql/provider.go b/postgresql/provider.go
index 6eb8c21e..3cf3b89b 100644
--- a/postgresql/provider.go
+++ b/postgresql/provider.go
@@ -130,6 +130,7 @@ func Provider() terraform.ResourceProvider {
"postgresql_default_privileges": resourcePostgreSQLDefaultPrivileges(),
"postgresql_extension": resourcePostgreSQLExtension(),
"postgresql_grant": resourcePostgreSQLGrant(),
+ "postgresql_grant_role": resourcePostgreSQLGrantRole(),
"postgresql_schema": resourcePostgreSQLSchema(),
"postgresql_role": resourcePostgreSQLRole(),
},
diff --git a/postgresql/resource_postgresql_grant_role.go b/postgresql/resource_postgresql_grant_role.go
new file mode 100644
index 00000000..902a4621
--- /dev/null
+++ b/postgresql/resource_postgresql_grant_role.go
@@ -0,0 +1,218 @@
+package postgresql
+
+import (
+ "database/sql"
+ "fmt"
+ "log"
+ "strconv"
+ "strings"
+
+ "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
+ "github.com/lib/pq"
+)
+
+const (
+ // This returns the role membership for role, grant_role
+ getGrantRoleQuery = `
+SELECT
+ pg_get_userbyid(member) as role,
+ pg_get_userbyid(roleid) as grant_role,
+ admin_option
+FROM
+ pg_auth_members
+WHERE
+ pg_get_userbyid(member) = $1 AND
+ pg_get_userbyid(roleid) = $2;
+`
+)
+
+func resourcePostgreSQLGrantRole() *schema.Resource {
+ return &schema.Resource{
+ Create: resourcePostgreSQLGrantRoleCreate,
+ Read: resourcePostgreSQLGrantRoleRead,
+ Delete: resourcePostgreSQLGrantRoleDelete,
+
+ Schema: map[string]*schema.Schema{
+ "role": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "The name of the role to grant grant_role",
+ },
+ "grant_role": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "The name of the role that is granted to role",
+ },
+ "with_admin_option": {
+ Type: schema.TypeBool,
+ Optional: true,
+ ForceNew: true,
+ Default: false,
+ Description: "Permit the grant recipient to grant it to others",
+ },
+ },
+ }
+}
+
+func resourcePostgreSQLGrantRoleRead(d *schema.ResourceData, meta interface{}) error {
+ client := meta.(*Client)
+
+ if !client.featureSupported(featurePrivileges) {
+ return fmt.Errorf(
+ "postgresql_grant_role resource is not supported for this Postgres version (%s)",
+ client.version,
+ )
+ }
+
+ client.catalogLock.RLock()
+ defer client.catalogLock.RUnlock()
+
+ return readGrantRole(client.DB(), d)
+}
+
+func resourcePostgreSQLGrantRoleCreate(d *schema.ResourceData, meta interface{}) error {
+ client := meta.(*Client)
+
+ if !client.featureSupported(featurePrivileges) {
+ return fmt.Errorf(
+ "postgresql_grant_role resource is not supported for this Postgres version (%s)",
+ client.version,
+ )
+ }
+
+ client.catalogLock.Lock()
+ defer client.catalogLock.Unlock()
+
+ txn, err := startTransaction(client, "")
+ if err != nil {
+ return err
+ }
+ defer deferredRollback(txn)
+
+ // Revoke the granted roles before granting them again.
+ if err = revokeRole(txn, d); err != nil {
+ return err
+ }
+
+ if err = grantRole(txn, d); err != nil {
+ return err
+ }
+
+ if err = txn.Commit(); err != nil {
+ return fmt.Errorf("could not commit transaction: %w", err)
+ }
+
+ d.SetId(generateGrantRoleID(d))
+
+ return readGrantRole(client.DB(), d)
+}
+
+func resourcePostgreSQLGrantRoleDelete(d *schema.ResourceData, meta interface{}) error {
+ client := meta.(*Client)
+
+ if !client.featureSupported(featurePrivileges) {
+ return fmt.Errorf(
+ "postgresql_grant_role resource is not supported for this Postgres version (%s)",
+ client.version,
+ )
+ }
+
+ client.catalogLock.Lock()
+ defer client.catalogLock.Unlock()
+
+ txn, err := startTransaction(client, "")
+ if err != nil {
+ return err
+ }
+ defer deferredRollback(txn)
+
+ if err = revokeRole(txn, d); err != nil {
+ return err
+ }
+
+ if err = txn.Commit(); err != nil {
+ return fmt.Errorf("could not commit transaction: %w", err)
+ }
+
+ return nil
+}
+
+func readGrantRole(db QueryAble, d *schema.ResourceData) error {
+ var roleName, grantRoleName string
+ var withAdminOption bool
+
+ grantRoleID := d.Id()
+
+ values := []interface{}{
+ &roleName,
+ &grantRoleName,
+ &withAdminOption,
+ }
+
+ err := db.QueryRow(getGrantRoleQuery, d.Get("role"), d.Get("grant_role")).Scan(values...)
+ switch {
+ case err == sql.ErrNoRows:
+ log.Printf("[WARN] PostgreSQL grant role (%q) not found", grantRoleID)
+ d.SetId("")
+ return nil
+ case err != nil:
+ return fmt.Errorf("Error reading grant role: %w", err)
+ }
+
+ d.Set("role", roleName)
+ d.Set("grant_role", grantRoleName)
+ d.Set("with_admin_option", withAdminOption)
+
+ d.SetId(generateGrantRoleID(d))
+
+ return nil
+}
+
+func createGrantRoleQuery(d *schema.ResourceData) string {
+ grantRole, _ := d.Get("grant_role").(string)
+ role, _ := d.Get("role").(string)
+
+ query := fmt.Sprintf(
+ "GRANT %s TO %s",
+ pq.QuoteIdentifier(grantRole),
+ pq.QuoteIdentifier(role),
+ )
+ if wao, _ := d.Get("with_admin_option").(bool); wao {
+ query = query + " WITH ADMIN OPTION"
+ }
+
+ return query
+}
+
+func createRevokeRoleQuery(d *schema.ResourceData) string {
+ grantRole, _ := d.Get("grant_role").(string)
+ role, _ := d.Get("role").(string)
+
+ return fmt.Sprintf(
+ "REVOKE %s FROM %s",
+ pq.QuoteIdentifier(grantRole),
+ pq.QuoteIdentifier(role),
+ )
+}
+
+func grantRole(txn *sql.Tx, d *schema.ResourceData) error {
+ query := createGrantRoleQuery(d)
+ if _, err := txn.Exec(query); err != nil {
+ return fmt.Errorf("could not execute grant query: %w", err)
+ }
+ return nil
+}
+
+func revokeRole(txn *sql.Tx, d *schema.ResourceData) error {
+ query := createRevokeRoleQuery(d)
+ if _, err := txn.Exec(query); err != nil {
+ return fmt.Errorf("could not execute revoke query: %w", err)
+ }
+ return nil
+}
+
+func generateGrantRoleID(d *schema.ResourceData) string {
+ return strings.Join([]string{d.Get("role").(string), d.Get("grant_role").(string), strconv.FormatBool(d.Get("with_admin_option").(bool))}, "_")
+}
diff --git a/postgresql/resource_postgresql_grant_role_test.go b/postgresql/resource_postgresql_grant_role_test.go
new file mode 100644
index 00000000..3f444f8c
--- /dev/null
+++ b/postgresql/resource_postgresql_grant_role_test.go
@@ -0,0 +1,172 @@
+package postgresql
+
+import (
+ "database/sql"
+ "fmt"
+ "strconv"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-sdk/helper/resource"
+ "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
+ "github.com/hashicorp/terraform-plugin-sdk/terraform"
+ "github.com/lib/pq"
+)
+
+func TestCreateGrantRoleQuery(t *testing.T) {
+ var roleName = "foo"
+ var grantRoleName = "bar"
+
+ cases := []struct {
+ resource map[string]interface{}
+ expected string
+ }{
+ {
+ resource: map[string]interface{}{
+ "role": roleName,
+ "grant_role": grantRoleName,
+ },
+ expected: fmt.Sprintf("GRANT %s TO %s", pq.QuoteIdentifier(grantRoleName), pq.QuoteIdentifier(roleName)),
+ },
+ {
+ resource: map[string]interface{}{
+ "role": roleName,
+ "grant_role": grantRoleName,
+ "with_admin_option": false,
+ },
+ expected: fmt.Sprintf("GRANT %s TO %s", pq.QuoteIdentifier(grantRoleName), pq.QuoteIdentifier(roleName)),
+ },
+ {
+ resource: map[string]interface{}{
+ "role": roleName,
+ "grant_role": grantRoleName,
+ "with_admin_option": true,
+ },
+ expected: fmt.Sprintf("GRANT %s TO %s WITH ADMIN OPTION", pq.QuoteIdentifier(grantRoleName), pq.QuoteIdentifier(roleName)),
+ },
+ }
+
+ for _, c := range cases {
+ out := createGrantRoleQuery(schema.TestResourceDataRaw(t, resourcePostgreSQLGrantRole().Schema, c.resource))
+ if out != c.expected {
+ t.Fatalf("Error matching output and expected: %#v vs %#v", out, c.expected)
+ }
+ }
+}
+
+func TestRevokeRoleQuery(t *testing.T) {
+ var roleName = "foo"
+ var grantRoleName = "bar"
+
+ expected := fmt.Sprintf("REVOKE %s FROM %s", pq.QuoteIdentifier(grantRoleName), pq.QuoteIdentifier(roleName))
+
+ cases := []struct {
+ resource map[string]interface{}
+ }{
+ {
+ resource: map[string]interface{}{
+ "role": roleName,
+ "grant_role": grantRoleName,
+ },
+ },
+ {
+ resource: map[string]interface{}{
+ "role": roleName,
+ "grant_role": grantRoleName,
+ "with_admin_option": false,
+ },
+ },
+ {
+ resource: map[string]interface{}{
+ "role": roleName,
+ "grant_role": grantRoleName,
+ "with_admin_option": true,
+ },
+ },
+ }
+
+ for _, c := range cases {
+ out := createRevokeRoleQuery(schema.TestResourceDataRaw(t, resourcePostgreSQLGrantRole().Schema, c.resource))
+ if out != expected {
+ t.Fatalf("Error matching output and expected: %#v vs %#v", out, expected)
+ }
+ }
+}
+
+func TestAccPostgresqlGrantRole(t *testing.T) {
+ skipIfNotAcc(t)
+
+ config := getTestConfig(t)
+ dsn := config.connStr("postgres")
+
+ dbSuffix, teardown := setupTestDatabase(t, false, true)
+ defer teardown()
+
+ _, roleName := getTestDBNames(dbSuffix)
+
+ grantedRoleName := "foo"
+
+ testAccPostgresqlGrantRoleResources := fmt.Sprintf(`
+ resource postgresql_role "grant" {
+ name = "%s"
+ }
+ resource postgresql_grant_role "grant_role" {
+ role = "%s"
+ grant_role = postgresql_role.grant.name
+ with_admin_option = true
+ }
+ `, grantedRoleName, roleName)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ testAccPreCheck(t)
+ testCheckCompatibleVersion(t, featurePrivileges)
+ },
+ Providers: testAccProviders,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccPostgresqlGrantRoleResources,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(
+ "postgresql_grant_role.grant_role", "role", roleName),
+ resource.TestCheckResourceAttr(
+ "postgresql_grant_role.grant_role", "grant_role", grantedRoleName),
+ resource.TestCheckResourceAttr(
+ "postgresql_grant_role.grant_role", "with_admin_option", strconv.FormatBool(true)),
+ checkGrantRole(t, dsn, roleName, grantedRoleName, true),
+ ),
+ },
+ },
+ })
+}
+
+func checkGrantRole(t *testing.T, dsn, role string, grantRole string, withAdmin bool) resource.TestCheckFunc {
+ return func(s *terraform.State) error {
+ db, err := sql.Open("postgres", dsn)
+ if err != nil {
+ t.Fatalf("could to create connection pool: %v", err)
+ }
+ defer db.Close()
+
+ var _rez int
+ err = db.QueryRow(`
+ SELECT 1
+ FROM pg_auth_members
+ WHERE pg_get_userbyid(member) = $1
+ AND pg_get_userbyid(roleid) = $2
+ AND admin_option = $3;
+ `, role, grantRole, withAdmin).Scan(&_rez)
+
+ switch {
+ case err == sql.ErrNoRows:
+ return fmt.Errorf(
+ "Role %s is not a member of %s",
+ role, grantRole,
+ )
+
+ case err != nil:
+ t.Fatalf("could not check granted role: %v", err)
+ }
+
+ return nil
+ }
+}
diff --git a/postgresql/resource_postgresql_role.go b/postgresql/resource_postgresql_role.go
index ecb4a43d..20d5a083 100644
--- a/postgresql/resource_postgresql_role.go
+++ b/postgresql/resource_postgresql_role.go
@@ -473,6 +473,9 @@ func readSearchPath(roleConfig pq.ByteaArray) []string {
config := string(v)
if strings.HasPrefix(config, roleSearchPathAttr) {
var result = strings.Split(strings.TrimPrefix(config, roleSearchPathAttr+"="), ", ")
+ for i := range result {
+ result[i] = strings.Trim(result[i], `"`)
+ }
return result
}
}
diff --git a/postgresql/resource_postgresql_role_test.go b/postgresql/resource_postgresql_role_test.go
index ac299ac8..b8d4f1f4 100644
--- a/postgresql/resource_postgresql_role_test.go
+++ b/postgresql/resource_postgresql_role_test.go
@@ -53,7 +53,7 @@ func TestAccPostgresqlRole_Basic(t *testing.T) {
resource.TestCheckResourceAttr("postgresql_role.sub_role", "name", "sub_role"),
resource.TestCheckResourceAttr("postgresql_role.sub_role", "roles.#", "2"),
- testAccCheckPostgresqlRoleExists("role_with_search_path", nil, []string{"bar", "foo"}),
+ testAccCheckPostgresqlRoleExists("role_with_search_path", nil, []string{"bar", "foo-with-hyphen"}),
// The int part in the attr name is the schema.HashString of the value.
resource.TestCheckResourceAttr("postgresql_role.sub_role", "roles.719783566", "myrole2"),
@@ -346,6 +346,9 @@ func checkSearchPath(client *Client, roleName string, expectedSearchPath []strin
}
searchPath := strings.Split(searchPathStr, ", ")
+ for i := range searchPath {
+ searchPath[i] = strings.Trim(searchPath[i], `"`)
+ }
sort.Strings(expectedSearchPath)
if !reflect.DeepEqual(searchPath, expectedSearchPath) {
return fmt.Errorf(
@@ -411,6 +414,6 @@ resource "postgresql_role" "sub_role" {
resource "postgresql_role" "role_with_search_path" {
name = "role_with_search_path"
- search_path = ["bar", "foo"]
+ search_path = ["bar", "foo-with-hyphen"]
}
`
diff --git a/postgresql/utils_test.go b/postgresql/utils_test.go
index 38d06ff9..0e6a7e52 100644
--- a/postgresql/utils_test.go
+++ b/postgresql/utils_test.go
@@ -114,6 +114,21 @@ func setupTestDatabase(t *testing.T, createDB, createRole bool) (string, func())
}
}
+// createTestRole creates a role before executing a terraform test
+// and provides the teardown function to delete all these resources.
+func createTestRole(t *testing.T, roleName string) func() {
+ config := getTestConfig(t)
+
+ dbExecute(t, config.connStr("postgres"), fmt.Sprintf(
+ "CREATE ROLE %s LOGIN ENCRYPTED PASSWORD '%s'",
+ roleName, testRolePassword,
+ ))
+
+ return func() {
+ dbExecute(t, config.connStr("postgres"), fmt.Sprintf("DROP ROLE IF EXISTS %s", roleName))
+ }
+}
+
func createTestTables(t *testing.T, dbSuffix string, tables []string, owner string) func() {
config := getTestConfig(t)
dbName, _ := getTestDBNames(dbSuffix)
diff --git a/website/docs/r/postgresql_grant_role.html.markdown b/website/docs/r/postgresql_grant_role.html.markdown
new file mode 100644
index 00000000..c06e5cc9
--- /dev/null
+++ b/website/docs/r/postgresql_grant_role.html.markdown
@@ -0,0 +1,33 @@
+---
+layout: "postgresql"
+page_title: "PostgreSQL: postgresql_grant_role"
+sidebar_current: "docs-postgresql-resource-postgresql_grant_role"
+description: |-
+ Creates and manages membership in a role to one or more other roles.
+---
+
+# postgresql\_grant\_role
+
+The ``postgresql_grant_role`` resource creates and manages membership in a role to one or more other roles in a non-authoritative way.
+
+When using ``postgresql_grant_role`` resource it is likely because the PostgreSQL role you are modifying was created outside of this provider.
+
+~> **Note:** This resource needs PostgreSQL version 9 or above.
+
+~> **Note:** `postgresql_grant_role` **cannot** be used in conjunction with `postgresql_role` or they will fight over what your role grants should be.
+
+## Usage
+
+```hcl
+resource "postgresql_grant_role" "grant_root" {
+ role = "root"
+ grant_role = "application"
+ with_admin_option = true
+}
+```
+
+## Argument Reference
+
+* `role` - (Required) The name of the role that is granted a new membership.
+* `grant_role` - (Required) The name of the role that is added to `role`.
+* `with_admin_option` - (Optional) Giving ability to grant membership to others or not for `role`. (Default: false)
diff --git a/website/postgresql.erb b/website/postgresql.erb
index 3c137827..bce73e5e 100644
--- a/website/postgresql.erb
+++ b/website/postgresql.erb
@@ -25,6 +25,9 @@