2018-10-10 23:42:57 +02:00
|
|
|
package pg
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
|
|
|
"fmt"
|
|
|
|
|
2021-05-17 17:42:17 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/backend"
|
2020-11-18 16:07:30 +01:00
|
|
|
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
|
2020-10-03 18:02:13 +02:00
|
|
|
"github.com/lib/pq"
|
2018-10-10 23:42:57 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
statesTableName = "states"
|
2018-10-17 19:31:12 +02:00
|
|
|
statesIndexName = "states_by_name"
|
2018-10-10 23:42:57 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// New creates a new backend for Postgres remote state.
|
|
|
|
func New() backend.Backend {
|
|
|
|
s := &schema.Backend{
|
|
|
|
Schema: map[string]*schema.Schema{
|
2020-10-12 22:46:57 +02:00
|
|
|
"conn_str": {
|
2018-10-10 23:42:57 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
Description: "Postgres connection string; a `postgres://` URL",
|
|
|
|
},
|
|
|
|
|
2020-10-12 22:46:57 +02:00
|
|
|
"schema_name": {
|
2018-10-10 23:42:57 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
2019-02-26 01:05:53 +01:00
|
|
|
Description: "Name of the automatically managed Postgres schema to store state",
|
2019-02-26 21:59:02 +01:00
|
|
|
Default: "terraform_remote_state",
|
2018-10-10 23:42:57 +02:00
|
|
|
},
|
2019-08-27 17:14:32 +02:00
|
|
|
|
2020-10-12 22:46:57 +02:00
|
|
|
"skip_schema_creation": {
|
2019-08-27 17:14:32 +02:00
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Description: "If set to `true`, Terraform won't try to create the Postgres schema",
|
|
|
|
Default: false,
|
|
|
|
},
|
2020-10-12 22:46:57 +02:00
|
|
|
|
|
|
|
"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",
|
|
|
|
},
|
2018-10-10 23:42:57 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2020-10-03 18:02:13 +02:00
|
|
|
b.schemaName = pq.QuoteIdentifier(data.Get("schema_name").(string))
|
2018-10-10 23:42:57 +02:00
|
|
|
|
|
|
|
db, err := sql.Open("postgres", b.connStr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare database schema, tables, & indexes.
|
|
|
|
var query string
|
2019-08-27 17:14:32 +02:00
|
|
|
|
|
|
|
if !data.Get("skip_schema_creation").(bool) {
|
|
|
|
// list all schemas to see if it exists
|
|
|
|
var count int
|
2020-10-03 18:02:13 +02:00
|
|
|
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 {
|
2019-08-27 17:14:32 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2018-10-10 23:42:57 +02:00
|
|
|
}
|
2020-10-12 22:46:57 +02:00
|
|
|
|
|
|
|
if !data.Get("skip_table_creation").(bool) {
|
2020-11-15 14:54:57 +01:00
|
|
|
if _, err := db.Exec("CREATE SEQUENCE IF NOT EXISTS public.global_states_id_seq AS bigint"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-12 22:46:57 +02:00
|
|
|
query = `CREATE TABLE IF NOT EXISTS %s.%s (
|
2020-11-15 14:54:57 +01:00
|
|
|
id bigint NOT NULL DEFAULT nextval('public.global_states_id_seq') PRIMARY KEY,
|
|
|
|
name text,
|
|
|
|
data text
|
2020-10-12 22:46:57 +02:00
|
|
|
)`
|
|
|
|
if _, err := db.Exec(fmt.Sprintf(query, b.schemaName, statesTableName)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-10-10 23:42:57 +02:00
|
|
|
}
|
2020-10-12 22:46:57 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2018-10-10 23:42:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Assign db after its schema is prepared.
|
|
|
|
b.db = db
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|