mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Improve environment variable support for the pg backend (#33045)
* Improve environment variable support for the pg backend This patch does two things: - it adds environment variable support to the parameters that did not have it (and uses `PG_CONN_STR` instead of `PGDATABASE` which is actually more appropriate to match the behavior of other PostgreSQL utilities) - better documents how to give the connection parameters as environment variables for the ones that were already supported based on the recommendation of @bsouth00 I will prepare a backport of the documentation part of this once it is merged. Closes https://github.com/hashicorp/terraform/issues/33024 * Remove global variable in test of the PG backend
This commit is contained in:
parent
7e2e834aff
commit
af571b2642
@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/internal/backend"
|
"github.com/hashicorp/terraform/internal/backend"
|
||||||
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
|
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
|
||||||
@ -15,41 +17,53 @@ const (
|
|||||||
statesIndexName = "states_by_name"
|
statesIndexName = "states_by_name"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func defaultBoolFunc(k string, dv bool) schema.SchemaDefaultFunc {
|
||||||
|
return func() (interface{}, error) {
|
||||||
|
if v := os.Getenv(k); v != "" {
|
||||||
|
return strconv.ParseBool(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dv, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// New creates a new backend for Postgres remote state.
|
// New creates a new backend for Postgres remote state.
|
||||||
func New() backend.Backend {
|
func New() backend.Backend {
|
||||||
s := &schema.Backend{
|
s := &schema.Backend{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"conn_str": {
|
"conn_str": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Optional: true,
|
||||||
Description: "Postgres connection string; a `postgres://` URL",
|
Description: "Postgres connection string; a `postgres://` URL",
|
||||||
DefaultFunc: schema.EnvDefaultFunc("PGDATABASE", nil),
|
DefaultFunc: schema.EnvDefaultFunc("PG_CONN_STR", nil),
|
||||||
},
|
},
|
||||||
|
|
||||||
"schema_name": {
|
"schema_name": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "Name of the automatically managed Postgres schema to store state",
|
Description: "Name of the automatically managed Postgres schema to store state",
|
||||||
Default: "terraform_remote_state",
|
DefaultFunc: schema.EnvDefaultFunc("PG_SCHEMA_NAME", "terraform_remote_state"),
|
||||||
},
|
},
|
||||||
|
|
||||||
"skip_schema_creation": {
|
"skip_schema_creation": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "If set to `true`, Terraform won't try to create the Postgres schema",
|
Description: "If set to `true`, Terraform won't try to create the Postgres schema",
|
||||||
Default: false,
|
DefaultFunc: defaultBoolFunc("PG_SKIP_SCHEMA_CREATION", false),
|
||||||
},
|
},
|
||||||
|
|
||||||
"skip_table_creation": {
|
"skip_table_creation": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "If set to `true`, Terraform won't try to create the Postgres table",
|
Description: "If set to `true`, Terraform won't try to create the Postgres table",
|
||||||
|
DefaultFunc: defaultBoolFunc("PG_SKIP_TABLE_CREATION", false),
|
||||||
},
|
},
|
||||||
|
|
||||||
"skip_index_creation": {
|
"skip_index_creation": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "If set to `true`, Terraform won't try to create the Postgres index",
|
Description: "If set to `true`, Terraform won't try to create the Postgres index",
|
||||||
|
DefaultFunc: defaultBoolFunc("PG_SKIP_INDEX_CREATION", false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ package pg
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -22,15 +23,22 @@ import (
|
|||||||
//
|
//
|
||||||
// A running Postgres server identified by env variable
|
// A running Postgres server identified by env variable
|
||||||
// DATABASE_URL is required for acceptance tests.
|
// DATABASE_URL is required for acceptance tests.
|
||||||
func testACC(t *testing.T) {
|
func testACC(t *testing.T) string {
|
||||||
skip := os.Getenv("TF_ACC") == ""
|
skip := os.Getenv("TF_ACC") == ""
|
||||||
if skip {
|
if skip {
|
||||||
t.Log("pg backend tests require setting TF_ACC")
|
t.Log("pg backend tests require setting TF_ACC")
|
||||||
t.Skip()
|
t.Skip()
|
||||||
}
|
}
|
||||||
if os.Getenv("DATABASE_URL") == "" {
|
databaseUrl, found := os.LookupEnv("DATABASE_URL")
|
||||||
os.Setenv("DATABASE_URL", "postgres://localhost/terraform_backend_pg_test?sslmode=disable")
|
if !found {
|
||||||
|
databaseUrl = "postgres://localhost/terraform_backend_pg_test?sslmode=disable"
|
||||||
|
os.Setenv("DATABASE_URL", databaseUrl)
|
||||||
}
|
}
|
||||||
|
u, err := url.Parse(databaseUrl)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return u.Path[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackend_impl(t *testing.T) {
|
func TestBackend_impl(t *testing.T) {
|
||||||
@ -38,14 +46,15 @@ func TestBackend_impl(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBackendConfig(t *testing.T) {
|
func TestBackendConfig(t *testing.T) {
|
||||||
testACC(t)
|
databaseName := testACC(t)
|
||||||
connStr := getDatabaseUrl()
|
connStr := getDatabaseUrl()
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
Name string
|
Name string
|
||||||
EnvVars map[string]string
|
EnvVars map[string]string
|
||||||
Config map[string]interface{}
|
Config map[string]interface{}
|
||||||
ExpectError string
|
ExpectConfigurationError string
|
||||||
|
ExpectConnectionError string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Name: "valid-config",
|
Name: "valid-config",
|
||||||
@ -55,21 +64,71 @@ func TestBackendConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "missing-conn-str",
|
Name: "missing-conn_str-defaults-to-localhost",
|
||||||
|
EnvVars: map[string]string{
|
||||||
|
"PGSSLMODE": "disable",
|
||||||
|
"PGDATABASE": databaseName,
|
||||||
|
},
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"schema_name": fmt.Sprintf("terraform_%s", t.Name()),
|
"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",
|
Name: "conn-str-env-var",
|
||||||
EnvVars: map[string]string{
|
EnvVars: map[string]string{
|
||||||
"PGDATABASE": connStr,
|
"PG_CONN_STR": connStr,
|
||||||
},
|
},
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"schema_name": fmt.Sprintf("terraform_%s", t.Name()),
|
"schema_name": fmt.Sprintf("terraform_%s", t.Name()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "setting-credentials-using-env-vars",
|
||||||
|
EnvVars: map[string]string{
|
||||||
|
"PGUSER": "baduser",
|
||||||
|
"PGPASSWORD": "badpassword",
|
||||||
|
},
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"conn_str": connStr,
|
||||||
|
"schema_name": fmt.Sprintf("terraform_%s", t.Name()),
|
||||||
|
},
|
||||||
|
ExpectConnectionError: `role "baduser" does not exist`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "host-in-env-vars",
|
||||||
|
EnvVars: map[string]string{
|
||||||
|
"PGHOST": "hostthatdoesnotexist",
|
||||||
|
},
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"schema_name": fmt.Sprintf("terraform_%s", t.Name()),
|
||||||
|
},
|
||||||
|
ExpectConnectionError: `no such host`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "boolean-env-vars",
|
||||||
|
EnvVars: map[string]string{
|
||||||
|
"PGSSLMODE": "disable",
|
||||||
|
"PG_SKIP_SCHEMA_CREATION": "f",
|
||||||
|
"PG_SKIP_TABLE_CREATION": "f",
|
||||||
|
"PG_SKIP_INDEX_CREATION": "f",
|
||||||
|
"PGDATABASE": databaseName,
|
||||||
|
},
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"schema_name": fmt.Sprintf("terraform_%s", t.Name()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "wrong-boolean-env-vars",
|
||||||
|
EnvVars: map[string]string{
|
||||||
|
"PGSSLMODE": "disable",
|
||||||
|
"PG_SKIP_SCHEMA_CREATION": "foo",
|
||||||
|
"PGDATABASE": databaseName,
|
||||||
|
},
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"schema_name": fmt.Sprintf("terraform_%s", t.Name()),
|
||||||
|
},
|
||||||
|
ExpectConfigurationError: `error getting default for "skip_schema_creation"`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@ -97,12 +156,12 @@ func TestBackendConfig(t *testing.T) {
|
|||||||
newObj, valDiags := b.PrepareConfig(obj)
|
newObj, valDiags := b.PrepareConfig(obj)
|
||||||
diags = diags.Append(valDiags.InConfigBody(config, ""))
|
diags = diags.Append(valDiags.InConfigBody(config, ""))
|
||||||
|
|
||||||
if tc.ExpectError != "" {
|
if tc.ExpectConfigurationError != "" {
|
||||||
if !diags.HasErrors() {
|
if !diags.HasErrors() {
|
||||||
t.Fatal("error expected but got none")
|
t.Fatal("error expected but got none")
|
||||||
}
|
}
|
||||||
if !strings.Contains(diags.ErrWithWarnings().Error(), tc.ExpectError) {
|
if !strings.Contains(diags.ErrWithWarnings().Error(), tc.ExpectConfigurationError) {
|
||||||
t.Fatalf("failed to find %q in %s", tc.ExpectError, diags.ErrWithWarnings())
|
t.Fatalf("failed to find %q in %s", tc.ExpectConfigurationError, diags.ErrWithWarnings())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} else if diags.HasErrors() {
|
} else if diags.HasErrors() {
|
||||||
@ -112,7 +171,16 @@ func TestBackendConfig(t *testing.T) {
|
|||||||
obj = newObj
|
obj = newObj
|
||||||
|
|
||||||
confDiags := b.Configure(obj)
|
confDiags := b.Configure(obj)
|
||||||
if len(confDiags) != 0 {
|
if tc.ExpectConnectionError != "" {
|
||||||
|
err := confDiags.InConfigBody(config, "").ErrWithWarnings()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("error expected but got none")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tc.ExpectConnectionError) {
|
||||||
|
t.Fatalf("failed to find %q in %s", tc.ExpectConnectionError, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else if len(confDiags) != 0 {
|
||||||
confDiags = confDiags.InConfigBody(config, "")
|
confDiags = confDiags.InConfigBody(config, "")
|
||||||
t.Fatal(confDiags.ErrWithWarnings())
|
t.Fatal(confDiags.ErrWithWarnings())
|
||||||
}
|
}
|
||||||
|
@ -27,9 +27,17 @@ createdb terraform_backend
|
|||||||
|
|
||||||
This `createdb` command is found in [Postgres client applications](https://www.postgresql.org/docs/10/reference-client.html) which are installed along with the database server.
|
This `createdb` command is found in [Postgres client applications](https://www.postgresql.org/docs/10/reference-client.html) which are installed along with the database server.
|
||||||
|
|
||||||
We recommend using a
|
|
||||||
[partial configuration](/terraform/language/settings/backends/configuration#partial-configuration)
|
### Using environment variables
|
||||||
for the `conn_str` variable, because it typically contains access credentials that should not be committed to source control:
|
|
||||||
|
We recommend using environment variables to configure the `pg` backend in order
|
||||||
|
not to have sensitive credentials written to disk and committed to source
|
||||||
|
control.
|
||||||
|
|
||||||
|
The `pg` backend supports the standard [`libpq` environment variables](https://www.postgresql.org/docs/current/libpq-envars.html).
|
||||||
|
|
||||||
|
The backend can be configured either by giving the whole configuration as an
|
||||||
|
environment variable:
|
||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
terraform {
|
terraform {
|
||||||
@ -37,16 +45,26 @@ terraform {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, set the credentials when initializing the configuration:
|
```shellsession
|
||||||
|
$ export PG_CONN_STR=postgres://user:pass@db.example.com/terraform_backend
|
||||||
```
|
$ terraform init
|
||||||
terraform init -backend-config="conn_str=postgres://user:pass@db.example.com/terraform_backend"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
To use a Postgres server running on the same machine as Terraform, configure localhost with SSL disabled:
|
or just the sensitive parameters:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
terraform {
|
||||||
|
backend "pg" {
|
||||||
|
conn_str = "postgres://db.example.com/terraform_backend"
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
terraform init -backend-config="conn_str=postgres://localhost/terraform_backend?sslmode=disable"
|
|
||||||
|
```shellsession
|
||||||
|
$ export PGUSER=user
|
||||||
|
$ read -s PGPASSWORD
|
||||||
|
$ export PGPASSWORD
|
||||||
|
$ terraform init
|
||||||
```
|
```
|
||||||
|
|
||||||
## Data Source Configuration
|
## Data Source Configuration
|
||||||
@ -68,11 +86,11 @@ data "terraform_remote_state" "network" {
|
|||||||
|
|
||||||
The following configuration options or environment variables are supported:
|
The following configuration options or environment variables are supported:
|
||||||
|
|
||||||
- `conn_str` - (Required) Postgres connection string; a `postgres://` URL. `conn_str` can also be set using the `PGDATABASE` environment variable.
|
- `conn_str` - Postgres connection string; a `postgres://` URL. The `PG_CONN_STR` and [standard `libpq`](https://www.postgresql.org/docs/current/libpq-envars.html) environment variables can also be used to indicate how to connect to the PostgreSQL database.
|
||||||
- `schema_name` - Name of the automatically-managed Postgres schema, default `terraform_remote_state`.
|
- `schema_name` - Name of the automatically-managed Postgres schema, default to `terraform_remote_state`. Can also be set using the `PG_SCHEMA_NAME` environment variable.
|
||||||
- `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_schema_creation` - If set to `true`, the Postgres schema must already exist. Can also be set using the `PG_SKIP_SCHEMA_CREATION` environment variable. 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.
|
- `skip_table_creation` - If set to `true`, the Postgres table must already exist. Can also be set using the `PG_SKIP_TABLE_CREATION` environment variable. Terraform won't try to create the table, this is useful when it has already been created by a database administrator.
|
||||||
- `skip_index_creation` - If set to `true`, the Postgres index must already exist. Terraform won't try to create the index, this is useful when it has already been created by a database administrator.
|
- `skip_index_creation` - If set to `true`, the Postgres index must already exist. Can also be set using the `PG_SKIP_INDEX_CREATION` environment variable. Terraform won't try to create the index, this is useful when it has already been created by a database administrator.
|
||||||
|
|
||||||
## Technical Design
|
## Technical Design
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user