From 1f603b1a7f8d80e404a83b0b81092476f3dae152 Mon Sep 17 00:00:00 2001 From: janaurka <5986148+janaurka@users.noreply.github.com> Date: Thu, 6 Apr 2023 00:42:44 +0200 Subject: [PATCH] backend/pg: Accept connection string in PGDATABASE environment variable --- internal/backend/remote-state/pg/backend.go | 1 + .../backend/remote-state/pg/backend_test.go | 140 +++++++++++++----- .../docs/language/settings/backends/pg.mdx | 2 +- 3 files changed, 105 insertions(+), 38 deletions(-) diff --git a/internal/backend/remote-state/pg/backend.go b/internal/backend/remote-state/pg/backend.go index cdcfb3a6e4..b54c13364a 100644 --- a/internal/backend/remote-state/pg/backend.go +++ b/internal/backend/remote-state/pg/backend.go @@ -23,6 +23,7 @@ func New() backend.Backend { Type: schema.TypeString, Required: true, Description: "Postgres connection string; a `postgres://` URL", + DefaultFunc: schema.EnvDefaultFunc("PGDATABASE", nil), }, "schema_name": { diff --git a/internal/backend/remote-state/pg/backend_test.go b/internal/backend/remote-state/pg/backend_test.go index 064c001f5b..69ebfa40ab 100644 --- a/internal/backend/remote-state/pg/backend_test.go +++ b/internal/backend/remote-state/pg/backend_test.go @@ -7,13 +7,15 @@ import ( "database/sql" "fmt" "os" + "strings" "testing" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/states/remote" "github.com/hashicorp/terraform/internal/states/statemgr" + "github.com/hashicorp/terraform/internal/tfdiags" "github.com/lib/pq" - _ "github.com/lib/pq" ) // Function to skip a test unless in ACCeptance test mode. @@ -38,46 +40,110 @@ func TestBackend_impl(t *testing.T) { func TestBackendConfig(t *testing.T) { testACC(t) connStr := getDatabaseUrl() - schemaName := pq.QuoteIdentifier(fmt.Sprintf("terraform_%s", t.Name())) - config := backend.TestWrapConfig(map[string]interface{}{ - "conn_str": connStr, - "schema_name": schemaName, - }) - schemaName = pq.QuoteIdentifier(schemaName) - - dbCleaner, err := sql.Open("postgres", connStr) - if err != nil { - t.Fatal(err) - } - defer dbCleaner.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") + testCases := []struct { + Name string + EnvVars map[string]string + Config map[string]interface{} + ExpectError string + }{ + { + Name: "valid-config", + Config: map[string]interface{}{ + "conn_str": connStr, + "schema_name": fmt.Sprintf("terraform_%s", t.Name()), + }, + }, + { + Name: "missing-conn-str", + Config: map[string]interface{}{ + "schema_name": fmt.Sprintf("terraform_%s", t.Name()), + }, + ExpectError: `The attribute "conn_str" is required, but no definition was found.`, + }, + { + Name: "conn-str-env-var", + EnvVars: map[string]string{ + "PGDATABASE": connStr, + }, + Config: map[string]interface{}{ + "schema_name": fmt.Sprintf("terraform_%s", t.Name()), + }, + }, } - _, err = b.db.Query(fmt.Sprintf("SELECT name, data FROM %s.%s LIMIT 1", schemaName, statesTableName)) - if err != nil { - t.Fatal(err) + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + for k, v := range tc.EnvVars { + t.Setenv(k, v) + } + + config := backend.TestWrapConfig(tc.Config) + schemaName := pq.QuoteIdentifier(tc.Config["schema_name"].(string)) + + dbCleaner, err := sql.Open("postgres", connStr) + if err != nil { + t.Fatal(err) + } + defer dbCleaner.Query(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schemaName)) + + var diags tfdiags.Diagnostics + b := New().(*Backend) + schema := b.ConfigSchema() + spec := schema.DecoderSpec() + obj, decDiags := hcldec.Decode(config, spec, nil) + diags = diags.Append(decDiags) + + newObj, valDiags := b.PrepareConfig(obj) + diags = diags.Append(valDiags.InConfigBody(config, "")) + + if tc.ExpectError != "" { + if !diags.HasErrors() { + t.Fatal("error expected but got none") + } + if !strings.Contains(diags.ErrWithWarnings().Error(), tc.ExpectError) { + t.Fatalf("failed to find %q in %s", tc.ExpectError, diags.ErrWithWarnings()) + } + return + } else if diags.HasErrors() { + t.Fatal(diags.ErrWithWarnings()) + } + + obj = newObj + + confDiags := b.Configure(obj) + if len(confDiags) != 0 { + confDiags = confDiags.InConfigBody(config, "") + t.Fatal(confDiags.ErrWithWarnings()) + } + + 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") + } + + backend.TestBackendStates(t, b) + }) } - _, 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") - } - - backend.TestBackendStates(t, b) } func TestBackendConfigSkipOptions(t *testing.T) { @@ -243,7 +309,7 @@ func TestBackendStates(t *testing.T) { if err != nil { t.Fatal(err) } - defer dbCleaner.Query("DROP SCHEMA IF EXISTS %s CASCADE", pq.QuoteIdentifier(schemaName)) + defer dbCleaner.Query(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", pq.QuoteIdentifier(schemaName))) config := backend.TestWrapConfig(map[string]interface{}{ "conn_str": connStr, diff --git a/website/docs/language/settings/backends/pg.mdx b/website/docs/language/settings/backends/pg.mdx index fe2acb6664..a95c3ba478 100644 --- a/website/docs/language/settings/backends/pg.mdx +++ b/website/docs/language/settings/backends/pg.mdx @@ -68,7 +68,7 @@ data "terraform_remote_state" "network" { The following configuration options or environment variables are supported: -- `conn_str` - (Required) Postgres connection string; a `postgres://` URL +- `conn_str` - (Required) Postgres connection string; a `postgres://` URL. `conn_str` can also be set using the `PGDATABASE` environment variable. - `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, this is useful when it has already been created by a database administrator. - `skip_table_creation` - If set to `true`, the Postgres table must already exist. Terraform won't try to create the table, this is useful when it has already been created by a database administrator.