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_]+)\}`) } // replaceVariables takes a configuration and a mapping of variables // and performs the structure walking necessary to properly replace // all the variables. func replaceVariables( c map[string]interface{}, vs map[string]string) error { w := &variableReplaceWalker{Values: vs} return reflectwalk.Walk(c, w) } // 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 }