mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-19 13:12:58 -06:00
b3f80b9469
Fixes #10075 Fixes #10013 When interpolating, we were only maintaining the last known slice index. If you had sibling slices then you could lose your slice index when exiting the slice. The resulting behavior was that no some runs the computed key would be: "slice.0.attr" and on others would be "slice.attr", the latter being incorrect. We now maintain a list of slice indexes so that as we unnest, we properly restore the old value. Surprisingly unrelated to the graph but the shadow graph caught this which is great. :)
448 lines
8.8 KiB
Go
448 lines
8.8 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/gob"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/hil/ast"
|
|
)
|
|
|
|
func TestNewRawConfig(t *testing.T) {
|
|
raw := map[string]interface{}{
|
|
"foo": "${var.bar}",
|
|
"bar": `${file("boom.txt")}`,
|
|
}
|
|
|
|
rc, err := NewRawConfig(raw)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if len(rc.Interpolations) != 2 {
|
|
t.Fatalf("bad: %#v", rc.Interpolations)
|
|
}
|
|
if len(rc.Variables) != 1 {
|
|
t.Fatalf("bad: %#v", rc.Variables)
|
|
}
|
|
}
|
|
|
|
func TestRawConfig_basic(t *testing.T) {
|
|
raw := map[string]interface{}{
|
|
"foo": "${var.bar}",
|
|
}
|
|
|
|
rc, err := NewRawConfig(raw)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Before interpolate, Config() should be the raw
|
|
if !reflect.DeepEqual(rc.Config(), raw) {
|
|
t.Fatalf("bad: %#v", rc.Config())
|
|
}
|
|
|
|
vars := map[string]ast.Variable{
|
|
"var.bar": ast.Variable{
|
|
Value: "baz",
|
|
Type: ast.TypeString,
|
|
},
|
|
}
|
|
if err := rc.Interpolate(vars); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := rc.Config()
|
|
expected := map[string]interface{}{
|
|
"foo": "baz",
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
if len(rc.UnknownKeys()) != 0 {
|
|
t.Fatalf("bad: %#v", rc.UnknownKeys())
|
|
}
|
|
}
|
|
|
|
func TestRawConfig_double(t *testing.T) {
|
|
raw := map[string]interface{}{
|
|
"foo": "${var.bar}",
|
|
}
|
|
|
|
rc, err := NewRawConfig(raw)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
vars := map[string]ast.Variable{
|
|
"var.bar": ast.Variable{
|
|
Value: "baz",
|
|
Type: ast.TypeString,
|
|
},
|
|
}
|
|
if err := rc.Interpolate(vars); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := rc.Config()
|
|
expected := map[string]interface{}{
|
|
"foo": "baz",
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
|
|
vars = map[string]ast.Variable{
|
|
"var.bar": ast.Variable{
|
|
Value: "what",
|
|
Type: ast.TypeString,
|
|
},
|
|
}
|
|
if err := rc.Interpolate(vars); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual = rc.Config()
|
|
expected = map[string]interface{}{
|
|
"foo": "what",
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
}
|
|
|
|
func TestRawConfigInterpolate_escaped(t *testing.T) {
|
|
raw := map[string]interface{}{
|
|
"foo": "bar-$${baz}",
|
|
}
|
|
|
|
rc, err := NewRawConfig(raw)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Before interpolate, Config() should be the raw
|
|
if !reflect.DeepEqual(rc.Config(), raw) {
|
|
t.Fatalf("bad: %#v", rc.Config())
|
|
}
|
|
|
|
if err := rc.Interpolate(nil); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := rc.Config()
|
|
expected := map[string]interface{}{
|
|
"foo": "bar-${baz}",
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
if len(rc.UnknownKeys()) != 0 {
|
|
t.Fatalf("bad: %#v", rc.UnknownKeys())
|
|
}
|
|
}
|
|
|
|
func TestRawConfig_merge(t *testing.T) {
|
|
raw1 := map[string]interface{}{
|
|
"foo": "${var.foo}",
|
|
"bar": "${var.bar}",
|
|
}
|
|
|
|
rc1, err := NewRawConfig(raw1)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
{
|
|
vars := map[string]ast.Variable{
|
|
"var.foo": ast.Variable{
|
|
Value: "foovalue",
|
|
Type: ast.TypeString,
|
|
},
|
|
"var.bar": ast.Variable{
|
|
Value: "nope",
|
|
Type: ast.TypeString,
|
|
},
|
|
}
|
|
if err := rc1.Interpolate(vars); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
raw2 := map[string]interface{}{
|
|
"bar": "${var.bar}",
|
|
"baz": "${var.baz}",
|
|
}
|
|
|
|
rc2, err := NewRawConfig(raw2)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
{
|
|
vars := map[string]ast.Variable{
|
|
"var.bar": ast.Variable{
|
|
Value: "barvalue",
|
|
Type: ast.TypeString,
|
|
},
|
|
"var.baz": ast.Variable{
|
|
Value: UnknownVariableValue,
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
}
|
|
if err := rc2.Interpolate(vars); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
// Merge the two
|
|
rc3 := rc1.Merge(rc2)
|
|
|
|
// Raw should be merged
|
|
raw3 := map[string]interface{}{
|
|
"foo": "${var.foo}",
|
|
"bar": "${var.bar}",
|
|
"baz": "${var.baz}",
|
|
}
|
|
if !reflect.DeepEqual(rc3.Raw, raw3) {
|
|
t.Fatalf("bad: %#v", rc3.Raw)
|
|
}
|
|
|
|
actual := rc3.Config()
|
|
expected := map[string]interface{}{
|
|
"foo": "foovalue",
|
|
"bar": "barvalue",
|
|
"baz": UnknownVariableValue,
|
|
}
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
|
|
expectedKeys := []string{"baz"}
|
|
if !reflect.DeepEqual(rc3.UnknownKeys(), expectedKeys) {
|
|
t.Fatalf("bad: %#v", rc3.UnknownKeys())
|
|
}
|
|
}
|
|
|
|
func TestRawConfig_syntax(t *testing.T) {
|
|
raw := map[string]interface{}{
|
|
"foo": "${var",
|
|
}
|
|
|
|
if _, err := NewRawConfig(raw); err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
func TestRawConfig_unknown(t *testing.T) {
|
|
raw := map[string]interface{}{
|
|
"foo": "${var.bar}",
|
|
}
|
|
|
|
rc, err := NewRawConfig(raw)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
vars := map[string]ast.Variable{
|
|
"var.bar": ast.Variable{
|
|
Value: UnknownVariableValue,
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
}
|
|
if err := rc.Interpolate(vars); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := rc.Config()
|
|
expected := map[string]interface{}{"foo": UnknownVariableValue}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
|
|
expectedKeys := []string{"foo"}
|
|
if !reflect.DeepEqual(rc.UnknownKeys(), expectedKeys) {
|
|
t.Fatalf("bad: %#v", rc.UnknownKeys())
|
|
}
|
|
}
|
|
|
|
func TestRawConfig_unknownPartial(t *testing.T) {
|
|
raw := map[string]interface{}{
|
|
"foo": "${var.bar}/32",
|
|
}
|
|
|
|
rc, err := NewRawConfig(raw)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
vars := map[string]ast.Variable{
|
|
"var.bar": ast.Variable{
|
|
Value: UnknownVariableValue,
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
}
|
|
if err := rc.Interpolate(vars); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := rc.Config()
|
|
expected := map[string]interface{}{"foo": UnknownVariableValue}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
|
|
expectedKeys := []string{"foo"}
|
|
if !reflect.DeepEqual(rc.UnknownKeys(), expectedKeys) {
|
|
t.Fatalf("bad: %#v", rc.UnknownKeys())
|
|
}
|
|
}
|
|
|
|
func TestRawConfig_unknownPartialList(t *testing.T) {
|
|
raw := map[string]interface{}{
|
|
"foo": []interface{}{
|
|
"${var.bar}/32",
|
|
},
|
|
}
|
|
|
|
rc, err := NewRawConfig(raw)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
vars := map[string]ast.Variable{
|
|
"var.bar": ast.Variable{
|
|
Value: UnknownVariableValue,
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
}
|
|
if err := rc.Interpolate(vars); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := rc.Config()
|
|
expected := map[string]interface{}{"foo": []interface{}{UnknownVariableValue}}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
|
|
expectedKeys := []string{"foo"}
|
|
if !reflect.DeepEqual(rc.UnknownKeys(), expectedKeys) {
|
|
t.Fatalf("bad: %#v", rc.UnknownKeys())
|
|
}
|
|
}
|
|
|
|
// This tests a race found where we were not maintaining the "slice index"
|
|
// accounting properly. The result would be that some computed keys would
|
|
// look like they had no slice index when they in fact do. This test is not
|
|
// very reliable but it did fail before the fix and passed after.
|
|
func TestRawConfig_sliceIndexLoss(t *testing.T) {
|
|
raw := map[string]interface{}{
|
|
"slice": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"foo": []interface{}{"foo/${var.unknown}"},
|
|
"bar": []interface{}{"bar"},
|
|
},
|
|
},
|
|
}
|
|
|
|
vars := map[string]ast.Variable{
|
|
"var.unknown": ast.Variable{
|
|
Value: UnknownVariableValue,
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
"var.known": ast.Variable{
|
|
Value: "123456",
|
|
Type: ast.TypeString,
|
|
},
|
|
}
|
|
|
|
// We run it a lot because its fast and we try to get a race out
|
|
for i := 0; i < 50; i++ {
|
|
rc, err := NewRawConfig(raw)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if err := rc.Interpolate(vars); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expectedKeys := []string{"slice.0.foo"}
|
|
if !reflect.DeepEqual(rc.UnknownKeys(), expectedKeys) {
|
|
t.Fatalf("bad: %#v", rc.UnknownKeys())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRawConfigValue(t *testing.T) {
|
|
raw := map[string]interface{}{
|
|
"foo": "${var.bar}",
|
|
}
|
|
|
|
rc, err := NewRawConfig(raw)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
rc.Key = ""
|
|
if rc.Value() != nil {
|
|
t.Fatalf("bad: %#v", rc.Value())
|
|
}
|
|
|
|
rc.Key = "foo"
|
|
if rc.Value() != "${var.bar}" {
|
|
t.Fatalf("err: %#v", rc.Value())
|
|
}
|
|
|
|
vars := map[string]ast.Variable{
|
|
"var.bar": ast.Variable{
|
|
Value: "baz",
|
|
Type: ast.TypeString,
|
|
},
|
|
}
|
|
if err := rc.Interpolate(vars); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if rc.Value() != "baz" {
|
|
t.Fatalf("bad: %#v", rc.Value())
|
|
}
|
|
}
|
|
|
|
func TestRawConfig_implGob(t *testing.T) {
|
|
var _ gob.GobDecoder = new(RawConfig)
|
|
var _ gob.GobEncoder = new(RawConfig)
|
|
}
|
|
|
|
// verify that RawMap returns a identical copy
|
|
func TestNewRawConfig_rawMap(t *testing.T) {
|
|
raw := map[string]interface{}{
|
|
"foo": "${var.bar}",
|
|
"bar": `${file("boom.txt")}`,
|
|
}
|
|
|
|
rc, err := NewRawConfig(raw)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
rawCopy := rc.RawMap()
|
|
if !reflect.DeepEqual(raw, rawCopy) {
|
|
t.Fatalf("bad: %#v", rawCopy)
|
|
}
|
|
|
|
// make sure they aren't the same map
|
|
raw["test"] = "value"
|
|
if reflect.DeepEqual(raw, rawCopy) {
|
|
t.Fatal("RawMap() didn't return a copy")
|
|
}
|
|
}
|