]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/config/hcl2shim/paths.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / config / hcl2shim / paths.go
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 }