diff options
author | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
---|---|---|
committer | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
commit | 107c1cdb09c575aa2f61d97f48d8587eb6bada4c (patch) | |
tree | ca7d008643efc555c388baeaf1d986e0b6b3e28c /vendor/github.com/hashicorp/terraform/plans | |
parent | 844b5a68d8af4791755b8f0ad293cc99f5959183 (diff) | |
download | terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.gz terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.zst terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.zip |
Upgrade to 0.12
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/plans')
16 files changed, 2273 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/plans/action.go b/vendor/github.com/hashicorp/terraform/plans/action.go new file mode 100644 index 0000000..c3e6a32 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/action.go | |||
@@ -0,0 +1,22 @@ | |||
1 | package plans | ||
2 | |||
3 | type Action rune | ||
4 | |||
5 | const ( | ||
6 | NoOp Action = 0 | ||
7 | Create Action = '+' | ||
8 | Read Action = '←' | ||
9 | Update Action = '~' | ||
10 | DeleteThenCreate Action = '∓' | ||
11 | CreateThenDelete Action = '±' | ||
12 | Delete Action = '-' | ||
13 | ) | ||
14 | |||
15 | //go:generate stringer -type Action | ||
16 | |||
17 | // IsReplace returns true if the action is one of the two actions that | ||
18 | // represents replacing an existing object with a new object: | ||
19 | // DeleteThenCreate or CreateThenDelete. | ||
20 | func (a Action) IsReplace() bool { | ||
21 | return a == DeleteThenCreate || a == CreateThenDelete | ||
22 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/action_string.go b/vendor/github.com/hashicorp/terraform/plans/action_string.go new file mode 100644 index 0000000..be43ab1 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/action_string.go | |||
@@ -0,0 +1,49 @@ | |||
1 | // Code generated by "stringer -type Action"; DO NOT EDIT. | ||
2 | |||
3 | package plans | ||
4 | |||
5 | import "strconv" | ||
6 | |||
7 | func _() { | ||
8 | // An "invalid array index" compiler error signifies that the constant values have changed. | ||
9 | // Re-run the stringer command to generate them again. | ||
10 | var x [1]struct{} | ||
11 | _ = x[NoOp-0] | ||
12 | _ = x[Create-43] | ||
13 | _ = x[Read-8592] | ||
14 | _ = x[Update-126] | ||
15 | _ = x[DeleteThenCreate-8723] | ||
16 | _ = x[CreateThenDelete-177] | ||
17 | _ = x[Delete-45] | ||
18 | } | ||
19 | |||
20 | const ( | ||
21 | _Action_name_0 = "NoOp" | ||
22 | _Action_name_1 = "Create" | ||
23 | _Action_name_2 = "Delete" | ||
24 | _Action_name_3 = "Update" | ||
25 | _Action_name_4 = "CreateThenDelete" | ||
26 | _Action_name_5 = "Read" | ||
27 | _Action_name_6 = "DeleteThenCreate" | ||
28 | ) | ||
29 | |||
30 | func (i Action) String() string { | ||
31 | switch { | ||
32 | case i == 0: | ||
33 | return _Action_name_0 | ||
34 | case i == 43: | ||
35 | return _Action_name_1 | ||
36 | case i == 45: | ||
37 | return _Action_name_2 | ||
38 | case i == 126: | ||
39 | return _Action_name_3 | ||
40 | case i == 177: | ||
41 | return _Action_name_4 | ||
42 | case i == 8592: | ||
43 | return _Action_name_5 | ||
44 | case i == 8723: | ||
45 | return _Action_name_6 | ||
46 | default: | ||
47 | return "Action(" + strconv.FormatInt(int64(i), 10) + ")" | ||
48 | } | ||
49 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/changes.go b/vendor/github.com/hashicorp/terraform/plans/changes.go new file mode 100644 index 0000000..d7e0dcd --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/changes.go | |||
@@ -0,0 +1,308 @@ | |||
1 | package plans | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/terraform/addrs" | ||
5 | "github.com/hashicorp/terraform/states" | ||
6 | "github.com/zclconf/go-cty/cty" | ||
7 | ) | ||
8 | |||
9 | // Changes describes various actions that Terraform will attempt to take if | ||
10 | // the corresponding plan is applied. | ||
11 | // | ||
12 | // A Changes object can be rendered into a visual diff (by the caller, using | ||
13 | // code in another package) for display to the user. | ||
14 | type Changes struct { | ||
15 | // Resources tracks planned changes to resource instance objects. | ||
16 | Resources []*ResourceInstanceChangeSrc | ||
17 | |||
18 | // Outputs tracks planned changes output values. | ||
19 | // | ||
20 | // Note that although an in-memory plan contains planned changes for | ||
21 | // outputs throughout the configuration, a plan serialized | ||
22 | // to disk retains only the root outputs because they are | ||
23 | // externally-visible, while other outputs are implementation details and | ||
24 | // can be easily re-calculated during the apply phase. Therefore only root | ||
25 | // module outputs will survive a round-trip through a plan file. | ||
26 | Outputs []*OutputChangeSrc | ||
27 | } | ||
28 | |||
29 | // NewChanges returns a valid Changes object that describes no changes. | ||
30 | func NewChanges() *Changes { | ||
31 | return &Changes{} | ||
32 | } | ||
33 | |||
34 | func (c *Changes) Empty() bool { | ||
35 | for _, res := range c.Resources { | ||
36 | if res.Action != NoOp { | ||
37 | return false | ||
38 | } | ||
39 | } | ||
40 | return true | ||
41 | } | ||
42 | |||
43 | // ResourceInstance returns the planned change for the current object of the | ||
44 | // resource instance of the given address, if any. Returns nil if no change is | ||
45 | // planned. | ||
46 | func (c *Changes) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstanceChangeSrc { | ||
47 | addrStr := addr.String() | ||
48 | for _, rc := range c.Resources { | ||
49 | if rc.Addr.String() == addrStr && rc.DeposedKey == states.NotDeposed { | ||
50 | return rc | ||
51 | } | ||
52 | } | ||
53 | |||
54 | return nil | ||
55 | } | ||
56 | |||
57 | // ResourceInstanceDeposed returns the plan change of a deposed object of | ||
58 | // the resource instance of the given address, if any. Returns nil if no change | ||
59 | // is planned. | ||
60 | func (c *Changes) ResourceInstanceDeposed(addr addrs.AbsResourceInstance, key states.DeposedKey) *ResourceInstanceChangeSrc { | ||
61 | addrStr := addr.String() | ||
62 | for _, rc := range c.Resources { | ||
63 | if rc.Addr.String() == addrStr && rc.DeposedKey == key { | ||
64 | return rc | ||
65 | } | ||
66 | } | ||
67 | |||
68 | return nil | ||
69 | } | ||
70 | |||
71 | // OutputValue returns the planned change for the output value with the | ||
72 | // given address, if any. Returns nil if no change is planned. | ||
73 | func (c *Changes) OutputValue(addr addrs.AbsOutputValue) *OutputChangeSrc { | ||
74 | addrStr := addr.String() | ||
75 | for _, oc := range c.Outputs { | ||
76 | if oc.Addr.String() == addrStr { | ||
77 | return oc | ||
78 | } | ||
79 | } | ||
80 | |||
81 | return nil | ||
82 | } | ||
83 | |||
84 | // SyncWrapper returns a wrapper object around the receiver that can be used | ||
85 | // to make certain changes to the receiver in a concurrency-safe way, as long | ||
86 | // as all callers share the same wrapper object. | ||
87 | func (c *Changes) SyncWrapper() *ChangesSync { | ||
88 | return &ChangesSync{ | ||
89 | changes: c, | ||
90 | } | ||
91 | } | ||
92 | |||
93 | // ResourceInstanceChange describes a change to a particular resource instance | ||
94 | // object. | ||
95 | type ResourceInstanceChange struct { | ||
96 | // Addr is the absolute address of the resource instance that the change | ||
97 | // will apply to. | ||
98 | Addr addrs.AbsResourceInstance | ||
99 | |||
100 | // DeposedKey is the identifier for a deposed object associated with the | ||
101 | // given instance, or states.NotDeposed if this change applies to the | ||
102 | // current object. | ||
103 | // | ||
104 | // A Replace change for a resource with create_before_destroy set will | ||
105 | // create a new DeposedKey temporarily during replacement. In that case, | ||
106 | // DeposedKey in the plan is always states.NotDeposed, representing that | ||
107 | // the current object is being replaced with the deposed. | ||
108 | DeposedKey states.DeposedKey | ||
109 | |||
110 | // Provider is the address of the provider configuration that was used | ||
111 | // to plan this change, and thus the configuration that must also be | ||
112 | // used to apply it. | ||
113 | ProviderAddr addrs.AbsProviderConfig | ||
114 | |||
115 | // Change is an embedded description of the change. | ||
116 | Change | ||
117 | |||
118 | // RequiredReplace is a set of paths that caused the change action to be | ||
119 | // Replace rather than Update. Always nil if the change action is not | ||
120 | // Replace. | ||
121 | // | ||
122 | // This is retained only for UI-plan-rendering purposes and so it does not | ||
123 | // currently survive a round-trip through a saved plan file. | ||
124 | RequiredReplace cty.PathSet | ||
125 | |||
126 | // Private allows a provider to stash any extra data that is opaque to | ||
127 | // Terraform that relates to this change. Terraform will save this | ||
128 | // byte-for-byte and return it to the provider in the apply call. | ||
129 | Private []byte | ||
130 | } | ||
131 | |||
132 | // Encode produces a variant of the reciever that has its change values | ||
133 | // serialized so it can be written to a plan file. Pass the implied type of the | ||
134 | // corresponding resource type schema for correct operation. | ||
135 | func (rc *ResourceInstanceChange) Encode(ty cty.Type) (*ResourceInstanceChangeSrc, error) { | ||
136 | cs, err := rc.Change.Encode(ty) | ||
137 | if err != nil { | ||
138 | return nil, err | ||
139 | } | ||
140 | return &ResourceInstanceChangeSrc{ | ||
141 | Addr: rc.Addr, | ||
142 | DeposedKey: rc.DeposedKey, | ||
143 | ProviderAddr: rc.ProviderAddr, | ||
144 | ChangeSrc: *cs, | ||
145 | RequiredReplace: rc.RequiredReplace, | ||
146 | Private: rc.Private, | ||
147 | }, err | ||
148 | } | ||
149 | |||
150 | // Simplify will, where possible, produce a change with a simpler action than | ||
151 | // the receiever given a flag indicating whether the caller is dealing with | ||
152 | // a normal apply or a destroy. This flag deals with the fact that Terraform | ||
153 | // Core uses a specialized graph node type for destroying; only that | ||
154 | // specialized node should set "destroying" to true. | ||
155 | // | ||
156 | // The following table shows the simplification behavior: | ||
157 | // | ||
158 | // Action Destroying? New Action | ||
159 | // --------+-------------+----------- | ||
160 | // Create true NoOp | ||
161 | // Delete false NoOp | ||
162 | // Replace true Delete | ||
163 | // Replace false Create | ||
164 | // | ||
165 | // For any combination not in the above table, the Simplify just returns the | ||
166 | // receiver as-is. | ||
167 | func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceChange { | ||
168 | if destroying { | ||
169 | switch rc.Action { | ||
170 | case Delete: | ||
171 | // We'll fall out and just return rc verbatim, then. | ||
172 | case CreateThenDelete, DeleteThenCreate: | ||
173 | return &ResourceInstanceChange{ | ||
174 | Addr: rc.Addr, | ||
175 | DeposedKey: rc.DeposedKey, | ||
176 | Private: rc.Private, | ||
177 | ProviderAddr: rc.ProviderAddr, | ||
178 | Change: Change{ | ||
179 | Action: Delete, | ||
180 | Before: rc.Before, | ||
181 | After: cty.NullVal(rc.Before.Type()), | ||
182 | }, | ||
183 | } | ||
184 | default: | ||
185 | return &ResourceInstanceChange{ | ||
186 | Addr: rc.Addr, | ||
187 | DeposedKey: rc.DeposedKey, | ||
188 | Private: rc.Private, | ||
189 | ProviderAddr: rc.ProviderAddr, | ||
190 | Change: Change{ | ||
191 | Action: NoOp, | ||
192 | Before: rc.Before, | ||
193 | After: rc.Before, | ||
194 | }, | ||
195 | } | ||
196 | } | ||
197 | } else { | ||
198 | switch rc.Action { | ||
199 | case Delete: | ||
200 | return &ResourceInstanceChange{ | ||
201 | Addr: rc.Addr, | ||
202 | DeposedKey: rc.DeposedKey, | ||
203 | Private: rc.Private, | ||
204 | ProviderAddr: rc.ProviderAddr, | ||
205 | Change: Change{ | ||
206 | Action: NoOp, | ||
207 | Before: rc.Before, | ||
208 | After: rc.Before, | ||
209 | }, | ||
210 | } | ||
211 | case CreateThenDelete, DeleteThenCreate: | ||
212 | return &ResourceInstanceChange{ | ||
213 | Addr: rc.Addr, | ||
214 | DeposedKey: rc.DeposedKey, | ||
215 | Private: rc.Private, | ||
216 | ProviderAddr: rc.ProviderAddr, | ||
217 | Change: Change{ | ||
218 | Action: Create, | ||
219 | Before: cty.NullVal(rc.After.Type()), | ||
220 | After: rc.After, | ||
221 | }, | ||
222 | } | ||
223 | } | ||
224 | } | ||
225 | |||
226 | // If we fall out here then our change is already simple enough. | ||
227 | return rc | ||
228 | } | ||
229 | |||
230 | // OutputChange describes a change to an output value. | ||
231 | type OutputChange struct { | ||
232 | // Addr is the absolute address of the output value that the change | ||
233 | // will apply to. | ||
234 | Addr addrs.AbsOutputValue | ||
235 | |||
236 | // Change is an embedded description of the change. | ||
237 | // | ||
238 | // For output value changes, the type constraint for the DynamicValue | ||
239 | // instances is always cty.DynamicPseudoType. | ||
240 | Change | ||
241 | |||
242 | // Sensitive, if true, indicates that either the old or new value in the | ||
243 | // change is sensitive and so a rendered version of the plan in the UI | ||
244 | // should elide the actual values while still indicating the action of the | ||
245 | // change. | ||
246 | Sensitive bool | ||
247 | } | ||
248 | |||
249 | // Encode produces a variant of the reciever that has its change values | ||
250 | // serialized so it can be written to a plan file. | ||
251 | func (oc *OutputChange) Encode() (*OutputChangeSrc, error) { | ||
252 | cs, err := oc.Change.Encode(cty.DynamicPseudoType) | ||
253 | if err != nil { | ||
254 | return nil, err | ||
255 | } | ||
256 | return &OutputChangeSrc{ | ||
257 | Addr: oc.Addr, | ||
258 | ChangeSrc: *cs, | ||
259 | Sensitive: oc.Sensitive, | ||
260 | }, err | ||
261 | } | ||
262 | |||
263 | // Change describes a single change with a given action. | ||
264 | type Change struct { | ||
265 | // Action defines what kind of change is being made. | ||
266 | Action Action | ||
267 | |||
268 | // Interpretation of Before and After depend on Action: | ||
269 | // | ||
270 | // NoOp Before and After are the same, unchanged value | ||
271 | // Create Before is nil, and After is the expected value after create. | ||
272 | // Read Before is any prior value (nil if no prior), and After is the | ||
273 | // value that was or will be read. | ||
274 | // Update Before is the value prior to update, and After is the expected | ||
275 | // value after update. | ||
276 | // Replace As with Update. | ||
277 | // Delete Before is the value prior to delete, and After is always nil. | ||
278 | // | ||
279 | // Unknown values may appear anywhere within the Before and After values, | ||
280 | // either as the values themselves or as nested elements within known | ||
281 | // collections/structures. | ||
282 | Before, After cty.Value | ||
283 | } | ||
284 | |||
285 | // Encode produces a variant of the reciever that has its change values | ||
286 | // serialized so it can be written to a plan file. Pass the type constraint | ||
287 | // that the values are expected to conform to; to properly decode the values | ||
288 | // later an identical type constraint must be provided at that time. | ||
289 | // | ||
290 | // Where a Change is embedded in some other struct, it's generally better | ||
291 | // to call the corresponding Encode method of that struct rather than working | ||
292 | // directly with its embedded Change. | ||
293 | func (c *Change) Encode(ty cty.Type) (*ChangeSrc, error) { | ||
294 | beforeDV, err := NewDynamicValue(c.Before, ty) | ||
295 | if err != nil { | ||
296 | return nil, err | ||
297 | } | ||
298 | afterDV, err := NewDynamicValue(c.After, ty) | ||
299 | if err != nil { | ||
300 | return nil, err | ||
301 | } | ||
302 | |||
303 | return &ChangeSrc{ | ||
304 | Action: c.Action, | ||
305 | Before: beforeDV, | ||
306 | After: afterDV, | ||
307 | }, nil | ||
308 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/changes_src.go b/vendor/github.com/hashicorp/terraform/plans/changes_src.go new file mode 100644 index 0000000..90153ea --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/changes_src.go | |||
@@ -0,0 +1,190 @@ | |||
1 | package plans | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | |||
6 | "github.com/hashicorp/terraform/addrs" | ||
7 | "github.com/hashicorp/terraform/states" | ||
8 | "github.com/zclconf/go-cty/cty" | ||
9 | ) | ||
10 | |||
11 | // ResourceInstanceChangeSrc is a not-yet-decoded ResourceInstanceChange. | ||
12 | // Pass the associated resource type's schema type to method Decode to | ||
13 | // obtain a ResourceInstancChange. | ||
14 | type ResourceInstanceChangeSrc struct { | ||
15 | // Addr is the absolute address of the resource instance that the change | ||
16 | // will apply to. | ||
17 | Addr addrs.AbsResourceInstance | ||
18 | |||
19 | // DeposedKey is the identifier for a deposed object associated with the | ||
20 | // given instance, or states.NotDeposed if this change applies to the | ||
21 | // current object. | ||
22 | // | ||
23 | // A Replace change for a resource with create_before_destroy set will | ||
24 | // create a new DeposedKey temporarily during replacement. In that case, | ||
25 | // DeposedKey in the plan is always states.NotDeposed, representing that | ||
26 | // the current object is being replaced with the deposed. | ||
27 | DeposedKey states.DeposedKey | ||
28 | |||
29 | // Provider is the address of the provider configuration that was used | ||
30 | // to plan this change, and thus the configuration that must also be | ||
31 | // used to apply it. | ||
32 | ProviderAddr addrs.AbsProviderConfig | ||
33 | |||
34 | // ChangeSrc is an embedded description of the not-yet-decoded change. | ||
35 | ChangeSrc | ||
36 | |||
37 | // RequiredReplace is a set of paths that caused the change action to be | ||
38 | // Replace rather than Update. Always nil if the change action is not | ||
39 | // Replace. | ||
40 | // | ||
41 | // This is retained only for UI-plan-rendering purposes and so it does not | ||
42 | // currently survive a round-trip through a saved plan file. | ||
43 | RequiredReplace cty.PathSet | ||
44 | |||
45 | // Private allows a provider to stash any extra data that is opaque to | ||
46 | // Terraform that relates to this change. Terraform will save this | ||
47 | // byte-for-byte and return it to the provider in the apply call. | ||
48 | Private []byte | ||
49 | } | ||
50 | |||
51 | // Decode unmarshals the raw representation of the instance object being | ||
52 | // changed. Pass the implied type of the corresponding resource type schema | ||
53 | // for correct operation. | ||
54 | func (rcs *ResourceInstanceChangeSrc) Decode(ty cty.Type) (*ResourceInstanceChange, error) { | ||
55 | change, err := rcs.ChangeSrc.Decode(ty) | ||
56 | if err != nil { | ||
57 | return nil, err | ||
58 | } | ||
59 | return &ResourceInstanceChange{ | ||
60 | Addr: rcs.Addr, | ||
61 | DeposedKey: rcs.DeposedKey, | ||
62 | ProviderAddr: rcs.ProviderAddr, | ||
63 | Change: *change, | ||
64 | RequiredReplace: rcs.RequiredReplace, | ||
65 | Private: rcs.Private, | ||
66 | }, nil | ||
67 | } | ||
68 | |||
69 | // DeepCopy creates a copy of the receiver where any pointers to nested mutable | ||
70 | // values are also copied, thus ensuring that future mutations of the receiver | ||
71 | // will not affect the copy. | ||
72 | // | ||
73 | // Some types used within a resource change are immutable by convention even | ||
74 | // though the Go language allows them to be mutated, such as the types from | ||
75 | // the addrs package. These are _not_ copied by this method, under the | ||
76 | // assumption that callers will behave themselves. | ||
77 | func (rcs *ResourceInstanceChangeSrc) DeepCopy() *ResourceInstanceChangeSrc { | ||
78 | if rcs == nil { | ||
79 | return nil | ||
80 | } | ||
81 | ret := *rcs | ||
82 | |||
83 | ret.RequiredReplace = cty.NewPathSet(ret.RequiredReplace.List()...) | ||
84 | |||
85 | if len(ret.Private) != 0 { | ||
86 | private := make([]byte, len(ret.Private)) | ||
87 | copy(private, ret.Private) | ||
88 | ret.Private = private | ||
89 | } | ||
90 | |||
91 | ret.ChangeSrc.Before = ret.ChangeSrc.Before.Copy() | ||
92 | ret.ChangeSrc.After = ret.ChangeSrc.After.Copy() | ||
93 | |||
94 | return &ret | ||
95 | } | ||
96 | |||
97 | // OutputChangeSrc describes a change to an output value. | ||
98 | type OutputChangeSrc struct { | ||
99 | // Addr is the absolute address of the output value that the change | ||
100 | // will apply to. | ||
101 | Addr addrs.AbsOutputValue | ||
102 | |||
103 | // ChangeSrc is an embedded description of the not-yet-decoded change. | ||
104 | // | ||
105 | // For output value changes, the type constraint for the DynamicValue | ||
106 | // instances is always cty.DynamicPseudoType. | ||
107 | ChangeSrc | ||
108 | |||
109 | // Sensitive, if true, indicates that either the old or new value in the | ||
110 | // change is sensitive and so a rendered version of the plan in the UI | ||
111 | // should elide the actual values while still indicating the action of the | ||
112 | // change. | ||
113 | Sensitive bool | ||
114 | } | ||
115 | |||
116 | // Decode unmarshals the raw representation of the output value being | ||
117 | // changed. | ||
118 | func (ocs *OutputChangeSrc) Decode() (*OutputChange, error) { | ||
119 | change, err := ocs.ChangeSrc.Decode(cty.DynamicPseudoType) | ||
120 | if err != nil { | ||
121 | return nil, err | ||
122 | } | ||
123 | return &OutputChange{ | ||
124 | Addr: ocs.Addr, | ||
125 | Change: *change, | ||
126 | Sensitive: ocs.Sensitive, | ||
127 | }, nil | ||
128 | } | ||
129 | |||
130 | // DeepCopy creates a copy of the receiver where any pointers to nested mutable | ||
131 | // values are also copied, thus ensuring that future mutations of the receiver | ||
132 | // will not affect the copy. | ||
133 | // | ||
134 | // Some types used within a resource change are immutable by convention even | ||
135 | // though the Go language allows them to be mutated, such as the types from | ||
136 | // the addrs package. These are _not_ copied by this method, under the | ||
137 | // assumption that callers will behave themselves. | ||
138 | func (ocs *OutputChangeSrc) DeepCopy() *OutputChangeSrc { | ||
139 | if ocs == nil { | ||
140 | return nil | ||
141 | } | ||
142 | ret := *ocs | ||
143 | |||
144 | ret.ChangeSrc.Before = ret.ChangeSrc.Before.Copy() | ||
145 | ret.ChangeSrc.After = ret.ChangeSrc.After.Copy() | ||
146 | |||
147 | return &ret | ||
148 | } | ||
149 | |||
150 | // ChangeSrc is a not-yet-decoded Change. | ||
151 | type ChangeSrc struct { | ||
152 | // Action defines what kind of change is being made. | ||
153 | Action Action | ||
154 | |||
155 | // Before and After correspond to the fields of the same name in Change, | ||
156 | // but have not yet been decoded from the serialized value used for | ||
157 | // storage. | ||
158 | Before, After DynamicValue | ||
159 | } | ||
160 | |||
161 | // Decode unmarshals the raw representations of the before and after values | ||
162 | // to produce a Change object. Pass the type constraint that the result must | ||
163 | // conform to. | ||
164 | // | ||
165 | // Where a ChangeSrc is embedded in some other struct, it's generally better | ||
166 | // to call the corresponding Decode method of that struct rather than working | ||
167 | // directly with its embedded Change. | ||
168 | func (cs *ChangeSrc) Decode(ty cty.Type) (*Change, error) { | ||
169 | var err error | ||
170 | before := cty.NullVal(ty) | ||
171 | after := cty.NullVal(ty) | ||
172 | |||
173 | if len(cs.Before) > 0 { | ||
174 | before, err = cs.Before.Decode(ty) | ||
175 | if err != nil { | ||
176 | return nil, fmt.Errorf("error decoding 'before' value: %s", err) | ||
177 | } | ||
178 | } | ||
179 | if len(cs.After) > 0 { | ||
180 | after, err = cs.After.Decode(ty) | ||
181 | if err != nil { | ||
182 | return nil, fmt.Errorf("error decoding 'after' value: %s", err) | ||
183 | } | ||
184 | } | ||
185 | return &Change{ | ||
186 | Action: cs.Action, | ||
187 | Before: before, | ||
188 | After: after, | ||
189 | }, nil | ||
190 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/changes_state.go b/vendor/github.com/hashicorp/terraform/plans/changes_state.go new file mode 100644 index 0000000..543e6c2 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/changes_state.go | |||
@@ -0,0 +1,15 @@ | |||
1 | package plans | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/terraform/states" | ||
5 | ) | ||
6 | |||
7 | // PlannedState merges the set of changes described by the receiver into the | ||
8 | // given prior state to produce the planned result state. | ||
9 | // | ||
10 | // The result is an approximation of the state as it would exist after | ||
11 | // applying these changes, omitting any values that cannot be determined until | ||
12 | // the changes are actually applied. | ||
13 | func (c *Changes) PlannedState(prior *states.State) (*states.State, error) { | ||
14 | panic("Changes.PlannedState not yet implemented") | ||
15 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/changes_sync.go b/vendor/github.com/hashicorp/terraform/plans/changes_sync.go new file mode 100644 index 0000000..6b4ff98 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/changes_sync.go | |||
@@ -0,0 +1,144 @@ | |||
1 | package plans | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "sync" | ||
6 | |||
7 | "github.com/hashicorp/terraform/addrs" | ||
8 | "github.com/hashicorp/terraform/states" | ||
9 | ) | ||
10 | |||
11 | // ChangesSync is a wrapper around a Changes that provides a concurrency-safe | ||
12 | // interface to insert new changes and retrieve copies of existing changes. | ||
13 | // | ||
14 | // Each ChangesSync is independent of all others, so all concurrent writers | ||
15 | // to a particular Changes must share a single ChangesSync. Behavior is | ||
16 | // undefined if any other caller makes changes to the underlying Changes | ||
17 | // object or its nested objects concurrently with any of the methods of a | ||
18 | // particular ChangesSync. | ||
19 | type ChangesSync struct { | ||
20 | lock sync.Mutex | ||
21 | changes *Changes | ||
22 | } | ||
23 | |||
24 | // AppendResourceInstanceChange records the given resource instance change in | ||
25 | // the set of planned resource changes. | ||
26 | // | ||
27 | // The caller must ensure that there are no concurrent writes to the given | ||
28 | // change while this method is running, but it is safe to resume mutating | ||
29 | // it after this method returns without affecting the saved change. | ||
30 | func (cs *ChangesSync) AppendResourceInstanceChange(changeSrc *ResourceInstanceChangeSrc) { | ||
31 | if cs == nil { | ||
32 | panic("AppendResourceInstanceChange on nil ChangesSync") | ||
33 | } | ||
34 | cs.lock.Lock() | ||
35 | defer cs.lock.Unlock() | ||
36 | |||
37 | s := changeSrc.DeepCopy() | ||
38 | cs.changes.Resources = append(cs.changes.Resources, s) | ||
39 | } | ||
40 | |||
41 | // GetResourceInstanceChange searches the set of resource instance changes for | ||
42 | // one matching the given address and generation, returning it if it exists. | ||
43 | // | ||
44 | // If no such change exists, nil is returned. | ||
45 | // | ||
46 | // The returned object is a deep copy of the change recorded in the plan, so | ||
47 | // callers may mutate it although it's generally better (less confusing) to | ||
48 | // treat planned changes as immutable after they've been initially constructed. | ||
49 | func (cs *ChangesSync) GetResourceInstanceChange(addr addrs.AbsResourceInstance, gen states.Generation) *ResourceInstanceChangeSrc { | ||
50 | if cs == nil { | ||
51 | panic("GetResourceInstanceChange on nil ChangesSync") | ||
52 | } | ||
53 | cs.lock.Lock() | ||
54 | defer cs.lock.Unlock() | ||
55 | |||
56 | if gen == states.CurrentGen { | ||
57 | return cs.changes.ResourceInstance(addr).DeepCopy() | ||
58 | } | ||
59 | if dk, ok := gen.(states.DeposedKey); ok { | ||
60 | return cs.changes.ResourceInstanceDeposed(addr, dk).DeepCopy() | ||
61 | } | ||
62 | panic(fmt.Sprintf("unsupported generation value %#v", gen)) | ||
63 | } | ||
64 | |||
65 | // RemoveResourceInstanceChange searches the set of resource instance changes | ||
66 | // for one matching the given address and generation, and removes it from the | ||
67 | // set if it exists. | ||
68 | func (cs *ChangesSync) RemoveResourceInstanceChange(addr addrs.AbsResourceInstance, gen states.Generation) { | ||
69 | if cs == nil { | ||
70 | panic("RemoveResourceInstanceChange on nil ChangesSync") | ||
71 | } | ||
72 | cs.lock.Lock() | ||
73 | defer cs.lock.Unlock() | ||
74 | |||
75 | dk := states.NotDeposed | ||
76 | if realDK, ok := gen.(states.DeposedKey); ok { | ||
77 | dk = realDK | ||
78 | } | ||
79 | |||
80 | addrStr := addr.String() | ||
81 | for i, r := range cs.changes.Resources { | ||
82 | if r.Addr.String() != addrStr || r.DeposedKey != dk { | ||
83 | continue | ||
84 | } | ||
85 | copy(cs.changes.Resources[i:], cs.changes.Resources[i+1:]) | ||
86 | cs.changes.Resources = cs.changes.Resources[:len(cs.changes.Resources)-1] | ||
87 | return | ||
88 | } | ||
89 | } | ||
90 | |||
91 | // AppendOutputChange records the given output value change in the set of | ||
92 | // planned value changes. | ||
93 | // | ||
94 | // The caller must ensure that there are no concurrent writes to the given | ||
95 | // change while this method is running, but it is safe to resume mutating | ||
96 | // it after this method returns without affecting the saved change. | ||
97 | func (cs *ChangesSync) AppendOutputChange(changeSrc *OutputChangeSrc) { | ||
98 | if cs == nil { | ||
99 | panic("AppendOutputChange on nil ChangesSync") | ||
100 | } | ||
101 | cs.lock.Lock() | ||
102 | defer cs.lock.Unlock() | ||
103 | |||
104 | s := changeSrc.DeepCopy() | ||
105 | cs.changes.Outputs = append(cs.changes.Outputs, s) | ||
106 | } | ||
107 | |||
108 | // GetOutputChange searches the set of output value changes for one matching | ||
109 | // the given address, returning it if it exists. | ||
110 | // | ||
111 | // If no such change exists, nil is returned. | ||
112 | // | ||
113 | // The returned object is a deep copy of the change recorded in the plan, so | ||
114 | // callers may mutate it although it's generally better (less confusing) to | ||
115 | // treat planned changes as immutable after they've been initially constructed. | ||
116 | func (cs *ChangesSync) GetOutputChange(addr addrs.AbsOutputValue) *OutputChangeSrc { | ||
117 | if cs == nil { | ||
118 | panic("GetOutputChange on nil ChangesSync") | ||
119 | } | ||
120 | cs.lock.Lock() | ||
121 | defer cs.lock.Unlock() | ||
122 | |||
123 | return cs.changes.OutputValue(addr) | ||
124 | } | ||
125 | |||
126 | // RemoveOutputChange searches the set of output value changes for one matching | ||
127 | // the given address, and removes it from the set if it exists. | ||
128 | func (cs *ChangesSync) RemoveOutputChange(addr addrs.AbsOutputValue) { | ||
129 | if cs == nil { | ||
130 | panic("RemoveOutputChange on nil ChangesSync") | ||
131 | } | ||
132 | cs.lock.Lock() | ||
133 | defer cs.lock.Unlock() | ||
134 | |||
135 | addrStr := addr.String() | ||
136 | for i, o := range cs.changes.Outputs { | ||
137 | if o.Addr.String() != addrStr { | ||
138 | continue | ||
139 | } | ||
140 | copy(cs.changes.Outputs[i:], cs.changes.Outputs[i+1:]) | ||
141 | cs.changes.Outputs = cs.changes.Outputs[:len(cs.changes.Outputs)-1] | ||
142 | return | ||
143 | } | ||
144 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/doc.go b/vendor/github.com/hashicorp/terraform/plans/doc.go new file mode 100644 index 0000000..01ca389 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/doc.go | |||
@@ -0,0 +1,5 @@ | |||
1 | // Package plans contains the types that are used to represent Terraform plans. | ||
2 | // | ||
3 | // A plan describes a set of changes that Terraform will make to update remote | ||
4 | // objects to match with changes to the configuration. | ||
5 | package plans | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/dynamic_value.go b/vendor/github.com/hashicorp/terraform/plans/dynamic_value.go new file mode 100644 index 0000000..51fbb24 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/dynamic_value.go | |||
@@ -0,0 +1,96 @@ | |||
1 | package plans | ||
2 | |||
3 | import ( | ||
4 | "github.com/zclconf/go-cty/cty" | ||
5 | ctymsgpack "github.com/zclconf/go-cty/cty/msgpack" | ||
6 | ) | ||
7 | |||
8 | // DynamicValue is the representation in the plan of a value whose type cannot | ||
9 | // be determined at compile time, such as because it comes from a schema | ||
10 | // defined in a plugin. | ||
11 | // | ||
12 | // This type is used as an indirection so that the overall plan structure can | ||
13 | // be decoded without schema available, and then the dynamic values accessed | ||
14 | // at a later time once the appropriate schema has been determined. | ||
15 | // | ||
16 | // Internally, DynamicValue is a serialized version of a cty.Value created | ||
17 | // against a particular type constraint. Callers should not access directly | ||
18 | // the serialized form, whose format may change in future. Values of this | ||
19 | // type must always be created by calling NewDynamicValue. | ||
20 | // | ||
21 | // The zero value of DynamicValue is nil, and represents the absense of a | ||
22 | // value within the Go type system. This is distinct from a cty.NullVal | ||
23 | // result, which represents the absense of a value within the cty type system. | ||
24 | type DynamicValue []byte | ||
25 | |||
26 | // NewDynamicValue creates a DynamicValue by serializing the given value | ||
27 | // against the given type constraint. The value must conform to the type | ||
28 | // constraint, or the result is undefined. | ||
29 | // | ||
30 | // If the value to be encoded has no predefined schema (for example, for | ||
31 | // module output values and input variables), set the type constraint to | ||
32 | // cty.DynamicPseudoType in order to save type information as part of the | ||
33 | // value, and then also pass cty.DynamicPseudoType to method Decode to recover | ||
34 | // the original value. | ||
35 | // | ||
36 | // cty.NilVal can be used to represent the absense of a value, but callers | ||
37 | // must be careful to distinguish values that are absent at the Go layer | ||
38 | // (cty.NilVal) vs. values that are absent at the cty layer (cty.NullVal | ||
39 | // results). | ||
40 | func NewDynamicValue(val cty.Value, ty cty.Type) (DynamicValue, error) { | ||
41 | // If we're given cty.NilVal (the zero value of cty.Value, which is | ||
42 | // distinct from a typed null value created by cty.NullVal) then we'll | ||
43 | // assume the caller is trying to represent the _absense_ of a value, | ||
44 | // and so we'll return a nil DynamicValue. | ||
45 | if val == cty.NilVal { | ||
46 | return DynamicValue(nil), nil | ||
47 | } | ||
48 | |||
49 | // Currently our internal encoding is msgpack, via ctymsgpack. | ||
50 | buf, err := ctymsgpack.Marshal(val, ty) | ||
51 | if err != nil { | ||
52 | return nil, err | ||
53 | } | ||
54 | |||
55 | return DynamicValue(buf), nil | ||
56 | } | ||
57 | |||
58 | // Decode retrieves the effective value from the receiever by interpreting the | ||
59 | // serialized form against the given type constraint. For correct results, | ||
60 | // the type constraint must match (or be consistent with) the one that was | ||
61 | // used to create the receiver. | ||
62 | // | ||
63 | // A nil DynamicValue decodes to cty.NilVal, which is not a valid value and | ||
64 | // instead represents the absense of a value. | ||
65 | func (v DynamicValue) Decode(ty cty.Type) (cty.Value, error) { | ||
66 | if v == nil { | ||
67 | return cty.NilVal, nil | ||
68 | } | ||
69 | |||
70 | return ctymsgpack.Unmarshal([]byte(v), ty) | ||
71 | } | ||
72 | |||
73 | // ImpliedType returns the type implied by the serialized structure of the | ||
74 | // receiving value. | ||
75 | // | ||
76 | // This will not necessarily be exactly the type that was given when the | ||
77 | // value was encoded, and in particular must not be used for values that | ||
78 | // were encoded with their static type given as cty.DynamicPseudoType. | ||
79 | // It is however safe to use this method for values that were encoded using | ||
80 | // their runtime type as the conforming type, with the result being | ||
81 | // semantically equivalent but with all lists and sets represented as tuples, | ||
82 | // and maps as objects, due to ambiguities of the serialization. | ||
83 | func (v DynamicValue) ImpliedType() (cty.Type, error) { | ||
84 | return ctymsgpack.ImpliedType([]byte(v)) | ||
85 | } | ||
86 | |||
87 | // Copy produces a copy of the receiver with a distinct backing array. | ||
88 | func (v DynamicValue) Copy() DynamicValue { | ||
89 | if v == nil { | ||
90 | return nil | ||
91 | } | ||
92 | |||
93 | ret := make(DynamicValue, len(v)) | ||
94 | copy(ret, v) | ||
95 | return ret | ||
96 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/objchange/all_null.go b/vendor/github.com/hashicorp/terraform/plans/objchange/all_null.go new file mode 100644 index 0000000..18a7e99 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/objchange/all_null.go | |||
@@ -0,0 +1,18 @@ | |||
1 | package objchange | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/terraform/configs/configschema" | ||
5 | "github.com/zclconf/go-cty/cty" | ||
6 | ) | ||
7 | |||
8 | // AllAttributesNull constructs a non-null cty.Value of the object type implied | ||
9 | // by the given schema that has all of its leaf attributes set to null and all | ||
10 | // of its nested block collections set to zero-length. | ||
11 | // | ||
12 | // This simulates what would result from decoding an empty configuration block | ||
13 | // with the given schema, except that it does not produce errors | ||
14 | func AllAttributesNull(schema *configschema.Block) cty.Value { | ||
15 | // "All attributes null" happens to be the definition of EmptyValue for | ||
16 | // a Block, so we can just delegate to that. | ||
17 | return schema.EmptyValue() | ||
18 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/objchange/compatible.go b/vendor/github.com/hashicorp/terraform/plans/objchange/compatible.go new file mode 100644 index 0000000..8b7ef43 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/objchange/compatible.go | |||
@@ -0,0 +1,437 @@ | |||
1 | package objchange | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "strconv" | ||
6 | |||
7 | "github.com/zclconf/go-cty/cty" | ||
8 | "github.com/zclconf/go-cty/cty/convert" | ||
9 | |||
10 | "github.com/hashicorp/terraform/configs/configschema" | ||
11 | ) | ||
12 | |||
13 | // AssertObjectCompatible checks whether the given "actual" value is a valid | ||
14 | // completion of the possibly-partially-unknown "planned" value. | ||
15 | // | ||
16 | // This means that any known leaf value in "planned" must be equal to the | ||
17 | // corresponding value in "actual", and various other similar constraints. | ||
18 | // | ||
19 | // Any inconsistencies are reported by returning a non-zero number of errors. | ||
20 | // These errors are usually (but not necessarily) cty.PathError values | ||
21 | // referring to a particular nested value within the "actual" value. | ||
22 | // | ||
23 | // The two values must have types that conform to the given schema's implied | ||
24 | // type, or this function will panic. | ||
25 | func AssertObjectCompatible(schema *configschema.Block, planned, actual cty.Value) []error { | ||
26 | return assertObjectCompatible(schema, planned, actual, nil) | ||
27 | } | ||
28 | |||
29 | func assertObjectCompatible(schema *configschema.Block, planned, actual cty.Value, path cty.Path) []error { | ||
30 | var errs []error | ||
31 | if planned.IsNull() && !actual.IsNull() { | ||
32 | errs = append(errs, path.NewErrorf("was absent, but now present")) | ||
33 | return errs | ||
34 | } | ||
35 | if actual.IsNull() && !planned.IsNull() { | ||
36 | errs = append(errs, path.NewErrorf("was present, but now absent")) | ||
37 | return errs | ||
38 | } | ||
39 | if planned.IsNull() { | ||
40 | // No further checks possible if both values are null | ||
41 | return errs | ||
42 | } | ||
43 | |||
44 | for name, attrS := range schema.Attributes { | ||
45 | plannedV := planned.GetAttr(name) | ||
46 | actualV := actual.GetAttr(name) | ||
47 | |||
48 | path := append(path, cty.GetAttrStep{Name: name}) | ||
49 | moreErrs := assertValueCompatible(plannedV, actualV, path) | ||
50 | if attrS.Sensitive { | ||
51 | if len(moreErrs) > 0 { | ||
52 | // Use a vague placeholder message instead, to avoid disclosing | ||
53 | // sensitive information. | ||
54 | errs = append(errs, path.NewErrorf("inconsistent values for sensitive attribute")) | ||
55 | } | ||
56 | } else { | ||
57 | errs = append(errs, moreErrs...) | ||
58 | } | ||
59 | } | ||
60 | for name, blockS := range schema.BlockTypes { | ||
61 | plannedV := planned.GetAttr(name) | ||
62 | actualV := actual.GetAttr(name) | ||
63 | |||
64 | // As a special case, if there were any blocks whose leaf attributes | ||
65 | // are all unknown then we assume (possibly incorrectly) that the | ||
66 | // HCL dynamic block extension is in use with an unknown for_each | ||
67 | // argument, and so we will do looser validation here that allows | ||
68 | // for those blocks to have expanded into a different number of blocks | ||
69 | // if the for_each value is now known. | ||
70 | maybeUnknownBlocks := couldHaveUnknownBlockPlaceholder(plannedV, blockS, false) | ||
71 | |||
72 | path := append(path, cty.GetAttrStep{Name: name}) | ||
73 | switch blockS.Nesting { | ||
74 | case configschema.NestingSingle, configschema.NestingGroup: | ||
75 | // If an unknown block placeholder was present then the placeholder | ||
76 | // may have expanded out into zero blocks, which is okay. | ||
77 | if maybeUnknownBlocks && actualV.IsNull() { | ||
78 | continue | ||
79 | } | ||
80 | moreErrs := assertObjectCompatible(&blockS.Block, plannedV, actualV, path) | ||
81 | errs = append(errs, moreErrs...) | ||
82 | case configschema.NestingList: | ||
83 | // A NestingList might either be a list or a tuple, depending on | ||
84 | // whether there are dynamically-typed attributes inside. However, | ||
85 | // both support a similar-enough API that we can treat them the | ||
86 | // same for our purposes here. | ||
87 | if !plannedV.IsKnown() || plannedV.IsNull() || actualV.IsNull() { | ||
88 | continue | ||
89 | } | ||
90 | |||
91 | if maybeUnknownBlocks { | ||
92 | // When unknown blocks are present the final blocks may be | ||
93 | // at different indices than the planned blocks, so unfortunately | ||
94 | // we can't do our usual checks in this case without generating | ||
95 | // false negatives. | ||
96 | continue | ||
97 | } | ||
98 | |||
99 | plannedL := plannedV.LengthInt() | ||
100 | actualL := actualV.LengthInt() | ||
101 | if plannedL != actualL { | ||
102 | errs = append(errs, path.NewErrorf("block count changed from %d to %d", plannedL, actualL)) | ||
103 | continue | ||
104 | } | ||
105 | for it := plannedV.ElementIterator(); it.Next(); { | ||
106 | idx, plannedEV := it.Element() | ||
107 | if !actualV.HasIndex(idx).True() { | ||
108 | continue | ||
109 | } | ||
110 | actualEV := actualV.Index(idx) | ||
111 | moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.IndexStep{Key: idx})) | ||
112 | errs = append(errs, moreErrs...) | ||
113 | } | ||
114 | case configschema.NestingMap: | ||
115 | // A NestingMap might either be a map or an object, depending on | ||
116 | // whether there are dynamically-typed attributes inside, but | ||
117 | // that's decided statically and so both values will have the same | ||
118 | // kind. | ||
119 | if plannedV.Type().IsObjectType() { | ||
120 | plannedAtys := plannedV.Type().AttributeTypes() | ||
121 | actualAtys := actualV.Type().AttributeTypes() | ||
122 | for k := range plannedAtys { | ||
123 | if _, ok := actualAtys[k]; !ok { | ||
124 | errs = append(errs, path.NewErrorf("block key %q has vanished", k)) | ||
125 | continue | ||
126 | } | ||
127 | |||
128 | plannedEV := plannedV.GetAttr(k) | ||
129 | actualEV := actualV.GetAttr(k) | ||
130 | moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.GetAttrStep{Name: k})) | ||
131 | errs = append(errs, moreErrs...) | ||
132 | } | ||
133 | if !maybeUnknownBlocks { // new blocks may appear if unknown blocks were present in the plan | ||
134 | for k := range actualAtys { | ||
135 | if _, ok := plannedAtys[k]; !ok { | ||
136 | errs = append(errs, path.NewErrorf("new block key %q has appeared", k)) | ||
137 | continue | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | } else { | ||
142 | if !plannedV.IsKnown() || plannedV.IsNull() || actualV.IsNull() { | ||
143 | continue | ||
144 | } | ||
145 | plannedL := plannedV.LengthInt() | ||
146 | actualL := actualV.LengthInt() | ||
147 | if plannedL != actualL && !maybeUnknownBlocks { // new blocks may appear if unknown blocks were persent in the plan | ||
148 | errs = append(errs, path.NewErrorf("block count changed from %d to %d", plannedL, actualL)) | ||
149 | continue | ||
150 | } | ||
151 | for it := plannedV.ElementIterator(); it.Next(); { | ||
152 | idx, plannedEV := it.Element() | ||
153 | if !actualV.HasIndex(idx).True() { | ||
154 | continue | ||
155 | } | ||
156 | actualEV := actualV.Index(idx) | ||
157 | moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.IndexStep{Key: idx})) | ||
158 | errs = append(errs, moreErrs...) | ||
159 | } | ||
160 | } | ||
161 | case configschema.NestingSet: | ||
162 | if !plannedV.IsKnown() || !actualV.IsKnown() || plannedV.IsNull() || actualV.IsNull() { | ||
163 | continue | ||
164 | } | ||
165 | |||
166 | setErrs := assertSetValuesCompatible(plannedV, actualV, path, func(plannedEV, actualEV cty.Value) bool { | ||
167 | errs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.IndexStep{Key: actualEV})) | ||
168 | return len(errs) == 0 | ||
169 | }) | ||
170 | errs = append(errs, setErrs...) | ||
171 | |||
172 | // There can be fewer elements in a set after its elements are all | ||
173 | // known (values that turn out to be equal will coalesce) but the | ||
174 | // number of elements must never get larger. | ||
175 | plannedL := plannedV.LengthInt() | ||
176 | actualL := actualV.LengthInt() | ||
177 | if plannedL < actualL { | ||
178 | errs = append(errs, path.NewErrorf("block set length changed from %d to %d", plannedL, actualL)) | ||
179 | } | ||
180 | default: | ||
181 | panic(fmt.Sprintf("unsupported nesting mode %s", blockS.Nesting)) | ||
182 | } | ||
183 | } | ||
184 | return errs | ||
185 | } | ||
186 | |||
187 | func assertValueCompatible(planned, actual cty.Value, path cty.Path) []error { | ||
188 | // NOTE: We don't normally use the GoString rendering of cty.Value in | ||
189 | // user-facing error messages as a rule, but we make an exception | ||
190 | // for this function because we expect the user to pass this message on | ||
191 | // verbatim to the provider development team and so more detail is better. | ||
192 | |||
193 | var errs []error | ||
194 | if planned.Type() == cty.DynamicPseudoType { | ||
195 | // Anything goes, then | ||
196 | return errs | ||
197 | } | ||
198 | if problems := planned.Type().TestConformance(actual.Type()); len(problems) > 0 { | ||
199 | errs = append(errs, path.NewErrorf("wrong final value type: %s", convert.MismatchMessage(actual.Type(), planned.Type()))) | ||
200 | // If the types don't match then we can't do any other comparisons, | ||
201 | // so we bail early. | ||
202 | return errs | ||
203 | } | ||
204 | |||
205 | if !planned.IsKnown() { | ||
206 | // We didn't know what were going to end up with during plan, so | ||
207 | // anything goes during apply. | ||
208 | return errs | ||
209 | } | ||
210 | |||
211 | if actual.IsNull() { | ||
212 | if planned.IsNull() { | ||
213 | return nil | ||
214 | } | ||
215 | errs = append(errs, path.NewErrorf("was %#v, but now null", planned)) | ||
216 | return errs | ||
217 | } | ||
218 | if planned.IsNull() { | ||
219 | errs = append(errs, path.NewErrorf("was null, but now %#v", actual)) | ||
220 | return errs | ||
221 | } | ||
222 | |||
223 | ty := planned.Type() | ||
224 | switch { | ||
225 | |||
226 | case !actual.IsKnown(): | ||
227 | errs = append(errs, path.NewErrorf("was known, but now unknown")) | ||
228 | |||
229 | case ty.IsPrimitiveType(): | ||
230 | if !actual.Equals(planned).True() { | ||
231 | errs = append(errs, path.NewErrorf("was %#v, but now %#v", planned, actual)) | ||
232 | } | ||
233 | |||
234 | case ty.IsListType() || ty.IsMapType() || ty.IsTupleType(): | ||
235 | for it := planned.ElementIterator(); it.Next(); { | ||
236 | k, plannedV := it.Element() | ||
237 | if !actual.HasIndex(k).True() { | ||
238 | errs = append(errs, path.NewErrorf("element %s has vanished", indexStrForErrors(k))) | ||
239 | continue | ||
240 | } | ||
241 | |||
242 | actualV := actual.Index(k) | ||
243 | moreErrs := assertValueCompatible(plannedV, actualV, append(path, cty.IndexStep{Key: k})) | ||
244 | errs = append(errs, moreErrs...) | ||
245 | } | ||
246 | |||
247 | for it := actual.ElementIterator(); it.Next(); { | ||
248 | k, _ := it.Element() | ||
249 | if !planned.HasIndex(k).True() { | ||
250 | errs = append(errs, path.NewErrorf("new element %s has appeared", indexStrForErrors(k))) | ||
251 | } | ||
252 | } | ||
253 | |||
254 | case ty.IsObjectType(): | ||
255 | atys := ty.AttributeTypes() | ||
256 | for name := range atys { | ||
257 | // Because we already tested that the two values have the same type, | ||
258 | // we can assume that the same attributes are present in both and | ||
259 | // focus just on testing their values. | ||
260 | plannedV := planned.GetAttr(name) | ||
261 | actualV := actual.GetAttr(name) | ||
262 | moreErrs := assertValueCompatible(plannedV, actualV, append(path, cty.GetAttrStep{Name: name})) | ||
263 | errs = append(errs, moreErrs...) | ||
264 | } | ||
265 | |||
266 | case ty.IsSetType(): | ||
267 | // We can't really do anything useful for sets here because changing | ||
268 | // an unknown element to known changes the identity of the element, and | ||
269 | // so we can't correlate them properly. However, we will at least check | ||
270 | // to ensure that the number of elements is consistent, along with | ||
271 | // the general type-match checks we ran earlier in this function. | ||
272 | if planned.IsKnown() && !planned.IsNull() && !actual.IsNull() { | ||
273 | |||
274 | setErrs := assertSetValuesCompatible(planned, actual, path, func(plannedV, actualV cty.Value) bool { | ||
275 | errs := assertValueCompatible(plannedV, actualV, append(path, cty.IndexStep{Key: actualV})) | ||
276 | return len(errs) == 0 | ||
277 | }) | ||
278 | errs = append(errs, setErrs...) | ||
279 | |||
280 | // There can be fewer elements in a set after its elements are all | ||
281 | // known (values that turn out to be equal will coalesce) but the | ||
282 | // number of elements must never get larger. | ||
283 | |||
284 | plannedL := planned.LengthInt() | ||
285 | actualL := actual.LengthInt() | ||
286 | if plannedL < actualL { | ||
287 | errs = append(errs, path.NewErrorf("length changed from %d to %d", plannedL, actualL)) | ||
288 | } | ||
289 | } | ||
290 | } | ||
291 | |||
292 | return errs | ||
293 | } | ||
294 | |||
295 | func indexStrForErrors(v cty.Value) string { | ||
296 | switch v.Type() { | ||
297 | case cty.Number: | ||
298 | return v.AsBigFloat().Text('f', -1) | ||
299 | case cty.String: | ||
300 | return strconv.Quote(v.AsString()) | ||
301 | default: | ||
302 | // Should be impossible, since no other index types are allowed! | ||
303 | return fmt.Sprintf("%#v", v) | ||
304 | } | ||
305 | } | ||
306 | |||
307 | // couldHaveUnknownBlockPlaceholder is a heuristic that recognizes how the | ||
308 | // HCL dynamic block extension behaves when it's asked to expand a block whose | ||
309 | // for_each argument is unknown. In such cases, it generates a single placeholder | ||
310 | // block with all leaf attribute values unknown, and once the for_each | ||
311 | // expression becomes known the placeholder may be replaced with any number | ||
312 | // of blocks, so object compatibility checks would need to be more liberal. | ||
313 | // | ||
314 | // Set "nested" if testing a block that is nested inside a candidate block | ||
315 | // placeholder; this changes the interpretation of there being no blocks of | ||
316 | // a type to allow for there being zero nested blocks. | ||
317 | func couldHaveUnknownBlockPlaceholder(v cty.Value, blockS *configschema.NestedBlock, nested bool) bool { | ||
318 | switch blockS.Nesting { | ||
319 | case configschema.NestingSingle, configschema.NestingGroup: | ||
320 | if nested && v.IsNull() { | ||
321 | return true // for nested blocks, a single block being unset doesn't disqualify from being an unknown block placeholder | ||
322 | } | ||
323 | return couldBeUnknownBlockPlaceholderElement(v, &blockS.Block) | ||
324 | default: | ||
325 | // These situations should be impossible for correct providers, but | ||
326 | // we permit the legacy SDK to produce some incorrect outcomes | ||
327 | // for compatibility with its existing logic, and so we must be | ||
328 | // tolerant here. | ||
329 | if !v.IsKnown() { | ||
330 | return true | ||
331 | } | ||
332 | if v.IsNull() { | ||
333 | return false // treated as if the list were empty, so we would see zero iterations below | ||
334 | } | ||
335 | |||
336 | // For all other nesting modes, our value should be something iterable. | ||
337 | for it := v.ElementIterator(); it.Next(); { | ||
338 | _, ev := it.Element() | ||
339 | if couldBeUnknownBlockPlaceholderElement(ev, &blockS.Block) { | ||
340 | return true | ||
341 | } | ||
342 | } | ||
343 | |||
344 | // Our default changes depending on whether we're testing the candidate | ||
345 | // block itself or something nested inside of it: zero blocks of a type | ||
346 | // can never contain a dynamic block placeholder, but a dynamic block | ||
347 | // placeholder might contain zero blocks of one of its own nested block | ||
348 | // types, if none were set in the config at all. | ||
349 | return nested | ||
350 | } | ||
351 | } | ||
352 | |||
353 | func couldBeUnknownBlockPlaceholderElement(v cty.Value, schema *configschema.Block) bool { | ||
354 | if v.IsNull() { | ||
355 | return false // null value can never be a placeholder element | ||
356 | } | ||
357 | if !v.IsKnown() { | ||
358 | return true // this should never happen for well-behaved providers, but can happen with the legacy SDK opt-outs | ||
359 | } | ||
360 | for name := range schema.Attributes { | ||
361 | av := v.GetAttr(name) | ||
362 | |||
363 | // Unknown block placeholders contain only unknown or null attribute | ||
364 | // values, depending on whether or not a particular attribute was set | ||
365 | // explicitly inside the content block. Note that this is imprecise: | ||
366 | // non-placeholders can also match this, so this function can generate | ||
367 | // false positives. | ||
368 | if av.IsKnown() && !av.IsNull() { | ||
369 | return false | ||
370 | } | ||
371 | } | ||
372 | for name, blockS := range schema.BlockTypes { | ||
373 | if !couldHaveUnknownBlockPlaceholder(v.GetAttr(name), blockS, true) { | ||
374 | return false | ||
375 | } | ||
376 | } | ||
377 | return true | ||
378 | } | ||
379 | |||
380 | // assertSetValuesCompatible checks that each of the elements in a can | ||
381 | // be correlated with at least one equivalent element in b and vice-versa, | ||
382 | // using the given correlation function. | ||
383 | // | ||
384 | // This allows the number of elements in the sets to change as long as all | ||
385 | // elements in both sets can be correlated, making this function safe to use | ||
386 | // with sets that may contain unknown values as long as the unknown case is | ||
387 | // addressed in some reasonable way in the callback function. | ||
388 | // | ||
389 | // The callback always recieves values from set a as its first argument and | ||
390 | // values from set b in its second argument, so it is safe to use with | ||
391 | // non-commutative functions. | ||
392 | // | ||
393 | // As with assertValueCompatible, we assume that the target audience of error | ||
394 | // messages here is a provider developer (via a bug report from a user) and so | ||
395 | // we intentionally violate our usual rule of keeping cty implementation | ||
396 | // details out of error messages. | ||
397 | func assertSetValuesCompatible(planned, actual cty.Value, path cty.Path, f func(aVal, bVal cty.Value) bool) []error { | ||
398 | a := planned | ||
399 | b := actual | ||
400 | |||
401 | // Our methodology here is a little tricky, to deal with the fact that | ||
402 | // it's impossible to directly correlate two non-equal set elements because | ||
403 | // they don't have identities separate from their values. | ||
404 | // The approach is to count the number of equivalent elements each element | ||
405 | // of a has in b and vice-versa, and then return true only if each element | ||
406 | // in both sets has at least one equivalent. | ||
407 | as := a.AsValueSlice() | ||
408 | bs := b.AsValueSlice() | ||
409 | aeqs := make([]bool, len(as)) | ||
410 | beqs := make([]bool, len(bs)) | ||
411 | for ai, av := range as { | ||
412 | for bi, bv := range bs { | ||
413 | if f(av, bv) { | ||
414 | aeqs[ai] = true | ||
415 | beqs[bi] = true | ||
416 | } | ||
417 | } | ||
418 | } | ||
419 | |||
420 | var errs []error | ||
421 | for i, eq := range aeqs { | ||
422 | if !eq { | ||
423 | errs = append(errs, path.NewErrorf("planned set element %#v does not correlate with any element in actual", as[i])) | ||
424 | } | ||
425 | } | ||
426 | if len(errs) > 0 { | ||
427 | // Exit early since otherwise we're likely to generate duplicate | ||
428 | // error messages from the other perspective in the subsequent loop. | ||
429 | return errs | ||
430 | } | ||
431 | for i, eq := range beqs { | ||
432 | if !eq { | ||
433 | errs = append(errs, path.NewErrorf("actual set element %#v does not correlate with any element in plan", bs[i])) | ||
434 | } | ||
435 | } | ||
436 | return errs | ||
437 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/objchange/doc.go b/vendor/github.com/hashicorp/terraform/plans/objchange/doc.go new file mode 100644 index 0000000..2c18a01 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/objchange/doc.go | |||
@@ -0,0 +1,4 @@ | |||
1 | // Package objchange deals with the business logic of taking a prior state | ||
2 | // value and a config value and producing a proposed new merged value, along | ||
3 | // with other related rules in this domain. | ||
4 | package objchange | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/objchange/lcs.go b/vendor/github.com/hashicorp/terraform/plans/objchange/lcs.go new file mode 100644 index 0000000..cbfefdd --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/objchange/lcs.go | |||
@@ -0,0 +1,104 @@ | |||
1 | package objchange | ||
2 | |||
3 | import ( | ||
4 | "github.com/zclconf/go-cty/cty" | ||
5 | ) | ||
6 | |||
7 | // LongestCommonSubsequence finds a sequence of values that are common to both | ||
8 | // x and y, with the same relative ordering as in both collections. This result | ||
9 | // is useful as a first step towards computing a diff showing added/removed | ||
10 | // elements in a sequence. | ||
11 | // | ||
12 | // The approached used here is a "naive" one, assuming that both xs and ys will | ||
13 | // generally be small in most reasonable Terraform configurations. For larger | ||
14 | // lists the time/space usage may be sub-optimal. | ||
15 | // | ||
16 | // A pair of lists may have multiple longest common subsequences. In that | ||
17 | // case, the one selected by this function is undefined. | ||
18 | func LongestCommonSubsequence(xs, ys []cty.Value) []cty.Value { | ||
19 | if len(xs) == 0 || len(ys) == 0 { | ||
20 | return make([]cty.Value, 0) | ||
21 | } | ||
22 | |||
23 | c := make([]int, len(xs)*len(ys)) | ||
24 | eqs := make([]bool, len(xs)*len(ys)) | ||
25 | w := len(xs) | ||
26 | |||
27 | for y := 0; y < len(ys); y++ { | ||
28 | for x := 0; x < len(xs); x++ { | ||
29 | eqV := xs[x].Equals(ys[y]) | ||
30 | eq := false | ||
31 | if eqV.IsKnown() && eqV.True() { | ||
32 | eq = true | ||
33 | eqs[(w*y)+x] = true // equality tests can be expensive, so cache it | ||
34 | } | ||
35 | if eq { | ||
36 | // Sequence gets one longer than for the cell at top left, | ||
37 | // since we'd append a new item to the sequence here. | ||
38 | if x == 0 || y == 0 { | ||
39 | c[(w*y)+x] = 1 | ||
40 | } else { | ||
41 | c[(w*y)+x] = c[(w*(y-1))+(x-1)] + 1 | ||
42 | } | ||
43 | } else { | ||
44 | // We follow the longest of the sequence above and the sequence | ||
45 | // to the left of us in the matrix. | ||
46 | l := 0 | ||
47 | u := 0 | ||
48 | if x > 0 { | ||
49 | l = c[(w*y)+(x-1)] | ||
50 | } | ||
51 | if y > 0 { | ||
52 | u = c[(w*(y-1))+x] | ||
53 | } | ||
54 | if l > u { | ||
55 | c[(w*y)+x] = l | ||
56 | } else { | ||
57 | c[(w*y)+x] = u | ||
58 | } | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | |||
63 | // The bottom right cell tells us how long our longest sequence will be | ||
64 | seq := make([]cty.Value, c[len(c)-1]) | ||
65 | |||
66 | // Now we will walk back from the bottom right cell, finding again all | ||
67 | // of the equal pairs to construct our sequence. | ||
68 | x := len(xs) - 1 | ||
69 | y := len(ys) - 1 | ||
70 | i := len(seq) - 1 | ||
71 | |||
72 | for x > -1 && y > -1 { | ||
73 | if eqs[(w*y)+x] { | ||
74 | // Add the value to our result list and then walk diagonally | ||
75 | // up and to the left. | ||
76 | seq[i] = xs[x] | ||
77 | x-- | ||
78 | y-- | ||
79 | i-- | ||
80 | } else { | ||
81 | // Take the path with the greatest sequence length in the matrix. | ||
82 | l := 0 | ||
83 | u := 0 | ||
84 | if x > 0 { | ||
85 | l = c[(w*y)+(x-1)] | ||
86 | } | ||
87 | if y > 0 { | ||
88 | u = c[(w*(y-1))+x] | ||
89 | } | ||
90 | if l > u { | ||
91 | x-- | ||
92 | } else { | ||
93 | y-- | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | |||
98 | if i > -1 { | ||
99 | // should never happen if the matrix was constructed properly | ||
100 | panic("not enough elements in sequence") | ||
101 | } | ||
102 | |||
103 | return seq | ||
104 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/objchange/normalize_obj.go b/vendor/github.com/hashicorp/terraform/plans/objchange/normalize_obj.go new file mode 100644 index 0000000..c23f44d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/objchange/normalize_obj.go | |||
@@ -0,0 +1,132 @@ | |||
1 | package objchange | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/terraform/configs/configschema" | ||
5 | "github.com/zclconf/go-cty/cty" | ||
6 | ) | ||
7 | |||
8 | // NormalizeObjectFromLegacySDK takes an object that may have been generated | ||
9 | // by the legacy Terraform SDK (i.e. returned from a provider with the | ||
10 | // LegacyTypeSystem opt-out set) and does its best to normalize it for the | ||
11 | // assumptions we would normally enforce if the provider had not opted out. | ||
12 | // | ||
13 | // In particular, this function guarantees that a value representing a nested | ||
14 | // block will never itself be unknown or null, instead representing that as | ||
15 | // a non-null value that may contain null/unknown values. | ||
16 | // | ||
17 | // The input value must still conform to the implied type of the given schema, | ||
18 | // or else this function may produce garbage results or panic. This is usually | ||
19 | // okay because type consistency is enforced when deserializing the value | ||
20 | // returned from the provider over the RPC wire protocol anyway. | ||
21 | func NormalizeObjectFromLegacySDK(val cty.Value, schema *configschema.Block) cty.Value { | ||
22 | if val == cty.NilVal || val.IsNull() { | ||
23 | // This should never happen in reasonable use, but we'll allow it | ||
24 | // and normalize to a null of the expected type rather than panicking | ||
25 | // below. | ||
26 | return cty.NullVal(schema.ImpliedType()) | ||
27 | } | ||
28 | |||
29 | vals := make(map[string]cty.Value) | ||
30 | for name := range schema.Attributes { | ||
31 | // No normalization for attributes, since them being type-conformant | ||
32 | // is all that we require. | ||
33 | vals[name] = val.GetAttr(name) | ||
34 | } | ||
35 | for name, blockS := range schema.BlockTypes { | ||
36 | lv := val.GetAttr(name) | ||
37 | |||
38 | // Legacy SDK never generates dynamically-typed attributes and so our | ||
39 | // normalization code doesn't deal with them, but we need to make sure | ||
40 | // we still pass them through properly so that we don't interfere with | ||
41 | // objects generated by other SDKs. | ||
42 | if ty := blockS.Block.ImpliedType(); ty.HasDynamicTypes() { | ||
43 | vals[name] = lv | ||
44 | continue | ||
45 | } | ||
46 | |||
47 | switch blockS.Nesting { | ||
48 | case configschema.NestingSingle, configschema.NestingGroup: | ||
49 | if lv.IsKnown() { | ||
50 | if lv.IsNull() && blockS.Nesting == configschema.NestingGroup { | ||
51 | vals[name] = blockS.EmptyValue() | ||
52 | } else { | ||
53 | vals[name] = NormalizeObjectFromLegacySDK(lv, &blockS.Block) | ||
54 | } | ||
55 | } else { | ||
56 | vals[name] = unknownBlockStub(&blockS.Block) | ||
57 | } | ||
58 | case configschema.NestingList: | ||
59 | switch { | ||
60 | case !lv.IsKnown(): | ||
61 | vals[name] = cty.ListVal([]cty.Value{unknownBlockStub(&blockS.Block)}) | ||
62 | case lv.IsNull() || lv.LengthInt() == 0: | ||
63 | vals[name] = cty.ListValEmpty(blockS.Block.ImpliedType()) | ||
64 | default: | ||
65 | subVals := make([]cty.Value, 0, lv.LengthInt()) | ||
66 | for it := lv.ElementIterator(); it.Next(); { | ||
67 | _, subVal := it.Element() | ||
68 | subVals = append(subVals, NormalizeObjectFromLegacySDK(subVal, &blockS.Block)) | ||
69 | } | ||
70 | vals[name] = cty.ListVal(subVals) | ||
71 | } | ||
72 | case configschema.NestingSet: | ||
73 | switch { | ||
74 | case !lv.IsKnown(): | ||
75 | vals[name] = cty.SetVal([]cty.Value{unknownBlockStub(&blockS.Block)}) | ||
76 | case lv.IsNull() || lv.LengthInt() == 0: | ||
77 | vals[name] = cty.SetValEmpty(blockS.Block.ImpliedType()) | ||
78 | default: | ||
79 | subVals := make([]cty.Value, 0, lv.LengthInt()) | ||
80 | for it := lv.ElementIterator(); it.Next(); { | ||
81 | _, subVal := it.Element() | ||
82 | subVals = append(subVals, NormalizeObjectFromLegacySDK(subVal, &blockS.Block)) | ||
83 | } | ||
84 | vals[name] = cty.SetVal(subVals) | ||
85 | } | ||
86 | default: | ||
87 | // The legacy SDK doesn't support NestingMap, so we just assume | ||
88 | // maps are always okay. (If not, we would've detected and returned | ||
89 | // an error to the user before we got here.) | ||
90 | vals[name] = lv | ||
91 | } | ||
92 | } | ||
93 | return cty.ObjectVal(vals) | ||
94 | } | ||
95 | |||
96 | // unknownBlockStub constructs an object value that approximates an unknown | ||
97 | // block by producing a known block object with all of its leaf attribute | ||
98 | // values set to unknown. | ||
99 | // | ||
100 | // Blocks themselves cannot be unknown, so if the legacy SDK tries to return | ||
101 | // such a thing, we'll use this result instead. This convention mimics how | ||
102 | // the dynamic block feature deals with being asked to iterate over an unknown | ||
103 | // value, because our value-checking functions already accept this convention | ||
104 | // as a special case. | ||
105 | func unknownBlockStub(schema *configschema.Block) cty.Value { | ||
106 | vals := make(map[string]cty.Value) | ||
107 | for name, attrS := range schema.Attributes { | ||
108 | vals[name] = cty.UnknownVal(attrS.Type) | ||
109 | } | ||
110 | for name, blockS := range schema.BlockTypes { | ||
111 | switch blockS.Nesting { | ||
112 | case configschema.NestingSingle, configschema.NestingGroup: | ||
113 | vals[name] = unknownBlockStub(&blockS.Block) | ||
114 | case configschema.NestingList: | ||
115 | // In principle we may be expected to produce a tuple value here, | ||
116 | // if there are any dynamically-typed attributes in our nested block, | ||
117 | // but the legacy SDK doesn't support that, so we just assume it'll | ||
118 | // never be necessary to normalize those. (Incorrect usage in any | ||
119 | // other SDK would be caught and returned as an error before we | ||
120 | // get here.) | ||
121 | vals[name] = cty.ListVal([]cty.Value{unknownBlockStub(&blockS.Block)}) | ||
122 | case configschema.NestingSet: | ||
123 | vals[name] = cty.SetVal([]cty.Value{unknownBlockStub(&blockS.Block)}) | ||
124 | case configschema.NestingMap: | ||
125 | // A nesting map can never be unknown since we then wouldn't know | ||
126 | // what the keys are. (Legacy SDK doesn't support NestingMap anyway, | ||
127 | // so this should never arise.) | ||
128 | vals[name] = cty.MapValEmpty(blockS.Block.ImpliedType()) | ||
129 | } | ||
130 | } | ||
131 | return cty.ObjectVal(vals) | ||
132 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/objchange/objchange.go b/vendor/github.com/hashicorp/terraform/plans/objchange/objchange.go new file mode 100644 index 0000000..5a8af14 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/objchange/objchange.go | |||
@@ -0,0 +1,390 @@ | |||
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 | // ProposedNewObject constructs a proposed new object value by combining the | ||
12 | // computed attribute values from "prior" with the configured attribute values | ||
13 | // from "config". | ||
14 | // | ||
15 | // Both value must conform to the given schema's implied type, or this function | ||
16 | // will panic. | ||
17 | // | ||
18 | // The prior value must be wholly known, but the config value may be unknown | ||
19 | // or have nested unknown values. | ||
20 | // | ||
21 | // The merging of the two objects includes the attributes of any nested blocks, | ||
22 | // which will be correlated in a manner appropriate for their nesting mode. | ||
23 | // Note in particular that the correlation for blocks backed by sets is a | ||
24 | // heuristic based on matching non-computed attribute values and so it may | ||
25 | // produce strange results with more "extreme" cases, such as a nested set | ||
26 | // block where _all_ attributes are computed. | ||
27 | func ProposedNewObject(schema *configschema.Block, prior, config cty.Value) cty.Value { | ||
28 | // If the config and prior are both null, return early here before | ||
29 | // populating the prior block. The prevents non-null blocks from appearing | ||
30 | // the proposed state value. | ||
31 | if config.IsNull() && prior.IsNull() { | ||
32 | return prior | ||
33 | } | ||
34 | |||
35 | if prior.IsNull() { | ||
36 | // In this case, we will construct a synthetic prior value that is | ||
37 | // similar to the result of decoding an empty configuration block, | ||
38 | // which simplifies our handling of the top-level attributes/blocks | ||
39 | // below by giving us one non-null level of object to pull values from. | ||
40 | prior = AllAttributesNull(schema) | ||
41 | } | ||
42 | return proposedNewObject(schema, prior, config) | ||
43 | } | ||
44 | |||
45 | // PlannedDataResourceObject is similar to ProposedNewObject but tailored for | ||
46 | // planning data resources in particular. Specifically, it replaces the values | ||
47 | // of any Computed attributes not set in the configuration with an unknown | ||
48 | // value, which serves as a placeholder for a value to be filled in by the | ||
49 | // provider when the data resource is finally read. | ||
50 | // | ||
51 | // Data resources are different because the planning of them is handled | ||
52 | // entirely within Terraform Core and not subject to customization by the | ||
53 | // provider. This function is, in effect, producing an equivalent result to | ||
54 | // passing the ProposedNewObject result into a provider's PlanResourceChange | ||
55 | // function, assuming a fixed implementation of PlanResourceChange that just | ||
56 | // fills in unknown values as needed. | ||
57 | func PlannedDataResourceObject(schema *configschema.Block, config cty.Value) cty.Value { | ||
58 | // Our trick here is to run the ProposedNewObject logic with an | ||
59 | // entirely-unknown prior value. Because of cty's unknown short-circuit | ||
60 | // behavior, any operation on prior returns another unknown, and so | ||
61 | // unknown values propagate into all of the parts of the resulting value | ||
62 | // that would normally be filled in by preserving the prior state. | ||
63 | prior := cty.UnknownVal(schema.ImpliedType()) | ||
64 | return proposedNewObject(schema, prior, config) | ||
65 | } | ||
66 | |||
67 | func proposedNewObject(schema *configschema.Block, prior, config cty.Value) cty.Value { | ||
68 | if config.IsNull() || !config.IsKnown() { | ||
69 | // This is a weird situation, but we'll allow it anyway to free | ||
70 | // callers from needing to specifically check for these cases. | ||
71 | return prior | ||
72 | } | ||
73 | if (!prior.Type().IsObjectType()) || (!config.Type().IsObjectType()) { | ||
74 | panic("ProposedNewObject only supports object-typed values") | ||
75 | } | ||
76 | |||
77 | // From this point onwards, we can assume that both values are non-null | ||
78 | // object types, and that the config value itself is known (though it | ||
79 | // may contain nested values that are unknown.) | ||
80 | |||
81 | newAttrs := map[string]cty.Value{} | ||
82 | for name, attr := range schema.Attributes { | ||
83 | priorV := prior.GetAttr(name) | ||
84 | configV := config.GetAttr(name) | ||
85 | var newV cty.Value | ||
86 | switch { | ||
87 | case attr.Computed && attr.Optional: | ||
88 | // This is the trickiest scenario: we want to keep the prior value | ||
89 | // if the config isn't overriding it. Note that due to some | ||
90 | // ambiguity here, setting an optional+computed attribute from | ||
91 | // config and then later switching the config to null in a | ||
92 | // subsequent change causes the initial config value to be "sticky" | ||
93 | // unless the provider specifically overrides it during its own | ||
94 | // plan customization step. | ||
95 | if configV.IsNull() { | ||
96 | newV = priorV | ||
97 | } else { | ||
98 | newV = configV | ||
99 | } | ||
100 | case attr.Computed: | ||
101 | // configV will always be null in this case, by definition. | ||
102 | // priorV may also be null, but that's okay. | ||
103 | newV = priorV | ||
104 | default: | ||
105 | // For non-computed attributes, we always take the config value, | ||
106 | // even if it is null. If it's _required_ then null values | ||
107 | // should've been caught during an earlier validation step, and | ||
108 | // so we don't really care about that here. | ||
109 | newV = configV | ||
110 | } | ||
111 | newAttrs[name] = newV | ||
112 | } | ||
113 | |||
114 | // Merging nested blocks is a little more complex, since we need to | ||
115 | // correlate blocks between both objects and then recursively propose | ||
116 | // a new object for each. The correlation logic depends on the nesting | ||
117 | // mode for each block type. | ||
118 | for name, blockType := range schema.BlockTypes { | ||
119 | priorV := prior.GetAttr(name) | ||
120 | configV := config.GetAttr(name) | ||
121 | var newV cty.Value | ||
122 | switch blockType.Nesting { | ||
123 | |||
124 | case configschema.NestingSingle, configschema.NestingGroup: | ||
125 | newV = ProposedNewObject(&blockType.Block, priorV, configV) | ||
126 | |||
127 | case configschema.NestingList: | ||
128 | // Nested blocks are correlated by index. | ||
129 | configVLen := 0 | ||
130 | if configV.IsKnown() && !configV.IsNull() { | ||
131 | configVLen = configV.LengthInt() | ||
132 | } | ||
133 | if configVLen > 0 { | ||
134 | newVals := make([]cty.Value, 0, configVLen) | ||
135 | for it := configV.ElementIterator(); it.Next(); { | ||
136 | idx, configEV := it.Element() | ||
137 | if priorV.IsKnown() && (priorV.IsNull() || !priorV.HasIndex(idx).True()) { | ||
138 | // If there is no corresponding prior element then | ||
139 | // we just take the config value as-is. | ||
140 | newVals = append(newVals, configEV) | ||
141 | continue | ||
142 | } | ||
143 | priorEV := priorV.Index(idx) | ||
144 | |||
145 | newEV := ProposedNewObject(&blockType.Block, priorEV, configEV) | ||
146 | newVals = append(newVals, newEV) | ||
147 | } | ||
148 | // Despite the name, a NestingList might also be a tuple, if | ||
149 | // its nested schema contains dynamically-typed attributes. | ||
150 | if configV.Type().IsTupleType() { | ||
151 | newV = cty.TupleVal(newVals) | ||
152 | } else { | ||
153 | newV = cty.ListVal(newVals) | ||
154 | } | ||
155 | } else { | ||
156 | // Despite the name, a NestingList might also be a tuple, if | ||
157 | // its nested schema contains dynamically-typed attributes. | ||
158 | if configV.Type().IsTupleType() { | ||
159 | newV = cty.EmptyTupleVal | ||
160 | } else { | ||
161 | newV = cty.ListValEmpty(blockType.ImpliedType()) | ||
162 | } | ||
163 | } | ||
164 | |||
165 | case configschema.NestingMap: | ||
166 | // Despite the name, a NestingMap may produce either a map or | ||
167 | // object value, depending on whether the nested schema contains | ||
168 | // dynamically-typed attributes. | ||
169 | if configV.Type().IsObjectType() { | ||
170 | // Nested blocks are correlated by key. | ||
171 | configVLen := 0 | ||
172 | if configV.IsKnown() && !configV.IsNull() { | ||
173 | configVLen = configV.LengthInt() | ||
174 | } | ||
175 | if configVLen > 0 { | ||
176 | newVals := make(map[string]cty.Value, configVLen) | ||
177 | atys := configV.Type().AttributeTypes() | ||
178 | for name := range atys { | ||
179 | configEV := configV.GetAttr(name) | ||
180 | if !priorV.IsKnown() || priorV.IsNull() || !priorV.Type().HasAttribute(name) { | ||
181 | // If there is no corresponding prior element then | ||
182 | // we just take the config value as-is. | ||
183 | newVals[name] = configEV | ||
184 | continue | ||
185 | } | ||
186 | priorEV := priorV.GetAttr(name) | ||
187 | |||
188 | newEV := ProposedNewObject(&blockType.Block, priorEV, configEV) | ||
189 | newVals[name] = newEV | ||
190 | } | ||
191 | // Although we call the nesting mode "map", we actually use | ||
192 | // object values so that elements might have different types | ||
193 | // in case of dynamically-typed attributes. | ||
194 | newV = cty.ObjectVal(newVals) | ||
195 | } else { | ||
196 | newV = cty.EmptyObjectVal | ||
197 | } | ||
198 | } else { | ||
199 | configVLen := 0 | ||
200 | if configV.IsKnown() && !configV.IsNull() { | ||
201 | configVLen = configV.LengthInt() | ||
202 | } | ||
203 | if configVLen > 0 { | ||
204 | newVals := make(map[string]cty.Value, configVLen) | ||
205 | for it := configV.ElementIterator(); it.Next(); { | ||
206 | idx, configEV := it.Element() | ||
207 | k := idx.AsString() | ||
208 | if priorV.IsKnown() && (priorV.IsNull() || !priorV.HasIndex(idx).True()) { | ||
209 | // If there is no corresponding prior element then | ||
210 | // we just take the config value as-is. | ||
211 | newVals[k] = configEV | ||
212 | continue | ||
213 | } | ||
214 | priorEV := priorV.Index(idx) | ||
215 | |||
216 | newEV := ProposedNewObject(&blockType.Block, priorEV, configEV) | ||
217 | newVals[k] = newEV | ||
218 | } | ||
219 | newV = cty.MapVal(newVals) | ||
220 | } else { | ||
221 | newV = cty.MapValEmpty(blockType.ImpliedType()) | ||
222 | } | ||
223 | } | ||
224 | |||
225 | case configschema.NestingSet: | ||
226 | if !configV.Type().IsSetType() { | ||
227 | panic("configschema.NestingSet value is not a set as expected") | ||
228 | } | ||
229 | |||
230 | // Nested blocks are correlated by comparing the element values | ||
231 | // after eliminating all of the computed attributes. In practice, | ||
232 | // this means that any config change produces an entirely new | ||
233 | // nested object, and we only propagate prior computed values | ||
234 | // if the non-computed attribute values are identical. | ||
235 | var cmpVals [][2]cty.Value | ||
236 | if priorV.IsKnown() && !priorV.IsNull() { | ||
237 | cmpVals = setElementCompareValues(&blockType.Block, priorV, false) | ||
238 | } | ||
239 | configVLen := 0 | ||
240 | if configV.IsKnown() && !configV.IsNull() { | ||
241 | configVLen = configV.LengthInt() | ||
242 | } | ||
243 | if configVLen > 0 { | ||
244 | used := make([]bool, len(cmpVals)) // track used elements in case multiple have the same compare value | ||
245 | newVals := make([]cty.Value, 0, configVLen) | ||
246 | for it := configV.ElementIterator(); it.Next(); { | ||
247 | _, configEV := it.Element() | ||
248 | var priorEV cty.Value | ||
249 | for i, cmp := range cmpVals { | ||
250 | if used[i] { | ||
251 | continue | ||
252 | } | ||
253 | if cmp[1].RawEquals(configEV) { | ||
254 | priorEV = cmp[0] | ||
255 | used[i] = true // we can't use this value on a future iteration | ||
256 | break | ||
257 | } | ||
258 | } | ||
259 | if priorEV == cty.NilVal { | ||
260 | priorEV = cty.NullVal(blockType.ImpliedType()) | ||
261 | } | ||
262 | |||
263 | newEV := ProposedNewObject(&blockType.Block, priorEV, configEV) | ||
264 | newVals = append(newVals, newEV) | ||
265 | } | ||
266 | newV = cty.SetVal(newVals) | ||
267 | } else { | ||
268 | newV = cty.SetValEmpty(blockType.Block.ImpliedType()) | ||
269 | } | ||
270 | |||
271 | default: | ||
272 | // Should never happen, since the above cases are comprehensive. | ||
273 | panic(fmt.Sprintf("unsupported block nesting mode %s", blockType.Nesting)) | ||
274 | } | ||
275 | |||
276 | newAttrs[name] = newV | ||
277 | } | ||
278 | |||
279 | return cty.ObjectVal(newAttrs) | ||
280 | } | ||
281 | |||
282 | // setElementCompareValues takes a known, non-null value of a cty.Set type and | ||
283 | // returns a table -- constructed of two-element arrays -- that maps original | ||
284 | // set element values to corresponding values that have all of the computed | ||
285 | // values removed, making them suitable for comparison with values obtained | ||
286 | // from configuration. The element type of the set must conform to the implied | ||
287 | // type of the given schema, or this function will panic. | ||
288 | // | ||
289 | // In the resulting slice, the zeroth element of each array is the original | ||
290 | // value and the one-indexed element is the corresponding "compare value". | ||
291 | // | ||
292 | // This is intended to help correlate prior elements with configured elements | ||
293 | // in ProposedNewObject. The result is a heuristic rather than an exact science, | ||
294 | // since e.g. two separate elements may reduce to the same value through this | ||
295 | // process. The caller must therefore be ready to deal with duplicates. | ||
296 | func setElementCompareValues(schema *configschema.Block, set cty.Value, isConfig bool) [][2]cty.Value { | ||
297 | ret := make([][2]cty.Value, 0, set.LengthInt()) | ||
298 | for it := set.ElementIterator(); it.Next(); { | ||
299 | _, ev := it.Element() | ||
300 | ret = append(ret, [2]cty.Value{ev, setElementCompareValue(schema, ev, isConfig)}) | ||
301 | } | ||
302 | return ret | ||
303 | } | ||
304 | |||
305 | // setElementCompareValue creates a new value that has all of the same | ||
306 | // non-computed attribute values as the one given but has all computed | ||
307 | // attribute values forced to null. | ||
308 | // | ||
309 | // If isConfig is true then non-null Optional+Computed attribute values will | ||
310 | // be preserved. Otherwise, they will also be set to null. | ||
311 | // | ||
312 | // The input value must conform to the schema's implied type, and the return | ||
313 | // value is guaranteed to conform to it. | ||
314 | func setElementCompareValue(schema *configschema.Block, v cty.Value, isConfig bool) cty.Value { | ||
315 | if v.IsNull() || !v.IsKnown() { | ||
316 | return v | ||
317 | } | ||
318 | |||
319 | attrs := map[string]cty.Value{} | ||
320 | for name, attr := range schema.Attributes { | ||
321 | switch { | ||
322 | case attr.Computed && attr.Optional: | ||
323 | if isConfig { | ||
324 | attrs[name] = v.GetAttr(name) | ||
325 | } else { | ||
326 | attrs[name] = cty.NullVal(attr.Type) | ||
327 | } | ||
328 | case attr.Computed: | ||
329 | attrs[name] = cty.NullVal(attr.Type) | ||
330 | default: | ||
331 | attrs[name] = v.GetAttr(name) | ||
332 | } | ||
333 | } | ||
334 | |||
335 | for name, blockType := range schema.BlockTypes { | ||
336 | switch blockType.Nesting { | ||
337 | |||
338 | case configschema.NestingSingle, configschema.NestingGroup: | ||
339 | attrs[name] = setElementCompareValue(&blockType.Block, v.GetAttr(name), isConfig) | ||
340 | |||
341 | case configschema.NestingList, configschema.NestingSet: | ||
342 | cv := v.GetAttr(name) | ||
343 | if cv.IsNull() || !cv.IsKnown() { | ||
344 | attrs[name] = cv | ||
345 | continue | ||
346 | } | ||
347 | if l := cv.LengthInt(); l > 0 { | ||
348 | elems := make([]cty.Value, 0, l) | ||
349 | for it := cv.ElementIterator(); it.Next(); { | ||
350 | _, ev := it.Element() | ||
351 | elems = append(elems, setElementCompareValue(&blockType.Block, ev, isConfig)) | ||
352 | } | ||
353 | if blockType.Nesting == configschema.NestingSet { | ||
354 | // SetValEmpty would panic if given elements that are not | ||
355 | // all of the same type, but that's guaranteed not to | ||
356 | // happen here because our input value was _already_ a | ||
357 | // set and we've not changed the types of any elements here. | ||
358 | attrs[name] = cty.SetVal(elems) | ||
359 | } else { | ||
360 | attrs[name] = cty.TupleVal(elems) | ||
361 | } | ||
362 | } else { | ||
363 | if blockType.Nesting == configschema.NestingSet { | ||
364 | attrs[name] = cty.SetValEmpty(blockType.Block.ImpliedType()) | ||
365 | } else { | ||
366 | attrs[name] = cty.EmptyTupleVal | ||
367 | } | ||
368 | } | ||
369 | |||
370 | case configschema.NestingMap: | ||
371 | cv := v.GetAttr(name) | ||
372 | if cv.IsNull() || !cv.IsKnown() { | ||
373 | attrs[name] = cv | ||
374 | continue | ||
375 | } | ||
376 | elems := make(map[string]cty.Value) | ||
377 | for it := cv.ElementIterator(); it.Next(); { | ||
378 | kv, ev := it.Element() | ||
379 | elems[kv.AsString()] = setElementCompareValue(&blockType.Block, ev, isConfig) | ||
380 | } | ||
381 | attrs[name] = cty.ObjectVal(elems) | ||
382 | |||
383 | default: | ||
384 | // Should never happen, since the above cases are comprehensive. | ||
385 | panic(fmt.Sprintf("unsupported block nesting mode %s", blockType.Nesting)) | ||
386 | } | ||
387 | } | ||
388 | |||
389 | return cty.ObjectVal(attrs) | ||
390 | } | ||
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 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plans/plan.go b/vendor/github.com/hashicorp/terraform/plans/plan.go new file mode 100644 index 0000000..5a3e454 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/plan.go | |||
@@ -0,0 +1,92 @@ | |||
1 | package plans | ||
2 | |||
3 | import ( | ||
4 | "sort" | ||
5 | |||
6 | "github.com/hashicorp/terraform/addrs" | ||
7 | "github.com/hashicorp/terraform/configs/configschema" | ||
8 | "github.com/zclconf/go-cty/cty" | ||
9 | ) | ||
10 | |||
11 | // Plan is the top-level type representing a planned set of changes. | ||
12 | // | ||
13 | // A plan is a summary of the set of changes required to move from a current | ||
14 | // state to a goal state derived from configuration. The described changes | ||
15 | // are not applied directly, but contain an approximation of the final | ||
16 | // result that will be completed during apply by resolving any values that | ||
17 | // cannot be predicted. | ||
18 | // | ||
19 | // A plan must always be accompanied by the state and configuration it was | ||
20 | // built from, since the plan does not itself include all of the information | ||
21 | // required to make the changes indicated. | ||
22 | type Plan struct { | ||
23 | VariableValues map[string]DynamicValue | ||
24 | Changes *Changes | ||
25 | TargetAddrs []addrs.Targetable | ||
26 | ProviderSHA256s map[string][]byte | ||
27 | Backend Backend | ||
28 | } | ||
29 | |||
30 | // Backend represents the backend-related configuration and other data as it | ||
31 | // existed when a plan was created. | ||
32 | type Backend struct { | ||
33 | // Type is the type of backend that the plan will apply against. | ||
34 | Type string | ||
35 | |||
36 | // Config is the configuration of the backend, whose schema is decided by | ||
37 | // the backend Type. | ||
38 | Config DynamicValue | ||
39 | |||
40 | // Workspace is the name of the workspace that was active when the plan | ||
41 | // was created. It is illegal to apply a plan created for one workspace | ||
42 | // to the state of another workspace. | ||
43 | // (This constraint is already enforced by the statefile lineage mechanism, | ||
44 | // but storing this explicitly allows us to return a better error message | ||
45 | // in the situation where the user has the wrong workspace selected.) | ||
46 | Workspace string | ||
47 | } | ||
48 | |||
49 | func NewBackend(typeName string, config cty.Value, configSchema *configschema.Block, workspaceName string) (*Backend, error) { | ||
50 | dv, err := NewDynamicValue(config, configSchema.ImpliedType()) | ||
51 | if err != nil { | ||
52 | return nil, err | ||
53 | } | ||
54 | |||
55 | return &Backend{ | ||
56 | Type: typeName, | ||
57 | Config: dv, | ||
58 | Workspace: workspaceName, | ||
59 | }, nil | ||
60 | } | ||
61 | |||
62 | // ProviderAddrs returns a list of all of the provider configuration addresses | ||
63 | // referenced throughout the receiving plan. | ||
64 | // | ||
65 | // The result is de-duplicated so that each distinct address appears only once. | ||
66 | func (p *Plan) ProviderAddrs() []addrs.AbsProviderConfig { | ||
67 | if p == nil || p.Changes == nil { | ||
68 | return nil | ||
69 | } | ||
70 | |||
71 | m := map[string]addrs.AbsProviderConfig{} | ||
72 | for _, rc := range p.Changes.Resources { | ||
73 | m[rc.ProviderAddr.String()] = rc.ProviderAddr | ||
74 | } | ||
75 | if len(m) == 0 { | ||
76 | return nil | ||
77 | } | ||
78 | |||
79 | // This is mainly just so we'll get stable results for testing purposes. | ||
80 | keys := make([]string, 0, len(m)) | ||
81 | for k := range m { | ||
82 | keys = append(keys, k) | ||
83 | } | ||
84 | sort.Strings(keys) | ||
85 | |||
86 | ret := make([]addrs.AbsProviderConfig, len(keys)) | ||
87 | for i, key := range keys { | ||
88 | ret[i] = m[key] | ||
89 | } | ||
90 | |||
91 | return ret | ||
92 | } | ||