terraform: prevent_destroy works for decreasing count
Fixes #5826 The `prevent_destroy` lifecycle configuration was not being checked when the count was decreased for a resource with a count. It was only checking when attributes changed on pre-existing resources. This fixes that.
This commit is contained in:
parent
18b3736ba4
commit
a332c121bc
|
@ -805,6 +805,127 @@ func TestContext2Plan_preventDestroy_good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_preventDestroy_countBad(t *testing.T) {
|
||||
m := testModule(t, "plan-prevent-destroy-count-bad")
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
State: &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "i-abc123",
|
||||
},
|
||||
},
|
||||
"aws_instance.foo.1": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "i-abc345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
plan, err := ctx.Plan()
|
||||
|
||||
expectedErr := "aws_instance.foo.1: the plan would destroy"
|
||||
if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) {
|
||||
t.Fatalf("expected err would contain %q\nerr: %s\nplan: %s",
|
||||
expectedErr, err, plan)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_preventDestroy_countGood(t *testing.T) {
|
||||
m := testModule(t, "plan-prevent-destroy-count-good")
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
State: &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "i-abc123",
|
||||
},
|
||||
},
|
||||
"aws_instance.foo.1": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "i-abc345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
plan, err := ctx.Plan()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if plan.Diff.Empty() {
|
||||
t.Fatalf("Expected non-empty plan, got %s", plan.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_preventDestroy_countGoodNoChange(t *testing.T) {
|
||||
m := testModule(t, "plan-prevent-destroy-count-good")
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
State: &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "i-abc123",
|
||||
Attributes: map[string]string{
|
||||
"current": "0",
|
||||
"type": "aws_instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
plan, err := ctx.Plan()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !plan.Diff.Empty() {
|
||||
t.Fatalf("Expected empty plan, got %s", plan.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_preventDestroy_destroyPlan(t *testing.T) {
|
||||
m := testModule(t, "plan-prevent-destroy-good")
|
||||
p := testProvider("aws")
|
||||
|
|
|
@ -10,8 +10,9 @@ import (
|
|||
// error if a resource has PreventDestroy configured and the diff
|
||||
// would destroy the resource.
|
||||
type EvalCheckPreventDestroy struct {
|
||||
Resource *config.Resource
|
||||
Diff **InstanceDiff
|
||||
Resource *config.Resource
|
||||
ResourceId string
|
||||
Diff **InstanceDiff
|
||||
}
|
||||
|
||||
func (n *EvalCheckPreventDestroy) Eval(ctx EvalContext) (interface{}, error) {
|
||||
|
@ -23,7 +24,12 @@ func (n *EvalCheckPreventDestroy) Eval(ctx EvalContext) (interface{}, error) {
|
|||
preventDestroy := n.Resource.Lifecycle.PreventDestroy
|
||||
|
||||
if diff.GetDestroy() && preventDestroy {
|
||||
return nil, fmt.Errorf(preventDestroyErrStr, n.Resource.Id())
|
||||
resourceId := n.ResourceId
|
||||
if resourceId == "" {
|
||||
resourceId = n.Resource.Id()
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(preventDestroyErrStr, resourceId)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
|
|
@ -168,8 +168,9 @@ func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error)
|
|||
// expand orphans, which have all the same semantics in a destroy
|
||||
// as a primary or tainted resource.
|
||||
steps = append(steps, &OrphanTransformer{
|
||||
State: state,
|
||||
View: n.Resource.Id(),
|
||||
Resource: n.Resource,
|
||||
State: state,
|
||||
View: n.Resource.Id(),
|
||||
})
|
||||
|
||||
steps = append(steps, &DeposedTransformer{
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
resource "aws_instance" "foo" {
|
||||
count = "1"
|
||||
current = "${count.index}"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
resource "aws_instance" "foo" {
|
||||
count = "1"
|
||||
current = "${count.index}"
|
||||
}
|
|
@ -17,6 +17,11 @@ type GraphNodeStateRepresentative interface {
|
|||
// OrphanTransformer is a GraphTransformer that adds orphans to the
|
||||
// graph. This transformer adds both resource and module orphans.
|
||||
type OrphanTransformer struct {
|
||||
// Resource is resource configuration. This is only non-nil when
|
||||
// expanding a resource that is in the configuration. It can't be
|
||||
// dependend on.
|
||||
Resource *config.Resource
|
||||
|
||||
// State is the global state. We require the global state to
|
||||
// properly find module orphans at our path.
|
||||
State *State
|
||||
|
@ -80,6 +85,7 @@ func (t *OrphanTransformer) Transform(g *Graph) error {
|
|||
resourceVertexes[i] = g.Add(&graphNodeOrphanResource{
|
||||
Path: g.Path,
|
||||
ResourceKey: rsk,
|
||||
Resource: t.Resource,
|
||||
Provider: rs.Provider,
|
||||
dependentOn: rs.Dependencies,
|
||||
})
|
||||
|
@ -159,6 +165,7 @@ func (n *graphNodeOrphanModule) Expand(b GraphBuilder) (GraphNodeSubgraph, error
|
|||
type graphNodeOrphanResource struct {
|
||||
Path []string
|
||||
ResourceKey *ResourceStateKey
|
||||
Resource *config.Resource
|
||||
Provider string
|
||||
|
||||
dependentOn []string
|
||||
|
@ -283,6 +290,11 @@ func (n *graphNodeOrphanResource) managedResourceEvalNodes(info *InstanceInfo) [
|
|||
State: &state,
|
||||
Output: &diff,
|
||||
},
|
||||
&EvalCheckPreventDestroy{
|
||||
Resource: n.Resource,
|
||||
ResourceId: n.ResourceKey.String(),
|
||||
Diff: &diff,
|
||||
},
|
||||
&EvalWriteDiff{
|
||||
Name: n.ResourceKey.String(),
|
||||
Diff: &diff,
|
||||
|
|
Loading…
Reference in New Issue