mirror of https://github.com/hashicorp/terraform
With validate, plan, and apply now all at least stubbed out we can see that they all share some similar logic for visiting everything that's relevant to an evaluation phase and gathering up diagnostics and other external reports about each object. Since it's important that the phases all visit the same objects so that we can produce consistent results between phases, we'll accept a little inversion-of-control complexity here in return for now having only two "walk-driver" implementations: one for visiting the "static" objects and one for visiting the "dynamic" objects. - The validate phase only visits static objects. - The plan phase visits static objects first, and then dynamic objects only if the static walk doesn't produce any errors. - The apply phase only visits the dynamic objects. This also required some retroactive changes to some of my earlier work on the validation phase since we can no longer assume that the validation walk is the only one which visits the "static" objects. Their methods now take EvalPhase arguments similar to those for the dynamic objects, and we differentiate between the results in each phase so that we can potentially add slight differences between the phases in future if needed. As of this commit, though, the validate phase and the static portion of the plan phase should produce identical results.pull/34738/head
parent
fafa36e73a
commit
f8d2fef129
@ -0,0 +1,110 @@
|
||||
package stackeval
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// DynamicEvaler is implemented by types that participate in dynamic
|
||||
// evaluation phases, which currently includes [PlanPhase] and [ApplyPhase].
|
||||
type DynamicEvaler interface {
|
||||
Plannable
|
||||
ApplyChecker
|
||||
}
|
||||
|
||||
// walkDynamicObjects is a generic helper for visiting all of the "dynamic
|
||||
// objects" in scope for a particular [Main] object. "Dynamic objects"
|
||||
// essentially means the objects that are involved in the plan and apply
|
||||
// operations, which includes instances of objects that can expand using
|
||||
// "count" or "for_each" arguments.
|
||||
//
|
||||
// The walk value stays constant throughout the walk, being passed to
|
||||
// all visited objects. Visits can happen concurrently, so any methods
|
||||
// offered by Output must be concurrency-safe.
|
||||
//
|
||||
// The type parameter Object should be either [Plannable] or [ApplyChecker]
|
||||
// depending on which walk this call is intending to drive. All dynamic
|
||||
// objects must implement both of those interfaces, although for many
|
||||
// object types the logic is equivalent across both.
|
||||
func walkDynamicObjects[Output any](
|
||||
ctx context.Context,
|
||||
walk *walkWithOutput[Output],
|
||||
main *Main,
|
||||
visit func(ctx context.Context, walk *walkWithOutput[Output], obj DynamicEvaler),
|
||||
) {
|
||||
walkDynamicObjectsInStack(ctx, walk, main.MainStack(ctx), visit)
|
||||
}
|
||||
|
||||
func walkDynamicObjectsInStack[Output any](
|
||||
ctx context.Context,
|
||||
walk *walkWithOutput[Output],
|
||||
stack *Stack,
|
||||
visit func(ctx context.Context, walk *walkWithOutput[Output], obj DynamicEvaler),
|
||||
) {
|
||||
// We'll get the expansion of any child stack calls going first, so that
|
||||
// we can explore downstream stacks concurrently with this one. Each
|
||||
// stack call can represent zero or more child stacks that we'll analyze
|
||||
// by recursive calls to this function.
|
||||
for _, call := range stack.EmbeddedStackCalls(ctx) {
|
||||
call := call // separate symbol per loop iteration
|
||||
|
||||
visit(ctx, walk, call)
|
||||
|
||||
// We need to perform the whole expansion in an overall async task
|
||||
// because it involves evaluating for_each expressions, and one
|
||||
// stack call's for_each might depend on the results of another.
|
||||
walk.AsyncTask(ctx, func(ctx context.Context) {
|
||||
insts := call.Instances(ctx, PlanPhase)
|
||||
for _, inst := range insts {
|
||||
visit(ctx, walk, inst)
|
||||
|
||||
childStack := inst.CalledStack(ctx)
|
||||
walkDynamicObjectsInStack(ctx, walk, childStack, visit)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// We also need to visit and check all of the other declarations in
|
||||
// the current stack.
|
||||
|
||||
for _, component := range stack.Components(ctx) {
|
||||
component := component // separate symbol per loop iteration
|
||||
|
||||
visit(ctx, walk, component)
|
||||
|
||||
// We need to perform the instance expansion in an overall async task
|
||||
// because it involves potentially evaluating a for_each expression.
|
||||
// and that might depend on data from elsewhere in the same stack.
|
||||
walk.AsyncTask(ctx, func(ctx context.Context) {
|
||||
insts := component.Instances(ctx, PlanPhase)
|
||||
for _, inst := range insts {
|
||||
visit(ctx, walk, inst)
|
||||
}
|
||||
})
|
||||
}
|
||||
for _, provider := range stack.Providers(ctx) {
|
||||
provider := provider // separate symbol per loop iteration
|
||||
|
||||
visit(ctx, walk, provider)
|
||||
|
||||
// We need to perform the instance expansion in an overall async
|
||||
// task because it involves potentially evaluating a for_each expression,
|
||||
// and that might depend on data from elsewhere in the same stack.
|
||||
walk.AsyncTask(ctx, func(ctx context.Context) {
|
||||
insts := provider.Instances(ctx, PlanPhase)
|
||||
for _, inst := range insts {
|
||||
visit(ctx, walk, inst)
|
||||
}
|
||||
})
|
||||
}
|
||||
for _, variable := range stack.InputVariables(ctx) {
|
||||
visit(ctx, walk, variable)
|
||||
}
|
||||
// TODO: Local values
|
||||
for _, output := range stack.OutputValues(ctx) {
|
||||
visit(ctx, walk, output)
|
||||
}
|
||||
|
||||
// Finally we'll also check the stack itself, to deal with any problems
|
||||
// with the stack as a whole rather than individual declarations inside.
|
||||
visit(ctx, walk, stack)
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
package stackeval
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// StaticEvaler is implemented by types that participate in static
|
||||
// evaluation phases, which currently includes [ValidatePhase] and [PlanPhase].
|
||||
type StaticEvaler interface {
|
||||
Validatable
|
||||
Plannable
|
||||
}
|
||||
|
||||
// walkDynamicObjects is a generic helper for visiting all of the "static
|
||||
// objects" in scope for a particular [Main] object. "Static objects"
|
||||
// essentially means the objects that are involved in the validation
|
||||
// operation, which typically includes objects representing static
|
||||
// configuration elements that haven't yet been expanded into their
|
||||
// dynamic counterparts.
|
||||
//
|
||||
// The walk value stays constant throughout the walk, being passed to
|
||||
// all visited objects. Visits can happen concurrently, so any methods
|
||||
// offered by Output must be concurrency-safe.
|
||||
//
|
||||
// The Object type parameter should either be Validatable or Plannable
|
||||
// depending on which of the two relevant evaluation phases this function
|
||||
// is supposed to be driving.
|
||||
func walkStaticObjects[Output any](
|
||||
ctx context.Context,
|
||||
walk *walkWithOutput[Output],
|
||||
main *Main,
|
||||
visit func(ctx context.Context, walk *walkWithOutput[Output], obj StaticEvaler),
|
||||
) {
|
||||
walkStaticObjectsInStackConfig(ctx, walk, main.MainStackConfig(ctx), visit)
|
||||
}
|
||||
|
||||
func walkStaticObjectsInStackConfig[Output any](
|
||||
ctx context.Context,
|
||||
walk *walkWithOutput[Output],
|
||||
stackConfig *StackConfig,
|
||||
visit func(ctx context.Context, walk *walkWithOutput[Output], obj StaticEvaler),
|
||||
) {
|
||||
for _, obj := range stackConfig.InputVariables(ctx) {
|
||||
visit(ctx, walk, obj)
|
||||
}
|
||||
|
||||
for _, obj := range stackConfig.OutputValues(ctx) {
|
||||
visit(ctx, walk, obj)
|
||||
}
|
||||
|
||||
// TODO: All of the other static object types
|
||||
|
||||
for _, obj := range stackConfig.StackCalls(ctx) {
|
||||
visit(ctx, walk, obj)
|
||||
}
|
||||
|
||||
for _, childCfg := range stackConfig.ChildConfigs(ctx) {
|
||||
walkStaticObjectsInStackConfig(ctx, walk, childCfg, visit)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue