1package terraform
2
3import (
4	"fmt"
5	"log"
6	"strings"
7
8	"github.com/hashicorp/go-multierror"
9	"github.com/hashicorp/hcl2/hcl"
10	"github.com/zclconf/go-cty/cty"
11
12	"github.com/hashicorp/terraform/addrs"
13	"github.com/hashicorp/terraform/configs"
14	"github.com/hashicorp/terraform/plans"
15	"github.com/hashicorp/terraform/plans/objchange"
16	"github.com/hashicorp/terraform/providers"
17	"github.com/hashicorp/terraform/provisioners"
18	"github.com/hashicorp/terraform/states"
19	"github.com/hashicorp/terraform/tfdiags"
20)
21
22// EvalApply is an EvalNode implementation that writes the diff to
23// the full diff.
24type EvalApply struct {
25	Addr           addrs.ResourceInstance
26	Config         *configs.Resource
27	Dependencies   []addrs.Referenceable
28	State          **states.ResourceInstanceObject
29	Change         **plans.ResourceInstanceChange
30	ProviderAddr   addrs.AbsProviderConfig
31	Provider       *providers.Interface
32	ProviderSchema **ProviderSchema
33	Output         **states.ResourceInstanceObject
34	CreateNew      *bool
35	Error          *error
36}
37
38// TODO: test
39func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
40	var diags tfdiags.Diagnostics
41
42	change := *n.Change
43	provider := *n.Provider
44	state := *n.State
45	absAddr := n.Addr.Absolute(ctx.Path())
46
47	if state == nil {
48		state = &states.ResourceInstanceObject{}
49	}
50
51	schema, _ := (*n.ProviderSchema).SchemaForResourceType(n.Addr.Resource.Mode, n.Addr.Resource.Type)
52	if schema == nil {
53		// Should be caught during validation, so we don't bother with a pretty error here
54		return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type)
55	}
56
57	if n.CreateNew != nil {
58		*n.CreateNew = (change.Action == plans.Create || change.Action.IsReplace())
59	}
60
61	configVal := cty.NullVal(cty.DynamicPseudoType)
62	if n.Config != nil {
63		var configDiags tfdiags.Diagnostics
64		keyData := EvalDataForInstanceKey(n.Addr.Key)
65		configVal, _, configDiags = ctx.EvaluateBlock(n.Config.Config, schema, nil, keyData)
66		diags = diags.Append(configDiags)
67		if configDiags.HasErrors() {
68			return nil, diags.Err()
69		}
70	}
71
72	log.Printf("[DEBUG] %s: applying the planned %s change", n.Addr.Absolute(ctx.Path()), change.Action)
73	resp := provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{
74		TypeName:       n.Addr.Resource.Type,
75		PriorState:     change.Before,
76		Config:         configVal,
77		PlannedState:   change.After,
78		PlannedPrivate: change.Private,
79	})
80	applyDiags := resp.Diagnostics
81	if n.Config != nil {
82		applyDiags = applyDiags.InConfigBody(n.Config.Config)
83	}
84	diags = diags.Append(applyDiags)
85
86	// Even if there are errors in the returned diagnostics, the provider may
87	// have returned a _partial_ state for an object that already exists but
88	// failed to fully configure, and so the remaining code must always run
89	// to completion but must be defensive against the new value being
90	// incomplete.
91	newVal := resp.NewState
92
93	if newVal == cty.NilVal {
94		// Providers are supposed to return a partial new value even when errors
95		// occur, but sometimes they don't and so in that case we'll patch that up
96		// by just using the prior state, so we'll at least keep track of the
97		// object for the user to retry.
98		newVal = change.Before
99
100		// As a special case, we'll set the new value to null if it looks like
101		// we were trying to execute a delete, because the provider in this case
102		// probably left the newVal unset intending it to be interpreted as "null".
103		if change.After.IsNull() {
104			newVal = cty.NullVal(schema.ImpliedType())
105		}
106
107		// Ideally we'd produce an error or warning here if newVal is nil and
108		// there are no errors in diags, because that indicates a buggy
109		// provider not properly reporting its result, but unfortunately many
110		// of our historical test mocks behave in this way and so producing
111		// a diagnostic here fails hundreds of tests. Instead, we must just
112		// silently retain the old value for now. Returning a nil value with
113		// no errors is still always considered a bug in the provider though,
114		// and should be fixed for any "real" providers that do it.
115	}
116
117	var conformDiags tfdiags.Diagnostics
118	for _, err := range newVal.Type().TestConformance(schema.ImpliedType()) {
119		conformDiags = conformDiags.Append(tfdiags.Sourceless(
120			tfdiags.Error,
121			"Provider produced invalid object",
122			fmt.Sprintf(
123				"Provider %q produced an invalid value after apply for %s. The result cannot not be saved in the Terraform state.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
124				n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()),
125			),
126		))
127	}
128	diags = diags.Append(conformDiags)
129	if conformDiags.HasErrors() {
130		// Bail early in this particular case, because an object that doesn't
131		// conform to the schema can't be saved in the state anyway -- the
132		// serializer will reject it.
133		return nil, diags.Err()
134	}
135
136	// After this point we have a type-conforming result object and so we
137	// must always run to completion to ensure it can be saved. If n.Error
138	// is set then we must not return a non-nil error, in order to allow
139	// evaluation to continue to a later point where our state object will
140	// be saved.
141
142	// By this point there must not be any unknown values remaining in our
143	// object, because we've applied the change and we can't save unknowns
144	// in our persistent state. If any are present then we will indicate an
145	// error (which is always a bug in the provider) but we will also replace
146	// them with nulls so that we can successfully save the portions of the
147	// returned value that are known.
148	if !newVal.IsWhollyKnown() {
149		// To generate better error messages, we'll go for a walk through the
150		// value and make a separate diagnostic for each unknown value we
151		// find.
152		cty.Walk(newVal, func(path cty.Path, val cty.Value) (bool, error) {
153			if !val.IsKnown() {
154				pathStr := tfdiags.FormatCtyPath(path)
155				diags = diags.Append(tfdiags.Sourceless(
156					tfdiags.Error,
157					"Provider returned invalid result object after apply",
158					fmt.Sprintf(
159						"After the apply operation, the provider still indicated an unknown value for %s%s. All values must be known after apply, so this is always a bug in the provider and should be reported in the provider's own repository. Terraform will still save the other known object values in the state.",
160						n.Addr.Absolute(ctx.Path()), pathStr,
161					),
162				))
163			}
164			return true, nil
165		})
166
167		// NOTE: This operation can potentially be lossy if there are multiple
168		// elements in a set that differ only by unknown values: after
169		// replacing with null these will be merged together into a single set
170		// element. Since we can only get here in the presence of a provider
171		// bug, we accept this because storing a result here is always a
172		// best-effort sort of thing.
173		newVal = cty.UnknownAsNull(newVal)
174	}
175
176	if change.Action != plans.Delete && !diags.HasErrors() {
177		// Only values that were marked as unknown in the planned value are allowed
178		// to change during the apply operation. (We do this after the unknown-ness
179		// check above so that we also catch anything that became unknown after
180		// being known during plan.)
181		//
182		// If we are returning other errors anyway then we'll give this
183		// a pass since the other errors are usually the explanation for
184		// this one and so it's more helpful to let the user focus on the
185		// root cause rather than distract with this extra problem.
186		if errs := objchange.AssertObjectCompatible(schema, change.After, newVal); len(errs) > 0 {
187			if resp.LegacyTypeSystem {
188				// The shimming of the old type system in the legacy SDK is not precise
189				// enough to pass this consistency check, so we'll give it a pass here,
190				// but we will generate a warning about it so that we are more likely
191				// to notice in the logs if an inconsistency beyond the type system
192				// leads to a downstream provider failure.
193				var buf strings.Builder
194				fmt.Fprintf(&buf, "[WARN] Provider %q produced an unexpected new value for %s, but we are tolerating it because it is using the legacy plugin SDK.\n    The following problems may be the cause of any confusing errors from downstream operations:", n.ProviderAddr.ProviderConfig.Type, absAddr)
195				for _, err := range errs {
196					fmt.Fprintf(&buf, "\n      - %s", tfdiags.FormatError(err))
197				}
198				log.Print(buf.String())
199
200				// The sort of inconsistency we won't catch here is if a known value
201				// in the plan is changed during apply. That can cause downstream
202				// problems because a dependent resource would make its own plan based
203				// on the planned value, and thus get a different result during the
204				// apply phase. This will usually lead to a "Provider produced invalid plan"
205				// error that incorrectly blames the downstream resource for the change.
206
207			} else {
208				for _, err := range errs {
209					diags = diags.Append(tfdiags.Sourceless(
210						tfdiags.Error,
211						"Provider produced inconsistent result after apply",
212						fmt.Sprintf(
213							"When applying changes to %s, provider %q produced an unexpected new value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
214							absAddr, n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatError(err),
215						),
216					))
217				}
218			}
219		}
220	}
221
222	// If a provider returns a null or non-null object at the wrong time then
223	// we still want to save that but it often causes some confusing behaviors
224	// where it seems like Terraform is failing to take any action at all,
225	// so we'll generate some errors to draw attention to it.
226	if !diags.HasErrors() {
227		if change.Action == plans.Delete && !newVal.IsNull() {
228			diags = diags.Append(tfdiags.Sourceless(
229				tfdiags.Error,
230				"Provider returned invalid result object after apply",
231				fmt.Sprintf(
232					"After applying a %s plan, the provider returned a non-null object for %s. Destroying should always produce a null value, so this is always a bug in the provider and should be reported in the provider's own repository. Terraform will still save this errant object in the state for debugging and recovery.",
233					change.Action, n.Addr.Absolute(ctx.Path()),
234				),
235			))
236		}
237		if change.Action != plans.Delete && newVal.IsNull() {
238			diags = diags.Append(tfdiags.Sourceless(
239				tfdiags.Error,
240				"Provider returned invalid result object after apply",
241				fmt.Sprintf(
242					"After applying a %s plan, the provider returned a null object for %s. Only destroying should always produce a null value, so this is always a bug in the provider and should be reported in the provider's own repository.",
243					change.Action, n.Addr.Absolute(ctx.Path()),
244				),
245			))
246		}
247	}
248
249	// Sometimes providers return a null value when an operation fails for some
250	// reason, but we'd rather keep the prior state so that the error can be
251	// corrected on a subsequent run. We must only do this for null new value
252	// though, or else we may discard partial updates the provider was able to
253	// complete.
254	if diags.HasErrors() && newVal.IsNull() {
255		// Otherwise, we'll continue but using the prior state as the new value,
256		// making this effectively a no-op. If the item really _has_ been
257		// deleted then our next refresh will detect that and fix it up.
258		// If change.Action is Create then change.Before will also be null,
259		// which is fine.
260		newVal = change.Before
261	}
262
263	var newState *states.ResourceInstanceObject
264	if !newVal.IsNull() { // null value indicates that the object is deleted, so we won't set a new state in that case
265		newState = &states.ResourceInstanceObject{
266			Status:       states.ObjectReady,
267			Value:        newVal,
268			Private:      resp.Private,
269			Dependencies: n.Dependencies, // Should be populated by the caller from the StateDependencies method on the resource instance node
270		}
271	}
272
273	// Write the final state
274	if n.Output != nil {
275		*n.Output = newState
276	}
277
278	if diags.HasErrors() {
279		// If the caller provided an error pointer then they are expected to
280		// handle the error some other way and we treat our own result as
281		// success.
282		if n.Error != nil {
283			err := diags.Err()
284			*n.Error = err
285			log.Printf("[DEBUG] %s: apply errored, but we're indicating that via the Error pointer rather than returning it: %s", n.Addr.Absolute(ctx.Path()), err)
286			return nil, nil
287		}
288	}
289
290	return nil, diags.ErrWithWarnings()
291}
292
293// EvalApplyPre is an EvalNode implementation that does the pre-Apply work
294type EvalApplyPre struct {
295	Addr   addrs.ResourceInstance
296	Gen    states.Generation
297	State  **states.ResourceInstanceObject
298	Change **plans.ResourceInstanceChange
299}
300
301// TODO: test
302func (n *EvalApplyPre) Eval(ctx EvalContext) (interface{}, error) {
303	change := *n.Change
304	absAddr := n.Addr.Absolute(ctx.Path())
305
306	if change == nil {
307		panic(fmt.Sprintf("EvalApplyPre for %s called with nil Change", absAddr))
308	}
309
310	if resourceHasUserVisibleApply(n.Addr) {
311		priorState := change.Before
312		plannedNewState := change.After
313
314		err := ctx.Hook(func(h Hook) (HookAction, error) {
315			return h.PreApply(absAddr, n.Gen, change.Action, priorState, plannedNewState)
316		})
317		if err != nil {
318			return nil, err
319		}
320	}
321
322	return nil, nil
323}
324
325// EvalApplyPost is an EvalNode implementation that does the post-Apply work
326type EvalApplyPost struct {
327	Addr  addrs.ResourceInstance
328	Gen   states.Generation
329	State **states.ResourceInstanceObject
330	Error *error
331}
332
333// TODO: test
334func (n *EvalApplyPost) Eval(ctx EvalContext) (interface{}, error) {
335	state := *n.State
336
337	if resourceHasUserVisibleApply(n.Addr) {
338		absAddr := n.Addr.Absolute(ctx.Path())
339		var newState cty.Value
340		if state != nil {
341			newState = state.Value
342		} else {
343			newState = cty.NullVal(cty.DynamicPseudoType)
344		}
345		var err error
346		if n.Error != nil {
347			err = *n.Error
348		}
349
350		hookErr := ctx.Hook(func(h Hook) (HookAction, error) {
351			return h.PostApply(absAddr, n.Gen, newState, err)
352		})
353		if hookErr != nil {
354			return nil, hookErr
355		}
356	}
357
358	return nil, *n.Error
359}
360
361// EvalMaybeTainted is an EvalNode that takes the planned change, new value,
362// and possible error from an apply operation and produces a new instance
363// object marked as tainted if it appears that a create operation has failed.
364//
365// This EvalNode never returns an error, to ensure that a subsequent EvalNode
366// can still record the possibly-tainted object in the state.
367type EvalMaybeTainted struct {
368	Addr   addrs.ResourceInstance
369	Gen    states.Generation
370	Change **plans.ResourceInstanceChange
371	State  **states.ResourceInstanceObject
372	Error  *error
373
374	// If StateOutput is not nil, its referent will be assigned either the same
375	// pointer as State or a new object with its status set as Tainted,
376	// depending on whether an error is given and if this was a create action.
377	StateOutput **states.ResourceInstanceObject
378}
379
380// TODO: test
381func (n *EvalMaybeTainted) Eval(ctx EvalContext) (interface{}, error) {
382	state := *n.State
383	change := *n.Change
384	err := *n.Error
385
386	if state != nil && state.Status == states.ObjectTainted {
387		log.Printf("[TRACE] EvalMaybeTainted: %s was already tainted, so nothing to do", n.Addr.Absolute(ctx.Path()))
388		return nil, nil
389	}
390
391	if n.StateOutput != nil {
392		if err != nil && change.Action == plans.Create {
393			// If there are errors during a _create_ then the object is
394			// in an undefined state, and so we'll mark it as tainted so
395			// we can try again on the next run.
396			//
397			// We don't do this for other change actions because errors
398			// during updates will often not change the remote object at all.
399			// If there _were_ changes prior to the error, it's the provider's
400			// responsibility to record the effect of those changes in the
401			// object value it returned.
402			log.Printf("[TRACE] EvalMaybeTainted: %s encountered an error during creation, so it is now marked as tainted", n.Addr.Absolute(ctx.Path()))
403			*n.StateOutput = state.AsTainted()
404		} else {
405			*n.StateOutput = state
406		}
407	}
408
409	return nil, nil
410}
411
412// resourceHasUserVisibleApply returns true if the given resource is one where
413// apply actions should be exposed to the user.
414//
415// Certain resources do apply actions only as an implementation detail, so
416// these should not be advertised to code outside of this package.
417func resourceHasUserVisibleApply(addr addrs.ResourceInstance) bool {
418	// Only managed resources have user-visible apply actions.
419	// In particular, this excludes data resources since we "apply" these
420	// only as an implementation detail of removing them from state when
421	// they are destroyed. (When reading, they don't get here at all because
422	// we present them as "Refresh" actions.)
423	return addr.ContainingResource().Mode == addrs.ManagedResourceMode
424}
425
426// EvalApplyProvisioners is an EvalNode implementation that executes
427// the provisioners for a resource.
428//
429// TODO(mitchellh): This should probably be split up into a more fine-grained
430// ApplyProvisioner (single) that is looped over.
431type EvalApplyProvisioners struct {
432	Addr           addrs.ResourceInstance
433	State          **states.ResourceInstanceObject
434	ResourceConfig *configs.Resource
435	CreateNew      *bool
436	Error          *error
437
438	// When is the type of provisioner to run at this point
439	When configs.ProvisionerWhen
440}
441
442// TODO: test
443func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
444	absAddr := n.Addr.Absolute(ctx.Path())
445	state := *n.State
446	if state == nil {
447		log.Printf("[TRACE] EvalApplyProvisioners: %s has no state, so skipping provisioners", n.Addr)
448		return nil, nil
449	}
450	if n.When == configs.ProvisionerWhenCreate && n.CreateNew != nil && !*n.CreateNew {
451		// If we're not creating a new resource, then don't run provisioners
452		log.Printf("[TRACE] EvalApplyProvisioners: %s is not freshly-created, so no provisioning is required", n.Addr)
453		return nil, nil
454	}
455	if state.Status == states.ObjectTainted {
456		// No point in provisioning an object that is already tainted, since
457		// it's going to get recreated on the next apply anyway.
458		log.Printf("[TRACE] EvalApplyProvisioners: %s is tainted, so skipping provisioning", n.Addr)
459		return nil, nil
460	}
461
462	provs := n.filterProvisioners()
463	if len(provs) == 0 {
464		// We have no provisioners, so don't do anything
465		return nil, nil
466	}
467
468	if n.Error != nil && *n.Error != nil {
469		// We're already tainted, so just return out
470		return nil, nil
471	}
472
473	{
474		// Call pre hook
475		err := ctx.Hook(func(h Hook) (HookAction, error) {
476			return h.PreProvisionInstance(absAddr, state.Value)
477		})
478		if err != nil {
479			return nil, err
480		}
481	}
482
483	// If there are no errors, then we append it to our output error
484	// if we have one, otherwise we just output it.
485	err := n.apply(ctx, provs)
486	if err != nil {
487		*n.Error = multierror.Append(*n.Error, err)
488		if n.Error == nil {
489			return nil, err
490		} else {
491			log.Printf("[TRACE] EvalApplyProvisioners: %s provisioning failed, but we will continue anyway at the caller's request", absAddr)
492			return nil, nil
493		}
494	}
495
496	{
497		// Call post hook
498		err := ctx.Hook(func(h Hook) (HookAction, error) {
499			return h.PostProvisionInstance(absAddr, state.Value)
500		})
501		if err != nil {
502			return nil, err
503		}
504	}
505
506	return nil, nil
507}
508
509// filterProvisioners filters the provisioners on the resource to only
510// the provisioners specified by the "when" option.
511func (n *EvalApplyProvisioners) filterProvisioners() []*configs.Provisioner {
512	// Fast path the zero case
513	if n.ResourceConfig == nil || n.ResourceConfig.Managed == nil {
514		return nil
515	}
516
517	if len(n.ResourceConfig.Managed.Provisioners) == 0 {
518		return nil
519	}
520
521	result := make([]*configs.Provisioner, 0, len(n.ResourceConfig.Managed.Provisioners))
522	for _, p := range n.ResourceConfig.Managed.Provisioners {
523		if p.When == n.When {
524			result = append(result, p)
525		}
526	}
527
528	return result
529}
530
531func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*configs.Provisioner) error {
532	var diags tfdiags.Diagnostics
533	instanceAddr := n.Addr
534	absAddr := instanceAddr.Absolute(ctx.Path())
535
536	// If there's a connection block defined directly inside the resource block
537	// then it'll serve as a base connection configuration for all of the
538	// provisioners.
539	var baseConn hcl.Body
540	if n.ResourceConfig.Managed != nil && n.ResourceConfig.Managed.Connection != nil {
541		baseConn = n.ResourceConfig.Managed.Connection.Config
542	}
543
544	for _, prov := range provs {
545		log.Printf("[TRACE] EvalApplyProvisioners: provisioning %s with %q", absAddr, prov.Type)
546
547		// Get the provisioner
548		provisioner := ctx.Provisioner(prov.Type)
549		schema := ctx.ProvisionerSchema(prov.Type)
550
551		keyData := EvalDataForInstanceKey(instanceAddr.Key)
552
553		// Evaluate the main provisioner configuration.
554		config, _, configDiags := ctx.EvaluateBlock(prov.Config, schema, instanceAddr, keyData)
555		diags = diags.Append(configDiags)
556
557		// If the provisioner block contains a connection block of its own then
558		// it can override the base connection configuration, if any.
559		var localConn hcl.Body
560		if prov.Connection != nil {
561			localConn = prov.Connection.Config
562		}
563
564		var connBody hcl.Body
565		switch {
566		case baseConn != nil && localConn != nil:
567			// Our standard merging logic applies here, similar to what we do
568			// with _override.tf configuration files: arguments from the
569			// base connection block will be masked by any arguments of the
570			// same name in the local connection block.
571			connBody = configs.MergeBodies(baseConn, localConn)
572		case baseConn != nil:
573			connBody = baseConn
574		case localConn != nil:
575			connBody = localConn
576		}
577
578		// start with an empty connInfo
579		connInfo := cty.NullVal(connectionBlockSupersetSchema.ImpliedType())
580
581		if connBody != nil {
582			var connInfoDiags tfdiags.Diagnostics
583			connInfo, _, connInfoDiags = ctx.EvaluateBlock(connBody, connectionBlockSupersetSchema, instanceAddr, keyData)
584			diags = diags.Append(connInfoDiags)
585			if diags.HasErrors() {
586				// "on failure continue" setting only applies to failures of the
587				// provisioner itself, not to invalid configuration.
588				return diags.Err()
589			}
590		}
591
592		{
593			// Call pre hook
594			err := ctx.Hook(func(h Hook) (HookAction, error) {
595				return h.PreProvisionInstanceStep(absAddr, prov.Type)
596			})
597			if err != nil {
598				return err
599			}
600		}
601
602		// The output function
603		outputFn := func(msg string) {
604			ctx.Hook(func(h Hook) (HookAction, error) {
605				h.ProvisionOutput(absAddr, prov.Type, msg)
606				return HookActionContinue, nil
607			})
608		}
609
610		output := CallbackUIOutput{OutputFn: outputFn}
611		resp := provisioner.ProvisionResource(provisioners.ProvisionResourceRequest{
612			Config:     config,
613			Connection: connInfo,
614			UIOutput:   &output,
615		})
616		applyDiags := resp.Diagnostics.InConfigBody(prov.Config)
617
618		// Call post hook
619		hookErr := ctx.Hook(func(h Hook) (HookAction, error) {
620			return h.PostProvisionInstanceStep(absAddr, prov.Type, applyDiags.Err())
621		})
622
623		switch prov.OnFailure {
624		case configs.ProvisionerOnFailureContinue:
625			if applyDiags.HasErrors() {
626				log.Printf("[WARN] Errors while provisioning %s with %q, but continuing as requested in configuration", n.Addr, prov.Type)
627			} else {
628				// Maybe there are warnings that we still want to see
629				diags = diags.Append(applyDiags)
630			}
631		default:
632			diags = diags.Append(applyDiags)
633			if applyDiags.HasErrors() {
634				log.Printf("[WARN] Errors while provisioning %s with %q, so aborting", n.Addr, prov.Type)
635				return diags.Err()
636			}
637		}
638
639		// Deal with the hook
640		if hookErr != nil {
641			return hookErr
642		}
643	}
644
645	return diags.ErrWithWarnings()
646}
647