terraform/terraform_remote_state: accept complex configs

The `remote` backend config contains an attribute that is defined as a `*schema.Set`, but currently only `string` values are accepted as the `config` attribute is defined as a `schema.TypeMap`.

Additionally the `b.Validate()` method wasn’t called to prevent a possible panic in case of unexpected configurations being passed to `b.Configure()`.

This commit is a bit of a hack to be able to support this in the 0.11 series. The 0.12 series will have proper support, so when merging 0.12 this should be reverted again.
This commit is contained in:
Sander van Harmelen 2018-08-29 14:41:56 +02:00
parent cf8516287e
commit 3e935c846f
3 changed files with 395 additions and 4 deletions

View File

@ -5,6 +5,7 @@ import (
"log" "log"
"time" "time"
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
backendInit "github.com/hashicorp/terraform/backend/init" backendInit "github.com/hashicorp/terraform/backend/init"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
@ -32,9 +33,368 @@ func dataSourceRemoteState() *schema.Resource {
}, },
}, },
// This field now contains all possible attributes that are supported
// by any of the existing backends. When merging this into 0.12 this
// should be reverted and instead the new 'cty.DynamicPseudoType' type
// should be used to make this work with any future backends as well.
"config": { "config": {
Type: schema.TypeMap, Type: schema.TypeSet,
Optional: true, Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"hostname": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"organization": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"token": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"workspaces": &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Schema{Type: schema.TypeMap},
},
"username": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"password": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"url": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"repo": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"subpath": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"storage_account_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"container_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"access_key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"environment": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"resource_group_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"arm_subscription_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"arm_client_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"arm_client_secret": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"arm_tenant_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"access_token": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"address": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"scheme": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"datacenter": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"http_auth": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"gzip": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"lock": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"ca_file": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"cert_file": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"key_file": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"endpoints": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"prefix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"cacert_path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"cert_path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"key_path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"bucket": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"credentials": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"project": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"region": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"encryption_key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"update_method": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"lock_address": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"lock_method": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"unlock_address": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"unlock_method": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"skip_cert_verification": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"account": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"user": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"key_material": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"key_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"insecure_skip_tls_verify": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"object_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"endpoint": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"encrypt": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"acl": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"secret_key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"kms_key_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"lock_table": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"dynamodb_table": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"profile": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"shared_credentials_file": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"role_arn": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"assume_role_policy": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"external_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"session_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"workspace_key_prefix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"skip_credentials_validation": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"skip_get_ec2_platforms": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"skip_region_validation": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"skip_requesting_account_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"skip_metadata_api_check": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"auth_url": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"container": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"user_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"user_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"region_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"tenant_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"tenant_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"domain_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"domain_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"insecure": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"cacert_file": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"cert": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"archive_container": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"archive_path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"expire_after": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
},
}, },
"defaults": { "defaults": {
@ -66,8 +426,28 @@ func dataSourceRemoteState() *schema.Resource {
func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error { func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
backendType := d.Get("backend").(string) backendType := d.Get("backend").(string)
// Get the configuration in a type we want. // Get the configuration in a type we want. This is a bit of a hack but makes
rawConfig, err := config.NewRawConfig(d.Get("config").(map[string]interface{})) // things work for the 'remote' backend as well. This can simply be deleted or
// reverted when merging this 0.12.
raw := make(map[string]interface{})
if cfg, ok := d.GetOk("config"); ok {
if raw, ok = cfg.(*schema.Set).List()[0].(map[string]interface{}); ok {
for k, v := range raw {
switch v := v.(type) {
case string:
if v == "" {
delete(raw, k)
}
case []interface{}:
if len(v) == 0 {
delete(raw, k)
}
}
}
}
}
rawConfig, err := config.NewRawConfig(raw)
if err != nil { if err != nil {
return fmt.Errorf("error initializing backend: %s", err) return fmt.Errorf("error initializing backend: %s", err)
} }
@ -86,6 +466,14 @@ func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
} }
b := f() b := f()
warns, errs := b.Validate(terraform.NewResourceConfig(rawConfig))
for _, warning := range warns {
log.Printf("[DEBUG] Warning validating backend config: %s", warning)
}
if len(errs) > 0 {
return fmt.Errorf("error validating backend config: %s", multierror.Append(nil, errs...))
}
// Configure the backend // Configure the backend
if err := b.Configure(terraform.NewResourceConfig(rawConfig)); err != nil { if err := b.Configure(terraform.NewResourceConfig(rawConfig)); err != nil {
return fmt.Errorf("error initializing backend: %s", err) return fmt.Errorf("error initializing backend: %s", err)

View File

@ -53,7 +53,9 @@ func TestState_complexOutputs(t *testing.T) {
Config: testAccState_complexOutputs, Config: testAccState_complexOutputs,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckStateValue("terraform_remote_state.foo", "backend", "local"), testAccCheckStateValue("terraform_remote_state.foo", "backend", "local"),
testAccCheckStateValue("terraform_remote_state.foo", "config.path", "./test-fixtures/complex_outputs.tfstate"), // This (adding the hash) should be reverted when merged into 0.12.
// testAccCheckStateValue("terraform_remote_state.foo", "config.path", "./test-fixtures/complex_outputs.tfstate"),
testAccCheckStateValue("terraform_remote_state.foo", "config.1590222752.path", "./test-fixtures/complex_outputs.tfstate"),
testAccCheckStateValue("terraform_remote_state.foo", "computed_set.#", "2"), testAccCheckStateValue("terraform_remote_state.foo", "computed_set.#", "2"),
testAccCheckStateValue("terraform_remote_state.foo", `map.%`, "2"), testAccCheckStateValue("terraform_remote_state.foo", `map.%`, "2"),
testAccCheckStateValue("terraform_remote_state.foo", `map.key`, "test"), testAccCheckStateValue("terraform_remote_state.foo", `map.key`, "test"),

View File

@ -315,6 +315,7 @@ func (d *ResourceData) State() *terraform.InstanceState {
mapW := &MapFieldWriter{Schema: d.schema} mapW := &MapFieldWriter{Schema: d.schema}
if err := mapW.WriteField(nil, rawMap); err != nil { if err := mapW.WriteField(nil, rawMap); err != nil {
log.Printf("[ERR] Error writing fields: %s", err)
return nil return nil
} }