372 lines
11 KiB
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")
|
|
}
|