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