diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/plans/objchange/plan_valid.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/plans/objchange/plan_valid.go | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/plans/objchange/plan_valid.go b/vendor/github.com/hashicorp/terraform/plans/objchange/plan_valid.go new file mode 100644 index 0000000..69acb89 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/objchange/plan_valid.go | |||
@@ -0,0 +1,267 @@ | |||
1 | package objchange | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | |||
6 | "github.com/zclconf/go-cty/cty" | ||
7 | |||
8 | "github.com/hashicorp/terraform/configs/configschema" | ||
9 | ) | ||
10 | |||
11 | // AssertPlanValid checks checks whether a planned new state returned by a | ||
12 | // provider's PlanResourceChange method is suitable to achieve a change | ||
13 | // from priorState to config. It returns a slice with nonzero length if | ||
14 | // any problems are detected. Because problems here indicate bugs in the | ||
15 | // provider that generated the plannedState, they are written with provider | ||
16 | // developers as an audience, rather than end-users. | ||
17 | // | ||
18 | // All of the given values must have the same type and must conform to the | ||
19 | // implied type of the given schema, or this function may panic or produce | ||
20 | // garbage results. | ||
21 | // | ||
22 | // During planning, a provider may only make changes to attributes that are | ||
23 | // null (unset) in the configuration and are marked as "computed" in the | ||
24 | // resource type schema, in order to insert any default values the provider | ||
25 | // may know about. If the default value cannot be determined until apply time, | ||
26 | // the provider can return an unknown value. Providers are forbidden from | ||
27 | // planning a change that disagrees with any non-null argument in the | ||
28 | // configuration. | ||
29 | // | ||
30 | // As a special exception, providers _are_ allowed to provide attribute values | ||
31 | // conflicting with configuration if and only if the planned value exactly | ||
32 | // matches the corresponding attribute value in the prior state. The provider | ||
33 | // can use this to signal that the new value is functionally equivalent to | ||
34 | // the old and thus no change is required. | ||
35 | func AssertPlanValid(schema *configschema.Block, priorState, config, plannedState cty.Value) []error { | ||
36 | return assertPlanValid(schema, priorState, config, plannedState, nil) | ||
37 | } | ||
38 | |||
39 | func assertPlanValid(schema *configschema.Block, priorState, config, plannedState cty.Value, path cty.Path) []error { | ||
40 | var errs []error | ||
41 | if plannedState.IsNull() && !config.IsNull() { | ||
42 | errs = append(errs, path.NewErrorf("planned for absense but config wants existence")) | ||
43 | return errs | ||
44 | } | ||
45 | if config.IsNull() && !plannedState.IsNull() { | ||
46 | errs = append(errs, path.NewErrorf("planned for existence but config wants absense")) | ||
47 | return errs | ||
48 | } | ||
49 | if plannedState.IsNull() { | ||
50 | // No further checks possible if the planned value is null | ||
51 | return errs | ||
52 | } | ||
53 | |||
54 | impTy := schema.ImpliedType() | ||
55 | |||
56 | for name, attrS := range schema.Attributes { | ||
57 | plannedV := plannedState.GetAttr(name) | ||
58 | configV := config.GetAttr(name) | ||
59 | priorV := cty.NullVal(attrS.Type) | ||
60 | if !priorState.IsNull() { | ||
61 | priorV = priorState.GetAttr(name) | ||
62 | } | ||
63 | |||
64 | path := append(path, cty.GetAttrStep{Name: name}) | ||
65 | moreErrs := assertPlannedValueValid(attrS, priorV, configV, plannedV, path) | ||
66 | errs = append(errs, moreErrs...) | ||
67 | } | ||
68 | for name, blockS := range schema.BlockTypes { | ||
69 | path := append(path, cty.GetAttrStep{Name: name}) | ||
70 | plannedV := plannedState.GetAttr(name) | ||
71 | configV := config.GetAttr(name) | ||
72 | priorV := cty.NullVal(impTy.AttributeType(name)) | ||
73 | if !priorState.IsNull() { | ||
74 | priorV = priorState.GetAttr(name) | ||
75 | } | ||
76 | if plannedV.RawEquals(configV) { | ||
77 | // Easy path: nothing has changed at all | ||
78 | continue | ||
79 | } | ||
80 | if !plannedV.IsKnown() { | ||
81 | errs = append(errs, path.NewErrorf("attribute representing nested block must not be unknown itself; set nested attribute values to unknown instead")) | ||
82 | continue | ||
83 | } | ||
84 | |||
85 | switch blockS.Nesting { | ||
86 | case configschema.NestingSingle, configschema.NestingGroup: | ||
87 | moreErrs := assertPlanValid(&blockS.Block, priorV, configV, plannedV, path) | ||
88 | errs = append(errs, moreErrs...) | ||
89 | case configschema.NestingList: | ||
90 | // A NestingList might either be a list or a tuple, depending on | ||
91 | // whether there are dynamically-typed attributes inside. However, | ||
92 | // both support a similar-enough API that we can treat them the | ||
93 | // same for our purposes here. | ||
94 | if plannedV.IsNull() { | ||
95 | errs = append(errs, path.NewErrorf("attribute representing a list of nested blocks must be empty to indicate no blocks, not null")) | ||
96 | continue | ||
97 | } | ||
98 | |||
99 | plannedL := plannedV.LengthInt() | ||
100 | configL := configV.LengthInt() | ||
101 | if plannedL != configL { | ||
102 | errs = append(errs, path.NewErrorf("block count in plan (%d) disagrees with count in config (%d)", plannedL, configL)) | ||
103 | continue | ||
104 | } | ||
105 | for it := plannedV.ElementIterator(); it.Next(); { | ||
106 | idx, plannedEV := it.Element() | ||
107 | path := append(path, cty.IndexStep{Key: idx}) | ||
108 | if !plannedEV.IsKnown() { | ||
109 | errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead")) | ||
110 | continue | ||
111 | } | ||
112 | if !configV.HasIndex(idx).True() { | ||
113 | continue // should never happen since we checked the lengths above | ||
114 | } | ||
115 | configEV := configV.Index(idx) | ||
116 | priorEV := cty.NullVal(blockS.ImpliedType()) | ||
117 | if !priorV.IsNull() && priorV.HasIndex(idx).True() { | ||
118 | priorEV = priorV.Index(idx) | ||
119 | } | ||
120 | |||
121 | moreErrs := assertPlanValid(&blockS.Block, priorEV, configEV, plannedEV, path) | ||
122 | errs = append(errs, moreErrs...) | ||
123 | } | ||
124 | case configschema.NestingMap: | ||
125 | if plannedV.IsNull() { | ||
126 | errs = append(errs, path.NewErrorf("attribute representing a map of nested blocks must be empty to indicate no blocks, not null")) | ||
127 | continue | ||
128 | } | ||
129 | |||
130 | // A NestingMap might either be a map or an object, depending on | ||
131 | // whether there are dynamically-typed attributes inside, but | ||
132 | // that's decided statically and so all values will have the same | ||
133 | // kind. | ||
134 | if plannedV.Type().IsObjectType() { | ||
135 | plannedAtys := plannedV.Type().AttributeTypes() | ||
136 | configAtys := configV.Type().AttributeTypes() | ||
137 | for k := range plannedAtys { | ||
138 | if _, ok := configAtys[k]; !ok { | ||
139 | errs = append(errs, path.NewErrorf("block key %q from plan is not present in config", k)) | ||
140 | continue | ||
141 | } | ||
142 | path := append(path, cty.GetAttrStep{Name: k}) | ||
143 | |||
144 | plannedEV := plannedV.GetAttr(k) | ||
145 | if !plannedEV.IsKnown() { | ||
146 | errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead")) | ||
147 | continue | ||
148 | } | ||
149 | configEV := configV.GetAttr(k) | ||
150 | priorEV := cty.NullVal(blockS.ImpliedType()) | ||
151 | if !priorV.IsNull() && priorV.Type().HasAttribute(k) { | ||
152 | priorEV = priorV.GetAttr(k) | ||
153 | } | ||
154 | moreErrs := assertPlanValid(&blockS.Block, priorEV, configEV, plannedEV, path) | ||
155 | errs = append(errs, moreErrs...) | ||
156 | } | ||
157 | for k := range configAtys { | ||
158 | if _, ok := plannedAtys[k]; !ok { | ||
159 | errs = append(errs, path.NewErrorf("block key %q from config is not present in plan", k)) | ||
160 | continue | ||
161 | } | ||
162 | } | ||
163 | } else { | ||
164 | plannedL := plannedV.LengthInt() | ||
165 | configL := configV.LengthInt() | ||
166 | if plannedL != configL { | ||
167 | errs = append(errs, path.NewErrorf("block count in plan (%d) disagrees with count in config (%d)", plannedL, configL)) | ||
168 | continue | ||
169 | } | ||
170 | for it := plannedV.ElementIterator(); it.Next(); { | ||
171 | idx, plannedEV := it.Element() | ||
172 | path := append(path, cty.IndexStep{Key: idx}) | ||
173 | if !plannedEV.IsKnown() { | ||
174 | errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead")) | ||
175 | continue | ||
176 | } | ||
177 | k := idx.AsString() | ||
178 | if !configV.HasIndex(idx).True() { | ||
179 | errs = append(errs, path.NewErrorf("block key %q from plan is not present in config", k)) | ||
180 | continue | ||
181 | } | ||
182 | configEV := configV.Index(idx) | ||
183 | priorEV := cty.NullVal(blockS.ImpliedType()) | ||
184 | if !priorV.IsNull() && priorV.HasIndex(idx).True() { | ||
185 | priorEV = priorV.Index(idx) | ||
186 | } | ||
187 | moreErrs := assertPlanValid(&blockS.Block, priorEV, configEV, plannedEV, path) | ||
188 | errs = append(errs, moreErrs...) | ||
189 | } | ||
190 | for it := configV.ElementIterator(); it.Next(); { | ||
191 | idx, _ := it.Element() | ||
192 | if !plannedV.HasIndex(idx).True() { | ||
193 | errs = append(errs, path.NewErrorf("block key %q from config is not present in plan", idx.AsString())) | ||
194 | continue | ||
195 | } | ||
196 | } | ||
197 | } | ||
198 | case configschema.NestingSet: | ||
199 | if plannedV.IsNull() { | ||
200 | errs = append(errs, path.NewErrorf("attribute representing a set of nested blocks must be empty to indicate no blocks, not null")) | ||
201 | continue | ||
202 | } | ||
203 | |||
204 | // Because set elements have no identifier with which to correlate | ||
205 | // them, we can't robustly validate the plan for a nested block | ||
206 | // backed by a set, and so unfortunately we need to just trust the | ||
207 | // provider to do the right thing. :( | ||
208 | // | ||
209 | // (In principle we could correlate elements by matching the | ||
210 | // subset of attributes explicitly set in config, except for the | ||
211 | // special diff suppression rule which allows for there to be a | ||
212 | // planned value that is constructed by mixing part of a prior | ||
213 | // value with part of a config value, creating an entirely new | ||
214 | // element that is not present in either prior nor config.) | ||
215 | for it := plannedV.ElementIterator(); it.Next(); { | ||
216 | idx, plannedEV := it.Element() | ||
217 | path := append(path, cty.IndexStep{Key: idx}) | ||
218 | if !plannedEV.IsKnown() { | ||
219 | errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead")) | ||
220 | continue | ||
221 | } | ||
222 | } | ||
223 | |||
224 | default: | ||
225 | panic(fmt.Sprintf("unsupported nesting mode %s", blockS.Nesting)) | ||
226 | } | ||
227 | } | ||
228 | |||
229 | return errs | ||
230 | } | ||
231 | |||
232 | func assertPlannedValueValid(attrS *configschema.Attribute, priorV, configV, plannedV cty.Value, path cty.Path) []error { | ||
233 | var errs []error | ||
234 | if plannedV.RawEquals(configV) { | ||
235 | // This is the easy path: provider didn't change anything at all. | ||
236 | return errs | ||
237 | } | ||
238 | if plannedV.RawEquals(priorV) && !priorV.IsNull() { | ||
239 | // Also pretty easy: there is a prior value and the provider has | ||
240 | // returned it unchanged. This indicates that configV and plannedV | ||
241 | // are functionally equivalent and so the provider wishes to disregard | ||
242 | // the configuration value in favor of the prior. | ||
243 | return errs | ||
244 | } | ||
245 | if attrS.Computed && configV.IsNull() { | ||
246 | // The provider is allowed to change the value of any computed | ||
247 | // attribute that isn't explicitly set in the config. | ||
248 | return errs | ||
249 | } | ||
250 | |||
251 | // If none of the above conditions match, the provider has made an invalid | ||
252 | // change to this attribute. | ||
253 | if priorV.IsNull() { | ||
254 | if attrS.Sensitive { | ||
255 | errs = append(errs, path.NewErrorf("sensitive planned value does not match config value")) | ||
256 | } else { | ||
257 | errs = append(errs, path.NewErrorf("planned value %#v does not match config value %#v", plannedV, configV)) | ||
258 | } | ||
259 | return errs | ||
260 | } | ||
261 | if attrS.Sensitive { | ||
262 | errs = append(errs, path.NewErrorf("sensitive planned value does not match config value nor prior value")) | ||
263 | } else { | ||
264 | errs = append(errs, path.NewErrorf("planned value %#v does not match config value %#v nor prior value %#v", plannedV, configV, priorV)) | ||
265 | } | ||
266 | return errs | ||
267 | } | ||