vars: allow special char in var lookup path

* extract source, path and fields from secrect name instead of treating it
as black box
* move nested variables lookup out of template into static_vars
* refactor trackers error handling

Signed-off-by: Rui Yang <ryang@pivotal.io>
Co-authored-by: Bohan Chen <bochen@pivotal.io>
This commit is contained in:
Rui Yang 2020-07-13 12:38:56 -04:00 committed by Rui Yang
parent a822574e7d
commit 1a6e35143a
11 changed files with 251 additions and 248 deletions

View File

@ -21,30 +21,39 @@ func (err UnusedVarsError) Error() string {
return fmt.Sprintf("unused vars: %s", strings.Join(err.Vars, ", "))
}
type MissingSourceError struct {
Name string
Source string
}
func (err MissingSourceError) Error() string {
return fmt.Sprintf("missing source '%s' in var: %s", err.Source, err.Name)
}
type MissingFieldError struct {
Path string
Name string
Field string
}
func (err MissingFieldError) Error() string {
return fmt.Sprintf("missing field '%s' in var: %s", err.Field, err.Path)
return fmt.Sprintf("missing field '%s' in var: %s", err.Field, err.Name)
}
type InvalidFieldError struct {
Path string
Name string
Field string
Value interface{}
}
func (err InvalidFieldError) Error() string {
return fmt.Sprintf("cannot access field '%s' of non-map value ('%T') from var: %s", err.Field, err.Value, err.Path)
return fmt.Sprintf("cannot access field '%s' of non-map value ('%T') from var: %s", err.Field, err.Value, err.Name)
}
type InvalidInterpolationError struct {
Path string
Name string
Value interface{}
}
func (err InvalidInterpolationError) Error() string {
return fmt.Sprintf("cannot interpolate non-primitive value (%T) from var: %s", err.Value, err.Path)
return fmt.Sprintf("cannot interpolate non-primitive value (%T) from var: %s", err.Value, err.Name)
}

View File

@ -23,7 +23,7 @@ var _ = Describe("MultiVariables", func() {
vars2 := StaticVariables{"key2": "val"}
vars := NewMultiVars([]Variables{vars1, vars2})
val, found, err := vars.Get(VariableDefinition{Name: "key3"})
val, found, err := vars.Get(VariableDefinition{Ref: VariableReference{Path: "key3"}})
Expect(val).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).ToNot(HaveOccurred())
@ -34,7 +34,7 @@ var _ = Describe("MultiVariables", func() {
vars2 := &FakeVariables{GetErr: errors.New("fake-err")}
vars := NewMultiVars([]Variables{vars1, vars2})
val, found, err := vars.Get(VariableDefinition{Name: "key3"})
val, found, err := vars.Get(VariableDefinition{Ref: VariableReference{Path: "key3"}})
Expect(val).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).To(Equal(errors.New("fake-err")))
@ -46,7 +46,7 @@ var _ = Describe("MultiVariables", func() {
vars3 := &FakeVariables{GetErr: errors.New("fake-err")}
vars := NewMultiVars([]Variables{vars1, vars2, vars3})
val, found, err := vars.Get(VariableDefinition{Name: "key2"})
val, found, err := vars.Get(VariableDefinition{Ref: VariableReference{Path: "key2"}})
Expect(val).To(Equal("val"))
Expect(found).To(BeTrue())
Expect(err).ToNot(HaveOccurred())
@ -61,12 +61,12 @@ var _ = Describe("MultiVariables", func() {
vars2 := StaticVariables{"key2": "val"}
vars := NewMultiVars([]Variables{vars1, vars2})
val, found, err := vars.Get(VariableDefinition{Name: "key2", Type: "type", Options: "opts"})
val, found, err := vars.Get(VariableDefinition{Ref: VariableReference{Path: "key2"}, Type: "type", Options: "opts"})
Expect(val).To(Equal("val"))
Expect(found).To(BeTrue())
Expect(err).ToNot(HaveOccurred())
Expect(vars1.GetVarDef).To(Equal(VariableDefinition{Name: "key2", Type: "type", Options: "opts"}))
Expect(vars1.GetVarDef).To(Equal(VariableDefinition{Ref: VariableReference{Path: "key2"}, Type: "type", Options: "opts"}))
})
})
@ -79,7 +79,12 @@ var _ = Describe("MultiVariables", func() {
vars := NewMultiVars([]Variables{StaticVariables{"a": "1", "b": "2"}, StaticVariables{"b": "3", "c": "4"}})
defs, err = vars.List()
Expect(defs).To(ConsistOf([]VariableDefinition{{Name: "a"}, {Name: "b"}, {Name: "b"}, {Name: "c"}}))
Expect(defs).To(ConsistOf([]VariableDefinition{
{Ref: VariableReference{Path: "a"}},
{Ref: VariableReference{Path: "b"}},
{Ref: VariableReference{Path: "b"}},
{Ref: VariableReference{Path: "c"}},
}))
Expect(err).ToNot(HaveOccurred())
})
})

View File

@ -1,10 +1,5 @@
package vars
import (
"fmt"
"strings"
)
type NamedVariables map[string]Variables
// Get checks var_source if presents, then forward var to underlying secret manager.
@ -12,34 +7,30 @@ type NamedVariables map[string]Variables
// the var_source name, and "foo" is the real var name that should be forwarded
// to the underlying secret manager.
func (m NamedVariables) Get(varDef VariableDefinition) (interface{}, bool, error) {
var sourceName, varName string
parts := strings.Split(varDef.Name, ":")
if len(parts) == 1 {
// No source name, then no need to query named vars.
if varDef.Ref.Source == "" {
return nil, false, nil
} else if len(parts) == 2 {
sourceName = parts[0]
varName = parts[1]
} else {
return nil, false, fmt.Errorf("invalid var: %s", varDef.Name)
}
if vars, ok := m[sourceName]; ok {
return vars.Get(VariableDefinition{Name: varName})
if vars, ok := m[varDef.Ref.Source]; ok {
return vars.Get(varDef)
}
return nil, false, fmt.Errorf("unknown var source: %s", sourceName)
return nil, false, MissingSourceError{Name: varDef.Ref.Name, Source: varDef.Ref.Source}
}
func (m NamedVariables) List() ([]VariableDefinition, error) {
var allDefs []VariableDefinition
for _, vars := range m {
for source, vars := range m {
defs, err := vars.List()
if err != nil {
return nil, err
}
for i, _ := range defs {
defs[i].Ref.Source = source
}
allDefs = append(allDefs, defs...)
}

View File

@ -23,33 +23,11 @@ var _ = Describe("NamedVariables", func() {
vars2 := StaticVariables{"key2": "val"}
vars := NamedVariables{"s1": vars1, "s2": vars2}
val, found, err := vars.Get(VariableDefinition{Name: "s3:key1"})
val, found, err := vars.Get(VariableDefinition{Ref: VariableReference{Name: "s3:foo", Source: "s3"}})
Expect(val).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(errors.New("unknown var source: s3")))
})
It("return no value and not found if var source name is not specified", func() {
vars1 := StaticVariables{"key1": "val"}
vars2 := StaticVariables{"key2": "val"}
vars := NamedVariables{"s1": vars1, "s2": vars2}
val, found, err := vars.Get(VariableDefinition{Name: "key1"})
Expect(val).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).ToNot(HaveOccurred())
})
It("return error as soon as one source fails", func() {
vars1 := StaticVariables{"key1": "val"}
vars2 := &FakeVariables{GetErr: errors.New("fake-err")}
vars := NamedVariables{"s1": vars1, "s2": vars2}
val, found, err := vars.Get(VariableDefinition{Name: "s2:key3"})
Expect(val).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).To(Equal(errors.New("fake-err")))
Expect(err.Error()).To(Equal("missing source 's3' in var: s3:foo"))
})
It("return found value as soon as one source succeeds", func() {
@ -58,7 +36,7 @@ var _ = Describe("NamedVariables", func() {
vars3 := &FakeVariables{GetErr: errors.New("fake-err")}
vars := NamedVariables{"s1": vars1, "s2": vars2, "s3": vars3}
val, found, err := vars.Get(VariableDefinition{Name: "s2:key2"})
val, found, err := vars.Get(VariableDefinition{Ref: VariableReference{Source: "s2", Path: "key2"}})
Expect(val).To(Equal("val"))
Expect(found).To(BeTrue())
Expect(err).ToNot(HaveOccurred())
@ -81,7 +59,12 @@ var _ = Describe("NamedVariables", func() {
}
defs, err = vars.List()
Expect(defs).To(ConsistOf([]VariableDefinition{{Name: "a"}, {Name: "b"}, {Name: "b"}, {Name: "c"}}))
Expect(defs).To(ConsistOf([]VariableDefinition{
{Ref: VariableReference{Source: "s1", Path: "a"}},
{Ref: VariableReference{Source: "s1", Path: "b"}},
{Ref: VariableReference{Source: "s2", Path: "b"}},
{Ref: VariableReference{Source: "s2", Path: "c"}},
}))
Expect(err).ToNot(HaveOccurred())
})
})

View File

@ -1,54 +1,55 @@
package vars
import (
"strings"
)
type StaticVariables map[string]interface{}
var _ Variables = StaticVariables{}
func (v StaticVariables) Get(varDef VariableDefinition) (interface{}, bool, error) {
val, found := v.processed()[varDef.Name]
name := varDef.Ref.Name
val, found := v[varDef.Ref.Path]
if !found {
return val, found, nil
}
for _, seg := range varDef.Ref.Fields {
switch v := val.(type) {
case map[interface{}]interface{}:
var found bool
val, found = v[seg]
if !found {
return nil, false, MissingFieldError{
Name: name,
Field: seg,
}
}
case map[string]interface{}:
var found bool
val, found = v[seg]
if !found {
return nil, false, MissingFieldError{
Name: name,
Field: seg,
}
}
default:
return nil, false, InvalidFieldError{
Name: name,
Field: seg,
Value: val,
}
}
}
return val, found, nil
}
func (v StaticVariables) List() ([]VariableDefinition, error) {
var defs []VariableDefinition
for name, _ := range v.processed() {
defs = append(defs, VariableDefinition{Name: name})
for name, _ := range v {
defs = append(defs, VariableDefinition{Ref: VariableReference{Path: name}})
}
return defs, nil
}
func (v StaticVariables) processed() map[string]interface{} {
processed := map[interface{}]interface{}{}
for name, val := range v {
pieces := strings.Split(name, ".")
if len(pieces) == 1 {
processed[name] = val
} else {
mapRef := processed
for _, p := range pieces[0 : len(pieces)-1] {
if _, found := processed[p]; !found {
mapRef[p] = map[interface{}]interface{}{}
}
mapRef = mapRef[p].(map[interface{}]interface{})
}
mapRef[pieces[len(pieces)-1]] = val
}
}
processedTyped := map[string]interface{}{}
for k, v := range processed {
processedTyped[k.(string)] = v
}
return processedTyped
}

View File

@ -12,7 +12,7 @@ var _ = Describe("StaticVariables", func() {
It("returns value and found if key is found", func() {
a := StaticVariables{"a": "foo"}
val, found, err := a.Get(VariableDefinition{Name: "a"})
val, found, err := a.Get(VariableDefinition{Ref: VariableReference{Path: "a"}})
Expect(val).To(Equal("foo"))
Expect(found).To(BeTrue())
Expect(err).ToNot(HaveOccurred())
@ -21,36 +21,33 @@ var _ = Describe("StaticVariables", func() {
It("returns nil and not found if key is not found", func() {
a := StaticVariables{"a": "foo"}
val, found, err := a.Get(VariableDefinition{Name: "b"})
val, found, err := a.Get(VariableDefinition{Ref: VariableReference{Path: "b"}})
Expect(val).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).ToNot(HaveOccurred())
})
It("recognizes keys that use dot notation for subvalues", func() {
a := StaticVariables{"a.subkey": "foo", "a.subkey2": "foo2"}
It("recognizes keys that has dot and colon", func() {
a := StaticVariables{"a.foo:bar": "foo"}
val, found, err := a.Get(VariableDefinition{Name: "a"})
Expect(val).To(Equal(map[interface{}]interface{}{"subkey": "foo", "subkey2": "foo2"}))
val, found, err := a.Get(VariableDefinition{Ref: VariableReference{Path: "a.foo:bar"}})
Expect(val).To(Equal("foo"))
Expect(found).To(BeTrue())
Expect(err).ToNot(HaveOccurred())
})
a = StaticVariables{"a.subkey.subsubkey": "foo", "a.subkey2": "foo2"}
val, found, err = a.Get(VariableDefinition{Name: "a"})
Expect(val).To(Equal(map[interface{}]interface{}{
"subkey": map[interface{}]interface{}{"subsubkey": "foo"},
It("recognizes keys has multiple fields", func() {
a := StaticVariables{"a": map[string]interface{}{
"subkey": map[string]interface{}{
"subsubkey": "foo",
},
"subkey2": "foo2",
}))
}}
val, found, err := a.Get(VariableDefinition{Ref: VariableReference{Path: "a", Fields: []string{"subkey", "subsubkey"}}})
Expect(err).ToNot(HaveOccurred())
Expect(val).To(Equal("foo"))
Expect(found).To(BeTrue())
Expect(err).ToNot(HaveOccurred())
a = StaticVariables{"a.subkey": "foo"}
val, found, err = a.Get(VariableDefinition{Name: "a.subkey"})
Expect(val).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).ToNot(HaveOccurred())
})
})
@ -61,7 +58,10 @@ var _ = Describe("StaticVariables", func() {
Expect(err).ToNot(HaveOccurred())
defs, err = StaticVariables{"a": "1", "b": "2"}.List()
Expect(defs).To(ConsistOf([]VariableDefinition{{Name: "a"}, {Name: "b"}}))
Expect(defs).To(ConsistOf([]VariableDefinition{
{Ref: VariableReference{Path: "a"}},
{Ref: VariableReference{Path: "b"}},
}))
Expect(err).ToNot(HaveOccurred())
})
})

View File

@ -1,7 +1,6 @@
package vars
import (
"errors"
"fmt"
"regexp"
"sort"
@ -51,7 +50,7 @@ func (t Template) Evaluate(vars Variables, opts EvaluateOpts) ([]byte, error) {
func (t Template) interpolateRoot(obj interface{}, tracker varsTracker) (interface{}, error) {
var err error
obj, err = interpolator{}.Interpolate(obj, varsLookup{tracker})
obj, err = interpolator{}.Interpolate(obj, tracker)
if err != nil {
return nil, err
}
@ -62,20 +61,21 @@ func (t Template) interpolateRoot(obj interface{}, tracker varsTracker) (interfa
type interpolator struct{}
var (
interpolationRegex = regexp.MustCompile(`\(\((!?([-/\.\w\pL]+\:)?[-/\.\w\pL]+)\)\)`)
pathRegex = regexp.MustCompile(`("[^"]*"|[^\.]+)+`)
interpolationRegex = regexp.MustCompile(`\(\((([-/\.\w\pL]+\:)?[-/\.:"\w\pL]+)\)\)`)
interpolationAnchoredRegex = regexp.MustCompile("\\A" + interpolationRegex.String() + "\\z")
)
func (i interpolator) Interpolate(node interface{}, varsLookup varsLookup) (interface{}, error) {
func (i interpolator) Interpolate(node interface{}, tracker varsTracker) (interface{}, error) {
switch typedNode := node.(type) {
case map[interface{}]interface{}:
for k, v := range typedNode {
evaluatedValue, err := i.Interpolate(v, varsLookup)
evaluatedValue, err := i.Interpolate(v, tracker)
if err != nil {
return nil, err
}
evaluatedKey, err := i.Interpolate(k, varsLookup)
evaluatedKey, err := i.Interpolate(k, tracker)
if err != nil {
return nil, err
}
@ -87,7 +87,7 @@ func (i interpolator) Interpolate(node interface{}, varsLookup varsLookup) (inte
case []interface{}:
for idx, x := range typedNode {
var err error
typedNode[idx], err = i.Interpolate(x, varsLookup)
typedNode[idx], err = i.Interpolate(x, tracker)
if err != nil {
return nil, err
}
@ -95,9 +95,9 @@ func (i interpolator) Interpolate(node interface{}, varsLookup varsLookup) (inte
case string:
for _, name := range i.extractVarNames(typedNode) {
foundVal, found, err := varsLookup.Get(name)
foundVal, found, err := tracker.Get(name)
if err != nil {
return nil, fmt.Errorf("var lookup '%s': %w", name, err)
return nil, err
}
if found {
@ -110,10 +110,9 @@ func (i interpolator) Interpolate(node interface{}, varsLookup varsLookup) (inte
case string, int, int16, int32, int64, uint, uint16, uint32, uint64:
foundValStr := fmt.Sprintf("%v", foundVal)
typedNode = strings.Replace(typedNode, fmt.Sprintf("((%s))", name), foundValStr, -1)
typedNode = strings.Replace(typedNode, fmt.Sprintf("((!%s))", name), foundValStr, -1)
default:
return nil, InvalidInterpolationError{
Path: name,
Name: name,
Value: foundVal,
}
}
@ -130,81 +129,44 @@ func (i interpolator) extractVarNames(value string) []string {
var names []string
for _, match := range interpolationRegex.FindAllSubmatch([]byte(value), -1) {
names = append(names, strings.TrimPrefix(string(match[1]), "!"))
names = append(names, string(match[1]))
}
return names
}
type varsLookup struct {
varsTracker
}
func parseVarName(name string) VariableReference {
var pathPieces []string
var ErrEmptyVar = errors.New("empty var")
varRef := VariableReference{Name: name}
// Get value of a var. Name can be the following formats: 1) 'foo', where foo
// is var name; 2) 'foo:bar', where foo is var source name, and bar is var name;
// 3) '.:foo', where . means a local var, foo is var name.
func (l varsLookup) Get(name string) (interface{}, bool, error) {
var splitName []string
if strings.Index(name, ":") > 0 {
parts := strings.Split(name, ":")
splitName = strings.Split(parts[1], ".")
splitName[0] = fmt.Sprintf("%s:%s", parts[0], splitName[0])
parts := strings.SplitN(name, ":", 2)
varRef.Source = parts[0]
pathPieces = pathRegex.FindAllString(parts[1], -1)
} else {
splitName = strings.Split(name, ".")
pathPieces = pathRegex.FindAllString(name, -1)
}
// this should be impossible since interpolationRegex only matches non-empty
// vars, but better to error than to panic
if len(splitName) == 0 {
return nil, false, ErrEmptyVar
varRef.Path = strings.ReplaceAll(pathPieces[0], "\"", "")
if len(pathPieces) >= 2 {
varRef.Fields = pathPieces[1:]
}
val, found, err := l.varsTracker.Get(splitName[0])
if !found || err != nil {
return val, found, err
}
for _, seg := range splitName[1:] {
switch v := val.(type) {
case map[interface{}]interface{}:
var found bool
val, found = v[seg]
if !found {
return nil, false, MissingFieldError{
Path: name,
Field: seg,
}
}
case map[string]interface{}:
var found bool
val, found = v[seg]
if !found {
return nil, false, MissingFieldError{
Path: name,
Field: seg,
}
}
default:
return nil, false, InvalidFieldError{
Path: name,
Field: seg,
Value: val,
}
}
}
return val, true, err
return varRef
}
// var ErrEmptyVar = errors.New("empty var")
type varsTracker struct {
vars Variables
expectAllFound bool
expectAllUsed bool
missing map[string]struct{} // track missing var names
missing map[string]error // track missing var names
visited map[string]struct{}
visitedAll map[string]struct{} // track all var names that were accessed
}
@ -214,18 +176,23 @@ func newVarsTracker(vars Variables, expectAllFound, expectAllUsed bool) varsTrac
vars: vars,
expectAllFound: expectAllFound,
expectAllUsed: expectAllUsed,
missing: map[string]struct{}{},
missing: map[string]error{},
visited: map[string]struct{}{},
visitedAll: map[string]struct{}{},
}
}
func (t varsTracker) Get(name string) (interface{}, bool, error) {
t.visitedAll[name] = struct{}{}
// Get value of a var. Name can be the following formats: 1) 'foo', where foo
// is var name; 2) 'foo:bar', where foo is var source name, and bar is var name;
// 3) '.:foo', where . means a local var, foo is var name.
func (t varsTracker) Get(varName string) (interface{}, bool, error) {
t.visitedAll[varName] = struct{}{}
val, found, err := t.vars.Get(VariableDefinition{Name: name})
varRef := parseVarName(varName)
val, found, err := t.vars.Get(VariableDefinition{Ref: varRef})
if !found {
t.missing[name] = struct{}{}
t.missing[varRef.Path] = err
}
return val, found, err
@ -250,7 +217,17 @@ func (t varsTracker) MissingError() error {
return nil
}
return UndefinedVarsError{Vars: names(t.missing)}
var missingErrors error
var undefinedVars []string
for path, err := range t.missing {
if err != nil {
missingErrors = multierror.Append(missingErrors, err)
} else {
undefinedVars = append(undefinedVars, path)
}
}
sort.Strings(undefinedVars)
return multierror.Append(UndefinedVarsError{Vars: undefinedVars}, missingErrors)
}
func (t varsTracker) ExtraError() error {
@ -266,8 +243,8 @@ func (t varsTracker) ExtraError() error {
unusedNames := map[string]struct{}{}
for _, def := range allDefs {
if _, found := t.visitedAll[def.Name]; !found {
unusedNames[def.Name] = struct{}{}
if _, found := t.visitedAll[def.Ref.Path]; !found {
unusedNames[def.Ref.Path] = struct{}{}
}
}

View File

@ -77,28 +77,19 @@ name6:
`)))
})
It("can interpolate different data types into a byte slice with !key", func() {
template := NewTemplate([]byte("otherstuff: ((!boule))"))
vars := StaticVariables{"boule": true}
result, err := template.Evaluate(vars, EvaluateOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(result).To(Equal([]byte("otherstuff: true\n")))
})
It("return errors if there are missing variable keys and ExpectAllKeys is true", func() {
template := NewTemplate([]byte(`
((key4))_array:
- ((key_in_array))
((key)): ((key2))
((key3)): 2
dup-key: ((key3))
((key4))_array:
- ((key_in_array))
`))
vars := StaticVariables{"key3": "foo"}
_, err := template.Evaluate(vars, EvaluateOpts{ExpectAllKeys: true})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("undefined vars: key, key2, key4, key_in_array"))
Expect(err.Error()).To(ContainSubstring("undefined vars: key, key2, key4, key_in_array"))
})
It("does not return error if there are missing variable keys and ExpectAllKeys is false", func() {
@ -181,6 +172,21 @@ dup-key: ((key3))
Expect(result).To(Equal([]byte("dash: underscore\n")))
})
It("can interpolate keys with dot and colon into a byte slice", func() {
template := NewTemplate([]byte("bar: ((foo:\"with.dot:colon\".buzz))"))
vars := NamedVariables{
"foo": StaticVariables{
"with.dot:colon": map[string]interface{}{
"buzz": "fuzz",
},
},
}
result, err := template.Evaluate(vars, EvaluateOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(string(result)).To(Equal(string([]byte("bar: fuzz\n"))))
})
It("can interpolate a secret key in the middle of a string", func() {
template := NewTemplate([]byte("url: https://((ip))"))
vars := StaticVariables{
@ -204,18 +210,6 @@ dup-key: ((key3))
Expect(result).To(Equal([]byte("uri: nats://nats:secret@10.0.0.0:4222\n")))
})
It("can interpolate multiple secret keys in the middle of a string even if keys have ! marks", func() {
template := NewTemplate([]byte("uri: nats://nats:((!password))@((ip)):4222"))
vars := StaticVariables{
"password": "secret",
"ip": "10.0.0.0",
}
result, err := template.Evaluate(vars, EvaluateOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(result).To(Equal([]byte("uri: nats://nats:secret@10.0.0.0:4222\n")))
})
It("can interpolate multiple keys of type string and int in the middle of a string", func() {
template := NewTemplate([]byte("address: ((ip)):((port))"))
vars := StaticVariables{
@ -299,18 +293,11 @@ dup-key: ((key3))
Expect(result).To(Equal([]byte("(()\n")))
})
It("strips away ! from variable keys", func() {
template := NewTemplate([]byte("abc: ((!key))\nxyz: [((!key))]"))
vars := StaticVariables{"key": "val"}
result, err := template.Evaluate(vars, EvaluateOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(result).To(Equal([]byte("abc: val\nxyz:\n- val\n")))
})
It("allow var_source name in variable keys", func() {
template := NewTemplate([]byte("abc: ((dummy:key))"))
vars := StaticVariables{"dummy:key": "val"}
vars := NamedVariables{
"dummy": StaticVariables{"key": "val"},
}
result, err := template.Evaluate(vars, EvaluateOpts{})
Expect(err).NotTo(HaveOccurred())

View File

@ -5,8 +5,15 @@ type Variables interface {
List() ([]VariableDefinition, error)
}
type VariableReference struct {
Name string
Source string
Path string
Fields []string
}
type VariableDefinition struct {
Name string
Ref VariableReference
Type string
Options interface{}
}

View File

@ -1,7 +1,6 @@
package vars
import (
"fmt"
"strings"
"sync"
)
@ -9,7 +8,6 @@ import (
// CredVarsTracker implements the interface Variables. It wraps a secret manager and
// tracks key-values fetched from the secret managers. It also provides a method to
// thread-safely iterate interpolated key-values.
type CredVarsTrackerIterator interface {
YieldCred(string, string)
}
@ -57,13 +55,10 @@ func (t *credVarsTracker) Get(varDef VariableDefinition) (interface{}, bool, err
)
redact := true
parts := strings.Split(varDef.Name, ":")
if len(parts) == 2 && parts[0] == "." {
varDef.Name = parts[1]
if varDef.Ref.Source == "." {
val, found, err = t.localVars.Get(varDef)
if found {
parts = strings.Split(varDef.Name, ".")
if _, ok := t.noRedactVarNames[parts[0]]; ok {
if _, ok := t.noRedactVarNames[varDef.Ref.Path]; ok {
redact = false
}
}
@ -73,30 +68,70 @@ func (t *credVarsTracker) Get(varDef VariableDefinition) (interface{}, bool, err
if t.enabled && found && redact {
t.lock.Lock()
t.track(varDef.Name, val)
t.track(varDef.Ref, val)
t.lock.Unlock()
}
return val, found, err
}
func (t *credVarsTracker) track(name string, val interface{}) {
func (t *credVarsTracker) track(varRef VariableReference, val interface{}) {
switch v := val.(type) {
case map[interface{}]interface{}:
for kk, vv := range v {
nn := fmt.Sprintf("%s.%s", name, kk.(string))
t.track(nn, vv)
t.track(VariableReference{
Path: varRef.Path,
Fields: append(varRef.Fields, kk.(string)),
}, vv)
}
case map[string]interface{}:
for kk, vv := range v {
nn := fmt.Sprintf("%s.%s", name, kk)
t.track(nn, vv)
t.track(VariableReference{
Path: varRef.Path,
Fields: append(varRef.Fields, kk),
}, vv)
}
case string:
t.interpolatedCreds[name] = v
paths := append([]string{varRef.Path}, varRef.Fields...)
t.interpolatedCreds[strings.Join(paths, ".")] = v
default:
// Do nothing
}
// switch v := val.(type) {
// case map[interface{}]interface{}:
// var found bool
// val, found = v[field]
// if found {
// varPath = fmt.Sprintf("%s.%s", varPath, field)
// }
// case map[string]interface{}:
// var found bool
// val, found = v[field]
// if found {
// varPath = fmt.Sprintf("%s.%s", varPath, field)
// }
// case string:
// t.interpolatedCreds[varPath] = v
// default:
// // Do nothing
// }
// case map[interface{}]interface{}:
// for kk, vv := range v {
// nn := fmt.Sprintf("%s.%s", name, kk.(string))
// t.track(nn, vv)
// }
// case map[string]interface{}:
// for kk, vv := range v {
// nn := fmt.Sprintf("%s.%s", name, kk)
// t.track(nn, vv)
// }
// case string:
// t.interpolatedCreds[name] = v
// default:
// // Do nothing
// }
}
func (t *credVarsTracker) List() ([]VariableDefinition, error) {

View File

@ -22,15 +22,15 @@ var _ = Describe("vars_tracker", func() {
found bool
err error
)
val, found, err = tracker.Get(VariableDefinition{Name: "k1"})
val, found, err = tracker.Get(VariableDefinition{Ref: VariableReference{Path: "k1"}})
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
Expect(val).To(Equal("v1"))
})
It("fetched variables are tracked", func() {
tracker.Get(VariableDefinition{Name: "k1"})
tracker.Get(VariableDefinition{Name: "k2"})
tracker.Get(VariableDefinition{Ref: VariableReference{Path: "k1"}})
tracker.Get(VariableDefinition{Ref: VariableReference{Path: "k2"}})
mapit := NewMapCredVarsTrackerIterator()
tracker.IterateInterpolatedCreds(mapit)
Expect(mapit.Data["k1"]).To(Equal("v1"))
@ -43,7 +43,11 @@ var _ = Describe("vars_tracker", func() {
Describe("List", func() {
It("returns list of names from multiple vars with duplicates", func() {
defs, err := tracker.List()
Expect(defs).To(ConsistOf([]VariableDefinition{{Name: "k1"}, {Name: "k2"}, {Name: "k3"}}))
Expect(defs).To(ConsistOf([]VariableDefinition{
{Ref: VariableReference{Path: "k1"}},
{Ref: VariableReference{Path: "k2"}},
{Ref: VariableReference{Path: "k3"}},
}))
Expect(err).ToNot(HaveOccurred())
})
})
@ -60,14 +64,14 @@ var _ = Describe("vars_tracker", func() {
found bool
err error
)
val, found, err = tracker.Get(VariableDefinition{Name: ".:foo"})
val, found, err = tracker.Get(VariableDefinition{Ref: VariableReference{Source: ".", Path: "foo"}})
Expect(err).To(BeNil())
Expect(found).To(BeTrue())
Expect(val).To(Equal("bar"))
})
It("fetched variables are tracked", func() {
tracker.Get(VariableDefinition{Name: ".:foo"})
tracker.Get(VariableDefinition{Ref: VariableReference{Source: ".", Path: "foo"}})
mapit := NewMapCredVarsTrackerIterator()
tracker.IterateInterpolatedCreds(mapit)
Expect(mapit.Data["foo"]).To(Equal("bar"))
@ -86,14 +90,14 @@ var _ = Describe("vars_tracker", func() {
found bool
err error
)
val, found, err = tracker.Get(VariableDefinition{Name: ".:foo"})
val, found, err = tracker.Get(VariableDefinition{Ref: VariableReference{Source: ".", Path: "foo"}})
Expect(err).To(BeNil())
Expect(found).To(BeTrue())
Expect(val).To(Equal("bar"))
})
It("fetched variables are not tracked", func() {
tracker.Get(VariableDefinition{Name: ".:foo"})
tracker.Get(VariableDefinition{Ref: VariableReference{Source: ".", Path: "foo"}})
mapit := NewMapCredVarsTrackerIterator()
tracker.IterateInterpolatedCreds(mapit)
Expect(mapit.Data["foo"]).To(BeNil())
@ -114,15 +118,15 @@ var _ = Describe("vars_tracker", func() {
found bool
err error
)
val, found, err = tracker.Get(VariableDefinition{Name: "k1"})
val, found, err = tracker.Get(VariableDefinition{Ref: VariableReference{Path: "k1"}})
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
Expect(val).To(Equal("v1"))
})
It("fetched variables should not be tracked", func() {
tracker.Get(VariableDefinition{Name: "k1"})
tracker.Get(VariableDefinition{Name: "k2"})
tracker.Get(VariableDefinition{Ref: VariableReference{Path: "k1"}})
tracker.Get(VariableDefinition{Ref: VariableReference{Path: "k2"}})
mapit := NewMapCredVarsTrackerIterator()
tracker.IterateInterpolatedCreds(mapit)
Expect(mapit.Data["k1"]).To(BeNil())
@ -134,7 +138,11 @@ var _ = Describe("vars_tracker", func() {
Describe("List", func() {
It("returns list of names from multiple vars with duplicates", func() {
defs, err := tracker.List()
Expect(defs).To(ConsistOf([]VariableDefinition{{Name: "k1"}, {Name: "k2"}, {Name: "k3"}}))
Expect(defs).To(ConsistOf([]VariableDefinition{
{Ref: VariableReference{Path: "k1"}},
{Ref: VariableReference{Path: "k2"}},
{Ref: VariableReference{Path: "k3"}},
}))
Expect(err).ToNot(HaveOccurred())
})
})