terraform: Filter untargeted variable nodes
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.
This commit is contained in:
parent
70999b1a64
commit
24c45fcd5d
|
@ -2114,6 +2114,43 @@ module.child:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext2Plan_targetedModuleUntargetedVariable(t *testing.T) {
|
||||||
|
m := testModule(t, "plan-targeted-module-untargeted-variable")
|
||||||
|
p := testProvider("aws")
|
||||||
|
p.DiffFn = testDiffFn
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
Targets: []string{"aws_instance.blue", "module.blue_mod"},
|
||||||
|
})
|
||||||
|
|
||||||
|
plan, err := ctx.Plan()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(plan.String())
|
||||||
|
expected := strings.TrimSpace(`
|
||||||
|
DIFF:
|
||||||
|
|
||||||
|
CREATE: aws_instance.blue
|
||||||
|
|
||||||
|
module.blue_mod:
|
||||||
|
CREATE: aws_instance.mod
|
||||||
|
type: "" => "aws_instance"
|
||||||
|
value: "" => "<computed>"
|
||||||
|
|
||||||
|
STATE:
|
||||||
|
|
||||||
|
<no state>
|
||||||
|
`)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/hashicorp/terraform/issues/4515
|
// https://github.com/hashicorp/terraform/issues/4515
|
||||||
func TestContext2Plan_targetedOverTen(t *testing.T) {
|
func TestContext2Plan_targetedOverTen(t *testing.T) {
|
||||||
m := testModule(t, "plan-targeted-over-ten")
|
m := testModule(t, "plan-targeted-over-ten")
|
||||||
|
|
|
@ -36,6 +36,14 @@ func (n *GraphNodeConfigVariable) DependableName() []string {
|
||||||
return []string{n.Name()}
|
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 {
|
func (n *GraphNodeConfigVariable) DependentOn() []string {
|
||||||
// If we don't have any value set, we don't depend on anything
|
// If we don't have any value set, we don't depend on anything
|
||||||
if n.Value == nil {
|
if n.Value == nil {
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
variable "id" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "mod" {
|
||||||
|
value = "${var.id}"
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
resource "aws_instance" "blue" { }
|
||||||
|
resource "aws_instance" "green" { }
|
||||||
|
|
||||||
|
module "blue_mod" {
|
||||||
|
source = "./child"
|
||||||
|
id = "${aws_instance.blue.id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
module "green_mod" {
|
||||||
|
source = "./child"
|
||||||
|
id = "${aws_instance.green.id}"
|
||||||
|
}
|
|
@ -37,11 +37,16 @@ func (t *TargetsTransformer) Transform(g *Graph) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range g.Vertices() {
|
for _, v := range g.Vertices() {
|
||||||
|
removable := false
|
||||||
if _, ok := v.(GraphNodeAddressable); ok {
|
if _, ok := v.(GraphNodeAddressable); ok {
|
||||||
if !targetedNodes.Include(v) {
|
removable = true
|
||||||
log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v))
|
}
|
||||||
g.Remove(v)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,3 +115,14 @@ func (t *TargetsTransformer) nodeIsTarget(
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue