From a37949d6abebb7bc9cf0e33f1d4c04c33de8f777 Mon Sep 17 00:00:00 2001 From: yann degat Date: Wed, 5 Jun 2019 15:03:57 +0200 Subject: [PATCH] remote-state/pg: skip schema creation instruction if exists * add `skip_schema_creation` option * add sanity check to avoid situations where postgres users hasn't been granted the "CREATE SCHEMA" right closes #21604 Signed-off-by: yann degat --- backend/remote-state/pg/backend.go | 29 ++++++++++++++-- backend/remote-state/pg/backend_test.go | 44 +++++++++++++++++++++++++ website/docs/backends/types/pg.html.md | 1 + 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/backend/remote-state/pg/backend.go b/backend/remote-state/pg/backend.go index 34e0a46c3312..e6dabfbbf72f 100644 --- a/backend/remote-state/pg/backend.go +++ b/backend/remote-state/pg/backend.go @@ -31,6 +31,13 @@ func New() backend.Backend { Description: "Name of the automatically managed Postgres schema to store state", Default: "terraform_remote_state", }, + + "skip_schema_creation": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Description: "If set to `true`, terraform won't try to create the schema", + Default: false, + }, }, } @@ -64,9 +71,25 @@ func (b *Backend) configure(ctx context.Context) error { // Prepare database schema, tables, & indexes. var query string - query = `CREATE SCHEMA IF NOT EXISTS %s` - if _, err := db.Exec(fmt.Sprintf(query, b.schemaName)); err != nil { - return err + + 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 lower(schema_name) = lower('%s')` + if err := db.QueryRow(fmt.Sprintf(query, b.schemaName)).Scan(&count); err != nil { + return err + } + + // skip schema creation if schema already exists + // `CREATE SCHEMA IF NOT EXISTS` is to avoid if ever + // a user hasn't been granted the `CREATE SCHEMA` right + if count < 1 { + // tries to create the schema + query = `CREATE SCHEMA %s` + if _, err := db.Exec(fmt.Sprintf(query, b.schemaName)); err != nil { + return err + } + } } query = `CREATE TABLE IF NOT EXISTS %s.%s ( id SERIAL PRIMARY KEY, diff --git a/backend/remote-state/pg/backend_test.go b/backend/remote-state/pg/backend_test.go index 7479c9acd0a6..17edd6a52d22 100644 --- a/backend/remote-state/pg/backend_test.go +++ b/backend/remote-state/pg/backend_test.go @@ -73,6 +73,50 @@ func TestBackendConfig(t *testing.T) { } } +func TestBackendConfigSkipSchema(t *testing.T) { + testACC(t) + connStr := getDatabaseUrl() + schemaName := fmt.Sprintf("terraform_%s", t.Name()) + db, err := sql.Open("postgres", connStr) + 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)) + + config := backend.TestWrapConfig(map[string]interface{}{ + "conn_str": connStr, + "schema_name": schemaName, + "skip_schema_creation": true, + }) + b := backend.TestBackendConfig(t, New(), config).(*Backend) + + if b == nil { + t.Fatal("Backend could not be 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) { testACC(t) connStr := getDatabaseUrl() diff --git a/website/docs/backends/types/pg.html.md b/website/docs/backends/types/pg.html.md index d94b3c4d4643..6a8417dee8df 100644 --- a/website/docs/backends/types/pg.html.md +++ b/website/docs/backends/types/pg.html.md @@ -71,6 +71,7 @@ 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`, terraform won't try to create the schema. ## Technical Design