mirror of
https://github.com/opentofu/opentofu.git
synced 2026-02-18 18:17:54 -05:00
planning: Sketch of even more managed resource instance actions
Some checks are pending
build / Build for freebsd_386 (push) Waiting to run
build / Build for linux_386 (push) Waiting to run
build / Build for openbsd_386 (push) Waiting to run
build / Build for windows_386 (push) Waiting to run
build / Build for freebsd_amd64 (push) Waiting to run
build / Build for linux_amd64 (push) Waiting to run
build / Build for openbsd_amd64 (push) Waiting to run
build / Build for solaris_amd64 (push) Waiting to run
build / Build for windows_amd64 (push) Waiting to run
build / Build for freebsd_arm (push) Waiting to run
build / Build for linux_arm (push) Waiting to run
build / Build for linux_arm64 (push) Waiting to run
build / Build for darwin_amd64 (push) Waiting to run
build / Build for darwin_arm64 (push) Waiting to run
build / End-to-end Tests for linux_386 (push) Waiting to run
build / End-to-end Tests for windows_386 (push) Waiting to run
build / End-to-end Tests for darwin_amd64 (push) Waiting to run
build / End-to-end Tests for linux_amd64 (push) Waiting to run
build / End-to-end Tests for windows_amd64 (push) Waiting to run
Quick Checks / List files changed for pull request (push) Waiting to run
Quick Checks / Unit tests for linux_386 (push) Blocked by required conditions
Quick Checks / Unit tests for linux_amd64 (push) Blocked by required conditions
Quick Checks / Unit tests for windows_amd64 (push) Blocked by required conditions
Quick Checks / Unit tests for linux_arm (push) Blocked by required conditions
Quick Checks / Unit tests for darwin_arm64 (push) Blocked by required conditions
Quick Checks / Unit tests for linux_arm64 (push) Blocked by required conditions
Quick Checks / Race Tests (push) Blocked by required conditions
Quick Checks / End-to-end Tests (push) Blocked by required conditions
Quick Checks / Code Consistency Checks (push) Blocked by required conditions
Quick Checks / License Checks (push) Waiting to run
Website checks / List files changed for pull request (push) Waiting to run
Website checks / Build (push) Blocked by required conditions
Website checks / Test Installation Instructions (push) Blocked by required conditions
Some checks are pending
build / Build for freebsd_386 (push) Waiting to run
build / Build for linux_386 (push) Waiting to run
build / Build for openbsd_386 (push) Waiting to run
build / Build for windows_386 (push) Waiting to run
build / Build for freebsd_amd64 (push) Waiting to run
build / Build for linux_amd64 (push) Waiting to run
build / Build for openbsd_amd64 (push) Waiting to run
build / Build for solaris_amd64 (push) Waiting to run
build / Build for windows_amd64 (push) Waiting to run
build / Build for freebsd_arm (push) Waiting to run
build / Build for linux_arm (push) Waiting to run
build / Build for linux_arm64 (push) Waiting to run
build / Build for darwin_amd64 (push) Waiting to run
build / Build for darwin_arm64 (push) Waiting to run
build / End-to-end Tests for linux_386 (push) Waiting to run
build / End-to-end Tests for windows_386 (push) Waiting to run
build / End-to-end Tests for darwin_amd64 (push) Waiting to run
build / End-to-end Tests for linux_amd64 (push) Waiting to run
build / End-to-end Tests for windows_amd64 (push) Waiting to run
Quick Checks / List files changed for pull request (push) Waiting to run
Quick Checks / Unit tests for linux_386 (push) Blocked by required conditions
Quick Checks / Unit tests for linux_amd64 (push) Blocked by required conditions
Quick Checks / Unit tests for windows_amd64 (push) Blocked by required conditions
Quick Checks / Unit tests for linux_arm (push) Blocked by required conditions
Quick Checks / Unit tests for darwin_arm64 (push) Blocked by required conditions
Quick Checks / Unit tests for linux_arm64 (push) Blocked by required conditions
Quick Checks / Race Tests (push) Blocked by required conditions
Quick Checks / End-to-end Tests (push) Blocked by required conditions
Quick Checks / Code Consistency Checks (push) Blocked by required conditions
Quick Checks / License Checks (push) Waiting to run
Website checks / List files changed for pull request (push) Waiting to run
Website checks / Build (push) Blocked by required conditions
Website checks / Test Installation Instructions (push) Blocked by required conditions
This provides at least a partial implementation of every resource instance action except the ones involving "forget" actions. However, we don't really quite have all of the building blocks needed to properly model "delete" yet, because properly handling those actions means we need to generate "backwards" dependency edges to preserve the guarantee that destroying happens in reverse order to creating. Therefore the main outcome of this commit is to add a bunch of FIXME and TODO comments explaining where the known gaps are, with the intention of then filling those gaps in later commits once we devise a suitable strategy to handle them. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This commit is contained in:
parent
ac9a26d5b9
commit
1e0a920a34
3 changed files with 208 additions and 2 deletions
|
|
@ -117,6 +117,10 @@ func (b *execGraphBuilder) managedResourceInstanceSubgraphDelete(
|
|||
) execgraph.ResourceInstanceResultRef {
|
||||
_, priorStateRef := b.managedResourceInstanceChangeAddrAndPriorStateRefs(plannedChange)
|
||||
plannedValRef := b.lower.ConstantValue(plannedChange.After)
|
||||
// FIXME: The ManagedApply operation in what we generate here should depend
|
||||
// on any other destroy operations we plan for resource instances that
|
||||
// depend on this one, so that we can preserve the guarantee that when
|
||||
// B depends on A we'll always destroy B before we destroy A.
|
||||
return b.managedResourceInstanceSubgraphPlanAndApply(
|
||||
execgraph.NilResultRef[*eval.DesiredResourceInstance](),
|
||||
priorStateRef,
|
||||
|
|
@ -222,6 +226,14 @@ func (b *execGraphBuilder) managedResourceInstanceSubgraphDeleteOrForgetThenCrea
|
|||
destroyPlanRef,
|
||||
execgraph.NilResultRef[*exec.ResourceInstanceObject](),
|
||||
providerClientRef,
|
||||
// FIXME: This should also depend on the destroy of any dependents that
|
||||
// are also being destroyed in this execution graph, to ensure the
|
||||
// expected "inside out" destroy order, but we're not currently keeping
|
||||
// track of destroy results anywhere and even if we were we would not
|
||||
// actually learn about them until after this function had returned,
|
||||
// so we need to introduce a way to add new dependencies here while
|
||||
// planning subsequent resource instances, and to make sure we're not
|
||||
// creating a dependency cycle each time we do so.
|
||||
b.lower.Waiter(createPlanRef), // wait for successful planning of the create step
|
||||
)
|
||||
createResultRef := b.lower.ManagedApply(
|
||||
|
|
@ -239,8 +251,73 @@ func (b *execGraphBuilder) managedResourceInstanceSubgraphCreateThenDelete(
|
|||
providerClientRef execgraph.ResultRef[*exec.ProviderClient],
|
||||
waitFor execgraph.AnyResultRef,
|
||||
) execgraph.ResourceInstanceResultRef {
|
||||
// TODO: Add a new execgraph opcode ManagedForget and use that here.
|
||||
panic("execgraph for cbd replace actions not yet implemented")
|
||||
// This has much the same effect as the separate delete and create
|
||||
// actions chained together, but we arrange the operations in such a
|
||||
// way that we don't make any changes unless we can produce valid final
|
||||
// plans for both changes.
|
||||
instAddrRef, priorStateRef := b.managedResourceInstanceChangeAddrAndPriorStateRefs(plannedChange)
|
||||
plannedValRef := b.lower.ConstantValue(plannedChange.After)
|
||||
desiredInstRef := b.lower.ResourceInstanceDesired(instAddrRef, waitFor)
|
||||
|
||||
// We plan both the create and destroy parts of this process before we
|
||||
// make any real changes, to reduce the risk that we'll be left in a
|
||||
// partially-applied state where we're left with a deposed object present
|
||||
// in the final state.
|
||||
createPlanRef := b.lower.ManagedFinalPlan(
|
||||
desiredInstRef,
|
||||
execgraph.NilResultRef[*exec.ResourceInstanceObject](),
|
||||
plannedValRef,
|
||||
providerClientRef,
|
||||
)
|
||||
destroyPlanRef := b.lower.ManagedFinalPlan(
|
||||
execgraph.NilResultRef[*eval.DesiredResourceInstance](),
|
||||
priorStateRef,
|
||||
b.lower.ConstantValue(cty.NullVal(
|
||||
// TODO: is this okay or do we need to use the type constraint derived from the schema?
|
||||
// The two could differ for resource types that have cty.DynamicPseudoType
|
||||
// attributes, like in kubernetes_manifest from the hashicorp/kubernetes provider,
|
||||
// where here we'd capture the type of the current manifest instead of recording
|
||||
// that the manifest's type is unknown. However, we don't typically fuss too much
|
||||
// about the exact type of a null, so this is probably fine.
|
||||
plannedChange.After.Type(),
|
||||
)),
|
||||
providerClientRef,
|
||||
)
|
||||
deposedObjRef := b.lower.ManagedDepose(
|
||||
priorStateRef,
|
||||
b.lower.Waiter(createPlanRef, destroyPlanRef),
|
||||
)
|
||||
createResultRef := b.lower.ManagedApply(
|
||||
createPlanRef,
|
||||
deposedObjRef, // will be restored as current if creation completely fails
|
||||
providerClientRef,
|
||||
b.lower.Waiter(),
|
||||
)
|
||||
// Nothing depends on the value from the destroy result, so if the destroy
|
||||
// fails after the create succeeded then we can proceed with applying any
|
||||
// downstream changes that refer to what we created and then we'll end with
|
||||
// the deposed object still in the state and error diagnostics explaining
|
||||
// why destroying it didn't work.
|
||||
// FIXME: the closer for the provider client ought to depend on this result
|
||||
// in its waitFor argument, because otherwise we're saying it's okay to
|
||||
// close the provider client concurrently with this operation, which will
|
||||
// not work.
|
||||
b.lower.ManagedApply(
|
||||
destroyPlanRef,
|
||||
execgraph.NilResultRef[*exec.ResourceInstanceObject](),
|
||||
providerClientRef,
|
||||
// FIXME: This should also depend on the destroy of any dependents that
|
||||
// are also being destroyed in this execution graph, to ensure the
|
||||
// expected "inside out" destroy order, but we're not currently keeping
|
||||
// track of destroy results anywhere and even if we were we would not
|
||||
// actually learn about them until after this function had returned,
|
||||
// so we need to introduce a way to add new dependencies here while
|
||||
// planning subsequent resource instances, and to make sure we're not
|
||||
// creating a dependency cycle each time we do so.
|
||||
b.lower.Waiter(createResultRef), // wait for successful applying of the create step
|
||||
)
|
||||
|
||||
return createResultRef
|
||||
}
|
||||
|
||||
func (b *execGraphBuilder) managedResourceInstanceChangeAddrAndPriorStateRefs(
|
||||
|
|
|
|||
|
|
@ -181,6 +181,110 @@ func TestExecGraphBuilder_ManagedResourceInstanceSubgraph(t *testing.T) {
|
|||
test.placeholder = r[5];
|
||||
`,
|
||||
},
|
||||
"delete then create with move": {
|
||||
func(b *execGraphBuilder, providerClientRef execgraph.ResultRef[*exec.ProviderClient]) execgraph.ResourceInstanceResultRef {
|
||||
oldInstAddr := addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test",
|
||||
Name: "old",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
||||
return b.ManagedResourceInstanceSubgraph(
|
||||
&plans.ResourceInstanceChange{
|
||||
Addr: instAddr,
|
||||
PrevRunAddr: oldInstAddr,
|
||||
Change: plans.Change{
|
||||
Action: plans.DeleteThenCreate,
|
||||
Before: cty.StringVal("before"),
|
||||
After: cty.StringVal("after"),
|
||||
},
|
||||
},
|
||||
providerClientRef,
|
||||
addrs.MakeSet[addrs.AbsResourceInstance](),
|
||||
)
|
||||
},
|
||||
`
|
||||
v[0] = cty.StringVal("after");
|
||||
v[1] = cty.NullVal(cty.String);
|
||||
|
||||
r[0] = ResourceInstancePrior(test.old);
|
||||
r[1] = ManagedChangeAddr(r[0], test.placeholder);
|
||||
r[2] = ResourceInstanceDesired(test.placeholder, await());
|
||||
r[3] = ManagedFinalPlan(r[2], nil, v[0], nil);
|
||||
r[4] = ManagedFinalPlan(nil, r[1], v[1], nil);
|
||||
r[5] = ManagedApply(r[4], nil, nil, await(r[3]));
|
||||
r[6] = ManagedApply(r[3], nil, nil, await(r[5]));
|
||||
|
||||
test.placeholder = r[6];
|
||||
`,
|
||||
},
|
||||
"create then delete": {
|
||||
func(b *execGraphBuilder, providerClientRef execgraph.ResultRef[*exec.ProviderClient]) execgraph.ResourceInstanceResultRef {
|
||||
return b.ManagedResourceInstanceSubgraph(
|
||||
&plans.ResourceInstanceChange{
|
||||
Addr: instAddr,
|
||||
PrevRunAddr: instAddr,
|
||||
Change: plans.Change{
|
||||
Action: plans.CreateThenDelete,
|
||||
Before: cty.StringVal("before"),
|
||||
After: cty.StringVal("after"),
|
||||
},
|
||||
},
|
||||
providerClientRef,
|
||||
addrs.MakeSet[addrs.AbsResourceInstance](),
|
||||
)
|
||||
},
|
||||
`
|
||||
v[0] = cty.StringVal("after");
|
||||
v[1] = cty.NullVal(cty.String);
|
||||
|
||||
r[0] = ResourceInstancePrior(test.placeholder);
|
||||
r[1] = ResourceInstanceDesired(test.placeholder, await());
|
||||
r[2] = ManagedFinalPlan(r[1], nil, v[0], nil);
|
||||
r[3] = ManagedFinalPlan(nil, r[0], v[1], nil);
|
||||
r[4] = ManagedDepose(r[0], await(r[2], r[3]));
|
||||
r[5] = ManagedApply(r[2], r[4], nil, await());
|
||||
r[6] = ManagedApply(r[3], nil, nil, await(r[5]));
|
||||
|
||||
test.placeholder = r[5];
|
||||
`,
|
||||
},
|
||||
"create then delete with move": {
|
||||
func(b *execGraphBuilder, providerClientRef execgraph.ResultRef[*exec.ProviderClient]) execgraph.ResourceInstanceResultRef {
|
||||
oldInstAddr := addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test",
|
||||
Name: "old",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
||||
return b.ManagedResourceInstanceSubgraph(
|
||||
&plans.ResourceInstanceChange{
|
||||
Addr: instAddr,
|
||||
PrevRunAddr: oldInstAddr,
|
||||
Change: plans.Change{
|
||||
Action: plans.CreateThenDelete,
|
||||
Before: cty.StringVal("before"),
|
||||
After: cty.StringVal("after"),
|
||||
},
|
||||
},
|
||||
providerClientRef,
|
||||
addrs.MakeSet[addrs.AbsResourceInstance](),
|
||||
)
|
||||
},
|
||||
`
|
||||
v[0] = cty.StringVal("after");
|
||||
v[1] = cty.NullVal(cty.String);
|
||||
|
||||
r[0] = ResourceInstancePrior(test.old);
|
||||
r[1] = ManagedChangeAddr(r[0], test.placeholder);
|
||||
r[2] = ResourceInstanceDesired(test.placeholder, await());
|
||||
r[3] = ManagedFinalPlan(r[2], nil, v[0], nil);
|
||||
r[4] = ManagedFinalPlan(nil, r[1], v[1], nil);
|
||||
r[5] = ManagedDepose(r[1], await(r[3], r[4]));
|
||||
r[6] = ManagedApply(r[3], r[5], nil, await());
|
||||
r[7] = ManagedApply(r[4], nil, nil, await(r[6]));
|
||||
|
||||
test.placeholder = r[6];
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
|
|
|
|||
|
|
@ -228,6 +228,31 @@ func (p *planningEvalGlue) ResourceInstanceValue(ctx context.Context, ri *config
|
|||
RequiredResourceInstances: riDeps,
|
||||
ResourceType: ri.Addr.Resource.Resource.Type,
|
||||
ResourceMode: ri.Addr.Resource.Resource.Mode,
|
||||
|
||||
// FIXME: all resource instances in a dependency chain must agree on
|
||||
// one value of CreateBeforeDestroy to use if more than one replace
|
||||
// planned in the chain, because otherwise there is no valid order to
|
||||
// use.
|
||||
//
|
||||
// For example, if subnet depends on vpc and both need to be replaced,
|
||||
// there are only two valid orders:
|
||||
// - destroy subnet, destroy vpc, create vpc, create subnet
|
||||
// - create vpc, create subnet, destroy subnet, destroy vpc
|
||||
//
|
||||
// We need to figure out what part of the system is responsible for
|
||||
// making this decision. The evaluator's configgraph package is probably
|
||||
// the only part of the system that has enough information to answer
|
||||
// this question, because answering it in the planning engine would
|
||||
// require us to know about other resource instances that depend on
|
||||
// the current one, which therefore haven't been planned yet. When
|
||||
// we make this decision we should think about the feature request in
|
||||
// https://github.com/opentofu/opentofu/issues/2523 , which implies
|
||||
// that create_before_destroy would permit arbitrary expression
|
||||
// evaluation.
|
||||
//
|
||||
// For now, we just don't support create_before_destroy at all in this
|
||||
// new implementation.
|
||||
CreateBeforeDestroy: false,
|
||||
}
|
||||
if providerInst, ok := configgraph.GetKnown(providerInst); ok {
|
||||
desired.ProviderInstance = &providerInst.Addr
|
||||
|
|
|
|||
Loading…
Reference in a new issue