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) {
|
func TestContext2Plan_preventDestroy_destroyPlan(t *testing.T) {
|
||||||
m := testModule(t, "plan-prevent-destroy-good")
|
m := testModule(t, "plan-prevent-destroy-good")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
|
|
@ -10,8 +10,9 @@ import (
|
||||||
// error if a resource has PreventDestroy configured and the diff
|
// error if a resource has PreventDestroy configured and the diff
|
||||||
// would destroy the resource.
|
// would destroy the resource.
|
||||||
type EvalCheckPreventDestroy struct {
|
type EvalCheckPreventDestroy struct {
|
||||||
Resource *config.Resource
|
Resource *config.Resource
|
||||||
Diff **InstanceDiff
|
ResourceId string
|
||||||
|
Diff **InstanceDiff
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *EvalCheckPreventDestroy) Eval(ctx EvalContext) (interface{}, error) {
|
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
|
preventDestroy := n.Resource.Lifecycle.PreventDestroy
|
||||||
|
|
||||||
if diff.GetDestroy() && 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
|
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
|
// expand orphans, which have all the same semantics in a destroy
|
||||||
// as a primary or tainted resource.
|
// as a primary or tainted resource.
|
||||||
steps = append(steps, &OrphanTransformer{
|
steps = append(steps, &OrphanTransformer{
|
||||||
State: state,
|
Resource: n.Resource,
|
||||||
View: n.Resource.Id(),
|
State: state,
|
||||||
|
View: n.Resource.Id(),
|
||||||
})
|
})
|
||||||
|
|
||||||
steps = append(steps, &DeposedTransformer{
|
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
|
// OrphanTransformer is a GraphTransformer that adds orphans to the
|
||||||
// graph. This transformer adds both resource and module orphans.
|
// graph. This transformer adds both resource and module orphans.
|
||||||
type OrphanTransformer struct {
|
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
|
// State is the global state. We require the global state to
|
||||||
// properly find module orphans at our path.
|
// properly find module orphans at our path.
|
||||||
State *State
|
State *State
|
||||||
|
@ -80,6 +85,7 @@ func (t *OrphanTransformer) Transform(g *Graph) error {
|
||||||
resourceVertexes[i] = g.Add(&graphNodeOrphanResource{
|
resourceVertexes[i] = g.Add(&graphNodeOrphanResource{
|
||||||
Path: g.Path,
|
Path: g.Path,
|
||||||
ResourceKey: rsk,
|
ResourceKey: rsk,
|
||||||
|
Resource: t.Resource,
|
||||||
Provider: rs.Provider,
|
Provider: rs.Provider,
|
||||||
dependentOn: rs.Dependencies,
|
dependentOn: rs.Dependencies,
|
||||||
})
|
})
|
||||||
|
@ -159,6 +165,7 @@ func (n *graphNodeOrphanModule) Expand(b GraphBuilder) (GraphNodeSubgraph, error
|
||||||
type graphNodeOrphanResource struct {
|
type graphNodeOrphanResource struct {
|
||||||
Path []string
|
Path []string
|
||||||
ResourceKey *ResourceStateKey
|
ResourceKey *ResourceStateKey
|
||||||
|
Resource *config.Resource
|
||||||
Provider string
|
Provider string
|
||||||
|
|
||||||
dependentOn []string
|
dependentOn []string
|
||||||
|
@ -283,6 +290,11 @@ func (n *graphNodeOrphanResource) managedResourceEvalNodes(info *InstanceInfo) [
|
||||||
State: &state,
|
State: &state,
|
||||||
Output: &diff,
|
Output: &diff,
|
||||||
},
|
},
|
||||||
|
&EvalCheckPreventDestroy{
|
||||||
|
Resource: n.Resource,
|
||||||
|
ResourceId: n.ResourceKey.String(),
|
||||||
|
Diff: &diff,
|
||||||
|
},
|
||||||
&EvalWriteDiff{
|
&EvalWriteDiff{
|
||||||
Name: n.ResourceKey.String(),
|
Name: n.ResourceKey.String(),
|
||||||
Diff: &diff,
|
Diff: &diff,
|
||||||
|
|
Loading…
Reference in New Issue