package terraform import ( "fmt" "reflect" "testing" "github.com/hashicorp/hil" "github.com/hashicorp/hil/ast" "github.com/hashicorp/terraform/config" "github.com/mitchellh/reflectwalk" ) func TestInstanceInfo(t *testing.T) { cases := []struct { Info *InstanceInfo Result string }{ { &InstanceInfo{ Id: "foo", }, "foo", }, { &InstanceInfo{ Id: "foo", ModulePath: rootModulePath, }, "foo", }, { &InstanceInfo{ Id: "foo", ModulePath: []string{"root", "consul"}, }, "module.consul.foo", }, } for i, tc := range cases { actual := tc.Info.HumanId() if actual != tc.Result { t.Fatalf("%d: %s", i, actual) } } } func TestResourceConfigGet(t *testing.T) { cases := []struct { Config map[string]interface{} Vars map[string]interface{} Key string Value interface{} }{ { Config: nil, Key: "foo", Value: nil, }, { Config: map[string]interface{}{ "foo": "bar", }, Key: "foo", Value: "bar", }, { Config: map[string]interface{}{ "foo": "${var.foo}", }, Key: "foo", Value: "${var.foo}", }, { Config: map[string]interface{}{ "foo": "${var.foo}", }, Vars: map[string]interface{}{"foo": unknownValue()}, Key: "foo", Value: "${var.foo}", }, { Config: map[string]interface{}{ "foo": []interface{}{1, 2, 5}, }, Key: "foo.0", Value: 1, }, { Config: map[string]interface{}{ "foo": []interface{}{1, 2, 5}, }, Key: "foo.5", Value: nil, }, // get from map { Config: map[string]interface{}{ "mapname": []map[string]interface{}{ map[string]interface{}{"key": 1}, }, }, Key: "mapname.0.key", Value: 1, }, // get from map with dot in key { Config: map[string]interface{}{ "mapname": []map[string]interface{}{ map[string]interface{}{"key.name": 1}, }, }, Key: "mapname.0.key.name", Value: 1, }, // get from map with overlapping key names { Config: map[string]interface{}{ "mapname": []map[string]interface{}{ map[string]interface{}{ "key.name": 1, "key.name.2": 2, }, }, }, Key: "mapname.0.key.name.2", Value: 2, }, { Config: map[string]interface{}{ "mapname": []map[string]interface{}{ map[string]interface{}{ "key.name": 1, "key.name.foo": 2, }, }, }, Key: "mapname.0.key.name", Value: 1, }, { Config: map[string]interface{}{ "mapname": []map[string]interface{}{ map[string]interface{}{ "listkey": []map[string]interface{}{ {"key": 3}, }, }, }, }, Key: "mapname.0.listkey.0.key", Value: 3, }, // A map assigned to a list via interpolation should Get a non-existent // value. The test code now also checks that Get doesn't return (nil, // true), which it previously did for this configuration. { Config: map[string]interface{}{ "maplist": "${var.maplist}", }, Key: "maplist.0", Value: nil, }, // Reference list of maps variable. // This does not work from GetRaw. { Vars: map[string]interface{}{ "maplist": []interface{}{ map[string]interface{}{ "key": "a", }, map[string]interface{}{ "key": "b", }, }, }, Config: map[string]interface{}{ "maplist": "${var.maplist}", }, Key: "maplist.0", Value: map[string]interface{}{"key": "a"}, }, // Reference a map-of-lists variable. // This does not work from GetRaw. { Vars: map[string]interface{}{ "listmap": map[string]interface{}{ "key1": []interface{}{"a", "b"}, "key2": []interface{}{"c", "d"}, }, }, Config: map[string]interface{}{ "listmap": "${var.listmap}", }, Key: "listmap.key1", Value: []interface{}{"a", "b"}, }, // FIXME: this is ambiguous, and matches the nested map // leaving here to catch this behaviour if it changes. { Config: map[string]interface{}{ "mapname": []map[string]interface{}{ map[string]interface{}{ "key.name": 1, "key.name.0": 2, "key": map[string]interface{}{"name": 3}, }, }, }, Key: "mapname.0.key.name", Value: 3, }, /* // TODO: can't access this nested list at all. // FIXME: key with name matching substring of nested list can panic { Config: map[string]interface{}{ "mapname": []map[string]interface{}{ map[string]interface{}{ "key.name": []map[string]interface{}{ {"subkey": 1}, }, "key": 3, }, }, }, Key: "mapname.0.key.name.0.subkey", Value: 3, }, */ } for i, tc := range cases { var rawC *config.RawConfig if tc.Config != nil { var err error rawC, err = config.NewRawConfig(tc.Config) if err != nil { t.Fatalf("err: %s", err) } } if tc.Vars != nil { vs := make(map[string]ast.Variable) for k, v := range tc.Vars { hilVar, err := hil.InterfaceToVariable(v) if err != nil { t.Fatalf("%#v to var: %s", v, err) } vs["var."+k] = hilVar } if err := rawC.Interpolate(vs); err != nil { t.Fatalf("err: %s", err) } } rc := NewResourceConfig(rawC) rc.interpolateForce() // Test getting a key t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) { v, ok := rc.Get(tc.Key) if ok && v == nil { t.Fatal("(nil, true) returned from Get") } if !reflect.DeepEqual(v, tc.Value) { t.Fatalf("%d bad: %#v", i, v) } }) // If we have vars, we don't test copying if len(tc.Vars) > 0 { continue } // Test copying and equality t.Run(fmt.Sprintf("copy-and-equal-%d", i), func(t *testing.T) { copy := rc.DeepCopy() if !reflect.DeepEqual(copy, rc) { t.Fatalf("bad:\n\n%#v\n\n%#v", copy, rc) } if !copy.Equal(rc) { t.Fatalf("copy != rc:\n\n%#v\n\n%#v", copy, rc) } if !rc.Equal(copy) { t.Fatalf("rc != copy:\n\n%#v\n\n%#v", copy, rc) } }) } } func TestResourceConfigGetRaw(t *testing.T) { cases := []struct { Config map[string]interface{} Vars map[string]interface{} Key string Value interface{} }{ // Referencing a list-of-maps variable doesn't work from GetRaw. // The ConfigFieldReader currently catches this case and looks up the // variable in the config. { Vars: map[string]interface{}{ "maplist": []interface{}{ map[string]interface{}{ "key": "a", }, map[string]interface{}{ "key": "b", }, }, }, Config: map[string]interface{}{ "maplist": "${var.maplist}", }, Key: "maplist.0", Value: nil, }, // Reference a map-of-lists variable. // The ConfigFieldReader currently catches this case and looks up the // variable in the config. { Vars: map[string]interface{}{ "listmap": map[string]interface{}{ "key1": []interface{}{"a", "b"}, "key2": []interface{}{"c", "d"}, }, }, Config: map[string]interface{}{ "listmap": "${var.listmap}", }, Key: "listmap.key1", Value: nil, }, } for i, tc := range cases { var rawC *config.RawConfig if tc.Config != nil { var err error rawC, err = config.NewRawConfig(tc.Config) if err != nil { t.Fatalf("err: %s", err) } } if tc.Vars != nil { vs := make(map[string]ast.Variable) for k, v := range tc.Vars { hilVar, err := hil.InterfaceToVariable(v) if err != nil { t.Fatalf("%#v to var: %s", v, err) } vs["var."+k] = hilVar } if err := rawC.Interpolate(vs); err != nil { t.Fatalf("err: %s", err) } } rc := NewResourceConfig(rawC) rc.interpolateForce() // Test getting a key t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) { v, ok := rc.GetRaw(tc.Key) if ok && v == nil { t.Fatal("(nil, true) returned from GetRaw") } if !reflect.DeepEqual(v, tc.Value) { t.Fatalf("%d bad: %#v", i, v) } }) } } func TestResourceConfigIsComputed(t *testing.T) { cases := []struct { Name string Config map[string]interface{} Vars map[string]interface{} Key string Result bool }{ { Name: "basic value", Config: map[string]interface{}{ "foo": "${var.foo}", }, Vars: map[string]interface{}{ "foo": unknownValue(), }, Key: "foo", Result: true, }, { Name: "set with a computed element", Config: map[string]interface{}{ "foo": "${var.foo}", }, Vars: map[string]interface{}{ "foo": []string{ "a", unknownValue(), }, }, Key: "foo", Result: true, }, { Name: "set with no computed elements", Config: map[string]interface{}{ "foo": "${var.foo}", }, Vars: map[string]interface{}{ "foo": []string{ "a", "b", }, }, Key: "foo", Result: false, }, /* { Name: "set count with computed elements", Config: map[string]interface{}{ "foo": "${var.foo}", }, Vars: map[string]interface{}{ "foo": []string{ "a", unknownValue(), }, }, Key: "foo.#", Result: true, }, */ { Name: "set count with computed elements", Config: map[string]interface{}{ "foo": []interface{}{"${var.foo}"}, }, Vars: map[string]interface{}{ "foo": []string{ "a", unknownValue(), }, }, Key: "foo.#", Result: true, }, { Name: "nested set with computed elements", Config: map[string]interface{}{ "route": []map[string]interface{}{ map[string]interface{}{ "index": "1", "gateway": []interface{}{"${var.foo}"}, }, }, }, Vars: map[string]interface{}{ "foo": unknownValue(), }, Key: "route.0.gateway", Result: true, }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { var rawC *config.RawConfig if tc.Config != nil { var err error rawC, err = config.NewRawConfig(tc.Config) if err != nil { t.Fatalf("err: %s", err) } } if tc.Vars != nil { vs := make(map[string]ast.Variable) for k, v := range tc.Vars { hilVar, err := hil.InterfaceToVariable(v) if err != nil { t.Fatalf("%#v to var: %s", v, err) } vs["var."+k] = hilVar } if err := rawC.Interpolate(vs); err != nil { t.Fatalf("err: %s", err) } } rc := NewResourceConfig(rawC) rc.interpolateForce() t.Logf("Config: %#v", rc) actual := rc.IsComputed(tc.Key) if actual != tc.Result { t.Fatalf("bad: %#v", actual) } }) } } func TestResourceConfigCheckSet(t *testing.T) { cases := []struct { Name string Config map[string]interface{} Vars map[string]interface{} Input []string Errs bool }{ { Name: "computed basic", Config: map[string]interface{}{ "foo": "${var.foo}", }, Vars: map[string]interface{}{ "foo": unknownValue(), }, Input: []string{"foo"}, Errs: false, }, { Name: "basic", Config: map[string]interface{}{ "foo": "bar", }, Vars: nil, Input: []string{"foo"}, Errs: false, }, { Name: "basic with not set", Config: map[string]interface{}{ "foo": "bar", }, Vars: nil, Input: []string{"foo", "bar"}, Errs: true, }, { Name: "basic with one computed", Config: map[string]interface{}{ "foo": "bar", "bar": "${var.foo}", }, Vars: map[string]interface{}{ "foo": unknownValue(), }, Input: []string{"foo", "bar"}, Errs: false, }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { var rawC *config.RawConfig if tc.Config != nil { var err error rawC, err = config.NewRawConfig(tc.Config) if err != nil { t.Fatalf("err: %s", err) } } if tc.Vars != nil { vs := make(map[string]ast.Variable) for k, v := range tc.Vars { hilVar, err := hil.InterfaceToVariable(v) if err != nil { t.Fatalf("%#v to var: %s", v, err) } vs["var."+k] = hilVar } if err := rawC.Interpolate(vs); err != nil { t.Fatalf("err: %s", err) } } rc := NewResourceConfig(rawC) rc.interpolateForce() t.Logf("Config: %#v", rc) errs := rc.CheckSet(tc.Input) if tc.Errs != (len(errs) > 0) { t.Fatalf("bad: %#v", errs) } }) } } func TestResourceConfigDeepCopy_nil(t *testing.T) { var nilRc *ResourceConfig actual := nilRc.DeepCopy() if actual != nil { t.Fatalf("bad: %#v", actual) } } func TestResourceConfigDeepCopy_nilComputed(t *testing.T) { rc := &ResourceConfig{} actual := rc.DeepCopy() if actual.ComputedKeys != nil { t.Fatalf("bad: %#v", actual) } } func TestResourceConfigEqual_nil(t *testing.T) { var nilRc *ResourceConfig notNil := NewResourceConfig(nil) if nilRc.Equal(notNil) { t.Fatal("should not be equal") } if notNil.Equal(nilRc) { t.Fatal("should not be equal") } } func TestResourceConfigEqual_computedKeyOrder(t *testing.T) { c := map[string]interface{}{"foo": "${a.b.c}"} rc := NewResourceConfig(config.TestRawConfig(t, c)) rc2 := NewResourceConfig(config.TestRawConfig(t, c)) // Set the computed keys manual rc.ComputedKeys = []string{"foo", "bar"} rc2.ComputedKeys = []string{"bar", "foo"} if !rc.Equal(rc2) { t.Fatal("should be equal") } } func TestUnknownCheckWalker(t *testing.T) { cases := []struct { Name string Input interface{} Result bool }{ { "primitive", 42, false, }, { "primitive computed", unknownValue(), true, }, { "list", []interface{}{"foo", unknownValue()}, true, }, { "nested list", []interface{}{ "foo", []interface{}{unknownValue()}, }, true, }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { var w unknownCheckWalker if err := reflectwalk.Walk(tc.Input, &w); err != nil { t.Fatalf("err: %s", err) } if w.Unknown != tc.Result { t.Fatalf("bad: %v", w.Unknown) } }) } } func testResourceConfig( t *testing.T, c map[string]interface{}) *ResourceConfig { raw, err := config.NewRawConfig(c) if err != nil { t.Fatalf("err: %s", err) } return NewResourceConfig(raw) }