Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions builtin/providers/terraform/data_source_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func dataSourceRemoteStateValidate(cfg cty.Value) tfdiags.Diagnostics {
// Getting the backend implicitly validates the configuration for it,
// but we can only do that if it's all known already.
if cfg.GetAttr("config").IsWhollyKnown() && cfg.GetAttr("backend").IsKnown() {
_, moreDiags := getBackend(cfg)
_, _, moreDiags := getBackend(cfg)
diags = diags.Append(moreDiags)
} else {
// Otherwise we'll just type-check the config object itself.
Expand Down Expand Up @@ -81,9 +81,15 @@ func dataSourceRemoteStateValidate(cfg cty.Value) tfdiags.Diagnostics {
func dataSourceRemoteStateRead(d cty.Value) (cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics

b, moreDiags := getBackend(d)
b, cfg, moreDiags := getBackend(d)
diags = diags.Append(moreDiags)
if diags.HasErrors() {
if moreDiags.HasErrors() {
return cty.NilVal, diags
}

configureDiags := b.Configure(cfg)
if configureDiags.HasErrors() {
diags = diags.Append(configureDiags.Err())
return cty.NilVal, diags
}

Expand Down Expand Up @@ -152,7 +158,7 @@ func dataSourceRemoteStateRead(d cty.Value) (cty.Value, tfdiags.Diagnostics) {
return cty.ObjectVal(newState), diags
}

func getBackend(cfg cty.Value) (backend.Backend, tfdiags.Diagnostics) {
func getBackend(cfg cty.Value) (backend.Backend, cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics

backendType := cfg.GetAttr("backend").AsString()
Expand All @@ -165,15 +171,15 @@ func getBackend(cfg cty.Value) (backend.Backend, tfdiags.Diagnostics) {

// Create the client to access our remote state
log.Printf("[DEBUG] Initializing remote state backend: %s", backendType)
f := backendInit.Backend(backendType)
f := getBackendFactory(backendType)
if f == nil {
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Invalid backend configuration",
fmt.Sprintf("There is no backend type named %q.", backendType),
cty.Path(nil).GetAttr("backend"),
))
return nil, diags
return nil, cty.NilVal, diags
}
b := f()

Expand All @@ -199,21 +205,28 @@ func getBackend(cfg cty.Value) (backend.Backend, tfdiags.Diagnostics) {
tfdiags.FormatError(err)),
cty.Path(nil).GetAttr("config"),
))
return nil, diags
return nil, cty.NilVal, diags
}

newVal, validateDiags := b.PrepareConfig(configVal)
diags = diags.Append(validateDiags)
if validateDiags.HasErrors() {
return nil, diags
return nil, cty.NilVal, diags
}
configVal = newVal

configureDiags := b.Configure(configVal)
if configureDiags.HasErrors() {
diags = diags.Append(configureDiags.Err())
return nil, diags
return b, newVal, diags
}

// overrideBackendFactories allows test cases to control the set of available
// backends to allow for more self-contained tests. This should never be set
// in non-test code.
var overrideBackendFactories map[string]backend.InitFn

func getBackendFactory(backendType string) backend.InitFn {
if len(overrideBackendFactories) > 0 {
// Tests may override the set of backend factories.
return overrideBackendFactories[backendType]
}

return b, diags
return backendInit.Backend(backendType)
}
105 changes: 104 additions & 1 deletion builtin/providers/terraform/data_source_state_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package terraform

import (
"github.com/hashicorp/terraform/tfdiags"
"fmt"
"log"
"testing"

"github.com/apparentlymart/go-dump/dump"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
)

Expand Down Expand Up @@ -41,6 +45,26 @@ func TestState_basic(t *testing.T) {
}),
false,
},
"_local": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("_local"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/basic.tfstate"),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("_local"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/basic.tfstate"),
}),
"outputs": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
}),
"workspace": cty.StringVal(backend.DefaultStateName),
"defaults": cty.NullVal(cty.DynamicPseudoType),
}),
false,
},
"complex outputs": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
Expand Down Expand Up @@ -213,6 +237,24 @@ func TestState_basic(t *testing.T) {
}),
false,
},
"nonexistent backend": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("nonexistent"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/basic.tfstate"),
}),
}),
cty.NilVal,
true,
},
"null config": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.NullVal(cty.DynamicPseudoType),
}),
cty.NilVal,
true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
Expand Down Expand Up @@ -245,3 +287,64 @@ func TestState_basic(t *testing.T) {
})
}
}

func TestState_validation(t *testing.T) {
// The main test TestState_basic covers both validation and reading of
// state snapshots, so this additional test is here only to verify that
// the validation step in isolation does not attempt to configure
// the backend.
overrideBackendFactories = map[string]backend.InitFn{
"failsconfigure": func() backend.Backend {
return backendFailsConfigure{}
},
}
defer func() {
// undo our overrides so we won't affect other tests
overrideBackendFactories = nil
}()

schema := dataSourceRemoteStateGetSchema().Block
config, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("failsconfigure"),
"config": cty.EmptyObjectVal,
}))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}

diags := dataSourceRemoteStateValidate(config)
if diags.HasErrors() {
t.Fatalf("unexpected errors\n%s", diags.Err().Error())
}
}

type backendFailsConfigure struct{}

func (b backendFailsConfigure) ConfigSchema() *configschema.Block {
log.Printf("[TRACE] backendFailsConfigure.ConfigSchema")
return &configschema.Block{} // intentionally empty configuration schema
}

func (b backendFailsConfigure) PrepareConfig(given cty.Value) (cty.Value, tfdiags.Diagnostics) {
// No special actions to take here
return given, nil
}

func (b backendFailsConfigure) Configure(config cty.Value) tfdiags.Diagnostics {
log.Printf("[TRACE] backendFailsConfigure.Configure(%#v)", config)
var diags tfdiags.Diagnostics
diags = diags.Append(fmt.Errorf("Configure should never be called"))
return diags
}

func (b backendFailsConfigure) StateMgr(workspace string) (statemgr.Full, error) {
return nil, fmt.Errorf("StateMgr not implemented")
}

func (b backendFailsConfigure) DeleteWorkspace(name string) error {
return fmt.Errorf("DeleteWorkspace not implemented")
}

func (b backendFailsConfigure) Workspaces() ([]string, error) {
return nil, fmt.Errorf("Workspaces not implemented")
}