From 12a0a21c0b2f0cc6b77bf3dae9c37c51c3dcaf2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Mon, 12 Oct 2020 22:46:57 +0200 Subject: [PATCH] Add skip_table_creation and skip_index_creation options to the pg backend Closes https://github.com/hashicorp/terraform/issues/25708 --- backend/remote-state/pg/backend.go | 44 +++++-- backend/remote-state/pg/backend_test.go | 152 ++++++++++++++++++------ website/docs/backends/types/pg.html.md | 2 + 3 files changed, 150 insertions(+), 48 deletions(-) diff --git a/backend/remote-state/pg/backend.go b/backend/remote-state/pg/backend.go index e31f99712..0191176a9 100644 --- a/backend/remote-state/pg/backend.go +++ b/backend/remote-state/pg/backend.go @@ -19,25 +19,37 @@ const ( func New() backend.Backend { s := &schema.Backend{ Schema: map[string]*schema.Schema{ - "conn_str": &schema.Schema{ + "conn_str": { Type: schema.TypeString, Required: true, Description: "Postgres connection string; a `postgres://` URL", }, - "schema_name": &schema.Schema{ + "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": &schema.Schema{ + "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", + }, }, } @@ -91,17 +103,23 @@ func (b *Backend) configure(ctx context.Context) error { } } } - query = `CREATE TABLE IF NOT EXISTS %s.%s ( - id SERIAL PRIMARY KEY, - name TEXT, - data TEXT - )` - if _, err := db.Exec(fmt.Sprintf(query, b.schemaName, statesTableName)); err != nil { - return err + + if !data.Get("skip_table_creation").(bool) { + query = `CREATE TABLE IF NOT EXISTS %s.%s ( + id SERIAL PRIMARY KEY, + name TEXT, + data TEXT + )` + if _, err := db.Exec(fmt.Sprintf(query, b.schemaName, statesTableName)); err != nil { + return err + } } - 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 + + 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. diff --git a/backend/remote-state/pg/backend_test.go b/backend/remote-state/pg/backend_test.go index 1b500e132..21a4272ad 100644 --- a/backend/remote-state/pg/backend_test.go +++ b/backend/remote-state/pg/backend_test.go @@ -79,49 +79,131 @@ func TestBackendConfig(t *testing.T) { backend.TestBackendStates(t, b) } -func TestBackendConfigSkipSchema(t *testing.T) { +func TestBackendConfigSkipOptions(t *testing.T) { testACC(t) connStr := getDatabaseUrl() - schemaName := fmt.Sprintf("terraform_%s", t.Name()) - config := backend.TestWrapConfig(map[string]interface{}{ - "conn_str": connStr, - "schema_name": schemaName, - "skip_schema_creation": true, - }) - schemaName = pq.QuoteIdentifier(schemaName) - db, err := sql.Open("postgres", connStr) - if err != nil { - t.Fatal(err) + + testCases := []struct { + Name string + SkipSchemaCreation bool + SkipTableCreation bool + SkipIndexCreation bool + Setup func(t *testing.T, db *sql.DB, schemaName string) + }{ + { + Name: "skip_schema_creation", + SkipSchemaCreation: true, + Setup: func(t *testing.T, db *sql.DB, schemaName string) { + // create the schema as a prerequisites + _, err := db.Query(fmt.Sprintf(`CREATE SCHEMA IF NOT EXISTS %s`, schemaName)) + if err != nil { + t.Fatal(err) + } + }, + }, + { + Name: "skip_table_creation", + SkipTableCreation: true, + Setup: func(t *testing.T, db *sql.DB, schemaName string) { + // since the table needs to be already created the schema must be too + _, err := db.Query(fmt.Sprintf(`CREATE SCHEMA %s`, schemaName)) + if err != nil { + t.Fatal(err) + } + _, err = db.Query(fmt.Sprintf(`CREATE TABLE %s.%s ( + id SERIAL PRIMARY KEY, + name TEXT, + data TEXT + )`, schemaName, statesTableName)) + if err != nil { + t.Fatal(err) + } + }, + }, + { + Name: "skip_index_creation", + SkipIndexCreation: true, + Setup: func(t *testing.T, db *sql.DB, schemaName string) { + // Everything need to exists for the index to be created + _, err := db.Query(fmt.Sprintf(`CREATE SCHEMA %s`, schemaName)) + if err != nil { + t.Fatal(err) + } + _, err = db.Query(fmt.Sprintf(`CREATE TABLE %s.%s ( + id SERIAL PRIMARY KEY, + name TEXT, + data TEXT + )`, schemaName, statesTableName)) + if err != nil { + t.Fatal(err) + } + _, err = db.Exec(fmt.Sprintf(`CREATE UNIQUE INDEX IF NOT EXISTS %s ON %s.%s (name)`, statesIndexName, schemaName, statesTableName)) + if err != nil { + t.Fatal(err) + } + }, + }, } - // create the schema as a prerequisites - db.Query(fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", schemaName)) - defer db.Query(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schemaName)) + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + schemaName := tc.Name - b := backend.TestBackendConfig(t, New(), config).(*Backend) + config := backend.TestWrapConfig(map[string]interface{}{ + "conn_str": connStr, + "schema_name": schemaName, + "skip_schema_creation": tc.SkipSchemaCreation, + "skip_table_creation": tc.SkipTableCreation, + "skip_index_creation": tc.SkipIndexCreation, + }) + schemaName = pq.QuoteIdentifier(schemaName) + db, err := sql.Open("postgres", connStr) + if err != nil { + t.Fatal(err) + } - if b == nil { - t.Fatal("Backend could not be configured") + tc.Setup(t, db, schemaName) + defer db.Query(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schemaName)) + + b := backend.TestBackendConfig(t, New(), config).(*Backend) + + if b == nil { + t.Fatal("Backend could not be configured") + } + + // Make sure everything has been created + + // This tests that both the schema and the table have been created + _, err = b.db.Query(fmt.Sprintf("SELECT name, data FROM %s.%s LIMIT 1", schemaName, statesTableName)) + if err != nil { + t.Fatal(err) + } + // Make sure that the index exists + query := `select count(*) from pg_indexes where schemaname=$1 and tablename=$2 and indexname=$3;` + var count int + if err := b.db.QueryRow(query, tc.Name, statesTableName, statesIndexName).Scan(&count); err != nil { + t.Fatal(err) + } + if count != 1 { + t.Fatalf("The index has not been created (%d)", count) + } + + _, err = b.StateMgr(backend.DefaultStateName) + if err != nil { + t.Fatal(err) + } + + s, err := b.StateMgr(backend.DefaultStateName) + if err != nil { + t.Fatal(err) + } + c := s.(*remote.State).Client.(*RemoteClient) + if c.Name != backend.DefaultStateName { + t.Fatal("RemoteClient name is not configured") + } + }) } - _, err = b.db.Query(fmt.Sprintf("SELECT name, data FROM %s.%s LIMIT 1", schemaName, statesTableName)) - if err != nil { - t.Fatal(err) - } - - _, err = b.StateMgr(backend.DefaultStateName) - if err != nil { - t.Fatal(err) - } - - s, err := b.StateMgr(backend.DefaultStateName) - if err != nil { - t.Fatal(err) - } - c := s.(*remote.State).Client.(*RemoteClient) - if c.Name != backend.DefaultStateName { - t.Fatal("RemoteClient name is not configured") - } } func TestBackendStates(t *testing.T) { diff --git a/website/docs/backends/types/pg.html.md b/website/docs/backends/types/pg.html.md index 9c837accd..7dd60e582 100644 --- a/website/docs/backends/types/pg.html.md +++ b/website/docs/backends/types/pg.html.md @@ -72,6 +72,8 @@ The following configuration options or environment variables are supported: * `conn_str` - (Required) Postgres connection string; a `postgres://` URL * `schema_name` - Name of the automatically-managed Postgres schema, default `terraform_remote_state`. * `skip_schema_creation` - If set to `true`, the Postgres schema must already exist. Terraform won't try to create the schema. Useful when the Postgres user does not have "create schema" permission on the database. + * `skip_table_creation` - If set to `true`, the Postgres table must already exist. Terraform won't try to create the table. Useful when the Postgres user does not have "create table" permission on the database. + * `skip_index_creation` - If set to `true`, the Postgres index must already exist. Terraform won't try to create the index. Useful when the Postgres user does not have "create index" permission on the database. ## Technical Design