diff --git a/backend/remote-state/pg/backend.go b/backend/remote-state/pg/backend.go index 34e0a46c3..71540fe20 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 Postgres 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 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 + } + } } 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 7479c9acd..17edd6a52 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 d94b3c4d4..76ceaa34c 100644 --- a/website/docs/backends/types/pg.html.md +++ b/website/docs/backends/types/pg.html.md @@ -71,7 +71,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. + ## Technical Design Postgres version 9.5 or newer is required to support advisory locks and the "ON CONFLICT" upsert syntax.