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.
|
||||
|
||||
-type=plan Type of graph to output. Can be: plan, plan-destroy, apply,
|
||||
legacy.
|
||||
validate, input, refresh.
|
||||
|
||||
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
|
|
|
@ -50,9 +50,6 @@ import (
|
|||
// of definition and use. This allows the compiler to enforce references
|
||||
// so it becomes easy to remove the features.
|
||||
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
|
||||
// allowed for awhile in order for it to not block operations.
|
||||
X_shadow = newBasicID("shadow", "SHADOW", true)
|
||||
|
@ -75,7 +72,6 @@ var (
|
|||
func init() {
|
||||
// The list of all experiments, update this when an experiment is added.
|
||||
All = []ID{
|
||||
X_legacyGraph,
|
||||
X_shadow,
|
||||
x_force,
|
||||
}
|
||||
|
|
|
@ -262,30 +262,11 @@ func (c *Context) Graph(typ GraphType, opts *ContextGraphOpts) (*Graph, error) {
|
|||
Targets: c.targets,
|
||||
Validate: opts.Validate,
|
||||
}).Build(RootModulePath)
|
||||
|
||||
case GraphTypeLegacy:
|
||||
return c.graphBuilder(opts).Build(RootModulePath)
|
||||
}
|
||||
|
||||
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.
|
||||
//
|
||||
// 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
|
||||
c.state = c.state.DeepCopy()
|
||||
|
||||
// Enable the new graph by default
|
||||
X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph)
|
||||
|
||||
// Build the graph.
|
||||
graphType := GraphTypeLegacy
|
||||
if !X_legacyGraph {
|
||||
graphType = GraphTypeApply
|
||||
}
|
||||
graph, err := c.Graph(graphType, nil)
|
||||
graph, err := c.Graph(GraphTypeApply, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -541,17 +515,10 @@ func (c *Context) Plan() (*Plan, error) {
|
|||
c.diff.init()
|
||||
c.diffLock.Unlock()
|
||||
|
||||
// Used throughout below
|
||||
X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph)
|
||||
|
||||
// Build the graph.
|
||||
graphType := GraphTypeLegacy
|
||||
if !X_legacyGraph {
|
||||
graphType := GraphTypePlan
|
||||
if c.destroy {
|
||||
graphType = GraphTypePlanDestroy
|
||||
} else {
|
||||
graphType = GraphTypePlan
|
||||
}
|
||||
}
|
||||
graph, err := c.Graph(graphType, nil)
|
||||
if err != nil {
|
||||
|
@ -576,6 +543,7 @@ func (c *Context) Plan() (*Plan, error) {
|
|||
p.Diff.DeepCopy()
|
||||
}
|
||||
|
||||
/*
|
||||
// We don't do the reverification during the new destroy plan because
|
||||
// it will use a different apply process.
|
||||
if X_legacyGraph {
|
||||
|
@ -585,6 +553,7 @@ func (c *Context) Plan() (*Plan, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
var errs error
|
||||
if len(walker.ValidationErrors) > 0 {
|
||||
|
@ -606,15 +575,8 @@ func (c *Context) Refresh() (*State, error) {
|
|||
// Copy our own state
|
||||
c.state = c.state.DeepCopy()
|
||||
|
||||
// Used throughout below
|
||||
X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph)
|
||||
|
||||
// Build the graph.
|
||||
graphType := GraphTypeLegacy
|
||||
if !X_legacyGraph {
|
||||
graphType = GraphTypeRefresh
|
||||
}
|
||||
graph, err := c.Graph(graphType, nil)
|
||||
graph, err := c.Graph(GraphTypeRefresh, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -7229,7 +7229,7 @@ template_file.child:
|
|||
type = template_file
|
||||
|
||||
Dependencies:
|
||||
template_file.parent
|
||||
template_file.parent.*
|
||||
template_file.parent:
|
||||
ID = foo
|
||||
template = Hi
|
||||
|
|
|
@ -29,11 +29,6 @@ type Graph struct {
|
|||
// RootModuleName
|
||||
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
|
||||
// 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
|
||||
|
@ -52,29 +47,6 @@ func (g *Graph) DirectedGraph() dag.Grapher {
|
|||
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.
|
||||
func (g *Graph) Add(v dag.Vertex) dag.Vertex {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -195,13 +150,6 @@ func (g *Graph) ConnectTo(v dag.Vertex, targets []string) []string {
|
|||
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
|
||||
// will be walked with full parallelism, so the walker should expect
|
||||
// to be called in concurrently.
|
||||
|
@ -210,10 +158,6 @@ func (g *Graph) Walk(walker GraphWalker) error {
|
|||
}
|
||||
|
||||
func (g *Graph) init() {
|
||||
if g.annotations == nil {
|
||||
g.annotations = make(map[dag.Vertex]map[string]interface{})
|
||||
}
|
||||
|
||||
if g.dependableMap == nil {
|
||||
g.dependableMap = make(map[string]dag.Vertex)
|
||||
}
|
||||
|
@ -346,16 +290,6 @@ func (g *Graph) walk(walker GraphWalker) error {
|
|||
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
|
||||
// depended on (an edge can be placed between this node and another) according
|
||||
// to the well-known name returned by DependableName.
|
||||
|
|
|
@ -4,8 +4,6 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
V dag.Vertex
|
||||
}
|
||||
|
@ -283,76 +77,3 @@ func (t *testBasicGraphBuilderTransform) Transform(g *Graph) error {
|
|||
const testBasicGraphBuilderStr = `
|
||||
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 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{
|
||||
Type: n.Config.Type,
|
||||
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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
|
@ -109,6 +110,63 @@ func (n *NodeAbstractResource) References() []string {
|
|||
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
|
||||
func (n *NodeAbstractResource) ProvidedBy() []string {
|
||||
// If we have a config we prefer that above all else
|
||||
|
|
|
@ -70,16 +70,8 @@ func (n *NodeApplyableResource) EvalTree() EvalNode {
|
|||
resource.CountIndex = 0
|
||||
}
|
||||
|
||||
// Determine the dependencies for the state. We use some older
|
||||
// code for this that we've used for a long time.
|
||||
var stateDeps []string
|
||||
{
|
||||
oldN := &graphNodeExpandedResource{
|
||||
Resource: n.Config,
|
||||
Index: addr.Index,
|
||||
}
|
||||
stateDeps = oldN.StateDependencies()
|
||||
}
|
||||
// Determine the dependencies for the state.
|
||||
stateDeps := n.StateReferences()
|
||||
|
||||
// Eval info is different depending on what kind of resource this is
|
||||
switch n.Config.Mode {
|
||||
|
|
|
@ -37,13 +37,8 @@ func (n *NodePlannableResourceInstance) EvalTree() EvalNode {
|
|||
resource.CountIndex = 0
|
||||
}
|
||||
|
||||
// Determine the dependencies for the state. We use some older
|
||||
// code for this that we've used for a long time.
|
||||
var stateDeps []string
|
||||
{
|
||||
oldN := &graphNodeExpandedResource{Resource: n.Config}
|
||||
stateDeps = oldN.StateDependencies()
|
||||
}
|
||||
// Determine the dependencies for the state.
|
||||
stateDeps := n.StateReferences()
|
||||
|
||||
// Eval info is different depending on what kind of resource this is
|
||||
switch n.Config.Mode {
|
||||
|
|
|
@ -49,25 +49,6 @@ type SemanticChecker interface {
|
|||
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
|
||||
// variables given satisfy the configuration itself.
|
||||
func smcUserVariables(c *config.Config, vs map[string]interface{}) []error {
|
||||
|
|
|
@ -1,111 +1,11 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
)
|
||||
|
||||
// ConfigTransformerOld is a GraphTransformer that adds the configuration
|
||||
// to the graph. The module used to configure this transformer must be
|
||||
// the root module. We'll look up the child module by the Path in the
|
||||
// Graph.
|
||||
type ConfigTransformerOld struct {
|
||||
Module *module.Tree
|
||||
}
|
||||
|
||||
func (t *ConfigTransformerOld) Transform(g *Graph) error {
|
||||
// A module is required and also must be completely loaded.
|
||||
if t.Module == nil {
|
||||
return errors.New("module must not be nil")
|
||||
}
|
||||
if !t.Module.Loaded() {
|
||||
return errors.New("module must be loaded")
|
||||
}
|
||||
|
||||
// Get the module we care about
|
||||
module := t.Module.Child(g.Path[1:])
|
||||
if module == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the configuration for this module
|
||||
config := module.Config()
|
||||
|
||||
// Create the node list we'll use for the graph
|
||||
nodes := make([]graphNodeConfig, 0,
|
||||
(len(config.Variables)+
|
||||
len(config.ProviderConfigs)+
|
||||
len(config.Modules)+
|
||||
len(config.Resources)+
|
||||
len(config.Outputs))*2)
|
||||
|
||||
// Write all the variables out
|
||||
for _, v := range config.Variables {
|
||||
nodes = append(nodes, &GraphNodeConfigVariable{
|
||||
Variable: v,
|
||||
ModuleTree: t.Module,
|
||||
ModulePath: g.Path,
|
||||
})
|
||||
}
|
||||
|
||||
// Write all the provider configs out
|
||||
for _, pc := range config.ProviderConfigs {
|
||||
nodes = append(nodes, &GraphNodeConfigProvider{Provider: pc})
|
||||
}
|
||||
|
||||
// Write all the resources out
|
||||
for _, r := range config.Resources {
|
||||
nodes = append(nodes, &GraphNodeConfigResource{
|
||||
Resource: r,
|
||||
Path: g.Path,
|
||||
})
|
||||
}
|
||||
|
||||
// Write all the modules out
|
||||
children := module.Children()
|
||||
for _, m := range config.Modules {
|
||||
path := make([]string, len(g.Path), len(g.Path)+1)
|
||||
copy(path, g.Path)
|
||||
path = append(path, m.Name)
|
||||
|
||||
nodes = append(nodes, &GraphNodeConfigModule{
|
||||
Path: path,
|
||||
Module: m,
|
||||
Tree: children[m.Name],
|
||||
})
|
||||
}
|
||||
|
||||
// Write all the outputs out
|
||||
for _, o := range config.Outputs {
|
||||
nodes = append(nodes, &GraphNodeConfigOutput{Output: o})
|
||||
}
|
||||
|
||||
// Err is where the final error value will go if there is one
|
||||
var err error
|
||||
|
||||
// Build the graph vertices
|
||||
for _, n := range nodes {
|
||||
g.Add(n)
|
||||
}
|
||||
|
||||
// Build up the dependencies. We have to do this outside of the above
|
||||
// loop since the nodes need to be in place for us to build the deps.
|
||||
for _, n := range nodes {
|
||||
if missing := g.ConnectDependent(n); len(missing) > 0 {
|
||||
for _, m := range missing {
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"%s: missing dependency: %s", n.Name(), m))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// varNameForVar returns the VarName value for an interpolated variable.
|
||||
// This value is compared to the VarName() value for the nodes within the
|
||||
// graph to build the graph edges.
|
||||
|
|
|
@ -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))
|
||||
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"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
|
@ -15,7 +14,6 @@ import (
|
|||
// they satisfy.
|
||||
type GraphNodeProvider interface {
|
||||
ProviderName() string
|
||||
ProviderConfig() *config.RawConfig
|
||||
}
|
||||
|
||||
// GraphNodeCloseProvider is an interface that nodes that can be a close
|
||||
|
@ -126,7 +124,7 @@ func (t *MissingProviderTransformer) Transform(g *Graph) error {
|
|||
// Initialize factory
|
||||
if t.Concrete == nil {
|
||||
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,
|
||||
}).(dag.Vertex)
|
||||
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
|
||||
// add a dummy node to check to make sure that we add
|
||||
// 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
|
||||
// this is a stop-gap while it still exists.
|
||||
var v dag.Vertex = raw
|
||||
if f, ok := v.(*graphNodeProviderFlat); ok {
|
||||
v = f.graphNodeProvider
|
||||
}
|
||||
|
||||
// Only care about providers
|
||||
pn, ok := v.(GraphNodeProvider)
|
||||
|
@ -313,15 +300,7 @@ func providerVertexMap(g *Graph) map[string]dag.Vertex {
|
|||
m := make(map[string]dag.Vertex)
|
||||
for _, v := range g.Vertices() {
|
||||
if pv, ok := v.(GraphNodeProvider); ok {
|
||||
key := pv.ProviderName()
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
key := providerMapKey(pv.ProviderName(), 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
|
||||
// graph (though it could to no ill effect). It implements
|
||||
// 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 (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
func TestProviderTransformer(t *testing.T) {
|
||||
|
@ -12,12 +10,26 @@ func TestProviderTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
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{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -73,12 +85,26 @@ func TestCloseProviderTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
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{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
|
@ -105,7 +131,8 @@ func TestCloseProviderTransformer_withTargets(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
transforms := []GraphTransformer{
|
||||
&ConfigTransformerOld{Module: mod},
|
||||
&ConfigTransformer{Module: mod},
|
||||
&MissingProviderTransformer{Providers: []string{"aws"}},
|
||||
&ProviderTransformer{},
|
||||
&CloseProviderTransformer{},
|
||||
&TargetsTransformer{
|
||||
|
@ -135,14 +162,21 @@ func TestMissingProviderTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
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 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -318,12 +352,19 @@ func TestPruneProviderTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
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"}}
|
||||
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 = `
|
||||
aws_instance.web
|
||||
provider.aws
|
||||
|
|
|
@ -107,18 +107,9 @@ func (t *MissingProvisionerTransformer) Transform(g *Graph) error {
|
|||
}
|
||||
|
||||
// Build the vertex
|
||||
var newV dag.Vertex = &graphNodeProvisioner{ProvisionerNameValue: p}
|
||||
if len(path) > 0 {
|
||||
// If we have a path, we do the flattening immediately. This
|
||||
// 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
|
||||
}
|
||||
}
|
||||
var newV dag.Vertex = &NodeProvisioner{
|
||||
NameValue: p,
|
||||
PathValue: path,
|
||||
}
|
||||
|
||||
// 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)
|
||||
for _, v := range g.Vertices() {
|
||||
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 {
|
||||
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}
|
||||
{
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
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"}}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
|
@ -112,12 +119,19 @@ func TestCloseProvisionerTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
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"}}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
|
@ -145,18 +159,6 @@ func TestCloseProvisionerTransformer(t *testing.T) {
|
|||
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 = `
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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}
|
||||
{
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
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{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
|
|
|
@ -6,6 +6,15 @@ import (
|
|||
"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
|
||||
// list of resources to target, limits the graph to only those resources and
|
||||
// their dependencies.
|
||||
|
@ -40,7 +49,7 @@ func (t *TargetsTransformer) Transform(g *Graph) error {
|
|||
|
||||
for _, v := range g.Vertices() {
|
||||
removable := false
|
||||
if _, ok := v.(GraphNodeAddressable); ok {
|
||||
if _, ok := v.(GraphNodeResource); ok {
|
||||
removable = true
|
||||
}
|
||||
if vr, ok := v.(RemovableIfNotTargeted); ok {
|
||||
|
@ -90,15 +99,6 @@ func (t *TargetsTransformer) selectTargetedNodes(
|
|||
var err error
|
||||
if t.Destroy {
|
||||
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 {
|
||||
deps, err = g.Ancestors(v)
|
||||
}
|
||||
|
@ -117,12 +117,12 @@ func (t *TargetsTransformer) selectTargetedNodes(
|
|||
|
||||
func (t *TargetsTransformer) nodeIsTarget(
|
||||
v dag.Vertex, addrs []ResourceAddress) bool {
|
||||
r, ok := v.(GraphNodeAddressable)
|
||||
r, ok := v.(GraphNodeResource)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
addr := r.ResourceAddress()
|
||||
addr := r.ResourceAddr()
|
||||
for _, targetAddr := range addrs {
|
||||
if targetAddr.Equals(addr) {
|
||||
return true
|
||||
|
|
|
@ -10,12 +10,26 @@ func TestTargetsTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
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"}}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
|
@ -41,12 +55,26 @@ func TestTargetsTransformer_destroy(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
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"},
|
||||
|
|
|
@ -10,12 +10,26 @@ func TestTransitiveReductionTransformer(t *testing.T) {
|
|||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformerOld{Module: mod}
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
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{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
|
|
Loading…
Reference in New Issue