]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/eval_read_data.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / eval_read_data.go
CommitLineData
bae9f6d2
JC
1package terraform
2
3import (
4 "fmt"
107c1cdb
ND
5 "log"
6
7 "github.com/zclconf/go-cty/cty"
8
9 "github.com/hashicorp/terraform/addrs"
10 "github.com/hashicorp/terraform/configs"
11 "github.com/hashicorp/terraform/plans"
12 "github.com/hashicorp/terraform/plans/objchange"
13 "github.com/hashicorp/terraform/providers"
14 "github.com/hashicorp/terraform/states"
15 "github.com/hashicorp/terraform/tfdiags"
bae9f6d2
JC
16)
17
107c1cdb
ND
18// EvalReadData is an EvalNode implementation that deals with the main part
19// of the data resource lifecycle: either actually reading from the data source
20// or generating a plan to do so.
21type EvalReadData struct {
22 Addr addrs.ResourceInstance
23 Config *configs.Resource
24 Dependencies []addrs.Referenceable
25 Provider *providers.Interface
26 ProviderAddr addrs.AbsProviderConfig
27 ProviderSchema **ProviderSchema
28
29 // Planned is set when dealing with data resources that were deferred to
30 // the apply walk, to let us see what was planned. If this is set, the
31 // evaluation of the config is required to produce a wholly-known
32 // configuration which is consistent with the partial object included
33 // in this planned change.
34 Planned **plans.ResourceInstanceChange
35
36 // ForcePlanRead, if true, overrides the usual behavior of immediately
37 // reading from the data source where possible, instead forcing us to
38 // _always_ generate a plan. This is used during the plan walk, since we
39 // mustn't actually apply anything there. (The resulting state doesn't
40 // get persisted)
41 ForcePlanRead bool
42
43 // The result from this EvalNode has a few different possibilities
44 // depending on the input:
45 // - If Planned is nil then we assume we're aiming to _produce_ the plan,
46 // and so the following two outcomes are possible:
47 // - OutputChange.Action is plans.NoOp and OutputState is the complete
48 // result of reading from the data source. This is the easy path.
49 // - OutputChange.Action is plans.Read and OutputState is a planned
50 // object placeholder (states.ObjectPlanned). In this case, the
51 // returned change must be recorded in the overral changeset and
52 // eventually passed to another instance of this struct during the
53 // apply walk.
54 // - If Planned is non-nil then we assume we're aiming to complete a
55 // planned read from an earlier plan walk. In this case the only possible
56 // non-error outcome is to set Output.Action (if non-nil) to a plans.NoOp
57 // change and put the complete resulting state in OutputState, ready to
58 // be saved in the overall state and used for expression evaluation.
59 OutputChange **plans.ResourceInstanceChange
60 OutputValue *cty.Value
61 OutputConfigValue *cty.Value
62 OutputState **states.ResourceInstanceObject
bae9f6d2
JC
63}
64
107c1cdb
ND
65func (n *EvalReadData) Eval(ctx EvalContext) (interface{}, error) {
66 absAddr := n.Addr.Absolute(ctx.Path())
67 log.Printf("[TRACE] EvalReadData: working on %s", absAddr)
bae9f6d2 68
107c1cdb
ND
69 if n.ProviderSchema == nil || *n.ProviderSchema == nil {
70 return nil, fmt.Errorf("provider schema not available for %s", n.Addr)
bae9f6d2
JC
71 }
72
107c1cdb
ND
73 var diags tfdiags.Diagnostics
74 var change *plans.ResourceInstanceChange
75 var configVal cty.Value
bae9f6d2 76
107c1cdb
ND
77 // TODO: Do we need to handle Delete changes here? EvalReadDataDiff and
78 // EvalReadDataApply did, but it seems like we should handle that via a
79 // separate mechanism since it boils down to just deleting the object from
80 // the state... and we do that on every plan anyway, forcing the data
81 // resource to re-read.
bae9f6d2 82
107c1cdb
ND
83 config := *n.Config
84 provider := *n.Provider
85 providerSchema := *n.ProviderSchema
86 schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource())
87 if schema == nil {
88 // Should be caught during validation, so we don't bother with a pretty error here
89 return nil, fmt.Errorf("provider %q does not support data source %q", n.ProviderAddr.ProviderConfig.Type, n.Addr.Resource.Type)
90 }
91
92 // We'll always start by evaluating the configuration. What we do after
93 // that will depend on the evaluation result along with what other inputs
94 // we were given.
95 objTy := schema.ImpliedType()
96 priorVal := cty.NullVal(objTy) // for data resources, prior is always null because we start fresh every time
97
98 keyData := EvalDataForInstanceKey(n.Addr.Key)
99
100 var configDiags tfdiags.Diagnostics
101 configVal, _, configDiags = ctx.EvaluateBlock(config.Config, schema, nil, keyData)
102 diags = diags.Append(configDiags)
103 if configDiags.HasErrors() {
104 return nil, diags.Err()
105 }
106
107 proposedNewVal := objchange.PlannedDataResourceObject(schema, configVal)
108
109 // If our configuration contains any unknown values then we must defer the
110 // read to the apply phase by producing a "Read" change for this resource,
111 // and a placeholder value for it in the state.
112 if n.ForcePlanRead || !configVal.IsWhollyKnown() {
113 // If the configuration is still unknown when we're applying a planned
114 // change then that indicates a bug in Terraform, since we should have
115 // everything resolved by now.
116 if n.Planned != nil && *n.Planned != nil {
117 return nil, fmt.Errorf(
118 "configuration for %s still contains unknown values during apply (this is a bug in Terraform; please report it!)",
119 absAddr,
120 )
121 }
122 if n.ForcePlanRead {
123 log.Printf("[TRACE] EvalReadData: %s configuration is fully known, but we're forcing a read plan to be created", absAddr)
124 } else {
125 log.Printf("[TRACE] EvalReadData: %s configuration not fully known yet, so deferring to apply phase", absAddr)
126 }
127
128 err := ctx.Hook(func(h Hook) (HookAction, error) {
129 return h.PreDiff(absAddr, states.CurrentGen, priorVal, proposedNewVal)
130 })
bae9f6d2
JC
131 if err != nil {
132 return nil, err
133 }
107c1cdb
ND
134
135 change = &plans.ResourceInstanceChange{
136 Addr: absAddr,
137 ProviderAddr: n.ProviderAddr,
138 Change: plans.Change{
139 Action: plans.Read,
140 Before: priorVal,
141 After: proposedNewVal,
142 },
bae9f6d2
JC
143 }
144
107c1cdb
ND
145 err = ctx.Hook(func(h Hook) (HookAction, error) {
146 return h.PostDiff(absAddr, states.CurrentGen, change.Action, priorVal, proposedNewVal)
147 })
148 if err != nil {
149 return nil, err
150 }
151
152 if n.OutputChange != nil {
153 *n.OutputChange = change
154 }
155 if n.OutputValue != nil {
156 *n.OutputValue = change.After
157 }
158 if n.OutputConfigValue != nil {
159 *n.OutputConfigValue = configVal
bae9f6d2 160 }
107c1cdb
ND
161 if n.OutputState != nil {
162 state := &states.ResourceInstanceObject{
163 Value: change.After,
164 Status: states.ObjectPlanned, // because the partial value in the plan must be used for now
165 Dependencies: n.Dependencies,
166 }
167 *n.OutputState = state
168 }
169
170 return nil, diags.ErrWithWarnings()
bae9f6d2
JC
171 }
172
107c1cdb
ND
173 if n.Planned != nil && *n.Planned != nil && (*n.Planned).Action != plans.Read {
174 // If any other action gets in here then that's always a bug; this
175 // EvalNode only deals with reading.
176 return nil, fmt.Errorf(
177 "invalid action %s for %s: only Read is supported (this is a bug in Terraform; please report it!)",
178 (*n.Planned).Action, absAddr,
179 )
180 }
181
182 // If we get down here then our configuration is complete and we're read
183 // to actually call the provider to read the data.
184 log.Printf("[TRACE] EvalReadData: %s configuration is complete, so reading from provider", absAddr)
185
186 err := ctx.Hook(func(h Hook) (HookAction, error) {
187 // We don't have a state yet, so we'll just give the hook an
188 // empty one to work with.
189 return h.PreRefresh(absAddr, states.CurrentGen, cty.NullVal(cty.DynamicPseudoType))
bae9f6d2
JC
190 })
191 if err != nil {
192 return nil, err
193 }
194
107c1cdb
ND
195 resp := provider.ReadDataSource(providers.ReadDataSourceRequest{
196 TypeName: n.Addr.Resource.Type,
197 Config: configVal,
198 })
199 diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config))
200 if diags.HasErrors() {
201 return nil, diags.Err()
202 }
203 newVal := resp.State
204 if newVal == cty.NilVal {
205 // This can happen with incompletely-configured mocks. We'll allow it
206 // and treat it as an alias for a properly-typed null value.
207 newVal = cty.NullVal(schema.ImpliedType())
208 }
209
210 for _, err := range newVal.Type().TestConformance(schema.ImpliedType()) {
211 diags = diags.Append(tfdiags.Sourceless(
212 tfdiags.Error,
213 "Provider produced invalid object",
214 fmt.Sprintf(
215 "Provider %q produced an invalid value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
216 n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()),
217 ),
218 ))
219 }
220 if diags.HasErrors() {
221 return nil, diags.Err()
222 }
223
224 if newVal.IsNull() {
225 diags = diags.Append(tfdiags.Sourceless(
226 tfdiags.Error,
227 "Provider produced null object",
228 fmt.Sprintf(
229 "Provider %q produced a null value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
230 n.ProviderAddr.ProviderConfig.Type, absAddr,
231 ),
232 ))
233 }
234 if !newVal.IsWhollyKnown() {
235 diags = diags.Append(tfdiags.Sourceless(
236 tfdiags.Error,
237 "Provider produced invalid object",
238 fmt.Sprintf(
239 "Provider %q produced a value for %s that is not wholly known.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
240 n.ProviderAddr.ProviderConfig.Type, absAddr,
241 ),
242 ))
243
244 // We'll still save the object, but we need to eliminate any unknown
245 // values first because we can't serialize them in the state file.
246 // Note that this may cause set elements to be coalesced if they
247 // differed only by having unknown values, but we don't worry about
248 // that here because we're saving the value only for inspection
249 // purposes; the error we added above will halt the graph walk.
250 newVal = cty.UnknownAsNull(newVal)
251 }
252
253 // Since we've completed the read, we actually have no change to make, but
254 // we'll produce a NoOp one anyway to preserve the usual flow of the
255 // plan phase and allow it to produce a complete plan.
256 change = &plans.ResourceInstanceChange{
257 Addr: absAddr,
258 ProviderAddr: n.ProviderAddr,
259 Change: plans.Change{
260 Action: plans.NoOp,
261 Before: newVal,
262 After: newVal,
263 },
264 }
265 state := &states.ResourceInstanceObject{
266 Value: change.After,
267 Status: states.ObjectReady, // because we completed the read from the provider
268 Dependencies: n.Dependencies,
269 }
270
271 err = ctx.Hook(func(h Hook) (HookAction, error) {
272 return h.PostRefresh(absAddr, states.CurrentGen, change.Before, newVal)
273 })
274 if err != nil {
275 return nil, err
276 }
bae9f6d2 277
107c1cdb
ND
278 if n.OutputChange != nil {
279 *n.OutputChange = change
280 }
281 if n.OutputValue != nil {
282 *n.OutputValue = change.After
283 }
284 if n.OutputConfigValue != nil {
285 *n.OutputConfigValue = configVal
286 }
bae9f6d2 287 if n.OutputState != nil {
bae9f6d2 288 *n.OutputState = state
bae9f6d2
JC
289 }
290
107c1cdb 291 return nil, diags.ErrWithWarnings()
bae9f6d2
JC
292}
293
294// EvalReadDataApply is an EvalNode implementation that executes a data
295// resource's ReadDataApply method to read data from the data source.
296type EvalReadDataApply struct {
107c1cdb
ND
297 Addr addrs.ResourceInstance
298 Provider *providers.Interface
299 ProviderAddr addrs.AbsProviderConfig
300 ProviderSchema **ProviderSchema
301 Output **states.ResourceInstanceObject
302 Config *configs.Resource
303 Change **plans.ResourceInstanceChange
304 StateReferences []addrs.Referenceable
bae9f6d2
JC
305}
306
307func (n *EvalReadDataApply) Eval(ctx EvalContext) (interface{}, error) {
bae9f6d2 308 provider := *n.Provider
107c1cdb
ND
309 change := *n.Change
310 providerSchema := *n.ProviderSchema
311 absAddr := n.Addr.Absolute(ctx.Path())
312
313 var diags tfdiags.Diagnostics
bae9f6d2
JC
314
315 // If the diff is for *destroying* this resource then we'll
316 // just drop its state and move on, since data resources don't
317 // support an actual "destroy" action.
107c1cdb 318 if change != nil && change.Action == plans.Delete {
bae9f6d2
JC
319 if n.Output != nil {
320 *n.Output = nil
321 }
322 return nil, nil
323 }
324
325 // For the purpose of external hooks we present a data apply as a
326 // "Refresh" rather than an "Apply" because creating a data source
327 // is presented to users/callers as a "read" operation.
328 err := ctx.Hook(func(h Hook) (HookAction, error) {
329 // We don't have a state yet, so we'll just give the hook an
330 // empty one to work with.
107c1cdb 331 return h.PreRefresh(absAddr, states.CurrentGen, cty.NullVal(cty.DynamicPseudoType))
bae9f6d2
JC
332 })
333 if err != nil {
334 return nil, err
335 }
336
107c1cdb
ND
337 resp := provider.ReadDataSource(providers.ReadDataSourceRequest{
338 TypeName: n.Addr.Resource.Type,
339 Config: change.After,
340 })
341 diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config))
342 if diags.HasErrors() {
343 return nil, diags.Err()
344 }
345
346 schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource())
347 if schema == nil {
348 // Should be caught during validation, so we don't bother with a pretty error here
349 return nil, fmt.Errorf("provider does not support data source %q", n.Addr.Resource.Type)
350 }
351
352 newVal := resp.State
353 for _, err := range newVal.Type().TestConformance(schema.ImpliedType()) {
354 diags = diags.Append(tfdiags.Sourceless(
355 tfdiags.Error,
356 "Provider produced invalid object",
357 fmt.Sprintf(
358 "Provider %q planned an invalid value for %s. The result could not be saved.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
359 n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()),
360 ),
361 ))
362 }
363 if diags.HasErrors() {
364 return nil, diags.Err()
bae9f6d2
JC
365 }
366
367 err = ctx.Hook(func(h Hook) (HookAction, error) {
107c1cdb 368 return h.PostRefresh(absAddr, states.CurrentGen, change.Before, newVal)
bae9f6d2
JC
369 })
370 if err != nil {
371 return nil, err
372 }
373
374 if n.Output != nil {
107c1cdb
ND
375 *n.Output = &states.ResourceInstanceObject{
376 Value: newVal,
377 Status: states.ObjectReady,
378 Dependencies: n.StateReferences,
379 }
bae9f6d2
JC
380 }
381
107c1cdb 382 return nil, diags.ErrWithWarnings()
bae9f6d2 383}