exec

package
v0.22.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2026 License: MPL-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package exec contains the models and main interface used for apply phase execution.

The types and functions in this package are typically used through the sibling package [execgraph] to model the data flow and dependencies between multiple operations, but the individual types, interfaces, and functions are exposed here to make it possible to write tests for individual parts of the apply engine without having to always build an execution graph.

These "vocabulary types" are separated into their own package mainly to minimize the risk of dependency cycles as other components make use of them. This package does not import any other package that is considered to be a component of the apply engine, but it can import packages that other parts of the apply engine are also expected to depend on, such as the packages which model state objects and provider clients.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ManagedResourceObjectFinalPlan

type ManagedResourceObjectFinalPlan struct {
	// Addr describes which resource instance object this plan was created for.
	//
	// This is to be used only for recording the new state of the object
	// after applying this plan, and should be treated opaquely. In particular,
	// nothing from these fields should be sent to a provider as part of
	// applying the plan because how we track resource instance objects between
	// rounds is an implementation detail that providers should not rely on so
	// that we can potentially change it in future while staying compatible
	// with existing provider plugins.
	Addr addrs.AbsResourceInstanceObject

	ProviderInstance addrs.AbsProviderInstanceCorrect

	// ResourceType is the resource type of the object this plan is for, as
	// would be understood by the provider that generated this plan.
	ResourceType string

	// RequiredResourceInstances are the addresses of zero or more resource
	// instances that must exist and must be fully converged before the
	// final plan for this resource instance could be calculated.
	//
	// These addresses can potentially contain unknown instance keys if the
	// configuration for this resource instance was derived from placeholders
	// for upstream resource instances that had unknown keys in their own
	// addresses.
	RequiredResourceInstances addrs.Set[addrs.AbsResourceInstance]

	// ConfigVal is the value representing the configuration for this
	// object, but only if it's a "desired" object. This is always a null
	// value for "orphan" instances and deposed objects, because they have
	// no configuration by definition.
	ConfigVal cty.Value
	// PriorStateVal is the value representing this object in the prior
	// state, or a null value if this object didn't previously exist and
	// is therefore presumably being created.
	PriorStateVal cty.Value
	// PlannedVal is the value returned by the provider when it was asked
	// to produce a plan. This is an approximation of the final result
	// with unknown values as placeholders for anything that won't be known
	// until after the change has been applied.
	PlannedVal cty.Value
	// ProviderPrivate is the raw "private" value that the provider returned
	// in its planning response, which must be sent back to the provider
	// verbatim when applying the plan.
	ProviderPrivate []byte

	// ProvisionersBefore and ProvisionersAfter are provisioners to run
	// before or after applying the plan, respectively.
	//
	// If ProvisionersBefore fail and are not configured to continue on failure
	// then the changes are not applied at all.
	//
	// ProvisionersAfter run only if the changes are applied successfully, and
	// then if they fail the object is left in a "tainted" state so that the
	// next plan/apply round knows that the object is not yet ready to use.
	ProvisionersBefore, ProvisionersAfter []*eval.ResourceProvisioner
}

ManagedResourceObjectFinalPlan represents a final plan -- ready to actually be applied -- for some managed resource instance object that could be any of a current object for a desired resource instance, a current object for an orphan resource instance, or a deposed object for any resource instance.

Note that for execution graph purposes a "replace" action is always represented as two separate "final plans", where the "delete" leg is represented by the configuration being null and the "create" leg is represented by the prior state being null. This struct type intentionally does not carry any information about the identity of the object the plan is for because that is implied by the relationships in the graph and there should be no assumptions about e.g. there being exactly one final plan per resource instance, etc.

func (*ManagedResourceObjectFinalPlan) IntoDeposed

IntoDeposed returns a new ManagedResourceObjectFinalPlan that represents the same change as the receiver but has DeposedKey set the given value.

Note that the result is only a shallow copy of the reciever, so nothing reachable through pointers should be modified in either object. Final plan objects are immutable by convention.

This function does not (and cannot) verify that the chosen deposed key is unique for the resource instance. It's the caller's responsibility to allocate a unique deposed key to use.

type Operations

type Operations interface {

	// ResourceInstanceCurrentMeta returns the metadata for the given resource
	// instance's "current" (non-deposed) object address, providing information
	// that is relevant regardless of what action is being taken for the object
	// or whether it is "desired" or not.
	//
	// For deposed object metadata, use [Operations.ManagedDeposedMeta] instead.
	ResourceInstanceCurrentMeta(
		ctx context.Context,
		instAddr addrs.AbsResourceInstance,
		prior *ResourceInstanceObject,
	) (*ResourceInstanceObjectMeta, tfdiags.Diagnostics)

	// ResourceInstanceDesired returns a representation of the "desired state"
	// for the resource instance object whose metadata is provided, or a nil
	// pointer if the given resource instance is not currently declared at all.
	//
	// Deposed objects cannot be "desired", so only metadata for current objects
	// may be passed to this operation.
	//
	// Real implementations of this use the configuration evaluator to finalize
	// the resource instance configuration based on other values that have been
	// previously resolved. A valid execution graph ensures that this method is
	// not called until all of the required upstream values are available.
	//
	// This operation is the only one that should return any diagnostics the
	// evaluator returns when producing the [DesiredResourceInstance] object,
	// which should include evaluating any preconditions declared for that
	// resource instance.
	ResourceInstanceDesired(
		ctx context.Context,
		meta *ResourceInstanceObjectMeta,
	) (*eval.DesiredResourceInstance, tfdiags.Diagnostics)

	// ResourceInstancePrior returns a representation of the "prior state" for
	// the given resource instance, or a nil pointer if there was no current
	// object bound to that resource instance address in the prior state.
	//
	// Real implementations of this use the prior state snapshot that was saved
	// as part of the plan that is being applied. That snapshot should already
	// conform to the current version of the schema for its resource type in
	// the associated provider due to having potentially been "upgraded" during
	// the planning phase.
	ResourceInstancePrior(
		ctx context.Context,
		instAddr addrs.AbsResourceInstance,
	) (*ResourceInstanceObject, tfdiags.Diagnostics)

	// ResourceInstancePostconditions tests whether the given object passes
	// any postconditions that were declared for it.
	//
	// This is not a real execution graph operation. Instead, execution graph
	// processing automatically makes calls to this as part of handling the
	// results from [Operations.ManagedApply], [Operations.ReadData], and
	// [Operations.OpenEphemeral] to ensure that postconditions always get
	// handled consistently for all resource modes.
	ResourceInstancePostconditions(
		ctx context.Context,
		result *ResourceInstanceObject,
	) tfdiags.Diagnostics

	// ManagedFinalPlan uses the given provider client to create the final
	// plan for a change to a managed resource instance object, and then
	// verifies that its result value conforms to what the provider promised
	// during planning, which is given as "plannedVal".
	//
	// "desired" is nil if the expected operation is to delete the object
	// described in "prior". Conversely, "prior" is nil if the expected
	// operation is to create a new object matching "desired". At least one
	// of those arguments is always non-nil, and them both being set represents
	// planning an in-place update to the object.
	//
	// This method must always either return a valid, non-nil final plan object
	// or must return at least one error diagnostic.
	ManagedFinalPlan(
		ctx context.Context,
		metadata *ResourceInstanceObjectMeta,
		desired *eval.DesiredResourceInstance,
		prior *ResourceInstanceObject,
		plannedVal cty.Value,
	) (*ManagedResourceObjectFinalPlan, tfdiags.Diagnostics)

	// ManagedApply uses the given provider client to apply the given plan.
	//
	// This operation MUST fully encapsulate all of the externally-visible
	// changes needed to apply a change such that when it returns -- whether
	// successfully or unsuccessfully -- the state has been left in a form that
	// accurately models whatever shape the remote system was left in, including
	// possibly saving a partially-created object returned by a provider so that
	// a future round can plan to attempt to repair it based on updated
	// configuration.
	//
	// If applying the plan fails in a way that causes there to be no new object
	// state to save, and if the "fallback" argument has a non-nil value, then
	// the fallback object (which is always a deposed object) should be
	// reinterpreted as the new current object for the resource instance. This
	// occurs when performing a "create then destroy" replace operation, so
	// that a total failure of the "create" step leaves OpenTofu still tracking
	// the previous object (which was presumably deposed earlier in the same
	// apply phase using ManagedPerformDepose) as the current object.
	//
	// This method must return whatever object was left as "current" in the
	// state, including possibly returning the "current-ized" version of
	// the fallback object when appropriate, or nil if this was a destroy
	// operation that succeeded. When used with the real apply engine the
	// result is propagated back into the configuration evaluator so that
	// downstream resource and provider configurations can make use of the
	// results in their own final plans.
	//
	// Execution graph processing automatically passes the result of this
	// function to [Operations.ResourceInstancePostconditions] when appropriate,
	// propagating any additional diagnostics it returns, and so implementers of
	// this method should not attempt to handle postconditions themselves.
	ManagedApply(
		ctx context.Context,
		plan *ManagedResourceObjectFinalPlan,
		fallback *ResourceInstanceObject,
	) (*ResourceInstanceObject, tfdiags.Diagnostics)

	// ManagedPerformDepose takes a "current" object for some resource instance
	// and changes it to be a "deposed" object for the same resource instance,
	// returning a new representation of the object with its
	// pseudorandomly-chosen unique DeposedKey.
	//
	// When using this as part of a "create then destroy" replace operation,
	// a correct execution graph arranges for the result to be propagated into
	// the "fallback" argument of a subsequent [Operations.ManagedApply] call,
	// so that the deposed object can be restored back to current if the
	// apply operation fails to the extent that no new object is created at all.
	//
	// The given object must not already have "DeposedKey" set, because that
	// would make it a deposed object instead of a current object.
	// If the given object is nil then this returns nil without changing
	// anything. In practice though the planning engine should not include
	// this operation unless it found an existing current object that needs to
	// be deposed as part of a create-then-destroy "replace" change.
	ManagedPerformDepose(
		ctx context.Context,
		object *ResourceInstanceObject,
		deletePlan *ManagedResourceObjectFinalPlan,
	) (*ResourceInstanceObject, tfdiags.Diagnostics)

	// ManagedDeposedMeta returns the metadata for a deposed object belonging
	// to the given resource instance, providing information that might be
	// needed in order to delete the object.
	//
	// For current object metadata, use [Operations.ResourceInstanceCurrentMeta]
	// instead.
	ManagedDeposedMeta(
		ctx context.Context,
		instAddr addrs.AbsResourceInstance,
		deposedKey states.DeposedKey,
		prior *ResourceInstanceObject,
	) (*ResourceInstanceObjectMeta, tfdiags.Diagnostics)

	// ManagedAlreadyDeposed returns a deposed object from the prior state,
	// nor nil if there is no such object.
	//
	// This deals with the relatively-uncommon situation where there was already
	// a deposed object present in the state at the beginning of the planning
	// phase, and that object did not get removed as a result of refreshing it.
	// That occurs only when a previous plan/apply round encountered an error
	// partway through a "create then destroy" replace operation where both
	// the newly-created object and the previously-existing object still exist.
	// In that case, this operation serves a similar purpose to
	// [Operations.ResourceInstancePrior] but returns a deposed object rather
	// than a current object.
	//
	// [Operations.ManagedPerformDepose] deals with the more common case where a
	// previously-"current" object becomes deposed during the apply phase as
	// part of handling a "create then destroy' replace operation.
	ManagedAlreadyDeposed(
		ctx context.Context,
		instAddr addrs.AbsResourceInstance,
		deposedKey states.DeposedKey,
	) (*ResourceInstanceObject, tfdiags.Diagnostics)

	// ManageChangeAddr rebinds the given object to be associated with
	// newInstAddr instead, and then returns a new representation of that object
	// with its updated address.
	//
	// This is used between [Operations.ResourceInstancePrior] and
	// [Operations.ManagedFinalPlan] whenever an existing resource instance
	// object is being moved to a new address using "moved" blocks. The move
	// is modelled as a separate action because it's okay for the final state
	// to reflect the address change even if subsequent plan/apply actions
	// fail.
	//
	// If the incoming object is nil then this also returns nil without making
	// any change and no errors. In practice though the planning engine should
	// not include this operation unless it found an existing object that needed
	// to be moved.
	//
	// This is for use with "current" resource instance objects only, so
	// implementers can assume that the given object will have no DeposedKey.
	ManagedChangeAddr(
		ctx context.Context,
		object *ResourceInstanceObject,
		newAddr addrs.AbsResourceInstance,
	) (*ResourceInstanceObject, tfdiags.Diagnostics)

	// DataRead uses the given provider client to read the latest value for a
	// desired data resource instance.
	//
	// This method always returns a non-nil object unless it returns at least
	// one error diagnostic explaining why it cannot. The result should also
	// be saved to the updated state before this method returns.
	//
	// This operation is used only when it isn't possible to read the data
	// resource value during the planning phase. If the desired resource
	// instance was already known enough to read it during the plan phase then
	// the prior state would already record its result and an so a call
	// to [Operations.ResourceInstancePrior] is sufficient to obtain the value.
	//
	// Execution graph processing automatically passes the result of this
	// function to [Operations.ResourceInstancePostconditions] when appropriate,
	// propagating any additional diagnostics it returns, and so implementers of
	// this method should not attempt to handle postconditions themselves.
	DataRead(
		ctx context.Context,
		desired *eval.DesiredResourceInstance,
		plannedVal cty.Value,
	) (*ResourceInstanceObject, tfdiags.Diagnostics)
}

Operations represents the full set of operations that can be used in an execution graph, and so implementations of this interface are used to actually perform those operations.

This interface essentially acts as "glue" between the execution graph and the broader environment where it's being used: the library of available provider plugins, the configuration evaluator, etc. The methods in this interface directly correspond to the supported execution graph opcodes except where stated otherwise in the comments associated with each method.

The design intention is that an implementer of this interface would have access to information that comes from outside of the graph and would be able to record any state updates that occur but the implementation SHOULD NOT need to retain information provided to one method call for direct use by another later method call to the same object. Any temporary data should be propagated between calls by the caller of this interface, which is typically the execution graph processor. If you're implementing a new feature that requires additional data to be propagated then you should arrange for it to propagate through method arguments and return values rather than keeping sidecar data inside your Operations implementation.

If you're writing a unit test then try to test at a tighter granularity than using this entire interface. Writing a mock implementation of this entire interface should be a last resort because that'd add additional burden to maintaining this interface over time as our needs for apply-time execution grow and change.

type ResourceInstanceObject

type ResourceInstanceObject struct {
	Addr addrs.AbsResourceInstanceObject

	// State is the object currently associated with the given address.
	State *states.ResourceInstanceObjectFull
}

ResourceInstanceObject associates a states.ResourceInstanceObjectFull with a resource instance address and optional deposed key.

Objects of this type should be treated as immutable. Use the methods of this type to derive new objects when modelling changes.

This is intended to model the idea that an object can move between different tracking addresses without being modified: an instance of this type represents the object existing at a particular address, with the intention that a caller would create a new object of this type whenever an object moves between addresses but should not need to change the underlying object itself.

If an operation _does_ cause an object to move to a new tracking address then it should be designed to take an object of this type as an argument representing the starting location and then to return a newly-constructed separate object of this type representing the new location, so that the change of address is modelled in the data flow between operations rather than as global mutable state.

func (*ResourceInstanceObject) IntoCurrent

IntoCurrent returns a new ResourceInstanceObject that has the same State as the receiver but has DeposedKey set to states.NotDeposed.

func (*ResourceInstanceObject) IntoDeposed

IntoDeposed returns a new ResourceInstanceObject that has the same State as the receiver but has DeposedKey set the given value.

This function does not (and cannot) verify that the chosen deposed key is unique for the resource instance. It's the caller's responsibility to allocate a unique deposed key to use.

func (*ResourceInstanceObject) WithNewAddr

WithNewAddr returns a new ResourceInstanceObject that has the same State as the receiver but has InstanceAddr set to the given address.

func (*ResourceInstanceObject) WithNewState

IntoCurrent returns a new ResourceInstanceObject that has the same address information as the receiver but has State set to the given object.

If the given state object is nil then the result is also nil, to represent the absense of an object. ResourceInstanceObject instances should only represent objects that actually exist.

type ResourceInstanceObjectMeta

type ResourceInstanceObjectMeta struct {
	// Addr identifies which resource instance object this metadata applies to.
	//
	// When an existing object will be moved to a new address during the
	// apply phase, for example using a "moved" block, this reflects the address
	// it's expected to have at the end of a successful apply phase. The address
	// in this field must therefore NOT be used to identify objects to retrieve
	// from the prior state.
	//
	// However, for objects being replaced in the create-then-destroy order note
	// that successful execution causes the original object to be deleted and a
	// new one to be created at the same address, and in that case we use the
	// address where the new object would be placed instead of the address
	// that the old object would be temporarily deposed to during the process.
	// This reflects a small inconsistency/ambiguity in our usual terminology
	// where "object" normally refers to the actual remote object, but in this
	// case it refers only to the _object address_ from OpenTofu's perspective,
	// and two different remote objects will appear at this address over
	// the course of the apply phase.
	Addr addrs.AbsResourceInstanceObject

	// ProviderInstance is the address of the provider instance that is
	// currently considered responsible for this resource instance object.
	//
	// A resource instance object is associated with a specific provider
	// instance throughout a plan/apply round, but may change which provider
	// instance it is associated with between rounds based on changes in the
	// configuration.
	ProviderInstance exprs.FromValue[addrs.AbsProviderInstanceCorrect]

	// ResourceType is the resource type of the object this metadata is for,
	// as would be understood by the provider identified in
	// [ResourceInstanceObjectMeta.ProviderInstance].
	ResourceType string

	// PostCreateProvisioners are the provisioners to execute immediately after
	// the resource instance object has been created.
	//
	// The contents of this field can only be relied on during a round where
	// the apply phase would create a resource instance object at the associated
	// address. Its contents are unspecified in other cases.
	PostCreateProvisioners []*eval.ResourceProvisioner

	// PreDeleteProvisioners are the provisioners to execute immediately before
	// the resource instance object would be deleted.
	//
	// The contents of this field can only be relied on during a round where
	// the apply phase would delete a resource instance object at the associated
	// address. Its contents are unspecified in other cases.
	PreDeleteProvisioners []*eval.ResourceProvisioner
}

ResourceInstanceObjectMeta represents various metadata for a resource instance object.

"Metadata" is loosely defined as including the sort of information we rely on even when an object is no longer "desired" and thus we'd plan to delete it, and so there's no normal declaration for the resource instance left in the configuration anymore but nonetheless there might be other information assembled from any combination of the following:

  • Metadata that was copied from configuration into prior state in the previous round.
  • Arguments in a "removed" block that's acting as a sort of "tombstone" for a previously-present resource block.
  • The resource block that an "orphan" resource instance was previously declared from, which remains in the configuration and possibly declares some settings that apply to all instances of the resource.

This type lives at the execution engine layer because it's an abstraction over a mixture of information from the configuration and information from the prior state, whereas the "evaluator"'s scope is strictly limited only to the configuration.

func BuildResourceInstanceObjectMeta

BuildResourceInstanceObjectMeta constructs a ResourceInstanceObjectMeta object that incorporates information from both the configuration and the prior state, generally preferring to use the configuration information when possible but using the prior state as a fallback.

This is our primary logic for deciding the effective metadata for a resource instance object based on all of the information currently known. The planning and applying engines should both use this function to ensure that they always agree about the metadata for a given resource instance object.

It's the caller's responsibility to ensure that all of the arguments agree about which resource instance object they are describing. The given object address will be the value of ResourceInstanceObjectMeta.Addr and so must be consistent with the documentation of that field.

At least one of fromConfig and state must be non-nil, or this function will panic. There is no reason to ask for metadata for an object that exists in neither the desired nor the prior state.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL