Merge pull request #9973 from hashicorp/f-new-plan
terraform: new plan graph
This commit is contained in:
commit
66ccc19d94
|
@ -488,28 +488,79 @@ func (c *Context) Plan() (*Plan, error) {
|
|||
c.diffLock.Unlock()
|
||||
|
||||
// Used throughout below
|
||||
X_newApply := experiment.Enabled(experiment.X_newApply)
|
||||
X_newDestroy := experiment.Enabled(experiment.X_newDestroy)
|
||||
newGraphEnabled := (c.destroy && X_newDestroy) || (!c.destroy && X_newApply)
|
||||
|
||||
// Build the graph. We have a branch here since for the pure-destroy
|
||||
// plan (c.destroy) we use a much simpler graph builder that simply
|
||||
// walks the state and reverses edges.
|
||||
var graph *Graph
|
||||
var err error
|
||||
if c.destroy && X_newDestroy {
|
||||
graph, err = (&DestroyPlanGraphBuilder{
|
||||
Module: c.module,
|
||||
State: c.state,
|
||||
Targets: c.targets,
|
||||
}).Build(RootModulePath)
|
||||
} else {
|
||||
graph, err = c.Graph(&ContextGraphOpts{Validate: true})
|
||||
// Build the original graph. This is before the new graph builders
|
||||
// coming in 0.8. We do this for shadow graphing.
|
||||
oldGraph, err := c.Graph(&ContextGraphOpts{Validate: true})
|
||||
if err != nil && newGraphEnabled {
|
||||
// If we had an error graphing but we're using the new graph,
|
||||
// just set it to nil and let it go. There are some features that
|
||||
// may work with the new graph that don't with the old.
|
||||
oldGraph = nil
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build the new graph. We do this no matter wht so we can shadow it.
|
||||
var newGraph *Graph
|
||||
err = nil
|
||||
if c.destroy {
|
||||
newGraph, err = (&DestroyPlanGraphBuilder{
|
||||
Module: c.module,
|
||||
State: c.state,
|
||||
Targets: c.targets,
|
||||
}).Build(RootModulePath)
|
||||
} else {
|
||||
newGraph, err = (&PlanGraphBuilder{
|
||||
Module: c.module,
|
||||
State: c.state,
|
||||
Providers: c.components.ResourceProviders(),
|
||||
Targets: c.targets,
|
||||
}).Build(RootModulePath)
|
||||
}
|
||||
if err != nil && !newGraphEnabled {
|
||||
// If we had an error graphing but we're not using this graph, just
|
||||
// set it to nil and record it as a shadow error.
|
||||
c.shadowErr = multierror.Append(c.shadowErr, fmt.Errorf(
|
||||
"Error building new graph: %s", err))
|
||||
|
||||
newGraph = nil
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Determine what is the real and what is the shadow. The logic here
|
||||
// is straightforward though the if statements are not:
|
||||
//
|
||||
// * If the new graph, shadow with experiment in both because the
|
||||
// experiment has less nodes so the original can't shadow.
|
||||
// * If not the new graph, shadow with the experiment
|
||||
//
|
||||
real := oldGraph
|
||||
shadow := newGraph
|
||||
if newGraphEnabled {
|
||||
log.Printf("[WARN] terraform: real graph is experiment, shadow is experiment")
|
||||
real = shadow
|
||||
} else {
|
||||
log.Printf("[WARN] terraform: real graph is original, shadow is experiment")
|
||||
}
|
||||
|
||||
// Special case here: if we're using destroy don't shadow it because
|
||||
// the new destroy graph behaves a bit differently on purpose by not
|
||||
// setting the module destroy flag.
|
||||
if c.destroy && !newGraphEnabled {
|
||||
shadow = nil
|
||||
}
|
||||
|
||||
// Do the walk
|
||||
walker, err := c.walk(graph, graph, operation)
|
||||
walker, err := c.walk(real, shadow, operation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -528,7 +579,7 @@ func (c *Context) Plan() (*Plan, error) {
|
|||
|
||||
// We don't do the reverification during the new destroy plan because
|
||||
// it will use a different apply process.
|
||||
if !(c.destroy && X_newDestroy) {
|
||||
if !newGraphEnabled {
|
||||
// Now that we have a diff, we can build the exact graph that Apply will use
|
||||
// and catch any possible cycles during the Plan phase.
|
||||
if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil {
|
||||
|
@ -814,7 +865,12 @@ func (c *Context) walk(
|
|||
//
|
||||
// This must be done BEFORE appending shadowWalkErr since the
|
||||
// shadowWalkErr may include expected errors.
|
||||
if c.shadowErr != nil && contextFailOnShadowError {
|
||||
//
|
||||
// We only do this if we don't have a real error. In the case of
|
||||
// a real error, we can't guarantee what nodes were and weren't
|
||||
// traversed in parallel scenarios so we can't guarantee no
|
||||
// shadow errors.
|
||||
if c.shadowErr != nil && contextFailOnShadowError && realErr == nil {
|
||||
panic(multierror.Prefix(c.shadowErr, "shadow graph:"))
|
||||
}
|
||||
|
||||
|
|
|
@ -3488,6 +3488,8 @@ func TestContext2Apply_destroyOrder(t *testing.T) {
|
|||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
t.Logf("State 1: %s", state)
|
||||
|
||||
// Next, plan and apply config-less to force a destroy with "apply"
|
||||
h.Active = true
|
||||
ctx = testContext2(t, &ContextOpts{
|
||||
|
@ -3697,8 +3699,10 @@ func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) {
|
|||
})
|
||||
|
||||
// First plan and apply a create operation
|
||||
if _, err := ctx.Plan(); err != nil {
|
||||
if p, err := ctx.Plan(); err != nil {
|
||||
t.Fatalf("plan err: %s", err)
|
||||
} else {
|
||||
t.Logf("Step 1 plan: %s", p)
|
||||
}
|
||||
|
||||
state, err = ctx.Apply()
|
||||
|
@ -3732,6 +3736,8 @@ func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) {
|
|||
t.Fatalf("destroy plan err: %s", err)
|
||||
}
|
||||
|
||||
t.Logf("Step 2 plan: %s", plan)
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := WritePlan(plan, &buf); err != nil {
|
||||
t.Fatalf("plan write err: %s", err)
|
||||
|
@ -3755,6 +3761,8 @@ func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("destroy apply err: %s", err)
|
||||
}
|
||||
|
||||
t.Logf("Step 2 state: %s", state)
|
||||
}
|
||||
|
||||
//Test that things were destroyed
|
||||
|
@ -3765,7 +3773,7 @@ module.child:
|
|||
<no state>
|
||||
`)
|
||||
if actual != expected {
|
||||
t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual)
|
||||
t.Fatalf("expected:\n\n%s\n\nactual:\n\n%s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5120,8 +5128,10 @@ func TestContext2Apply_targetedModuleDep(t *testing.T) {
|
|||
Targets: []string{"aws_instance.foo"},
|
||||
})
|
||||
|
||||
if _, err := ctx.Plan(); err != nil {
|
||||
if p, err := ctx.Plan(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
} else {
|
||||
t.Logf("Diff: %s", p)
|
||||
}
|
||||
|
||||
state, err := ctx.Apply()
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestContext2Plan(t *testing.T) {
|
||||
func TestContext2Plan_basic(t *testing.T) {
|
||||
m := testModule(t, "plan-good")
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
|
@ -626,7 +626,7 @@ func TestContext2Plan_moduleVar(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_moduleVarWrongType(t *testing.T) {
|
||||
func TestContext2Plan_moduleVarWrongTypeBasic(t *testing.T) {
|
||||
m := testModule(t, "plan-module-wrong-var-type")
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
|
|
|
@ -32,6 +32,30 @@ type Diff struct {
|
|||
Modules []*ModuleDiff
|
||||
}
|
||||
|
||||
// Prune cleans out unused structures in the diff without affecting
|
||||
// the behavior of the diff at all.
|
||||
//
|
||||
// This is not safe to call concurrently. This is safe to call on a
|
||||
// nil Diff.
|
||||
func (d *Diff) Prune() {
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Prune all empty modules
|
||||
newModules := make([]*ModuleDiff, 0, len(d.Modules))
|
||||
for _, m := range d.Modules {
|
||||
// If the module isn't empty, we keep it
|
||||
if !m.Empty() {
|
||||
newModules = append(newModules, m)
|
||||
}
|
||||
}
|
||||
if len(newModules) == 0 {
|
||||
newModules = nil
|
||||
}
|
||||
d.Modules = newModules
|
||||
}
|
||||
|
||||
// AddModule adds the module with the given path to the diff.
|
||||
//
|
||||
// This should be the preferred method to add module diffs since it
|
||||
|
@ -212,6 +236,10 @@ func (d *ModuleDiff) ChangeType() DiffChangeType {
|
|||
|
||||
// Empty returns true if the diff has no changes within this module.
|
||||
func (d *ModuleDiff) Empty() bool {
|
||||
if d.Destroy {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(d.Resources) == 0 {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -103,6 +103,53 @@ func TestDiffEqual(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDiffPrune(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
D1, D2 *Diff
|
||||
}{
|
||||
"nil": {
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
"empty": {
|
||||
new(Diff),
|
||||
new(Diff),
|
||||
},
|
||||
|
||||
"empty module": {
|
||||
&Diff{
|
||||
Modules: []*ModuleDiff{
|
||||
&ModuleDiff{Path: []string{"root", "foo"}},
|
||||
},
|
||||
},
|
||||
&Diff{},
|
||||
},
|
||||
|
||||
"destroy module": {
|
||||
&Diff{
|
||||
Modules: []*ModuleDiff{
|
||||
&ModuleDiff{Path: []string{"root", "foo"}, Destroy: true},
|
||||
},
|
||||
},
|
||||
&Diff{
|
||||
Modules: []*ModuleDiff{
|
||||
&ModuleDiff{Path: []string{"root", "foo"}, Destroy: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc.D1.Prune()
|
||||
if !tc.D1.Equal(tc.D2) {
|
||||
t.Fatalf("bad:\n\n%#v\n\n%#v", tc.D1, tc.D2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleDiff_ChangeType(t *testing.T) {
|
||||
cases := []struct {
|
||||
Diff *ModuleDiff
|
||||
|
|
|
@ -129,7 +129,7 @@ func (b *BuiltinGraphBuilder) Build(path []string) (*Graph, error) {
|
|||
func (b *BuiltinGraphBuilder) Steps(path []string) []GraphTransformer {
|
||||
steps := []GraphTransformer{
|
||||
// Create all our resources from the configuration and state
|
||||
&ConfigTransformer{Module: b.Root},
|
||||
&ConfigTransformerOld{Module: b.Root},
|
||||
&OrphanTransformer{
|
||||
State: b.State,
|
||||
Module: b.Root,
|
||||
|
|
|
@ -38,7 +38,7 @@ func (b *ImportGraphBuilder) Steps() []GraphTransformer {
|
|||
|
||||
steps := []GraphTransformer{
|
||||
// Create all our resources from the configuration and state
|
||||
&ConfigTransformer{Module: mod},
|
||||
&ConfigTransformerOld{Module: mod},
|
||||
|
||||
// Add the import steps
|
||||
&ImportStateTransformer{Targets: b.ImportTargets},
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
// PlanGraphBuilder implements GraphBuilder and is responsible for building
|
||||
// a graph for planning (creating a Terraform Diff).
|
||||
//
|
||||
// The primary difference between this graph and others:
|
||||
//
|
||||
// * Based on the config since it represents the target state
|
||||
//
|
||||
// * Ignores lifecycle options since no lifecycle events occur here. This
|
||||
// simplifies the graph significantly since complex transforms such as
|
||||
// create-before-destroy can be completely ignored.
|
||||
//
|
||||
type PlanGraphBuilder struct {
|
||||
// Module is the root module for the graph to build.
|
||||
Module *module.Tree
|
||||
|
||||
// State is the current state
|
||||
State *State
|
||||
|
||||
// Providers is the list of providers supported.
|
||||
Providers []string
|
||||
|
||||
// Targets are resources to target
|
||||
Targets []string
|
||||
|
||||
// DisableReduce, if true, will not reduce the graph. Great for testing.
|
||||
DisableReduce bool
|
||||
}
|
||||
|
||||
// See GraphBuilder
|
||||
func (b *PlanGraphBuilder) Build(path []string) (*Graph, error) {
|
||||
return (&BasicGraphBuilder{
|
||||
Steps: b.Steps(),
|
||||
Validate: true,
|
||||
Name: "plan",
|
||||
}).Build(path)
|
||||
}
|
||||
|
||||
// See GraphBuilder
|
||||
func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
||||
// Custom factory for creating providers.
|
||||
providerFactory := func(name string, path []string) GraphNodeProvider {
|
||||
return &NodeApplyableProvider{
|
||||
NameValue: name,
|
||||
PathValue: path,
|
||||
}
|
||||
}
|
||||
|
||||
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
||||
return &NodePlannableResource{
|
||||
NodeAbstractResource: a,
|
||||
}
|
||||
}
|
||||
|
||||
concreteResourceOrphan := func(a *NodeAbstractResource) dag.Vertex {
|
||||
return &NodePlannableResourceOrphan{
|
||||
NodeAbstractResource: a,
|
||||
}
|
||||
}
|
||||
|
||||
steps := []GraphTransformer{
|
||||
// Creates all the resources represented in the config
|
||||
&ConfigTransformer{
|
||||
Concrete: concreteResource,
|
||||
Module: b.Module,
|
||||
},
|
||||
|
||||
// Add the outputs
|
||||
&OutputTransformer{Module: b.Module},
|
||||
|
||||
// Add orphan resources
|
||||
&OrphanResourceTransformer{
|
||||
Concrete: concreteResourceOrphan,
|
||||
State: b.State,
|
||||
Module: b.Module,
|
||||
},
|
||||
|
||||
// Attach the configuration to any resources
|
||||
&AttachResourceConfigTransformer{Module: b.Module},
|
||||
|
||||
// Attach the state
|
||||
&AttachStateTransformer{State: b.State},
|
||||
|
||||
// Connect so that the references are ready for targeting. We'll
|
||||
// have to connect again later for providers and so on.
|
||||
&ReferenceTransformer{},
|
||||
|
||||
// Target
|
||||
&TargetsTransformer{Targets: b.Targets},
|
||||
|
||||
// Create all the providers
|
||||
&MissingProviderTransformer{Providers: b.Providers, Factory: providerFactory},
|
||||
&ProviderTransformer{},
|
||||
&DisableProviderTransformer{},
|
||||
&ParentProviderTransformer{},
|
||||
&AttachProviderConfigTransformer{Module: b.Module},
|
||||
|
||||
// Add root variables
|
||||
&RootVariableTransformer{Module: b.Module},
|
||||
|
||||
// Add module variables
|
||||
&ModuleVariableTransformer{Module: b.Module},
|
||||
|
||||
// Connect references again to connect the providers, module variables,
|
||||
// etc. This is idempotent.
|
||||
&ReferenceTransformer{},
|
||||
|
||||
// Single root
|
||||
&RootTransformer{},
|
||||
}
|
||||
|
||||
if !b.DisableReduce {
|
||||
// Perform the transitive reduction to make our graph a bit
|
||||
// more sane if possible (it usually is possible).
|
||||
steps = append(steps, &TransitiveReductionTransformer{})
|
||||
}
|
||||
|
||||
return steps
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlanGraphBuilder_impl(t *testing.T) {
|
||||
var _ GraphBuilder = new(PlanGraphBuilder)
|
||||
}
|
||||
|
||||
func TestPlanGraphBuilder(t *testing.T) {
|
||||
b := &PlanGraphBuilder{
|
||||
Module: testModule(t, "graph-builder-plan-basic"),
|
||||
Providers: []string{"aws", "openstack"},
|
||||
DisableReduce: true,
|
||||
}
|
||||
|
||||
g, err := b.Build(RootModulePath)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(g.Path, RootModulePath) {
|
||||
t.Fatalf("bad: %#v", g.Path)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testPlanGraphBuilderStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
const testPlanGraphBuilderStr = `
|
||||
aws_instance.web
|
||||
aws_security_group.firewall
|
||||
provider.aws
|
||||
var.foo
|
||||
aws_load_balancer.weblb
|
||||
aws_instance.web
|
||||
provider.aws
|
||||
aws_security_group.firewall
|
||||
provider.aws
|
||||
openstack_floating_ip.random
|
||||
provider.openstack
|
||||
provider.aws
|
||||
openstack_floating_ip.random
|
||||
provider.openstack
|
||||
var.foo
|
||||
`
|
|
@ -26,7 +26,7 @@ func TestGraphNodeConfigModuleExpand(t *testing.T) {
|
|||
|
||||
g, err := node.Expand(&BasicGraphBuilder{
|
||||
Steps: []GraphTransformer{
|
||||
&ConfigTransformer{Module: mod},
|
||||
&ConfigTransformerOld{Module: mod},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -51,7 +51,7 @@ func TestGraphNodeConfigModuleExpandFlatten(t *testing.T) {
|
|||
|
||||
g, err := node.Expand(&BasicGraphBuilder{
|
||||
Steps: []GraphTransformer{
|
||||
&ConfigTransformer{Module: mod},
|
||||
&ConfigTransformerOld{Module: mod},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
@ -156,7 +156,7 @@ func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error)
|
|||
steps := make([]GraphTransformer, 0, 5)
|
||||
|
||||
// Expand counts.
|
||||
steps = append(steps, &ResourceCountTransformer{
|
||||
steps = append(steps, &ResourceCountTransformerOld{
|
||||
Resource: n.Resource,
|
||||
Destroy: n.Destroy,
|
||||
Targets: n.Targets,
|
||||
|
|
|
@ -2,6 +2,7 @@ package terraform
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
)
|
||||
|
@ -38,7 +39,12 @@ func (n *NodeApplyableOutput) References() []string {
|
|||
var result []string
|
||||
result = append(result, ReferencesFromConfig(n.Config.RawConfig)...)
|
||||
for _, v := range result {
|
||||
result = append(result, v+".destroy")
|
||||
split := strings.Split(v, "/")
|
||||
for i, s := range split {
|
||||
split[i] = s + ".destroy"
|
||||
}
|
||||
|
||||
result = append(result, strings.Join(split, "/"))
|
||||
}
|
||||
|
||||
return result
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
@ -43,11 +45,43 @@ func (n *NodeAbstractResource) Path() []string {
|
|||
|
||||
// GraphNodeReferenceable
|
||||
func (n *NodeAbstractResource) ReferenceableName() []string {
|
||||
if n.Config == nil {
|
||||
// We always are referenceable as "type.name" as long as
|
||||
// we have a config or address. Determine what that value is.
|
||||
var id string
|
||||
if n.Config != nil {
|
||||
id = n.Config.Id()
|
||||
} else if n.Addr != nil {
|
||||
addrCopy := n.Addr.Copy()
|
||||
addrCopy.Index = -1
|
||||
id = addrCopy.String()
|
||||
} else {
|
||||
// No way to determine our type.name, just return
|
||||
return nil
|
||||
}
|
||||
|
||||
return []string{n.Config.Id()}
|
||||
var result []string
|
||||
|
||||
// Always include our own ID. This is primarily for backwards
|
||||
// compatibility with states that didn't yet support the more
|
||||
// specific dep string.
|
||||
result = append(result, id)
|
||||
|
||||
// We represent all multi-access
|
||||
result = append(result, fmt.Sprintf("%s.*", id))
|
||||
|
||||
// We represent either a specific number, or all numbers
|
||||
suffix := "N"
|
||||
if n.Addr != nil {
|
||||
idx := n.Addr.Index
|
||||
if idx == -1 {
|
||||
idx = 0
|
||||
}
|
||||
|
||||
suffix = fmt.Sprintf("%d", idx)
|
||||
}
|
||||
result = append(result, fmt.Sprintf("%s.%s", id, suffix))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GraphNodeReferencer
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
// NodePlannableResource represents a resource that is "plannable":
|
||||
// it is ready to be planned in order to create a diff.
|
||||
type NodePlannableResource struct {
|
||||
*NodeAbstractResource
|
||||
|
||||
// Set by GraphNodeTargetable and used during DynamicExpand to
|
||||
// forward targets downwards.
|
||||
targets []ResourceAddress
|
||||
}
|
||||
|
||||
// GraphNodeTargetable
|
||||
func (n *NodePlannableResource) SetTargets(targets []ResourceAddress) {
|
||||
n.targets = targets
|
||||
}
|
||||
|
||||
// GraphNodeEvalable
|
||||
func (n *NodePlannableResource) EvalTree() EvalNode {
|
||||
return &EvalSequence{
|
||||
Nodes: []EvalNode{
|
||||
// The EvalTree for a plannable resource primarily involves
|
||||
// interpolating the count since it can contain variables
|
||||
// we only just received access to.
|
||||
//
|
||||
// With the interpolated count, we can then DynamicExpand
|
||||
// into the proper number of instances.
|
||||
&EvalInterpolate{Config: n.Config.RawCount},
|
||||
|
||||
&EvalCountFixZeroOneBoundary{Resource: n.Config},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GraphNodeDynamicExpandable
|
||||
func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
||||
// Grab the state which we read
|
||||
state, lock := ctx.State()
|
||||
lock.RLock()
|
||||
defer lock.RUnlock()
|
||||
|
||||
// Expand the resource count which must be available by now from EvalTree
|
||||
count, err := n.Config.Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The concrete resource factory we'll use
|
||||
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
||||
// Add the config and state since we don't do that via transforms
|
||||
a.Config = n.Config
|
||||
|
||||
return &NodePlannableResourceInstance{
|
||||
NodeAbstractResource: a,
|
||||
}
|
||||
}
|
||||
|
||||
// The concrete resource factory we'll use for oprhans
|
||||
concreteResourceOrphan := func(a *NodeAbstractResource) dag.Vertex {
|
||||
// Add the config and state since we don't do that via transforms
|
||||
a.Config = n.Config
|
||||
|
||||
return &NodePlannableResourceOrphan{
|
||||
NodeAbstractResource: a,
|
||||
}
|
||||
}
|
||||
|
||||
// Start creating the steps
|
||||
steps := []GraphTransformer{
|
||||
// Expand the count.
|
||||
&ResourceCountTransformer{
|
||||
Concrete: concreteResource,
|
||||
Count: count,
|
||||
Addr: n.ResourceAddr(),
|
||||
},
|
||||
|
||||
// Add the count orphans
|
||||
&OrphanResourceCountTransformer{
|
||||
Concrete: concreteResourceOrphan,
|
||||
Count: count,
|
||||
Addr: n.ResourceAddr(),
|
||||
State: state,
|
||||
},
|
||||
|
||||
// Attach the state
|
||||
&AttachStateTransformer{State: state},
|
||||
|
||||
// Targeting
|
||||
&TargetsTransformer{ParsedTargets: n.targets},
|
||||
|
||||
// Connect references so ordering is correct
|
||||
&ReferenceTransformer{},
|
||||
|
||||
// Make sure there is a single root
|
||||
&RootTransformer{},
|
||||
}
|
||||
|
||||
// Build the graph
|
||||
b := &BasicGraphBuilder{Steps: steps, Validate: true}
|
||||
return b.Build(ctx.Path())
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
)
|
||||
|
||||
// NodePlannableResourceInstance represents a _single_ resource
|
||||
// instance that is plannable. This means this represents a single
|
||||
// count index, for example.
|
||||
type NodePlannableResourceInstance struct {
|
||||
*NodeAbstractResource
|
||||
}
|
||||
|
||||
// GraphNodeEvalable
|
||||
func (n *NodePlannableResourceInstance) EvalTree() EvalNode {
|
||||
addr := n.NodeAbstractResource.Addr
|
||||
|
||||
// stateId is the ID to put into the state
|
||||
stateId := addr.stateId()
|
||||
if addr.Index > -1 {
|
||||
stateId = fmt.Sprintf("%s.%d", stateId, addr.Index)
|
||||
}
|
||||
|
||||
// Build the instance info. More of this will be populated during eval
|
||||
info := &InstanceInfo{
|
||||
Id: stateId,
|
||||
Type: addr.Type,
|
||||
ModulePath: normalizeModulePath(addr.Path),
|
||||
}
|
||||
|
||||
// Build the resource for eval
|
||||
resource := &Resource{
|
||||
Name: addr.Name,
|
||||
Type: addr.Type,
|
||||
CountIndex: addr.Index,
|
||||
}
|
||||
if resource.CountIndex < 0 {
|
||||
resource.CountIndex = 0
|
||||
}
|
||||
|
||||
// Determine the dependencies for the state. We use some older
|
||||
// code for this that we've used for a long time.
|
||||
var stateDeps []string
|
||||
{
|
||||
oldN := &graphNodeExpandedResource{Resource: n.Config}
|
||||
stateDeps = oldN.StateDependencies()
|
||||
}
|
||||
|
||||
// Eval info is different depending on what kind of resource this is
|
||||
switch n.Config.Mode {
|
||||
case config.ManagedResourceMode:
|
||||
return n.evalTreeManagedResource(
|
||||
stateId, info, resource, stateDeps,
|
||||
)
|
||||
case config.DataResourceMode:
|
||||
return n.evalTreeDataResource(
|
||||
stateId, info, resource, stateDeps)
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NodePlannableResourceInstance) evalTreeDataResource(
|
||||
stateId string, info *InstanceInfo,
|
||||
resource *Resource, stateDeps []string) EvalNode {
|
||||
var provider ResourceProvider
|
||||
var config *ResourceConfig
|
||||
var diff *InstanceDiff
|
||||
var state *InstanceState
|
||||
|
||||
return &EvalSequence{
|
||||
Nodes: []EvalNode{
|
||||
&EvalReadState{
|
||||
Name: stateId,
|
||||
Output: &state,
|
||||
},
|
||||
|
||||
// We need to re-interpolate the config here because some
|
||||
// of the attributes may have become computed during
|
||||
// earlier planning, due to other resources having
|
||||
// "requires new resource" diffs.
|
||||
&EvalInterpolate{
|
||||
Config: n.Config.RawConfig.Copy(),
|
||||
Resource: resource,
|
||||
Output: &config,
|
||||
},
|
||||
|
||||
&EvalIf{
|
||||
If: func(ctx EvalContext) (bool, error) {
|
||||
computed := config.ComputedKeys != nil && len(config.ComputedKeys) > 0
|
||||
|
||||
// If the configuration is complete and we
|
||||
// already have a state then we don't need to
|
||||
// do any further work during apply, because we
|
||||
// already populated the state during refresh.
|
||||
if !computed && state != nil {
|
||||
return true, EvalEarlyExitError{}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
},
|
||||
Then: EvalNoop{},
|
||||
},
|
||||
|
||||
&EvalGetProvider{
|
||||
Name: n.ProvidedBy()[0],
|
||||
Output: &provider,
|
||||
},
|
||||
|
||||
&EvalReadDataDiff{
|
||||
Info: info,
|
||||
Config: &config,
|
||||
Provider: &provider,
|
||||
Output: &diff,
|
||||
OutputState: &state,
|
||||
},
|
||||
|
||||
&EvalWriteState{
|
||||
Name: stateId,
|
||||
ResourceType: n.Config.Type,
|
||||
Provider: n.Config.Provider,
|
||||
Dependencies: stateDeps,
|
||||
State: &state,
|
||||
},
|
||||
|
||||
&EvalWriteDiff{
|
||||
Name: stateId,
|
||||
Diff: &diff,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NodePlannableResourceInstance) evalTreeManagedResource(
|
||||
stateId string, info *InstanceInfo,
|
||||
resource *Resource, stateDeps []string) EvalNode {
|
||||
// Declare a bunch of variables that are used for state during
|
||||
// evaluation. Most of this are written to by-address below.
|
||||
var provider ResourceProvider
|
||||
var diff *InstanceDiff
|
||||
var state *InstanceState
|
||||
var resourceConfig *ResourceConfig
|
||||
|
||||
return &EvalSequence{
|
||||
Nodes: []EvalNode{
|
||||
&EvalInterpolate{
|
||||
Config: n.Config.RawConfig.Copy(),
|
||||
Resource: resource,
|
||||
Output: &resourceConfig,
|
||||
},
|
||||
&EvalGetProvider{
|
||||
Name: n.ProvidedBy()[0],
|
||||
Output: &provider,
|
||||
},
|
||||
// Re-run validation to catch any errors we missed, e.g. type
|
||||
// mismatches on computed values.
|
||||
&EvalValidateResource{
|
||||
Provider: &provider,
|
||||
Config: &resourceConfig,
|
||||
ResourceName: n.Config.Name,
|
||||
ResourceType: n.Config.Type,
|
||||
ResourceMode: n.Config.Mode,
|
||||
IgnoreWarnings: true,
|
||||
},
|
||||
&EvalReadState{
|
||||
Name: stateId,
|
||||
Output: &state,
|
||||
},
|
||||
&EvalDiff{
|
||||
Info: info,
|
||||
Config: &resourceConfig,
|
||||
Resource: n.Config,
|
||||
Provider: &provider,
|
||||
State: &state,
|
||||
OutputDiff: &diff,
|
||||
OutputState: &state,
|
||||
},
|
||||
&EvalCheckPreventDestroy{
|
||||
Resource: n.Config,
|
||||
Diff: &diff,
|
||||
},
|
||||
&EvalWriteState{
|
||||
Name: stateId,
|
||||
ResourceType: n.Config.Type,
|
||||
Provider: n.Config.Provider,
|
||||
Dependencies: stateDeps,
|
||||
State: &state,
|
||||
},
|
||||
&EvalWriteDiff{
|
||||
Name: stateId,
|
||||
Diff: &diff,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// NodePlannableResourceOrphan represents a resource that is "applyable":
|
||||
// it is ready to be applied and is represented by a diff.
|
||||
type NodePlannableResourceOrphan struct {
|
||||
*NodeAbstractResource
|
||||
}
|
||||
|
||||
func (n *NodePlannableResourceOrphan) Name() string {
|
||||
return n.NodeAbstractResource.Name() + " (orphan)"
|
||||
}
|
||||
|
||||
// GraphNodeEvalable
|
||||
func (n *NodePlannableResourceOrphan) EvalTree() EvalNode {
|
||||
addr := n.NodeAbstractResource.Addr
|
||||
|
||||
// stateId is the ID to put into the state
|
||||
stateId := addr.stateId()
|
||||
if addr.Index > -1 {
|
||||
stateId = fmt.Sprintf("%s.%d", stateId, addr.Index)
|
||||
}
|
||||
|
||||
// Build the instance info. More of this will be populated during eval
|
||||
info := &InstanceInfo{
|
||||
Id: stateId,
|
||||
Type: addr.Type,
|
||||
ModulePath: normalizeModulePath(addr.Path),
|
||||
}
|
||||
|
||||
// Declare a bunch of variables that are used for state during
|
||||
// evaluation. Most of this are written to by-address below.
|
||||
var diff *InstanceDiff
|
||||
var state *InstanceState
|
||||
|
||||
return &EvalSequence{
|
||||
Nodes: []EvalNode{
|
||||
&EvalReadState{
|
||||
Name: stateId,
|
||||
Output: &state,
|
||||
},
|
||||
&EvalDiffDestroy{
|
||||
Info: info,
|
||||
State: &state,
|
||||
Output: &diff,
|
||||
},
|
||||
&EvalCheckPreventDestroy{
|
||||
Resource: n.Config,
|
||||
ResourceId: stateId,
|
||||
Diff: &diff,
|
||||
},
|
||||
&EvalWriteDiff{
|
||||
Name: stateId,
|
||||
Diff: &diff,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -29,6 +29,10 @@ type ResourceAddress struct {
|
|||
|
||||
// Copy returns a copy of this ResourceAddress
|
||||
func (r *ResourceAddress) Copy() *ResourceAddress {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := &ResourceAddress{
|
||||
Path: make([]string, 0, len(r.Path)),
|
||||
Index: r.Index,
|
||||
|
|
|
@ -475,7 +475,7 @@ func (p *shadowResourceProviderShadow) ValidateResource(t string, c *ResourceCon
|
|||
p.ErrorLock.Lock()
|
||||
defer p.ErrorLock.Unlock()
|
||||
p.Error = multierror.Append(p.Error, fmt.Errorf(
|
||||
"Unknown 'ValidateResource' shadow value: %#v", raw))
|
||||
"Unknown 'ValidateResource' shadow value for %q: %#v", key, raw))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -567,7 +567,7 @@ func (p *shadowResourceProviderShadow) Diff(
|
|||
p.ErrorLock.Lock()
|
||||
defer p.ErrorLock.Unlock()
|
||||
p.Error = multierror.Append(p.Error, fmt.Errorf(
|
||||
"Unknown 'diff' shadow value: %#v", raw))
|
||||
"Unknown 'diff' shadow value for %q: %#v", key, raw))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1,5 @@
|
|||
variable "value" {}
|
||||
|
||||
resource "aws_instance" "bar" {
|
||||
foo = "${var.value}"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
variable "foo" {
|
||||
default = "bar"
|
||||
description = "bar"
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
foo = "${openstack_floating_ip.random.value}"
|
||||
}
|
||||
|
||||
resource "openstack_floating_ip" "random" {}
|
||||
|
||||
resource "aws_security_group" "firewall" {}
|
||||
|
||||
resource "aws_instance" "web" {
|
||||
ami = "${var.foo}"
|
||||
security_groups = [
|
||||
"foo",
|
||||
"${aws_security_group.firewall.foo}"
|
||||
]
|
||||
}
|
||||
|
||||
resource "aws_load_balancer" "weblb" {
|
||||
members = "${aws_instance.web.id_list}"
|
||||
}
|
|
@ -5,3 +5,6 @@ variable "map_in" {
|
|||
us-west-2 = "ami-67890"
|
||||
}
|
||||
}
|
||||
|
||||
// We have to reference it so it isn't pruned
|
||||
output "output" { value = "${var.map_in}" }
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
# Purposefully empty
|
|
@ -0,0 +1 @@
|
|||
resource "aws_instance" "foo" { count = 3 }
|
|
@ -3,121 +3,94 @@ package terraform
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
// ConfigTransformer is a GraphTransformer that adds the configuration
|
||||
// to the graph. The module used to configure this transformer must be
|
||||
// the root module. We'll look up the child module by the Path in the
|
||||
// Graph.
|
||||
// ConfigTransformer is a GraphTransformer that adds all the resources
|
||||
// from the configuration to the graph.
|
||||
//
|
||||
// The module used to configure this transformer must be the root module.
|
||||
//
|
||||
// Only resources are added to the graph. Variables, outputs, and
|
||||
// providers must be added via other transforms.
|
||||
//
|
||||
// Unlike ConfigTransformerOld, this transformer creates a graph with
|
||||
// all resources including module resources, rather than creating module
|
||||
// nodes that are then "flattened".
|
||||
type ConfigTransformer struct {
|
||||
Concrete ConcreteResourceNodeFunc
|
||||
|
||||
Module *module.Tree
|
||||
}
|
||||
|
||||
func (t *ConfigTransformer) Transform(g *Graph) error {
|
||||
// A module is required and also must be completely loaded.
|
||||
// If no module is given, we don't do anything
|
||||
if t.Module == nil {
|
||||
return errors.New("module must not be nil")
|
||||
}
|
||||
if !t.Module.Loaded() {
|
||||
return errors.New("module must be loaded")
|
||||
}
|
||||
|
||||
// Get the module we care about
|
||||
module := t.Module.Child(g.Path[1:])
|
||||
if module == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the configuration for this module
|
||||
config := module.Config()
|
||||
|
||||
// Create the node list we'll use for the graph
|
||||
nodes := make([]graphNodeConfig, 0,
|
||||
(len(config.Variables)+
|
||||
len(config.ProviderConfigs)+
|
||||
len(config.Modules)+
|
||||
len(config.Resources)+
|
||||
len(config.Outputs))*2)
|
||||
|
||||
// Write all the variables out
|
||||
for _, v := range config.Variables {
|
||||
nodes = append(nodes, &GraphNodeConfigVariable{
|
||||
Variable: v,
|
||||
ModuleTree: t.Module,
|
||||
ModulePath: g.Path,
|
||||
})
|
||||
// If the module isn't loaded, that is simply an error
|
||||
if !t.Module.Loaded() {
|
||||
return errors.New("module must be loaded for ConfigTransformer")
|
||||
}
|
||||
|
||||
// Write all the provider configs out
|
||||
for _, pc := range config.ProviderConfigs {
|
||||
nodes = append(nodes, &GraphNodeConfigProvider{Provider: pc})
|
||||
// Start the transformation process
|
||||
return t.transform(g, t.Module)
|
||||
}
|
||||
|
||||
// Write all the resources out
|
||||
for _, r := range config.Resources {
|
||||
nodes = append(nodes, &GraphNodeConfigResource{
|
||||
Resource: r,
|
||||
Path: g.Path,
|
||||
})
|
||||
}
|
||||
|
||||
// Write all the modules out
|
||||
children := module.Children()
|
||||
for _, m := range config.Modules {
|
||||
path := make([]string, len(g.Path), len(g.Path)+1)
|
||||
copy(path, g.Path)
|
||||
path = append(path, m.Name)
|
||||
|
||||
nodes = append(nodes, &GraphNodeConfigModule{
|
||||
Path: path,
|
||||
Module: m,
|
||||
Tree: children[m.Name],
|
||||
})
|
||||
}
|
||||
|
||||
// Write all the outputs out
|
||||
for _, o := range config.Outputs {
|
||||
nodes = append(nodes, &GraphNodeConfigOutput{Output: o})
|
||||
}
|
||||
|
||||
// Err is where the final error value will go if there is one
|
||||
var err error
|
||||
|
||||
// Build the graph vertices
|
||||
for _, n := range nodes {
|
||||
g.Add(n)
|
||||
}
|
||||
|
||||
// Build up the dependencies. We have to do this outside of the above
|
||||
// loop since the nodes need to be in place for us to build the deps.
|
||||
for _, n := range nodes {
|
||||
if missing := g.ConnectDependent(n); len(missing) > 0 {
|
||||
for _, m := range missing {
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"%s: missing dependency: %s", n.Name(), m))
|
||||
}
|
||||
}
|
||||
func (t *ConfigTransformer) transform(g *Graph, m *module.Tree) error {
|
||||
// If no config, do nothing
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add our resources
|
||||
if err := t.transformSingle(g, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// varNameForVar returns the VarName value for an interpolated variable.
|
||||
// This value is compared to the VarName() value for the nodes within the
|
||||
// graph to build the graph edges.
|
||||
func varNameForVar(raw config.InterpolatedVariable) string {
|
||||
switch v := raw.(type) {
|
||||
case *config.ModuleVariable:
|
||||
return fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)
|
||||
case *config.ResourceVariable:
|
||||
return v.ResourceId()
|
||||
case *config.UserVariable:
|
||||
return fmt.Sprintf("var.%s", v.Name)
|
||||
default:
|
||||
return ""
|
||||
// Transform all the children.
|
||||
for _, c := range m.Children() {
|
||||
if err := t.transform(g, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ConfigTransformer) transformSingle(g *Graph, m *module.Tree) error {
|
||||
log.Printf("[TRACE] ConfigTransformer: Starting for path: %v", m.Path())
|
||||
|
||||
// Get the configuration for this module
|
||||
config := m.Config()
|
||||
|
||||
// Build the path we're at
|
||||
path := m.Path()
|
||||
|
||||
// Write all the resources out
|
||||
for _, r := range config.Resources {
|
||||
// Build the resource address
|
||||
addr, err := parseResourceAddressConfig(r)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf(
|
||||
"Error parsing config address, this is a bug: %#v", r))
|
||||
}
|
||||
addr.Path = path
|
||||
|
||||
// Build the abstract node and the concrete one
|
||||
abstract := &NodeAbstractResource{Addr: addr}
|
||||
var node dag.Vertex = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
node = f(abstract)
|
||||
}
|
||||
|
||||
// Add it to the graph
|
||||
g.Add(node)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
)
|
||||
|
||||
// ConfigTransformerOld is a GraphTransformer that adds the configuration
|
||||
// to the graph. The module used to configure this transformer must be
|
||||
// the root module. We'll look up the child module by the Path in the
|
||||
// Graph.
|
||||
type ConfigTransformerOld struct {
|
||||
Module *module.Tree
|
||||
}
|
||||
|
||||
func (t *ConfigTransformerOld) Transform(g *Graph) error {
|
||||
// A module is required and also must be completely loaded.
|
||||
if t.Module == nil {
|
||||
return errors.New("module must not be nil")
|
||||
}
|
||||
if !t.Module.Loaded() {
|
||||
return errors.New("module must be loaded")
|
||||
}
|
||||
|
||||
// Get the module we care about
|
||||
module := t.Module.Child(g.Path[1:])
|
||||
if module == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the configuration for this module
|
||||
config := module.Config()
|
||||
|
||||
// Create the node list we'll use for the graph
|
||||
nodes := make([]graphNodeConfig, 0,
|
||||
(len(config.Variables)+
|
||||
len(config.ProviderConfigs)+
|
||||
len(config.Modules)+
|
||||
len(config.Resources)+
|
||||
len(config.Outputs))*2)
|
||||
|
||||
// Write all the variables out
|
||||
for _, v := range config.Variables {
|
||||
nodes = append(nodes, &GraphNodeConfigVariable{
|
||||
Variable: v,
|
||||
ModuleTree: t.Module,
|
||||
ModulePath: g.Path,
|
||||
})
|
||||
}
|
||||
|
||||
// Write all the provider configs out
|
||||
for _, pc := range config.ProviderConfigs {
|
||||
nodes = append(nodes, &GraphNodeConfigProvider{Provider: pc})
|
||||
}
|
||||
|
||||
// Write all the resources out
|
||||
for _, r := range config.Resources {
|
||||
nodes = append(nodes, &GraphNodeConfigResource{
|
||||
Resource: r,
|
||||
Path: g.Path,
|
||||
})
|
||||
}
|
||||
|
||||
// Write all the modules out
|
||||
children := module.Children()
|
||||
for _, m := range config.Modules {
|
||||
path := make([]string, len(g.Path), len(g.Path)+1)
|
||||
copy(path, g.Path)
|
||||
path = append(path, m.Name)
|
||||
|
||||
nodes = append(nodes, &GraphNodeConfigModule{
|
||||
Path: path,
|
||||
Module: m,
|
||||
Tree: children[m.Name],
|
||||
})
|
||||
}
|
||||
|
||||
// Write all the outputs out
|
||||
for _, o := range config.Outputs {
|
||||
nodes = append(nodes, &GraphNodeConfigOutput{Output: o})
|
||||
}
|
||||
|
||||
// Err is where the final error value will go if there is one
|
||||
var err error
|
||||
|
||||
// Build the graph vertices
|
||||
for _, n := range nodes {
|
||||
g.Add(n)
|
||||
}
|
||||
|
||||
// Build up the dependencies. We have to do this outside of the above
|
||||
// loop since the nodes need to be in place for us to build the deps.
|
||||
for _, n := range nodes {
|
||||
if missing := g.ConnectDependent(n); len(missing) > 0 {
|
||||
for _, m := range missing {
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"%s: missing dependency: %s", n.Name(), m))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// varNameForVar returns the VarName value for an interpolated variable.
|
||||
// This value is compared to the VarName() value for the nodes within the
|
||||
// graph to build the graph edges.
|
||||
func varNameForVar(raw config.InterpolatedVariable) string {
|
||||
switch v := raw.(type) {
|
||||
case *config.ModuleVariable:
|
||||
return fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)
|
||||
case *config.ResourceVariable:
|
||||
return v.ResourceId()
|
||||
case *config.UserVariable:
|
||||
return fmt.Sprintf("var.%s", v.Name)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
)
|
||||
|
||||
func TestConfigTransformerOld_nilModule(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformerOld{}
|
||||
if err := tf.Transform(&g); err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigTransformerOld_unloadedModule(t *testing.T) {
|
||||
mod, err := module.NewTreeModule(
|
||||
"", filepath.Join(fixtureDir, "graph-basic"))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigTransformerOld(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformerOld{Module: testModule(t, "graph-basic")}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testGraphBasicStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigTransformerOld_dependsOn(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformerOld{Module: testModule(t, "graph-depends-on")}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testGraphDependsOnStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigTransformerOld_modules(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformerOld{Module: testModule(t, "graph-modules")}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testGraphModulesStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigTransformerOld_outputs(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformerOld{Module: testModule(t, "graph-outputs")}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testGraphOutputsStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigTransformerOld_providerAlias(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformerOld{Module: testModule(t, "graph-provider-alias")}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testGraphProviderAliasStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigTransformerOld_errMissingDeps(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformerOld{Module: testModule(t, "graph-missing-deps")}
|
||||
if err := tf.Transform(&g); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
const testGraphBasicStr = `
|
||||
aws_instance.web
|
||||
aws_security_group.firewall
|
||||
var.foo
|
||||
aws_load_balancer.weblb
|
||||
aws_instance.web
|
||||
aws_security_group.firewall
|
||||
openstack_floating_ip.random
|
||||
provider.aws
|
||||
openstack_floating_ip.random
|
||||
var.foo
|
||||
`
|
||||
|
||||
const testGraphDependsOnStr = `
|
||||
aws_instance.db
|
||||
aws_instance.web
|
||||
aws_instance.web
|
||||
`
|
||||
|
||||
const testGraphModulesStr = `
|
||||
aws_instance.web
|
||||
aws_security_group.firewall
|
||||
module.consul
|
||||
aws_security_group.firewall
|
||||
module.consul
|
||||
aws_security_group.firewall
|
||||
provider.aws
|
||||
`
|
||||
|
||||
const testGraphOutputsStr = `
|
||||
aws_instance.foo
|
||||
output.foo
|
||||
aws_instance.foo
|
||||
`
|
||||
|
||||
const testGraphProviderAliasStr = `
|
||||
provider.aws
|
||||
provider.aws.bar
|
||||
provider.aws.foo
|
||||
`
|
|
@ -11,8 +11,12 @@ import (
|
|||
func TestConfigTransformer_nilModule(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformer{}
|
||||
if err := tf.Transform(&g); err == nil {
|
||||
t.Fatal("should error")
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(g.Vertices()) > 0 {
|
||||
t.Fatalf("graph is not empty: %s", g.String())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,113 +42,15 @@ func TestConfigTransformer(t *testing.T) {
|
|||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testGraphBasicStr)
|
||||
expected := strings.TrimSpace(testConfigTransformerGraphBasicStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigTransformer_dependsOn(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformer{Module: testModule(t, "graph-depends-on")}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testGraphDependsOnStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigTransformer_modules(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformer{Module: testModule(t, "graph-modules")}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testGraphModulesStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigTransformer_outputs(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformer{Module: testModule(t, "graph-outputs")}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testGraphOutputsStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigTransformer_providerAlias(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformer{Module: testModule(t, "graph-provider-alias")}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testGraphProviderAliasStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigTransformer_errMissingDeps(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
tf := &ConfigTransformer{Module: testModule(t, "graph-missing-deps")}
|
||||
if err := tf.Transform(&g); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
const testGraphBasicStr = `
|
||||
const testConfigTransformerGraphBasicStr = `
|
||||
aws_instance.web
|
||||
aws_security_group.firewall
|
||||
var.foo
|
||||
aws_load_balancer.weblb
|
||||
aws_instance.web
|
||||
aws_security_group.firewall
|
||||
openstack_floating_ip.random
|
||||
provider.aws
|
||||
openstack_floating_ip.random
|
||||
var.foo
|
||||
`
|
||||
|
||||
const testGraphDependsOnStr = `
|
||||
aws_instance.db
|
||||
aws_instance.web
|
||||
aws_instance.web
|
||||
`
|
||||
|
||||
const testGraphModulesStr = `
|
||||
aws_instance.web
|
||||
aws_security_group.firewall
|
||||
module.consul
|
||||
aws_security_group.firewall
|
||||
module.consul
|
||||
aws_security_group.firewall
|
||||
provider.aws
|
||||
`
|
||||
|
||||
const testGraphOutputsStr = `
|
||||
aws_instance.foo
|
||||
output.foo
|
||||
aws_instance.foo
|
||||
`
|
||||
|
||||
const testGraphProviderAliasStr = `
|
||||
provider.aws
|
||||
provider.aws.bar
|
||||
provider.aws.foo
|
||||
`
|
||||
|
|
|
@ -120,10 +120,14 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
|
|||
&AttachStateTransformer{State: t.State},
|
||||
}
|
||||
|
||||
// Go through the all destroyers and find what they're destroying.
|
||||
// Use this to find the dependencies, look up if any of them are being
|
||||
// destroyed, and to make the proper edge.
|
||||
for d, dns := range destroyers {
|
||||
// Go through all the nodes being destroyed and create a graph.
|
||||
// The resulting graph is only of things being CREATED. For example,
|
||||
// following our example, the resulting graph would be:
|
||||
//
|
||||
// A, B (with no edges)
|
||||
//
|
||||
var tempG Graph
|
||||
for d, _ := range destroyers {
|
||||
// d is what is being destroyed. We parse the resource address
|
||||
// which it came from it is a panic if this fails.
|
||||
addr, err := ParseResourceAddress(d)
|
||||
|
@ -135,27 +139,48 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
|
|||
// find the dependencies we need to: build a graph and use the
|
||||
// attach config and state transformers then ask for references.
|
||||
node := &NodeAbstractResource{Addr: addr}
|
||||
{
|
||||
var g Graph
|
||||
g.Add(node)
|
||||
tempG.Add(node)
|
||||
}
|
||||
|
||||
// Run the graph transforms so we have the information we need to
|
||||
// build references.
|
||||
for _, s := range steps {
|
||||
if err := s.Transform(&g); err != nil {
|
||||
if err := s.Transform(&tempG); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the references of the creation node. If it has none,
|
||||
// then there are no edges to make here.
|
||||
prefix := modulePrefixStr(normalizeModulePath(addr.Path))
|
||||
deps := modulePrefixList(node.References(), prefix)
|
||||
// Create a reference map for easy lookup
|
||||
refMap := NewReferenceMap(tempG.Vertices())
|
||||
|
||||
// Go through all the nodes in the graph and determine what they
|
||||
// depend on.
|
||||
for _, v := range tempG.Vertices() {
|
||||
// Find all the references
|
||||
refs, _ := refMap.References(v)
|
||||
log.Printf(
|
||||
"[TRACE] DestroyEdgeTransformer: creation of %q depends on %#v",
|
||||
d, deps)
|
||||
if len(deps) == 0 {
|
||||
"[TRACE] DestroyEdgeTransformer: creation node %q references %v",
|
||||
dag.VertexName(v), refs)
|
||||
|
||||
// If we have no references, then we won't need to do anything
|
||||
if len(refs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the destroy node for this. In the example of our struct,
|
||||
// we are currently at B and we're looking for B_d.
|
||||
rn, ok := v.(GraphNodeResource)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
addr := rn.ResourceAddr()
|
||||
if addr == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
dns := destroyers[addr.String()]
|
||||
|
||||
// We have dependencies, check if any are being destroyed
|
||||
// to build the list of things that we must depend on!
|
||||
//
|
||||
|
@ -163,17 +188,28 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
|
|||
//
|
||||
// B_d => A_d => A => B
|
||||
//
|
||||
// Then at this point in the algorithm we started with A_d,
|
||||
// we built A (to get dependencies), and we found B. We're now looking
|
||||
// to see if B_d exists.
|
||||
// Then at this point in the algorithm we started with B_d,
|
||||
// we built B (to get dependencies), and we found A. We're now looking
|
||||
// to see if A_d exists.
|
||||
var depDestroyers []dag.Vertex
|
||||
for _, d := range deps {
|
||||
if ds, ok := destroyers[d]; ok {
|
||||
for _, v := range refs {
|
||||
rn, ok := v.(GraphNodeResource)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
addr := rn.ResourceAddr()
|
||||
if addr == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
key := addr.String()
|
||||
if ds, ok := destroyers[key]; ok {
|
||||
for _, d := range ds {
|
||||
depDestroyers = append(depDestroyers, d.(dag.Vertex))
|
||||
log.Printf(
|
||||
"[TRACE] DestroyEdgeTransformer: destruction of %q depends on %s",
|
||||
addr.String(), dag.VertexName(d))
|
||||
key, dag.VertexName(d))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestDestroyEdgeTransformer(t *testing.T) {
|
||||
func TestDestroyEdgeTransformer_basic(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
|
||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})
|
||||
|
|
|
@ -10,7 +10,7 @@ func TestDestroyTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ func TestDestroyTransformer_dependsOn(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ func TestCreateBeforeDestroyTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ func TestCreateBeforeDestroyTransformer_twice(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ func TestPruneDestroyTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ func TestPruneDestroyTransformer_diff(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ func TestPruneDestroyTransformer_count(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -251,7 +251,7 @@ func TestPruneDestroyTransformer_countDec(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -297,7 +297,7 @@ func TestPruneDestroyTransformer_countState(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -347,7 +347,7 @@ func TestPruneDestroyTransformer_prefixMatch(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -396,7 +396,7 @@ func TestPruneDestroyTransformer_tainted(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ func TestFlattenTransformer(t *testing.T) {
|
|||
var b BasicGraphBuilder
|
||||
b = BasicGraphBuilder{
|
||||
Steps: []GraphTransformer{
|
||||
&ConfigTransformer{Module: mod},
|
||||
&ConfigTransformerOld{Module: mod},
|
||||
&VertexTransformer{
|
||||
Transforms: []GraphVertexTransformer{
|
||||
&ExpandTransform{
|
||||
|
@ -41,7 +41,7 @@ func TestFlattenTransformer_withProxy(t *testing.T) {
|
|||
var b BasicGraphBuilder
|
||||
b = BasicGraphBuilder{
|
||||
Steps: []GraphTransformer{
|
||||
&ConfigTransformer{Module: mod},
|
||||
&ConfigTransformerOld{Module: mod},
|
||||
&VertexTransformer{
|
||||
Transforms: []GraphVertexTransformer{
|
||||
&ExpandTransform{
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
// OrphanResourceCountTransformer is a GraphTransformer that adds orphans
|
||||
// for an expanded count to the graph. The determination of this depends
|
||||
// on the count argument given.
|
||||
//
|
||||
// Orphans are found by comparing the count to what is found in the state.
|
||||
// This transform assumes that if an element in the state is within the count
|
||||
// bounds given, that it is not an orphan.
|
||||
type OrphanResourceCountTransformer struct {
|
||||
Concrete ConcreteResourceNodeFunc
|
||||
|
||||
Count int // Actual count of the resource
|
||||
Addr *ResourceAddress // Addr of the resource to look for orphans
|
||||
State *State // Full global state
|
||||
}
|
||||
|
||||
func (t *OrphanResourceCountTransformer) Transform(g *Graph) error {
|
||||
log.Printf("[TRACE] OrphanResourceCount: Starting...")
|
||||
|
||||
// Grab the module in the state just for this resource address
|
||||
ms := t.State.ModuleByPath(normalizeModulePath(t.Addr.Path))
|
||||
if ms == nil {
|
||||
// If no state, there can't be orphans
|
||||
return nil
|
||||
}
|
||||
|
||||
orphanIndex := -1
|
||||
if t.Count == 1 {
|
||||
orphanIndex = 0
|
||||
}
|
||||
|
||||
// Go through the orphans and add them all to the state
|
||||
for key, _ := range ms.Resources {
|
||||
// Build the address
|
||||
addr, err := parseResourceAddressInternal(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr.Path = ms.Path[1:]
|
||||
|
||||
// Copy the address for comparison. If we aren't looking at
|
||||
// the same resource, then just ignore it.
|
||||
addrCopy := addr.Copy()
|
||||
addrCopy.Index = -1
|
||||
if !addrCopy.Equals(t.Addr) {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] OrphanResourceCount: Checking: %s", addr)
|
||||
|
||||
idx := addr.Index
|
||||
|
||||
// If we have zero and the index here is 0 or 1, then we
|
||||
// change the index to a high number so that we treat it as
|
||||
// an orphan.
|
||||
if t.Count <= 0 && idx <= 0 {
|
||||
idx = t.Count + 1
|
||||
}
|
||||
|
||||
// If we have a count greater than 0 and we're at the zero index,
|
||||
// we do a special case check to see if our state also has a
|
||||
// -1 index value. If so, this is an orphan because our rules are
|
||||
// that if both a -1 and 0 are in the state, the 0 is destroyed.
|
||||
if t.Count > 0 && idx == orphanIndex {
|
||||
// This is a piece of cleverness (beware), but its simple:
|
||||
// if orphanIndex is 0, then check -1, else check 0.
|
||||
checkIndex := (orphanIndex + 1) * -1
|
||||
|
||||
key := &ResourceStateKey{
|
||||
Name: addr.Name,
|
||||
Type: addr.Type,
|
||||
Mode: addr.Mode,
|
||||
Index: checkIndex,
|
||||
}
|
||||
|
||||
if _, ok := ms.Resources[key.String()]; ok {
|
||||
// We have a -1 index, too. Make an arbitrarily high
|
||||
// index so that we always mark this as an orphan.
|
||||
log.Printf(
|
||||
"[WARN] OrphanResourceCount: %q both -1 and 0 index found, orphaning %d",
|
||||
addr, orphanIndex)
|
||||
idx = t.Count + 1
|
||||
}
|
||||
}
|
||||
|
||||
// If the index is within the count bounds, it is not an orphan
|
||||
if idx < t.Count {
|
||||
continue
|
||||
}
|
||||
|
||||
// Build the abstract node and the concrete one
|
||||
abstract := &NodeAbstractResource{Addr: addr}
|
||||
var node dag.Vertex = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
node = f(abstract)
|
||||
}
|
||||
|
||||
// Add it to the graph
|
||||
g.Add(node)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,373 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOrphanResourceCountTransformer(t *testing.T) {
|
||||
addr, err := parseResourceAddressInternal("aws_instance.foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.2": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceCountTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
Count: 1,
|
||||
Addr: addr,
|
||||
State: state,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceCountBasicStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceCountTransformer_zero(t *testing.T) {
|
||||
addr, err := parseResourceAddressInternal("aws_instance.foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.2": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceCountTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
Count: 0,
|
||||
Addr: addr,
|
||||
State: state,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceCountZeroStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceCountTransformer_oneNoIndex(t *testing.T) {
|
||||
addr, err := parseResourceAddressInternal("aws_instance.foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.2": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceCountTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
Count: 1,
|
||||
Addr: addr,
|
||||
State: state,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceCountOneNoIndexStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceCountTransformer_oneIndex(t *testing.T) {
|
||||
addr, err := parseResourceAddressInternal("aws_instance.foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.1": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceCountTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
Count: 1,
|
||||
Addr: addr,
|
||||
State: state,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceCountOneIndexStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceCountTransformer_zeroAndNone(t *testing.T) {
|
||||
addr, err := parseResourceAddressInternal("aws_instance.foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceCountTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
Count: 1,
|
||||
Addr: addr,
|
||||
State: state,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceCountZeroAndNoneStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceCountTransformer_zeroAndNoneCount(t *testing.T) {
|
||||
addr, err := parseResourceAddressInternal("aws_instance.foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceCountTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
Count: 2,
|
||||
Addr: addr,
|
||||
State: state,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceCountZeroAndNoneCountStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
const testTransformOrphanResourceCountBasicStr = `
|
||||
aws_instance.foo[2] (orphan)
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceCountZeroStr = `
|
||||
aws_instance.foo (orphan)
|
||||
aws_instance.foo[2] (orphan)
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceCountOneNoIndexStr = `
|
||||
aws_instance.foo[2] (orphan)
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceCountOneIndexStr = `
|
||||
aws_instance.foo[1] (orphan)
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceCountZeroAndNoneStr = `
|
||||
aws_instance.foo[0] (orphan)
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceCountZeroAndNoneCountStr = `
|
||||
aws_instance.foo (orphan)
|
||||
`
|
|
@ -0,0 +1,74 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
// OrphanResourceTransformer is a GraphTransformer that adds resource
|
||||
// orphans to the graph. A resource orphan is a resource that is
|
||||
// represented in the state but not in the configuration.
|
||||
//
|
||||
// This only adds orphans that have no representation at all in the
|
||||
// configuration.
|
||||
type OrphanResourceTransformer struct {
|
||||
Concrete ConcreteResourceNodeFunc
|
||||
|
||||
// State is the global state. We require the global state to
|
||||
// properly find module orphans at our path.
|
||||
State *State
|
||||
|
||||
// Module is the root module. We'll look up the proper configuration
|
||||
// using the graph path.
|
||||
Module *module.Tree
|
||||
}
|
||||
|
||||
func (t *OrphanResourceTransformer) Transform(g *Graph) error {
|
||||
if t.State == nil {
|
||||
// If the entire state is nil, there can't be any orphans
|
||||
return nil
|
||||
}
|
||||
|
||||
// Go through the modules and for each module transform in order
|
||||
// to add the orphan.
|
||||
for _, ms := range t.State.Modules {
|
||||
if err := t.transform(g, ms); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *OrphanResourceTransformer) transform(g *Graph, ms *ModuleState) error {
|
||||
// Get the configuration for this path. The configuration might be
|
||||
// nil if the module was removed from the configuration. This is okay,
|
||||
// this just means that every resource is an orphan.
|
||||
var c *config.Config
|
||||
if m := t.Module.Child(ms.Path[1:]); m != nil {
|
||||
c = m.Config()
|
||||
}
|
||||
|
||||
// Go through the orphans and add them all to the state
|
||||
for _, key := range ms.Orphans(c) {
|
||||
// Build the abstract resource
|
||||
addr, err := parseResourceAddressInternal(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr.Path = ms.Path[1:]
|
||||
|
||||
// Build the abstract node and the concrete one
|
||||
abstract := &NodeAbstractResource{Addr: addr}
|
||||
var node dag.Vertex = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
node = f(abstract)
|
||||
}
|
||||
|
||||
// Add it to the graph
|
||||
g.Add(node)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
func TestOrphanResourceTransformer(t *testing.T) {
|
||||
mod := testModule(t, "transform-orphan-basic")
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
// The orphan
|
||||
"aws_instance.db": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
State: state, Module: mod,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceBasicStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceTransformer_countGood(t *testing.T) {
|
||||
mod := testModule(t, "transform-orphan-count")
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.1": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
State: state, Module: mod,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceCountStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceTransformer_countBad(t *testing.T) {
|
||||
mod := testModule(t, "transform-orphan-count-empty")
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.1": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
State: state, Module: mod,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceCountBadStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceTransformer_modules(t *testing.T) {
|
||||
mod := testModule(t, "transform-orphan-modules")
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
&ModuleState{
|
||||
Path: []string{"root", "child"},
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
State: state, Module: mod,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceModulesStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
const testTransformOrphanResourceBasicStr = `
|
||||
aws_instance.db (orphan)
|
||||
aws_instance.web
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceCountStr = `
|
||||
aws_instance.foo
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceCountBadStr = `
|
||||
aws_instance.foo[0] (orphan)
|
||||
aws_instance.foo[1] (orphan)
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceModulesStr = `
|
||||
aws_instance.foo
|
||||
module.child.aws_instance.web (orphan)
|
||||
`
|
||||
|
||||
func testOrphanResourceConcreteFunc(a *NodeAbstractResource) dag.Vertex {
|
||||
return &testOrphanResourceConcrete{a}
|
||||
}
|
||||
|
||||
type testOrphanResourceConcrete struct {
|
||||
*NodeAbstractResource
|
||||
}
|
||||
|
||||
func (n *testOrphanResourceConcrete) Name() string {
|
||||
return fmt.Sprintf("%s (orphan)", n.NodeAbstractResource.Name())
|
||||
}
|
|
@ -35,7 +35,7 @@ func TestOrphanTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ func TestOrphanTransformer_modules(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ func TestOrphanTransformer_modulesDeps(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ func TestOrphanTransformer_modulesDepsOrphan(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ func TestOrphanTransformer_modulesNoRoot(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -282,7 +282,7 @@ func TestOrphanTransformer_resourceDepends(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -305,7 +305,7 @@ func TestOrphanTransformer_nilState(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ func TestAddOutputOrphanTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ func TestProviderTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ func TestCloseProviderTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ func TestCloseProviderTransformer_withTargets(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
transforms := []GraphTransformer{
|
||||
&ConfigTransformer{Module: mod},
|
||||
&ConfigTransformerOld{Module: mod},
|
||||
&ProviderTransformer{},
|
||||
&CloseProviderTransformer{},
|
||||
&TargetsTransformer{
|
||||
|
@ -135,7 +135,7 @@ func TestMissingProviderTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -318,7 +318,7 @@ func TestPruneProviderTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -364,7 +364,7 @@ func TestDisableProviderTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
transforms := []GraphTransformer{
|
||||
&ConfigTransformer{Module: mod},
|
||||
&ConfigTransformerOld{Module: mod},
|
||||
&MissingProviderTransformer{Providers: []string{"aws"}},
|
||||
&ProviderTransformer{},
|
||||
&DisableProviderTransformerOld{},
|
||||
|
@ -390,7 +390,7 @@ func TestDisableProviderTransformer_keep(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
transforms := []GraphTransformer{
|
||||
&ConfigTransformer{Module: mod},
|
||||
&ConfigTransformerOld{Module: mod},
|
||||
&MissingProviderTransformer{Providers: []string{"aws"}},
|
||||
&ProviderTransformer{},
|
||||
&DisableProviderTransformerOld{},
|
||||
|
|
|
@ -12,7 +12,7 @@ func TestMissingProvisionerTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ func TestCloseProvisionerTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package terraform
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
|
@ -53,6 +55,14 @@ func (t *ReferenceTransformer) Transform(g *Graph) error {
|
|||
// Find the things that reference things and connect them
|
||||
for _, v := range vs {
|
||||
parents, _ := m.References(v)
|
||||
parentsDbg := make([]string, len(parents))
|
||||
for i, v := range parents {
|
||||
parentsDbg[i] = dag.VertexName(v)
|
||||
}
|
||||
log.Printf(
|
||||
"[DEBUG] ReferenceTransformer: %q references: %v",
|
||||
dag.VertexName(v), parentsDbg)
|
||||
|
||||
for _, parent := range parents {
|
||||
g.Connect(dag.BasicEdge(v, parent))
|
||||
}
|
||||
|
@ -81,14 +91,18 @@ func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []string) {
|
|||
var matches []dag.Vertex
|
||||
var missing []string
|
||||
prefix := m.prefix(v)
|
||||
for _, n := range rn.References() {
|
||||
for _, ns := range rn.References() {
|
||||
found := false
|
||||
for _, n := range strings.Split(ns, "/") {
|
||||
n = prefix + n
|
||||
parents, ok := m.references[n]
|
||||
if !ok {
|
||||
missing = append(missing, n)
|
||||
continue
|
||||
}
|
||||
|
||||
// Mark that we found a match
|
||||
found = true
|
||||
|
||||
// Make sure this isn't a self reference, which isn't included
|
||||
selfRef := false
|
||||
for _, p := range parents {
|
||||
|
@ -102,6 +116,12 @@ func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []string) {
|
|||
}
|
||||
|
||||
matches = append(matches, parents...)
|
||||
break
|
||||
}
|
||||
|
||||
if !found {
|
||||
missing = append(missing, ns)
|
||||
}
|
||||
}
|
||||
|
||||
return matches, missing
|
||||
|
@ -209,10 +229,9 @@ func NewReferenceMap(vs []dag.Vertex) *ReferenceMap {
|
|||
func ReferencesFromConfig(c *config.RawConfig) []string {
|
||||
var result []string
|
||||
for _, v := range c.Variables {
|
||||
if r := ReferenceFromInterpolatedVar(v); r != "" {
|
||||
result = append(result, r)
|
||||
if r := ReferenceFromInterpolatedVar(v); len(r) > 0 {
|
||||
result = append(result, r...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -220,15 +239,31 @@ func ReferencesFromConfig(c *config.RawConfig) []string {
|
|||
|
||||
// ReferenceFromInterpolatedVar returns the reference from this variable,
|
||||
// or an empty string if there is no reference.
|
||||
func ReferenceFromInterpolatedVar(v config.InterpolatedVariable) string {
|
||||
func ReferenceFromInterpolatedVar(v config.InterpolatedVariable) []string {
|
||||
switch v := v.(type) {
|
||||
case *config.ModuleVariable:
|
||||
return fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)
|
||||
return []string{fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)}
|
||||
case *config.ResourceVariable:
|
||||
return v.ResourceId()
|
||||
id := v.ResourceId()
|
||||
|
||||
// If we have a multi-reference (splat), then we depend on ALL
|
||||
// resources with this type/name.
|
||||
if v.Multi && v.Index == -1 {
|
||||
return []string{fmt.Sprintf("%s.*", id)}
|
||||
}
|
||||
|
||||
// Otherwise, we depend on a specific index.
|
||||
idx := v.Index
|
||||
if !v.Multi || v.Index == -1 {
|
||||
idx = 0
|
||||
}
|
||||
|
||||
// Depend on the index, as well as "N" which represents the
|
||||
// un-expanded set of resources.
|
||||
return []string{fmt.Sprintf("%s.%d/%s.N", id, idx, id)}
|
||||
case *config.UserVariable:
|
||||
return fmt.Sprintf("var.%s", v.Name)
|
||||
return []string{fmt.Sprintf("var.%s", v.Name)}
|
||||
default:
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,56 @@ func TestReferenceTransformer_path(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestReferenceTransformer_backup(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
g.Add(&graphNodeRefParentTest{
|
||||
NameValue: "A",
|
||||
Names: []string{"A"},
|
||||
})
|
||||
g.Add(&graphNodeRefChildTest{
|
||||
NameValue: "B",
|
||||
Refs: []string{"C/A"},
|
||||
})
|
||||
|
||||
tf := &ReferenceTransformer{}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformRefBackupStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReferenceTransformer_backupPrimary(t *testing.T) {
|
||||
g := Graph{Path: RootModulePath}
|
||||
g.Add(&graphNodeRefParentTest{
|
||||
NameValue: "A",
|
||||
Names: []string{"A"},
|
||||
})
|
||||
g.Add(&graphNodeRefChildTest{
|
||||
NameValue: "B",
|
||||
Refs: []string{"C/A"},
|
||||
})
|
||||
g.Add(&graphNodeRefParentTest{
|
||||
NameValue: "C",
|
||||
Names: []string{"C"},
|
||||
})
|
||||
|
||||
tf := &ReferenceTransformer{}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformRefBackupPrimaryStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReferenceMapReferences(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Nodes []dag.Vertex
|
||||
|
@ -202,6 +252,19 @@ B
|
|||
A
|
||||
`
|
||||
|
||||
const testTransformRefBackupStr = `
|
||||
A
|
||||
B
|
||||
A
|
||||
`
|
||||
|
||||
const testTransformRefBackupPrimaryStr = `
|
||||
A
|
||||
B
|
||||
C
|
||||
C
|
||||
`
|
||||
|
||||
const testTransformRefPathStr = `
|
||||
A
|
||||
B
|
||||
|
|
|
@ -8,15 +8,15 @@ import (
|
|||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
// ResourceCountTransformer is a GraphTransformer that expands the count
|
||||
// ResourceCountTransformerOld is a GraphTransformer that expands the count
|
||||
// out for a specific resource.
|
||||
type ResourceCountTransformer struct {
|
||||
type ResourceCountTransformerOld struct {
|
||||
Resource *config.Resource
|
||||
Destroy bool
|
||||
Targets []ResourceAddress
|
||||
}
|
||||
|
||||
func (t *ResourceCountTransformer) Transform(g *Graph) error {
|
||||
func (t *ResourceCountTransformerOld) Transform(g *Graph) error {
|
||||
// Expand the resource count
|
||||
count, err := t.Resource.Count()
|
||||
if err != nil {
|
||||
|
@ -72,7 +72,7 @@ func (t *ResourceCountTransformer) Transform(g *Graph) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *ResourceCountTransformer) nodeIsTargeted(node dag.Vertex) bool {
|
||||
func (t *ResourceCountTransformerOld) nodeIsTargeted(node dag.Vertex) bool {
|
||||
// no targets specified, everything stays in the graph
|
||||
if len(t.Targets) == 0 {
|
||||
return true
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
// ResourceCountTransformer is a GraphTransformer that expands the count
|
||||
// out for a specific resource.
|
||||
//
|
||||
// This assumes that the count is already interpolated.
|
||||
type ResourceCountTransformer struct {
|
||||
Concrete ConcreteResourceNodeFunc
|
||||
|
||||
Count int
|
||||
Addr *ResourceAddress
|
||||
}
|
||||
|
||||
func (t *ResourceCountTransformer) Transform(g *Graph) error {
|
||||
// Don't allow the count to be negative
|
||||
if t.Count < 0 {
|
||||
return fmt.Errorf("negative count: %d", t.Count)
|
||||
}
|
||||
|
||||
// For each count, build and add the node
|
||||
for i := 0; i < t.Count; i++ {
|
||||
// Set the index. If our count is 1 we special case it so that
|
||||
// we handle the "resource.0" and "resource" boundary properly.
|
||||
index := i
|
||||
if t.Count == 1 {
|
||||
index = -1
|
||||
}
|
||||
|
||||
// Build the resource address
|
||||
addr := t.Addr.Copy()
|
||||
addr.Index = index
|
||||
|
||||
// Build the abstract node and the concrete one
|
||||
abstract := &NodeAbstractResource{Addr: addr}
|
||||
var node dag.Vertex = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
node = f(abstract)
|
||||
}
|
||||
|
||||
// Add it to the graph
|
||||
g.Add(node)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -5,64 +5,64 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestResourceCountTransformer(t *testing.T) {
|
||||
func TestResourceCountTransformerOld(t *testing.T) {
|
||||
cfg := testModule(t, "transform-resource-count-basic").Config()
|
||||
resource := cfg.Resources[0]
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ResourceCountTransformer{Resource: resource}
|
||||
tf := &ResourceCountTransformerOld{Resource: resource}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testResourceCountTransformStr)
|
||||
expected := strings.TrimSpace(testResourceCountTransformOldStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceCountTransformer_countNegative(t *testing.T) {
|
||||
func TestResourceCountTransformerOld_countNegative(t *testing.T) {
|
||||
cfg := testModule(t, "transform-resource-count-negative").Config()
|
||||
resource := cfg.Resources[0]
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ResourceCountTransformer{Resource: resource}
|
||||
tf := &ResourceCountTransformerOld{Resource: resource}
|
||||
if err := tf.Transform(&g); err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceCountTransformer_deps(t *testing.T) {
|
||||
func TestResourceCountTransformerOld_deps(t *testing.T) {
|
||||
cfg := testModule(t, "transform-resource-count-deps").Config()
|
||||
resource := cfg.Resources[0]
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ResourceCountTransformer{Resource: resource}
|
||||
tf := &ResourceCountTransformerOld{Resource: resource}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testResourceCountTransformDepsStr)
|
||||
expected := strings.TrimSpace(testResourceCountTransformOldDepsStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
const testResourceCountTransformStr = `
|
||||
const testResourceCountTransformOldStr = `
|
||||
aws_instance.foo #0
|
||||
aws_instance.foo #1
|
||||
aws_instance.foo #2
|
||||
`
|
||||
|
||||
const testResourceCountTransformDepsStr = `
|
||||
const testResourceCountTransformOldDepsStr = `
|
||||
aws_instance.foo #0
|
||||
aws_instance.foo #1
|
||||
aws_instance.foo #0
|
||||
|
|
|
@ -10,7 +10,7 @@ func TestRootTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ func TestTargetsTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ func TestTargetsTransformer_destroy(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ func TestTransitiveReductionTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue