diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/config/hcl2shim/paths.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/config/hcl2shim/paths.go | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/config/hcl2shim/paths.go b/vendor/github.com/hashicorp/terraform/config/hcl2shim/paths.go new file mode 100644 index 0000000..3403c02 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/config/hcl2shim/paths.go | |||
@@ -0,0 +1,276 @@ | |||
1 | package hcl2shim | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "reflect" | ||
6 | "strconv" | ||
7 | "strings" | ||
8 | |||
9 | "github.com/zclconf/go-cty/cty" | ||
10 | ) | ||
11 | |||
12 | // RequiresReplace takes a list of flatmapped paths from a | ||
13 | // InstanceDiff.Attributes along with the corresponding cty.Type, and returns | ||
14 | // the list of the cty.Paths that are flagged as causing the resource | ||
15 | // replacement (RequiresNew). | ||
16 | // This will filter out redundant paths, paths that refer to flatmapped indexes | ||
17 | // (e.g. "#", "%"), and will return any changes within a set as the path to the | ||
18 | // set itself. | ||
19 | func RequiresReplace(attrs []string, ty cty.Type) ([]cty.Path, error) { | ||
20 | var paths []cty.Path | ||
21 | |||
22 | for _, attr := range attrs { | ||
23 | p, err := requiresReplacePath(attr, ty) | ||
24 | if err != nil { | ||
25 | return nil, err | ||
26 | } | ||
27 | |||
28 | paths = append(paths, p) | ||
29 | } | ||
30 | |||
31 | // now trim off any trailing paths that aren't GetAttrSteps, since only an | ||
32 | // attribute itself can require replacement | ||
33 | paths = trimPaths(paths) | ||
34 | |||
35 | // There may be redundant paths due to set elements or index attributes | ||
36 | // Do some ugly n^2 filtering, but these are always fairly small sets. | ||
37 | for i := 0; i < len(paths)-1; i++ { | ||
38 | for j := i + 1; j < len(paths); j++ { | ||
39 | if reflect.DeepEqual(paths[i], paths[j]) { | ||
40 | // swap the tail and slice it off | ||
41 | paths[j], paths[len(paths)-1] = paths[len(paths)-1], paths[j] | ||
42 | paths = paths[:len(paths)-1] | ||
43 | j-- | ||
44 | } | ||
45 | } | ||
46 | } | ||
47 | |||
48 | return paths, nil | ||
49 | } | ||
50 | |||
51 | // trimPaths removes any trailing steps that aren't of type GetAttrSet, since | ||
52 | // only an attribute itself can require replacement | ||
53 | func trimPaths(paths []cty.Path) []cty.Path { | ||
54 | var trimmed []cty.Path | ||
55 | for _, path := range paths { | ||
56 | path = trimPath(path) | ||
57 | if len(path) > 0 { | ||
58 | trimmed = append(trimmed, path) | ||
59 | } | ||
60 | } | ||
61 | return trimmed | ||
62 | } | ||
63 | |||
64 | func trimPath(path cty.Path) cty.Path { | ||
65 | for len(path) > 0 { | ||
66 | _, isGetAttr := path[len(path)-1].(cty.GetAttrStep) | ||
67 | if isGetAttr { | ||
68 | break | ||
69 | } | ||
70 | path = path[:len(path)-1] | ||
71 | } | ||
72 | return path | ||
73 | } | ||
74 | |||
75 | // requiresReplacePath takes a key from a flatmap along with the cty.Type | ||
76 | // describing the structure, and returns the cty.Path that would be used to | ||
77 | // reference the nested value in the data structure. | ||
78 | // This is used specifically to record the RequiresReplace attributes from a | ||
79 | // ResourceInstanceDiff. | ||
80 | func requiresReplacePath(k string, ty cty.Type) (cty.Path, error) { | ||
81 | if k == "" { | ||
82 | return nil, nil | ||
83 | } | ||
84 | if !ty.IsObjectType() { | ||
85 | panic(fmt.Sprintf("requires replace path on non-object type: %#v", ty)) | ||
86 | } | ||
87 | |||
88 | path, err := pathFromFlatmapKeyObject(k, ty.AttributeTypes()) | ||
89 | if err != nil { | ||
90 | return path, fmt.Errorf("[%s] %s", k, err) | ||
91 | } | ||
92 | return path, nil | ||
93 | } | ||
94 | |||
95 | func pathSplit(p string) (string, string) { | ||
96 | parts := strings.SplitN(p, ".", 2) | ||
97 | head := parts[0] | ||
98 | rest := "" | ||
99 | if len(parts) > 1 { | ||
100 | rest = parts[1] | ||
101 | } | ||
102 | return head, rest | ||
103 | } | ||
104 | |||
105 | func pathFromFlatmapKeyObject(key string, atys map[string]cty.Type) (cty.Path, error) { | ||
106 | k, rest := pathSplit(key) | ||
107 | |||
108 | path := cty.Path{cty.GetAttrStep{Name: k}} | ||
109 | |||
110 | ty, ok := atys[k] | ||
111 | if !ok { | ||
112 | return path, fmt.Errorf("attribute %q not found", k) | ||
113 | } | ||
114 | |||
115 | if rest == "" { | ||
116 | return path, nil | ||
117 | } | ||
118 | |||
119 | p, err := pathFromFlatmapKeyValue(rest, ty) | ||
120 | if err != nil { | ||
121 | return path, err | ||
122 | } | ||
123 | |||
124 | return append(path, p...), nil | ||
125 | } | ||
126 | |||
127 | func pathFromFlatmapKeyValue(key string, ty cty.Type) (cty.Path, error) { | ||
128 | var path cty.Path | ||
129 | var err error | ||
130 | |||
131 | switch { | ||
132 | case ty.IsPrimitiveType(): | ||
133 | err = fmt.Errorf("invalid step %q with type %#v", key, ty) | ||
134 | case ty.IsObjectType(): | ||
135 | path, err = pathFromFlatmapKeyObject(key, ty.AttributeTypes()) | ||
136 | case ty.IsTupleType(): | ||
137 | path, err = pathFromFlatmapKeyTuple(key, ty.TupleElementTypes()) | ||
138 | case ty.IsMapType(): | ||
139 | path, err = pathFromFlatmapKeyMap(key, ty) | ||
140 | case ty.IsListType(): | ||
141 | path, err = pathFromFlatmapKeyList(key, ty) | ||
142 | case ty.IsSetType(): | ||
143 | path, err = pathFromFlatmapKeySet(key, ty) | ||
144 | default: | ||
145 | err = fmt.Errorf("unrecognized type: %s", ty.FriendlyName()) | ||
146 | } | ||
147 | |||
148 | if err != nil { | ||
149 | return path, err | ||
150 | } | ||
151 | |||
152 | return path, nil | ||
153 | } | ||
154 | |||
155 | func pathFromFlatmapKeyTuple(key string, etys []cty.Type) (cty.Path, error) { | ||
156 | var path cty.Path | ||
157 | var err error | ||
158 | |||
159 | k, rest := pathSplit(key) | ||
160 | |||
161 | // we don't need to convert the index keys to paths | ||
162 | if k == "#" { | ||
163 | return path, nil | ||
164 | } | ||
165 | |||
166 | idx, err := strconv.Atoi(k) | ||
167 | if err != nil { | ||
168 | return path, err | ||
169 | } | ||
170 | |||
171 | path = cty.Path{cty.IndexStep{Key: cty.NumberIntVal(int64(idx))}} | ||
172 | |||
173 | if idx >= len(etys) { | ||
174 | return path, fmt.Errorf("index %s out of range in %#v", key, etys) | ||
175 | } | ||
176 | |||
177 | if rest == "" { | ||
178 | return path, nil | ||
179 | } | ||
180 | |||
181 | ty := etys[idx] | ||
182 | |||
183 | p, err := pathFromFlatmapKeyValue(rest, ty.ElementType()) | ||
184 | if err != nil { | ||
185 | return path, err | ||
186 | } | ||
187 | |||
188 | return append(path, p...), nil | ||
189 | } | ||
190 | |||
191 | func pathFromFlatmapKeyMap(key string, ty cty.Type) (cty.Path, error) { | ||
192 | var path cty.Path | ||
193 | var err error | ||
194 | |||
195 | k, rest := key, "" | ||
196 | if !ty.ElementType().IsPrimitiveType() { | ||
197 | k, rest = pathSplit(key) | ||
198 | } | ||
199 | |||
200 | // we don't need to convert the index keys to paths | ||
201 | if k == "%" { | ||
202 | return path, nil | ||
203 | } | ||
204 | |||
205 | path = cty.Path{cty.IndexStep{Key: cty.StringVal(k)}} | ||
206 | |||
207 | if rest == "" { | ||
208 | return path, nil | ||
209 | } | ||
210 | |||
211 | p, err := pathFromFlatmapKeyValue(rest, ty.ElementType()) | ||
212 | if err != nil { | ||
213 | return path, err | ||
214 | } | ||
215 | |||
216 | return append(path, p...), nil | ||
217 | } | ||
218 | |||
219 | func pathFromFlatmapKeyList(key string, ty cty.Type) (cty.Path, error) { | ||
220 | var path cty.Path | ||
221 | var err error | ||
222 | |||
223 | k, rest := pathSplit(key) | ||
224 | |||
225 | // we don't need to convert the index keys to paths | ||
226 | if key == "#" { | ||
227 | return path, nil | ||
228 | } | ||
229 | |||
230 | idx, err := strconv.Atoi(k) | ||
231 | if err != nil { | ||
232 | return path, err | ||
233 | } | ||
234 | |||
235 | path = cty.Path{cty.IndexStep{Key: cty.NumberIntVal(int64(idx))}} | ||
236 | |||
237 | if rest == "" { | ||
238 | return path, nil | ||
239 | } | ||
240 | |||
241 | p, err := pathFromFlatmapKeyValue(rest, ty.ElementType()) | ||
242 | if err != nil { | ||
243 | return path, err | ||
244 | } | ||
245 | |||
246 | return append(path, p...), nil | ||
247 | } | ||
248 | |||
249 | func pathFromFlatmapKeySet(key string, ty cty.Type) (cty.Path, error) { | ||
250 | // once we hit a set, we can't return consistent paths, so just mark the | ||
251 | // set as a whole changed. | ||
252 | return nil, nil | ||
253 | } | ||
254 | |||
255 | // FlatmapKeyFromPath returns the flatmap equivalent of the given cty.Path for | ||
256 | // use in generating legacy style diffs. | ||
257 | func FlatmapKeyFromPath(path cty.Path) string { | ||
258 | var parts []string | ||
259 | |||
260 | for _, step := range path { | ||
261 | switch step := step.(type) { | ||
262 | case cty.GetAttrStep: | ||
263 | parts = append(parts, step.Name) | ||
264 | case cty.IndexStep: | ||
265 | switch ty := step.Key.Type(); { | ||
266 | case ty == cty.String: | ||
267 | parts = append(parts, step.Key.AsString()) | ||
268 | case ty == cty.Number: | ||
269 | i, _ := step.Key.AsBigFloat().Int64() | ||
270 | parts = append(parts, strconv.Itoa(int(i))) | ||
271 | } | ||
272 | } | ||
273 | } | ||
274 | |||
275 | return strings.Join(parts, ".") | ||
276 | } | ||