Skip to content

Commit 23d0133

Browse files
authored
add postgresql_script resource (#18)
Add a `postgresql_script` resource enabling to execute an array of sql queries.
2 parents 9864481 + 0d873a3 commit 23d0133

File tree

5 files changed

+354
-0
lines changed

5 files changed

+354
-0
lines changed

postgresql/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ func Provider() *schema.Provider {
201201
"postgresql_server": resourcePostgreSQLServer(),
202202
"postgresql_user_mapping": resourcePostgreSQLUserMapping(),
203203
"postgresql_alter_role": resourcePostgreSQLAlterRole(),
204+
"postgresql_script": resourcePostgreSQLScript(),
204205
},
205206

206207
DataSourcesMap: map[string]*schema.Resource{
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package postgresql
2+
3+
import (
4+
"crypto/sha1"
5+
"encoding/hex"
6+
"log"
7+
"time"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
)
11+
12+
const (
13+
scriptCommandsAttr = "commands"
14+
scriptTriesAttr = "tries"
15+
scriptBackoffDelayAttr = "backoff_delay"
16+
scriptShasumAttr = "shasum"
17+
)
18+
19+
func resourcePostgreSQLScript() *schema.Resource {
20+
return &schema.Resource{
21+
Create: PGResourceFunc(resourcePostgreSQLScriptCreateOrUpdate),
22+
Read: PGResourceFunc(resourcePostgreSQLScriptRead),
23+
Update: PGResourceFunc(resourcePostgreSQLScriptCreateOrUpdate),
24+
Delete: PGResourceFunc(resourcePostgreSQLScriptDelete),
25+
26+
Schema: map[string]*schema.Schema{
27+
scriptCommandsAttr: {
28+
Type: schema.TypeList,
29+
Required: true,
30+
Description: "List of SQL commands to execute",
31+
Elem: &schema.Schema{
32+
Type: schema.TypeString,
33+
},
34+
},
35+
scriptTriesAttr: {
36+
Type: schema.TypeInt,
37+
Optional: true,
38+
Default: 1,
39+
Description: "Number of tries for a failing command",
40+
},
41+
scriptBackoffDelayAttr: {
42+
Type: schema.TypeInt,
43+
Optional: true,
44+
Default: 1,
45+
Description: "Number of seconds between two tries of the batch of commands",
46+
},
47+
scriptShasumAttr: {
48+
Type: schema.TypeString,
49+
Computed: true,
50+
Description: "Shasum of commands",
51+
},
52+
},
53+
}
54+
}
55+
56+
func resourcePostgreSQLScriptCreateOrUpdate(db *DBConnection, d *schema.ResourceData) error {
57+
commands := d.Get(scriptCommandsAttr).([]any)
58+
tries := d.Get(scriptTriesAttr).(int)
59+
backoffDelay := d.Get(scriptBackoffDelayAttr).(int)
60+
61+
sum := shasumCommands(commands)
62+
63+
if err := executeCommands(db, commands, tries, backoffDelay); err != nil {
64+
return err
65+
}
66+
67+
d.Set(scriptShasumAttr, sum)
68+
d.SetId(sum)
69+
return nil
70+
}
71+
72+
func resourcePostgreSQLScriptRead(db *DBConnection, d *schema.ResourceData) error {
73+
commands := d.Get(scriptCommandsAttr).([]any)
74+
newSum := shasumCommands(commands)
75+
d.Set(scriptShasumAttr, newSum)
76+
77+
return nil
78+
}
79+
80+
func resourcePostgreSQLScriptDelete(db *DBConnection, d *schema.ResourceData) error {
81+
return nil
82+
}
83+
84+
func executeCommands(db *DBConnection, commands []any, tries int, backoffDelay int) error {
85+
for try := 1; ; try++ {
86+
err := executeBatch(db, commands)
87+
if err == nil {
88+
return nil
89+
} else {
90+
if try >= tries {
91+
return err
92+
}
93+
time.Sleep(time.Duration(backoffDelay) * time.Second)
94+
}
95+
}
96+
}
97+
98+
func executeBatch(db *DBConnection, commands []any) error {
99+
for _, command := range commands {
100+
log.Printf("[ERROR] Executing %s", command.(string))
101+
_, err := db.Query(command.(string))
102+
103+
if err != nil {
104+
log.Println("[ERROR] Error catched:", err)
105+
if _, rollbackError := db.Query("ROLLBACK"); rollbackError != nil {
106+
log.Println("[ERROR] Rollback raised an error:", rollbackError)
107+
}
108+
return err
109+
}
110+
}
111+
return nil
112+
}
113+
114+
func shasumCommands(commands []any) string {
115+
sha := sha1.New()
116+
for _, command := range commands {
117+
sha.Write([]byte(command.(string)))
118+
}
119+
return hex.EncodeToString(sha.Sum(nil))
120+
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package postgresql
2+
3+
import (
4+
"regexp"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
8+
)
9+
10+
func TestAccPostgresqlScript_basic(t *testing.T) {
11+
config := `
12+
resource "postgresql_script" "test" {
13+
commands = [
14+
"SELECT 1;"
15+
]
16+
tries = 2
17+
backoff_delay = 4
18+
}
19+
`
20+
21+
resource.Test(t, resource.TestCase{
22+
PreCheck: func() { testAccPreCheck(t) },
23+
Providers: testAccProviders,
24+
Steps: []resource.TestStep{
25+
{
26+
Config: config,
27+
Check: resource.ComposeTestCheckFunc(
28+
resource.TestCheckResourceAttr("postgresql_script.test", "commands.0", "SELECT 1;"),
29+
resource.TestCheckResourceAttr("postgresql_script.test", "tries", "2"),
30+
resource.TestCheckResourceAttr("postgresql_script.test", "backoff_delay", "4"),
31+
),
32+
},
33+
},
34+
})
35+
}
36+
37+
func TestAccPostgresqlScript_multiple(t *testing.T) {
38+
config := `
39+
resource "postgresql_script" "test" {
40+
commands = [
41+
"SELECT 1;",
42+
"SELECT 2;",
43+
"SELECT 3;"
44+
]
45+
tries = 2
46+
backoff_delay = 4
47+
}
48+
`
49+
50+
resource.Test(t, resource.TestCase{
51+
PreCheck: func() { testAccPreCheck(t) },
52+
Providers: testAccProviders,
53+
Steps: []resource.TestStep{
54+
{
55+
Config: config,
56+
Check: resource.ComposeTestCheckFunc(
57+
resource.TestCheckResourceAttr("postgresql_script.test", "commands.0", "SELECT 1;"),
58+
resource.TestCheckResourceAttr("postgresql_script.test", "commands.1", "SELECT 2;"),
59+
resource.TestCheckResourceAttr("postgresql_script.test", "commands.2", "SELECT 3;"),
60+
resource.TestCheckResourceAttr("postgresql_script.test", "tries", "2"),
61+
resource.TestCheckResourceAttr("postgresql_script.test", "backoff_delay", "4"),
62+
),
63+
},
64+
},
65+
})
66+
}
67+
68+
func TestAccPostgresqlScript_default(t *testing.T) {
69+
config := `
70+
resource "postgresql_script" "test" {
71+
commands = [
72+
"SELECT 1;"
73+
]
74+
}
75+
`
76+
77+
resource.Test(t, resource.TestCase{
78+
PreCheck: func() { testAccPreCheck(t) },
79+
Providers: testAccProviders,
80+
Steps: []resource.TestStep{
81+
{
82+
Config: config,
83+
Check: resource.ComposeTestCheckFunc(
84+
resource.TestCheckResourceAttr("postgresql_script.test", "commands.0", "SELECT 1;"),
85+
resource.TestCheckResourceAttr("postgresql_script.test", "tries", "1"),
86+
resource.TestCheckResourceAttr("postgresql_script.test", "backoff_delay", "1"),
87+
),
88+
},
89+
},
90+
})
91+
}
92+
93+
func TestAccPostgresqlScript_reapply(t *testing.T) {
94+
config := `
95+
resource "postgresql_script" "test" {
96+
commands = [
97+
"SELECT 1;"
98+
]
99+
}
100+
`
101+
102+
configChange := `
103+
resource "postgresql_script" "test" {
104+
commands = [
105+
"SELECT 2;"
106+
]
107+
}
108+
`
109+
110+
resource.Test(t, resource.TestCase{
111+
PreCheck: func() { testAccPreCheck(t) },
112+
Providers: testAccProviders,
113+
Steps: []resource.TestStep{
114+
{
115+
Config: config,
116+
Check: resource.ComposeTestCheckFunc(
117+
resource.TestCheckResourceAttr("postgresql_script.test", "commands.0", "SELECT 1;"),
118+
),
119+
},
120+
{
121+
Config: config,
122+
Check: resource.ComposeTestCheckFunc(
123+
resource.TestCheckResourceAttr("postgresql_script.test", "commands.0", "SELECT 1;"),
124+
),
125+
},
126+
{
127+
Config: configChange,
128+
Check: resource.ComposeTestCheckFunc(
129+
resource.TestCheckResourceAttr("postgresql_script.test", "commands.0", "SELECT 2;"),
130+
),
131+
},
132+
},
133+
})
134+
}
135+
136+
func TestAccPostgresqlScript_fail(t *testing.T) {
137+
config := `
138+
resource "postgresql_script" "invalid" {
139+
commands = [
140+
"SLC FROM nowhere;"
141+
]
142+
tries = 2
143+
backoff_delay = 2
144+
}
145+
`
146+
147+
resource.Test(t, resource.TestCase{
148+
PreCheck: func() { testAccPreCheck(t) },
149+
Providers: testAccProviders,
150+
Steps: []resource.TestStep{
151+
{
152+
Config: config,
153+
ExpectError: regexp.MustCompile("syntax error"),
154+
},
155+
},
156+
})
157+
}
158+
159+
func TestAccPostgresqlScript_failMultiple(t *testing.T) {
160+
config := `
161+
resource "postgresql_script" "invalid" {
162+
commands = [
163+
"BEGIN",
164+
"SLC FROM nowhere;",
165+
"COMMIT"
166+
]
167+
tries = 2
168+
backoff_delay = 2
169+
}
170+
`
171+
172+
resource.Test(t, resource.TestCase{
173+
PreCheck: func() { testAccPreCheck(t) },
174+
Providers: testAccProviders,
175+
Steps: []resource.TestStep{
176+
{
177+
Config: config,
178+
ExpectError: regexp.MustCompile("syntax error"),
179+
},
180+
},
181+
})
182+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
layout: "postgresql"
3+
page_title: "PostgreSQL: postgresql_script"
4+
sidebar_current: "docs-postgresql-resource-postgresql_script"
5+
description: |-
6+
Execute a SQL script
7+
---
8+
9+
# postgresql\_script
10+
11+
The ``postgresql_script`` execute a script given as parameter. This script will be executed each time it changes.
12+
13+
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.
14+
15+
## Usage
16+
17+
```hcl
18+
resource "postgresql_script" "foo" {
19+
commands = [
20+
"command 1",
21+
"command 2"
22+
]
23+
tries = 1
24+
backoff_delay = 1
25+
}
26+
```
27+
28+
## Argument Reference
29+
30+
* `commands` - (Required) An array of commands to execute, one by one.
31+
* `tries` - (Optional) Number of tries of a command before raising an error.
32+
* `backoff_delay` - (Optional) In case of failure, time in second to wait before a retry.
33+
34+
## Examples
35+
36+
Revoke default accesses for public schema:
37+
38+
```hcl
39+
resource "postgresql_script" "foo" {
40+
commands = [
41+
"BEBIN",
42+
"SELECT * FROM table",
43+
"COMMIT"
44+
]
45+
tries = 3
46+
backoff_delay = 1
47+
}
48+
```

website/postgresql.erb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@
5555
<li<%= sidebar_current("docs-postgresql-resource-postgresql_user_mapping") %>>
5656
<a href="/docs/providers/postgresql/r/postgresql_user_mapping.html">postgresql_user_mapping</a>
5757
</li>
58+
<li<%= sidebar_current("docs-postgresql-resource-postgresql_script") %>>
59+
<a href="/docs/providers/postgresql/r/postgresql_script.html">postgresql_script</a>
60+
</li>
5861
</ul>
5962
</li>
6063

0 commit comments

Comments
 (0)