terraform/internal/builtin/providers/terraform/data_source_state_test.go

372 lines
11 KiB
Go

package terraform
import (
"fmt"
"log"
"testing"
"github.com/apparentlymart/go-dump/dump"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/zclconf/go-cty/cty"
)
func TestResource(t *testing.T) {
if err := dataSourceRemoteStateGetSchema().Block.InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestState_basic(t *testing.T) {
var tests = map[string]struct {
Config cty.Value
Want cty.Value
Err bool
}{
"basic": {
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"),
}),
"defaults": cty.NullVal(cty.DynamicPseudoType),
"workspace": cty.NullVal(cty.String),
}),
false,
},
"workspace": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"workspace": cty.StringVal(backend.DefaultStateName),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/basic.tfstate"),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"workspace": cty.StringVal(backend.DefaultStateName),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/basic.tfstate"),
}),
"outputs": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
}),
"defaults": cty.NullVal(cty.DynamicPseudoType),
}),
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"),
}),
"defaults": cty.NullVal(cty.DynamicPseudoType),
"workspace": cty.NullVal(cty.String),
}),
false,
},
"complex outputs": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/complex_outputs.tfstate"),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/complex_outputs.tfstate"),
}),
"outputs": cty.ObjectVal(map[string]cty.Value{
"computed_map": cty.MapVal(map[string]cty.Value{
"key1": cty.StringVal("value1"),
}),
"computed_set": cty.ListVal([]cty.Value{
cty.StringVal("setval1"),
cty.StringVal("setval2"),
}),
"map": cty.MapVal(map[string]cty.Value{
"key": cty.StringVal("test"),
"test": cty.StringVal("test"),
}),
"set": cty.ListVal([]cty.Value{
cty.StringVal("test1"),
cty.StringVal("test2"),
}),
}),
"defaults": cty.NullVal(cty.DynamicPseudoType),
"workspace": cty.NullVal(cty.String),
}),
false,
},
"null outputs": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/null_outputs.tfstate"),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/null_outputs.tfstate"),
}),
"outputs": cty.ObjectVal(map[string]cty.Value{
"map": cty.NullVal(cty.Map(cty.String)),
"list": cty.NullVal(cty.List(cty.String)),
}),
"defaults": cty.NullVal(cty.DynamicPseudoType),
"workspace": cty.NullVal(cty.String),
}),
false,
},
"defaults": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/empty.tfstate"),
}),
"defaults": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/empty.tfstate"),
}),
"defaults": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
}),
"outputs": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
}),
"workspace": cty.NullVal(cty.String),
}),
false,
},
"missing": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/missing.tfstate"), // intentionally not present on disk
}),
}),
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/missing.tfstate"),
}),
"defaults": cty.NullVal(cty.DynamicPseudoType),
"outputs": cty.EmptyObjectVal,
"workspace": cty.NullVal(cty.String),
}),
true,
},
"wrong type for config": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.StringVal("nope"),
}),
cty.NilVal,
true,
},
"wrong type for config with unknown backend": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.UnknownVal(cty.String),
"config": cty.StringVal("nope"),
}),
cty.NilVal,
true,
},
"wrong type for config with unknown config": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.UnknownVal(cty.String),
}),
cty.NilVal,
true,
},
"wrong type for defaults": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/basic.tfstate"),
}),
"defaults": cty.StringVal("nope"),
}),
cty.NilVal,
true,
},
"config as map": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.MapVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/empty.tfstate"),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.MapVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/empty.tfstate"),
}),
"defaults": cty.NullVal(cty.DynamicPseudoType),
"outputs": cty.EmptyObjectVal,
"workspace": cty.NullVal(cty.String),
}),
false,
},
"defaults as map": {
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/basic.tfstate"),
}),
"defaults": cty.MapValEmpty(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
"backend": cty.StringVal("local"),
"config": cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal("./testdata/basic.tfstate"),
}),
"defaults": cty.MapValEmpty(cty.String),
"outputs": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
}),
"workspace": cty.NullVal(cty.String),
}),
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) {
schema := dataSourceRemoteStateGetSchema().Block
config, err := schema.CoerceValue(test.Config)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
diags := dataSourceRemoteStateValidate(config)
var got cty.Value
if !diags.HasErrors() && config.IsWhollyKnown() {
var moreDiags tfdiags.Diagnostics
got, moreDiags = dataSourceRemoteStateRead(config)
diags = diags.Append(moreDiags)
}
if test.Err {
if !diags.HasErrors() {
t.Fatal("succeeded; want error")
}
} else if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
if test.Want != cty.NilVal && !test.Want.RawEquals(got) {
t.Errorf("wrong result\nconfig: %sgot: %swant: %s", dump.Value(config), dump.Value(got), dump.Value(test.Want))
}
})
}
}
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")
}