don't evaluate destroy instances
Orphaned instances that are create_before_destroy will still be in the state when their references are evaluated. We need to skip instances that are planned to be destroyed altogether, as they can't be part of an evaluation.
This commit is contained in:
parent
be34a0e76f
commit
6243a6307a
|
@ -11357,3 +11357,85 @@ output "myoutput" {
|
||||||
t.Fatal("expected empty state, got:", state)
|
t.Fatal("expected empty state, got:", state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext2Apply_scaleInCBD(t *testing.T) {
|
||||||
|
m := testModuleInline(t, map[string]string{
|
||||||
|
"main.tf": `
|
||||||
|
resource "test_instance" "a" {
|
||||||
|
count = 1
|
||||||
|
lifecycle {
|
||||||
|
create_before_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_instance" "b" {
|
||||||
|
foo = join(".", test_instance.a[*].id)
|
||||||
|
}
|
||||||
|
|
||||||
|
output "out" {
|
||||||
|
value = join(".", test_instance.a[*].id)
|
||||||
|
}
|
||||||
|
`})
|
||||||
|
|
||||||
|
state := states.NewState()
|
||||||
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
||||||
|
root.SetResourceInstanceCurrent(
|
||||||
|
mustResourceInstanceAddr("test_instance.a[0]").Resource,
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
AttrsJSON: []byte(`{"id":"a0"}`),
|
||||||
|
Dependencies: []addrs.ConfigResource{mustResourceAddr("module.child.aws_instance.child")},
|
||||||
|
},
|
||||||
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
||||||
|
)
|
||||||
|
root.SetResourceInstanceCurrent(
|
||||||
|
mustResourceInstanceAddr("test_instance.a[1]").Resource,
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
AttrsJSON: []byte(`{"id":"a1"}`),
|
||||||
|
Dependencies: []addrs.ConfigResource{mustResourceAddr("module.child.aws_instance.child")},
|
||||||
|
},
|
||||||
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
||||||
|
)
|
||||||
|
root.SetResourceInstanceCurrent(
|
||||||
|
mustResourceInstanceAddr("test_instance.b").Resource,
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
AttrsJSON: []byte(`{"id":"b", "foo":"old.old"}`),
|
||||||
|
Dependencies: []addrs.ConfigResource{mustResourceAddr("test_instance.a")},
|
||||||
|
},
|
||||||
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
||||||
|
)
|
||||||
|
|
||||||
|
p := testProvider("test")
|
||||||
|
p.ApplyFn = func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) {
|
||||||
|
return testApplyFn(info, s, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.DiffFn = testDiffFn
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Config: m,
|
||||||
|
Providers: map[addrs.Provider]providers.Factory{
|
||||||
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
State: state,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, diags := ctx.Plan()
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.ErrWithWarnings())
|
||||||
|
}
|
||||||
|
|
||||||
|
// if resource b isn't going to apply correctly, we will get an error about
|
||||||
|
// an invalid plan value
|
||||||
|
state, diags = ctx.Apply()
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.ErrWithWarnings())
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the output, as those can't cause an error planning the value
|
||||||
|
out := state.RootModule().OutputValues["out"].Value.AsString()
|
||||||
|
if out != "a0" {
|
||||||
|
t.Fatalf(`expected output "new", got: %q`, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -655,10 +655,20 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
|
||||||
|
|
||||||
instAddr := addr.Instance(key).Absolute(d.ModulePath)
|
instAddr := addr.Instance(key).Absolute(d.ModulePath)
|
||||||
|
|
||||||
|
change := d.Evaluator.Changes.GetResourceInstanceChange(instAddr, states.CurrentGen)
|
||||||
|
if change != nil {
|
||||||
|
// Don't take any resources that are yet to be deleted into account.
|
||||||
|
// If the referenced resource is CreateBeforeDestroy, then orphaned
|
||||||
|
// instances will be in the state, as they are not destroyed until
|
||||||
|
// after their dependants are updated.
|
||||||
|
if change.Action == plans.Delete {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Planned resources are temporarily stored in state with empty values,
|
// Planned resources are temporarily stored in state with empty values,
|
||||||
// and need to be replaced bu the planned value here.
|
// and need to be replaced bu the planned value here.
|
||||||
if is.Current.Status == states.ObjectPlanned {
|
if is.Current.Status == states.ObjectPlanned {
|
||||||
change := d.Evaluator.Changes.GetResourceInstanceChange(instAddr, states.CurrentGen)
|
|
||||||
if change == nil {
|
if change == nil {
|
||||||
// If the object is in planned status then we should not get
|
// If the object is in planned status then we should not get
|
||||||
// here, since we should have found a pending value in the plan
|
// here, since we should have found a pending value in the plan
|
||||||
|
|
Loading…
Reference in New Issue