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
115 changes: 115 additions & 0 deletions postgresql/resource_postgresql_script.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package postgresql

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

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

const (
scriptCommandsAttr = "commands"
scriptTriesAttr = "tries"
scriptTimeoutAttr = "timeout"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's not really a timeout. More a backoff ?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, can you rename?

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",
},
scriptTimeoutAttr: {
Type: schema.TypeInt,
Optional: true,
Default: 1,
Description: "Number of seconds between two tries for a command",
},
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)
timeout := d.Get(scriptTimeoutAttr).(int)

sum := shasumCommands(commands)

if err := executeCommands(db, commands, tries, timeout); 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, timeout int) error {
for i := 1; ; i++ {
var err error
for _, command := range commands {
log.Printf("[DEBUG] Executing (%d try) %s", i, command.(string))
_, err = db.Query(command.(string))

if err != nil {
log.Println("[DEBUG] Error catched:", err)
if _, err := db.Query("ROLLBACK"); err != nil {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plase use a different variable name

log.Println("[DEBUG] Rollback raised an error:", err)
}
if i >= tries {
return err
}
time.Sleep(time.Duration(timeout) * time.Second)
break
}
}
if err == nil {
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
timeout = 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", "timeout", "4"),
),
},
},
})
}

func TestAccPostgresqlScript_multiple(t *testing.T) {
config := `
resource "postgresql_script" "test" {
commands = [
"SELECT 1;",
"SELECT 2;",
"SELECT 3;"
]
tries = 2
timeout = 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", "timeout", "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", "timeout", "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
timeout = 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
timeout = 2
}
`

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
ExpectError: regexp.MustCompile("syntax error"),
},
},
})
}
46 changes: 46 additions & 0 deletions website/docs/r/postgresql_script.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
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.

## Usage

```hcl
resource "postgresql_script" "foo" {
commands = [
"command 1",
"command 2"
]
tries = 1
timeout = 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.
* `timeout` - (Optional) Time in second between two failing commands.

## Examples

Revoke default accesses for public schema:

```hcl
resource "postgresql_script" "foo" {
commands = [
"BEBIN",
"SELECT * FROM table",
"COMMIT"
]
tries = 3
timeout = 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