mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
24c45fcd5d
When targeting, only Addressable untargeted nodes were being removed from the graph. Variable nodes are not directly Addressable, so they were hanging around. This caused problems with module variables that referred to Resource nodes. The Resource node would be filtered out of the graph, but the module Variable node would not, so it would try to interpolate during the graph walk and be unable to find it's referent. This would present itself as strange "cannot find variable" errors for variables that were uninvolved with the currently targeted set of resources. Here, we introduce a new interface that can be implemented by graph nodes to indicate they should be filtered out from targeting even though they are not directly addressable themselves.
275 lines
7.5 KiB
Go
275 lines
7.5 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/hashicorp/terraform/config"
|
|
"github.com/hashicorp/terraform/config/module"
|
|
"github.com/hashicorp/terraform/dag"
|
|
)
|
|
|
|
// GraphNodeConfigVariable represents a Variable in the config.
|
|
type GraphNodeConfigVariable struct {
|
|
Variable *config.Variable
|
|
|
|
// Value, if non-nil, will be used to set the value of the variable
|
|
// during evaluation. If this is nil, evaluation will do nothing.
|
|
//
|
|
// Module is the name of the module to set the variables on.
|
|
Module string
|
|
Value *config.RawConfig
|
|
|
|
ModuleTree *module.Tree
|
|
ModulePath []string
|
|
}
|
|
|
|
func (n *GraphNodeConfigVariable) Name() string {
|
|
return fmt.Sprintf("var.%s", n.Variable.Name)
|
|
}
|
|
|
|
func (n *GraphNodeConfigVariable) ConfigType() GraphNodeConfigType {
|
|
return GraphNodeConfigTypeVariable
|
|
}
|
|
|
|
func (n *GraphNodeConfigVariable) DependableName() []string {
|
|
return []string{n.Name()}
|
|
}
|
|
|
|
// RemoveIfNotTargeted implements RemovableIfNotTargeted.
|
|
// When targeting is active, variables that are not targeted should be removed
|
|
// from the graph, because otherwise module variables trying to interpolate
|
|
// their references can fail when they're missing the referent resource node.
|
|
func (n *GraphNodeConfigVariable) RemoveIfNotTargeted() bool {
|
|
return true
|
|
}
|
|
|
|
func (n *GraphNodeConfigVariable) DependentOn() []string {
|
|
// If we don't have any value set, we don't depend on anything
|
|
if n.Value == nil {
|
|
return nil
|
|
}
|
|
|
|
// Get what we depend on based on our value
|
|
vars := n.Value.Variables
|
|
result := make([]string, 0, len(vars))
|
|
for _, v := range vars {
|
|
if vn := varNameForVar(v); vn != "" {
|
|
result = append(result, vn)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (n *GraphNodeConfigVariable) VariableName() string {
|
|
return n.Variable.Name
|
|
}
|
|
|
|
// GraphNodeDestroyEdgeInclude impl.
|
|
func (n *GraphNodeConfigVariable) DestroyEdgeInclude(v dag.Vertex) bool {
|
|
// Only include this variable in a destroy edge if the source vertex
|
|
// "v" has a count dependency on this variable.
|
|
log.Printf("[DEBUG] DestroyEdgeInclude: Checking: %s", dag.VertexName(v))
|
|
cv, ok := v.(GraphNodeCountDependent)
|
|
if !ok {
|
|
log.Printf("[DEBUG] DestroyEdgeInclude: Not GraphNodeCountDependent: %s", dag.VertexName(v))
|
|
return false
|
|
}
|
|
|
|
for _, d := range cv.CountDependentOn() {
|
|
for _, d2 := range n.DependableName() {
|
|
log.Printf("[DEBUG] DestroyEdgeInclude: d = %s : d2 = %s", d, d2)
|
|
if d == d2 {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GraphNodeNoopPrunable
|
|
func (n *GraphNodeConfigVariable) Noop(opts *NoopOpts) bool {
|
|
log.Printf("[DEBUG] Checking variable noop: %s", n.Name())
|
|
// If we have no diff, always keep this in the graph. We have to do
|
|
// this primarily for validation: we want to validate that variable
|
|
// interpolations are valid even if there are no resources that
|
|
// depend on them.
|
|
if opts.Diff == nil || opts.Diff.Empty() {
|
|
log.Printf("[DEBUG] No diff, not a noop")
|
|
return false
|
|
}
|
|
|
|
// We have to find our our module diff since we do funky things with
|
|
// the flat node's implementation of Path() below.
|
|
modDiff := opts.Diff.ModuleByPath(n.ModulePath)
|
|
|
|
// If we're destroying, we have no need of variables unless they are depended
|
|
// on by the count of a resource.
|
|
if modDiff != nil && modDiff.Destroy {
|
|
if n.hasDestroyEdgeInPath(opts, nil) {
|
|
log.Printf("[DEBUG] Variable has destroy edge from %s, not a noop",
|
|
dag.VertexName(opts.Vertex))
|
|
return false
|
|
}
|
|
log.Printf("[DEBUG] Variable has no included destroy edges: noop!")
|
|
return true
|
|
}
|
|
|
|
for _, v := range opts.Graph.UpEdges(opts.Vertex).List() {
|
|
// This is terrible, but I can't think of a better way to do this.
|
|
if dag.VertexName(v) == rootNodeName {
|
|
continue
|
|
}
|
|
|
|
log.Printf("[DEBUG] Found up edge to %s, var is not noop", dag.VertexName(v))
|
|
return false
|
|
}
|
|
|
|
log.Printf("[DEBUG] No up edges, treating variable as a noop")
|
|
return true
|
|
}
|
|
|
|
// hasDestroyEdgeInPath recursively walks for a destroy edge, ensuring that
|
|
// a variable both has no immediate destroy edges or any in its full module
|
|
// path, ensuring that links do not get severed in the middle.
|
|
func (n *GraphNodeConfigVariable) hasDestroyEdgeInPath(opts *NoopOpts, vertex dag.Vertex) bool {
|
|
if vertex == nil {
|
|
vertex = opts.Vertex
|
|
}
|
|
|
|
log.Printf("[DEBUG] hasDestroyEdgeInPath: Looking for destroy edge: %s - %T", dag.VertexName(vertex), vertex)
|
|
for _, v := range opts.Graph.UpEdges(vertex).List() {
|
|
if len(opts.Graph.UpEdges(v).List()) > 1 {
|
|
if n.hasDestroyEdgeInPath(opts, v) == true {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Here we borrow the implementation of DestroyEdgeInclude, whose logic
|
|
// and semantics are exactly what we want here. We add a check for the
|
|
// the root node, since we have to always depend on its existance.
|
|
if cv, ok := vertex.(*GraphNodeConfigVariableFlat); ok {
|
|
if dag.VertexName(v) == rootNodeName || cv.DestroyEdgeInclude(v) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GraphNodeProxy impl.
|
|
func (n *GraphNodeConfigVariable) Proxy() bool {
|
|
return true
|
|
}
|
|
|
|
// GraphNodeEvalable impl.
|
|
func (n *GraphNodeConfigVariable) EvalTree() EvalNode {
|
|
// If we have no value, do nothing
|
|
if n.Value == nil {
|
|
return &EvalNoop{}
|
|
}
|
|
|
|
// Otherwise, interpolate the value of this variable and set it
|
|
// within the variables mapping.
|
|
var config *ResourceConfig
|
|
variables := make(map[string]interface{})
|
|
return &EvalSequence{
|
|
Nodes: []EvalNode{
|
|
&EvalInterpolate{
|
|
Config: n.Value,
|
|
Output: &config,
|
|
},
|
|
|
|
&EvalVariableBlock{
|
|
Config: &config,
|
|
VariableValues: variables,
|
|
},
|
|
|
|
&EvalCoerceMapVariable{
|
|
Variables: variables,
|
|
ModulePath: n.ModulePath,
|
|
ModuleTree: n.ModuleTree,
|
|
},
|
|
|
|
&EvalTypeCheckVariable{
|
|
Variables: variables,
|
|
ModulePath: n.ModulePath,
|
|
ModuleTree: n.ModuleTree,
|
|
},
|
|
|
|
&EvalSetVariables{
|
|
Module: &n.Module,
|
|
Variables: variables,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// GraphNodeFlattenable impl.
|
|
func (n *GraphNodeConfigVariable) Flatten(p []string) (dag.Vertex, error) {
|
|
return &GraphNodeConfigVariableFlat{
|
|
GraphNodeConfigVariable: n,
|
|
PathValue: p,
|
|
}, nil
|
|
}
|
|
|
|
type GraphNodeConfigVariableFlat struct {
|
|
*GraphNodeConfigVariable
|
|
|
|
PathValue []string
|
|
}
|
|
|
|
func (n *GraphNodeConfigVariableFlat) Name() string {
|
|
return fmt.Sprintf(
|
|
"%s.%s", modulePrefixStr(n.PathValue), n.GraphNodeConfigVariable.Name())
|
|
}
|
|
|
|
func (n *GraphNodeConfigVariableFlat) DependableName() []string {
|
|
return []string{n.Name()}
|
|
}
|
|
|
|
func (n *GraphNodeConfigVariableFlat) DependentOn() []string {
|
|
// We only wrap the dependencies and such if we have a path that is
|
|
// longer than 2 elements (root, child, more). This is because when
|
|
// flattened, variables can point outside the graph.
|
|
prefix := ""
|
|
if len(n.PathValue) > 2 {
|
|
prefix = modulePrefixStr(n.PathValue[:len(n.PathValue)-1])
|
|
}
|
|
|
|
return modulePrefixList(
|
|
n.GraphNodeConfigVariable.DependentOn(),
|
|
prefix)
|
|
}
|
|
|
|
func (n *GraphNodeConfigVariableFlat) Path() []string {
|
|
if len(n.PathValue) > 2 {
|
|
return n.PathValue[:len(n.PathValue)-1]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (n *GraphNodeConfigVariableFlat) Noop(opts *NoopOpts) bool {
|
|
// First look for provider nodes that depend on this variable downstream
|
|
modDiff := opts.Diff.ModuleByPath(n.ModulePath)
|
|
if modDiff != nil && modDiff.Destroy {
|
|
ds, err := opts.Graph.Descendents(n)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Error looking up descendents of %s: %s", n.Name(), err)
|
|
} else {
|
|
for _, d := range ds.List() {
|
|
if _, ok := d.(GraphNodeProvider); ok {
|
|
log.Printf("[DEBUG] This variable is depended on by a provider, can't be a noop.")
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then fall back to existing impl
|
|
return n.GraphNodeConfigVariable.Noop(opts)
|
|
}
|