From 179c502ffbc8192606447bf202af7a1806ab9133 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Tue, 8 Oct 2024 11:55:19 -0400 Subject: [PATCH] make ephemeral resource work during destroy Ephemeral resource behavior is a strange mix of temporary values and existing resource types, and their operation during destroy is going to be more difficult to manage than normal resources. The pruneUnusedNodesTransformer is probably no longer equipped to handle the situation, and will probably need to be build in a different manner, but this minor change allows us to move forward for now. --- .../builtin/providers/terraform/provider.go | 1 + internal/terraform/context_plan.go | 1 + internal/terraform/graph_builder_plan.go | 2 +- internal/terraform/transform_config.go | 26 +++++++++++-------- internal/terraform/transform_destroy_edge.go | 10 +++++++ 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/internal/builtin/providers/terraform/provider.go b/internal/builtin/providers/terraform/provider.go index 525fa91629..300bd81d95 100644 --- a/internal/builtin/providers/terraform/provider.go +++ b/internal/builtin/providers/terraform/provider.go @@ -24,6 +24,7 @@ func NewProvider() providers.Interface { // GetSchema returns the complete schema for the provider. func (p *Provider) GetProviderSchema() providers.GetProviderSchemaResponse { resp := providers.GetProviderSchemaResponse{ + Provider: providers.Schema{}, ServerCapabilities: providers.ServerCapabilities{ MoveResourceState: true, }, diff --git a/internal/terraform/context_plan.go b/internal/terraform/context_plan.go index da15f9bc63..6ad958fd40 100644 --- a/internal/terraform/context_plan.go +++ b/internal/terraform/context_plan.go @@ -378,6 +378,7 @@ func (c *Context) checkApplyGraph(plan *plans.Plan, config *configs.Config, opts return nil } log.Println("[DEBUG] building apply graph to check for errors") + _, _, diags := c.applyGraph(plan, config, opts.ApplyOpts(), true) return diags } diff --git a/internal/terraform/graph_builder_plan.go b/internal/terraform/graph_builder_plan.go index a16ad908d3..3b6cfa8470 100644 --- a/internal/terraform/graph_builder_plan.go +++ b/internal/terraform/graph_builder_plan.go @@ -137,7 +137,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { &ConfigTransformer{ Concrete: b.ConcreteResource, Config: b.Config, - skip: b.Operation == walkDestroy || b.Operation == walkPlanDestroy, + destroy: b.Operation == walkDestroy || b.Operation == walkPlanDestroy, importTargets: b.ImportTargets, diff --git a/internal/terraform/transform_config.go b/internal/terraform/transform_config.go index 86678235cb..aa2ee0b74b 100644 --- a/internal/terraform/transform_config.go +++ b/internal/terraform/transform_config.go @@ -35,7 +35,8 @@ type ConfigTransformer struct { ModeFilter bool Mode addrs.ResourceMode - skip bool + // some actions are skipped during the destroy process + destroy bool // importTargets specifies a slice of addresses that will have state // imported for them. @@ -52,11 +53,6 @@ type ConfigTransformer struct { } func (t *ConfigTransformer) Transform(g *Graph) error { - // no import ops happen during destroy - if t.skip { - return nil - } - // If no configuration is available, we don't do anything if t.Config == nil { return nil @@ -97,12 +93,17 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er log.Printf("[TRACE] ConfigTransformer: Starting for path: %v", path) var allResources []*configs.Resource - for _, r := range module.ManagedResources { - allResources = append(allResources, r) - } - for _, r := range module.DataResources { - allResources = append(allResources, r) + if !t.destroy { + for _, r := range module.ManagedResources { + allResources = append(allResources, r) + } + for _, r := range module.DataResources { + allResources = append(allResources, r) + } } + + // ephemeral resources act like temporary values and must be added to the + // graph even during destroy operations. for _, r := range module.EphemeralResources { allResources = append(allResources, r) } @@ -204,6 +205,9 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er // validateImportTargets ensures that the import target module exists in the // configuration. Individual resources will be check by the validation node. func (t *ConfigTransformer) validateImportTargets() error { + if t.destroy { + return nil + } var diags tfdiags.Diagnostics for _, i := range t.importTargets { diff --git a/internal/terraform/transform_destroy_edge.go b/internal/terraform/transform_destroy_edge.go index 64c38aa0ff..6c8e201a9b 100644 --- a/internal/terraform/transform_destroy_edge.go +++ b/internal/terraform/transform_destroy_edge.go @@ -346,6 +346,16 @@ func (t *pruneUnusedNodesTransformer) Transform(g *Graph) error { } case graphNodeExpandsInstances: + // FIXME: Ephemeral resources have kind of broken this + // transformer. They work like temporary values in that they + // should not exist when there are no dependencies, but also + // share expansion nodes with all other resource modes. We + // can't use graphNodeTemporaryValue because then all + // resources will be caught by the previous case here. + if n, ok := n.(GraphNodeConfigResource); ok && n.ResourceAddr().Resource.Mode == addrs.EphemeralResourceMode { + return + } + // Any nodes that expand instances are kept when their // instances may need to be evaluated. for _, v := range g.UpEdges(n) {