2015-03-24 11:18:15 -05:00
|
|
|
package terraform
|
|
|
|
|
2015-05-01 11:01:49 -05:00
|
|
|
import (
|
|
|
|
"log"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/dag"
|
|
|
|
)
|
2015-03-24 11:18:15 -05:00
|
|
|
|
2017-01-26 22:16:06 -06:00
|
|
|
// 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([]ResourceAddress)
|
|
|
|
}
|
|
|
|
|
2017-05-10 20:27:49 -05:00
|
|
|
// GraphNodeTargetDownstream is an interface for graph nodes that need to
|
|
|
|
// be remain present under targeting if any of their dependencies are targeted.
|
|
|
|
// TargetDownstream is called with the set of vertices that are direct
|
|
|
|
// dependencies for the node, and it should return true if the node must remain
|
|
|
|
// in the graph in support of those dependencies.
|
|
|
|
//
|
|
|
|
// This is used in situations where the dependency edges are representing an
|
|
|
|
// ordering relationship but the dependency must still be visited if its
|
|
|
|
// dependencies are visited. This is true for outputs, for example, since
|
|
|
|
// they must get updated if any of their dependent resources get updated,
|
|
|
|
// which would not normally be true if one of their dependencies were targeted.
|
|
|
|
type GraphNodeTargetDownstream interface {
|
|
|
|
TargetDownstream(targeted, untargeted *dag.Set) bool
|
|
|
|
}
|
|
|
|
|
2015-03-24 11:18:15 -05:00
|
|
|
// 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 []string
|
|
|
|
|
2016-01-07 14:43:43 -06:00
|
|
|
// List of parsed targets, provided by callers like ResourceCountTransform
|
|
|
|
// that already have the targets parsed
|
|
|
|
ParsedTargets []ResourceAddress
|
|
|
|
|
core: -target option to also select resources in descendant modules
Previously the behavior for -target when given a module address was to
target only resources directly within that module, ignoring any resources
defined in child modules.
This behavior turned out to be counter-intuitive, since users expected
the -target address to be interpreted hierarchically.
We'll now use the new "Contains" function for addresses, which provides
a hierarchical "containment" concept that is more consistent with user
expectations. In particular, it allows module.foo to match
module.foo.module.bar.aws_instance.baz, where before that would not have
been true.
Since Contains isn't commutative (unlike Equals) this requires some
special handling for targeting specific indices. When given an argument
like -target=aws_instance.foo[0], the initial graph construction (for
both plan and refresh) is for the resource nodes from configuration, which
have not yet been expanded to separate indexed instances. Thus we need
to do the first pass of TargetsTransformer in mode where indices are
ignored, with the work then completed by the DynamicExpand method which
re-applies the TargetsTransformer in index-sensitive mode.
This is a breaking change for anyone depending on the previous behavior
of -target, since it will now select more resources than before. There is
no way provided to obtain the previous behavior. Eventually we may support
negative targeting, which could then combine with positive targets to
regain the previous behavior as an explicit choice.
2017-06-15 20:15:41 -05:00
|
|
|
// If set, the index portions of resource addresses will be ignored
|
|
|
|
// for comparison. This is used when transforming a graph where
|
|
|
|
// counted resources have not yet been expanded, since otherwise
|
|
|
|
// the unexpanded nodes (which never have indices) would not match.
|
|
|
|
IgnoreIndices bool
|
|
|
|
|
2015-03-24 11:18:15 -05:00
|
|
|
// Set to true when we're in a `terraform destroy` or a
|
|
|
|
// `terraform plan -destroy`
|
|
|
|
Destroy bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TargetsTransformer) Transform(g *Graph) error {
|
2016-01-07 14:43:43 -06:00
|
|
|
if len(t.Targets) > 0 && len(t.ParsedTargets) == 0 {
|
2015-03-30 19:02:36 -05:00
|
|
|
addrs, err := t.parseTargetAddresses()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-10-20 20:59:02 -05:00
|
|
|
|
2016-01-07 14:43:43 -06:00
|
|
|
t.ParsedTargets = addrs
|
|
|
|
}
|
2016-10-20 20:59:02 -05:00
|
|
|
|
2016-01-07 14:43:43 -06:00
|
|
|
if len(t.ParsedTargets) > 0 {
|
|
|
|
targetedNodes, err := t.selectTargetedNodes(g, t.ParsedTargets)
|
2015-03-24 11:18:15 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, v := range g.Vertices() {
|
2016-07-29 10:53:13 -05:00
|
|
|
removable := false
|
2017-01-26 22:16:06 -06:00
|
|
|
if _, ok := v.(GraphNodeResource); ok {
|
2016-07-29 10:53:13 -05:00
|
|
|
removable = true
|
|
|
|
}
|
|
|
|
if vr, ok := v.(RemovableIfNotTargeted); ok {
|
|
|
|
removable = vr.RemoveIfNotTargeted()
|
|
|
|
}
|
|
|
|
if removable && !targetedNodes.Include(v) {
|
|
|
|
log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v))
|
|
|
|
g.Remove(v)
|
2015-03-24 11:18:15 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-20 20:59:02 -05:00
|
|
|
|
2015-03-24 11:18:15 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-03-30 19:02:36 -05:00
|
|
|
func (t *TargetsTransformer) parseTargetAddresses() ([]ResourceAddress, error) {
|
|
|
|
addrs := make([]ResourceAddress, len(t.Targets))
|
|
|
|
for i, target := range t.Targets {
|
|
|
|
ta, err := ParseResourceAddress(target)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
addrs[i] = *ta
|
|
|
|
}
|
2016-10-20 20:59:02 -05:00
|
|
|
|
2015-03-30 19:02:36 -05:00
|
|
|
return addrs, nil
|
|
|
|
}
|
|
|
|
|
2015-04-15 13:53:32 -05:00
|
|
|
// Returns the list of targeted nodes. A targeted node is either addressed
|
|
|
|
// directly, or is an Ancestor of a targeted node. Destroy mode keeps
|
|
|
|
// Descendents instead of Ancestors.
|
2015-03-30 19:02:36 -05:00
|
|
|
func (t *TargetsTransformer) selectTargetedNodes(
|
|
|
|
g *Graph, addrs []ResourceAddress) (*dag.Set, error) {
|
2015-03-24 11:18:15 -05:00
|
|
|
targetedNodes := new(dag.Set)
|
2017-05-10 20:27:49 -05:00
|
|
|
|
|
|
|
vertices := g.Vertices()
|
|
|
|
|
|
|
|
for _, v := range vertices {
|
2015-04-15 13:53:32 -05:00
|
|
|
if t.nodeIsTarget(v, addrs) {
|
|
|
|
targetedNodes.Add(v)
|
2015-03-24 11:18:15 -05:00
|
|
|
|
2015-04-15 13:53:32 -05:00
|
|
|
// 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)
|
2015-03-30 19:02:36 -05:00
|
|
|
}
|
2015-03-24 11:18:15 -05:00
|
|
|
|
|
|
|
var deps *dag.Set
|
|
|
|
var err error
|
|
|
|
if t.Destroy {
|
2015-04-15 13:53:32 -05:00
|
|
|
deps, err = g.Descendents(v)
|
2015-03-24 11:18:15 -05:00
|
|
|
} else {
|
2015-04-15 13:53:32 -05:00
|
|
|
deps, err = g.Ancestors(v)
|
2015-03-24 11:18:15 -05:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, d := range deps.List() {
|
|
|
|
targetedNodes.Add(d)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-20 20:59:02 -05:00
|
|
|
|
2017-05-10 20:27:49 -05:00
|
|
|
// Handle nodes that need to be included if their dependencies are included.
|
|
|
|
// This requires multiple passes since we need to catch transitive
|
|
|
|
// dependencies if and only if they are via other nodes that also
|
|
|
|
// support TargetDownstream. For example:
|
|
|
|
// output -> output -> targeted-resource: both outputs need to be targeted
|
|
|
|
// output -> non-targeted-resource -> targeted-resource: output not targeted
|
|
|
|
//
|
|
|
|
// We'll keep looping until we stop targeting more nodes.
|
|
|
|
queue := targetedNodes.List()
|
|
|
|
for len(queue) > 0 {
|
|
|
|
vertices := queue
|
|
|
|
queue = nil // ready to append for next iteration if neccessary
|
|
|
|
for _, v := range vertices {
|
|
|
|
dependers := g.UpEdges(v)
|
|
|
|
if dependers == nil {
|
|
|
|
// indicates that there are no up edges for this node, so
|
|
|
|
// we have nothing to do here.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
dependers = dependers.Filter(func(dv interface{}) bool {
|
|
|
|
// Can ignore nodes that are already targeted
|
|
|
|
/*if targetedNodes.Include(dv) {
|
|
|
|
return false
|
|
|
|
}*/
|
|
|
|
|
|
|
|
_, ok := dv.(GraphNodeTargetDownstream)
|
|
|
|
return ok
|
|
|
|
})
|
|
|
|
|
|
|
|
if dependers.Len() == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, dv := range dependers.List() {
|
|
|
|
if targetedNodes.Include(dv) {
|
|
|
|
// Already present, so nothing to do
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// We'll give the node some information about what it's
|
|
|
|
// depending on in case that informs its decision about whether
|
|
|
|
// it is safe to be targeted.
|
|
|
|
deps := g.DownEdges(v)
|
|
|
|
depsTargeted := deps.Intersection(targetedNodes)
|
|
|
|
depsUntargeted := deps.Difference(depsTargeted)
|
|
|
|
|
|
|
|
if dv.(GraphNodeTargetDownstream).TargetDownstream(depsTargeted, depsUntargeted) {
|
|
|
|
targetedNodes.Add(dv)
|
|
|
|
// Need to visit this node on the next pass to see if it
|
|
|
|
// has any transitive dependers.
|
|
|
|
queue = append(queue, dv)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-24 11:18:15 -05:00
|
|
|
return targetedNodes, nil
|
|
|
|
}
|
|
|
|
|
2015-03-30 19:02:36 -05:00
|
|
|
func (t *TargetsTransformer) nodeIsTarget(
|
2015-04-15 13:53:32 -05:00
|
|
|
v dag.Vertex, addrs []ResourceAddress) bool {
|
2017-01-26 22:16:06 -06:00
|
|
|
r, ok := v.(GraphNodeResource)
|
2015-04-15 13:53:32 -05:00
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
2016-10-20 20:59:02 -05:00
|
|
|
|
2017-01-26 22:16:06 -06:00
|
|
|
addr := r.ResourceAddr()
|
2015-03-30 19:02:36 -05:00
|
|
|
for _, targetAddr := range addrs {
|
core: -target option to also select resources in descendant modules
Previously the behavior for -target when given a module address was to
target only resources directly within that module, ignoring any resources
defined in child modules.
This behavior turned out to be counter-intuitive, since users expected
the -target address to be interpreted hierarchically.
We'll now use the new "Contains" function for addresses, which provides
a hierarchical "containment" concept that is more consistent with user
expectations. In particular, it allows module.foo to match
module.foo.module.bar.aws_instance.baz, where before that would not have
been true.
Since Contains isn't commutative (unlike Equals) this requires some
special handling for targeting specific indices. When given an argument
like -target=aws_instance.foo[0], the initial graph construction (for
both plan and refresh) is for the resource nodes from configuration, which
have not yet been expanded to separate indexed instances. Thus we need
to do the first pass of TargetsTransformer in mode where indices are
ignored, with the work then completed by the DynamicExpand method which
re-applies the TargetsTransformer in index-sensitive mode.
This is a breaking change for anyone depending on the previous behavior
of -target, since it will now select more resources than before. There is
no way provided to obtain the previous behavior. Eventually we may support
negative targeting, which could then combine with positive targets to
regain the previous behavior as an explicit choice.
2017-06-15 20:15:41 -05:00
|
|
|
if t.IgnoreIndices {
|
|
|
|
// targetAddr is not a pointer, so we can safely mutate it without
|
|
|
|
// interfering with references elsewhere.
|
|
|
|
targetAddr.Index = -1
|
|
|
|
}
|
|
|
|
if targetAddr.Contains(addr) {
|
2015-03-24 11:18:15 -05:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2016-10-20 20:59:02 -05:00
|
|
|
|
2015-03-24 11:18:15 -05:00
|
|
|
return false
|
|
|
|
}
|
2016-07-29 10:53:13 -05:00
|
|
|
|
|
|
|
// RemovableIfNotTargeted is a special interface for graph nodes that
|
|
|
|
// aren't directly addressable, but need to be removed from the graph when they
|
|
|
|
// are not targeted. (Nodes that are not directly targeted end up in the set of
|
|
|
|
// targeted nodes because something that _is_ targeted depends on them.) The
|
|
|
|
// initial use case for this interface is GraphNodeConfigVariable, which was
|
|
|
|
// having trouble interpolating for module variables in targeted scenarios that
|
|
|
|
// filtered out the resource node being referenced.
|
|
|
|
type RemovableIfNotTargeted interface {
|
|
|
|
RemoveIfNotTargeted() bool
|
|
|
|
}
|