opentofu/internal/tofu/transform_targets.go
2023-09-20 15:16:53 +03:00

163 lines
4.3 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tofu
import (
"log"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/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
}