-
Notifications
You must be signed in to change notification settings - Fork 9.5k
/
backend.go
133 lines (111 loc) · 3.39 KB
/
backend.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package pg
import (
"context"
"database/sql"
"fmt"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
"github.com/lib/pq"
)
const (
statesTableName = "states"
statesIndexName = "states_by_name"
)
// New creates a new backend for Postgres remote state.
func New() backend.Backend {
s := &schema.Backend{
Schema: map[string]*schema.Schema{
"conn_str": {
Type: schema.TypeString,
Required: true,
Description: "Postgres connection string; a `postgres://` URL",
},
"schema_name": {
Type: schema.TypeString,
Optional: true,
Description: "Name of the automatically managed Postgres schema to store state",
Default: "terraform_remote_state",
},
"skip_schema_creation": {
Type: schema.TypeBool,
Optional: true,
Description: "If set to `true`, Terraform won't try to create the Postgres schema",
Default: false,
},
"skip_table_creation": {
Type: schema.TypeBool,
Optional: true,
Description: "If set to `true`, Terraform won't try to create the Postgres table",
},
"skip_index_creation": {
Type: schema.TypeBool,
Optional: true,
Description: "If set to `true`, Terraform won't try to create the Postgres index",
},
},
}
result := &Backend{Backend: s}
result.Backend.ConfigureFunc = result.configure
return result
}
type Backend struct {
*schema.Backend
// The fields below are set from configure
db *sql.DB
configData *schema.ResourceData
connStr string
schemaName string
}
func (b *Backend) configure(ctx context.Context) error {
// Grab the resource data
b.configData = schema.FromContextBackendConfig(ctx)
data := b.configData
b.connStr = data.Get("conn_str").(string)
b.schemaName = pq.QuoteIdentifier(data.Get("schema_name").(string))
db, err := sql.Open("postgres", b.connStr)
if err != nil {
return err
}
// Prepare database schema, tables, & indexes.
var query string
if !data.Get("skip_schema_creation").(bool) {
// list all schemas to see if it exists
var count int
query = `select count(1) from information_schema.schemata where schema_name = $1`
if err := db.QueryRow(query, data.Get("schema_name").(string)).Scan(&count); err != nil {
return err
}
// skip schema creation if schema already exists
// `CREATE SCHEMA IF NOT EXISTS` is to be avoided if ever
// a user hasn't been granted the `CREATE SCHEMA` privilege
if count < 1 {
// tries to create the schema
query = `CREATE SCHEMA IF NOT EXISTS %s`
if _, err := db.Exec(fmt.Sprintf(query, b.schemaName)); err != nil {
return err
}
}
}
if !data.Get("skip_table_creation").(bool) {
if _, err := db.Exec("CREATE SEQUENCE IF NOT EXISTS public.global_states_id_seq AS bigint"); err != nil {
return err
}
query = `CREATE TABLE IF NOT EXISTS %s.%s (
id bigint NOT NULL DEFAULT nextval('public.global_states_id_seq') PRIMARY KEY,
name text UNIQUE,
data text
)`
if _, err := db.Exec(fmt.Sprintf(query, b.schemaName, statesTableName)); err != nil {
return err
}
}
if !data.Get("skip_index_creation").(bool) {
query = `CREATE UNIQUE INDEX IF NOT EXISTS %s ON %s.%s (name)`
if _, err := db.Exec(fmt.Sprintf(query, statesIndexName, b.schemaName, statesTableName)); err != nil {
return err
}
}
// Assign db after its schema is prepared.
b.db = db
return nil
}