// computeVars takes the State and given RawConfig and processes all // the variables. This dynamically discovers the attributes instead of // using a static map[string]string that the genericWalkFn uses. func (c *Context) computeVars(raw *config.RawConfig) error { // If there isn't a raw configuration, don't do anything if raw == nil { return nil } // If there are on variables, then we're done if len(raw.Variables) == 0 { return nil } // Start building up the variables. First, defaults vs := make(map[string]string) for k, v := range c.defaultVars { vs[k] = v } // Next, the actual computed variables for n, rawV := range raw.Variables { switch v := rawV.(type) { case *config.ResourceVariable: var attr string var err error if v.Multi && v.Index == -1 { attr, err = c.computeResourceMultiVariable(v) } else { attr, err = c.computeResourceVariable(v) } if err != nil { return err } vs[n] = attr case *config.UserVariable: val, ok := c.variables[v.Name] if ok { vs[n] = val continue } // Look up if we have any variables with this prefix because // those are map overrides. Include those. for k, val := range c.variables { if strings.HasPrefix(k, v.Name+".") { vs["var."+k] = val } } } } // Interpolate the variables return raw.Interpolate(vs) }
func (ctx *BuiltinEvalContext) Interpolate( cfg *config.RawConfig, r *Resource) (*ResourceConfig, error) { if cfg != nil { scope := &InterpolationScope{ Path: ctx.Path(), Resource: r, } vs, err := ctx.Interpolater.Values(scope, cfg.Variables) if err != nil { return nil, err } // Do the interpolation if err := cfg.Interpolate(vs); err != nil { return nil, err } } result := NewResourceConfig(cfg) result.interpolateForce() return result, nil }
func TestResourceConfigGet(t *testing.T) { cases := []struct { Config map[string]interface{} Vars map[string]string Key string Value interface{} }{ { Config: nil, Key: "foo", Value: nil, }, { Config: map[string]interface{}{ "foo": "${var.foo}", }, Key: "foo", Value: "${var.foo}", }, { Config: map[string]interface{}{ "foo": "${var.foo}", }, Vars: map[string]string{"foo": "bar"}, Key: "foo", Value: "bar", }, { 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, }, // 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 { vs["var."+k] = ast.Variable{Value: v, Type: ast.TypeString} } 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, _ := rc.Get(tc.Key) 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 TestResourceConfigGet(t *testing.T) { cases := []struct { Config map[string]interface{} Vars map[string]string Key string Value interface{} }{ { Config: nil, Key: "foo", Value: nil, }, { Config: map[string]interface{}{ "foo": "${var.foo}", }, Key: "foo", Value: "${var.foo}", }, { Config: map[string]interface{}{ "foo": "${var.foo}", }, Vars: map[string]string{"foo": "bar"}, Key: "foo", Value: "bar", }, { 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, }, } 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 { vs["var."+k] = ast.Variable{Value: v, Type: ast.TypeString} } if err := rawC.Interpolate(vs); err != nil { t.Fatalf("err: %s", err) } } rc := NewResourceConfig(rawC) rc.interpolateForce() v, _ := rc.Get(tc.Key) if !reflect.DeepEqual(v, tc.Value) { t.Fatalf("%d bad: %#v", i, v) } } }
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 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 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 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) } }) } }
// computeVars takes the State and given RawConfig and processes all // the variables. This dynamically discovers the attributes instead of // using a static map[string]string that the genericWalkFn uses. func (c *walkContext) computeVars( raw *config.RawConfig, r *Resource) error { // If there isn't a raw configuration, don't do anything if raw == nil { return nil } // Copy the default variables vs := make(map[string]string) for k, v := range c.defaultVariables { vs[k] = v } // Next, the actual computed variables for n, rawV := range raw.Variables { switch v := rawV.(type) { case *config.CountVariable: switch v.Type { case config.CountValueIndex: if r != nil { vs[n] = strconv.FormatInt(int64(r.CountIndex), 10) } } case *config.ModuleVariable: if c.Operation == walkValidate { vs[n] = config.UnknownVariableValue continue } value, err := c.computeModuleVariable(v) if err != nil { return err } vs[n] = value case *config.PathVariable: switch v.Type { case config.PathValueCwd: wd, err := os.Getwd() if err != nil { return fmt.Errorf( "Couldn't get cwd for var %s: %s", v.FullKey(), err) } vs[n] = wd case config.PathValueModule: if t := c.Context.module.Child(c.Path[1:]); t != nil { vs[n] = t.Config().Dir } case config.PathValueRoot: vs[n] = c.Context.module.Config().Dir } case *config.ResourceVariable: if c.Operation == walkValidate { vs[n] = config.UnknownVariableValue continue } var attr string var err error if v.Multi && v.Index == -1 { attr, err = c.computeResourceMultiVariable(v) } else { attr, err = c.computeResourceVariable(v) } if err != nil { return err } vs[n] = attr case *config.UserVariable: val, ok := c.Variables[v.Name] if ok { vs[n] = val continue } if _, ok := vs[n]; !ok && c.Operation == walkValidate { vs[n] = config.UnknownVariableValue continue } // Look up if we have any variables with this prefix because // those are map overrides. Include those. for k, val := range c.Variables { if strings.HasPrefix(k, v.Name+".") { vs["var."+k] = val } } } } // Interpolate the variables return raw.Interpolate(vs) }