From 9d4f44da9b52be3625068eaeaca2104f1c26809a Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 24 Jan 2024 18:17:27 -0800 Subject: [PATCH] terraform: Graph nodes can belong to partial-expanded module prefixes The new interface GraphNodePartialExpandedModule is a mutually-exclusive alternative to GraphNodeModuleInstance for graph nodes that represent the unbounded set of as-yet-unknown instances sharing a common known module prefix. For nodes that implement this interface, their Execute and DynamicExpand methods will receive an EvalContext that is configured to perform expression evaluation using safe placeholder values instead of using the real state and plan, since (once implemented in future commits) the graph nodes of this sort will be used for situations where we're deferring part of the plan until we have more information to use while planning. --- internal/terraform/graph.go | 31 +++++++++++++------ .../terraform/graph_interface_subgraph.go | 18 +++++++++++ internal/terraform/node_local.go | 4 +++ internal/terraform/node_module_variable.go | 4 +++ internal/terraform/node_output.go | 4 +++ 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/internal/terraform/graph.go b/internal/terraform/graph.go index 559c44927718..e139f4533418 100644 --- a/internal/terraform/graph.go +++ b/internal/terraform/graph.go @@ -67,21 +67,34 @@ func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics { } }() - // vertexCtx is the context that we use when evaluating. This - // is normally the context of our graph but can be overridden - // with a GraphNodeModuleInstance impl. - vertexCtx := ctx - if pn, ok := v.(GraphNodeModuleInstance); ok { - vertexCtx = walker.EnterPath(pn.Path()) - defer walker.ExitPath(pn.Path()) - } - if g.checkAndApplyOverrides(ctx.Overrides(), v) { // We can skip whole vertices if they are in a module that has been // overridden. + log.Printf("[TRACE] vertex %q: overridden by a test double, so skipping", dag.VertexName(v)) return } + // vertexCtx is the context that we use when evaluating. This + // is normally the global context but can be overridden + // with a GraphNodeModuleInstance or GraphNodePartialExpandedModule + // impl. (The two interfaces are intentionally mutually-exclusive by + // both having the same method name but with different signatures, + // since a node can't belong to two different contexts at once.) + vertexCtx := ctx + if pn, ok := v.(GraphNodeModuleInstance); ok { + moduleAddr := pn.Path() // An addrs.ModuleInstance + log.Printf("[TRACE] vertex %q: belongs to %s", dag.VertexName(v), moduleAddr) + vertexCtx = walker.EnterPath(moduleAddr) + defer walker.ExitPath(pn.Path()) + } else if pn, ok := v.(GraphNodePartialExpandedModule); ok { + moduleAddr := pn.Path() // An addrs.PartialExpandedModule + log.Printf("[TRACE] vertex %q: belongs to all of %s", dag.VertexName(v), moduleAddr) + vertexCtx = walker.EnterPartialExpandedPath(pn.Path()) + defer walker.ExitPartialExpandedPath(pn.Path()) + } else { + log.Printf("[TRACE] vertex %q: does not belong to any module instance", dag.VertexName(v)) + } + // If the node is exec-able, then execute it. if ev, ok := v.(GraphNodeExecutable); ok { diags = diags.Append(walker.Execute(vertexCtx, ev)) diff --git a/internal/terraform/graph_interface_subgraph.go b/internal/terraform/graph_interface_subgraph.go index 2e013aeca0fa..f70fdeb84bd8 100644 --- a/internal/terraform/graph_interface_subgraph.go +++ b/internal/terraform/graph_interface_subgraph.go @@ -18,3 +18,21 @@ type GraphNodeModuleInstance interface { type GraphNodeModulePath interface { ModulePath() addrs.Module } + +// GraphNodePartialExpandedModule says that a node represents an unbounded +// set of objects within an unbounded set of module instances that happen +// to share a known address prefix. +// +// Nodes of this type typically produce placeholder data to support partial +// evaluation despite the full analysis of a module being deferred to a future +// plan when more information will be available. They might also perform +// checks and raise errors when something can be proven to be definitely +// invalid regardless of what the final set of module instances turns out to +// be. +// +// Node types implementing this interface cannot also implement +// [GraphNodeModuleInstance], because it is not possible to evaluate a +// node in two different contexts at once. +type GraphNodePartialExpandedModule interface { + Path() addrs.PartialExpandedModule +} diff --git a/internal/terraform/node_local.go b/internal/terraform/node_local.go index defd5fc10a21..067878e534a6 100644 --- a/internal/terraform/node_local.go +++ b/internal/terraform/node_local.go @@ -201,4 +201,8 @@ type nodeLocalInPartialModule struct { Config *configs.Local } +func (n *nodeLocalInPartialModule) Path() addrs.PartialExpandedModule { + return n.Addr.Module +} + // TODO: Implement nodeLocalUnexpandedPlaceholder.Execute diff --git a/internal/terraform/node_module_variable.go b/internal/terraform/node_module_variable.go index 93c23a562a3b..11a053e77224 100644 --- a/internal/terraform/node_module_variable.go +++ b/internal/terraform/node_module_variable.go @@ -323,4 +323,8 @@ type nodeModuleVariableInPartialModule struct { DestroyApply bool } +func (n *nodeModuleVariableInPartialModule) Path() addrs.PartialExpandedModule { + return n.Addr.Module +} + // TODO: Implement nodeModuleVariableInPartialModule.Execute diff --git a/internal/terraform/node_output.go b/internal/terraform/node_output.go index bfa53d1fa6cb..3be1fceeee9e 100644 --- a/internal/terraform/node_output.go +++ b/internal/terraform/node_output.go @@ -464,6 +464,10 @@ type nodeOutputInPartialModule struct { RefreshOnly bool } +func (n *nodeOutputInPartialModule) Path() addrs.PartialExpandedModule { + return n.Addr.Module +} + // TODO: Implement nodeOutputInPartialModule.Execute // NodeDestroyableOutput represents an output that is "destroyable":