Merge pull request #19613 from hashicorp/b-avoid-ds-on-destroy
backend/local: Avoid rendering data sources on destroy
This commit is contained in:
commit
76d1161000
|
@ -8,6 +8,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/command/format"
|
"github.com/hashicorp/terraform/command/format"
|
||||||
"github.com/hashicorp/terraform/plans"
|
"github.com/hashicorp/terraform/plans"
|
||||||
|
@ -189,7 +190,14 @@ func (b *Local) opPlan(
|
||||||
|
|
||||||
func (b *Local) renderPlan(plan *plans.Plan, schemas *terraform.Schemas) {
|
func (b *Local) renderPlan(plan *plans.Plan, schemas *terraform.Schemas) {
|
||||||
counts := map[plans.Action]int{}
|
counts := map[plans.Action]int{}
|
||||||
|
var rChanges []*plans.ResourceInstanceChangeSrc
|
||||||
for _, change := range plan.Changes.Resources {
|
for _, change := range plan.Changes.Resources {
|
||||||
|
if change.Action == plans.Delete && change.Addr.Resource.Resource.Mode == addrs.DataResourceMode {
|
||||||
|
// Avoid rendering data sources on deletion
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rChanges = append(rChanges, change)
|
||||||
counts[change.Action]++
|
counts[change.Action]++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +233,6 @@ func (b *Local) renderPlan(plan *plans.Plan, schemas *terraform.Schemas) {
|
||||||
// here. The ordering of resource changes in a plan is not significant,
|
// here. The ordering of resource changes in a plan is not significant,
|
||||||
// but we can only do this safely here because we can assume that nobody
|
// but we can only do this safely here because we can assume that nobody
|
||||||
// is concurrently modifying our changes while we're trying to print it.
|
// is concurrently modifying our changes while we're trying to print it.
|
||||||
rChanges := plan.Changes.Resources
|
|
||||||
sort.Slice(rChanges, func(i, j int) bool {
|
sort.Slice(rChanges, func(i, j int) bool {
|
||||||
iA := rChanges[i].Addr
|
iA := rChanges[i].Addr
|
||||||
jA := rChanges[j].Addr
|
jA := rChanges[j].Addr
|
||||||
|
|
|
@ -212,6 +212,94 @@ func TestLocal_planDestroy(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLocal_planDestroy_withDataSources(t *testing.T) {
|
||||||
|
b, cleanup := TestLocal(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
p := TestLocalProvider(t, b, "test", planFixtureSchema())
|
||||||
|
testStateFile(t, b.StatePath, testPlanState_withDataSource())
|
||||||
|
|
||||||
|
b.CLI = cli.NewMockUi()
|
||||||
|
|
||||||
|
outDir := testTempDir(t)
|
||||||
|
defer os.RemoveAll(outDir)
|
||||||
|
planPath := filepath.Join(outDir, "plan.tfplan")
|
||||||
|
|
||||||
|
op, configCleanup := testOperationPlan(t, "./test-fixtures/destroy-with-ds")
|
||||||
|
defer configCleanup()
|
||||||
|
op.Destroy = true
|
||||||
|
op.PlanRefresh = true
|
||||||
|
op.PlanOutPath = planPath
|
||||||
|
cfg := cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"path": cty.StringVal(b.StatePath),
|
||||||
|
})
|
||||||
|
cfgRaw, err := plans.NewDynamicValue(cfg, cfg.Type())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
op.PlanOutBackend = &plans.Backend{
|
||||||
|
// Just a placeholder so that we can generate a valid plan file.
|
||||||
|
Type: "local",
|
||||||
|
Config: cfgRaw,
|
||||||
|
}
|
||||||
|
|
||||||
|
run, err := b.Operation(context.Background(), op)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
<-run.Done()
|
||||||
|
if run.Result != backend.OperationSuccess {
|
||||||
|
t.Fatalf("plan operation failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.ReadResourceCalled {
|
||||||
|
t.Fatal("ReadResource should be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.ReadDataSourceCalled {
|
||||||
|
t.Fatal("ReadDataSourceCalled should be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
if run.PlanEmpty {
|
||||||
|
t.Fatal("plan should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data source should still exist in the the plan file
|
||||||
|
plan := testReadPlan(t, planPath)
|
||||||
|
if len(plan.Changes.Resources) != 2 {
|
||||||
|
t.Fatalf("Expected exactly 1 resource for destruction, %d given: %q",
|
||||||
|
len(plan.Changes.Resources), getAddrs(plan.Changes.Resources))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data source should not be rendered in the output
|
||||||
|
expectedOutput := `Terraform will perform the following actions:
|
||||||
|
|
||||||
|
# test_instance.foo will be destroyed
|
||||||
|
- resource "test_instance" "foo" {
|
||||||
|
- ami = "bar" -> null
|
||||||
|
|
||||||
|
- network_interface {
|
||||||
|
- description = "Main network interface" -> null
|
||||||
|
- device_index = 0 -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plan: 0 to add, 0 to change, 1 to destroy.`
|
||||||
|
|
||||||
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
||||||
|
if !strings.Contains(output, expectedOutput) {
|
||||||
|
t.Fatalf("Unexpected output (expected no data source):\n%s", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAddrs(resources []*plans.ResourceInstanceChangeSrc) []string {
|
||||||
|
addrs := make([]string, len(resources), len(resources))
|
||||||
|
for i, r := range resources {
|
||||||
|
addrs[i] = r.Addr.String()
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
||||||
func TestLocal_planOutPathNoChange(t *testing.T) {
|
func TestLocal_planOutPathNoChange(t *testing.T) {
|
||||||
b, cleanup := TestLocal(t)
|
b, cleanup := TestLocal(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
@ -336,6 +424,48 @@ func testPlanState() *states.State {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testPlanState_withDataSource() *states.State {
|
||||||
|
state := states.NewState()
|
||||||
|
rootModule := state.RootModule()
|
||||||
|
rootModule.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "foo",
|
||||||
|
}.Instance(addrs.IntKey(0)),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
AttrsJSON: []byte(`{
|
||||||
|
"ami": "bar",
|
||||||
|
"network_interface": [{
|
||||||
|
"device_index": 0,
|
||||||
|
"description": "Main network interface"
|
||||||
|
}]
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
addrs.ProviderConfig{
|
||||||
|
Type: "test",
|
||||||
|
}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
rootModule.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.DataResourceMode,
|
||||||
|
Type: "test_ds",
|
||||||
|
Name: "bar",
|
||||||
|
}.Instance(addrs.IntKey(0)),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
AttrsJSON: []byte(`{
|
||||||
|
"filter": "foo"
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
addrs.ProviderConfig{
|
||||||
|
Type: "test",
|
||||||
|
}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
func testReadPlan(t *testing.T, path string) *plans.Plan {
|
func testReadPlan(t *testing.T, path string) *plans.Plan {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
@ -376,5 +506,12 @@ func planFixtureSchema() *terraform.ProviderSchema {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
DataSources: map[string]*configschema.Block{
|
||||||
|
"test_ds": {
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"filter": {Type: cty.String, Required: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
resource "test_instance" "foo" {
|
||||||
|
ami = "bar"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "test_ds" "bar" {
|
||||||
|
filter = "foo"
|
||||||
|
}
|
|
@ -69,6 +69,9 @@ func TestLocalProvider(t *testing.T, b *Local, name string, schema *terraform.Pr
|
||||||
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
||||||
return providers.ReadResourceResponse{NewState: req.PriorState}
|
return providers.ReadResourceResponse{NewState: req.PriorState}
|
||||||
}
|
}
|
||||||
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
||||||
|
return providers.ReadDataSourceResponse{State: req.Config}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the opts
|
// Initialize the opts
|
||||||
if b.ContextOpts == nil {
|
if b.ContextOpts == nil {
|
||||||
|
|
Loading…
Reference in New Issue