Merge pull request #25206 from hashicorp/jbardin/target-with-expansion

Targeting with module expansion
This commit is contained in:
James Bardin 2020-06-12 12:44:49 -04:00 committed by GitHub
commit 22680d7409
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 102 additions and 74 deletions

View File

@ -414,9 +414,26 @@ func (m ModuleInstance) TargetContains(other Targetable) bool {
} }
for i, ourStep := range m { for i, ourStep := range m {
otherStep := to[i] otherStep := to[i]
if ourStep != otherStep {
if ourStep.Name != otherStep.Name {
return false return false
} }
// if this is our last step, because all targets are parsed as
// instances, this may be a ModuleInstance intended to be used as a
// Module.
if i == len(m)-1 {
if ourStep.InstanceKey == NoKey {
// If the other step is a keyed instance, then we contain that
// step, and if it isn't it's a match, which is true either way
return true
}
}
if ourStep.InstanceKey != otherStep.InstanceKey {
return false
}
} }
return true return true

View File

@ -20,13 +20,6 @@ func TestTargetContains(t *testing.T) {
mustParseTarget("module.foo"), mustParseTarget("module.foo"),
true, true,
}, },
{
// module.foo is an unkeyed module instance here, so it cannot
// contain another instance
mustParseTarget("module.foo"),
mustParseTarget("module.foo[0]"),
false,
},
{ {
RootModuleInstance, RootModuleInstance,
mustParseTarget("module.foo"), mustParseTarget("module.foo"),
@ -99,6 +92,11 @@ func TestTargetContains(t *testing.T) {
mustParseTarget("module.bar.test_resource.foo[0]"), mustParseTarget("module.bar.test_resource.foo[0]"),
true, true,
}, },
{
mustParseTarget("module.bax"),
mustParseTarget("module.bax[0].test_resource.foo[0]"),
true,
},
// Config paths, while never returned from parsing a target, must still // Config paths, while never returned from parsing a target, must still
// be targetable // be targetable

View File

@ -5882,3 +5882,68 @@ output"out" {
t.Fatal(diags.ErrWithWarnings()) t.Fatal(diags.ErrWithWarnings())
} }
} }
func TestContext2Plan_targetExpandedAddress(t *testing.T) {
m := testModuleInline(t, map[string]string{
"main.tf": `
module "mod" {
count = 3
source = "./mod"
}
`,
"mod/main.tf": `
resource "aws_instance" "foo" {
count = 2
}
`,
})
p := testProvider("aws")
p.DiffFn = testDiffFn
targets := []addrs.Targetable{}
target, diags := addrs.ParseTargetStr("module.mod[1].aws_instance.foo[0]")
if diags.HasErrors() {
t.Fatal(diags.ErrWithWarnings())
}
targets = append(targets, target.Subject)
target, diags = addrs.ParseTargetStr("module.mod[2]")
if diags.HasErrors() {
t.Fatal(diags.ErrWithWarnings())
}
targets = append(targets, target.Subject)
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Targets: targets,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatal(diags.ErrWithWarnings())
}
expected := map[string]plans.Action{
// the single targeted mod[1] instances
`module.mod[1].aws_instance.foo[0]`: plans.Create,
// the whole mode[2]
`module.mod[2].aws_instance.foo[0]`: plans.Create,
`module.mod[2].aws_instance.foo[1]`: plans.Create,
}
for _, res := range plan.Changes.Resources {
want := expected[res.Addr.String()]
if res.Action != want {
t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action)
}
delete(expected, res.Addr.String())
}
for res, action := range expected {
t.Errorf("missing %s change for %s", action, res)
}
}

View File

@ -123,6 +123,7 @@ func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, er
a.ProviderMetas = n.ProviderMetas a.ProviderMetas = n.ProviderMetas
a.dependsOn = n.dependsOn a.dependsOn = n.dependsOn
a.forceDependsOn = n.forceDependsOn a.forceDependsOn = n.forceDependsOn
a.Targets = n.Targets
return &NodeRefreshableDataResourceInstance{ return &NodeRefreshableDataResourceInstance{
NodeAbstractResourceInstance: a, NodeAbstractResourceInstance: a,

View File

@ -145,6 +145,10 @@ func (n *nodeCloseModule) ReferenceableAddrs() []addrs.Referenceable {
} }
} }
func (n *nodeCloseModule) TargetDownstream(targeted, untargeted dag.Set) bool {
return true
}
func (n *nodeCloseModule) Name() string { func (n *nodeCloseModule) Name() string {
if len(n.Addr) == 0 { if len(n.Addr) == 0 {
return "root" return "root"

View File

@ -23,6 +23,7 @@ var (
_ GraphNodeConfigResource = (*nodeExpandApplyableResource)(nil) _ GraphNodeConfigResource = (*nodeExpandApplyableResource)(nil)
_ GraphNodeAttachResourceConfig = (*nodeExpandApplyableResource)(nil) _ GraphNodeAttachResourceConfig = (*nodeExpandApplyableResource)(nil)
_ graphNodeExpandsInstances = (*nodeExpandApplyableResource)(nil) _ graphNodeExpandsInstances = (*nodeExpandApplyableResource)(nil)
_ GraphNodeTargetable = (*nodeExpandApplyableResource)(nil)
) )
func (n *nodeExpandApplyableResource) expandsInstances() {} func (n *nodeExpandApplyableResource) expandsInstances() {}

View File

@ -29,6 +29,7 @@ var (
_ GraphNodeReferencer = (*nodeExpandPlannableResource)(nil) _ GraphNodeReferencer = (*nodeExpandPlannableResource)(nil)
_ GraphNodeConfigResource = (*nodeExpandPlannableResource)(nil) _ GraphNodeConfigResource = (*nodeExpandPlannableResource)(nil)
_ GraphNodeAttachResourceConfig = (*nodeExpandPlannableResource)(nil) _ GraphNodeAttachResourceConfig = (*nodeExpandPlannableResource)(nil)
_ GraphNodeTargetable = (*nodeExpandPlannableResource)(nil)
) )
func (n *nodeExpandPlannableResource) Name() string { func (n *nodeExpandPlannableResource) Name() string {

View File

@ -43,10 +43,6 @@ type TargetsTransformer struct {
// counted resources have not yet been expanded, since otherwise // counted resources have not yet been expanded, since otherwise
// the unexpanded nodes (which never have indices) would not match. // the unexpanded nodes (which never have indices) would not match.
IgnoreIndices bool IgnoreIndices bool
// Set to true when we're in a `terraform destroy` or a
// `terraform plan -destroy`
Destroy bool
} }
func (t *TargetsTransformer) Transform(g *Graph) error { func (t *TargetsTransformer) Transform(g *Graph) error {
@ -78,7 +74,7 @@ func (t *TargetsTransformer) Transform(g *Graph) error {
// Returns a set of targeted nodes. A targeted node is either addressed // 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 // directly, address indirectly via its container, or it's a dependency of a
// targeted node. Destroy mode keeps dependents instead of dependencies. // targeted node.
func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targetable) (dag.Set, error) { func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targetable) (dag.Set, error) {
targetedNodes := make(dag.Set) targetedNodes := make(dag.Set)
@ -95,13 +91,7 @@ func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targeta
tn.SetTargets(addrs) tn.SetTargets(addrs)
} }
var deps dag.Set deps, err := g.Ancestors(v)
var err error
if t.Destroy {
deps, err = g.Descendents(v)
} else {
deps, err = g.Ancestors(v)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -239,8 +229,11 @@ func (t *TargetsTransformer) nodeIsTarget(v dag.Vertex, targets []addrs.Targetab
// vertexAddr because instance addresses are contained within // vertexAddr because instance addresses are contained within
// their associated resources, and so .TargetContains will take // their associated resources, and so .TargetContains will take
// care of this for us. // care of this for us.
if instance, isInstance := targetAddr.(addrs.AbsResourceInstance); isInstance { switch instance := targetAddr.(type) {
targetAddr = instance.ContainingResource() case addrs.AbsResourceInstance:
targetAddr = instance.ContainingResource().Config()
case addrs.ModuleInstance:
targetAddr = instance.Module()
} }
} }
if targetAddr.TargetContains(vertexAddr) { if targetAddr.TargetContains(vertexAddr) {

View File

@ -200,55 +200,3 @@ output.grandchild_id (expand)
t.Fatalf("bad:\n\nexpected:\n%s\n\ngot:\n%s\n", expected, actual) t.Fatalf("bad:\n\nexpected:\n%s\n\ngot:\n%s\n", expected, actual)
} }
} }
func TestTargetsTransformer_destroy(t *testing.T) {
mod := testModule(t, "transform-targets-destroy")
g := Graph{Path: addrs.RootModuleInstance}
{
tf := &ConfigTransformer{Config: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &AttachResourceConfigTransformer{Config: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &ReferenceTransformer{}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &TargetsTransformer{
Targets: []addrs.Targetable{
addrs.RootModuleInstance.Resource(
addrs.ManagedResourceMode, "aws_instance", "me",
),
},
Destroy: true,
}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(`
aws_elb.me
aws_instance.me
aws_instance.me
aws_instance.metoo
aws_instance.me
`)
if actual != expected {
t.Fatalf("bad:\n\nexpected:\n%s\n\ngot:\n%s\n", expected, actual)
}
}