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