-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2775 from hashicorp/b-noop-prune
Prune unused resources from graph
- Loading branch information
Showing
10 changed files
with
277 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
variable "value" {} | ||
|
||
resource "aws_instance" "bar" { | ||
value = "${var.value}" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
resource "aws_instance" "foo" {} | ||
|
||
module "child" { | ||
source = "./child" | ||
value = "${aws_instance.foo.output}" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package terraform | ||
|
||
import ( | ||
"github.com/hashicorp/terraform/dag" | ||
) | ||
|
||
// GraphNodeNoopPrunable can be implemented by nodes that can be | ||
// pruned if they are noops. | ||
type GraphNodeNoopPrunable interface { | ||
Noop(*NoopOpts) bool | ||
} | ||
|
||
// NoopOpts are the options available to determine if your node is a noop. | ||
type NoopOpts struct { | ||
Graph *Graph | ||
Vertex dag.Vertex | ||
Diff *Diff | ||
State *State | ||
ModDiff *ModuleDiff | ||
ModState *ModuleState | ||
} | ||
|
||
// PruneNoopTransformer is a graph transform that prunes nodes that | ||
// consider themselves no-ops. This is done to both simplify the graph | ||
// as well as to remove graph nodes that might otherwise cause problems | ||
// during the graph run. Therefore, this transformer isn't completely | ||
// an optimization step, and can instead be considered critical to | ||
// Terraform operations. | ||
// | ||
// Example of the above case: variables for modules interpolate their values. | ||
// Interpolation will fail on destruction (since attributes are being deleted), | ||
// but variables shouldn't even eval if there is nothing that will consume | ||
// the variable. Therefore, variables can note that they can be omitted | ||
// safely in this case. | ||
// | ||
// The PruneNoopTransformer will prune nodes depth first, and will automatically | ||
// create connect through the dependencies of pruned nodes. For example, | ||
// if we have a graph A => B => C (A depends on B, etc.), and B decides to | ||
// be removed, we'll still be left with A => C; the edge will be properly | ||
// connected. | ||
type PruneNoopTransformer struct { | ||
Diff *Diff | ||
State *State | ||
} | ||
|
||
func (t *PruneNoopTransformer) Transform(g *Graph) error { | ||
// Find the leaves. | ||
leaves := make([]dag.Vertex, 0, 10) | ||
for _, v := range g.Vertices() { | ||
if g.DownEdges(v).Len() == 0 { | ||
leaves = append(leaves, v) | ||
} | ||
} | ||
|
||
// Do a depth first walk from the leaves and remove things. | ||
return g.ReverseDepthFirstWalk(leaves, func(v dag.Vertex, depth int) error { | ||
// We need a prunable | ||
pn, ok := v.(GraphNodeNoopPrunable) | ||
if !ok { | ||
return nil | ||
} | ||
|
||
// Start building the noop opts | ||
path := g.Path | ||
if pn, ok := v.(GraphNodeSubPath); ok { | ||
path = pn.Path() | ||
} | ||
|
||
var modDiff *ModuleDiff | ||
var modState *ModuleState | ||
if t.Diff != nil { | ||
modDiff = t.Diff.ModuleByPath(path) | ||
} | ||
if t.State != nil { | ||
modState = t.State.ModuleByPath(path) | ||
} | ||
|
||
// Determine if its a noop. If it isn't, just return | ||
noop := pn.Noop(&NoopOpts{ | ||
Graph: g, | ||
Vertex: v, | ||
Diff: t.Diff, | ||
State: t.State, | ||
ModDiff: modDiff, | ||
ModState: modState, | ||
}) | ||
if !noop { | ||
return nil | ||
} | ||
|
||
// It is a noop! We first preserve edges. | ||
up := g.UpEdges(v).List() | ||
for _, downV := range g.DownEdges(v).List() { | ||
for _, upV := range up { | ||
g.Connect(dag.BasicEdge(upV, downV)) | ||
} | ||
} | ||
|
||
// Then remove it | ||
g.Remove(v) | ||
|
||
return nil | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package terraform | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform/dag" | ||
) | ||
|
||
func TestPruneNoopTransformer(t *testing.T) { | ||
g := Graph{Path: RootModulePath} | ||
|
||
a := &testGraphNodeNoop{NameValue: "A"} | ||
b := &testGraphNodeNoop{NameValue: "B", Value: true} | ||
c := &testGraphNodeNoop{NameValue: "C"} | ||
|
||
g.Add(a) | ||
g.Add(b) | ||
g.Add(c) | ||
g.Connect(dag.BasicEdge(a, b)) | ||
g.Connect(dag.BasicEdge(b, c)) | ||
|
||
{ | ||
tf := &PruneNoopTransformer{} | ||
if err := tf.Transform(&g); err != nil { | ||
t.Fatalf("err: %s", err) | ||
} | ||
} | ||
|
||
actual := strings.TrimSpace(g.String()) | ||
expected := strings.TrimSpace(testTransformPruneNoopStr) | ||
if actual != expected { | ||
t.Fatalf("bad:\n\n%s", actual) | ||
} | ||
} | ||
|
||
const testTransformPruneNoopStr = ` | ||
A | ||
C | ||
C | ||
` | ||
|
||
type testGraphNodeNoop struct { | ||
NameValue string | ||
Value bool | ||
} | ||
|
||
func (v *testGraphNodeNoop) Name() string { | ||
return v.NameValue | ||
} | ||
|
||
func (v *testGraphNodeNoop) Noop(*NoopOpts) bool { | ||
return v.Value | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters