mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-03 20:50:59 -05:00
Merge pull request #37833 from hashicorp/jbardin/force-replace-instance
Some checks are pending
build / Determine intended Terraform version (push) Waiting to run
build / Determine Go toolchain version (push) Waiting to run
build / Generate release metadata (push) Blocked by required conditions
build / Build for freebsd_386 (push) Blocked by required conditions
build / Build for linux_386 (push) Blocked by required conditions
build / Build for openbsd_386 (push) Blocked by required conditions
build / Build for windows_386 (push) Blocked by required conditions
build / Build for darwin_amd64 (push) Blocked by required conditions
build / Build for freebsd_amd64 (push) Blocked by required conditions
build / Build for linux_amd64 (push) Blocked by required conditions
build / Build for openbsd_amd64 (push) Blocked by required conditions
build / Build for solaris_amd64 (push) Blocked by required conditions
build / Build for windows_amd64 (push) Blocked by required conditions
build / Build for freebsd_arm (push) Blocked by required conditions
build / Build for linux_arm (push) Blocked by required conditions
build / Build for darwin_arm64 (push) Blocked by required conditions
build / Build for linux_arm64 (push) Blocked by required conditions
build / Build for windows_arm64 (push) Blocked by required conditions
build / Build Docker image for linux_386 (push) Blocked by required conditions
build / Build Docker image for linux_amd64 (push) Blocked by required conditions
build / Build Docker image for linux_arm (push) Blocked by required conditions
build / Build Docker image for linux_arm64 (push) Blocked by required conditions
build / Build e2etest for linux_386 (push) Blocked by required conditions
build / Build e2etest for windows_386 (push) Blocked by required conditions
build / Build e2etest for darwin_amd64 (push) Blocked by required conditions
build / Build e2etest for linux_amd64 (push) Blocked by required conditions
build / Build e2etest for windows_amd64 (push) Blocked by required conditions
build / Build e2etest for linux_arm (push) Blocked by required conditions
build / Build e2etest for darwin_arm64 (push) Blocked by required conditions
build / Build e2etest for linux_arm64 (push) Blocked by required conditions
build / Run e2e test for linux_386 (push) Blocked by required conditions
build / Run e2e test for windows_386 (push) Blocked by required conditions
build / Run e2e test for darwin_amd64 (push) Blocked by required conditions
build / Run e2e test for linux_amd64 (push) Blocked by required conditions
build / Run e2e test for windows_amd64 (push) Blocked by required conditions
build / Run e2e test for linux_arm (push) Blocked by required conditions
build / Run e2e test for linux_arm64 (push) Blocked by required conditions
build / Run terraform-exec test for linux amd64 (push) Blocked by required conditions
Quick Checks / Unit Tests (push) Waiting to run
Quick Checks / Race Tests (push) Waiting to run
Quick Checks / End-to-end Tests (push) Waiting to run
Quick Checks / Code Consistency Checks (push) Waiting to run
Some checks are pending
build / Determine intended Terraform version (push) Waiting to run
build / Determine Go toolchain version (push) Waiting to run
build / Generate release metadata (push) Blocked by required conditions
build / Build for freebsd_386 (push) Blocked by required conditions
build / Build for linux_386 (push) Blocked by required conditions
build / Build for openbsd_386 (push) Blocked by required conditions
build / Build for windows_386 (push) Blocked by required conditions
build / Build for darwin_amd64 (push) Blocked by required conditions
build / Build for freebsd_amd64 (push) Blocked by required conditions
build / Build for linux_amd64 (push) Blocked by required conditions
build / Build for openbsd_amd64 (push) Blocked by required conditions
build / Build for solaris_amd64 (push) Blocked by required conditions
build / Build for windows_amd64 (push) Blocked by required conditions
build / Build for freebsd_arm (push) Blocked by required conditions
build / Build for linux_arm (push) Blocked by required conditions
build / Build for darwin_arm64 (push) Blocked by required conditions
build / Build for linux_arm64 (push) Blocked by required conditions
build / Build for windows_arm64 (push) Blocked by required conditions
build / Build Docker image for linux_386 (push) Blocked by required conditions
build / Build Docker image for linux_amd64 (push) Blocked by required conditions
build / Build Docker image for linux_arm (push) Blocked by required conditions
build / Build Docker image for linux_arm64 (push) Blocked by required conditions
build / Build e2etest for linux_386 (push) Blocked by required conditions
build / Build e2etest for windows_386 (push) Blocked by required conditions
build / Build e2etest for darwin_amd64 (push) Blocked by required conditions
build / Build e2etest for linux_amd64 (push) Blocked by required conditions
build / Build e2etest for windows_amd64 (push) Blocked by required conditions
build / Build e2etest for linux_arm (push) Blocked by required conditions
build / Build e2etest for darwin_arm64 (push) Blocked by required conditions
build / Build e2etest for linux_arm64 (push) Blocked by required conditions
build / Run e2e test for linux_386 (push) Blocked by required conditions
build / Run e2e test for windows_386 (push) Blocked by required conditions
build / Run e2e test for darwin_amd64 (push) Blocked by required conditions
build / Run e2e test for linux_amd64 (push) Blocked by required conditions
build / Run e2e test for windows_amd64 (push) Blocked by required conditions
build / Run e2e test for linux_arm (push) Blocked by required conditions
build / Run e2e test for linux_arm64 (push) Blocked by required conditions
build / Run terraform-exec test for linux amd64 (push) Blocked by required conditions
Quick Checks / Unit Tests (push) Waiting to run
Quick Checks / Race Tests (push) Waiting to run
Quick Checks / End-to-end Tests (push) Waiting to run
Quick Checks / Code Consistency Checks (push) Waiting to run
Don't carry all `-replace` addresses through to every instance
This commit is contained in:
commit
f8ae45cfc8
6 changed files with 22 additions and 37 deletions
5
.changes/v1.14/BUG FIXES-20251029-175958.yaml
Normal file
5
.changes/v1.14/BUG FIXES-20251029-175958.yaml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
kind: BUG FIXES
|
||||
body: Combinations of replace_triggered_by and -replace could result in some instances not being replaced
|
||||
time: 2025-10-29T17:59:58.326396-04:00
|
||||
custom:
|
||||
Issue: "37833"
|
||||
|
|
@ -4,6 +4,8 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/dag"
|
||||
|
|
@ -117,7 +119,7 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
|||
concreteResourceInstance := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
||||
return &NodeApplyableResourceInstance{
|
||||
NodeAbstractResourceInstance: a,
|
||||
forceReplace: b.ForceReplace,
|
||||
forceReplace: slices.ContainsFunc(b.ForceReplace, a.Addr.Equal),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -791,7 +791,7 @@ func (n *NodeAbstractResourceInstance) plan(
|
|||
plannedChange *plans.ResourceInstanceChange,
|
||||
currentState *states.ResourceInstanceObject,
|
||||
createBeforeDestroy bool,
|
||||
forceReplace []addrs.AbsResourceInstance,
|
||||
forceReplace bool,
|
||||
) (*plans.ResourceInstanceChange, *states.ResourceInstanceObject, *providers.Deferred, instances.RepetitionData, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
var keyData instances.RepetitionData
|
||||
|
|
@ -2971,26 +2971,7 @@ func resourceInstancePrevRunAddr(ctx EvalContext, currentAddr addrs.AbsResourceI
|
|||
return table.OldAddr(currentAddr)
|
||||
}
|
||||
|
||||
func getAction(addr addrs.AbsResourceInstance, priorVal, plannedNewVal cty.Value, createBeforeDestroy bool, writeOnly cty.PathSet, forceReplace []addrs.AbsResourceInstance, reqRep cty.PathSet) (action plans.Action, actionReason plans.ResourceInstanceChangeActionReason) {
|
||||
// The user might also ask us to force replacing a particular resource
|
||||
// instance, regardless of whether the provider thinks it needs replacing.
|
||||
// For example, users typically do this if they learn a particular object
|
||||
// has become degraded in an immutable infrastructure scenario and so
|
||||
// replacing it with a new object is a viable repair path.
|
||||
matchedForceReplace := false
|
||||
for _, candidateAddr := range forceReplace {
|
||||
if candidateAddr.Equal(addr) {
|
||||
matchedForceReplace = true
|
||||
break
|
||||
}
|
||||
|
||||
// For "force replace" purposes we require an exact resource instance
|
||||
// address to match. If a user forgets to include the instance key
|
||||
// for a multi-instance resource then it won't match here, but we
|
||||
// have an earlier check in NodePlannableResource.Execute that should
|
||||
// prevent us from getting here in that case.
|
||||
}
|
||||
|
||||
func getAction(addr addrs.AbsResourceInstance, priorVal, plannedNewVal cty.Value, createBeforeDestroy bool, writeOnly cty.PathSet, forceReplace bool, reqRep cty.PathSet) (action plans.Action, actionReason plans.ResourceInstanceChangeActionReason) {
|
||||
// Unmark for this test for value equality.
|
||||
eqV := plannedNewVal.Equals(priorVal)
|
||||
eq := eqV.IsKnown() && eqV.True()
|
||||
|
|
@ -2998,7 +2979,7 @@ func getAction(addr addrs.AbsResourceInstance, priorVal, plannedNewVal cty.Value
|
|||
switch {
|
||||
case priorVal.IsNull():
|
||||
action = plans.Create
|
||||
case matchedForceReplace || !reqRep.Empty() || !writeOnly.Intersection(reqRep).Empty():
|
||||
case forceReplace || !reqRep.Empty() || !writeOnly.Intersection(reqRep).Empty():
|
||||
// If the user "forced replace" of this instance of if there are any
|
||||
// "requires replace" paths left _after our filtering above_ then this
|
||||
// is a replace action.
|
||||
|
|
@ -3008,12 +2989,12 @@ func getAction(addr addrs.AbsResourceInstance, priorVal, plannedNewVal cty.Value
|
|||
action = plans.DeleteThenCreate
|
||||
}
|
||||
switch {
|
||||
case matchedForceReplace:
|
||||
case forceReplace:
|
||||
actionReason = plans.ResourceInstanceReplaceByRequest
|
||||
case !reqRep.Empty():
|
||||
actionReason = plans.ResourceInstanceReplaceBecauseCannotUpdate
|
||||
}
|
||||
case eq && !matchedForceReplace:
|
||||
case eq && !forceReplace:
|
||||
action = plans.NoOp
|
||||
default:
|
||||
action = plans.Update
|
||||
|
|
|
|||
|
|
@ -31,11 +31,9 @@ type NodeApplyableResourceInstance struct {
|
|||
|
||||
graphNodeDeposer // implementation of GraphNodeDeposerConfig
|
||||
|
||||
// forceReplace are resource instance addresses where the user wants to
|
||||
// force generating a replace action. This set isn't pre-filtered, so
|
||||
// it might contain addresses that have nothing to do with the resource
|
||||
// that this node represents, which the node itself must therefore ignore.
|
||||
forceReplace []addrs.AbsResourceInstance
|
||||
// forceReplace indicates that this resource is being replaced for external
|
||||
// reasons, like a -replace flag or via replace_triggered_by.
|
||||
forceReplace bool
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package terraform
|
|||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
|
|
@ -586,7 +587,7 @@ func (n *nodeExpandPlannableResource) concreteResource(ctx EvalContext, knownImp
|
|||
ForceCreateBeforeDestroy: n.CreateBeforeDestroy(),
|
||||
skipRefresh: n.skipRefresh,
|
||||
skipPlanChanges: skipPlanChanges,
|
||||
forceReplace: n.forceReplace,
|
||||
forceReplace: slices.ContainsFunc(n.forceReplace, a.Addr.Equal),
|
||||
}
|
||||
|
||||
if importID, ok := knownImports.GetOk(a.Addr); ok {
|
||||
|
|
|
|||
|
|
@ -40,11 +40,9 @@ type NodePlannableResourceInstance struct {
|
|||
// for any instances.
|
||||
skipPlanChanges bool
|
||||
|
||||
// forceReplace are resource instance addresses where the user wants to
|
||||
// force generating a replace action. This set isn't pre-filtered, so
|
||||
// it might contain addresses that have nothing to do with the resource
|
||||
// that this node represents, which the node itself must therefore ignore.
|
||||
forceReplace []addrs.AbsResourceInstance
|
||||
// forceReplace indicates that this resource is being replaced for external
|
||||
// reasons, like a -replace flag or via replace_triggered_by.
|
||||
forceReplace bool
|
||||
|
||||
// replaceTriggeredBy stores references from replace_triggered_by which
|
||||
// triggered this instance to be replaced.
|
||||
|
|
@ -568,7 +566,7 @@ func (n *NodePlannableResourceInstance) replaceTriggered(ctx EvalContext, repDat
|
|||
// triggered the replacement in the plan.
|
||||
// Rather than further complicating the plan method with more
|
||||
// options, we can refactor both of these features later.
|
||||
n.forceReplace = append(n.forceReplace, n.Addr)
|
||||
n.forceReplace = true
|
||||
log.Printf("[DEBUG] ReplaceTriggeredBy forcing replacement of %s due to change in %s", n.Addr, ref.DisplayString())
|
||||
|
||||
n.replaceTriggeredBy = append(n.replaceTriggeredBy, ref)
|
||||
|
|
|
|||
Loading…
Reference in a new issue