Skip to content
Merged
1 change: 1 addition & 0 deletions postgresql/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ func Provider() *schema.Provider {
"postgresql_server": resourcePostgreSQLServer(),
"postgresql_user_mapping": resourcePostgreSQLUserMapping(),
"postgresql_alter_role": resourcePostgreSQLAlterRole(),
"postgresql_script": resourcePostgreSQLScript(),
},

DataSourcesMap: map[string]*schema.Resource{
Expand Down
120 changes: 120 additions & 0 deletions postgresql/resource_postgresql_script.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package postgresql

import (
"crypto/sha1"
"encoding/hex"
"log"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const (
scriptCommandsAttr = "commands"
scriptTriesAttr = "tries"
scriptBackoffDelayAttr = "backoff_delay"
scriptShasumAttr = "shasum"
)

func resourcePostgreSQLScript() *schema.Resource {
return &schema.Resource{
Create: PGResourceFunc(resourcePostgreSQLScriptCreateOrUpdate),
Read: PGResourceFunc(resourcePostgreSQLScriptRead),
Update: PGResourceFunc(resourcePostgreSQLScriptCreateOrUpdate),
Delete: PGResourceFunc(resourcePostgreSQLScriptDelete),

Schema: map[string]*schema.Schema{
scriptCommandsAttr: {
Type: schema.TypeList,
Required: true,
Description: "List of SQL commands to execute",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
scriptTriesAttr: {
Type: schema.TypeInt,
Optional: true,
Default: 1,
Description: "Number of tries for a failing command",
},
scriptBackoffDelayAttr: {
Type: schema.TypeInt,
Optional: true,
Default: 1,
Description: "Number of seconds between two tries of the batch of commands",
},
scriptShasumAttr: {
Type: schema.TypeString,
Computed: true,
Description: "Shasum of commands",
},
},
}
}

func resourcePostgreSQLScriptCreateOrUpdate(db *DBConnection, d *schema.ResourceData) error {
commands := d.Get(scriptCommandsAttr).([]any)
tries := d.Get(scriptTriesAttr).(int)
backoffDelay := d.Get(scriptBackoffDelayAttr).(int)

sum := shasumCommands(commands)

if err := executeCommands(db, commands, tries, backoffDelay); err != nil {
return err
}

d.Set(scriptShasumAttr, sum)
d.SetId(sum)
return nil
}

func resourcePostgreSQLScriptRead(db *DBConnection, d *schema.ResourceData) error {
commands := d.Get(scriptCommandsAttr).([]any)
newSum := shasumCommands(commands)
d.Set(scriptShasumAttr, newSum)

return nil
}

func resourcePostgreSQLScriptDelete(db *DBConnection, d *schema.ResourceData) error {
return nil
}

func executeCommands(db *DBConnection, commands []any, tries int, backoffDelay int) error {
for try := 1; ; try++ {
err := executeBatch(db, commands)
if err == nil {
return nil
} else {
if try >= tries {
return err
}
time.Sleep(time.Duration(backoffDelay) * time.Second)
}
}
}

func executeBatch(db *DBConnection, commands []any) error {
for _, command := range commands {
log.Printf("[ERROR] Executing %s", command.(string))
_, err := db.Query(command.(string))

if err != nil {
log.Println("[ERROR] Error catched:", err)
if _, rollbackError := db.Query("ROLLBACK"); rollbackError != nil {
log.Println("[ERROR] Rollback raised an error:", rollbackError)
}
return err
}
}
return nil
}

func shasumCommands(commands []any) string {
sha := sha1.New()
for _, command := range commands {
sha.Write([]byte(command.(string)))
}
return hex.EncodeToString(sha.Sum(nil))
}
182 changes: 182 additions & 0 deletions postgresql/resource_postgresql_script_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package postgresql

import (
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccPostgresqlScript_basic(t *testing.T) {
config := `
resource "postgresql_script" "test" {
commands = [
"SELECT 1;"
]
tries = 2
backoff_delay = 4
}
`

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("postgresql_script.test", "commands.0", "SELECT 1;"),
resource.TestCheckResourceAttr("postgresql_script.test", "tries", "2"),
resource.TestCheckResourceAttr("postgresql_script.test", "backoff_delay", "4"),
),
},
},
})
}

func TestAccPostgresqlScript_multiple(t *testing.T) {
config := `
resource "postgresql_script" "test" {
commands = [
"SELECT 1;",
"SELECT 2;",
"SELECT 3;"
]
tries = 2
backoff_delay = 4
}
`

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("postgresql_script.test", "commands.0", "SELECT 1;"),
resource.TestCheckResourceAttr("postgresql_script.test", "commands.1", "SELECT 2;"),
resource.TestCheckResourceAttr("postgresql_script.test", "commands.2", "SELECT 3;"),
resource.TestCheckResourceAttr("postgresql_script.test", "tries", "2"),
resource.TestCheckResourceAttr("postgresql_script.test", "backoff_delay", "4"),
),
},
},
})
}

func TestAccPostgresqlScript_default(t *testing.T) {
config := `
resource "postgresql_script" "test" {
commands = [
"SELECT 1;"
]
}
`

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("postgresql_script.test", "commands.0", "SELECT 1;"),
resource.TestCheckResourceAttr("postgresql_script.test", "tries", "1"),
resource.TestCheckResourceAttr("postgresql_script.test", "backoff_delay", "1"),
),
},
},
})
}

func TestAccPostgresqlScript_reapply(t *testing.T) {
config := `
resource "postgresql_script" "test" {
commands = [
"SELECT 1;"
]
}
`

configChange := `
resource "postgresql_script" "test" {
commands = [
"SELECT 2;"
]
}
`

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("postgresql_script.test", "commands.0", "SELECT 1;"),
),
},
{
Config: config,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("postgresql_script.test", "commands.0", "SELECT 1;"),
),
},
{
Config: configChange,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("postgresql_script.test", "commands.0", "SELECT 2;"),
),
},
},
})
}

func TestAccPostgresqlScript_fail(t *testing.T) {
config := `
resource "postgresql_script" "invalid" {
commands = [
"SLC FROM nowhere;"
]
tries = 2
backoff_delay = 2
}
`

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
ExpectError: regexp.MustCompile("syntax error"),
},
},
})
}

func TestAccPostgresqlScript_failMultiple(t *testing.T) {
config := `
resource "postgresql_script" "invalid" {
commands = [
"BEGIN",
"SLC FROM nowhere;",
"COMMIT"
]
tries = 2
backoff_delay = 2
}
`

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
ExpectError: regexp.MustCompile("syntax error"),
},
},
})
}
48 changes: 48 additions & 0 deletions website/docs/r/postgresql_script.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
layout: "postgresql"
page_title: "PostgreSQL: postgresql_script"
sidebar_current: "docs-postgresql-resource-postgresql_script"
description: |-
Execute a SQL script
---

# postgresql\_script

The ``postgresql_script`` execute a script given as parameter. This script will be executed each time it changes.

If one command of the batch fails, the provider will send a `ROLLBACK` command to the database, and retry, according to the tries / backoff_delay configuration.

## Usage

```hcl
resource "postgresql_script" "foo" {
commands = [
"command 1",
"command 2"
]
tries = 1
backoff_delay = 1
}
```

## Argument Reference

* `commands` - (Required) An array of commands to execute, one by one.
* `tries` - (Optional) Number of tries of a command before raising an error.
* `backoff_delay` - (Optional) In case of failure, time in second to wait before a retry.

## Examples

Revoke default accesses for public schema:

```hcl
resource "postgresql_script" "foo" {
commands = [
"BEBIN",
"SELECT * FROM table",
"COMMIT"
]
tries = 3
backoff_delay = 1
}
```
3 changes: 3 additions & 0 deletions website/postgresql.erb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
<li<%= sidebar_current("docs-postgresql-resource-postgresql_user_mapping") %>>
<a href="/docs/providers/postgresql/r/postgresql_user_mapping.html">postgresql_user_mapping</a>
</li>
<li<%= sidebar_current("docs-postgresql-resource-postgresql_script") %>>
<a href="/docs/providers/postgresql/r/postgresql_script.html">postgresql_script</a>
</li>
</ul>
</li>

Expand Down
Loading