This repository has been archived by the owner on Jul 25, 2023. It is now read-only.
forked from hashicorp/terraform
-
Notifications
You must be signed in to change notification settings - Fork 0
/
interpolate_walk.go
155 lines (127 loc) · 3.45 KB
/
interpolate_walk.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package config
import (
"fmt"
"reflect"
"regexp"
"strings"
"github.com/mitchellh/reflectwalk"
)
// interpRegexp is a regexp that matches interpolations such as ${foo.bar}
var interpRegexp *regexp.Regexp = regexp.MustCompile(
`(?i)(\$+)\{([\s*-.,\(\)a-z0-9_"]+)\}`)
// interpolationWalker implements interfaces for the reflectwalk package
// (github.com/mitchellh/reflectwalk) that can be used to automatically
// execute a callback for an interpolation.
type interpolationWalker struct {
F interpolationWalkerFunc
Replace bool
key []string
loc reflectwalk.Location
cs []reflect.Value
csData interface{}
unknownKeys []string
}
// interpolationWalkerFunc is the callback called by interpolationWalk.
// It is called with any interpolation found. It should return a value
// to replace the interpolation with, along with any errors.
//
// If Replace is set to false in interpolationWalker, then the replace
// value can be anything as it will have no effect.
type interpolationWalkerFunc func(Interpolation) (string, error)
func (w *interpolationWalker) Enter(loc reflectwalk.Location) error {
w.loc = loc
return nil
}
func (w *interpolationWalker) Exit(loc reflectwalk.Location) error {
w.loc = reflectwalk.None
switch loc {
case reflectwalk.Map:
w.cs = w.cs[:len(w.cs)-1]
case reflectwalk.MapValue:
w.key = w.key[:len(w.key)-1]
}
return nil
}
func (w *interpolationWalker) Map(m reflect.Value) error {
w.cs = append(w.cs, m)
return nil
}
func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error {
w.csData = k
w.key = append(w.key, k.String())
return nil
}
func (w *interpolationWalker) Primitive(v reflect.Value) error {
setV := v
// We only care about strings
if v.Kind() == reflect.Interface {
setV = v
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 := interpRegexp.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
}
// Interpolation found, instantiate it
key := match[2]
i, err := ExprParse(key)
if err != nil {
return err
}
replaceVal, err := w.F(i)
if err != nil {
return fmt.Errorf(
"%s: %s",
key,
err)
}
if w.Replace {
// If this is an unknown variable, then we remove it from
// the configuration.
if replaceVal == UnknownVariableValue {
w.removeCurrent()
return nil
}
result = strings.Replace(result, match[0], replaceVal, -1)
}
}
if w.Replace {
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.
m := w.cs[len(w.cs)-1]
mk := w.csData.(reflect.Value)
m.SetMapIndex(mk, resultVal)
} else {
// Otherwise, we should be addressable
setV.Set(resultVal)
}
}
return nil
}
func (w *interpolationWalker) removeCurrent() {
c := w.cs[len(w.cs)-1]
switch c.Kind() {
case reflect.Map:
// Zero value so that we delete the map key
var val reflect.Value
// Get the key and delete it
k := w.csData.(reflect.Value)
c.SetMapIndex(k, val)
}
// Append the key to the unknown keys
w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
}