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:
parent
a822574e7d
commit
1a6e35143a
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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...)
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
})
|
||||
|
|
123
vars/template.go
123
vars/template.go
|
@ -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{}{}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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{}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue