Skip to content

Commit 93ff272

Browse files
authored
Merge pull request hashicorp#31163 from hashicorp/jbardin/plan-destroy
Use plan graph builder for destroy
2 parents 4fb8a5a + e36ee75 commit 93ff272

11 files changed

Lines changed: 215 additions & 170 deletions

‎internal/terraform/context_apply2_test.go‎

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package terraform
33
import (
44
"errors"
55
"fmt"
6+
"strings"
67
"sync"
78
"testing"
89
"time"
@@ -912,3 +913,140 @@ resource "test_resource" "c" {
912913
}
913914
})
914915
}
916+
917+
// pass an input through some expanded values, and back to a provider to make
918+
// sure we can fully evaluate a provider configuration during a destroy plan.
919+
func TestContext2Apply_destroyWithConfiguredProvider(t *testing.T) {
920+
m := testModuleInline(t, map[string]string{
921+
"main.tf": `
922+
variable "in" {
923+
type = map(string)
924+
default = {
925+
"a" = "first"
926+
"b" = "second"
927+
}
928+
}
929+
930+
module "mod" {
931+
source = "./mod"
932+
for_each = var.in
933+
in = each.value
934+
}
935+
936+
locals {
937+
config = [for each in module.mod : each.out]
938+
}
939+
940+
provider "other" {
941+
output = [for each in module.mod : each.out]
942+
local = local.config
943+
var = var.in
944+
}
945+
946+
resource "other_object" "other" {
947+
}
948+
`,
949+
"./mod/main.tf": `
950+
variable "in" {
951+
type = string
952+
}
953+
954+
data "test_object" "d" {
955+
test_string = var.in
956+
}
957+
958+
resource "test_object" "a" {
959+
test_string = var.in
960+
}
961+
962+
output "out" {
963+
value = data.test_object.d.output
964+
}
965+
`})
966+
967+
testProvider := &MockProvider{
968+
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
969+
Provider: providers.Schema{Block: simpleTestSchema()},
970+
ResourceTypes: map[string]providers.Schema{
971+
"test_object": providers.Schema{Block: simpleTestSchema()},
972+
},
973+
DataSources: map[string]providers.Schema{
974+
"test_object": providers.Schema{
975+
Block: &configschema.Block{
976+
Attributes: map[string]*configschema.Attribute{
977+
"test_string": {
978+
Type: cty.String,
979+
Optional: true,
980+
},
981+
"output": {
982+
Type: cty.String,
983+
Computed: true,
984+
},
985+
},
986+
},
987+
},
988+
},
989+
},
990+
}
991+
992+
testProvider.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
993+
cfg := req.Config.AsValueMap()
994+
s := cfg["test_string"].AsString()
995+
if !strings.Contains("firstsecond", s) {
996+
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("expected 'first' or 'second', got %s", s))
997+
return resp
998+
}
999+
1000+
cfg["output"] = cty.StringVal(s + "-ok")
1001+
resp.State = cty.ObjectVal(cfg)
1002+
return resp
1003+
}
1004+
1005+
otherProvider := &MockProvider{
1006+
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
1007+
Provider: providers.Schema{
1008+
Block: &configschema.Block{
1009+
Attributes: map[string]*configschema.Attribute{
1010+
"output": {
1011+
Type: cty.List(cty.String),
1012+
Optional: true,
1013+
},
1014+
"local": {
1015+
Type: cty.List(cty.String),
1016+
Optional: true,
1017+
},
1018+
"var": {
1019+
Type: cty.Map(cty.String),
1020+
Optional: true,
1021+
},
1022+
},
1023+
},
1024+
},
1025+
ResourceTypes: map[string]providers.Schema{
1026+
"other_object": providers.Schema{Block: simpleTestSchema()},
1027+
},
1028+
},
1029+
}
1030+
1031+
ctx := testContext2(t, &ContextOpts{
1032+
Providers: map[addrs.Provider]providers.Factory{
1033+
addrs.NewDefaultProvider("test"): testProviderFuncFixed(testProvider),
1034+
addrs.NewDefaultProvider("other"): testProviderFuncFixed(otherProvider),
1035+
},
1036+
})
1037+
1038+
opts := SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))
1039+
plan, diags := ctx.Plan(m, states.NewState(), opts)
1040+
assertNoErrors(t, diags)
1041+
1042+
state, diags := ctx.Apply(plan, m)
1043+
assertNoErrors(t, diags)
1044+
1045+
// TODO: extend this to ensure the otherProvider is always properly
1046+
// configured during the destroy plan
1047+
1048+
opts.Mode = plans.DestroyMode
1049+
// destroy only a single instance not included in the moved statements
1050+
_, diags = ctx.Plan(m, state, opts)
1051+
assertNoErrors(t, diags)
1052+
}

‎internal/terraform/context_plan.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
574574
}).Build(addrs.RootModuleInstance)
575575
return graph, walkPlan, diags
576576
case plans.DestroyMode:
577-
graph, diags := (&DestroyPlanGraphBuilder{
577+
graph, diags := DestroyPlanGraphBuilder(&PlanGraphBuilder{
578578
Config: config,
579579
State: prevRunState,
580580
RootVariableValues: opts.SetVariables,

‎internal/terraform/graph_builder_apply.go‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,7 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
136136
&ForcedCBDTransformer{},
137137

138138
// Destruction ordering
139-
&DestroyEdgeTransformer{
140-
Config: b.Config,
141-
State: b.State,
142-
},
139+
&DestroyEdgeTransformer{},
143140
&CBDEdgeTransformer{
144141
Config: b.Config,
145142
State: b.State,
Lines changed: 5 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,17 @@
11
package terraform
22

33
import (
4-
"github.com/hashicorp/terraform/internal/addrs"
5-
"github.com/hashicorp/terraform/internal/configs"
64
"github.com/hashicorp/terraform/internal/dag"
7-
"github.com/hashicorp/terraform/internal/states"
8-
"github.com/hashicorp/terraform/internal/tfdiags"
95
)
106

11-
// DestroyPlanGraphBuilder implements GraphBuilder and is responsible for
12-
// planning a pure-destroy.
13-
//
14-
// Planning a pure destroy operation is simple because we can ignore most
15-
// ordering configuration and simply reverse the state. This graph mainly
16-
// exists for targeting, because we need to walk the destroy dependencies to
17-
// ensure we plan the required resources. Without the requirement for
18-
// targeting, the plan could theoretically be created directly from the state.
19-
type DestroyPlanGraphBuilder struct {
20-
// Config is the configuration tree to build the plan from.
21-
Config *configs.Config
22-
23-
// State is the current state
24-
State *states.State
25-
26-
// RootVariableValues are the raw input values for root input variables
27-
// given by the caller, which we'll resolve into final values as part
28-
// of the plan walk.
29-
RootVariableValues InputValues
30-
31-
// Plugins is a library of plug-in components (providers and
32-
// provisioners) available for use.
33-
Plugins *contextPlugins
34-
35-
// Targets are resources to target
36-
Targets []addrs.Targetable
37-
38-
// If set, skipRefresh will cause us stop skip refreshing any existing
39-
// resource instances as part of our planning. This will cause us to fail
40-
// to detect if an object has already been deleted outside of Terraform.
41-
skipRefresh bool
42-
}
43-
44-
// See GraphBuilder
45-
func (b *DestroyPlanGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) {
46-
return (&BasicGraphBuilder{
47-
Steps: b.Steps(),
48-
Name: "DestroyPlanGraphBuilder",
49-
}).Build(path)
50-
}
51-
52-
// See GraphBuilder
53-
func (b *DestroyPlanGraphBuilder) Steps() []GraphTransformer {
54-
concreteResourceInstance := func(a *NodeAbstractResourceInstance) dag.Vertex {
7+
func DestroyPlanGraphBuilder(p *PlanGraphBuilder) GraphBuilder {
8+
p.ConcreteResourceInstance = func(a *NodeAbstractResourceInstance) dag.Vertex {
559
return &NodePlanDestroyableResourceInstance{
5610
NodeAbstractResourceInstance: a,
57-
skipRefresh: b.skipRefresh,
58-
}
59-
}
60-
concreteResourceInstanceDeposed := func(a *NodeAbstractResourceInstance, key states.DeposedKey) dag.Vertex {
61-
return &NodePlanDeposedResourceInstanceObject{
62-
NodeAbstractResourceInstance: a,
63-
DeposedKey: key,
64-
skipRefresh: b.skipRefresh,
65-
}
66-
}
67-
68-
concreteProvider := func(a *NodeAbstractProvider) dag.Vertex {
69-
return &NodeApplyableProvider{
70-
NodeAbstractProvider: a,
11+
skipRefresh: p.skipRefresh,
7112
}
7213
}
14+
p.destroy = true
7315

74-
steps := []GraphTransformer{
75-
// Creates nodes for the resource instances tracked in the state.
76-
&StateTransformer{
77-
ConcreteCurrent: concreteResourceInstance,
78-
ConcreteDeposed: concreteResourceInstanceDeposed,
79-
State: b.State,
80-
},
81-
82-
// Create the delete changes for root module outputs.
83-
&OutputTransformer{
84-
Config: b.Config,
85-
Destroy: true,
86-
},
87-
88-
// Attach the state
89-
&AttachStateTransformer{State: b.State},
90-
91-
// Attach the configuration to any resources
92-
&AttachResourceConfigTransformer{Config: b.Config},
93-
94-
transformProviders(concreteProvider, b.Config),
95-
96-
// Destruction ordering. We require this only so that
97-
// targeting below will prune the correct things.
98-
&DestroyEdgeTransformer{
99-
Config: b.Config,
100-
State: b.State,
101-
},
102-
103-
&TargetsTransformer{Targets: b.Targets},
104-
105-
// Close opened plugin connections
106-
&CloseProviderTransformer{},
107-
108-
// Close the root module
109-
&CloseRootModuleTransformer{},
110-
111-
&TransitiveReductionTransformer{},
112-
}
113-
114-
return steps
16+
return p
11517
}

0 commit comments

Comments
 (0)