]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/eval_read_data.go
update vendor and go.mod
[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
863486a6
AG
98 forEach, _ := evaluateResourceForEachExpression(n.Config.ForEach, ctx)
99 keyData := EvalDataForInstanceKey(n.Addr.Key, forEach)
107c1cdb
ND
100
101 var configDiags tfdiags.Diagnostics
102 configVal, _, configDiags = ctx.EvaluateBlock(config.Config, schema, nil, keyData)
103 diags = diags.Append(configDiags)
104 if configDiags.HasErrors() {
105 return nil, diags.Err()
106 }
107
108 proposedNewVal := objchange.PlannedDataResourceObject(schema, configVal)
109
110 // If our configuration contains any unknown values then we must defer the
111 // read to the apply phase by producing a "Read" change for this resource,
112 // and a placeholder value for it in the state.
113 if n.ForcePlanRead || !configVal.IsWhollyKnown() {
114 // If the configuration is still unknown when we're applying a planned
115 // change then that indicates a bug in Terraform, since we should have
116 // everything resolved by now.
117 if n.Planned != nil && *n.Planned != nil {
118 return nil, fmt.Errorf(
119 "configuration for %s still contains unknown values during apply (this is a bug in Terraform; please report it!)",
120 absAddr,
121 )
122 }
123 if n.ForcePlanRead {
124 log.Printf("[TRACE] EvalReadData: %s configuration is fully known, but we're forcing a read plan to be created", absAddr)
125 } else {
126 log.Printf("[TRACE] EvalReadData: %s configuration not fully known yet, so deferring to apply phase", absAddr)
127 }
128
129 err := ctx.Hook(func(h Hook) (HookAction, error) {
130 return h.PreDiff(absAddr, states.CurrentGen, priorVal, proposedNewVal)
131 })
bae9f6d2
JC
132 if err != nil {
133 return nil, err
134 }
107c1cdb
ND
135
136 change = &plans.ResourceInstanceChange{
137 Addr: absAddr,
138 ProviderAddr: n.ProviderAddr,
139 Change: plans.Change{
140 Action: plans.Read,
141 Before: priorVal,
142 After: proposedNewVal,
143 },
bae9f6d2
JC
144 }
145
107c1cdb
ND
146 err = ctx.Hook(func(h Hook) (HookAction, error) {
147 return h.PostDiff(absAddr, states.CurrentGen, change.Action, priorVal, proposedNewVal)
148 })
149 if err != nil {
150 return nil, err
151 }
152
153 if n.OutputChange != nil {
154 *n.OutputChange = change
155 }
156 if n.OutputValue != nil {
157 *n.OutputValue = change.After
158 }
159 if n.OutputConfigValue != nil {
160 *n.OutputConfigValue = configVal
bae9f6d2 161 }
107c1cdb
ND
162 if n.OutputState != nil {
163 state := &states.ResourceInstanceObject{
164 Value: change.After,
165 Status: states.ObjectPlanned, // because the partial value in the plan must be used for now
166 Dependencies: n.Dependencies,
167 }
168 *n.OutputState = state
169 }
170
171 return nil, diags.ErrWithWarnings()
bae9f6d2
JC
172 }
173
107c1cdb
ND
174 if n.Planned != nil && *n.Planned != nil && (*n.Planned).Action != plans.Read {
175 // If any other action gets in here then that's always a bug; this
176 // EvalNode only deals with reading.
177 return nil, fmt.Errorf(
178 "invalid action %s for %s: only Read is supported (this is a bug in Terraform; please report it!)",
179 (*n.Planned).Action, absAddr,
180 )
181 }
182
863486a6
AG
183 log.Printf("[TRACE] Re-validating config for %s", absAddr)
184 validateResp := provider.ValidateDataSourceConfig(
185 providers.ValidateDataSourceConfigRequest{
186 TypeName: n.Addr.Resource.Type,
187 Config: configVal,
188 },
189 )
190 if validateResp.Diagnostics.HasErrors() {
191 return nil, validateResp.Diagnostics.InConfigBody(n.Config.Config).Err()
192 }
193
107c1cdb
ND
194 // If we get down here then our configuration is complete and we're read
195 // to actually call the provider to read the data.
196 log.Printf("[TRACE] EvalReadData: %s configuration is complete, so reading from provider", absAddr)
197
198 err := ctx.Hook(func(h Hook) (HookAction, error) {
199 // We don't have a state yet, so we'll just give the hook an
200 // empty one to work with.
201 return h.PreRefresh(absAddr, states.CurrentGen, cty.NullVal(cty.DynamicPseudoType))
bae9f6d2
JC
202 })
203 if err != nil {
204 return nil, err
205 }
206
107c1cdb
ND
207 resp := provider.ReadDataSource(providers.ReadDataSourceRequest{
208 TypeName: n.Addr.Resource.Type,
209 Config: configVal,
210 })
211 diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config))
212 if diags.HasErrors() {
213 return nil, diags.Err()
214 }
215 newVal := resp.State
216 if newVal == cty.NilVal {
217 // This can happen with incompletely-configured mocks. We'll allow it
218 // and treat it as an alias for a properly-typed null value.
219 newVal = cty.NullVal(schema.ImpliedType())
220 }
221
222 for _, err := range newVal.Type().TestConformance(schema.ImpliedType()) {
223 diags = diags.Append(tfdiags.Sourceless(
224 tfdiags.Error,
225 "Provider produced invalid object",
226 fmt.Sprintf(
227 "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.",
228 n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()),
229 ),
230 ))
231 }
232 if diags.HasErrors() {
233 return nil, diags.Err()
234 }
235
236 if newVal.IsNull() {
237 diags = diags.Append(tfdiags.Sourceless(
238 tfdiags.Error,
239 "Provider produced null object",
240 fmt.Sprintf(
241 "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.",
242 n.ProviderAddr.ProviderConfig.Type, absAddr,
243 ),
244 ))
245 }
246 if !newVal.IsWhollyKnown() {
247 diags = diags.Append(tfdiags.Sourceless(
248 tfdiags.Error,
249 "Provider produced invalid object",
250 fmt.Sprintf(
251 "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.",
252 n.ProviderAddr.ProviderConfig.Type, absAddr,
253 ),
254 ))
255
256 // We'll still save the object, but we need to eliminate any unknown
257 // values first because we can't serialize them in the state file.
258 // Note that this may cause set elements to be coalesced if they
259 // differed only by having unknown values, but we don't worry about
260 // that here because we're saving the value only for inspection
261 // purposes; the error we added above will halt the graph walk.
262 newVal = cty.UnknownAsNull(newVal)
263 }
264
265 // Since we've completed the read, we actually have no change to make, but
266 // we'll produce a NoOp one anyway to preserve the usual flow of the
267 // plan phase and allow it to produce a complete plan.
268 change = &plans.ResourceInstanceChange{
269 Addr: absAddr,
270 ProviderAddr: n.ProviderAddr,
271 Change: plans.Change{
272 Action: plans.NoOp,
273 Before: newVal,
274 After: newVal,
275 },
276 }
277 state := &states.ResourceInstanceObject{
278 Value: change.After,
279 Status: states.ObjectReady, // because we completed the read from the provider
280 Dependencies: n.Dependencies,
281 }
282
283 err = ctx.Hook(func(h Hook) (HookAction, error) {
284 return h.PostRefresh(absAddr, states.CurrentGen, change.Before, newVal)
285 })
286 if err != nil {
287 return nil, err
288 }
bae9f6d2 289
107c1cdb
ND
290 if n.OutputChange != nil {
291 *n.OutputChange = change
292 }
293 if n.OutputValue != nil {
294 *n.OutputValue = change.After
295 }
296 if n.OutputConfigValue != nil {
297 *n.OutputConfigValue = configVal
298 }
bae9f6d2 299 if n.OutputState != nil {
bae9f6d2 300 *n.OutputState = state
bae9f6d2
JC
301 }
302
107c1cdb 303 return nil, diags.ErrWithWarnings()
bae9f6d2
JC
304}
305
306// EvalReadDataApply is an EvalNode implementation that executes a data
307// resource's ReadDataApply method to read data from the data source.
308type EvalReadDataApply struct {
107c1cdb
ND
309 Addr addrs.ResourceInstance
310 Provider *providers.Interface
311 ProviderAddr addrs.AbsProviderConfig
312 ProviderSchema **ProviderSchema
313 Output **states.ResourceInstanceObject
314 Config *configs.Resource
315 Change **plans.ResourceInstanceChange
316 StateReferences []addrs.Referenceable
bae9f6d2
JC
317}
318
319func (n *EvalReadDataApply) Eval(ctx EvalContext) (interface{}, error) {
bae9f6d2 320 provider := *n.Provider
107c1cdb
ND
321 change := *n.Change
322 providerSchema := *n.ProviderSchema
323 absAddr := n.Addr.Absolute(ctx.Path())
324
325 var diags tfdiags.Diagnostics
bae9f6d2
JC
326
327 // If the diff is for *destroying* this resource then we'll
328 // just drop its state and move on, since data resources don't
329 // support an actual "destroy" action.
107c1cdb 330 if change != nil && change.Action == plans.Delete {
bae9f6d2
JC
331 if n.Output != nil {
332 *n.Output = nil
333 }
334 return nil, nil
335 }
336
337 // For the purpose of external hooks we present a data apply as a
338 // "Refresh" rather than an "Apply" because creating a data source
339 // is presented to users/callers as a "read" operation.
340 err := ctx.Hook(func(h Hook) (HookAction, error) {
341 // We don't have a state yet, so we'll just give the hook an
342 // empty one to work with.
107c1cdb 343 return h.PreRefresh(absAddr, states.CurrentGen, cty.NullVal(cty.DynamicPseudoType))
bae9f6d2
JC
344 })
345 if err != nil {
346 return nil, err
347 }
348
107c1cdb
ND
349 resp := provider.ReadDataSource(providers.ReadDataSourceRequest{
350 TypeName: n.Addr.Resource.Type,
351 Config: change.After,
352 })
353 diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config))
354 if diags.HasErrors() {
355 return nil, diags.Err()
356 }
357
358 schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource())
359 if schema == nil {
360 // Should be caught during validation, so we don't bother with a pretty error here
361 return nil, fmt.Errorf("provider does not support data source %q", n.Addr.Resource.Type)
362 }
363
364 newVal := resp.State
365 for _, err := range newVal.Type().TestConformance(schema.ImpliedType()) {
366 diags = diags.Append(tfdiags.Sourceless(
367 tfdiags.Error,
368 "Provider produced invalid object",
369 fmt.Sprintf(
370 "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.",
371 n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()),
372 ),
373 ))
374 }
375 if diags.HasErrors() {
376 return nil, diags.Err()
bae9f6d2
JC
377 }
378
379 err = ctx.Hook(func(h Hook) (HookAction, error) {
107c1cdb 380 return h.PostRefresh(absAddr, states.CurrentGen, change.Before, newVal)
bae9f6d2
JC
381 })
382 if err != nil {
383 return nil, err
384 }
385
386 if n.Output != nil {
107c1cdb
ND
387 *n.Output = &states.ResourceInstanceObject{
388 Value: newVal,
389 Status: states.ObjectReady,
390 Dependencies: n.StateReferences,
391 }
bae9f6d2
JC
392 }
393
107c1cdb 394 return nil, diags.ErrWithWarnings()
bae9f6d2 395}