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:
James Bardin 2020-06-26 17:30:00 -04:00
parent be34a0e76f
commit 6243a6307a
2 changed files with 93 additions and 1 deletions

View File

@ -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)
}
}

View File

@ -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