package config import ( "encoding/gob" "reflect" "testing" hcl2 "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hil/ast" "github.com/hashicorp/terraform/configs/hcl2shim" ) 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: hcl2shim.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": hcl2shim.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: hcl2shim.UnknownVariableValue, Type: ast.TypeUnknown, }, } if err := rc.Interpolate(vars); err != nil { t.Fatalf("err: %s", err) } actual := rc.Config() expected := map[string]interface{}{"foo": hcl2shim.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: hcl2shim.UnknownVariableValue, Type: ast.TypeUnknown, }, } if err := rc.Interpolate(vars); err != nil { t.Fatalf("err: %s", err) } actual := rc.Config() expected := map[string]interface{}{"foo": hcl2shim.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: hcl2shim.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{}{hcl2shim.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: hcl2shim.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 TestRawConfigCopy(t *testing.T) { raw := map[string]interface{}{ "foo": "${var.bar}", } rc, err := NewRawConfig(raw) if err != nil { t.Fatalf("err: %s", err) } rc.Key = "foo" if rc.Value() != "${var.bar}" { t.Fatalf("err: %#v", rc.Value()) } // Interpolate the first one 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()) } // Copy and interpolate { rc2 := rc.Copy() if rc2.Value() != "${var.bar}" { t.Fatalf("err: %#v", rc2.Value()) } vars := map[string]ast.Variable{ "var.bar": ast.Variable{ Value: "qux", Type: ast.TypeString, }, } if err := rc2.Interpolate(vars); err != nil { t.Fatalf("err: %s", err) } if rc2.Value() != "qux" { t.Fatalf("bad: %#v", rc2.Value()) } } } func TestRawConfigCopyHCL2(t *testing.T) { rc := NewRawConfigHCL2(hcl2.EmptyBody()) rc2 := rc.Copy() if rc.Body == nil { t.Errorf("RawConfig copy has a nil Body") } if rc2.Raw != nil { t.Errorf("RawConfig copy got a non-nil Raw") } } 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 an 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") } }