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  }