162 lines
3.4 KiB
Go
162 lines
3.4 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/mitchellh/reflectwalk"
|
|
)
|
|
|
|
// varRegexp is a regexp that matches variables such as ${foo.bar}
|
|
var varRegexp *regexp.Regexp
|
|
|
|
func init() {
|
|
varRegexp = regexp.MustCompile(`(?i)(\$+)\{([-.a-z0-9_]+)\}`)
|
|
}
|
|
|
|
// variableDetectWalker implements interfaces for the reflectwalk package
|
|
// (github.com/mitchellh/reflectwalk) that can be used to automatically
|
|
// pull out the variables that need replacing.
|
|
type variableDetectWalker struct {
|
|
Variables map[string]InterpolatedVariable
|
|
}
|
|
|
|
func (w *variableDetectWalker) Primitive(v reflect.Value) error {
|
|
// We only care about strings
|
|
if v.Kind() == reflect.Interface {
|
|
v = v.Elem()
|
|
}
|
|
if v.Kind() != reflect.String {
|
|
return nil
|
|
}
|
|
|
|
// XXX: This can be a lot more efficient if we used a real
|
|
// parser. A regexp is a hammer though that will get this working.
|
|
|
|
matches := varRegexp.FindAllStringSubmatch(v.String(), -1)
|
|
if len(matches) == 0 {
|
|
return nil
|
|
}
|
|
|
|
for _, match := range matches {
|
|
dollars := len(match[1])
|
|
|
|
// If there are even amounts of dollar signs, then it is escaped
|
|
if dollars%2 == 0 {
|
|
continue
|
|
}
|
|
|
|
// Otherwise, record it
|
|
key := match[2]
|
|
if w.Variables == nil {
|
|
w.Variables = make(map[string]InterpolatedVariable)
|
|
}
|
|
if _, ok := w.Variables[key]; ok {
|
|
continue
|
|
}
|
|
|
|
var err error
|
|
var iv InterpolatedVariable
|
|
if strings.Index(key, ".") == -1 {
|
|
return fmt.Errorf(
|
|
"Interpolated variable '%s' has bad format. "+
|
|
"Did you mean 'var.%s'?",
|
|
key, key)
|
|
}
|
|
|
|
if strings.HasPrefix(key, "var.") {
|
|
iv, err = NewUserVariable(key)
|
|
} else {
|
|
iv, err = NewResourceVariable(key)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w.Variables[key] = iv
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// variableReplaceWalker implements interfaces for reflectwalk that
|
|
// is used to replace variables with their values.
|
|
//
|
|
// If Values does not have every available value, then the program
|
|
// will _panic_. The variableDetectWalker will tell you all variables
|
|
// you need.
|
|
type variableReplaceWalker struct {
|
|
Values map[string]string
|
|
|
|
loc reflectwalk.Location
|
|
m, mk reflect.Value
|
|
}
|
|
|
|
func (w *variableReplaceWalker) Enter(loc reflectwalk.Location) error {
|
|
w.loc = loc
|
|
return nil
|
|
}
|
|
|
|
func (w *variableReplaceWalker) Exit(loc reflectwalk.Location) error {
|
|
w.loc = reflectwalk.None
|
|
return nil
|
|
}
|
|
|
|
func (w *variableReplaceWalker) MapElem(m, k, v reflect.Value) error {
|
|
w.m = m
|
|
w.mk = k
|
|
return nil
|
|
}
|
|
|
|
func (w *variableReplaceWalker) Primitive(v reflect.Value) error {
|
|
// We only care about strings
|
|
setV := v
|
|
if v.Kind() == reflect.Interface {
|
|
setV = v
|
|
v = v.Elem()
|
|
}
|
|
if v.Kind() != reflect.String {
|
|
return nil
|
|
}
|
|
|
|
matches := varRegexp.FindAllStringSubmatch(v.String(), -1)
|
|
if len(matches) == 0 {
|
|
return nil
|
|
}
|
|
|
|
result := v.String()
|
|
for _, match := range matches {
|
|
dollars := len(match[1])
|
|
|
|
// If there are even amounts of dollar signs, then it is escaped
|
|
if dollars%2 == 0 {
|
|
continue
|
|
}
|
|
|
|
// Get the key
|
|
key := match[2]
|
|
value, ok := w.Values[key]
|
|
if !ok {
|
|
panic("no value for variable key: " + key)
|
|
}
|
|
|
|
// Replace
|
|
result = strings.Replace(result, match[0], value, -1)
|
|
}
|
|
|
|
resultVal := reflect.ValueOf(result)
|
|
if w.loc == reflectwalk.MapValue {
|
|
// If we're in a map, then the only way to set a map value is
|
|
// to set it directly.
|
|
w.m.SetMapIndex(w.mk, resultVal)
|
|
} else {
|
|
// Otherwise, we should be addressable
|
|
setV.Set(resultVal)
|
|
}
|
|
|
|
return nil
|
|
}
|