mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-18 12:42:58 -06:00
f40800b3a4
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
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/states/statemgr"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
"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")
|
|
}
|