Merge pull request #11454 from hashicorp/f-goodbye-legacy
core: remove legacy graph
This commit is contained in:
commit
d224d872b9
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
|
||||||
`
|
|
||||||
*/
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
`
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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())
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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]]
|
|
||||||
}
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
||||||
`
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
`
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
`
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
`
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
`
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
`
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
`
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"},
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue