mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
6aac79e194
This commit adds the groundwork for supporting module outputs of types other than string. In order to do so, the state version is increased from 1 to 2 (though the "public-facing" state version is actually as the first state file was binary). Tests are added to ensure that V2 (1) state is upgraded to V3 (2) state, though no separate read path is required since the V2 JSON will unmarshal correctly into the V3 structure. Outputs in a ModuleState are now of type map[string]interface{}, and a test covers round-tripping string, []string and map[string]string, which should cover all of the types in question. Type switches have been added where necessary to deal with the interface{} value, but they currently default to panicking when the input is not a string.
489 lines
12 KiB
Go
489 lines
12 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/hil/ast"
|
|
"github.com/hashicorp/terraform/config"
|
|
)
|
|
|
|
func TestInterpolater_countIndex(t *testing.T) {
|
|
i := &Interpolater{}
|
|
|
|
scope := &InterpolationScope{
|
|
Path: rootModulePath,
|
|
Resource: &Resource{CountIndex: 42},
|
|
}
|
|
|
|
testInterpolate(t, i, scope, "count.index", ast.Variable{
|
|
Value: 42,
|
|
Type: ast.TypeInt,
|
|
})
|
|
}
|
|
|
|
func TestInterpolater_countIndexInWrongContext(t *testing.T) {
|
|
i := &Interpolater{}
|
|
|
|
scope := &InterpolationScope{
|
|
Path: rootModulePath,
|
|
}
|
|
|
|
n := "count.index"
|
|
|
|
v, err := config.NewInterpolatedVariable(n)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expectedErr := fmt.Errorf("foo: count.index is only valid within resources")
|
|
|
|
_, err = i.Values(scope, map[string]config.InterpolatedVariable{
|
|
"foo": v,
|
|
})
|
|
|
|
if !reflect.DeepEqual(expectedErr, err) {
|
|
t.Fatalf("expected: %#v, got %#v", expectedErr, err)
|
|
}
|
|
}
|
|
|
|
func TestInterpolater_moduleVariable(t *testing.T) {
|
|
lock := new(sync.RWMutex)
|
|
state := &State{
|
|
Modules: []*ModuleState{
|
|
&ModuleState{
|
|
Path: rootModulePath,
|
|
Resources: map[string]*ResourceState{
|
|
"aws_instance.web": &ResourceState{
|
|
Type: "aws_instance",
|
|
Primary: &InstanceState{
|
|
ID: "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&ModuleState{
|
|
Path: []string{RootModuleName, "child"},
|
|
Outputs: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
i := &Interpolater{
|
|
State: state,
|
|
StateLock: lock,
|
|
}
|
|
|
|
scope := &InterpolationScope{
|
|
Path: rootModulePath,
|
|
}
|
|
|
|
testInterpolate(t, i, scope, "module.child.foo", ast.Variable{
|
|
Value: "bar",
|
|
Type: ast.TypeString,
|
|
})
|
|
}
|
|
|
|
func TestInterpolater_pathCwd(t *testing.T) {
|
|
i := &Interpolater{}
|
|
scope := &InterpolationScope{}
|
|
|
|
expected, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
testInterpolate(t, i, scope, "path.cwd", ast.Variable{
|
|
Value: expected,
|
|
Type: ast.TypeString,
|
|
})
|
|
}
|
|
|
|
func TestInterpolater_pathModule(t *testing.T) {
|
|
mod := testModule(t, "interpolate-path-module")
|
|
i := &Interpolater{
|
|
Module: mod,
|
|
}
|
|
scope := &InterpolationScope{
|
|
Path: []string{RootModuleName, "child"},
|
|
}
|
|
|
|
path := mod.Child([]string{"child"}).Config().Dir
|
|
testInterpolate(t, i, scope, "path.module", ast.Variable{
|
|
Value: path,
|
|
Type: ast.TypeString,
|
|
})
|
|
}
|
|
|
|
func TestInterpolater_pathRoot(t *testing.T) {
|
|
mod := testModule(t, "interpolate-path-module")
|
|
i := &Interpolater{
|
|
Module: mod,
|
|
}
|
|
scope := &InterpolationScope{
|
|
Path: []string{RootModuleName, "child"},
|
|
}
|
|
|
|
path := mod.Config().Dir
|
|
testInterpolate(t, i, scope, "path.root", ast.Variable{
|
|
Value: path,
|
|
Type: ast.TypeString,
|
|
})
|
|
}
|
|
|
|
func TestInterpolater_resourceVariable(t *testing.T) {
|
|
lock := new(sync.RWMutex)
|
|
state := &State{
|
|
Modules: []*ModuleState{
|
|
&ModuleState{
|
|
Path: rootModulePath,
|
|
Resources: map[string]*ResourceState{
|
|
"aws_instance.web": &ResourceState{
|
|
Type: "aws_instance",
|
|
Primary: &InstanceState{
|
|
ID: "bar",
|
|
Attributes: map[string]string{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
i := &Interpolater{
|
|
Module: testModule(t, "interpolate-resource-variable"),
|
|
State: state,
|
|
StateLock: lock,
|
|
}
|
|
|
|
scope := &InterpolationScope{
|
|
Path: rootModulePath,
|
|
}
|
|
|
|
testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{
|
|
Value: "bar",
|
|
Type: ast.TypeString,
|
|
})
|
|
}
|
|
|
|
func TestInterpolater_resourceVariableMulti(t *testing.T) {
|
|
lock := new(sync.RWMutex)
|
|
state := &State{
|
|
Modules: []*ModuleState{
|
|
&ModuleState{
|
|
Path: rootModulePath,
|
|
Resources: map[string]*ResourceState{
|
|
"aws_instance.web": &ResourceState{
|
|
Type: "aws_instance",
|
|
Primary: &InstanceState{
|
|
ID: "bar",
|
|
Attributes: map[string]string{
|
|
"foo": config.UnknownVariableValue,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
i := &Interpolater{
|
|
Module: testModule(t, "interpolate-resource-variable"),
|
|
State: state,
|
|
StateLock: lock,
|
|
}
|
|
|
|
scope := &InterpolationScope{
|
|
Path: rootModulePath,
|
|
}
|
|
|
|
testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{
|
|
Value: config.UnknownVariableValue,
|
|
Type: ast.TypeString,
|
|
})
|
|
}
|
|
|
|
func TestInterpolator_resourceMultiAttributes(t *testing.T) {
|
|
lock := new(sync.RWMutex)
|
|
state := &State{
|
|
Modules: []*ModuleState{
|
|
&ModuleState{
|
|
Path: rootModulePath,
|
|
Resources: map[string]*ResourceState{
|
|
"aws_route53_zone.yada": &ResourceState{
|
|
Type: "aws_route53_zone",
|
|
Dependencies: []string{},
|
|
Primary: &InstanceState{
|
|
ID: "AAABBBCCCDDDEEE",
|
|
Attributes: map[string]string{
|
|
"name_servers.#": "4",
|
|
"name_servers.0": "ns-1334.awsdns-38.org",
|
|
"name_servers.1": "ns-1680.awsdns-18.co.uk",
|
|
"name_servers.2": "ns-498.awsdns-62.com",
|
|
"name_servers.3": "ns-601.awsdns-11.net",
|
|
"listeners.#": "1",
|
|
"listeners.0": "red",
|
|
"tags.#": "1",
|
|
"tags.Name": "reindeer",
|
|
"nothing.#": "0",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
i := &Interpolater{
|
|
Module: testModule(t, "interpolate-multi-vars"),
|
|
StateLock: lock,
|
|
State: state,
|
|
}
|
|
|
|
scope := &InterpolationScope{
|
|
Path: rootModulePath,
|
|
}
|
|
|
|
name_servers := []string{
|
|
"ns-1334.awsdns-38.org",
|
|
"ns-1680.awsdns-18.co.uk",
|
|
"ns-498.awsdns-62.com",
|
|
"ns-601.awsdns-11.net",
|
|
}
|
|
expectedNameServers := config.NewStringList(name_servers).String()
|
|
|
|
// More than 1 element
|
|
testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{
|
|
Value: expectedNameServers,
|
|
Type: ast.TypeString,
|
|
})
|
|
|
|
// Exactly 1 element
|
|
testInterpolate(t, i, scope, "aws_route53_zone.yada.listeners", ast.Variable{
|
|
Value: config.NewStringList([]string{"red"}).String(),
|
|
Type: ast.TypeString,
|
|
})
|
|
|
|
// Zero elements
|
|
testInterpolate(t, i, scope, "aws_route53_zone.yada.nothing", ast.Variable{
|
|
Value: config.NewStringList([]string{}).String(),
|
|
Type: ast.TypeString,
|
|
})
|
|
|
|
// Maps still need to work
|
|
testInterpolate(t, i, scope, "aws_route53_zone.yada.tags.Name", ast.Variable{
|
|
Value: "reindeer",
|
|
Type: ast.TypeString,
|
|
})
|
|
}
|
|
|
|
func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) {
|
|
i := getInterpolaterFixture(t)
|
|
scope := &InterpolationScope{
|
|
Path: rootModulePath,
|
|
}
|
|
|
|
name_servers := []string{
|
|
"ns-1334.awsdns-38.org",
|
|
"ns-1680.awsdns-18.co.uk",
|
|
"ns-498.awsdns-62.com",
|
|
"ns-601.awsdns-11.net",
|
|
"ns-000.awsdns-38.org",
|
|
"ns-444.awsdns-18.co.uk",
|
|
"ns-666.awsdns-11.net",
|
|
"ns-999.awsdns-62.com",
|
|
}
|
|
|
|
// More than 1 element
|
|
expectedNameServers := config.NewStringList(name_servers[0:4]).String()
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.0.name_servers", ast.Variable{
|
|
Value: expectedNameServers,
|
|
Type: ast.TypeString,
|
|
})
|
|
// More than 1 element in both
|
|
expectedNameServers = config.NewStringList(name_servers).String()
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.*.name_servers", ast.Variable{
|
|
Value: expectedNameServers,
|
|
Type: ast.TypeString,
|
|
})
|
|
|
|
// Exactly 1 element
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.0.listeners", ast.Variable{
|
|
Value: config.NewStringList([]string{"red"}).String(),
|
|
Type: ast.TypeString,
|
|
})
|
|
// Exactly 1 element in both
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.*.listeners", ast.Variable{
|
|
Value: config.NewStringList([]string{"red", "blue"}).String(),
|
|
Type: ast.TypeString,
|
|
})
|
|
|
|
// Zero elements
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.0.nothing", ast.Variable{
|
|
Value: config.NewStringList([]string{}).String(),
|
|
Type: ast.TypeString,
|
|
})
|
|
// Zero + 1 element
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special", ast.Variable{
|
|
Value: config.NewStringList([]string{"extra"}).String(),
|
|
Type: ast.TypeString,
|
|
})
|
|
|
|
// Maps still need to work
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.0.tags.Name", ast.Variable{
|
|
Value: "reindeer",
|
|
Type: ast.TypeString,
|
|
})
|
|
// Maps still need to work in both
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.*.tags.Name", ast.Variable{
|
|
Value: config.NewStringList([]string{"reindeer", "white-hart"}).String(),
|
|
Type: ast.TypeString,
|
|
})
|
|
}
|
|
|
|
func TestInterpolator_resourceMultiAttributesComputed(t *testing.T) {
|
|
lock := new(sync.RWMutex)
|
|
// The state would never be written with an UnknownVariableValue in it, but
|
|
// it can/does exist that way in memory during the plan phase.
|
|
state := &State{
|
|
Modules: []*ModuleState{
|
|
&ModuleState{
|
|
Path: rootModulePath,
|
|
Resources: map[string]*ResourceState{
|
|
"aws_route53_zone.yada": &ResourceState{
|
|
Type: "aws_route53_zone",
|
|
Primary: &InstanceState{
|
|
ID: "z-abc123",
|
|
Attributes: map[string]string{
|
|
"name_servers.#": config.UnknownVariableValue,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
i := &Interpolater{
|
|
Module: testModule(t, "interpolate-multi-vars"),
|
|
StateLock: lock,
|
|
State: state,
|
|
}
|
|
|
|
scope := &InterpolationScope{
|
|
Path: rootModulePath,
|
|
}
|
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{
|
|
Value: config.UnknownVariableValue,
|
|
Type: ast.TypeString,
|
|
})
|
|
}
|
|
|
|
func TestInterpolater_selfVarWithoutResource(t *testing.T) {
|
|
i := &Interpolater{}
|
|
|
|
scope := &InterpolationScope{
|
|
Path: rootModulePath,
|
|
}
|
|
|
|
v, err := config.NewInterpolatedVariable("self.name")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
_, err = i.Values(scope, map[string]config.InterpolatedVariable{"foo": v})
|
|
if err == nil {
|
|
t.Fatalf("expected err, got none")
|
|
}
|
|
}
|
|
|
|
func getInterpolaterFixture(t *testing.T) *Interpolater {
|
|
lock := new(sync.RWMutex)
|
|
state := &State{
|
|
Modules: []*ModuleState{
|
|
&ModuleState{
|
|
Path: rootModulePath,
|
|
Resources: map[string]*ResourceState{
|
|
"aws_route53_zone.terra.0": &ResourceState{
|
|
Type: "aws_route53_zone",
|
|
Dependencies: []string{},
|
|
Primary: &InstanceState{
|
|
ID: "AAABBBCCCDDDEEE",
|
|
Attributes: map[string]string{
|
|
"name_servers.#": "4",
|
|
"name_servers.0": "ns-1334.awsdns-38.org",
|
|
"name_servers.1": "ns-1680.awsdns-18.co.uk",
|
|
"name_servers.2": "ns-498.awsdns-62.com",
|
|
"name_servers.3": "ns-601.awsdns-11.net",
|
|
"listeners.#": "1",
|
|
"listeners.0": "red",
|
|
"tags.#": "1",
|
|
"tags.Name": "reindeer",
|
|
"nothing.#": "0",
|
|
},
|
|
},
|
|
},
|
|
"aws_route53_zone.terra.1": &ResourceState{
|
|
Type: "aws_route53_zone",
|
|
Dependencies: []string{},
|
|
Primary: &InstanceState{
|
|
ID: "EEEFFFGGGHHHIII",
|
|
Attributes: map[string]string{
|
|
"name_servers.#": "4",
|
|
"name_servers.0": "ns-000.awsdns-38.org",
|
|
"name_servers.1": "ns-444.awsdns-18.co.uk",
|
|
"name_servers.2": "ns-999.awsdns-62.com",
|
|
"name_servers.3": "ns-666.awsdns-11.net",
|
|
"listeners.#": "1",
|
|
"listeners.0": "blue",
|
|
"special.#": "1",
|
|
"special.0": "extra",
|
|
"tags.#": "1",
|
|
"tags.Name": "white-hart",
|
|
"nothing.#": "0",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
return &Interpolater{
|
|
Module: testModule(t, "interpolate-multi-vars"),
|
|
StateLock: lock,
|
|
State: state,
|
|
}
|
|
}
|
|
|
|
func testInterpolate(
|
|
t *testing.T, i *Interpolater,
|
|
scope *InterpolationScope,
|
|
n string, expectedVar ast.Variable) {
|
|
v, err := config.NewInterpolatedVariable(n)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual, err := i.Values(scope, map[string]config.InterpolatedVariable{
|
|
"foo": v,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expected := map[string]ast.Variable{
|
|
"foo": expectedVar,
|
|
}
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("%q: actual: %#v\nexpected: %#v", n, actual, expected)
|
|
}
|
|
}
|