Merge pull request #11454 from hashicorp/f-goodbye-legacy

core: remove legacy graph
This commit is contained in:
Mitchell Hashimoto 2017-01-27 11:08:32 -08:00 committed by GitHub
commit d224d872b9
53 changed files with 283 additions and 6220 deletions

View File

@ -158,7 +158,8 @@ Options:
-no-color If specified, output won't contain any color. -no-color If specified, output won't contain any color.
-type=plan Type of graph to output. Can be: plan, plan-destroy, apply, -type=plan Type of graph to output. Can be: plan, plan-destroy, apply,
legacy. validate, input, refresh.
` `
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)

View File

@ -50,9 +50,6 @@ import (
// of definition and use. This allows the compiler to enforce references // of definition and use. This allows the compiler to enforce references
// so it becomes easy to remove the features. // so it becomes easy to remove the features.
var ( var (
// Reuse the old graphs from TF 0.7.x. These will be removed at some point.
X_legacyGraph = newBasicID("legacy-graph", "LEGACY_GRAPH", false)
// Shadow graph. This is already on by default. Disabling it will be // Shadow graph. This is already on by default. Disabling it will be
// allowed for awhile in order for it to not block operations. // allowed for awhile in order for it to not block operations.
X_shadow = newBasicID("shadow", "SHADOW", true) X_shadow = newBasicID("shadow", "SHADOW", true)
@ -75,7 +72,6 @@ var (
func init() { func init() {
// The list of all experiments, update this when an experiment is added. // The list of all experiments, update this when an experiment is added.
All = []ID{ All = []ID{
X_legacyGraph,
X_shadow, X_shadow,
x_force, x_force,
} }

View File

@ -262,30 +262,11 @@ func (c *Context) Graph(typ GraphType, opts *ContextGraphOpts) (*Graph, error) {
Targets: c.targets, Targets: c.targets,
Validate: opts.Validate, Validate: opts.Validate,
}).Build(RootModulePath) }).Build(RootModulePath)
case GraphTypeLegacy:
return c.graphBuilder(opts).Build(RootModulePath)
} }
return nil, fmt.Errorf("unknown graph type: %s", typ) return nil, fmt.Errorf("unknown graph type: %s", typ)
} }
// GraphBuilder returns the GraphBuilder that will be used to create
// the graphs for this context.
func (c *Context) graphBuilder(g *ContextGraphOpts) GraphBuilder {
return &BuiltinGraphBuilder{
Root: c.module,
Diff: c.diff,
Providers: c.components.ResourceProviders(),
Provisioners: c.components.ResourceProvisioners(),
State: c.state,
Targets: c.targets,
Destroy: c.destroy,
Validate: g.Validate,
Verbose: g.Verbose,
}
}
// ShadowError returns any errors caught during a shadow operation. // ShadowError returns any errors caught during a shadow operation.
// //
// A shadow operation is an operation run in parallel to a real operation // A shadow operation is an operation run in parallel to a real operation
@ -465,15 +446,8 @@ func (c *Context) Apply() (*State, error) {
// Copy our own state // Copy our own state
c.state = c.state.DeepCopy() c.state = c.state.DeepCopy()
// Enable the new graph by default
X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph)
// Build the graph. // Build the graph.
graphType := GraphTypeLegacy graph, err := c.Graph(GraphTypeApply, nil)
if !X_legacyGraph {
graphType = GraphTypeApply
}
graph, err := c.Graph(graphType, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -541,17 +515,10 @@ func (c *Context) Plan() (*Plan, error) {
c.diff.init() c.diff.init()
c.diffLock.Unlock() c.diffLock.Unlock()
// Used throughout below
X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph)
// Build the graph. // Build the graph.
graphType := GraphTypeLegacy graphType := GraphTypePlan
if !X_legacyGraph {
if c.destroy { if c.destroy {
graphType = GraphTypePlanDestroy graphType = GraphTypePlanDestroy
} else {
graphType = GraphTypePlan
}
} }
graph, err := c.Graph(graphType, nil) graph, err := c.Graph(graphType, nil)
if err != nil { if err != nil {
@ -576,6 +543,7 @@ func (c *Context) Plan() (*Plan, error) {
p.Diff.DeepCopy() p.Diff.DeepCopy()
} }
/*
// We don't do the reverification during the new destroy plan because // We don't do the reverification during the new destroy plan because
// it will use a different apply process. // it will use a different apply process.
if X_legacyGraph { if X_legacyGraph {
@ -585,6 +553,7 @@ func (c *Context) Plan() (*Plan, error) {
return nil, err return nil, err
} }
} }
*/
var errs error var errs error
if len(walker.ValidationErrors) > 0 { if len(walker.ValidationErrors) > 0 {
@ -606,15 +575,8 @@ func (c *Context) Refresh() (*State, error) {
// Copy our own state // Copy our own state
c.state = c.state.DeepCopy() c.state = c.state.DeepCopy()
// Used throughout below
X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph)
// Build the graph. // Build the graph.
graphType := GraphTypeLegacy graph, err := c.Graph(GraphTypeRefresh, nil)
if !X_legacyGraph {
graphType = GraphTypeRefresh
}
graph, err := c.Graph(graphType, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -7229,7 +7229,7 @@ template_file.child:
type = template_file type = template_file
Dependencies: Dependencies:
template_file.parent template_file.parent.*
template_file.parent: template_file.parent:
ID = foo ID = foo
template = Hi template = Hi

View File

@ -29,11 +29,6 @@ type Graph struct {
// RootModuleName // RootModuleName
Path []string Path []string
// annotations are the annotations that are added to vertices. Annotations
// are arbitrary metadata taht is used for various logic. Annotations
// should have unique keys that are referenced via constants.
annotations map[dag.Vertex]map[string]interface{}
// dependableMap is a lookaside table for fast lookups for connecting // dependableMap is a lookaside table for fast lookups for connecting
// dependencies by their GraphNodeDependable value to avoid O(n^3)-like // dependencies by their GraphNodeDependable value to avoid O(n^3)-like
// situations and turn them into O(1) with respect to the number of new // situations and turn them into O(1) with respect to the number of new
@ -52,29 +47,6 @@ func (g *Graph) DirectedGraph() dag.Grapher {
return &g.AcyclicGraph return &g.AcyclicGraph
} }
// Annotations returns the annotations that are configured for the
// given vertex. The map is guaranteed to be non-nil but may be empty.
//
// The returned map may be modified to modify the annotations of the
// vertex.
func (g *Graph) Annotations(v dag.Vertex) map[string]interface{} {
g.once.Do(g.init)
// If this vertex isn't in the graph, then just return an empty map
if !g.HasVertex(v) {
return map[string]interface{}{}
}
// Get the map, if it doesn't exist yet then initialize it
m, ok := g.annotations[v]
if !ok {
m = make(map[string]interface{})
g.annotations[v] = m
}
return m
}
// Add is the same as dag.Graph.Add. // Add is the same as dag.Graph.Add.
func (g *Graph) Add(v dag.Vertex) dag.Vertex { func (g *Graph) Add(v dag.Vertex) dag.Vertex {
g.once.Do(g.init) g.once.Do(g.init)
@ -89,14 +61,6 @@ func (g *Graph) Add(v dag.Vertex) dag.Vertex {
} }
} }
// If this initializes annotations, then do that
if av, ok := v.(GraphNodeAnnotationInit); ok {
as := g.Annotations(v)
for k, v := range av.AnnotationInit() {
as[k] = v
}
}
return v return v
} }
@ -111,9 +75,6 @@ func (g *Graph) Remove(v dag.Vertex) dag.Vertex {
} }
} }
// Remove the annotations
delete(g.annotations, v)
// Call upwards to remove it from the actual graph // Call upwards to remove it from the actual graph
return g.Graph.Remove(v) return g.Graph.Remove(v)
} }
@ -133,12 +94,6 @@ func (g *Graph) Replace(o, n dag.Vertex) bool {
} }
} }
// Move the annotation if it exists
if m, ok := g.annotations[o]; ok {
g.annotations[n] = m
delete(g.annotations, o)
}
return g.Graph.Replace(o, n) return g.Graph.Replace(o, n)
} }
@ -195,13 +150,6 @@ func (g *Graph) ConnectTo(v dag.Vertex, targets []string) []string {
return missing return missing
} }
// Dependable finds the vertices in the graph that have the given dependable
// names and returns them.
func (g *Graph) Dependable(n string) dag.Vertex {
// TODO: do we need this?
return nil
}
// Walk walks the graph with the given walker for callbacks. The graph // Walk walks the graph with the given walker for callbacks. The graph
// will be walked with full parallelism, so the walker should expect // will be walked with full parallelism, so the walker should expect
// to be called in concurrently. // to be called in concurrently.
@ -210,10 +158,6 @@ func (g *Graph) Walk(walker GraphWalker) error {
} }
func (g *Graph) init() { func (g *Graph) init() {
if g.annotations == nil {
g.annotations = make(map[dag.Vertex]map[string]interface{})
}
if g.dependableMap == nil { if g.dependableMap == nil {
g.dependableMap = make(map[string]dag.Vertex) g.dependableMap = make(map[string]dag.Vertex)
} }
@ -346,16 +290,6 @@ func (g *Graph) walk(walker GraphWalker) error {
return g.AcyclicGraph.Walk(walkFn) return g.AcyclicGraph.Walk(walkFn)
} }
// GraphNodeAnnotationInit is an interface that allows a node to
// initialize it's annotations.
//
// AnnotationInit will be called _once_ when the node is added to a
// graph for the first time and is expected to return it's initial
// annotations.
type GraphNodeAnnotationInit interface {
AnnotationInit() map[string]interface{}
}
// GraphNodeDependable is an interface which says that a node can be // GraphNodeDependable is an interface which says that a node can be
// depended on (an edge can be placed between this node and another) according // depended on (an edge can be placed between this node and another) according
// to the well-known name returned by DependableName. // to the well-known name returned by DependableName.

View File

@ -4,8 +4,6 @@ import (
"fmt" "fmt"
"log" "log"
"strings" "strings"
"github.com/hashicorp/terraform/config/module"
) )
// GraphBuilder is an interface that can be implemented and used with // GraphBuilder is an interface that can be implemented and used with
@ -77,160 +75,3 @@ func (b *BasicGraphBuilder) Build(path []string) (*Graph, error) {
return g, nil return g, nil
} }
// BuiltinGraphBuilder is responsible for building the complete graph that
// Terraform uses for execution. It is an opinionated builder that defines
// the step order required to build a complete graph as is used and expected
// by Terraform.
//
// If you require a custom graph, you'll have to build it up manually
// on your own by building a new GraphBuilder implementation.
type BuiltinGraphBuilder struct {
// Root is the root module of the graph to build.
Root *module.Tree
// Diff is the diff. The proper module diffs will be looked up.
Diff *Diff
// State is the global state. The proper module states will be looked
// up by graph path.
State *State
// Providers is the list of providers supported.
Providers []string
// Provisioners is the list of provisioners supported.
Provisioners []string
// Targets is the user-specified list of resources to target.
Targets []string
// Destroy is set to true when we're in a `terraform destroy` or a
// `terraform plan -destroy`
Destroy bool
// Determines whether the GraphBuilder should perform graph validation before
// returning the Graph. Generally you want this to be done, except when you'd
// like to inspect a problematic graph.
Validate bool
// Verbose is set to true when the graph should be built "worst case",
// skipping any prune steps. This is used for early cycle detection during
// Validate and for manual inspection via `terraform graph -verbose`.
Verbose bool
}
// Build builds the graph according to the steps returned by Steps.
func (b *BuiltinGraphBuilder) Build(path []string) (*Graph, error) {
basic := &BasicGraphBuilder{
Steps: b.Steps(path),
Validate: b.Validate,
Name: "BuiltinGraphBuilder",
}
return basic.Build(path)
}
// Steps returns the ordered list of GraphTransformers that must be executed
// to build a complete graph.
func (b *BuiltinGraphBuilder) Steps(path []string) []GraphTransformer {
steps := []GraphTransformer{
// Create all our resources from the configuration and state
&ConfigTransformerOld{Module: b.Root},
&OrphanTransformer{
State: b.State,
Module: b.Root,
},
// Output-related transformations
&AddOutputOrphanTransformer{State: b.State},
// Provider-related transformations
&MissingProviderTransformer{Providers: b.Providers},
&ProviderTransformer{},
&DisableProviderTransformerOld{},
// Provisioner-related transformations
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
&ProvisionerTransformer{},
// Run our vertex-level transforms
&VertexTransformer{
Transforms: []GraphVertexTransformer{
// Expand any statically expanded nodes, such as module graphs
&ExpandTransform{
Builder: b,
},
},
},
// Flatten stuff
&FlattenTransformer{},
// Make sure all the connections that are proxies are connected through
&ProxyTransformer{},
}
// If we're on the root path, then we do a bunch of other stuff.
// We don't do the following for modules.
if len(path) <= 1 {
steps = append(steps,
// Optionally reduces the graph to a user-specified list of targets and
// their dependencies.
&TargetsTransformer{Targets: b.Targets, Destroy: b.Destroy},
// Create orphan output nodes
&OrphanOutputTransformer{Module: b.Root, State: b.State},
// Prune the providers. This must happen only once because flattened
// modules might depend on empty providers.
&PruneProviderTransformer{},
// Create the destruction nodes
&DestroyTransformer{FullDestroy: b.Destroy},
b.conditional(&conditionalOpts{
If: func() bool { return !b.Destroy },
Then: &CreateBeforeDestroyTransformer{},
}),
b.conditional(&conditionalOpts{
If: func() bool { return !b.Verbose },
Then: &PruneDestroyTransformer{Diff: b.Diff, State: b.State},
}),
// Remove the noop nodes
&PruneNoopTransformer{Diff: b.Diff, State: b.State},
// Insert nodes to close opened plugin connections
&CloseProviderTransformer{},
&CloseProvisionerTransformer{},
// Perform the transitive reduction to make our graph a bit
// more sane if possible (it usually is possible).
&TransitiveReductionTransformer{},
)
}
// Make sure we have a single root
steps = append(steps, &RootTransformer{})
// Remove nils
for i, s := range steps {
if s == nil {
steps = append(steps[:i], steps[i+1:]...)
}
}
return steps
}
type conditionalOpts struct {
If func() bool
Then GraphTransformer
}
func (b *BuiltinGraphBuilder) conditional(o *conditionalOpts) GraphTransformer {
if o.If != nil && o.Then != nil && o.If() {
return o.Then
}
return nil
}

View File

@ -65,212 +65,6 @@ func TestBasicGraphBuilder_validateOff(t *testing.T) {
} }
} }
func TestBuiltinGraphBuilder_impl(t *testing.T) {
var _ GraphBuilder = new(BuiltinGraphBuilder)
}
// This test is not meant to test all the transforms but rather just
// to verify we get some basic sane graph out. Special tests to ensure
// specific ordering of steps should be added in other tests.
func TestBuiltinGraphBuilder(t *testing.T) {
b := &BuiltinGraphBuilder{
Root: testModule(t, "graph-builder-basic"),
Validate: true,
}
g, err := b.Build(RootModulePath)
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testBuiltinGraphBuilderBasicStr)
if actual != expected {
t.Fatalf("bad: %s", actual)
}
}
func TestBuiltinGraphBuilder_Verbose(t *testing.T) {
b := &BuiltinGraphBuilder{
Root: testModule(t, "graph-builder-basic"),
Validate: true,
Verbose: true,
}
g, err := b.Build(RootModulePath)
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testBuiltinGraphBuilderVerboseStr)
if actual != expected {
t.Fatalf("bad: %s", actual)
}
}
// This tests that the CreateBeforeDestoryTransformer is not present when
// we perform a "terraform destroy" operation. We don't actually do anything
// else.
func TestBuiltinGraphBuilder_CreateBeforeDestroy_Destroy_Bypass(t *testing.T) {
b := &BuiltinGraphBuilder{
Root: testModule(t, "graph-builder-basic"),
Validate: true,
Destroy: true,
}
steps := b.Steps([]string{})
actual := false
expected := false
for _, v := range steps {
switch v.(type) {
case *CreateBeforeDestroyTransformer:
actual = true
}
}
if actual != expected {
t.Fatalf("bad: CreateBeforeDestroyTransformer still in root path")
}
}
// This tests that the CreateBeforeDestoryTransformer *is* present
// during a non-destroy operation (ie: Destroy not set).
func TestBuiltinGraphBuilder_CreateBeforeDestroy_NonDestroy_Present(t *testing.T) {
b := &BuiltinGraphBuilder{
Root: testModule(t, "graph-builder-basic"),
Validate: true,
}
steps := b.Steps([]string{})
actual := false
expected := true
for _, v := range steps {
switch v.(type) {
case *CreateBeforeDestroyTransformer:
actual = true
}
}
if actual != expected {
t.Fatalf("bad: CreateBeforeDestroyTransformer not in root path")
}
}
// This tests a cycle we got when a CBD resource depends on a non-CBD
// resource. This cycle shouldn't happen in the general case anymore.
func TestBuiltinGraphBuilder_cbdDepNonCbd(t *testing.T) {
b := &BuiltinGraphBuilder{
Root: testModule(t, "graph-builder-cbd-non-cbd"),
Validate: true,
}
_, err := b.Build(RootModulePath)
if err != nil {
t.Fatalf("err: %s", err)
}
}
// This now returns no errors due to a general fix while building the graph
func TestBuiltinGraphBuilder_cbdDepNonCbd_errorsWhenVerbose(t *testing.T) {
b := &BuiltinGraphBuilder{
Root: testModule(t, "graph-builder-cbd-non-cbd"),
Validate: true,
Verbose: true,
}
_, err := b.Build(RootModulePath)
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestBuiltinGraphBuilder_multiLevelModule(t *testing.T) {
b := &BuiltinGraphBuilder{
Root: testModule(t, "graph-builder-multi-level-module"),
Validate: true,
}
g, err := b.Build(RootModulePath)
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testBuiltinGraphBuilderMultiLevelStr)
if actual != expected {
t.Fatalf("bad: %s", actual)
}
}
func TestBuiltinGraphBuilder_orphanDeps(t *testing.T) {
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "foo",
},
},
"aws_instance.bar": &ResourceState{
Type: "aws_instance",
Dependencies: []string{"aws_instance.foo"},
Primary: &InstanceState{
ID: "bar",
},
},
},
},
},
}
b := &BuiltinGraphBuilder{
Root: testModule(t, "graph-builder-orphan-deps"),
State: state,
Validate: true,
}
g, err := b.Build(RootModulePath)
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testBuiltinGraphBuilderOrphanDepsStr)
if actual != expected {
t.Fatalf("bad: %s", actual)
}
}
/*
TODO: This exposes a really bad bug we need to fix after we merge
the f-ast-branch. This bug still exists in master.
// This test tests that the graph builder properly expands modules.
func TestBuiltinGraphBuilder_modules(t *testing.T) {
b := &BuiltinGraphBuilder{
Root: testModule(t, "graph-builder-modules"),
}
g, err := b.Build(RootModulePath)
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testBuiltinGraphBuilderModuleStr)
if actual != expected {
t.Fatalf("bad: %s", actual)
}
}
*/
type testBasicGraphBuilderTransform struct { type testBasicGraphBuilderTransform struct {
V dag.Vertex V dag.Vertex
} }
@ -283,76 +77,3 @@ func (t *testBasicGraphBuilderTransform) Transform(g *Graph) error {
const testBasicGraphBuilderStr = ` const testBasicGraphBuilderStr = `
1 1
` `
const testBuiltinGraphBuilderBasicStr = `
aws_instance.db
provider.aws
aws_instance.web
aws_instance.db
provider.aws
provider.aws (close)
aws_instance.web
`
const testBuiltinGraphBuilderVerboseStr = `
aws_instance.db
aws_instance.db (destroy)
aws_instance.db (destroy)
aws_instance.web (destroy)
aws_instance.web
aws_instance.db
aws_instance.web (destroy)
provider.aws
provider.aws
provider.aws (close)
aws_instance.web
`
const testBuiltinGraphBuilderMultiLevelStr = `
module.foo.module.bar.output.value
module.foo.module.bar.var.bar
module.foo.var.foo
module.foo.module.bar.plan-destroy
module.foo.module.bar.var.bar
module.foo.var.foo
module.foo.plan-destroy
module.foo.var.foo
root
module.foo.module.bar.output.value
module.foo.module.bar.plan-destroy
module.foo.module.bar.var.bar
module.foo.plan-destroy
module.foo.var.foo
`
const testBuiltinGraphBuilderOrphanDepsStr = `
aws_instance.bar (orphan)
provider.aws
aws_instance.foo (orphan)
aws_instance.bar (orphan)
provider.aws
provider.aws (close)
aws_instance.foo (orphan)
`
/*
TODO: Commented out this const as it's likely this needs to
be updated when the TestBuiltinGraphBuilder_modules test is
enabled again.
const testBuiltinGraphBuilderModuleStr = `
aws_instance.web
aws_instance.web (destroy)
aws_instance.web (destroy)
aws_security_group.firewall
module.consul (expanded)
provider.aws
aws_security_group.firewall
aws_security_group.firewall (destroy)
aws_security_group.firewall (destroy)
provider.aws
module.consul (expanded)
aws_security_group.firewall
provider.aws
provider.aws
`
*/

View File

@ -1,37 +0,0 @@
package terraform
import (
"github.com/hashicorp/terraform/dag"
)
// graphNodeConfig is an interface that all graph nodes for the
// configuration graph need to implement in order to build the variable
// dependencies properly.
type graphNodeConfig interface {
dag.NamedVertex
// All graph nodes should be dependent on other things, and able to
// be depended on.
GraphNodeDependable
GraphNodeDependent
// ConfigType returns the type of thing in the configuration that
// this node represents, such as a resource, module, etc.
ConfigType() GraphNodeConfigType
}
// GraphNodeAddressable is an interface that all graph nodes for the
// configuration graph need to implement in order to be be addressed / targeted
// properly.
type GraphNodeAddressable interface {
ResourceAddress() *ResourceAddress
}
// GraphNodeTargetable is an interface for graph nodes to implement when they
// need to be told about incoming targets. This is useful for nodes that need
// to respect targets as they dynamically expand. Note that the list of targets
// provided will contain every target provided, and each implementing graph
// node must filter this list to targets considered relevant.
type GraphNodeTargetable interface {
SetTargets([]ResourceAddress)
}

View File

@ -1,215 +0,0 @@
package terraform
import (
"fmt"
"strings"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeConfigModule represents a module within the configuration graph.
type GraphNodeConfigModule struct {
Path []string
Module *config.Module
Tree *module.Tree
}
func (n *GraphNodeConfigModule) ConfigType() GraphNodeConfigType {
return GraphNodeConfigTypeModule
}
func (n *GraphNodeConfigModule) DependableName() []string {
config := n.Tree.Config()
result := make([]string, 1, len(config.Outputs)+1)
result[0] = n.Name()
for _, o := range config.Outputs {
result = append(result, fmt.Sprintf("%s.output.%s", n.Name(), o.Name))
}
return result
}
func (n *GraphNodeConfigModule) DependentOn() []string {
vars := n.Module.RawConfig.Variables
result := make([]string, 0, len(vars))
for _, v := range vars {
if vn := varNameForVar(v); vn != "" {
result = append(result, vn)
}
}
return result
}
func (n *GraphNodeConfigModule) Name() string {
return fmt.Sprintf("module.%s", n.Module.Name)
}
// GraphNodeExpandable
func (n *GraphNodeConfigModule) Expand(b GraphBuilder) (GraphNodeSubgraph, error) {
// Build the graph first
graph, err := b.Build(n.Path)
if err != nil {
return nil, err
}
{
// Add the destroy marker to the graph
t := &ModuleDestroyTransformerOld{}
if err := t.Transform(graph); err != nil {
return nil, err
}
}
// Build the actual subgraph node
return &graphNodeModuleExpanded{
Original: n,
Graph: graph,
Variables: make(map[string]interface{}),
}, nil
}
// GraphNodeExpandable
func (n *GraphNodeConfigModule) ProvidedBy() []string {
// Build up the list of providers by simply going over our configuration
// to find the providers that are configured there as well as the
// providers that the resources use.
config := n.Tree.Config()
providers := make(map[string]struct{})
for _, p := range config.ProviderConfigs {
providers[p.Name] = struct{}{}
}
for _, r := range config.Resources {
providers[resourceProvider(r.Type, r.Provider)] = struct{}{}
}
// Turn the map into a string. This makes sure that the list is
// de-dupped since we could be going over potentially many resources.
result := make([]string, 0, len(providers))
for p, _ := range providers {
result = append(result, p)
}
return result
}
// graphNodeModuleExpanded represents a module where the graph has
// been expanded. It stores the graph of the module as well as a reference
// to the map of variables.
type graphNodeModuleExpanded struct {
Original *GraphNodeConfigModule
Graph *Graph
// Variables is a map of the input variables. This reference should
// be shared with ModuleInputTransformer in order to create a connection
// where the variables are set properly.
Variables map[string]interface{}
}
func (n *graphNodeModuleExpanded) Name() string {
return fmt.Sprintf("%s (expanded)", dag.VertexName(n.Original))
}
func (n *graphNodeModuleExpanded) ConfigType() GraphNodeConfigType {
return GraphNodeConfigTypeModule
}
// GraphNodeDependable
func (n *graphNodeModuleExpanded) DependableName() []string {
return n.Original.DependableName()
}
// GraphNodeDependent
func (n *graphNodeModuleExpanded) DependentOn() []string {
return n.Original.DependentOn()
}
// GraphNodeDotter impl.
func (n *graphNodeModuleExpanded) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
return &dag.DotNode{
Name: name,
Attrs: map[string]string{
"label": dag.VertexName(n.Original),
"shape": "component",
},
}
}
// GraphNodeEvalable impl.
func (n *graphNodeModuleExpanded) EvalTree() EvalNode {
var resourceConfig *ResourceConfig
return &EvalSequence{
Nodes: []EvalNode{
&EvalInterpolate{
Config: n.Original.Module.RawConfig,
Output: &resourceConfig,
},
&EvalVariableBlock{
Config: &resourceConfig,
VariableValues: n.Variables,
},
},
}
}
// GraphNodeFlattenable impl.
func (n *graphNodeModuleExpanded) FlattenGraph() *Graph {
graph := n.Subgraph().(*Graph)
input := n.Original.Module.RawConfig
// Go over each vertex and do some modifications to the graph for
// flattening. We have to skip some nodes (graphNodeModuleSkippable)
// as well as setup the variable values.
for _, v := range graph.Vertices() {
// If this is a variable, then look it up in the raw configuration.
// If it exists in the raw configuration, set the value of it.
if vn, ok := v.(*GraphNodeConfigVariable); ok && input != nil {
key := vn.VariableName()
if v, ok := input.Raw[key]; ok {
config, err := config.NewRawConfig(map[string]interface{}{
key: v,
})
if err != nil {
// This shouldn't happen because it is already in
// a RawConfig above meaning it worked once before.
panic(err)
}
// Set the variable value so it is interpolated properly.
// Also set the module so we set the value on it properly.
vn.Module = graph.Path[len(graph.Path)-1]
vn.Value = config
}
}
}
return graph
}
// GraphNodeSubgraph impl.
func (n *graphNodeModuleExpanded) Subgraph() dag.Grapher {
return n.Graph
}
func modulePrefixStr(p []string) string {
parts := make([]string, 0, len(p)*2)
for _, p := range p[1:] {
parts = append(parts, "module", p)
}
return strings.Join(parts, ".")
}
func modulePrefixList(result []string, prefix string) []string {
if prefix != "" {
for i, v := range result {
result[i] = fmt.Sprintf("%s.%s", prefix, v)
}
}
return result
}

View File

@ -1,80 +0,0 @@
package terraform
import (
"strings"
"testing"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag"
)
func TestGraphNodeConfigModule_impl(t *testing.T) {
var _ dag.Vertex = new(GraphNodeConfigModule)
var _ dag.NamedVertex = new(GraphNodeConfigModule)
var _ graphNodeConfig = new(GraphNodeConfigModule)
var _ GraphNodeExpandable = new(GraphNodeConfigModule)
}
func TestGraphNodeConfigModuleExpand(t *testing.T) {
mod := testModule(t, "graph-node-module-expand")
node := &GraphNodeConfigModule{
Path: []string{RootModuleName, "child"},
Module: &config.Module{},
Tree: nil,
}
g, err := node.Expand(&BasicGraphBuilder{
Steps: []GraphTransformer{
&ConfigTransformerOld{Module: mod},
},
})
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.Subgraph().(*Graph).String())
expected := strings.TrimSpace(testGraphNodeModuleExpandStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestGraphNodeConfigModuleExpandFlatten(t *testing.T) {
mod := testModule(t, "graph-node-module-flatten")
node := &GraphNodeConfigModule{
Path: []string{RootModuleName, "child"},
Module: &config.Module{},
Tree: nil,
}
g, err := node.Expand(&BasicGraphBuilder{
Steps: []GraphTransformer{
&ConfigTransformerOld{Module: mod},
},
})
if err != nil {
t.Fatalf("err: %s", err)
}
fg := g.(GraphNodeFlatGraph)
actual := strings.TrimSpace(fg.FlattenGraph().String())
expected := strings.TrimSpace(testGraphNodeModuleExpandFlattenStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
const testGraphNodeModuleExpandStr = `
aws_instance.bar
aws_instance.foo
aws_instance.foo
plan-destroy
`
const testGraphNodeModuleExpandFlattenStr = `
aws_instance.foo
plan-destroy
`

View File

@ -1,106 +0,0 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeConfigOutput represents an output configured within the
// configuration.
type GraphNodeConfigOutput struct {
Output *config.Output
}
func (n *GraphNodeConfigOutput) Name() string {
return fmt.Sprintf("output.%s", n.Output.Name)
}
func (n *GraphNodeConfigOutput) ConfigType() GraphNodeConfigType {
return GraphNodeConfigTypeOutput
}
func (n *GraphNodeConfigOutput) OutputName() string {
return n.Output.Name
}
func (n *GraphNodeConfigOutput) DependableName() []string {
return []string{n.Name()}
}
func (n *GraphNodeConfigOutput) DependentOn() []string {
vars := n.Output.RawConfig.Variables
result := make([]string, 0, len(vars))
for _, v := range vars {
if vn := varNameForVar(v); vn != "" {
result = append(result, vn)
}
}
return result
}
// GraphNodeEvalable impl.
func (n *GraphNodeConfigOutput) EvalTree() EvalNode {
return &EvalOpFilter{
Ops: []walkOperation{walkRefresh, walkPlan, walkApply,
walkDestroy, walkInput, walkValidate},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalWriteOutput{
Name: n.Output.Name,
Sensitive: n.Output.Sensitive,
Value: n.Output.RawConfig,
},
},
},
}
}
// GraphNodeProxy impl.
func (n *GraphNodeConfigOutput) Proxy() bool {
return true
}
// GraphNodeDestroyEdgeInclude impl.
func (n *GraphNodeConfigOutput) DestroyEdgeInclude(dag.Vertex) bool {
return false
}
// GraphNodeFlattenable impl.
func (n *GraphNodeConfigOutput) Flatten(p []string) (dag.Vertex, error) {
return &GraphNodeConfigOutputFlat{
GraphNodeConfigOutput: n,
PathValue: p,
}, nil
}
// Same as GraphNodeConfigOutput, but for flattening
type GraphNodeConfigOutputFlat struct {
*GraphNodeConfigOutput
PathValue []string
}
func (n *GraphNodeConfigOutputFlat) Name() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue), n.GraphNodeConfigOutput.Name())
}
func (n *GraphNodeConfigOutputFlat) Path() []string {
return n.PathValue
}
func (n *GraphNodeConfigOutputFlat) DependableName() []string {
return modulePrefixList(
n.GraphNodeConfigOutput.DependableName(),
modulePrefixStr(n.PathValue))
}
func (n *GraphNodeConfigOutputFlat) DependentOn() []string {
prefix := modulePrefixStr(n.PathValue)
return modulePrefixList(
n.GraphNodeConfigOutput.DependentOn(),
prefix)
}

View File

@ -1,133 +0,0 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeConfigProvider represents a configured provider within the
// configuration graph. These are only immediately in the graph when an
// explicit `provider` configuration block is in the configuration.
type GraphNodeConfigProvider struct {
Provider *config.ProviderConfig
}
func (n *GraphNodeConfigProvider) Name() string {
return fmt.Sprintf("provider.%s", n.ProviderName())
}
func (n *GraphNodeConfigProvider) ConfigType() GraphNodeConfigType {
return GraphNodeConfigTypeProvider
}
func (n *GraphNodeConfigProvider) DependableName() []string {
return []string{n.Name()}
}
func (n *GraphNodeConfigProvider) DependentOn() []string {
vars := n.Provider.RawConfig.Variables
result := make([]string, 0, len(vars))
for _, v := range vars {
if vn := varNameForVar(v); vn != "" {
result = append(result, vn)
}
}
return result
}
// GraphNodeEvalable impl.
func (n *GraphNodeConfigProvider) EvalTree() EvalNode {
return ProviderEvalTree(n.ProviderName(), n.Provider.RawConfig)
}
// GraphNodeProvider implementation
func (n *GraphNodeConfigProvider) ProviderName() string {
if n.Provider.Alias == "" {
return n.Provider.Name
} else {
return fmt.Sprintf("%s.%s", n.Provider.Name, n.Provider.Alias)
}
}
// GraphNodeProvider implementation
func (n *GraphNodeConfigProvider) ProviderConfig() *config.RawConfig {
return n.Provider.RawConfig
}
// GraphNodeDotter impl.
func (n *GraphNodeConfigProvider) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
return &dag.DotNode{
Name: name,
Attrs: map[string]string{
"label": n.Name(),
"shape": "diamond",
},
}
}
// GraphNodeDotterOrigin impl.
func (n *GraphNodeConfigProvider) DotOrigin() bool {
return true
}
// GraphNodeFlattenable impl.
func (n *GraphNodeConfigProvider) Flatten(p []string) (dag.Vertex, error) {
return &GraphNodeConfigProviderFlat{
GraphNodeConfigProvider: n,
PathValue: p,
}, nil
}
// Same as GraphNodeConfigProvider, but for flattening
type GraphNodeConfigProviderFlat struct {
*GraphNodeConfigProvider
PathValue []string
}
func (n *GraphNodeConfigProviderFlat) Name() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue), n.GraphNodeConfigProvider.Name())
}
func (n *GraphNodeConfigProviderFlat) Path() []string {
return n.PathValue
}
func (n *GraphNodeConfigProviderFlat) DependableName() []string {
return modulePrefixList(
n.GraphNodeConfigProvider.DependableName(),
modulePrefixStr(n.PathValue))
}
func (n *GraphNodeConfigProviderFlat) DependentOn() []string {
prefixed := modulePrefixList(
n.GraphNodeConfigProvider.DependentOn(),
modulePrefixStr(n.PathValue))
result := make([]string, len(prefixed), len(prefixed)+1)
copy(result, prefixed)
// If we're in a module, then depend on our parent's provider
if len(n.PathValue) > 1 {
prefix := modulePrefixStr(n.PathValue[:len(n.PathValue)-1])
if prefix != "" {
prefix += "."
}
result = append(result, fmt.Sprintf(
"%s%s",
prefix, n.GraphNodeConfigProvider.Name()))
}
return result
}
func (n *GraphNodeConfigProviderFlat) ProviderName() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue),
n.GraphNodeConfigProvider.ProviderName())
}

View File

@ -1,539 +0,0 @@
package terraform
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeCountDependent is implemented by resources for giving only
// the dependencies they have from the "count" field.
type GraphNodeCountDependent interface {
CountDependentOn() []string
}
// GraphNodeConfigResource represents a resource within the config graph.
type GraphNodeConfigResource struct {
Resource *config.Resource
// If set to true, this resource represents a resource
// that will be destroyed in some way.
Destroy bool
// Used during DynamicExpand to target indexes
Targets []ResourceAddress
Path []string
}
func (n *GraphNodeConfigResource) Copy() *GraphNodeConfigResource {
ncr := &GraphNodeConfigResource{
Resource: n.Resource.Copy(),
Destroy: n.Destroy,
Targets: make([]ResourceAddress, 0, len(n.Targets)),
Path: make([]string, 0, len(n.Path)),
}
for _, t := range n.Targets {
ncr.Targets = append(ncr.Targets, *t.Copy())
}
for _, p := range n.Path {
ncr.Path = append(ncr.Path, p)
}
return ncr
}
func (n *GraphNodeConfigResource) ConfigType() GraphNodeConfigType {
return GraphNodeConfigTypeResource
}
func (n *GraphNodeConfigResource) DependableName() []string {
return []string{n.Resource.Id()}
}
// GraphNodeCountDependent impl.
func (n *GraphNodeConfigResource) CountDependentOn() []string {
result := make([]string, 0, len(n.Resource.RawCount.Variables))
for _, v := range n.Resource.RawCount.Variables {
if vn := varNameForVar(v); vn != "" {
result = append(result, vn)
}
}
return result
}
// GraphNodeDependent impl.
func (n *GraphNodeConfigResource) DependentOn() []string {
result := make([]string, len(n.Resource.DependsOn),
(len(n.Resource.RawCount.Variables)+
len(n.Resource.RawConfig.Variables)+
len(n.Resource.DependsOn))*2)
copy(result, n.Resource.DependsOn)
for _, v := range n.Resource.RawCount.Variables {
if vn := varNameForVar(v); vn != "" {
result = append(result, vn)
}
}
for _, v := range n.Resource.RawConfig.Variables {
if vn := varNameForVar(v); vn != "" {
result = append(result, vn)
}
}
for _, p := range n.Resource.Provisioners {
for _, v := range p.ConnInfo.Variables {
if vn := varNameForVar(v); vn != "" && vn != n.Resource.Id() {
result = append(result, vn)
}
}
for _, v := range p.RawConfig.Variables {
if vn := varNameForVar(v); vn != "" && vn != n.Resource.Id() {
result = append(result, vn)
}
}
}
return result
}
// VarWalk calls a callback for all the variables that this resource
// depends on.
func (n *GraphNodeConfigResource) VarWalk(fn func(config.InterpolatedVariable)) {
for _, v := range n.Resource.RawCount.Variables {
fn(v)
}
for _, v := range n.Resource.RawConfig.Variables {
fn(v)
}
for _, p := range n.Resource.Provisioners {
for _, v := range p.ConnInfo.Variables {
fn(v)
}
for _, v := range p.RawConfig.Variables {
fn(v)
}
}
}
func (n *GraphNodeConfigResource) Name() string {
result := n.Resource.Id()
if n.Destroy {
result += " (destroy)"
}
return result
}
// GraphNodeDotter impl.
func (n *GraphNodeConfigResource) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
if n.Destroy && !opts.Verbose {
return nil
}
return &dag.DotNode{
Name: name,
Attrs: map[string]string{
"label": n.Name(),
"shape": "box",
},
}
}
// GraphNodeFlattenable impl.
func (n *GraphNodeConfigResource) Flatten(p []string) (dag.Vertex, error) {
return &GraphNodeConfigResourceFlat{
GraphNodeConfigResource: n,
PathValue: p,
}, nil
}
// GraphNodeDynamicExpandable impl.
func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
state, lock := ctx.State()
lock.RLock()
defer lock.RUnlock()
// Start creating the steps
steps := make([]GraphTransformer, 0, 5)
// Expand counts.
steps = append(steps, &ResourceCountTransformerOld{
Resource: n.Resource,
Destroy: n.Destroy,
Targets: n.Targets,
})
// Additional destroy modifications.
if n.Destroy {
// If we're destroying a primary or tainted resource, we want to
// expand orphans, which have all the same semantics in a destroy
// as a primary or tainted resource.
steps = append(steps, &OrphanTransformer{
Resource: n.Resource,
State: state,
View: n.Resource.Id(),
})
steps = append(steps, &DeposedTransformer{
State: state,
View: n.Resource.Id(),
})
}
// We always want to apply targeting
steps = append(steps, &TargetsTransformer{
ParsedTargets: n.Targets,
Destroy: n.Destroy,
})
// Always end with the root being added
steps = append(steps, &RootTransformer{})
// Build the graph
b := &BasicGraphBuilder{
Steps: steps,
Validate: true,
Name: "GraphNodeConfigResource",
}
return b.Build(ctx.Path())
}
// GraphNodeAddressable impl.
func (n *GraphNodeConfigResource) ResourceAddress() *ResourceAddress {
return &ResourceAddress{
Path: n.Path[1:],
Index: -1,
InstanceType: TypePrimary,
Name: n.Resource.Name,
Type: n.Resource.Type,
Mode: n.Resource.Mode,
}
}
// GraphNodeTargetable impl.
func (n *GraphNodeConfigResource) SetTargets(targets []ResourceAddress) {
n.Targets = targets
}
// GraphNodeEvalable impl.
func (n *GraphNodeConfigResource) EvalTree() EvalNode {
return &EvalSequence{
Nodes: []EvalNode{
&EvalInterpolate{Config: n.Resource.RawCount},
&EvalCountCheckComputed{Resource: n.Resource},
&EvalOpFilter{
Ops: []walkOperation{walkValidate},
Node: &EvalValidateCount{Resource: n.Resource},
},
&EvalCountFixZeroOneBoundary{Resource: n.Resource},
},
}
}
// GraphNodeProviderConsumer
func (n *GraphNodeConfigResource) ProvidedBy() []string {
return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)}
}
// GraphNodeProvisionerConsumer
func (n *GraphNodeConfigResource) ProvisionedBy() []string {
result := make([]string, len(n.Resource.Provisioners))
for i, p := range n.Resource.Provisioners {
result[i] = p.Type
}
return result
}
// GraphNodeDestroyable
func (n *GraphNodeConfigResource) DestroyNode() GraphNodeDestroy {
// If we're already a destroy node, then don't do anything
if n.Destroy {
return nil
}
result := &graphNodeResourceDestroy{
GraphNodeConfigResource: *n.Copy(),
Original: n,
}
result.Destroy = true
return result
}
// GraphNodeNoopPrunable
func (n *GraphNodeConfigResource) Noop(opts *NoopOpts) bool {
log.Printf("[DEBUG] Checking resource noop: %s", n.Name())
// We don't have any noop optimizations for destroy nodes yet
if n.Destroy {
log.Printf("[DEBUG] Destroy node, not a noop")
return false
}
// If there is no diff, then we aren't a noop since something needs to
// be done (such as a plan). We only check if we're a noop in a diff.
if opts.Diff == nil || opts.Diff.Empty() {
log.Printf("[DEBUG] No diff, not a noop")
return false
}
// If the count has any interpolations, we can't prune this node since
// we need to be sure to evaluate the count so that splat variables work
// later (which need to know the full count).
if len(n.Resource.RawCount.Interpolations) > 0 {
log.Printf("[DEBUG] Count has interpolations, not a noop")
return false
}
// If we have no module diff, we're certainly a noop. This is because
// it means there is a diff, and that the module we're in just isn't
// in it, meaning we're not doing anything.
if opts.ModDiff == nil || opts.ModDiff.Empty() {
log.Printf("[DEBUG] No mod diff, treating resource as a noop")
return true
}
// Grab the ID which is the prefix (in the case count > 0 at some point)
prefix := n.Resource.Id()
// Go through the diff and if there are any with our name on it, keep us
found := false
for k, _ := range opts.ModDiff.Resources {
if strings.HasPrefix(k, prefix) {
log.Printf("[DEBUG] Diff has %s, resource is not a noop", k)
found = true
break
}
}
log.Printf("[DEBUG] Final noop value: %t", !found)
return !found
}
// Same as GraphNodeConfigResource, but for flattening
type GraphNodeConfigResourceFlat struct {
*GraphNodeConfigResource
PathValue []string
}
func (n *GraphNodeConfigResourceFlat) Name() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue), n.GraphNodeConfigResource.Name())
}
func (n *GraphNodeConfigResourceFlat) Path() []string {
return n.PathValue
}
func (n *GraphNodeConfigResourceFlat) DependableName() []string {
return modulePrefixList(
n.GraphNodeConfigResource.DependableName(),
modulePrefixStr(n.PathValue))
}
func (n *GraphNodeConfigResourceFlat) DependentOn() []string {
prefix := modulePrefixStr(n.PathValue)
return modulePrefixList(
n.GraphNodeConfigResource.DependentOn(),
prefix)
}
func (n *GraphNodeConfigResourceFlat) ProvidedBy() []string {
prefix := modulePrefixStr(n.PathValue)
return modulePrefixList(
n.GraphNodeConfigResource.ProvidedBy(),
prefix)
}
func (n *GraphNodeConfigResourceFlat) ProvisionedBy() []string {
prefix := modulePrefixStr(n.PathValue)
return modulePrefixList(
n.GraphNodeConfigResource.ProvisionedBy(),
prefix)
}
// GraphNodeDestroyable impl.
func (n *GraphNodeConfigResourceFlat) DestroyNode() GraphNodeDestroy {
// Get our parent destroy node. If we don't have any, just return
raw := n.GraphNodeConfigResource.DestroyNode()
if raw == nil {
return nil
}
node, ok := raw.(*graphNodeResourceDestroy)
if !ok {
panic(fmt.Sprintf("unknown destroy node: %s %T", dag.VertexName(raw), raw))
}
// Otherwise, wrap it so that it gets the proper module treatment.
return &graphNodeResourceDestroyFlat{
graphNodeResourceDestroy: node,
PathValue: n.PathValue,
FlatCreateNode: n,
}
}
type graphNodeResourceDestroyFlat struct {
*graphNodeResourceDestroy
PathValue []string
// Needs to be able to properly yield back a flattened create node to prevent
FlatCreateNode *GraphNodeConfigResourceFlat
}
func (n *graphNodeResourceDestroyFlat) Name() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeResourceDestroy.Name())
}
func (n *graphNodeResourceDestroyFlat) Path() []string {
return n.PathValue
}
func (n *graphNodeResourceDestroyFlat) CreateNode() dag.Vertex {
return n.FlatCreateNode
}
func (n *graphNodeResourceDestroyFlat) ProvidedBy() []string {
prefix := modulePrefixStr(n.PathValue)
return modulePrefixList(
n.GraphNodeConfigResource.ProvidedBy(),
prefix)
}
// graphNodeResourceDestroy represents the logical destruction of a
// resource. This node doesn't mean it will be destroyed for sure, but
// instead that if a destroy were to happen, it must happen at this point.
type graphNodeResourceDestroy struct {
GraphNodeConfigResource
Original *GraphNodeConfigResource
}
func (n *graphNodeResourceDestroy) CreateBeforeDestroy() bool {
// CBD is enabled if the resource enables it
return n.Original.Resource.Lifecycle.CreateBeforeDestroy && n.Destroy
}
func (n *graphNodeResourceDestroy) CreateNode() dag.Vertex {
return n.Original
}
func (n *graphNodeResourceDestroy) DestroyInclude(d *ModuleDiff, s *ModuleState) bool {
if n.Destroy {
return n.destroyInclude(d, s)
}
return true
}
func (n *graphNodeResourceDestroy) destroyInclude(
d *ModuleDiff, s *ModuleState) bool {
// Get the count, and specifically the raw value of the count
// (with interpolations and all). If the count is NOT a static "1",
// then we keep the destroy node no matter what.
//
// The reasoning for this is complicated and not intuitively obvious,
// but I attempt to explain it below.
//
// The destroy transform works by generating the worst case graph,
// with worst case being the case that every resource already exists
// and needs to be destroy/created (force-new). There is a single important
// edge case where this actually results in a real-life cycle: if a
// create-before-destroy (CBD) resource depends on a non-CBD resource.
// Imagine a EC2 instance "foo" with CBD depending on a security
// group "bar" without CBD, and conceptualize the worst case destroy
// order:
//
// 1.) SG must be destroyed (non-CBD)
// 2.) SG must be created/updated
// 3.) EC2 instance must be created (CBD, requires the SG be made)
// 4.) EC2 instance must be destroyed (requires SG be destroyed)
//
// Except, #1 depends on #4, since the SG can't be destroyed while
// an EC2 instance is using it (AWS API requirements). As you can see,
// this is a real life cycle that can't be automatically reconciled
// except under two conditions:
//
// 1.) SG is also CBD. This doesn't work 100% of the time though
// since the non-CBD resource might not support CBD. To make matters
// worse, the entire transitive closure of dependencies must be
// CBD (if the SG depends on a VPC, you have the same problem).
// 2.) EC2 must not CBD. This can't happen automatically because CBD
// is used as a way to ensure zero (or minimal) downtime Terraform
// applies, and it isn't acceptable for TF to ignore this request,
// since it can result in unexpected downtime.
//
// Therefore, we compromise with this edge case here: if there is
// a static count of "1", we prune the diff to remove cycles during a
// graph optimization path if we don't see the resource in the diff.
// If the count is set to ANYTHING other than a static "1" (variable,
// computed attribute, static number greater than 1), then we keep the
// destroy, since it is required for dynamic graph expansion to find
// orphan count objects.
//
// This isn't ideal logic, but its strictly better without introducing
// new impossibilities. It breaks the cycle in practical cases, and the
// cycle comes back in no cases we've found to be practical, but just
// as the cycle would already exist without this anyways.
count := n.Original.Resource.RawCount
if raw := count.Raw[count.Key]; raw != "1" {
return true
}
// Okay, we're dealing with a static count. There are a few ways
// to include this resource.
prefix := n.Original.Resource.Id()
// If we're present in the diff proper, then keep it. We're looking
// only for resources in the diff that match our resource or a count-index
// of our resource that are marked for destroy.
if d != nil {
for k, v := range d.Resources {
match := k == prefix || strings.HasPrefix(k, prefix+".")
if match && v.GetDestroy() {
return true
}
}
}
// If we're in the state as a primary in any form, then keep it.
// This does a prefix check so it will also catch orphans on count
// decreases to "1".
if s != nil {
for k, v := range s.Resources {
// Ignore exact matches
if k == prefix {
continue
}
// Ignore anything that doesn't have a "." afterwards so that
// we only get our own resource and any counts on it.
if !strings.HasPrefix(k, prefix+".") {
continue
}
// Ignore exact matches and the 0'th index. We only care
// about if there is a decrease in count.
if k == prefix+".0" {
continue
}
if v.Primary != nil {
return true
}
}
// If we're in the state as _both_ "foo" and "foo.0", then
// keep it, since we treat the latter as an orphan.
_, okOne := s.Resources[prefix]
_, okTwo := s.Resources[prefix+".0"]
if okOne && okTwo {
return true
}
}
return false
}

View File

@ -1,110 +0,0 @@
package terraform
import (
"reflect"
"testing"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag"
)
func TestGraphNodeConfigOutput_impl(t *testing.T) {
var _ dag.Vertex = new(GraphNodeConfigOutput)
var _ dag.NamedVertex = new(GraphNodeConfigOutput)
var _ graphNodeConfig = new(GraphNodeConfigOutput)
var _ GraphNodeOutput = new(GraphNodeConfigOutput)
var _ GraphNodeProxy = new(GraphNodeConfigOutput)
}
func TestGraphNodeConfigProvider_impl(t *testing.T) {
var _ dag.Vertex = new(GraphNodeConfigProvider)
var _ dag.NamedVertex = new(GraphNodeConfigProvider)
var _ graphNodeConfig = new(GraphNodeConfigProvider)
var _ GraphNodeProvider = new(GraphNodeConfigProvider)
}
func TestGraphNodeConfigProvider_ProviderName(t *testing.T) {
n := &GraphNodeConfigProvider{
Provider: &config.ProviderConfig{Name: "foo"},
}
if v := n.ProviderName(); v != "foo" {
t.Fatalf("bad: %#v", v)
}
}
func TestGraphNodeConfigProvider_ProviderName_alias(t *testing.T) {
n := &GraphNodeConfigProvider{
Provider: &config.ProviderConfig{Name: "foo", Alias: "bar"},
}
if v := n.ProviderName(); v != "foo.bar" {
t.Fatalf("bad: %#v", v)
}
}
func TestGraphNodeConfigProvider_Name(t *testing.T) {
n := &GraphNodeConfigProvider{
Provider: &config.ProviderConfig{Name: "foo"},
}
if v := n.Name(); v != "provider.foo" {
t.Fatalf("bad: %#v", v)
}
}
func TestGraphNodeConfigProvider_Name_alias(t *testing.T) {
n := &GraphNodeConfigProvider{
Provider: &config.ProviderConfig{Name: "foo", Alias: "bar"},
}
if v := n.Name(); v != "provider.foo.bar" {
t.Fatalf("bad: %#v", v)
}
}
func TestGraphNodeConfigResource_impl(t *testing.T) {
var _ dag.Vertex = new(GraphNodeConfigResource)
var _ dag.NamedVertex = new(GraphNodeConfigResource)
var _ graphNodeConfig = new(GraphNodeConfigResource)
var _ GraphNodeProviderConsumer = new(GraphNodeConfigResource)
var _ GraphNodeProvisionerConsumer = new(GraphNodeConfigResource)
}
func TestGraphNodeConfigResource_ProvidedBy(t *testing.T) {
n := &GraphNodeConfigResource{
Resource: &config.Resource{Type: "aws_instance"},
}
if v := n.ProvidedBy(); v[0] != "aws" {
t.Fatalf("bad: %#v", v)
}
}
func TestGraphNodeConfigResource_ProvidedBy_alias(t *testing.T) {
n := &GraphNodeConfigResource{
Resource: &config.Resource{Type: "aws_instance", Provider: "aws.bar"},
}
if v := n.ProvidedBy(); v[0] != "aws.bar" {
t.Fatalf("bad: %#v", v)
}
}
func TestGraphNodeConfigResource_ProvisionedBy(t *testing.T) {
n := &GraphNodeConfigResource{
Resource: &config.Resource{
Type: "aws_instance",
Provisioners: []*config.Provisioner{
&config.Provisioner{Type: "foo"},
&config.Provisioner{Type: "bar"},
},
},
}
expected := []string{"foo", "bar"}
actual := n.ProvisionedBy()
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}

View File

@ -1,16 +0,0 @@
package terraform
//go:generate stringer -type=GraphNodeConfigType graph_config_node_type.go
// GraphNodeConfigType is an enum for the type of thing that a graph
// node represents from the configuration.
type GraphNodeConfigType int
const (
GraphNodeConfigTypeInvalid GraphNodeConfigType = 0
GraphNodeConfigTypeResource GraphNodeConfigType = iota
GraphNodeConfigTypeProvider
GraphNodeConfigTypeModule
GraphNodeConfigTypeOutput
GraphNodeConfigTypeVariable
)

View File

@ -1,274 +0,0 @@
package terraform
import (
"fmt"
"log"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeConfigVariable represents a Variable in the config.
type GraphNodeConfigVariable struct {
Variable *config.Variable
// Value, if non-nil, will be used to set the value of the variable
// during evaluation. If this is nil, evaluation will do nothing.
//
// Module is the name of the module to set the variables on.
Module string
Value *config.RawConfig
ModuleTree *module.Tree
ModulePath []string
}
func (n *GraphNodeConfigVariable) Name() string {
return fmt.Sprintf("var.%s", n.Variable.Name)
}
func (n *GraphNodeConfigVariable) ConfigType() GraphNodeConfigType {
return GraphNodeConfigTypeVariable
}
func (n *GraphNodeConfigVariable) DependableName() []string {
return []string{n.Name()}
}
// RemoveIfNotTargeted implements RemovableIfNotTargeted.
// When targeting is active, variables that are not targeted should be removed
// from the graph, because otherwise module variables trying to interpolate
// their references can fail when they're missing the referent resource node.
func (n *GraphNodeConfigVariable) RemoveIfNotTargeted() bool {
return true
}
func (n *GraphNodeConfigVariable) DependentOn() []string {
// If we don't have any value set, we don't depend on anything
if n.Value == nil {
return nil
}
// Get what we depend on based on our value
vars := n.Value.Variables
result := make([]string, 0, len(vars))
for _, v := range vars {
if vn := varNameForVar(v); vn != "" {
result = append(result, vn)
}
}
return result
}
func (n *GraphNodeConfigVariable) VariableName() string {
return n.Variable.Name
}
// GraphNodeDestroyEdgeInclude impl.
func (n *GraphNodeConfigVariable) DestroyEdgeInclude(v dag.Vertex) bool {
// Only include this variable in a destroy edge if the source vertex
// "v" has a count dependency on this variable.
log.Printf("[DEBUG] DestroyEdgeInclude: Checking: %s", dag.VertexName(v))
cv, ok := v.(GraphNodeCountDependent)
if !ok {
log.Printf("[DEBUG] DestroyEdgeInclude: Not GraphNodeCountDependent: %s", dag.VertexName(v))
return false
}
for _, d := range cv.CountDependentOn() {
for _, d2 := range n.DependableName() {
log.Printf("[DEBUG] DestroyEdgeInclude: d = %s : d2 = %s", d, d2)
if d == d2 {
return true
}
}
}
return false
}
// GraphNodeNoopPrunable
func (n *GraphNodeConfigVariable) Noop(opts *NoopOpts) bool {
log.Printf("[DEBUG] Checking variable noop: %s", n.Name())
// If we have no diff, always keep this in the graph. We have to do
// this primarily for validation: we want to validate that variable
// interpolations are valid even if there are no resources that
// depend on them.
if opts.Diff == nil || opts.Diff.Empty() {
log.Printf("[DEBUG] No diff, not a noop")
return false
}
// We have to find our our module diff since we do funky things with
// the flat node's implementation of Path() below.
modDiff := opts.Diff.ModuleByPath(n.ModulePath)
// If we're destroying, we have no need of variables unless they are depended
// on by the count of a resource.
if modDiff != nil && modDiff.Destroy {
if n.hasDestroyEdgeInPath(opts, nil) {
log.Printf("[DEBUG] Variable has destroy edge from %s, not a noop",
dag.VertexName(opts.Vertex))
return false
}
log.Printf("[DEBUG] Variable has no included destroy edges: noop!")
return true
}
for _, v := range opts.Graph.UpEdges(opts.Vertex).List() {
// This is terrible, but I can't think of a better way to do this.
if dag.VertexName(v) == rootNodeName {
continue
}
log.Printf("[DEBUG] Found up edge to %s, var is not noop", dag.VertexName(v))
return false
}
log.Printf("[DEBUG] No up edges, treating variable as a noop")
return true
}
// hasDestroyEdgeInPath recursively walks for a destroy edge, ensuring that
// a variable both has no immediate destroy edges or any in its full module
// path, ensuring that links do not get severed in the middle.
func (n *GraphNodeConfigVariable) hasDestroyEdgeInPath(opts *NoopOpts, vertex dag.Vertex) bool {
if vertex == nil {
vertex = opts.Vertex
}
log.Printf("[DEBUG] hasDestroyEdgeInPath: Looking for destroy edge: %s - %T", dag.VertexName(vertex), vertex)
for _, v := range opts.Graph.UpEdges(vertex).List() {
if len(opts.Graph.UpEdges(v).List()) > 1 {
if n.hasDestroyEdgeInPath(opts, v) == true {
return true
}
}
// Here we borrow the implementation of DestroyEdgeInclude, whose logic
// and semantics are exactly what we want here. We add a check for the
// the root node, since we have to always depend on its existance.
if cv, ok := vertex.(*GraphNodeConfigVariableFlat); ok {
if dag.VertexName(v) == rootNodeName || cv.DestroyEdgeInclude(v) {
return true
}
}
}
return false
}
// GraphNodeProxy impl.
func (n *GraphNodeConfigVariable) Proxy() bool {
return true
}
// GraphNodeEvalable impl.
func (n *GraphNodeConfigVariable) EvalTree() EvalNode {
// If we have no value, do nothing
if n.Value == nil {
return &EvalNoop{}
}
// Otherwise, interpolate the value of this variable and set it
// within the variables mapping.
var config *ResourceConfig
variables := make(map[string]interface{})
return &EvalSequence{
Nodes: []EvalNode{
&EvalInterpolate{
Config: n.Value,
Output: &config,
},
&EvalVariableBlock{
Config: &config,
VariableValues: variables,
},
&EvalCoerceMapVariable{
Variables: variables,
ModulePath: n.ModulePath,
ModuleTree: n.ModuleTree,
},
&EvalTypeCheckVariable{
Variables: variables,
ModulePath: n.ModulePath,
ModuleTree: n.ModuleTree,
},
&EvalSetVariables{
Module: &n.Module,
Variables: variables,
},
},
}
}
// GraphNodeFlattenable impl.
func (n *GraphNodeConfigVariable) Flatten(p []string) (dag.Vertex, error) {
return &GraphNodeConfigVariableFlat{
GraphNodeConfigVariable: n,
PathValue: p,
}, nil
}
type GraphNodeConfigVariableFlat struct {
*GraphNodeConfigVariable
PathValue []string
}
func (n *GraphNodeConfigVariableFlat) Name() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue), n.GraphNodeConfigVariable.Name())
}
func (n *GraphNodeConfigVariableFlat) DependableName() []string {
return []string{n.Name()}
}
func (n *GraphNodeConfigVariableFlat) DependentOn() []string {
// We only wrap the dependencies and such if we have a path that is
// longer than 2 elements (root, child, more). This is because when
// flattened, variables can point outside the graph.
prefix := ""
if len(n.PathValue) > 2 {
prefix = modulePrefixStr(n.PathValue[:len(n.PathValue)-1])
}
return modulePrefixList(
n.GraphNodeConfigVariable.DependentOn(),
prefix)
}
func (n *GraphNodeConfigVariableFlat) Path() []string {
if len(n.PathValue) > 2 {
return n.PathValue[:len(n.PathValue)-1]
}
return nil
}
func (n *GraphNodeConfigVariableFlat) Noop(opts *NoopOpts) bool {
// First look for provider nodes that depend on this variable downstream
modDiff := opts.Diff.ModuleByPath(n.ModulePath)
if modDiff != nil && modDiff.Destroy {
ds, err := opts.Graph.Descendents(n)
if err != nil {
log.Printf("[ERROR] Error looking up descendents of %s: %s", n.Name(), err)
} else {
for _, d := range ds.List() {
if _, ok := d.(GraphNodeProvider); ok {
log.Printf("[DEBUG] This variable is depended on by a provider, can't be a noop.")
return false
}
}
}
}
// Then fall back to existing impl
return n.GraphNodeConfigVariable.Noop(opts)
}

View File

@ -1,21 +0,0 @@
package terraform
import (
"testing"
"github.com/hashicorp/terraform/dag"
)
func TestGraphNodeConfigVariable_impl(t *testing.T) {
var _ dag.Vertex = new(GraphNodeConfigVariable)
var _ dag.NamedVertex = new(GraphNodeConfigVariable)
var _ graphNodeConfig = new(GraphNodeConfigVariable)
var _ GraphNodeProxy = new(GraphNodeConfigVariable)
}
func TestGraphNodeConfigVariableFlat_impl(t *testing.T) {
var _ dag.Vertex = new(GraphNodeConfigVariableFlat)
var _ dag.NamedVertex = new(GraphNodeConfigVariableFlat)
var _ graphNodeConfig = new(GraphNodeConfigVariableFlat)
var _ GraphNodeProxy = new(GraphNodeConfigVariableFlat)
}

View File

@ -1,16 +0,0 @@
// Code generated by "stringer -type=GraphNodeConfigType graph_config_node_type.go"; DO NOT EDIT
package terraform
import "fmt"
const _GraphNodeConfigType_name = "GraphNodeConfigTypeInvalidGraphNodeConfigTypeResourceGraphNodeConfigTypeProviderGraphNodeConfigTypeModuleGraphNodeConfigTypeOutputGraphNodeConfigTypeVariable"
var _GraphNodeConfigType_index = [...]uint8{0, 26, 53, 80, 105, 130, 157}
func (i GraphNodeConfigType) String() string {
if i < 0 || i >= GraphNodeConfigType(len(_GraphNodeConfigType_index)-1) {
return fmt.Sprintf("GraphNodeConfigType(%d)", i)
}
return _GraphNodeConfigType_name[_GraphNodeConfigType_index[i]:_GraphNodeConfigType_index[i+1]]
}

View File

@ -92,21 +92,10 @@ func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode {
// If the config isn't empty we update the state // If the config isn't empty we update the state
if n.Config != nil { if n.Config != nil {
// 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,
Index: addr.Index,
}
stateDeps = oldN.StateDependencies()
}
rs = &ResourceState{ rs = &ResourceState{
Type: n.Config.Type, Type: n.Config.Type,
Provider: n.Config.Provider, Provider: n.Config.Provider,
Dependencies: stateDeps, Dependencies: n.StateReferences(),
} }
} }

View File

@ -0,0 +1,44 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/config"
)
// NodeProvisioner represents a provider that has no associated operations.
// It registers all the common interfaces across operations for providers.
type NodeProvisioner struct {
NameValue string
PathValue []string
// The fields below will be automatically set using the Attach
// interfaces if you're running those transforms, but also be explicitly
// set if you already have that information.
Config *config.ProviderConfig
}
func (n *NodeProvisioner) Name() string {
result := fmt.Sprintf("provisioner.%s", n.NameValue)
if len(n.PathValue) > 1 {
result = fmt.Sprintf("%s.%s", modulePrefixStr(n.PathValue), result)
}
return result
}
// GraphNodeSubPath
func (n *NodeProvisioner) Path() []string {
return n.PathValue
}
// GraphNodeProvisioner
func (n *NodeProvisioner) ProvisionerName() string {
return n.NameValue
}
// GraphNodeEvalable impl.
func (n *NodeProvisioner) EvalTree() EvalNode {
return &EvalInitProvisioner{Name: n.NameValue}
}

View File

@ -2,6 +2,7 @@ package terraform
import ( import (
"fmt" "fmt"
"strings"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/dag"
@ -109,6 +110,63 @@ func (n *NodeAbstractResource) References() []string {
return nil return nil
} }
// StateReferences returns the dependencies to put into the state for
// this resource.
func (n *NodeAbstractResource) StateReferences() []string {
self := n.ReferenceableName()
// Determine what our "prefix" is for checking for references to
// ourself.
addrCopy := n.Addr.Copy()
addrCopy.Index = -1
selfPrefix := addrCopy.String() + "."
depsRaw := n.References()
deps := make([]string, 0, len(depsRaw))
for _, d := range depsRaw {
// Ignore any variable dependencies
if strings.HasPrefix(d, "var.") {
continue
}
// If this has a backup ref, ignore those for now. The old state
// file never contained those and I'd rather store the rich types we
// add in the future.
if idx := strings.IndexRune(d, '/'); idx != -1 {
d = d[:idx]
}
// If we're referencing ourself, then ignore it
found := false
for _, s := range self {
if d == s {
found = true
}
}
if found {
continue
}
// If this is a reference to ourself and a specific index, we keep
// it. For example, if this resource is "foo.bar" and the reference
// is "foo.bar.0" then we keep it exact. Otherwise, we strip it.
if strings.HasSuffix(d, ".0") && !strings.HasPrefix(d, selfPrefix) {
d = d[:len(d)-2]
}
// This is sad. The dependencies are currently in the format of
// "module.foo.bar" (the full field). This strips the field off.
if strings.HasPrefix(d, "module.") {
parts := strings.SplitN(d, ".", 3)
d = strings.Join(parts[0:2], ".")
}
deps = append(deps, d)
}
return deps
}
// GraphNodeProviderConsumer // GraphNodeProviderConsumer
func (n *NodeAbstractResource) ProvidedBy() []string { func (n *NodeAbstractResource) ProvidedBy() []string {
// If we have a config we prefer that above all else // If we have a config we prefer that above all else

View File

@ -70,16 +70,8 @@ func (n *NodeApplyableResource) EvalTree() EvalNode {
resource.CountIndex = 0 resource.CountIndex = 0
} }
// Determine the dependencies for the state. We use some older // Determine the dependencies for the state.
// code for this that we've used for a long time. stateDeps := n.StateReferences()
var stateDeps []string
{
oldN := &graphNodeExpandedResource{
Resource: n.Config,
Index: addr.Index,
}
stateDeps = oldN.StateDependencies()
}
// Eval info is different depending on what kind of resource this is // Eval info is different depending on what kind of resource this is
switch n.Config.Mode { switch n.Config.Mode {

View File

@ -37,13 +37,8 @@ func (n *NodePlannableResourceInstance) EvalTree() EvalNode {
resource.CountIndex = 0 resource.CountIndex = 0
} }
// Determine the dependencies for the state. We use some older // Determine the dependencies for the state.
// code for this that we've used for a long time. stateDeps := n.StateReferences()
var stateDeps []string
{
oldN := &graphNodeExpandedResource{Resource: n.Config}
stateDeps = oldN.StateDependencies()
}
// Eval info is different depending on what kind of resource this is // Eval info is different depending on what kind of resource this is
switch n.Config.Mode { switch n.Config.Mode {

View File

@ -49,25 +49,6 @@ type SemanticChecker interface {
Check(*dag.Graph, dag.Vertex) error Check(*dag.Graph, dag.Vertex) error
} }
// SemanticCheckModulesExist is an implementation of SemanticChecker that
// verifies that all the modules that are referenced in the graph exist.
type SemanticCheckModulesExist struct{}
// TODO: test
func (*SemanticCheckModulesExist) Check(g *dag.Graph, v dag.Vertex) error {
mn, ok := v.(*GraphNodeConfigModule)
if !ok {
return nil
}
if mn.Tree == nil {
return fmt.Errorf(
"module '%s' not found", mn.Module.Name)
}
return nil
}
// smcUserVariables does all the semantic checks to verify that the // smcUserVariables does all the semantic checks to verify that the
// variables given satisfy the configuration itself. // variables given satisfy the configuration itself.
func smcUserVariables(c *config.Config, vs map[string]interface{}) []error { func smcUserVariables(c *config.Config, vs map[string]interface{}) []error {

View File

@ -1,111 +1,11 @@
package terraform package terraform
import ( import (
"errors"
"fmt" "fmt"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/config" "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. // varNameForVar returns the VarName value for an interpolated variable.
// This value is compared to the VarName() value for the nodes within the // This value is compared to the VarName() value for the nodes within the
// graph to build the graph edges. // graph to build the graph edges.

View File

@ -1,150 +0,0 @@
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
`

View File

@ -1,284 +0,0 @@
package terraform
import (
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeDestroyable is the interface that nodes that can be destroyed
// must implement. This is used to automatically handle the creation of
// destroy nodes in the graph and the dependency ordering of those destroys.
type GraphNodeDestroyable interface {
// DestroyNode returns the node used for the destroy with the given
// mode. If this returns nil, then a destroy node for that mode
// will not be added.
DestroyNode() GraphNodeDestroy
}
// GraphNodeDestroy is the interface that must implemented by
// nodes that destroy.
type GraphNodeDestroy interface {
dag.Vertex
// CreateBeforeDestroy is called to check whether this node
// should be created before it is destroyed. The CreateBeforeDestroy
// transformer uses this information to setup the graph.
CreateBeforeDestroy() bool
// CreateNode returns the node used for the create side of this
// destroy. This must already exist within the graph.
CreateNode() dag.Vertex
}
// GraphNodeDestroyPrunable is the interface that can be implemented to
// signal that this node can be pruned depending on state.
type GraphNodeDestroyPrunable interface {
// DestroyInclude is called to check if this node should be included
// with the given state. The state and diff must NOT be modified.
DestroyInclude(*ModuleDiff, *ModuleState) bool
}
// GraphNodeEdgeInclude can be implemented to not include something
// as an edge within the destroy graph. This is usually done because it
// might cause unnecessary cycles.
type GraphNodeDestroyEdgeInclude interface {
DestroyEdgeInclude(dag.Vertex) bool
}
// DestroyTransformer is a GraphTransformer that creates the destruction
// nodes for things that _might_ be destroyed.
type DestroyTransformer struct {
FullDestroy bool
}
func (t *DestroyTransformer) Transform(g *Graph) error {
var connect, remove []dag.Edge
nodeToCn := make(map[dag.Vertex]dag.Vertex, len(g.Vertices()))
nodeToDn := make(map[dag.Vertex]dag.Vertex, len(g.Vertices()))
for _, v := range g.Vertices() {
// If it is not a destroyable, we don't care
cn, ok := v.(GraphNodeDestroyable)
if !ok {
continue
}
// Grab the destroy side of the node and connect it through
n := cn.DestroyNode()
if n == nil {
continue
}
// Store it
nodeToCn[n] = cn
nodeToDn[cn] = n
// If the creation node is equal to the destroy node, then
// don't do any of the edge jump rope below.
if n.(interface{}) == cn.(interface{}) {
continue
}
// Add it to the graph
g.Add(n)
// Inherit all the edges from the old node
downEdges := g.DownEdges(v).List()
for _, edgeRaw := range downEdges {
// If this thing specifically requests to not be depended on
// by destroy nodes, then don't.
if i, ok := edgeRaw.(GraphNodeDestroyEdgeInclude); ok &&
!i.DestroyEdgeInclude(v) {
continue
}
g.Connect(dag.BasicEdge(n, edgeRaw.(dag.Vertex)))
}
// Add a new edge to connect the node to be created to
// the destroy node.
connect = append(connect, dag.BasicEdge(v, n))
}
// Go through the nodes we added and determine if they depend
// on any nodes with a destroy node. If so, depend on that instead.
for n, _ := range nodeToCn {
for _, downRaw := range g.DownEdges(n).List() {
target := downRaw.(dag.Vertex)
cn2, ok := target.(GraphNodeDestroyable)
if !ok {
continue
}
newTarget := nodeToDn[cn2]
if newTarget == nil {
continue
}
// Make the new edge and transpose
connect = append(connect, dag.BasicEdge(newTarget, n))
// Remove the old edge
remove = append(remove, dag.BasicEdge(n, target))
}
}
// Atomatically add/remove the edges
for _, e := range connect {
g.Connect(e)
}
for _, e := range remove {
g.RemoveEdge(e)
}
return nil
}
// CreateBeforeDestroyTransformer is a GraphTransformer that modifies
// the destroys of some nodes so that the creation happens before the
// destroy.
type CreateBeforeDestroyTransformer struct{}
func (t *CreateBeforeDestroyTransformer) Transform(g *Graph) error {
// We "stage" the edge connections/destroys in these slices so that
// while we're doing the edge transformations (transpositions) in
// the graph, we're not affecting future edge transpositions. These
// slices let us stage ALL the changes that WILL happen so that all
// of the transformations happen atomically.
var connect, destroy []dag.Edge
for _, v := range g.Vertices() {
// We only care to use the destroy nodes
dn, ok := v.(GraphNodeDestroy)
if !ok {
continue
}
// If the node doesn't need to create before destroy, then continue
if !dn.CreateBeforeDestroy() {
if noCreateBeforeDestroyAncestors(g, dn) {
continue
}
// PURPOSELY HACKY FIX SINCE THIS TRANSFORM IS DEPRECATED.
// This is a hacky way to fix GH-10439. For a detailed description
// of the fix, see CBDEdgeTransformer, which is the equivalent
// transform used by the new graphs.
//
// This transform is deprecated because it is only used by the
// old graphs which are going to be removed.
var update *config.Resource
if dn, ok := v.(*graphNodeResourceDestroy); ok {
update = dn.Original.Resource
}
if dn, ok := v.(*graphNodeResourceDestroyFlat); ok {
update = dn.Original.Resource
}
if update != nil {
update.Lifecycle.CreateBeforeDestroy = true
}
}
// Get the creation side of this node
cn := dn.CreateNode()
// Take all the things which depend on the creation node and
// make them dependencies on the destruction. Clarifying this
// with an example: if you have a web server and a load balancer
// and the load balancer depends on the web server, then when we
// do a create before destroy, we want to make sure the steps are:
//
// 1.) Create new web server
// 2.) Update load balancer
// 3.) Delete old web server
//
// This ensures that.
for _, sourceRaw := range g.UpEdges(cn).List() {
source := sourceRaw.(dag.Vertex)
// If the graph has a "root" node (one added by a RootTransformer and not
// just a resource that happens to have no ancestors), we don't want to
// add any edges to it, because then it ceases to be a root.
if _, ok := source.(graphNodeRoot); ok {
continue
}
connect = append(connect, dag.BasicEdge(dn, source))
}
// Swap the edge so that the destroy depends on the creation
// happening...
connect = append(connect, dag.BasicEdge(dn, cn))
destroy = append(destroy, dag.BasicEdge(cn, dn))
}
for _, edge := range connect {
g.Connect(edge)
}
for _, edge := range destroy {
g.RemoveEdge(edge)
}
return nil
}
// noCreateBeforeDestroyAncestors verifies that a vertex has no ancestors that
// are CreateBeforeDestroy.
// If this vertex has an ancestor with CreateBeforeDestroy, we will need to
// inherit that behavior and re-order the edges even if this node type doesn't
// directly implement CreateBeforeDestroy.
func noCreateBeforeDestroyAncestors(g *Graph, v dag.Vertex) bool {
s, _ := g.Ancestors(v)
if s == nil {
return true
}
for _, v := range s.List() {
dn, ok := v.(GraphNodeDestroy)
if !ok {
continue
}
if dn.CreateBeforeDestroy() {
// some ancestor is CreateBeforeDestroy, so we need to follow suit
return false
}
}
return true
}
// PruneDestroyTransformer is a GraphTransformer that removes the destroy
// nodes that aren't in the diff.
type PruneDestroyTransformer struct {
Diff *Diff
State *State
}
func (t *PruneDestroyTransformer) Transform(g *Graph) error {
for _, v := range g.Vertices() {
// If it is not a destroyer, we don't care
dn, ok := v.(GraphNodeDestroyPrunable)
if !ok {
continue
}
path := g.Path
if pn, ok := v.(GraphNodeSubPath); ok {
path = pn.Path()
}
var modDiff *ModuleDiff
var modState *ModuleState
if t.Diff != nil {
modDiff = t.Diff.ModuleByPath(path)
}
if t.State != nil {
modState = t.State.ModuleByPath(path)
}
// Remove it if we should
if !dn.DestroyInclude(modDiff, modState) {
g.Remove(v)
}
}
return nil
}

View File

@ -1,506 +0,0 @@
package terraform
import (
"strings"
"testing"
)
func TestDestroyTransformer(t *testing.T) {
mod := testModule(t, "transform-destroy-basic")
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &DestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformDestroyBasicStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestDestroyTransformer_dependsOn(t *testing.T) {
mod := testModule(t, "transform-destroy-depends-on")
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &DestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformDestroyBasicStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestCreateBeforeDestroyTransformer(t *testing.T) {
mod := testModule(t, "transform-create-before-destroy-basic")
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &DestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &CreateBeforeDestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformCreateBeforeDestroyBasicStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestCreateBeforeDestroyTransformer_twice(t *testing.T) {
mod := testModule(t, "transform-create-before-destroy-twice")
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &DestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &CreateBeforeDestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformCreateBeforeDestroyTwiceStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestPruneDestroyTransformer(t *testing.T) {
var diff *Diff
mod := testModule(t, "transform-destroy-basic")
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &DestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &PruneDestroyTransformer{Diff: diff}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformPruneDestroyBasicStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestPruneDestroyTransformer_diff(t *testing.T) {
mod := testModule(t, "transform-destroy-basic")
diff := &Diff{
Modules: []*ModuleDiff{
&ModuleDiff{
Path: RootModulePath,
Resources: map[string]*InstanceDiff{
"aws_instance.bar": &InstanceDiff{},
},
},
},
}
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &DestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &PruneDestroyTransformer{Diff: diff}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformPruneDestroyBasicDiffStr)
if actual != expected {
t.Fatalf("expected:\n\n%s\n\nbad:\n\n%s", expected, actual)
}
}
func TestPruneDestroyTransformer_count(t *testing.T) {
mod := testModule(t, "transform-destroy-prune-count")
diff := &Diff{}
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &DestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &PruneDestroyTransformer{Diff: diff}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformPruneDestroyCountStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestPruneDestroyTransformer_countDec(t *testing.T) {
mod := testModule(t, "transform-destroy-basic")
diff := &Diff{}
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: RootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.bar.1": &ResourceState{
Primary: &InstanceState{},
},
"aws_instance.bar.2": &ResourceState{
Primary: &InstanceState{},
},
},
},
},
}
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &DestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &PruneDestroyTransformer{Diff: diff, State: state}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformPruneDestroyCountDecStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestPruneDestroyTransformer_countState(t *testing.T) {
mod := testModule(t, "transform-destroy-basic")
diff := &Diff{}
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: RootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.bar": &ResourceState{
Primary: &InstanceState{},
},
},
},
},
}
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &DestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &PruneDestroyTransformer{Diff: diff, State: state}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformPruneDestroyCountStateStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestPruneDestroyTransformer_prefixMatch(t *testing.T) {
mod := testModule(t, "transform-destroy-prefix")
diff := &Diff{}
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: RootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.foo-bar.0": &ResourceState{
Primary: &InstanceState{ID: "foo"},
},
"aws_instance.foo-bar.1": &ResourceState{
Primary: &InstanceState{ID: "foo"},
},
},
},
},
}
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &DestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &PruneDestroyTransformer{Diff: diff, State: state}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformPruneDestroyPrefixStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestPruneDestroyTransformer_tainted(t *testing.T) {
mod := testModule(t, "transform-destroy-basic")
diff := &Diff{}
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: RootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.bar": &ResourceState{
Primary: &InstanceState{
ID: "foo",
Tainted: true,
},
},
},
},
},
}
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &DestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &PruneDestroyTransformer{Diff: diff, State: state}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformPruneDestroyTaintedStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
const testTransformDestroyBasicStr = `
aws_instance.bar
aws_instance.bar (destroy)
aws_instance.foo
aws_instance.bar (destroy)
aws_instance.foo
aws_instance.foo (destroy)
aws_instance.foo (destroy)
aws_instance.bar (destroy)
`
const testTransformPruneDestroyBasicStr = `
aws_instance.bar
aws_instance.foo
aws_instance.foo
`
const testTransformPruneDestroyBasicDiffStr = `
aws_instance.bar
aws_instance.foo
aws_instance.foo
`
const testTransformPruneDestroyCountStr = `
aws_instance.bar
aws_instance.bar (destroy)
aws_instance.foo
aws_instance.bar (destroy)
aws_instance.foo
`
const testTransformPruneDestroyCountDecStr = `
aws_instance.bar
aws_instance.bar (destroy)
aws_instance.foo
aws_instance.bar (destroy)
aws_instance.foo
`
const testTransformPruneDestroyCountStateStr = `
aws_instance.bar
aws_instance.foo
aws_instance.foo
`
const testTransformPruneDestroyPrefixStr = `
aws_instance.foo
aws_instance.foo-bar
aws_instance.foo-bar (destroy)
aws_instance.foo-bar (destroy)
`
const testTransformPruneDestroyTaintedStr = `
aws_instance.bar
aws_instance.foo
aws_instance.foo
`
const testTransformCreateBeforeDestroyBasicStr = `
aws_instance.web
aws_instance.web (destroy)
aws_instance.web
aws_load_balancer.lb
aws_load_balancer.lb (destroy)
aws_load_balancer.lb
aws_instance.web
aws_load_balancer.lb (destroy)
aws_load_balancer.lb (destroy)
`
const testTransformCreateBeforeDestroyTwiceStr = `
aws_autoscale.bar
aws_lc.foo
aws_autoscale.bar (destroy)
aws_autoscale.bar
aws_lc.foo
aws_lc.foo (destroy)
aws_autoscale.bar
aws_autoscale.bar (destroy)
aws_lc.foo
`

View File

@ -46,20 +46,3 @@ func (t *ExpandTransform) Transform(v dag.Vertex) (dag.Vertex, error) {
log.Printf("[DEBUG] vertex %q: static expanding", dag.VertexName(ev)) log.Printf("[DEBUG] vertex %q: static expanding", dag.VertexName(ev))
return ev.Expand(t.Builder) return ev.Expand(t.Builder)
} }
type GraphNodeBasicSubgraph struct {
NameValue string
Graph *Graph
}
func (n *GraphNodeBasicSubgraph) Name() string {
return n.NameValue
}
func (n *GraphNodeBasicSubgraph) Subgraph() dag.Grapher {
return n.Graph
}
func (n *GraphNodeBasicSubgraph) FlattenGraph() *Graph {
return n.Graph
}

View File

@ -1,107 +0,0 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeFlatGraph must be implemented by nodes that have subgraphs
// that they want flattened into the graph.
type GraphNodeFlatGraph interface {
FlattenGraph() *Graph
}
// GraphNodeFlattenable must be implemented by all nodes that can be
// flattened. If a FlattenGraph returns any nodes that can't be flattened,
// it will be an error.
//
// If Flatten returns nil for the Vertex along with a nil error, it will
// removed from the graph.
type GraphNodeFlattenable interface {
Flatten(path []string) (dag.Vertex, error)
}
// FlattenTransformer is a transformer that goes through the graph, finds
// subgraphs that can be flattened, and flattens them into this graph,
// removing the prior subgraph node.
type FlattenTransformer struct{}
func (t *FlattenTransformer) Transform(g *Graph) error {
for _, v := range g.Vertices() {
fn, ok := v.(GraphNodeFlatGraph)
if !ok {
continue
}
// If we don't want to be flattened, don't do it
subgraph := fn.FlattenGraph()
if subgraph == nil {
continue
}
// Get all the things that depend on this node. We'll re-connect
// dependents later. We have to copy these here since the UpEdges
// value will be deleted after the Remove below.
dependents := make([]dag.Vertex, 0, 5)
for _, v := range g.UpEdges(v).List() {
dependents = append(dependents, v)
}
// Remove the old node
g.Remove(v)
// Go through the subgraph and flatten all the nodes
for _, sv := range subgraph.Vertices() {
// If the vertex already has a subpath then we assume it has
// already been flattened. Ignore it.
if _, ok := sv.(GraphNodeSubPath); ok {
continue
}
fn, ok := sv.(GraphNodeFlattenable)
if !ok {
return fmt.Errorf(
"unflattenable node: %s %T",
dag.VertexName(sv), sv)
}
v, err := fn.Flatten(subgraph.Path)
if err != nil {
return fmt.Errorf(
"error flattening %s (%T): %s",
dag.VertexName(sv), sv, err)
}
if v == nil {
subgraph.Remove(v)
} else {
subgraph.Replace(sv, v)
}
}
// Now that we've handled any changes to the graph that are
// needed, we can add them all to our graph along with their edges.
for _, sv := range subgraph.Vertices() {
g.Add(sv)
}
for _, se := range subgraph.Edges() {
g.Connect(se)
}
// Connect the dependencies for all the new nodes that we added.
// This will properly connect variables to their sources, for example.
for _, sv := range subgraph.Vertices() {
g.ConnectDependent(sv)
}
// Re-connect all the things that dependent on the graph
// we just flattened. This should connect them back into the
// correct nodes if their DependentOn() is setup correctly.
for _, v := range dependents {
g.ConnectDependent(v)
}
}
return nil
}

View File

@ -1,95 +0,0 @@
package terraform
import (
"strings"
"testing"
)
func TestFlattenTransformer(t *testing.T) {
mod := testModule(t, "transform-flatten")
var b BasicGraphBuilder
b = BasicGraphBuilder{
Steps: []GraphTransformer{
&ConfigTransformerOld{Module: mod},
&VertexTransformer{
Transforms: []GraphVertexTransformer{
&ExpandTransform{
Builder: &b,
},
},
},
&FlattenTransformer{},
},
}
g, err := b.Build(rootModulePath)
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformFlattenStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestFlattenTransformer_withProxy(t *testing.T) {
mod := testModule(t, "transform-flatten")
var b BasicGraphBuilder
b = BasicGraphBuilder{
Steps: []GraphTransformer{
&ConfigTransformerOld{Module: mod},
&VertexTransformer{
Transforms: []GraphVertexTransformer{
&ExpandTransform{
Builder: &b,
},
},
},
&FlattenTransformer{},
&ProxyTransformer{},
},
}
g, err := b.Build(rootModulePath)
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformFlattenProxyStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
const testTransformFlattenStr = `
aws_instance.parent
aws_instance.parent-output
module.child.output.output
module.child.aws_instance.child
module.child.var.var
module.child.output.output
module.child.aws_instance.child
module.child.plan-destroy
module.child.var.var
aws_instance.parent
`
const testTransformFlattenProxyStr = `
aws_instance.parent
aws_instance.parent-output
module.child.aws_instance.child
module.child.output.output
module.child.aws_instance.child
aws_instance.parent
module.child.var.var
module.child.output.output
module.child.aws_instance.child
module.child.plan-destroy
module.child.var.var
aws_instance.parent
`

View File

@ -1,62 +0,0 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/dag"
)
// ModuleDestroyTransformer is a GraphTransformer that adds a node
// to the graph that will just mark the full module for destroy in
// the destroy scenario.
type ModuleDestroyTransformerOld struct{}
func (t *ModuleDestroyTransformerOld) Transform(g *Graph) error {
// Create the node
n := &graphNodeModuleDestroy{Path: g.Path}
// Add it to the graph. We don't need any edges because
// it can happen whenever.
g.Add(n)
return nil
}
type graphNodeModuleDestroy struct {
Path []string
}
func (n *graphNodeModuleDestroy) Name() string {
return "plan-destroy"
}
// GraphNodeEvalable impl.
func (n *graphNodeModuleDestroy) EvalTree() EvalNode {
return &EvalOpFilter{
Ops: []walkOperation{walkPlanDestroy},
Node: &EvalDiffDestroyModule{Path: n.Path},
}
}
// GraphNodeFlattenable impl.
func (n *graphNodeModuleDestroy) Flatten(p []string) (dag.Vertex, error) {
return &graphNodeModuleDestroyFlat{
graphNodeModuleDestroy: n,
PathValue: p,
}, nil
}
type graphNodeModuleDestroyFlat struct {
*graphNodeModuleDestroy
PathValue []string
}
func (n *graphNodeModuleDestroyFlat) Name() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeModuleDestroy.Name())
}
func (n *graphNodeModuleDestroyFlat) Path() []string {
return n.PathValue
}

View File

@ -1,104 +0,0 @@
package terraform
import (
"github.com/hashicorp/terraform/dag"
)
// GraphNodeNoopPrunable can be implemented by nodes that can be
// pruned if they are noops.
type GraphNodeNoopPrunable interface {
Noop(*NoopOpts) bool
}
// NoopOpts are the options available to determine if your node is a noop.
type NoopOpts struct {
Graph *Graph
Vertex dag.Vertex
Diff *Diff
State *State
ModDiff *ModuleDiff
ModState *ModuleState
}
// PruneNoopTransformer is a graph transform that prunes nodes that
// consider themselves no-ops. This is done to both simplify the graph
// as well as to remove graph nodes that might otherwise cause problems
// during the graph run. Therefore, this transformer isn't completely
// an optimization step, and can instead be considered critical to
// Terraform operations.
//
// Example of the above case: variables for modules interpolate their values.
// Interpolation will fail on destruction (since attributes are being deleted),
// but variables shouldn't even eval if there is nothing that will consume
// the variable. Therefore, variables can note that they can be omitted
// safely in this case.
//
// The PruneNoopTransformer will prune nodes depth first, and will automatically
// create connect through the dependencies of pruned nodes. For example,
// if we have a graph A => B => C (A depends on B, etc.), and B decides to
// be removed, we'll still be left with A => C; the edge will be properly
// connected.
type PruneNoopTransformer struct {
Diff *Diff
State *State
}
func (t *PruneNoopTransformer) Transform(g *Graph) error {
// Find the leaves.
leaves := make([]dag.Vertex, 0, 10)
for _, v := range g.Vertices() {
if g.DownEdges(v).Len() == 0 {
leaves = append(leaves, v)
}
}
// Do a depth first walk from the leaves and remove things.
return g.ReverseDepthFirstWalk(leaves, func(v dag.Vertex, depth int) error {
// We need a prunable
pn, ok := v.(GraphNodeNoopPrunable)
if !ok {
return nil
}
// Start building the noop opts
path := g.Path
if pn, ok := v.(GraphNodeSubPath); ok {
path = pn.Path()
}
var modDiff *ModuleDiff
var modState *ModuleState
if t.Diff != nil {
modDiff = t.Diff.ModuleByPath(path)
}
if t.State != nil {
modState = t.State.ModuleByPath(path)
}
// Determine if its a noop. If it isn't, just return
noop := pn.Noop(&NoopOpts{
Graph: g,
Vertex: v,
Diff: t.Diff,
State: t.State,
ModDiff: modDiff,
ModState: modState,
})
if !noop {
return nil
}
// It is a noop! We first preserve edges.
up := g.UpEdges(v).List()
for _, downV := range g.DownEdges(v).List() {
for _, upV := range up {
g.Connect(dag.BasicEdge(upV, downV))
}
}
// Then remove it
g.Remove(v)
return nil
})
}

View File

@ -1,54 +0,0 @@
package terraform
import (
"strings"
"testing"
"github.com/hashicorp/terraform/dag"
)
func TestPruneNoopTransformer(t *testing.T) {
g := Graph{Path: RootModulePath}
a := &testGraphNodeNoop{NameValue: "A"}
b := &testGraphNodeNoop{NameValue: "B", Value: true}
c := &testGraphNodeNoop{NameValue: "C"}
g.Add(a)
g.Add(b)
g.Add(c)
g.Connect(dag.BasicEdge(a, b))
g.Connect(dag.BasicEdge(b, c))
{
tf := &PruneNoopTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformPruneNoopStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
const testTransformPruneNoopStr = `
A
C
C
`
type testGraphNodeNoop struct {
NameValue string
Value bool
}
func (v *testGraphNodeNoop) Name() string {
return v.NameValue
}
func (v *testGraphNodeNoop) Noop(*NoopOpts) bool {
return v.Value
}

View File

@ -1,437 +0,0 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeStateRepresentative is an interface that can be implemented by
// a node to say that it is representing a resource in the state.
type GraphNodeStateRepresentative interface {
StateId() []string
}
// OrphanTransformer is a GraphTransformer that adds orphans to the
// graph. This transformer adds both resource and module orphans.
type OrphanTransformer struct {
// Resource is resource configuration. This is only non-nil when
// expanding a resource that is in the configuration. It can't be
// dependend on.
Resource *config.Resource
// 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
// View, if non-nil will set a view on the module state.
View string
}
func (t *OrphanTransformer) Transform(g *Graph) error {
if t.State == nil {
// If the entire state is nil, there can't be any orphans
return nil
}
// Build up all our state representatives
resourceRep := make(map[string]struct{})
for _, v := range g.Vertices() {
if sr, ok := v.(GraphNodeStateRepresentative); ok {
for _, k := range sr.StateId() {
resourceRep[k] = struct{}{}
}
}
}
var config *config.Config
if t.Module != nil {
if module := t.Module.Child(g.Path[1:]); module != nil {
config = module.Config()
}
}
var resourceVertexes []dag.Vertex
if state := t.State.ModuleByPath(g.Path); state != nil {
// If we have state, then we can have orphan resources
// If we have a view, get the view
if t.View != "" {
state = state.View(t.View)
}
resourceOrphans := state.Orphans(config)
resourceVertexes = make([]dag.Vertex, len(resourceOrphans))
for i, k := range resourceOrphans {
// If this orphan is represented by some other node somehow,
// then ignore it.
if _, ok := resourceRep[k]; ok {
continue
}
rs := state.Resources[k]
rsk, err := ParseResourceStateKey(k)
if err != nil {
return err
}
resourceVertexes[i] = g.Add(&graphNodeOrphanResource{
Path: g.Path,
ResourceKey: rsk,
Resource: t.Resource,
Provider: rs.Provider,
dependentOn: rs.Dependencies,
})
}
}
// Go over each module orphan and add it to the graph. We store the
// vertexes and states outside so that we can connect dependencies later.
moduleOrphans := t.State.ModuleOrphans(g.Path, config)
moduleVertexes := make([]dag.Vertex, len(moduleOrphans))
for i, path := range moduleOrphans {
var deps []string
if s := t.State.ModuleByPath(path); s != nil {
deps = s.Dependencies
}
moduleVertexes[i] = g.Add(&graphNodeOrphanModule{
Path: path,
dependentOn: deps,
})
}
// Now do the dependencies. We do this _after_ adding all the orphan
// nodes above because there are cases in which the orphans themselves
// depend on other orphans.
// Resource dependencies
for _, v := range resourceVertexes {
g.ConnectDependent(v)
}
// Module dependencies
for _, v := range moduleVertexes {
g.ConnectDependent(v)
}
return nil
}
// graphNodeOrphanModule is the graph vertex representing an orphan resource..
type graphNodeOrphanModule struct {
Path []string
dependentOn []string
}
func (n *graphNodeOrphanModule) DependableName() []string {
return []string{n.dependableName()}
}
func (n *graphNodeOrphanModule) DependentOn() []string {
return n.dependentOn
}
func (n *graphNodeOrphanModule) Name() string {
return fmt.Sprintf("%s (orphan)", n.dependableName())
}
func (n *graphNodeOrphanModule) dependableName() string {
return fmt.Sprintf("module.%s", n.Path[len(n.Path)-1])
}
// GraphNodeExpandable
func (n *graphNodeOrphanModule) Expand(b GraphBuilder) (GraphNodeSubgraph, error) {
g, err := b.Build(n.Path)
if err != nil {
return nil, err
}
return &GraphNodeBasicSubgraph{
NameValue: n.Name(),
Graph: g,
}, nil
}
// graphNodeOrphanResource is the graph vertex representing an orphan resource..
type graphNodeOrphanResource struct {
Path []string
ResourceKey *ResourceStateKey
Resource *config.Resource
Provider string
dependentOn []string
}
func (n *graphNodeOrphanResource) ConfigType() GraphNodeConfigType {
return GraphNodeConfigTypeResource
}
func (n *graphNodeOrphanResource) ResourceAddress() *ResourceAddress {
return &ResourceAddress{
Index: n.ResourceKey.Index,
InstanceType: TypePrimary,
Name: n.ResourceKey.Name,
Path: n.Path[1:],
Type: n.ResourceKey.Type,
Mode: n.ResourceKey.Mode,
}
}
func (n *graphNodeOrphanResource) DependableName() []string {
return []string{n.dependableName()}
}
func (n *graphNodeOrphanResource) DependentOn() []string {
return n.dependentOn
}
func (n *graphNodeOrphanResource) Flatten(p []string) (dag.Vertex, error) {
return &graphNodeOrphanResourceFlat{
graphNodeOrphanResource: n,
PathValue: p,
}, nil
}
func (n *graphNodeOrphanResource) Name() string {
return fmt.Sprintf("%s (orphan)", n.ResourceKey)
}
func (n *graphNodeOrphanResource) ProvidedBy() []string {
return []string{resourceProvider(n.ResourceKey.Type, n.Provider)}
}
// GraphNodeEvalable impl.
func (n *graphNodeOrphanResource) EvalTree() EvalNode {
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
// Build instance info
info := &InstanceInfo{Id: n.ResourceKey.String(), Type: n.ResourceKey.Type}
info.uniqueExtra = "destroy"
seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})
// Each resource mode has its own lifecycle
switch n.ResourceKey.Mode {
case config.ManagedResourceMode:
seq.Nodes = append(
seq.Nodes,
n.managedResourceEvalNodes(info)...,
)
case config.DataResourceMode:
seq.Nodes = append(
seq.Nodes,
n.dataResourceEvalNodes(info)...,
)
default:
panic(fmt.Errorf("unsupported resource mode %s", n.ResourceKey.Mode))
}
return seq
}
func (n *graphNodeOrphanResource) managedResourceEvalNodes(info *InstanceInfo) []EvalNode {
var provider ResourceProvider
var state *InstanceState
nodes := make([]EvalNode, 0, 3)
// Refresh the resource
nodes = append(nodes, &EvalOpFilter{
Ops: []walkOperation{walkRefresh},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
Name: n.ResourceKey.String(),
Output: &state,
},
&EvalRefresh{
Info: info,
Provider: &provider,
State: &state,
Output: &state,
},
&EvalWriteState{
Name: n.ResourceKey.String(),
ResourceType: n.ResourceKey.Type,
Provider: n.Provider,
Dependencies: n.DependentOn(),
State: &state,
},
},
},
})
// Diff the resource
var diff *InstanceDiff
nodes = append(nodes, &EvalOpFilter{
Ops: []walkOperation{walkPlan, walkPlanDestroy},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalReadState{
Name: n.ResourceKey.String(),
Output: &state,
},
&EvalDiffDestroy{
Info: info,
State: &state,
Output: &diff,
},
&EvalCheckPreventDestroy{
Resource: n.Resource,
ResourceId: n.ResourceKey.String(),
Diff: &diff,
},
&EvalWriteDiff{
Name: n.ResourceKey.String(),
Diff: &diff,
},
},
},
})
// Apply
var err error
nodes = append(nodes, &EvalOpFilter{
Ops: []walkOperation{walkApply, walkDestroy},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalReadDiff{
Name: n.ResourceKey.String(),
Diff: &diff,
},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
Name: n.ResourceKey.String(),
Output: &state,
},
&EvalApplyPre{
Info: info,
State: &state,
Diff: &diff,
},
&EvalApply{
Info: info,
State: &state,
Diff: &diff,
Provider: &provider,
Output: &state,
Error: &err,
},
&EvalWriteState{
Name: n.ResourceKey.String(),
ResourceType: n.ResourceKey.Type,
Provider: n.Provider,
Dependencies: n.DependentOn(),
State: &state,
},
&EvalApplyPost{
Info: info,
State: &state,
Error: &err,
},
&EvalUpdateStateHook{},
},
},
})
return nodes
}
func (n *graphNodeOrphanResource) dataResourceEvalNodes(info *InstanceInfo) []EvalNode {
nodes := make([]EvalNode, 0, 3)
// This will remain nil, since we don't retain states for orphaned
// data resources.
var state *InstanceState
// On both refresh and apply we just drop our state altogether,
// since the config resource validation pass will have proven that the
// resources remaining in the configuration don't need it.
nodes = append(nodes, &EvalOpFilter{
Ops: []walkOperation{walkRefresh, walkApply},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalWriteState{
Name: n.ResourceKey.String(),
ResourceType: n.ResourceKey.Type,
Provider: n.Provider,
Dependencies: n.DependentOn(),
State: &state, // state is nil
},
},
},
})
return nodes
}
func (n *graphNodeOrphanResource) dependableName() string {
return n.ResourceKey.String()
}
// GraphNodeDestroyable impl.
func (n *graphNodeOrphanResource) DestroyNode() GraphNodeDestroy {
return n
}
// GraphNodeDestroy impl.
func (n *graphNodeOrphanResource) CreateBeforeDestroy() bool {
return false
}
func (n *graphNodeOrphanResource) CreateNode() dag.Vertex {
return n
}
// Same as graphNodeOrphanResource, but for flattening
type graphNodeOrphanResourceFlat struct {
*graphNodeOrphanResource
PathValue []string
}
func (n *graphNodeOrphanResourceFlat) Name() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeOrphanResource.Name())
}
func (n *graphNodeOrphanResourceFlat) Path() []string {
return n.PathValue
}
// GraphNodeDestroyable impl.
func (n *graphNodeOrphanResourceFlat) DestroyNode() GraphNodeDestroy {
return n
}
// GraphNodeDestroy impl.
func (n *graphNodeOrphanResourceFlat) CreateBeforeDestroy() bool {
return false
}
func (n *graphNodeOrphanResourceFlat) CreateNode() dag.Vertex {
return n
}
func (n *graphNodeOrphanResourceFlat) ProvidedBy() []string {
return modulePrefixList(
n.graphNodeOrphanResource.ProvidedBy(),
modulePrefixStr(n.PathValue))
}

View File

@ -1,389 +0,0 @@
package terraform
import (
"strings"
"testing"
"github.com/hashicorp/terraform/dag"
)
func TestOrphanTransformer(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 := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
transform := &OrphanTransformer{State: state, Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformOrphanBasicStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestOrphanTransformer_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",
},
},
},
},
// Orphan module
&ModuleState{
Path: []string{RootModuleName, "foo"},
Resources: map[string]*ResourceState{
"aws_instance.web": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
},
},
}
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
transform := &OrphanTransformer{State: state, Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformOrphanModulesStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestOrphanTransformer_modulesDeps(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",
},
},
},
},
// Orphan module
&ModuleState{
Path: []string{RootModuleName, "foo"},
Resources: map[string]*ResourceState{
"aws_instance.web": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
Dependencies: []string{
"aws_instance.foo",
},
},
},
}
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
transform := &OrphanTransformer{State: state, Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformOrphanModulesDepsStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestOrphanTransformer_modulesDepsOrphan(t *testing.T) {
mod := testModule(t, "transform-orphan-modules")
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: RootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.web": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
},
// Orphan module
&ModuleState{
Path: []string{RootModuleName, "foo"},
Resources: map[string]*ResourceState{
"aws_instance.web": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
Dependencies: []string{
"aws_instance.web",
},
},
},
}
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
transform := &OrphanTransformer{State: state, Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformOrphanModulesDepsOrphanStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestOrphanTransformer_modulesNoRoot(t *testing.T) {
mod := testModule(t, "transform-orphan-modules")
state := &State{
Modules: []*ModuleState{
// Orphan module
&ModuleState{
Path: []string{RootModuleName, "foo"},
Resources: map[string]*ResourceState{
"aws_instance.web": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
},
},
}
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
transform := &OrphanTransformer{State: state, Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformOrphanModulesNoRootStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestOrphanTransformer_resourceDepends(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",
},
Dependencies: []string{
"aws_instance.web",
},
},
},
},
},
}
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
transform := &OrphanTransformer{State: state, Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformOrphanResourceDependsStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestOrphanTransformer_nilState(t *testing.T) {
mod := testModule(t, "transform-orphan-basic")
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
transform := &OrphanTransformer{State: nil, Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformOrphanNilStateStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestGraphNodeOrphanModule_impl(t *testing.T) {
var _ dag.Vertex = new(graphNodeOrphanModule)
var _ dag.NamedVertex = new(graphNodeOrphanModule)
var _ GraphNodeExpandable = new(graphNodeOrphanModule)
}
func TestGraphNodeOrphanResource_impl(t *testing.T) {
var _ dag.Vertex = new(graphNodeOrphanResource)
var _ dag.NamedVertex = new(graphNodeOrphanResource)
var _ GraphNodeProviderConsumer = new(graphNodeOrphanResource)
var _ GraphNodeAddressable = new(graphNodeOrphanResource)
}
func TestGraphNodeOrphanResource_ProvidedBy(t *testing.T) {
n := &graphNodeOrphanResource{ResourceKey: &ResourceStateKey{Type: "aws_instance"}}
if v := n.ProvidedBy(); v[0] != "aws" {
t.Fatalf("bad: %#v", v)
}
}
func TestGraphNodeOrphanResource_ProvidedBy_alias(t *testing.T) {
n := &graphNodeOrphanResource{ResourceKey: &ResourceStateKey{Type: "aws_instance"}, Provider: "aws.bar"}
if v := n.ProvidedBy(); v[0] != "aws.bar" {
t.Fatalf("bad: %#v", v)
}
}
const testTransformOrphanBasicStr = `
aws_instance.db (orphan)
aws_instance.web
`
const testTransformOrphanModulesStr = `
aws_instance.foo
module.foo (orphan)
`
const testTransformOrphanModulesDepsStr = `
aws_instance.foo
module.foo (orphan)
aws_instance.foo
`
const testTransformOrphanModulesDepsOrphanStr = `
aws_instance.foo
aws_instance.web (orphan)
module.foo (orphan)
aws_instance.web (orphan)
`
const testTransformOrphanNilStateStr = `
aws_instance.web
`
const testTransformOrphanResourceDependsStr = `
aws_instance.db (orphan)
aws_instance.web
aws_instance.web
`
const testTransformOrphanModulesNoRootStr = `
aws_instance.foo
module.foo (orphan)
`

View File

@ -1,101 +0,0 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeOutput is an interface that nodes that are outputs must
// implement. The OutputName returned is the name of the output key
// that they manage.
type GraphNodeOutput interface {
OutputName() string
}
// AddOutputOrphanTransformer is a transformer that adds output orphans
// to the graph. Output orphans are outputs that are no longer in the
// configuration and therefore need to be removed from the state.
//
// NOTE: This is the _old_ way to add output orphans that is used with
// legacy graph builders. The new way is OrphanOutputTransformer.
type AddOutputOrphanTransformer struct {
State *State
}
func (t *AddOutputOrphanTransformer) Transform(g *Graph) error {
// Get the state for this module. If we have no state, we have no orphans
state := t.State.ModuleByPath(g.Path)
if state == nil {
return nil
}
// Create the set of outputs we do have in the graph
found := make(map[string]struct{})
for _, v := range g.Vertices() {
on, ok := v.(GraphNodeOutput)
if !ok {
continue
}
found[on.OutputName()] = struct{}{}
}
// Go over all the outputs. If we don't have a graph node for it,
// create it. It doesn't need to depend on anything, since its just
// setting it empty.
for k, _ := range state.Outputs {
if _, ok := found[k]; ok {
continue
}
g.Add(&graphNodeOrphanOutput{OutputName: k})
}
return nil
}
type graphNodeOrphanOutput struct {
OutputName string
}
func (n *graphNodeOrphanOutput) Name() string {
return fmt.Sprintf("output.%s (orphan)", n.OutputName)
}
func (n *graphNodeOrphanOutput) EvalTree() EvalNode {
return &EvalOpFilter{
Ops: []walkOperation{walkApply, walkDestroy, walkRefresh},
Node: &EvalDeleteOutput{
Name: n.OutputName,
},
}
}
// GraphNodeFlattenable impl.
func (n *graphNodeOrphanOutput) Flatten(p []string) (dag.Vertex, error) {
return &graphNodeOrphanOutputFlat{
graphNodeOrphanOutput: n,
PathValue: p,
}, nil
}
type graphNodeOrphanOutputFlat struct {
*graphNodeOrphanOutput
PathValue []string
}
func (n *graphNodeOrphanOutputFlat) Name() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeOrphanOutput.Name())
}
func (n *graphNodeOrphanOutputFlat) EvalTree() EvalNode {
return &EvalOpFilter{
Ops: []walkOperation{walkApply, walkDestroy, walkRefresh},
Node: &EvalDeleteOutput{
Name: n.OutputName,
},
}
}

View File

@ -1,51 +0,0 @@
package terraform
import (
"strings"
"testing"
)
func TestAddOutputOrphanTransformer(t *testing.T) {
mod := testModule(t, "transform-orphan-output-basic")
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: RootModulePath,
Outputs: map[string]*OutputState{
"foo": &OutputState{
Value: "bar",
Type: "string",
},
"bar": &OutputState{
Value: "baz",
Type: "string",
},
},
},
},
}
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformerOld{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
transform := &AddOutputOrphanTransformer{State: state}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformOrphanOutputBasicStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
const testTransformOrphanOutputBasicStr = `
output.bar (orphan)
output.foo
`

View File

@ -6,7 +6,6 @@ import (
"strings" "strings"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/dag"
) )
@ -15,7 +14,6 @@ import (
// they satisfy. // they satisfy.
type GraphNodeProvider interface { type GraphNodeProvider interface {
ProviderName() string ProviderName() string
ProviderConfig() *config.RawConfig
} }
// GraphNodeCloseProvider is an interface that nodes that can be a close // GraphNodeCloseProvider is an interface that nodes that can be a close
@ -126,7 +124,7 @@ func (t *MissingProviderTransformer) Transform(g *Graph) error {
// Initialize factory // Initialize factory
if t.Concrete == nil { if t.Concrete == nil {
t.Concrete = func(a *NodeAbstractProvider) dag.Vertex { t.Concrete = func(a *NodeAbstractProvider) dag.Vertex {
return &graphNodeProvider{ProviderNameValue: a.NameValue} return a
} }
} }
@ -188,14 +186,6 @@ func (t *MissingProviderTransformer) Transform(g *Graph) error {
PathValue: path, PathValue: path,
}).(dag.Vertex) }).(dag.Vertex)
if len(path) > 0 { if len(path) > 0 {
if fn, ok := v.(GraphNodeFlattenable); ok {
var err error
v, err = fn.Flatten(path)
if err != nil {
return err
}
}
// We'll need the parent provider as well, so let's // We'll need the parent provider as well, so let's
// add a dummy node to check to make sure that we add // add a dummy node to check to make sure that we add
// that parent provider. // that parent provider.
@ -230,9 +220,6 @@ func (t *ParentProviderTransformer) Transform(g *Graph) error {
// We eventually want to get rid of the flat version entirely so // We eventually want to get rid of the flat version entirely so
// this is a stop-gap while it still exists. // this is a stop-gap while it still exists.
var v dag.Vertex = raw var v dag.Vertex = raw
if f, ok := v.(*graphNodeProviderFlat); ok {
v = f.graphNodeProvider
}
// Only care about providers // Only care about providers
pn, ok := v.(GraphNodeProvider) pn, ok := v.(GraphNodeProvider)
@ -313,15 +300,7 @@ func providerVertexMap(g *Graph) map[string]dag.Vertex {
m := make(map[string]dag.Vertex) m := make(map[string]dag.Vertex)
for _, v := range g.Vertices() { for _, v := range g.Vertices() {
if pv, ok := v.(GraphNodeProvider); ok { if pv, ok := v.(GraphNodeProvider); ok {
key := pv.ProviderName() key := providerMapKey(pv.ProviderName(), v)
// This special case is because the new world view of providers
// is that they should return only their pure name (not the full
// module path with ProviderName). Working towards this future.
if _, ok := v.(*NodeApplyableProvider); ok {
key = providerMapKey(pv.ProviderName(), v)
}
m[key] = v m[key] = v
} }
} }
@ -376,97 +355,6 @@ func (n *graphNodeCloseProvider) DotNode(name string, opts *dag.DotOpts) *dag.Do
} }
} }
type graphNodeProvider struct {
ProviderNameValue string
}
func (n *graphNodeProvider) Name() string {
return fmt.Sprintf("provider.%s", n.ProviderNameValue)
}
// GraphNodeEvalable impl.
func (n *graphNodeProvider) EvalTree() EvalNode {
return ProviderEvalTree(n.ProviderNameValue, nil)
}
// GraphNodeDependable impl.
func (n *graphNodeProvider) DependableName() []string {
return []string{n.Name()}
}
// GraphNodeProvider
func (n *graphNodeProvider) ProviderName() string {
return n.ProviderNameValue
}
func (n *graphNodeProvider) ProviderConfig() *config.RawConfig {
return nil
}
// GraphNodeDotter impl.
func (n *graphNodeProvider) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
return &dag.DotNode{
Name: name,
Attrs: map[string]string{
"label": n.Name(),
"shape": "diamond",
},
}
}
// GraphNodeDotterOrigin impl.
func (n *graphNodeProvider) DotOrigin() bool {
return true
}
// GraphNodeFlattenable impl.
func (n *graphNodeProvider) Flatten(p []string) (dag.Vertex, error) {
return &graphNodeProviderFlat{
graphNodeProvider: n,
PathValue: p,
}, nil
}
// Same as graphNodeMissingProvider, but for flattening
type graphNodeProviderFlat struct {
*graphNodeProvider
PathValue []string
}
func (n *graphNodeProviderFlat) Name() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeProvider.Name())
}
func (n *graphNodeProviderFlat) Path() []string {
return n.PathValue
}
func (n *graphNodeProviderFlat) ProviderName() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue),
n.graphNodeProvider.ProviderName())
}
// GraphNodeDependable impl.
func (n *graphNodeProviderFlat) DependableName() []string {
return []string{n.Name()}
}
func (n *graphNodeProviderFlat) DependentOn() []string {
var result []string
// If we're in a module, then depend on all parent providers. Some of
// these may not exist, hence we depend on all of them.
for i := len(n.PathValue); i > 1; i-- {
prefix := modulePrefixStr(n.PathValue[:i-1])
result = modulePrefixList(n.graphNodeProvider.DependableName(), prefix)
}
return result
}
// graphNodeProviderConsumerDummy is a struct that never enters the real // graphNodeProviderConsumerDummy is a struct that never enters the real
// graph (though it could to no ill effect). It implements // graph (though it could to no ill effect). It implements
// GraphNodeProviderConsumer and GraphNodeSubpath as a way to force // GraphNodeProviderConsumer and GraphNodeSubpath as a way to force

View File

@ -1,174 +0,0 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag"
)
// DisableProviderTransformer "disables" any providers that are only
// depended on by modules.
//
// NOTE: "old" = used by old graph builders, will be removed one day
type DisableProviderTransformerOld struct{}
func (t *DisableProviderTransformerOld) Transform(g *Graph) error {
// Since we're comparing against edges, we need to make sure we connect
g.ConnectDependents()
for _, v := range g.Vertices() {
// We only care about providers
pn, ok := v.(GraphNodeProvider)
if !ok || pn.ProviderName() == "" {
continue
}
// Go through all the up-edges (things that depend on this
// provider) and if any is not a module, then ignore this node.
nonModule := false
for _, sourceRaw := range g.UpEdges(v).List() {
source := sourceRaw.(dag.Vertex)
cn, ok := source.(graphNodeConfig)
if !ok {
nonModule = true
break
}
if cn.ConfigType() != GraphNodeConfigTypeModule {
nonModule = true
break
}
}
if nonModule {
// We found something that depends on this provider that
// isn't a module, so skip it.
continue
}
// Disable the provider by replacing it with a "disabled" provider
disabled := &graphNodeDisabledProvider{GraphNodeProvider: pn}
if !g.Replace(v, disabled) {
panic(fmt.Sprintf(
"vertex disappeared from under us: %s",
dag.VertexName(v)))
}
}
return nil
}
type graphNodeDisabledProvider struct {
GraphNodeProvider
}
// GraphNodeEvalable impl.
func (n *graphNodeDisabledProvider) EvalTree() EvalNode {
var resourceConfig *ResourceConfig
return &EvalOpFilter{
Ops: []walkOperation{walkInput, walkValidate, walkRefresh, walkPlan, walkApply, walkDestroy},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalInterpolate{
Config: n.ProviderConfig(),
Output: &resourceConfig,
},
&EvalBuildProviderConfig{
Provider: n.ProviderName(),
Config: &resourceConfig,
Output: &resourceConfig,
},
&EvalSetProviderConfig{
Provider: n.ProviderName(),
Config: &resourceConfig,
},
},
},
}
}
// GraphNodeFlattenable impl.
func (n *graphNodeDisabledProvider) Flatten(p []string) (dag.Vertex, error) {
return &graphNodeDisabledProviderFlat{
graphNodeDisabledProvider: n,
PathValue: p,
}, nil
}
func (n *graphNodeDisabledProvider) Name() string {
return fmt.Sprintf("%s (disabled)", dag.VertexName(n.GraphNodeProvider))
}
// GraphNodeDotter impl.
func (n *graphNodeDisabledProvider) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
return &dag.DotNode{
Name: name,
Attrs: map[string]string{
"label": n.Name(),
"shape": "diamond",
},
}
}
// GraphNodeDotterOrigin impl.
func (n *graphNodeDisabledProvider) DotOrigin() bool {
return true
}
// GraphNodeDependable impl.
func (n *graphNodeDisabledProvider) DependableName() []string {
return []string{"provider." + n.ProviderName()}
}
// GraphNodeProvider impl.
func (n *graphNodeDisabledProvider) ProviderName() string {
return n.GraphNodeProvider.ProviderName()
}
// GraphNodeProvider impl.
func (n *graphNodeDisabledProvider) ProviderConfig() *config.RawConfig {
return n.GraphNodeProvider.ProviderConfig()
}
// Same as graphNodeDisabledProvider, but for flattening
type graphNodeDisabledProviderFlat struct {
*graphNodeDisabledProvider
PathValue []string
}
func (n *graphNodeDisabledProviderFlat) Name() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeDisabledProvider.Name())
}
func (n *graphNodeDisabledProviderFlat) Path() []string {
return n.PathValue
}
func (n *graphNodeDisabledProviderFlat) ProviderName() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue),
n.graphNodeDisabledProvider.ProviderName())
}
// GraphNodeDependable impl.
func (n *graphNodeDisabledProviderFlat) DependableName() []string {
return modulePrefixList(
n.graphNodeDisabledProvider.DependableName(),
modulePrefixStr(n.PathValue))
}
func (n *graphNodeDisabledProviderFlat) DependentOn() []string {
var result []string
// If we're in a module, then depend on our parent's provider
if len(n.PathValue) > 1 {
prefix := modulePrefixStr(n.PathValue[:len(n.PathValue)-1])
result = modulePrefixList(
n.graphNodeDisabledProvider.DependableName(), prefix)
}
return result
}

View File

@ -3,8 +3,6 @@ package terraform
import ( import (
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/terraform/dag"
) )
func TestProviderTransformer(t *testing.T) { func TestProviderTransformer(t *testing.T) {
@ -12,12 +10,26 @@ func TestProviderTransformer(t *testing.T) {
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
{ {
tf := &ConfigTransformerOld{Module: mod} tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil { if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
} }
{
transform := &AttachResourceConfigTransformer{Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &MissingProviderTransformer{Providers: []string{"aws"}}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
transform := &ProviderTransformer{} transform := &ProviderTransformer{}
if err := transform.Transform(&g); err != nil { if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
@ -73,12 +85,26 @@ func TestCloseProviderTransformer(t *testing.T) {
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
{ {
tf := &ConfigTransformerOld{Module: mod} tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil { if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
} }
{
transform := &AttachResourceConfigTransformer{Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &MissingProviderTransformer{Providers: []string{"aws"}}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{ {
transform := &ProviderTransformer{} transform := &ProviderTransformer{}
if err := transform.Transform(&g); err != nil { if err := transform.Transform(&g); err != nil {
@ -105,7 +131,8 @@ func TestCloseProviderTransformer_withTargets(t *testing.T) {
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
transforms := []GraphTransformer{ transforms := []GraphTransformer{
&ConfigTransformerOld{Module: mod}, &ConfigTransformer{Module: mod},
&MissingProviderTransformer{Providers: []string{"aws"}},
&ProviderTransformer{}, &ProviderTransformer{},
&CloseProviderTransformer{}, &CloseProviderTransformer{},
&TargetsTransformer{ &TargetsTransformer{
@ -135,14 +162,21 @@ func TestMissingProviderTransformer(t *testing.T) {
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
{ {
tf := &ConfigTransformerOld{Module: mod} tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil { if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
} }
{ {
transform := &MissingProviderTransformer{Providers: []string{"foo", "bar"}} transform := &AttachResourceConfigTransformer{Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &MissingProviderTransformer{Providers: []string{"aws", "foo", "bar"}}
if err := transform.Transform(&g); err != nil { if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
@ -318,12 +352,19 @@ func TestPruneProviderTransformer(t *testing.T) {
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
{ {
tf := &ConfigTransformerOld{Module: mod} tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil { if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
} }
{
transform := &AttachResourceConfigTransformer{Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{ {
transform := &MissingProviderTransformer{Providers: []string{"foo"}} transform := &MissingProviderTransformer{Providers: []string{"foo"}}
if err := transform.Transform(&g); err != nil { if err := transform.Transform(&g); err != nil {
@ -359,71 +400,6 @@ func TestPruneProviderTransformer(t *testing.T) {
} }
} }
func TestDisableProviderTransformer(t *testing.T) {
mod := testModule(t, "transform-provider-disable")
g := Graph{Path: RootModulePath}
transforms := []GraphTransformer{
&ConfigTransformerOld{Module: mod},
&MissingProviderTransformer{Providers: []string{"aws"}},
&ProviderTransformer{},
&DisableProviderTransformerOld{},
&CloseProviderTransformer{},
&PruneProviderTransformer{},
}
for _, tr := range transforms {
if err := tr.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformDisableProviderBasicStr)
if actual != expected {
t.Fatalf("expected:\n%s\n\ngot:\n%s\n", expected, actual)
}
}
func TestDisableProviderTransformer_keep(t *testing.T) {
mod := testModule(t, "transform-provider-disable-keep")
g := Graph{Path: RootModulePath}
transforms := []GraphTransformer{
&ConfigTransformerOld{Module: mod},
&MissingProviderTransformer{Providers: []string{"aws"}},
&ProviderTransformer{},
&DisableProviderTransformerOld{},
&CloseProviderTransformer{},
&PruneProviderTransformer{},
}
for _, tr := range transforms {
if err := tr.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformDisableProviderKeepStr)
if actual != expected {
t.Fatalf("expected:\n%s\n\ngot:\n%s\n", expected, actual)
}
}
func TestGraphNodeProvider_impl(t *testing.T) {
var _ dag.Vertex = new(graphNodeProvider)
var _ dag.NamedVertex = new(graphNodeProvider)
var _ GraphNodeProvider = new(graphNodeProvider)
}
func TestGraphNodeProvider_ProviderName(t *testing.T) {
n := &graphNodeProvider{ProviderNameValue: "foo"}
if v := n.ProviderName(); v != "foo" {
t.Fatalf("bad: %#v", v)
}
}
const testTransformProviderBasicStr = ` const testTransformProviderBasicStr = `
aws_instance.web aws_instance.web
provider.aws provider.aws

View File

@ -107,18 +107,9 @@ func (t *MissingProvisionerTransformer) Transform(g *Graph) error {
} }
// Build the vertex // Build the vertex
var newV dag.Vertex = &graphNodeProvisioner{ProvisionerNameValue: p} var newV dag.Vertex = &NodeProvisioner{
if len(path) > 0 { NameValue: p,
// If we have a path, we do the flattening immediately. This PathValue: path,
// is to support new-style graph nodes that are already
// flattened.
if fn, ok := newV.(GraphNodeFlattenable); ok {
var err error
newV, err = fn.Flatten(path)
if err != nil {
return err
}
}
} }
// Add the missing provisioner node to the graph // Add the missing provisioner node to the graph
@ -178,7 +169,8 @@ func provisionerVertexMap(g *Graph) map[string]dag.Vertex {
m := make(map[string]dag.Vertex) m := make(map[string]dag.Vertex)
for _, v := range g.Vertices() { for _, v := range g.Vertices() {
if pv, ok := v.(GraphNodeProvisioner); ok { if pv, ok := v.(GraphNodeProvisioner); ok {
m[pv.ProvisionerName()] = v key := provisionerMapKey(pv.ProvisionerName(), v)
m[key] = v
} }
} }
@ -212,50 +204,3 @@ func (n *graphNodeCloseProvisioner) EvalTree() EvalNode {
func (n *graphNodeCloseProvisioner) CloseProvisionerName() string { func (n *graphNodeCloseProvisioner) CloseProvisionerName() string {
return n.ProvisionerNameValue return n.ProvisionerNameValue
} }
type graphNodeProvisioner struct {
ProvisionerNameValue string
}
func (n *graphNodeProvisioner) Name() string {
return fmt.Sprintf("provisioner.%s", n.ProvisionerNameValue)
}
// GraphNodeEvalable impl.
func (n *graphNodeProvisioner) EvalTree() EvalNode {
return &EvalInitProvisioner{Name: n.ProvisionerNameValue}
}
func (n *graphNodeProvisioner) ProvisionerName() string {
return n.ProvisionerNameValue
}
// GraphNodeFlattenable impl.
func (n *graphNodeProvisioner) Flatten(p []string) (dag.Vertex, error) {
return &graphNodeProvisionerFlat{
graphNodeProvisioner: n,
PathValue: p,
}, nil
}
// Same as graphNodeMissingProvisioner, but for flattening
type graphNodeProvisionerFlat struct {
*graphNodeProvisioner
PathValue []string
}
func (n *graphNodeProvisionerFlat) Name() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeProvisioner.Name())
}
func (n *graphNodeProvisionerFlat) Path() []string {
return n.PathValue
}
func (n *graphNodeProvisionerFlat) ProvisionerName() string {
return fmt.Sprintf(
"%s.%s", modulePrefixStr(n.PathValue),
n.graphNodeProvisioner.ProvisionerName())
}

View File

@ -12,12 +12,19 @@ func TestMissingProvisionerTransformer(t *testing.T) {
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
{ {
tf := &ConfigTransformerOld{Module: mod} tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil { if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
} }
{
transform := &AttachResourceConfigTransformer{Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{ {
transform := &MissingProvisionerTransformer{Provisioners: []string{"shell"}} transform := &MissingProvisionerTransformer{Provisioners: []string{"shell"}}
if err := transform.Transform(&g); err != nil { if err := transform.Transform(&g); err != nil {
@ -112,12 +119,19 @@ func TestCloseProvisionerTransformer(t *testing.T) {
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
{ {
tf := &ConfigTransformerOld{Module: mod} tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil { if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
} }
{
transform := &AttachResourceConfigTransformer{Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{ {
transform := &MissingProvisionerTransformer{Provisioners: []string{"shell"}} transform := &MissingProvisionerTransformer{Provisioners: []string{"shell"}}
if err := transform.Transform(&g); err != nil { if err := transform.Transform(&g); err != nil {
@ -145,18 +159,6 @@ func TestCloseProvisionerTransformer(t *testing.T) {
t.Fatalf("bad:\n\n%s", actual) t.Fatalf("bad:\n\n%s", actual)
} }
} }
func TestGraphNodeProvisioner_impl(t *testing.T) {
var _ dag.Vertex = new(graphNodeProvisioner)
var _ dag.NamedVertex = new(graphNodeProvisioner)
var _ GraphNodeProvisioner = new(graphNodeProvisioner)
}
func TestGraphNodeProvisioner_ProvisionerName(t *testing.T) {
n := &graphNodeProvisioner{ProvisionerNameValue: "foo"}
if v := n.ProvisionerName(); v != "foo" {
t.Fatalf("bad: %#v", v)
}
}
const testTransformMissingProvisionerBasicStr = ` const testTransformMissingProvisionerBasicStr = `
aws_instance.web aws_instance.web

View File

@ -1,62 +0,0 @@
package terraform
import (
"github.com/hashicorp/terraform/dag"
)
// GraphNodeProxy must be implemented by nodes that are proxies.
//
// A node that is a proxy says that anything that depends on this
// node (the proxy), should also copy all the things that the proxy
// itself depends on. Example:
//
// A => proxy => C
//
// Should transform into (two edges):
//
// A => proxy => C
// A => C
//
// The purpose for this is because some transforms only look at direct
// edge connections and the proxy generally isn't meaningful in those
// situations, so we should complete all the edges.
type GraphNodeProxy interface {
Proxy() bool
}
// ProxyTransformer is a transformer that goes through the graph, finds
// vertices that are marked as proxies, and connects through their
// dependents. See above for what a proxy is.
type ProxyTransformer struct{}
func (t *ProxyTransformer) Transform(g *Graph) error {
for _, v := range g.Vertices() {
pn, ok := v.(GraphNodeProxy)
if !ok {
continue
}
// If we don't want to be proxies, don't do it
if !pn.Proxy() {
continue
}
// Connect all the things that depend on this to things that
// we depend on as the proxy. See docs for GraphNodeProxy for
// a visual explanation.
for _, s := range g.UpEdges(v).List() {
for _, t := range g.DownEdges(v).List() {
g.Connect(GraphProxyEdge{
Edge: dag.BasicEdge(s, t),
})
}
}
}
return nil
}
// GraphProxyEdge is the edge that is used for proxied edges.
type GraphProxyEdge struct {
dag.Edge
}

View File

@ -1,52 +0,0 @@
package terraform
import (
"strings"
"testing"
"github.com/hashicorp/terraform/dag"
)
func TestProxyTransformer(t *testing.T) {
var g Graph
proxy := &testNodeProxy{NameValue: "proxy"}
g.Add("A")
g.Add("C")
g.Add(proxy)
g.Connect(dag.BasicEdge("A", proxy))
g.Connect(dag.BasicEdge(proxy, "C"))
{
tf := &ProxyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testProxyTransformStr)
if actual != expected {
t.Fatalf("bad: %s", actual)
}
}
type testNodeProxy struct {
NameValue string
}
func (n *testNodeProxy) Name() string {
return n.NameValue
}
func (n *testNodeProxy) Proxy() bool {
return true
}
const testProxyTransformStr = `
A
C
proxy
C
proxy
C
`

View File

@ -300,3 +300,22 @@ func ReferenceFromInterpolatedVar(v config.InterpolatedVariable) []string {
return nil return nil
} }
} }
func modulePrefixStr(p []string) string {
parts := make([]string, 0, len(p)*2)
for _, p := range p[1:] {
parts = append(parts, "module", p)
}
return strings.Join(parts, ".")
}
func modulePrefixList(result []string, prefix string) []string {
if prefix != "" {
for i, v := range result {
result[i] = fmt.Sprintf("%s.%s", prefix, v)
}
}
return result
}

View File

@ -1,967 +0,0 @@
package terraform
import (
"fmt"
"strings"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag"
)
// ResourceCountTransformerOld is a GraphTransformer that expands the count
// out for a specific resource.
type ResourceCountTransformerOld struct {
Resource *config.Resource
Destroy bool
Targets []ResourceAddress
}
func (t *ResourceCountTransformerOld) Transform(g *Graph) error {
// Expand the resource count
count, err := t.Resource.Count()
if err != nil {
return err
}
// Don't allow the count to be negative
if count < 0 {
return fmt.Errorf("negative count: %d", count)
}
// For each count, build and add the node
nodes := make([]dag.Vertex, 0, count)
for i := 0; i < 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 count == 1 {
index = -1
}
// Save the node for later so we can do connections. Make the
// proper node depending on if we're just a destroy node or if
// were a regular node.
var node dag.Vertex = &graphNodeExpandedResource{
Index: index,
Resource: t.Resource,
Path: g.Path,
}
if t.Destroy {
node = &graphNodeExpandedResourceDestroy{
graphNodeExpandedResource: node.(*graphNodeExpandedResource),
}
}
// Skip nodes if targeting excludes them
if !t.nodeIsTargeted(node) {
continue
}
// Add the node now
nodes = append(nodes, node)
g.Add(node)
}
// Make the dependency connections
for _, n := range nodes {
// Connect the dependents. We ignore the return value for missing
// dependents since that should've been caught at a higher level.
g.ConnectDependent(n)
}
return nil
}
func (t *ResourceCountTransformerOld) nodeIsTargeted(node dag.Vertex) bool {
// no targets specified, everything stays in the graph
if len(t.Targets) == 0 {
return true
}
addressable, ok := node.(GraphNodeAddressable)
if !ok {
return false
}
addr := addressable.ResourceAddress()
for _, targetAddr := range t.Targets {
if targetAddr.Equals(addr) {
return true
}
}
return false
}
type graphNodeExpandedResource struct {
Index int
Resource *config.Resource
Path []string
}
func (n *graphNodeExpandedResource) Name() string {
if n.Index == -1 {
return n.Resource.Id()
}
return fmt.Sprintf("%s #%d", n.Resource.Id(), n.Index)
}
// GraphNodeAddressable impl.
func (n *graphNodeExpandedResource) ResourceAddress() *ResourceAddress {
// We want this to report the logical index properly, so we must undo the
// special case from the expand
index := n.Index
if index == -1 {
index = 0
}
return &ResourceAddress{
Path: n.Path[1:],
Index: index,
InstanceType: TypePrimary,
Name: n.Resource.Name,
Type: n.Resource.Type,
Mode: n.Resource.Mode,
}
}
// graphNodeConfig impl.
func (n *graphNodeExpandedResource) ConfigType() GraphNodeConfigType {
return GraphNodeConfigTypeResource
}
// GraphNodeDependable impl.
func (n *graphNodeExpandedResource) DependableName() []string {
return []string{
n.Resource.Id(),
n.stateId(),
}
}
// GraphNodeDependent impl.
func (n *graphNodeExpandedResource) DependentOn() []string {
configNode := &GraphNodeConfigResource{Resource: n.Resource}
result := configNode.DependentOn()
// Walk the variables to find any count-specific variables we depend on.
configNode.VarWalk(func(v config.InterpolatedVariable) {
rv, ok := v.(*config.ResourceVariable)
if !ok {
return
}
// We only want ourselves
if rv.ResourceId() != n.Resource.Id() {
return
}
// If this isn't a multi-access (which shouldn't be allowed but
// is verified elsewhere), then we depend on the specific count
// of this resource, ignoring ourself (which again should be
// validated elsewhere).
if rv.Index > -1 {
id := fmt.Sprintf("%s.%d", rv.ResourceId(), rv.Index)
if id != n.stateId() && id != n.stateId()+".0" {
result = append(result, id)
}
}
})
return result
}
// GraphNodeProviderConsumer
func (n *graphNodeExpandedResource) ProvidedBy() []string {
return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)}
}
func (n *graphNodeExpandedResource) StateDependencies() []string {
depsRaw := n.DependentOn()
deps := make([]string, 0, len(depsRaw))
for _, d := range depsRaw {
// Ignore any variable dependencies
if strings.HasPrefix(d, "var.") {
continue
}
// This is sad. The dependencies are currently in the format of
// "module.foo.bar" (the full field). This strips the field off.
if strings.HasPrefix(d, "module.") {
parts := strings.SplitN(d, ".", 3)
d = strings.Join(parts[0:2], ".")
}
deps = append(deps, d)
}
return deps
}
// GraphNodeEvalable impl.
func (n *graphNodeExpandedResource) EvalTree() EvalNode {
var provider ResourceProvider
var resourceConfig *ResourceConfig
// Build the resource. If we aren't part of a multi-resource, then
// we still consider ourselves as count index zero.
index := n.Index
if index < 0 {
index = 0
}
resource := &Resource{
Name: n.Resource.Name,
Type: n.Resource.Type,
CountIndex: index,
}
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
// Validate the resource
vseq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
vseq.Nodes = append(vseq.Nodes, &EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
})
vseq.Nodes = append(vseq.Nodes, &EvalInterpolate{
Config: n.Resource.RawConfig.Copy(),
Resource: resource,
Output: &resourceConfig,
})
vseq.Nodes = append(vseq.Nodes, &EvalValidateResource{
Provider: &provider,
Config: &resourceConfig,
ResourceName: n.Resource.Name,
ResourceType: n.Resource.Type,
ResourceMode: n.Resource.Mode,
})
// Validate all the provisioners
for _, p := range n.Resource.Provisioners {
var provisioner ResourceProvisioner
vseq.Nodes = append(vseq.Nodes, &EvalGetProvisioner{
Name: p.Type,
Output: &provisioner,
}, &EvalInterpolate{
Config: p.RawConfig.Copy(),
Resource: resource,
Output: &resourceConfig,
}, &EvalValidateProvisioner{
Provisioner: &provisioner,
Config: &resourceConfig,
})
}
// Add the validation operations
seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkValidate},
Node: vseq,
})
// Build instance info
info := n.instanceInfo()
seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})
// Each resource mode has its own lifecycle
switch n.Resource.Mode {
case config.ManagedResourceMode:
seq.Nodes = append(
seq.Nodes,
n.managedResourceEvalNodes(resource, info, resourceConfig)...,
)
case config.DataResourceMode:
seq.Nodes = append(
seq.Nodes,
n.dataResourceEvalNodes(resource, info, resourceConfig)...,
)
default:
panic(fmt.Errorf("unsupported resource mode %s", n.Resource.Mode))
}
return seq
}
func (n *graphNodeExpandedResource) managedResourceEvalNodes(resource *Resource, info *InstanceInfo, resourceConfig *ResourceConfig) []EvalNode {
var diff *InstanceDiff
var provider ResourceProvider
var state *InstanceState
nodes := make([]EvalNode, 0, 5)
// Refresh the resource
nodes = append(nodes, &EvalOpFilter{
Ops: []walkOperation{walkRefresh},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
Name: n.stateId(),
Output: &state,
},
&EvalRefresh{
Info: info,
Provider: &provider,
State: &state,
Output: &state,
},
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.StateDependencies(),
State: &state,
},
},
},
})
// Diff the resource
nodes = append(nodes, &EvalOpFilter{
Ops: []walkOperation{walkPlan},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalInterpolate{
Config: n.Resource.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.Resource.Name,
ResourceType: n.Resource.Type,
ResourceMode: n.Resource.Mode,
IgnoreWarnings: true,
},
&EvalReadState{
Name: n.stateId(),
Output: &state,
},
&EvalDiff{
Info: info,
Config: &resourceConfig,
Resource: n.Resource,
Provider: &provider,
State: &state,
OutputDiff: &diff,
OutputState: &state,
},
&EvalCheckPreventDestroy{
Resource: n.Resource,
Diff: &diff,
},
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.StateDependencies(),
State: &state,
},
&EvalWriteDiff{
Name: n.stateId(),
Diff: &diff,
},
},
},
})
// Diff the resource for destruction
nodes = append(nodes, &EvalOpFilter{
Ops: []walkOperation{walkPlanDestroy},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalReadState{
Name: n.stateId(),
Output: &state,
},
&EvalDiffDestroy{
Info: info,
State: &state,
Output: &diff,
},
&EvalCheckPreventDestroy{
Resource: n.Resource,
Diff: &diff,
},
&EvalWriteDiff{
Name: n.stateId(),
Diff: &diff,
},
},
},
})
// Apply
var diffApply *InstanceDiff
var err error
var createNew bool
var createBeforeDestroyEnabled bool
nodes = append(nodes, &EvalOpFilter{
Ops: []walkOperation{walkApply, walkDestroy},
Node: &EvalSequence{
Nodes: []EvalNode{
// Get the saved diff for apply
&EvalReadDiff{
Name: n.stateId(),
Diff: &diffApply,
},
// We don't want to do any destroys
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
if diffApply == nil {
return true, EvalEarlyExitError{}
}
if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 {
return true, EvalEarlyExitError{}
}
diffApply.SetDestroy(false)
return true, nil
},
Then: EvalNoop{},
},
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
destroy := false
if diffApply != nil {
destroy = diffApply.GetDestroy() || diffApply.RequiresNew()
}
createBeforeDestroyEnabled =
n.Resource.Lifecycle.CreateBeforeDestroy &&
destroy
return createBeforeDestroyEnabled, nil
},
Then: &EvalDeposeState{
Name: n.stateId(),
},
},
&EvalInterpolate{
Config: n.Resource.RawConfig.Copy(),
Resource: resource,
Output: &resourceConfig,
},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
Name: n.stateId(),
Output: &state,
},
// Re-run validation to catch any errors we missed, e.g. type
// mismatches on computed values.
&EvalValidateResource{
Provider: &provider,
Config: &resourceConfig,
ResourceName: n.Resource.Name,
ResourceType: n.Resource.Type,
ResourceMode: n.Resource.Mode,
IgnoreWarnings: true,
},
&EvalDiff{
Info: info,
Config: &resourceConfig,
Resource: n.Resource,
Provider: &provider,
Diff: &diffApply,
State: &state,
OutputDiff: &diffApply,
},
// Get the saved diff
&EvalReadDiff{
Name: n.stateId(),
Diff: &diff,
},
// Compare the diffs
&EvalCompareDiff{
Info: info,
One: &diff,
Two: &diffApply,
},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
Name: n.stateId(),
Output: &state,
},
&EvalApplyPre{
Info: info,
State: &state,
Diff: &diffApply,
},
&EvalApply{
Info: info,
State: &state,
Diff: &diffApply,
Provider: &provider,
Output: &state,
Error: &err,
CreateNew: &createNew,
},
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.StateDependencies(),
State: &state,
},
&EvalApplyProvisioners{
Info: info,
State: &state,
Resource: n.Resource,
InterpResource: resource,
CreateNew: &createNew,
Error: &err,
},
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
return createBeforeDestroyEnabled && err != nil, nil
},
Then: &EvalUndeposeState{
Name: n.stateId(),
State: &state,
},
Else: &EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.StateDependencies(),
State: &state,
},
},
// We clear the diff out here so that future nodes
// don't see a diff that is already complete. There
// is no longer a diff!
&EvalWriteDiff{
Name: n.stateId(),
Diff: nil,
},
&EvalApplyPost{
Info: info,
State: &state,
Error: &err,
},
&EvalUpdateStateHook{},
},
},
})
return nodes
}
func (n *graphNodeExpandedResource) dataResourceEvalNodes(resource *Resource, info *InstanceInfo, resourceConfig *ResourceConfig) []EvalNode {
//var diff *InstanceDiff
var provider ResourceProvider
var config *ResourceConfig
var diff *InstanceDiff
var state *InstanceState
nodes := make([]EvalNode, 0, 5)
// Refresh the resource
nodes = append(nodes, &EvalOpFilter{
Ops: []walkOperation{walkRefresh},
Node: &EvalSequence{
Nodes: []EvalNode{
// Always destroy the existing state first, since we must
// make sure that values from a previous read will not
// get interpolated if we end up needing to defer our
// loading until apply time.
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.StateDependencies(),
State: &state, // state is nil here
},
&EvalInterpolate{
Config: n.Resource.RawConfig.Copy(),
Resource: resource,
Output: &config,
},
// The rest of this pass can proceed only if there are no
// computed values in our config.
// (If there are, we'll deal with this during the plan and
// apply phases.)
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 {
return true, EvalEarlyExitError{}
}
// If the config explicitly has a depends_on for this
// data source, assume the intention is to prevent
// refreshing ahead of that dependency.
if len(n.Resource.DependsOn) > 0 {
return true, EvalEarlyExitError{}
}
return true, nil
},
Then: EvalNoop{},
},
// The remainder of this pass is the same as running
// a "plan" pass immediately followed by an "apply" pass,
// populating the state early so it'll be available to
// provider configurations that need this data during
// refresh/plan.
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadDataDiff{
Info: info,
Config: &config,
Provider: &provider,
Output: &diff,
OutputState: &state,
},
&EvalReadDataApply{
Info: info,
Diff: &diff,
Provider: &provider,
Output: &state,
},
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.StateDependencies(),
State: &state,
},
&EvalUpdateStateHook{},
},
},
})
// Diff the resource
nodes = append(nodes, &EvalOpFilter{
Ops: []walkOperation{walkPlan},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalReadState{
Name: n.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.Resource.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: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.StateDependencies(),
State: &state,
},
&EvalWriteDiff{
Name: n.stateId(),
Diff: &diff,
},
},
},
})
// Diff the resource for destruction
nodes = append(nodes, &EvalOpFilter{
Ops: []walkOperation{walkPlanDestroy},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalReadState{
Name: n.stateId(),
Output: &state,
},
// Since EvalDiffDestroy doesn't interact with the
// provider at all, we can safely share the same
// implementation for data vs. managed resources.
&EvalDiffDestroy{
Info: info,
State: &state,
Output: &diff,
},
&EvalWriteDiff{
Name: n.stateId(),
Diff: &diff,
},
},
},
})
// Apply
nodes = append(nodes, &EvalOpFilter{
Ops: []walkOperation{walkApply, walkDestroy},
Node: &EvalSequence{
Nodes: []EvalNode{
// Get the saved diff for apply
&EvalReadDiff{
Name: n.stateId(),
Diff: &diff,
},
// Stop here if we don't actually have a diff
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
if diff == nil {
return true, EvalEarlyExitError{}
}
if diff.GetAttributesLen() == 0 {
return true, EvalEarlyExitError{}
}
return true, nil
},
Then: EvalNoop{},
},
// We need to re-interpolate the config here, rather than
// just using the diff's values directly, because we've
// potentially learned more variable values during the
// apply pass that weren't known when the diff was produced.
&EvalInterpolate{
Config: n.Resource.RawConfig.Copy(),
Resource: resource,
Output: &config,
},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
// Make a new diff with our newly-interpolated config.
&EvalReadDataDiff{
Info: info,
Config: &config,
Previous: &diff,
Provider: &provider,
Output: &diff,
},
&EvalReadDataApply{
Info: info,
Diff: &diff,
Provider: &provider,
Output: &state,
},
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.StateDependencies(),
State: &state,
},
// Clear the diff now that we've applied it, so
// later nodes won't see a diff that's now a no-op.
&EvalWriteDiff{
Name: n.stateId(),
Diff: nil,
},
&EvalUpdateStateHook{},
},
},
})
return nodes
}
// instanceInfo is used for EvalTree.
func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo {
return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
}
// stateId is the name used for the state key
func (n *graphNodeExpandedResource) stateId() string {
if n.Index == -1 {
return n.Resource.Id()
}
return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
}
// GraphNodeStateRepresentative impl.
func (n *graphNodeExpandedResource) StateId() []string {
return []string{n.stateId()}
}
// graphNodeExpandedResourceDestroy represents an expanded resource that
// is to be destroyed.
type graphNodeExpandedResourceDestroy struct {
*graphNodeExpandedResource
}
func (n *graphNodeExpandedResourceDestroy) Name() string {
return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name())
}
// graphNodeConfig impl.
func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType {
return GraphNodeConfigTypeResource
}
// GraphNodeEvalable impl.
func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
info := n.instanceInfo()
info.uniqueExtra = "destroy"
var diffApply *InstanceDiff
var provider ResourceProvider
var state *InstanceState
var err error
return &EvalOpFilter{
Ops: []walkOperation{walkApply, walkDestroy},
Node: &EvalSequence{
Nodes: []EvalNode{
// Get the saved diff for apply
&EvalReadDiff{
Name: n.stateId(),
Diff: &diffApply,
},
// Filter the diff so we only get the destroy
&EvalFilterDiff{
Diff: &diffApply,
Output: &diffApply,
Destroy: true,
},
// If we're not destroying, then compare diffs
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
if diffApply != nil && diffApply.GetDestroy() {
return true, nil
}
return true, EvalEarlyExitError{}
},
Then: EvalNoop{},
},
// Load the instance info so we have the module path set
&EvalInstanceInfo{Info: info},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
Name: n.stateId(),
Output: &state,
},
&EvalRequireState{
State: &state,
},
// Make sure we handle data sources properly.
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
if n.Resource.Mode == config.DataResourceMode {
return true, nil
}
return false, nil
},
Then: &EvalReadDataApply{
Info: info,
Diff: &diffApply,
Provider: &provider,
Output: &state,
},
Else: &EvalApply{
Info: info,
State: &state,
Diff: &diffApply,
Provider: &provider,
Output: &state,
Error: &err,
},
},
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.StateDependencies(),
State: &state,
},
&EvalApplyPost{
Info: info,
State: &state,
Error: &err,
},
},
},
}
}

View File

@ -1,69 +0,0 @@
package terraform
import (
"strings"
"testing"
)
func TestResourceCountTransformerOld(t *testing.T) {
cfg := testModule(t, "transform-resource-count-basic").Config()
resource := cfg.Resources[0]
g := Graph{Path: RootModulePath}
{
tf := &ResourceCountTransformerOld{Resource: resource}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testResourceCountTransformOldStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestResourceCountTransformerOld_countNegative(t *testing.T) {
cfg := testModule(t, "transform-resource-count-negative").Config()
resource := cfg.Resources[0]
g := Graph{Path: RootModulePath}
{
tf := &ResourceCountTransformerOld{Resource: resource}
if err := tf.Transform(&g); err == nil {
t.Fatal("should error")
}
}
}
func TestResourceCountTransformerOld_deps(t *testing.T) {
cfg := testModule(t, "transform-resource-count-deps").Config()
resource := cfg.Resources[0]
g := Graph{Path: RootModulePath}
{
tf := &ResourceCountTransformerOld{Resource: resource}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testResourceCountTransformOldDepsStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
const testResourceCountTransformOldStr = `
aws_instance.foo #0
aws_instance.foo #1
aws_instance.foo #2
`
const testResourceCountTransformOldDepsStr = `
aws_instance.foo #0
aws_instance.foo #1
aws_instance.foo #0
`

View File

@ -36,7 +36,3 @@ type graphNodeRoot struct{}
func (n graphNodeRoot) Name() string { func (n graphNodeRoot) Name() string {
return rootNodeName return rootNodeName
} }
func (n graphNodeRoot) Flatten(p []string) (dag.Vertex, error) {
return n, nil
}

View File

@ -10,12 +10,21 @@ func TestRootTransformer(t *testing.T) {
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
{ {
tf := &ConfigTransformerOld{Module: mod} tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil { if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
} }
{
transform := &MissingProviderTransformer{
Providers: []string{"aws", "do"},
}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{ {
transform := &ProviderTransformer{} transform := &ProviderTransformer{}
if err := transform.Transform(&g); err != nil { if err := transform.Transform(&g); err != nil {

View File

@ -6,6 +6,15 @@ import (
"github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/dag"
) )
// GraphNodeTargetable is an interface for graph nodes to implement when they
// need to be told about incoming targets. This is useful for nodes that need
// to respect targets as they dynamically expand. Note that the list of targets
// provided will contain every target provided, and each implementing graph
// node must filter this list to targets considered relevant.
type GraphNodeTargetable interface {
SetTargets([]ResourceAddress)
}
// TargetsTransformer is a GraphTransformer that, when the user specifies a // TargetsTransformer is a GraphTransformer that, when the user specifies a
// list of resources to target, limits the graph to only those resources and // list of resources to target, limits the graph to only those resources and
// their dependencies. // their dependencies.
@ -40,7 +49,7 @@ func (t *TargetsTransformer) Transform(g *Graph) error {
for _, v := range g.Vertices() { for _, v := range g.Vertices() {
removable := false removable := false
if _, ok := v.(GraphNodeAddressable); ok { if _, ok := v.(GraphNodeResource); ok {
removable = true removable = true
} }
if vr, ok := v.(RemovableIfNotTargeted); ok { if vr, ok := v.(RemovableIfNotTargeted); ok {
@ -90,15 +99,6 @@ func (t *TargetsTransformer) selectTargetedNodes(
var err error var err error
if t.Destroy { if t.Destroy {
deps, err = g.Descendents(v) deps, err = g.Descendents(v)
// Select any variables that we depend on in case we need them later for
// interpolating in the count
ancestors, _ := g.Ancestors(v)
for _, a := range ancestors.List() {
if _, ok := a.(*GraphNodeConfigVariableFlat); ok {
deps.Add(a)
}
}
} else { } else {
deps, err = g.Ancestors(v) deps, err = g.Ancestors(v)
} }
@ -117,12 +117,12 @@ func (t *TargetsTransformer) selectTargetedNodes(
func (t *TargetsTransformer) nodeIsTarget( func (t *TargetsTransformer) nodeIsTarget(
v dag.Vertex, addrs []ResourceAddress) bool { v dag.Vertex, addrs []ResourceAddress) bool {
r, ok := v.(GraphNodeAddressable) r, ok := v.(GraphNodeResource)
if !ok { if !ok {
return false return false
} }
addr := r.ResourceAddress() addr := r.ResourceAddr()
for _, targetAddr := range addrs { for _, targetAddr := range addrs {
if targetAddr.Equals(addr) { if targetAddr.Equals(addr) {
return true return true

View File

@ -10,12 +10,26 @@ func TestTargetsTransformer(t *testing.T) {
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
{ {
tf := &ConfigTransformerOld{Module: mod} tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil { if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
} }
{
transform := &AttachResourceConfigTransformer{Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &ReferenceTransformer{}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{ {
transform := &TargetsTransformer{Targets: []string{"aws_instance.me"}} transform := &TargetsTransformer{Targets: []string{"aws_instance.me"}}
if err := transform.Transform(&g); err != nil { if err := transform.Transform(&g); err != nil {
@ -41,12 +55,26 @@ func TestTargetsTransformer_destroy(t *testing.T) {
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
{ {
tf := &ConfigTransformerOld{Module: mod} tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil { if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
} }
{
transform := &AttachResourceConfigTransformer{Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &ReferenceTransformer{}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{ {
transform := &TargetsTransformer{ transform := &TargetsTransformer{
Targets: []string{"aws_instance.me"}, Targets: []string{"aws_instance.me"},

View File

@ -10,12 +10,26 @@ func TestTransitiveReductionTransformer(t *testing.T) {
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
{ {
tf := &ConfigTransformerOld{Module: mod} tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil { if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
} }
{
transform := &AttachResourceConfigTransformer{Module: mod}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &ReferenceTransformer{}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{ {
transform := &TransitiveReductionTransformer{} transform := &TransitiveReductionTransformer{}
if err := transform.Transform(&g); err != nil { if err := transform.Transform(&g); err != nil {