Module Expansion: Part 2 (#24154)

* WIP: dynamic expand

* WIP: add variable and local support

* WIP: outputs

* WIP: Add referencer

* String representation, fixing tests it impacts

* Fixes TestContext2Apply_outputOrphanModule

* Fix TestContext2Apply_plannedDestroyInterpolatedCount

* Update DestroyOutputTransformer and associated types to reflect PlannableOutputs

* Remove comment about locals

* Remove module count enablement

* Removes allowing count for modules, and reverts the test,
while adding a Skip()'d test that works when you re-enable
the config

* update TargetDownstream signature to match master

* remove unnecessary method

Co-authored-by: James Bardin <j.bardin@gmail.com>
This commit is contained in:
Pam Selle 2020-02-24 17:42:32 -05:00 committed by GitHub
parent d095f57290
commit c249943360
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 468 additions and 213 deletions

View File

@ -14,6 +14,15 @@ func (v InputVariable) String() string {
return "var." + v.Name
}
// Absolute converts the receiver into an absolute address within the given
// module instance.
func (v InputVariable) Absolute(m ModuleInstance) AbsInputVariableInstance {
return AbsInputVariableInstance{
Module: m,
Variable: v,
}
}
// AbsInputVariableInstance is the address of an input variable within a
// particular module instance.
type AbsInputVariableInstance struct {

View File

@ -33,7 +33,11 @@ func (m Module) String() string {
if len(m) == 0 {
return ""
}
return strings.Join([]string(m), ".")
var steps []string
for _, s := range m {
steps = append(steps, "module", s)
}
return strings.Join(steps, ".")
}
// Child returns the address of a child call in the receiver, identified by the

View File

@ -274,8 +274,8 @@
}
},
"provider_config": {
"module_test_foo:test": {
"module_address": "module_test_foo",
"module.module_test_foo:test": {
"module_address": "module.module_test_foo",
"name": "test"
}
}

View File

@ -8,6 +8,7 @@ import (
"log"
"os"
"path/filepath"
"strings"
version "github.com/hashicorp/go-version"
@ -48,7 +49,11 @@ type Record struct {
type Manifest map[string]Record
func (m Manifest) ModuleKey(path addrs.Module) string {
return path.String()
if len(path) == 0 {
return ""
}
return strings.Join([]string(path), ".")
}
// manifestSnapshotFile is an internal struct used only to assist in our JSON

View File

@ -767,6 +767,7 @@ aws_instance.foo:
}
func TestContext2Apply_emptyModule(t *testing.T) {
// A module with only outputs (no resources)
m := testModule(t, "apply-empty-module")
p := testProvider("aws")
p.ApplyFn = testApplyFn

View File

@ -429,6 +429,81 @@ func TestContext2Plan_modules(t *testing.T) {
checkVals(t, expected, ric.After)
}
}
func TestContext2Plan_moduleCount(t *testing.T) {
// This test is skipped with count disabled.
t.Skip()
//FIXME: add for_each and single modules to this test
m := testModule(t, "plan-modules-count")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
ProviderResolver: providers.ResolverFixed(
map[addrs.Provider]providers.Factory{
addrs.NewLegacyProvider("aws"): testProviderFuncFixed(p),
},
),
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
if len(plan.Changes.Resources) != 6 {
t.Error("expected 6 resource in plan, got", len(plan.Changes.Resources))
}
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
expectFoo := objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("2"),
"type": cty.StringVal("aws_instance")},
)
expectNum := objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"num": cty.NumberIntVal(2),
"type": cty.StringVal("aws_instance"),
})
expectExpansion := objectVal(t, schema, map[string]cty.Value{
"bar": cty.StringVal("baz"),
"id": cty.UnknownVal(cty.String),
"num": cty.NumberIntVal(2),
"type": cty.StringVal("aws_instance"),
})
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
var expected cty.Value
switch i := ric.Addr.String(); i {
case "aws_instance.bar":
expected = expectFoo
case "aws_instance.foo":
expected = expectNum
case "module.child[0].aws_instance.foo[0]",
"module.child[0].aws_instance.foo[1]",
"module.child[1].aws_instance.foo[0]",
"module.child[1].aws_instance.foo[1]":
expected = expectExpansion
default:
t.Fatal("unknown instance:", i)
}
checkVals(t, expected, ric.After)
}
}
// GH-1475
func TestContext2Plan_moduleCycle(t *testing.T) {

View File

@ -490,15 +490,18 @@ func (n *EvalWriteResourceState) Eval(ctx EvalContext) (interface{}, error) {
// We'll record our expansion decision in the shared "expander" object
// so that later operations (i.e. DynamicExpand and expression evaluation)
// can refer to it.
// can refer to it. Since this node represents the abstract module, we need
// to expand the module here to create all resources.
expander := ctx.InstanceExpander()
for _, module := range expander.ExpandModule(ctx.Path().Module()) {
switch eachMode {
case states.EachList:
expander.SetResourceCount(ctx.Path(), n.Addr, count)
expander.SetResourceCount(module, n.Addr, count)
case states.EachMap:
expander.SetResourceForEach(ctx.Path(), n.Addr, forEach)
expander.SetResourceForEach(module, n.Addr, forEach)
default:
expander.SetResourceSingle(ctx.Path(), n.Addr)
expander.SetResourceSingle(module, n.Addr)
}
}
return nil, nil

View File

@ -5,6 +5,8 @@ import (
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/lang"
"github.com/hashicorp/terraform/states"
)
// nodeExpandModule represents a module call in the configuration that
@ -12,8 +14,10 @@ import (
// configured.
type nodeExpandModule struct {
CallerAddr addrs.ModuleInstance
Addr addrs.Module
Call addrs.ModuleCall
Config *configs.Module
ModuleCall *configs.ModuleCall
}
var (
@ -29,14 +33,20 @@ func (n *nodeExpandModule) Name() string {
// GraphNodeSubPath implementation
func (n *nodeExpandModule) Path() addrs.ModuleInstance {
// Notice that the node represents the module call and so we report
// the parent module as the path. The module call we're representing
// might expand into multiple child module instances during our work here.
// This node represents the module call within a module,
// so return the CallerAddr as the path as the module
// call may expand into multiple child instances
return n.CallerAddr
}
// GraphNodeReferencer implementation
func (n *nodeExpandModule) References() []*addrs.Reference {
var refs []*addrs.Reference
if n.ModuleCall == nil {
return nil
}
// Expansion only uses the count and for_each expressions, so this
// particular graph node only refers to those.
// Individual variable values in the module call definition might also
@ -47,18 +57,14 @@ func (n *nodeExpandModule) References() []*addrs.Reference {
// our call, these references will be correctly interpreted as being
// in the calling module's namespace, not the namespaces of any of the
// child module instances we might expand to during our evaluation.
var ret []*addrs.Reference
// TODO: Once count and for_each are actually supported, analyze their
// expressions for references here.
/*
if n.Config.Count != nil {
ret = append(ret, n.Config.Count.References()...)
if n.ModuleCall.Count != nil {
refs, _ = lang.ReferencesInExpr(n.ModuleCall.Count)
}
if n.Config.ForEach != nil {
ret = append(ret, n.Config.ForEach.References()...)
if n.ModuleCall.ForEach != nil {
refs, _ = lang.ReferencesInExpr(n.ModuleCall.ForEach)
}
*/
return ret
return appendResourceDestroyReferences(refs)
}
// RemovableIfNotTargeted implementation
@ -74,23 +80,56 @@ func (n *nodeExpandModule) EvalTree() EvalNode {
CallerAddr: n.CallerAddr,
Call: n.Call,
Config: n.Config,
ModuleCall: n.ModuleCall,
}
}
// evalPrepareModuleExpansion is an EvalNode implementation
// that sets the count or for_each on the instance expander
type evalPrepareModuleExpansion struct {
CallerAddr addrs.ModuleInstance
Call addrs.ModuleCall
Config *configs.Module
ModuleCall *configs.ModuleCall
}
func (n *evalPrepareModuleExpansion) Eval(ctx EvalContext) (interface{}, error) {
// Modules don't support any of the repetition arguments yet, so their
// expansion type is always "single". We just record this here to make
// the expander data structure consistent for now.
// FIXME: Once the rest of Terraform Core is ready to support expanding
// modules, evaluate the "count" and "for_each" arguments here in a
// similar way as in EvalWriteResourceState.
eachMode := states.NoEach
expander := ctx.InstanceExpander()
if n.ModuleCall == nil {
// FIXME: should we have gotten here with no module call?
log.Printf("[TRACE] evalPrepareModuleExpansion: %s is a singleton", n.CallerAddr.Child(n.Call.Name, addrs.NoKey))
ctx.InstanceExpander().SetModuleSingle(n.CallerAddr, n.Call)
expander.SetModuleSingle(n.CallerAddr, n.Call)
return nil, nil
}
count, countDiags := evaluateResourceCountExpression(n.ModuleCall.Count, ctx)
if countDiags.HasErrors() {
return nil, countDiags.Err()
}
if count >= 0 { // -1 signals "count not set"
eachMode = states.EachList
}
forEach, forEachDiags := evaluateResourceForEachExpression(n.ModuleCall.ForEach, ctx)
if forEachDiags.HasErrors() {
return nil, forEachDiags.Err()
}
if forEach != nil {
eachMode = states.EachMap
}
switch eachMode {
case states.EachList:
expander.SetModuleCount(ctx.Path(), n.Call, count)
case states.EachMap:
expander.SetModuleForEach(ctx.Path(), n.Call, forEach)
default:
expander.SetModuleSingle(n.CallerAddr, n.Call)
}
return nil, nil
}

View File

@ -39,12 +39,12 @@ func (n *NodeModuleRemoved) EvalTree() EvalNode {
}
}
func (n *NodeModuleRemoved) ReferenceOutside() (selfPath, referencePath addrs.ModuleInstance) {
func (n *NodeModuleRemoved) ReferenceOutside() (selfPath, referencePath addrs.Module) {
// Our "References" implementation indicates that this node depends on
// the call to the module it represents, which implicitly depends on
// everything inside the module. That reference must therefore be
// interpreted in terms of our parent module.
return n.Addr, n.Addr.Parent()
return n.Addr.Module(), n.Addr.Parent().Module()
}
func (n *NodeModuleRemoved) References() []*addrs.Reference {

View File

@ -1,6 +1,8 @@
package terraform
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
@ -9,6 +11,101 @@ import (
"github.com/zclconf/go-cty/cty"
)
// NodePlannableModuleVariable is the placeholder for an variable that has not yet had
// its module path expanded.
type NodePlannableModuleVariable struct {
Addr addrs.InputVariable
Module addrs.Module
Config *configs.Variable
Expr hcl.Expression
}
var (
_ GraphNodeDynamicExpandable = (*NodePlannableModuleVariable)(nil)
_ GraphNodeReferenceOutside = (*NodePlannableModuleVariable)(nil)
_ GraphNodeReferenceable = (*NodePlannableModuleVariable)(nil)
_ GraphNodeReferencer = (*NodePlannableModuleVariable)(nil)
_ GraphNodeSubPath = (*NodePlannableModuleVariable)(nil)
_ RemovableIfNotTargeted = (*NodePlannableModuleVariable)(nil)
)
func (n *NodePlannableModuleVariable) DynamicExpand(ctx EvalContext) (*Graph, error) {
var g Graph
expander := ctx.InstanceExpander()
for _, module := range expander.ExpandModule(ctx.Path().Module()) {
o := &NodeApplyableModuleVariable{
Addr: n.Addr.Absolute(module),
Config: n.Config,
Expr: n.Expr,
}
g.Add(o)
}
return &g, nil
}
func (n *NodePlannableModuleVariable) Name() string {
return fmt.Sprintf("%s.%s", n.Module, n.Addr.String())
}
// GraphNodeSubPath
func (n *NodePlannableModuleVariable) Path() addrs.ModuleInstance {
// Return an UnkeyedInstanceShim as our placeholder,
// given that modules will be unexpanded at this point in the walk
return n.Module.UnkeyedInstanceShim()
}
// GraphNodeReferencer
func (n *NodePlannableModuleVariable) References() []*addrs.Reference {
// If we have no value expression, we cannot depend on anything.
if n.Expr == nil {
return nil
}
// Variables in the root don't depend on anything, because their values
// are gathered prior to the graph walk and recorded in the context.
if len(n.Module) == 0 {
return nil
}
// Otherwise, we depend on anything referenced by our value expression.
// We ignore diagnostics here under the assumption that we'll re-eval
// all these things later and catch them then; for our purposes here,
// we only care about valid references.
//
// Due to our GraphNodeReferenceOutside implementation, the addresses
// returned by this function are interpreted in the _parent_ module from
// where our associated variable was declared, which is correct because
// our value expression is assigned within a "module" block in the parent
// module.
refs, _ := lang.ReferencesInExpr(n.Expr)
return refs
}
// GraphNodeReferenceOutside implementation
func (n *NodePlannableModuleVariable) ReferenceOutside() (selfPath, referencePath addrs.Module) {
return n.Module, n.Module.Parent()
}
// GraphNodeReferenceable
func (n *NodePlannableModuleVariable) ReferenceableAddrs() []addrs.Referenceable {
// FIXME: References for module variables probably need to be thought out a bit more
// Otherwise, we can reference the output via the address itself, or the
// module call
_, call := n.Module.Call()
return []addrs.Referenceable{n.Addr, call}
}
// RemovableIfNotTargeted
func (n *NodePlannableModuleVariable) RemoveIfNotTargeted() bool {
return true
}
// GraphNodeTargetDownstream
func (n *NodePlannableModuleVariable) TargetDownstream(targetedDeps, untargetedDeps dag.Set) bool {
return true
}
// NodeApplyableModuleVariable represents a module variable input during
// the apply step.
type NodeApplyableModuleVariable struct {
@ -48,15 +145,15 @@ func (n *NodeApplyableModuleVariable) RemoveIfNotTargeted() bool {
}
// GraphNodeReferenceOutside implementation
func (n *NodeApplyableModuleVariable) ReferenceOutside() (selfPath, referencePath addrs.ModuleInstance) {
func (n *NodeApplyableModuleVariable) ReferenceOutside() (selfPath, referencePath addrs.Module) {
// Module input variables have their value expressions defined in the
// context of their calling (parent) module, and so references from
// a node of this type should be resolved in the parent module instance.
referencePath = n.Addr.Module.Parent()
referencePath = n.Addr.Module.Parent().Module()
// Input variables are _referenced_ from their own module, though.
selfPath = n.Addr.Module
selfPath = n.Addr.Module.Module()
return // uses named return values
}

View File

@ -9,6 +9,98 @@ import (
"github.com/hashicorp/terraform/lang"
)
// NodePlannableOutput is the placeholder for an output that has not yet had
// its module path expanded.
type NodePlannableOutput struct {
Addr addrs.OutputValue
Module addrs.Module
Config *configs.Output
}
var (
_ GraphNodeSubPath = (*NodePlannableOutput)(nil)
_ RemovableIfNotTargeted = (*NodePlannableOutput)(nil)
_ GraphNodeReferenceable = (*NodePlannableOutput)(nil)
//_ GraphNodeEvalable = (*NodePlannableOutput)(nil)
_ GraphNodeReferencer = (*NodePlannableOutput)(nil)
_ GraphNodeDynamicExpandable = (*NodePlannableOutput)(nil)
)
func (n *NodePlannableOutput) DynamicExpand(ctx EvalContext) (*Graph, error) {
var g Graph
expander := ctx.InstanceExpander()
for _, module := range expander.ExpandModule(ctx.Path().Module()) {
o := &NodeApplyableOutput{
Addr: n.Addr.Absolute(module),
Config: n.Config,
}
// log.Printf("[TRACE] Expanding output: adding %s as %T", o.Addr.String(), o)
g.Add(o)
}
return &g, nil
}
func (n *NodePlannableOutput) Name() string {
return n.Addr.Absolute(n.Module.UnkeyedInstanceShim()).String()
}
// GraphNodeSubPath
func (n *NodePlannableOutput) Path() addrs.ModuleInstance {
// Return an UnkeyedInstanceShim as our placeholder,
// given that modules will be unexpanded at this point in the walk
return n.Module.UnkeyedInstanceShim()
}
// GraphNodeReferenceable
func (n *NodePlannableOutput) ReferenceableAddrs() []addrs.Referenceable {
// An output in the root module can't be referenced at all.
if n.Module.IsRoot() {
return nil
}
// the output is referenced through the module call, and via the
// module itself.
_, call := n.Module.Call()
// FIXME: make something like ModuleCallOutput for this type of reference
// that doesn't need an instance shim
callOutput := addrs.ModuleCallOutput{
Call: call.Instance(addrs.NoKey),
Name: n.Addr.Name,
}
// Otherwise, we can reference the output via the
// module call itself
return []addrs.Referenceable{call, callOutput}
}
// GraphNodeReferenceOutside implementation
func (n *NodePlannableOutput) ReferenceOutside() (selfPath, referencePath addrs.Module) {
// Output values have their expressions resolved in the context of the
// module where they are defined.
referencePath = n.Module
// ...but they are referenced in the context of their calling module.
selfPath = referencePath.Parent()
return // uses named return values
}
// GraphNodeReferencer
func (n *NodePlannableOutput) References() []*addrs.Reference {
return appendResourceDestroyReferences(referencesForOutput(n.Config))
}
// RemovableIfNotTargeted
func (n *NodePlannableOutput) RemoveIfNotTargeted() bool {
return true
}
// GraphNodeTargetDownstream
func (n *NodePlannableOutput) TargetDownstream(targetedDeps, untargetedDeps dag.Set) bool {
return true
}
// NodeApplyableOutput represents an output that is "applyable":
// it is ready to be applied.
type NodeApplyableOutput struct {
@ -51,21 +143,19 @@ func (n *NodeApplyableOutput) TargetDownstream(targetedDeps, untargetedDeps dag.
return true
}
func referenceOutsideForOutput(addr addrs.AbsOutputValue) (selfPath, referencePath addrs.ModuleInstance) {
func referenceOutsideForOutput(addr addrs.AbsOutputValue) (selfPath, referencePath addrs.Module) {
// Output values have their expressions resolved in the context of the
// module where they are defined.
referencePath = addr.Module
referencePath = addr.Module.Module()
// ...but they are referenced in the context of their calling module.
selfPath = addr.Module.Parent()
selfPath = addr.Module.Parent().Module()
return // uses named return values
}
// GraphNodeReferenceOutside implementation
func (n *NodeApplyableOutput) ReferenceOutside() (selfPath, referencePath addrs.ModuleInstance) {
func (n *NodeApplyableOutput) ReferenceOutside() (selfPath, referencePath addrs.Module) {
return referenceOutsideForOutput(n.Addr)
}
@ -83,8 +173,8 @@ func referenceableAddrsForOutput(addr addrs.AbsOutputValue) []addrs.Referenceabl
// was declared.
_, outp := addr.ModuleCallOutput()
_, call := addr.Module.CallInstance()
return []addrs.Referenceable{outp, call}
return []addrs.Referenceable{outp, call}
}
// GraphNodeReferenceable
@ -141,7 +231,8 @@ func (n *NodeApplyableOutput) DotNode(name string, opts *dag.DotOpts) *dag.DotNo
// NodeDestroyableOutput represents an output that is "destroybale":
// its application will remove the output from the state.
type NodeDestroyableOutput struct {
Addr addrs.AbsOutputValue
Addr addrs.OutputValue
Module addrs.Module
Config *configs.Output // Config is the output in the config
}
@ -160,7 +251,7 @@ func (n *NodeDestroyableOutput) Name() string {
// GraphNodeSubPath
func (n *NodeDestroyableOutput) Path() addrs.ModuleInstance {
return n.Addr.Module
return n.Module.UnkeyedInstanceShim()
}
// RemovableIfNotTargeted
@ -184,7 +275,7 @@ func (n *NodeDestroyableOutput) References() []*addrs.Reference {
// GraphNodeEvalable
func (n *NodeDestroyableOutput) EvalTree() EvalNode {
return &EvalDeleteOutput{
Addr: n.Addr.OutputValue,
Addr: n.Addr,
}
}

View File

@ -23,7 +23,7 @@ func (n *NodeOutputOrphan) Name() string {
}
// GraphNodeReferenceOutside implementation
func (n *NodeOutputOrphan) ReferenceOutside() (selfPath, referencePath addrs.ModuleInstance) {
func (n *NodeOutputOrphan) ReferenceOutside() (selfPath, referencePath addrs.Module) {
return referenceOutsideForOutput(n.Addr)
}

View File

@ -43,6 +43,10 @@ type GraphNodeResourceInstance interface {
// operations. It registers all the interfaces for a resource that common
// across multiple operation types.
type NodeAbstractResource struct {
//FIXME: AbstractResources are no longer absolute, because modules are not expanded.
// Addr addrs.Resource
// Module addrs.Module
Addr addrs.AbsResource // Addr is the address for this resource
// The fields below will be automatically set using the Attach

View File

@ -14,7 +14,7 @@ import (
"github.com/hashicorp/terraform/tfdiags"
)
// NodeRefreshableManagedResource represents a resource that is expanabled into
// NodeRefreshableManagedResource represents a resource that is expandable into
// NodeRefreshableManagedResourceInstance. Resource count orphans are also added.
type NodeRefreshableManagedResource struct {
*NodeAbstractResource
@ -61,13 +61,16 @@ func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph,
// Inform our instance expander about our expansion results above,
// and then use it to calculate the instance addresses we'll expand for.
expander := ctx.InstanceExpander()
for _, module := range expander.ExpandModule(ctx.Path().Module()) {
switch {
case count >= 0:
expander.SetResourceCount(ctx.Path(), n.ResourceAddr().Resource, count)
expander.SetResourceCount(module, n.ResourceAddr().Resource, count)
case forEachMap != nil:
expander.SetResourceForEach(ctx.Path(), n.ResourceAddr().Resource, forEachMap)
expander.SetResourceForEach(module, n.ResourceAddr().Resource, forEachMap)
default:
expander.SetResourceSingle(ctx.Path(), n.ResourceAddr().Resource)
expander.SetResourceSingle(module, n.ResourceAddr().Resource)
}
}
instanceAddrs := expander.ExpandResource(ctx.Path().Module(), n.ResourceAddr().Resource)

View File

@ -0,0 +1,12 @@
variable "foo" {}
variable "bar" {}
resource "aws_instance" "foo" {
count = 2
num = var.foo
bar = "baz" #var.bar
}
output "out" {
value = aws_instance.foo[0].id
}

View File

@ -0,0 +1,28 @@
locals {
val = 2
bar = "baz"
}
variable "myvar" {
default = "baz"
}
module "child" {
count = local.val
foo = 2
bar = var.myvar
source = "./child"
}
output "out" {
value = module.child[*].out
}
resource "aws_instance" "foo" {
num = 2
}
resource "aws_instance" "bar" {
foo = "${aws_instance.foo.num}"
}

View File

@ -43,10 +43,12 @@ func (t *ModuleExpansionTransformer) transform(g *Graph, c *configs.Config, pare
fullAddr := c.Path.UnkeyedInstanceShim()
callerAddr, callAddr := fullAddr.Call()
modulecall := c.Parent.Module.ModuleCalls["child"]
v := &nodeExpandModule{
CallerAddr: callerAddr,
Call: callAddr,
Config: c.Module,
ModuleCall: modulecall,
}
g.Add(v)
log.Printf("[TRACE] ModuleExpansionTransformer: Added %s as %T", fullAddr, v)

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
@ -110,15 +111,17 @@ func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, c *configs
}
}
// For now we treat all module variables as "applyable", even though
// such nodes are valid to use on other walks too. We may specialize
// this in future if we find reasons to employ different behaviors
// in different scenarios.
node := &NodeApplyableModuleVariable{
Addr: path.InputVariable(v.Name),
// Add a plannable node, as the variable may expand
// during module expansion
node := &NodePlannableModuleVariable{
Addr: addrs.InputVariable{
Name: v.Name,
},
Module: c.Path,
Config: v,
Expr: expr,
}
g.Add(node)
}

View File

@ -3,6 +3,7 @@ package terraform
import (
"log"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/dag"
)
@ -36,20 +37,16 @@ func (t *OutputTransformer) transform(g *Graph, c *configs.Config) error {
}
}
// Our addressing system distinguishes between modules and module instances,
// but we're not yet ready to make that distinction here (since we don't
// support "count"/"for_each" on modules) and so we just do a naive
// transform of the module path into a module instance path, assuming that
// no keys are in use. This should be removed when "count" and "for_each"
// are implemented for modules.
path := c.Path.UnkeyedInstanceShim()
// Add plannable outputs to the graph, which will be dynamically expanded
// into NodeApplyableOutputs to reflect possible expansion
// through the presence of "count" or "for_each" on the modules.
for _, o := range c.Module.Outputs {
addr := path.OutputValue(o.Name)
node := &NodeApplyableOutput{
Addr: addr,
node := &NodePlannableOutput{
Addr: addrs.OutputValue{Name: o.Name},
Module: c.Path,
Config: o,
}
log.Printf("[TRACE] OutputTransformer: adding %s as %T", o.Name, node)
g.Add(node)
}
@ -70,7 +67,7 @@ func (t *DestroyOutputTransformer) Transform(g *Graph) error {
}
for _, v := range g.Vertices() {
output, ok := v.(*NodeApplyableOutput)
output, ok := v.(*NodePlannableOutput)
if !ok {
continue
}
@ -78,6 +75,7 @@ func (t *DestroyOutputTransformer) Transform(g *Graph) error {
// create the destroy node for this output
node := &NodeDestroyableOutput{
Addr: output.Addr,
Module: output.Module,
Config: output.Config,
}

View File

@ -66,7 +66,7 @@ type GraphNodeAttachDependencies interface {
type GraphNodeReferenceOutside interface {
// ReferenceOutside returns a path in which any references from this node
// are resolved.
ReferenceOutside() (selfPath, referencePath addrs.ModuleInstance)
ReferenceOutside() (selfPath, referencePath addrs.Module)
}
// ReferenceTransformer is a GraphTransformer that connects all the
@ -85,8 +85,7 @@ func (t *ReferenceTransformer) Transform(g *Graph) error {
// use their own state.
continue
}
parents, _ := m.References(v)
parents := m.References(v)
parentsDbg := make([]string, len(parents))
for i, v := range parents {
parentsDbg[i] = dag.VertexName(v)
@ -196,7 +195,12 @@ func (t *PruneUnusedValuesTransformer) Transform(g *Graph) error {
if v.Addr.Module.IsRoot() && !t.Destroy {
continue
}
case *NodeLocal, *NodeApplyableModuleVariable:
case *NodePlannableOutput:
// Have similar guardrails for plannable outputs as applyable above
if v.Module.IsRoot() && !t.Destroy {
continue
}
case *NodeLocal, *NodeApplyableModuleVariable, *NodePlannableModuleVariable:
// OK
default:
// We're only concerned with variables, locals and outputs
@ -239,27 +243,20 @@ type ReferenceMap struct {
// A particular reference key might actually identify multiple vertices,
// e.g. in situations where one object is contained inside another.
vertices map[string][]dag.Vertex
// edges is a map whose keys are a subset of the internal reference keys
// from "vertices", and whose values are the nodes that refer to each
// key. The values in this map are the referrers, while values in
// "verticies" are the referents. The keys in both cases are referents.
edges map[string][]dag.Vertex
}
// References returns the set of vertices that the given vertex refers to,
// and any referenced addresses that do not have corresponding vertices.
func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []addrs.Referenceable) {
func (m *ReferenceMap) References(v dag.Vertex) []dag.Vertex {
rn, ok := v.(GraphNodeReferencer)
if !ok {
return nil, nil
return nil
}
if _, ok := v.(GraphNodeSubPath); !ok {
return nil, nil
return nil
}
var matches []dag.Vertex
var missing []addrs.Referenceable
for _, ref := range rn.References() {
subject := ref.Subject
@ -278,7 +275,6 @@ func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []addrs.Reference
}
key = m.referenceMapKey(v, subject)
}
vertices := m.vertices[key]
for _, rv := range vertices {
// don't include self-references
@ -287,47 +283,6 @@ func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []addrs.Reference
}
matches = append(matches, rv)
}
if len(vertices) == 0 {
missing = append(missing, ref.Subject)
}
}
return matches, missing
}
// Referrers returns the set of vertices that refer to the given vertex.
func (m *ReferenceMap) Referrers(v dag.Vertex) []dag.Vertex {
rn, ok := v.(GraphNodeReferenceable)
if !ok {
return nil
}
sp, ok := v.(GraphNodeSubPath)
if !ok {
return nil
}
var matches []dag.Vertex
for _, addr := range rn.ReferenceableAddrs() {
key := m.mapKey(sp.Path(), addr)
referrers, ok := m.edges[key]
if !ok {
continue
}
// If the referrer set includes our own given vertex then we skip,
// since we don't want to return self-references.
selfRef := false
for _, p := range referrers {
if p == v {
selfRef = true
break
}
}
if selfRef {
continue
}
matches = append(matches, referrers...)
}
return matches
@ -354,7 +309,7 @@ func (m *ReferenceMap) vertexReferenceablePath(v dag.Vertex) addrs.ModuleInstanc
// Vertex is referenced from a different module than where it was
// declared.
path, _ := outside.ReferenceOutside()
return path
return path.UnkeyedInstanceShim()
}
// Vertex is referenced from the same module as where it was declared.
@ -373,12 +328,11 @@ func vertexReferencePath(referrer dag.Vertex) addrs.ModuleInstance {
panic(fmt.Errorf("vertexReferencePath on vertex type %T which doesn't implement GraphNodeSubPath", sp))
}
var path addrs.ModuleInstance
if outside, ok := referrer.(GraphNodeReferenceOutside); ok {
// Vertex makes references to objects in a different module than where
// it was declared.
_, path = outside.ReferenceOutside()
return path
_, path := outside.ReferenceOutside()
return path.UnkeyedInstanceShim()
}
// Vertex makes references to objects in the same module as where it
@ -443,34 +397,7 @@ func NewReferenceMap(vs []dag.Vertex) *ReferenceMap {
}
}
// Build the lookup table for referenced by
edges := make(map[string][]dag.Vertex)
for _, v := range vs {
_, ok := v.(GraphNodeSubPath)
if !ok {
// Only nodes with paths can participate in a reference map.
continue
}
rn, ok := v.(GraphNodeReferencer)
if !ok {
// We're only looking for referenceable nodes
continue
}
// Go through and cache them
for _, ref := range rn.References() {
if ref.Subject == nil {
// Should never happen
panic(fmt.Sprintf("%T.References returned reference with nil subject", rn))
}
key := m.referenceMapKey(v, ref.Subject)
edges[key] = append(edges[key], v)
}
}
m.vertices = vertices
m.edges = edges
return &m
}
@ -503,6 +430,8 @@ func appendResourceDestroyReferences(refs []*addrs.Reference) []*addrs.Reference
newRef.Subject = tr.Phase(addrs.ResourceInstancePhaseDestroy)
refs = append(refs, &newRef)
}
// FIXME: Using this method in module expansion references,
// May want to refactor this method beyond resources
}
return refs
}

View File

@ -113,55 +113,7 @@ func TestReferenceMapReferences(t *testing.T) {
for tn, tc := range cases {
t.Run(tn, func(t *testing.T) {
rm := NewReferenceMap(tc.Nodes)
result, _ := rm.References(tc.Check)
var resultStr []string
for _, v := range result {
resultStr = append(resultStr, dag.VertexName(v))
}
sort.Strings(resultStr)
sort.Strings(tc.Result)
if !reflect.DeepEqual(resultStr, tc.Result) {
t.Fatalf("bad: %#v", resultStr)
}
})
}
}
func TestReferenceMapReferencedBy(t *testing.T) {
cases := map[string]struct {
Nodes []dag.Vertex
Check dag.Vertex
Result []string
}{
"simple": {
Nodes: []dag.Vertex{
&graphNodeRefChildTest{
NameValue: "A",
Refs: []string{"A"},
},
&graphNodeRefChildTest{
NameValue: "B",
Refs: []string{"A"},
},
&graphNodeRefChildTest{
NameValue: "C",
Refs: []string{"B"},
},
},
Check: &graphNodeRefParentTest{
NameValue: "foo",
Names: []string{"A"},
},
Result: []string{"A", "B"},
},
}
for tn, tc := range cases {
t.Run(tn, func(t *testing.T) {
rm := NewReferenceMap(tc.Nodes)
result := rm.Referrers(tc.Check)
result := rm.References(tc.Check)
var resultStr []string
for _, v := range result {