mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-26 00:41:27 -06:00
b9e076ec66
When working with a ConfigResource, the generalization of a ModuleInstance to a Module was inadvertently dropped, and there was to test coverage for that type of target. Ensure we can target a specific module instance alone.
160 lines
4.2 KiB
Go
160 lines
4.2 KiB
Go
package terraform
|
|
|
|
import (
|
|
"log"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/dag"
|
|
)
|
|
|
|
// GraphNodeTargetable is an interface for graph nodes to implement when they
|
|
// need to be told about incoming targets. This is useful for nodes that need
|
|
// to respect targets as they dynamically expand. Note that the list of targets
|
|
// provided will contain every target provided, and each implementing graph
|
|
// node must filter this list to targets considered relevant.
|
|
type GraphNodeTargetable interface {
|
|
SetTargets([]addrs.Targetable)
|
|
}
|
|
|
|
// TargetsTransformer is a GraphTransformer that, when the user specifies a
|
|
// list of resources to target, limits the graph to only those resources and
|
|
// their dependencies.
|
|
type TargetsTransformer struct {
|
|
// List of targeted resource names specified by the user
|
|
Targets []addrs.Targetable
|
|
}
|
|
|
|
func (t *TargetsTransformer) Transform(g *Graph) error {
|
|
if len(t.Targets) > 0 {
|
|
targetedNodes, err := t.selectTargetedNodes(g, t.Targets)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, v := range g.Vertices() {
|
|
if !targetedNodes.Include(v) {
|
|
log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v))
|
|
g.Remove(v)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Returns a set of targeted nodes. A targeted node is either addressed
|
|
// directly, address indirectly via its container, or it's a dependency of a
|
|
// targeted node.
|
|
func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targetable) (dag.Set, error) {
|
|
targetedNodes := make(dag.Set)
|
|
|
|
vertices := g.Vertices()
|
|
|
|
for _, v := range vertices {
|
|
if t.nodeIsTarget(v, addrs) {
|
|
targetedNodes.Add(v)
|
|
|
|
// We inform nodes that ask about the list of targets - helps for nodes
|
|
// that need to dynamically expand. Note that this only occurs for nodes
|
|
// that are already directly targeted.
|
|
if tn, ok := v.(GraphNodeTargetable); ok {
|
|
tn.SetTargets(addrs)
|
|
}
|
|
|
|
deps, _ := g.Ancestors(v)
|
|
for _, d := range deps {
|
|
targetedNodes.Add(d)
|
|
}
|
|
}
|
|
}
|
|
|
|
// It is expected that outputs which are only derived from targeted
|
|
// resources are also updated. While we don't include any other possible
|
|
// side effects from the targeted nodes, these are added because outputs
|
|
// cannot be targeted on their own.
|
|
// Start by finding the root module output nodes themselves
|
|
for _, v := range vertices {
|
|
// outputs are all temporary value types
|
|
tv, ok := v.(graphNodeTemporaryValue)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// root module outputs indicate that while they are an output type,
|
|
// they not temporary and will return false here.
|
|
if tv.temporaryValue() {
|
|
continue
|
|
}
|
|
|
|
// If this output is descended only from targeted resources, then we
|
|
// will keep it
|
|
deps, _ := g.Ancestors(v)
|
|
found := 0
|
|
for _, d := range deps {
|
|
switch d.(type) {
|
|
case GraphNodeResourceInstance:
|
|
case GraphNodeConfigResource:
|
|
default:
|
|
continue
|
|
}
|
|
|
|
if !targetedNodes.Include(d) {
|
|
// this dependency isn't being targeted, so we can't process this
|
|
// output
|
|
found = 0
|
|
break
|
|
}
|
|
|
|
found++
|
|
}
|
|
|
|
if found > 0 {
|
|
// we found an output we can keep; add it, and all it's dependencies
|
|
targetedNodes.Add(v)
|
|
for _, d := range deps {
|
|
targetedNodes.Add(d)
|
|
}
|
|
}
|
|
}
|
|
|
|
return targetedNodes, nil
|
|
}
|
|
|
|
func (t *TargetsTransformer) nodeIsTarget(v dag.Vertex, targets []addrs.Targetable) bool {
|
|
var vertexAddr addrs.Targetable
|
|
switch r := v.(type) {
|
|
case GraphNodeResourceInstance:
|
|
vertexAddr = r.ResourceInstanceAddr()
|
|
case GraphNodeConfigResource:
|
|
vertexAddr = r.ResourceAddr()
|
|
|
|
default:
|
|
// Only resource and resource instance nodes can be targeted.
|
|
return false
|
|
}
|
|
|
|
for _, targetAddr := range targets {
|
|
switch vertexAddr.(type) {
|
|
case addrs.ConfigResource:
|
|
// Before expansion happens, we only have nodes that know their
|
|
// ConfigResource address. We need to take the more specific
|
|
// target addresses and generalize them in order to compare with a
|
|
// ConfigResource.
|
|
switch target := targetAddr.(type) {
|
|
case addrs.AbsResourceInstance:
|
|
targetAddr = target.ContainingResource().Config()
|
|
case addrs.AbsResource:
|
|
targetAddr = target.Config()
|
|
case addrs.ModuleInstance:
|
|
targetAddr = target.Module()
|
|
}
|
|
}
|
|
|
|
if targetAddr.TargetContains(vertexAddr) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|