mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Merge pull request #1781 from hashicorp/b-module-deps
Flatten modules into main graph
This commit is contained in:
commit
c8635654cb
@ -410,7 +410,7 @@ func (c *Context) Validate() ([]string, []error) {
|
|||||||
// in the validate stage
|
// in the validate stage
|
||||||
graph, err := c.Graph(&ContextGraphOpts{
|
graph, err := c.Graph(&ContextGraphOpts{
|
||||||
Validate: true,
|
Validate: true,
|
||||||
Verbose: true,
|
Verbose: false,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, []error{err}
|
return nil, []error{err}
|
||||||
|
@ -2365,6 +2365,8 @@ func TestContext2Validate_countVariableNoDefault(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: What should we do here?
|
||||||
func TestContext2Validate_cycle(t *testing.T) {
|
func TestContext2Validate_cycle(t *testing.T) {
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
m := testModule(t, "validate-cycle")
|
m := testModule(t, "validate-cycle")
|
||||||
@ -2383,6 +2385,7 @@ func TestContext2Validate_cycle(t *testing.T) {
|
|||||||
t.Fatalf("expected 1 err, got: %s", e)
|
t.Fatalf("expected 1 err, got: %s", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func TestContext2Validate_moduleBadOutput(t *testing.T) {
|
func TestContext2Validate_moduleBadOutput(t *testing.T) {
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
@ -2443,6 +2446,26 @@ func TestContext2Validate_moduleBadResource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext2Validate_moduleDepsShouldNotCycle(t *testing.T) {
|
||||||
|
m := testModule(t, "validate-module-deps-cycle")
|
||||||
|
p := testProvider("aws")
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
w, e := ctx.Validate()
|
||||||
|
|
||||||
|
if len(w) > 0 {
|
||||||
|
t.Fatalf("expected no warnings, got: %s", w)
|
||||||
|
}
|
||||||
|
if len(e) > 0 {
|
||||||
|
t.Fatalf("expected no errors, got: %s", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContext2Validate_moduleProviderInherit(t *testing.T) {
|
func TestContext2Validate_moduleProviderInherit(t *testing.T) {
|
||||||
m := testModule(t, "validate-module-pc-inherit")
|
m := testModule(t, "validate-module-pc-inherit")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
@ -4053,6 +4076,88 @@ func TestContext2Apply_module(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext2Apply_moduleDestroyOrder(t *testing.T) {
|
||||||
|
m := testModule(t, "apply-module-destroy-order")
|
||||||
|
p := testProvider("aws")
|
||||||
|
p.DiffFn = testDiffFn
|
||||||
|
|
||||||
|
// Create a custom apply function to track the order they were destroyed
|
||||||
|
var order []string
|
||||||
|
var orderLock sync.Mutex
|
||||||
|
p.ApplyFn = func(
|
||||||
|
info *InstanceInfo,
|
||||||
|
is *InstanceState,
|
||||||
|
id *InstanceDiff) (*InstanceState, error) {
|
||||||
|
orderLock.Lock()
|
||||||
|
defer orderLock.Unlock()
|
||||||
|
|
||||||
|
order = append(order, is.ID)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
state := &State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.b": &ResourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&ModuleState{
|
||||||
|
Path: []string{"root", "child"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.a": &ResourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "a",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outputs: map[string]string{
|
||||||
|
"a_output": "a",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
State: state,
|
||||||
|
Destroy: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if _, err := ctx.Plan(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
state, err := ctx.Apply()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{"b", "a"}
|
||||||
|
if !reflect.DeepEqual(order, expected) {
|
||||||
|
t.Fatalf("bad: %#v", order)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
actual := strings.TrimSpace(state.String())
|
||||||
|
expected := strings.TrimSpace(testTerraformApplyModuleDestroyOrderStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad: \n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContext2Apply_moduleVarResourceCount(t *testing.T) {
|
func TestContext2Apply_moduleVarResourceCount(t *testing.T) {
|
||||||
m := testModule(t, "apply-module-var-resource-count")
|
m := testModule(t, "apply-module-var-resource-count")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
@ -58,10 +58,10 @@ type EvalContext interface {
|
|||||||
// that is currently being acted upon.
|
// that is currently being acted upon.
|
||||||
Interpolate(*config.RawConfig, *Resource) (*ResourceConfig, error)
|
Interpolate(*config.RawConfig, *Resource) (*ResourceConfig, error)
|
||||||
|
|
||||||
// SetVariables sets the variables for interpolation. These variables
|
// SetVariables sets the variables for the module within
|
||||||
// should not have a "var." prefix. For example: "var.foo" should be
|
// this context with the name n. This function call is additive:
|
||||||
// "foo" as the key.
|
// the second parameter is merged with any previous call.
|
||||||
SetVariables(map[string]string)
|
SetVariables(string, map[string]string)
|
||||||
|
|
||||||
// Diff returns the global diff as well as the lock that should
|
// Diff returns the global diff as well as the lock that should
|
||||||
// be used to modify that diff.
|
// be used to modify that diff.
|
||||||
|
@ -12,8 +12,20 @@ import (
|
|||||||
// BuiltinEvalContext is an EvalContext implementation that is used by
|
// BuiltinEvalContext is an EvalContext implementation that is used by
|
||||||
// Terraform by default.
|
// Terraform by default.
|
||||||
type BuiltinEvalContext struct {
|
type BuiltinEvalContext struct {
|
||||||
PathValue []string
|
// PathValue is the Path that this context is operating within.
|
||||||
|
PathValue []string
|
||||||
|
|
||||||
|
// Interpolater setting below affect the interpolation of variables.
|
||||||
|
//
|
||||||
|
// The InterpolaterVars are the exact value for ${var.foo} values.
|
||||||
|
// The map is shared between all contexts and is a mapping of
|
||||||
|
// PATH to KEY to VALUE. Because it is shared by all contexts as well
|
||||||
|
// as the Interpolater itself, it is protected by InterpolaterVarLock
|
||||||
|
// which must be locked during any access to the map.
|
||||||
Interpolater *Interpolater
|
Interpolater *Interpolater
|
||||||
|
InterpolaterVars map[string]map[string]string
|
||||||
|
InterpolaterVarLock *sync.Mutex
|
||||||
|
|
||||||
Hooks []Hook
|
Hooks []Hook
|
||||||
InputValue UIInput
|
InputValue UIInput
|
||||||
Providers map[string]ResourceProviderFactory
|
Providers map[string]ResourceProviderFactory
|
||||||
@ -237,9 +249,23 @@ func (ctx *BuiltinEvalContext) Path() []string {
|
|||||||
return ctx.PathValue
|
return ctx.PathValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *BuiltinEvalContext) SetVariables(vs map[string]string) {
|
func (ctx *BuiltinEvalContext) SetVariables(n string, vs map[string]string) {
|
||||||
|
ctx.InterpolaterVarLock.Lock()
|
||||||
|
defer ctx.InterpolaterVarLock.Unlock()
|
||||||
|
|
||||||
|
path := make([]string, len(ctx.Path())+1)
|
||||||
|
copy(path, ctx.Path())
|
||||||
|
path[len(path)-1] = n
|
||||||
|
key := PathCacheKey(path)
|
||||||
|
|
||||||
|
vars := ctx.InterpolaterVars[key]
|
||||||
|
if vars == nil {
|
||||||
|
vars = make(map[string]string)
|
||||||
|
ctx.InterpolaterVars[key] = vars
|
||||||
|
}
|
||||||
|
|
||||||
for k, v := range vs {
|
for k, v := range vs {
|
||||||
ctx.Interpolater.Variables[k] = v
|
vars[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +65,7 @@ type MockEvalContext struct {
|
|||||||
PathPath []string
|
PathPath []string
|
||||||
|
|
||||||
SetVariablesCalled bool
|
SetVariablesCalled bool
|
||||||
|
SetVariablesModule string
|
||||||
SetVariablesVariables map[string]string
|
SetVariablesVariables map[string]string
|
||||||
|
|
||||||
DiffCalled bool
|
DiffCalled bool
|
||||||
@ -162,8 +163,9 @@ func (c *MockEvalContext) Path() []string {
|
|||||||
return c.PathPath
|
return c.PathPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MockEvalContext) SetVariables(vs map[string]string) {
|
func (c *MockEvalContext) SetVariables(n string, vs map[string]string) {
|
||||||
c.SetVariablesCalled = true
|
c.SetVariablesCalled = true
|
||||||
|
c.SetVariablesModule = n
|
||||||
c.SetVariablesVariables = vs
|
c.SetVariablesVariables = vs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,12 +11,13 @@ import (
|
|||||||
// EvalSetVariables is an EvalNode implementation that sets the variables
|
// EvalSetVariables is an EvalNode implementation that sets the variables
|
||||||
// explicitly for interpolation later.
|
// explicitly for interpolation later.
|
||||||
type EvalSetVariables struct {
|
type EvalSetVariables struct {
|
||||||
|
Module *string
|
||||||
Variables map[string]string
|
Variables map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
// TODO: test
|
||||||
func (n *EvalSetVariables) Eval(ctx EvalContext) (interface{}, error) {
|
func (n *EvalSetVariables) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
ctx.SetVariables(n.Variables)
|
ctx.SetVariables(*n.Module, n.Variables)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +54,33 @@ func (g *Graph) Add(v dag.Vertex) dag.Vertex {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove is the same as dag.Graph.Remove
|
||||||
|
func (g *Graph) Remove(v dag.Vertex) dag.Vertex {
|
||||||
|
g.once.Do(g.init)
|
||||||
|
|
||||||
|
// If this is a depend-able node, then remove the lookaside info
|
||||||
|
if dv, ok := v.(GraphNodeDependable); ok {
|
||||||
|
for _, n := range dv.DependableName() {
|
||||||
|
delete(g.dependableMap, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call upwards to remove it from the actual graph
|
||||||
|
return g.Graph.Remove(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace is the same as dag.Graph.Replace
|
||||||
|
func (g *Graph) Replace(o, n dag.Vertex) bool {
|
||||||
|
// Go through and update our lookaside to point to the new vertex
|
||||||
|
for k, v := range g.dependableMap {
|
||||||
|
if v == o {
|
||||||
|
g.dependableMap[k] = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.Graph.Replace(o, n)
|
||||||
|
}
|
||||||
|
|
||||||
// ConnectDependent connects a GraphNodeDependent to all of its
|
// ConnectDependent connects a GraphNodeDependent to all of its
|
||||||
// GraphNodeDependables. It returns the list of dependents it was
|
// GraphNodeDependables. It returns the list of dependents it was
|
||||||
// unable to connect to.
|
// unable to connect to.
|
||||||
@ -129,8 +156,8 @@ func (g *Graph) init() {
|
|||||||
|
|
||||||
func (g *Graph) walk(walker GraphWalker) error {
|
func (g *Graph) walk(walker GraphWalker) error {
|
||||||
// The callbacks for enter/exiting a graph
|
// The callbacks for enter/exiting a graph
|
||||||
ctx := walker.EnterGraph(g)
|
ctx := walker.EnterPath(g.Path)
|
||||||
defer walker.ExitGraph(g)
|
defer walker.ExitPath(g.Path)
|
||||||
|
|
||||||
// Get the path for logs
|
// Get the path for logs
|
||||||
path := strings.Join(ctx.Path(), ".")
|
path := strings.Join(ctx.Path(), ".")
|
||||||
@ -143,6 +170,15 @@ func (g *Graph) walk(walker GraphWalker) error {
|
|||||||
walker.EnterVertex(v)
|
walker.EnterVertex(v)
|
||||||
defer func() { walker.ExitVertex(v, rerr) }()
|
defer func() { walker.ExitVertex(v, rerr) }()
|
||||||
|
|
||||||
|
// vertexCtx is the context that we use when evaluating. This
|
||||||
|
// is normally the context of our graph but can be overridden
|
||||||
|
// with a GraphNodeSubPath impl.
|
||||||
|
vertexCtx := ctx
|
||||||
|
if pn, ok := v.(GraphNodeSubPath); ok && len(pn.Path()) > 0 {
|
||||||
|
vertexCtx = walker.EnterPath(pn.Path())
|
||||||
|
defer walker.ExitPath(pn.Path())
|
||||||
|
}
|
||||||
|
|
||||||
// If the node is eval-able, then evaluate it.
|
// If the node is eval-able, then evaluate it.
|
||||||
if ev, ok := v.(GraphNodeEvalable); ok {
|
if ev, ok := v.(GraphNodeEvalable); ok {
|
||||||
tree := ev.EvalTree()
|
tree := ev.EvalTree()
|
||||||
@ -155,7 +191,7 @@ func (g *Graph) walk(walker GraphWalker) error {
|
|||||||
// then callback with the output.
|
// then callback with the output.
|
||||||
log.Printf("[DEBUG] vertex %s.%s: evaluating", path, dag.VertexName(v))
|
log.Printf("[DEBUG] vertex %s.%s: evaluating", path, dag.VertexName(v))
|
||||||
tree = walker.EnterEvalTree(v, tree)
|
tree = walker.EnterEvalTree(v, tree)
|
||||||
output, err := Eval(tree, ctx)
|
output, err := Eval(tree, vertexCtx)
|
||||||
if rerr = walker.ExitEvalTree(v, output, err); rerr != nil {
|
if rerr = walker.ExitEvalTree(v, output, err); rerr != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -167,7 +203,7 @@ func (g *Graph) walk(walker GraphWalker) error {
|
|||||||
"[DEBUG] vertex %s.%s: expanding/walking dynamic subgraph",
|
"[DEBUG] vertex %s.%s: expanding/walking dynamic subgraph",
|
||||||
path,
|
path,
|
||||||
dag.VertexName(v))
|
dag.VertexName(v))
|
||||||
g, err := ev.DynamicExpand(ctx)
|
g, err := ev.DynamicExpand(vertexCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rerr = err
|
rerr = err
|
||||||
return
|
return
|
||||||
|
@ -91,7 +91,7 @@ type BuiltinGraphBuilder struct {
|
|||||||
// Build builds the graph according to the steps returned by Steps.
|
// Build builds the graph according to the steps returned by Steps.
|
||||||
func (b *BuiltinGraphBuilder) Build(path []string) (*Graph, error) {
|
func (b *BuiltinGraphBuilder) Build(path []string) (*Graph, error) {
|
||||||
basic := &BasicGraphBuilder{
|
basic := &BasicGraphBuilder{
|
||||||
Steps: b.Steps(),
|
Steps: b.Steps(path),
|
||||||
Validate: b.Validate,
|
Validate: b.Validate,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ func (b *BuiltinGraphBuilder) Build(path []string) (*Graph, error) {
|
|||||||
|
|
||||||
// Steps returns the ordered list of GraphTransformers that must be executed
|
// Steps returns the ordered list of GraphTransformers that must be executed
|
||||||
// to build a complete graph.
|
// to build a complete graph.
|
||||||
func (b *BuiltinGraphBuilder) Steps() []GraphTransformer {
|
func (b *BuiltinGraphBuilder) Steps(path []string) []GraphTransformer {
|
||||||
steps := []GraphTransformer{
|
steps := []GraphTransformer{
|
||||||
// Create all our resources from the configuration and state
|
// Create all our resources from the configuration and state
|
||||||
&ConfigTransformer{Module: b.Root},
|
&ConfigTransformer{Module: b.Root},
|
||||||
@ -134,24 +134,41 @@ func (b *BuiltinGraphBuilder) Steps() []GraphTransformer {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Flatten stuff
|
||||||
|
&FlattenTransformer{},
|
||||||
|
|
||||||
|
// Make sure all the connections that are proxies are connected through
|
||||||
|
&ProxyTransformer{},
|
||||||
|
|
||||||
// Optionally reduces the graph to a user-specified list of targets and
|
// Optionally reduces the graph to a user-specified list of targets and
|
||||||
// their dependencies.
|
// their dependencies.
|
||||||
&TargetsTransformer{Targets: b.Targets, Destroy: b.Destroy},
|
&TargetsTransformer{Targets: b.Targets, Destroy: b.Destroy},
|
||||||
|
|
||||||
// Create the destruction nodes
|
// Make sure we have a single root
|
||||||
&DestroyTransformer{},
|
|
||||||
&CreateBeforeDestroyTransformer{},
|
|
||||||
b.conditional(&conditionalOpts{
|
|
||||||
If: func() bool { return !b.Verbose },
|
|
||||||
Then: &PruneDestroyTransformer{Diff: b.Diff, State: b.State},
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Make sure we create one root
|
|
||||||
&RootTransformer{},
|
&RootTransformer{},
|
||||||
|
}
|
||||||
|
|
||||||
// Perform the transitive reduction to make our graph a bit
|
// If we're on the root path, then we do a bunch of other stuff.
|
||||||
// more sane if possible (it usually is possible).
|
// We don't do the following for modules.
|
||||||
&TransitiveReductionTransformer{},
|
if len(path) <= 1 {
|
||||||
|
steps = append(steps,
|
||||||
|
// Create the destruction nodes
|
||||||
|
&DestroyTransformer{},
|
||||||
|
&CreateBeforeDestroyTransformer{},
|
||||||
|
b.conditional(&conditionalOpts{
|
||||||
|
If: func() bool { return !b.Verbose },
|
||||||
|
Then: &PruneDestroyTransformer{Diff: b.Diff, State: b.State},
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Make sure we have a single root after the above changes.
|
||||||
|
// This is the 2nd root transformer. In practice this shouldn't
|
||||||
|
// actually matter as the RootTransformer is idempotent.
|
||||||
|
&RootTransformer{},
|
||||||
|
|
||||||
|
// Perform the transitive reduction to make our graph a bit
|
||||||
|
// more sane if possible (it usually is possible).
|
||||||
|
&TransitiveReductionTransformer{},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove nils
|
// Remove nils
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
|
||||||
"github.com/hashicorp/terraform/config/module"
|
|
||||||
"github.com/hashicorp/terraform/dag"
|
"github.com/hashicorp/terraform/dag"
|
||||||
"github.com/hashicorp/terraform/dot"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// graphNodeConfig is an interface that all graph nodes for the
|
// graphNodeConfig is an interface that all graph nodes for the
|
||||||
@ -45,634 +39,3 @@ type GraphNodeTargetable interface {
|
|||||||
|
|
||||||
SetTargets([]ResourceAddress)
|
SetTargets([]ResourceAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
|
||||||
return []string{n.Name()}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 parameters node to the module
|
|
||||||
t := &ModuleInputTransformer{Variables: make(map[string]string)}
|
|
||||||
if err := t.Transform(graph); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the actual subgraph node
|
|
||||||
return &graphNodeModuleExpanded{
|
|
||||||
Original: n,
|
|
||||||
Graph: graph,
|
|
||||||
InputConfig: n.Module.RawConfig,
|
|
||||||
Variables: t.Variables,
|
|
||||||
}, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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},
|
|
||||||
Node: &EvalSequence{
|
|
||||||
Nodes: []EvalNode{
|
|
||||||
&EvalWriteOutput{
|
|
||||||
Name: n.Output.Name,
|
|
||||||
Value: n.Output.RawConfig,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 *GraphDotOpts) *dot.Node {
|
|
||||||
return dot.NewNode(name, map[string]string{
|
|
||||||
"label": n.Name(),
|
|
||||||
"shape": "diamond",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeDotterOrigin impl.
|
|
||||||
func (n *GraphNodeConfigProvider) DotOrigin() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeConfigResource represents a resource within the config graph.
|
|
||||||
type GraphNodeConfigResource struct {
|
|
||||||
Resource *config.Resource
|
|
||||||
|
|
||||||
// If this is set to anything other than destroyModeNone, then this
|
|
||||||
// resource represents a resource that will be destroyed in some way.
|
|
||||||
DestroyMode GraphNodeDestroyMode
|
|
||||||
|
|
||||||
// Used during DynamicExpand to target indexes
|
|
||||||
Targets []ResourceAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *GraphNodeConfigResource) ConfigType() GraphNodeConfigType {
|
|
||||||
return GraphNodeConfigTypeResource
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *GraphNodeConfigResource) DependableName() []string {
|
|
||||||
return []string{n.Resource.Id()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
switch n.DestroyMode {
|
|
||||||
case DestroyNone:
|
|
||||||
case DestroyPrimary:
|
|
||||||
result += " (destroy)"
|
|
||||||
case DestroyTainted:
|
|
||||||
result += " (destroy tainted)"
|
|
||||||
default:
|
|
||||||
result += " (unknown destroy type)"
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeDotter impl.
|
|
||||||
func (n *GraphNodeConfigResource) DotNode(name string, opts *GraphDotOpts) *dot.Node {
|
|
||||||
if n.DestroyMode != DestroyNone && !opts.Verbose {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return dot.NewNode(name, map[string]string{
|
|
||||||
"label": n.Name(),
|
|
||||||
"shape": "box",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// Primary and non-destroy modes are responsible for creating/destroying
|
|
||||||
// all the nodes, expanding counts.
|
|
||||||
switch n.DestroyMode {
|
|
||||||
case DestroyNone:
|
|
||||||
fallthrough
|
|
||||||
case DestroyPrimary:
|
|
||||||
steps = append(steps, &ResourceCountTransformer{
|
|
||||||
Resource: n.Resource,
|
|
||||||
Destroy: n.DestroyMode != DestroyNone,
|
|
||||||
Targets: n.Targets,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional destroy modifications.
|
|
||||||
switch n.DestroyMode {
|
|
||||||
case DestroyPrimary:
|
|
||||||
// If we're destroying the primary instance, then we want to
|
|
||||||
// expand orphans, which have all the same semantics in a destroy
|
|
||||||
// as a primary.
|
|
||||||
steps = append(steps, &OrphanTransformer{
|
|
||||||
State: state,
|
|
||||||
View: n.Resource.Id(),
|
|
||||||
Targeting: (len(n.Targets) > 0),
|
|
||||||
})
|
|
||||||
|
|
||||||
steps = append(steps, &DeposedTransformer{
|
|
||||||
State: state,
|
|
||||||
View: n.Resource.Id(),
|
|
||||||
})
|
|
||||||
case DestroyTainted:
|
|
||||||
// If we're only destroying tainted resources, then we only
|
|
||||||
// want to find tainted resources and destroy them here.
|
|
||||||
steps = append(steps, &TaintedTransformer{
|
|
||||||
State: state,
|
|
||||||
View: n.Resource.Id(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always end with the root being added
|
|
||||||
steps = append(steps, &RootTransformer{})
|
|
||||||
|
|
||||||
// Build the graph
|
|
||||||
b := &BasicGraphBuilder{Steps: steps}
|
|
||||||
return b.Build(ctx.Path())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeAddressable impl.
|
|
||||||
func (n *GraphNodeConfigResource) ResourceAddress() *ResourceAddress {
|
|
||||||
return &ResourceAddress{
|
|
||||||
// Indicates no specific index; will match on other three fields
|
|
||||||
Index: -1,
|
|
||||||
InstanceType: TypePrimary,
|
|
||||||
Name: n.Resource.Name,
|
|
||||||
Type: n.Resource.Type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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},
|
|
||||||
&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(mode GraphNodeDestroyMode) GraphNodeDestroy {
|
|
||||||
// If we're already a destroy node, then don't do anything
|
|
||||||
if n.DestroyMode != DestroyNone {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &graphNodeResourceDestroy{
|
|
||||||
GraphNodeConfigResource: *n,
|
|
||||||
Original: n,
|
|
||||||
}
|
|
||||||
result.DestroyMode = mode
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 in addition to us
|
|
||||||
// being responsible for destroying the primary state. The primary
|
|
||||||
// state destroy node is the only destroy node that needs to be
|
|
||||||
// "shuffled" according to the CBD rules, since tainted resources
|
|
||||||
// don't have the same inverse dependencies.
|
|
||||||
return n.Original.Resource.Lifecycle.CreateBeforeDestroy &&
|
|
||||||
n.DestroyMode == DestroyPrimary
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeResourceDestroy) CreateNode() dag.Vertex {
|
|
||||||
return n.Original
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeResourceDestroy) DestroyInclude(d *ModuleDiff, s *ModuleState) bool {
|
|
||||||
switch n.DestroyMode {
|
|
||||||
case DestroyPrimary:
|
|
||||||
return n.destroyIncludePrimary(d, s)
|
|
||||||
case DestroyTainted:
|
|
||||||
return n.destroyIncludeTainted(d, s)
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeResourceDestroy) destroyIncludeTainted(
|
|
||||||
d *ModuleDiff, s *ModuleState) bool {
|
|
||||||
// If there is no state, there can't by any tainted.
|
|
||||||
if s == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab the ID which is the prefix (in the case count > 0 at some point)
|
|
||||||
prefix := n.Original.Resource.Id()
|
|
||||||
|
|
||||||
// Go through the resources and find any with our prefix. If there
|
|
||||||
// are any tainted, we need to keep it.
|
|
||||||
for k, v := range s.Resources {
|
|
||||||
if !strings.HasPrefix(k, prefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(v.Tainted) > 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We didn't find any tainted nodes, return
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeResourceDestroy) destroyIncludePrimary(
|
|
||||||
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/tainted 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.
|
|
||||||
if d != nil {
|
|
||||||
for k, _ := range d.Resources {
|
|
||||||
if strings.HasPrefix(k, prefix) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 dag.Vertex
|
|
||||||
Graph *Graph
|
|
||||||
InputConfig *config.RawConfig
|
|
||||||
|
|
||||||
// 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]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeModuleExpanded) Name() string {
|
|
||||||
return fmt.Sprintf("%s (expanded)", dag.VertexName(n.Original))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeModuleExpanded) ConfigType() GraphNodeConfigType {
|
|
||||||
return GraphNodeConfigTypeModule
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeDotter impl.
|
|
||||||
func (n *graphNodeModuleExpanded) DotNode(name string, opts *GraphDotOpts) *dot.Node {
|
|
||||||
return dot.NewNode(name, 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.InputConfig,
|
|
||||||
Output: &resourceConfig,
|
|
||||||
},
|
|
||||||
|
|
||||||
&EvalVariableBlock{
|
|
||||||
Config: &resourceConfig,
|
|
||||||
Variables: n.Variables,
|
|
||||||
},
|
|
||||||
|
|
||||||
&EvalOpFilter{
|
|
||||||
Ops: []walkOperation{walkPlanDestroy},
|
|
||||||
Node: &EvalSequence{
|
|
||||||
Nodes: []EvalNode{
|
|
||||||
&EvalDiffDestroyModule{Path: n.Graph.Path},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeSubgraph impl.
|
|
||||||
func (n *graphNodeModuleExpanded) Subgraph() *Graph {
|
|
||||||
return n.Graph
|
|
||||||
}
|
|
||||||
|
230
terraform/graph_config_node_module.go
Normal file
230
terraform/graph_config_node_module.go
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
"github.com/hashicorp/terraform/dot"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 parameters node to the module
|
||||||
|
t := &ModuleInputTransformer{Variables: make(map[string]string)}
|
||||||
|
if err := t.Transform(graph); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Add the destroy marker to the graph
|
||||||
|
t := &ModuleDestroyTransformer{}
|
||||||
|
if err := t.Transform(graph); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the actual subgraph node
|
||||||
|
return &graphNodeModuleExpanded{
|
||||||
|
Original: n,
|
||||||
|
Graph: graph,
|
||||||
|
Variables: t.Variables,
|
||||||
|
}, 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]string
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *GraphDotOpts) *dot.Node {
|
||||||
|
return dot.NewNode(name, 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,
|
||||||
|
Variables: n.Variables,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeFlattenable impl.
|
||||||
|
func (n *graphNodeModuleExpanded) FlattenGraph() *Graph {
|
||||||
|
graph := n.Subgraph()
|
||||||
|
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 sn, ok := v.(graphNodeModuleSkippable); ok && sn.FlattenSkip() {
|
||||||
|
graph.Remove(v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() *Graph {
|
||||||
|
return n.Graph
|
||||||
|
}
|
||||||
|
|
||||||
|
// This interface can be implemented to be skipped/ignored when
|
||||||
|
// flattening the module graph.
|
||||||
|
type graphNodeModuleSkippable interface {
|
||||||
|
FlattenSkip() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
82
terraform/graph_config_node_module_test.go
Normal file
82
terraform/graph_config_node_module_test.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
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{
|
||||||
|
&ConfigTransformer{Module: mod},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.Subgraph().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{
|
||||||
|
&ConfigTransformer{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
|
||||||
|
module inputs
|
||||||
|
module inputs
|
||||||
|
plan-destroy
|
||||||
|
`
|
||||||
|
|
||||||
|
const testGraphNodeModuleExpandFlattenStr = `
|
||||||
|
aws_instance.foo
|
||||||
|
plan-destroy
|
||||||
|
`
|
104
terraform/graph_config_node_output.go
Normal file
104
terraform/graph_config_node_output.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
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},
|
||||||
|
Node: &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
&EvalWriteOutput{
|
||||||
|
Name: n.Output.Name,
|
||||||
|
Value: n.Output.RawConfig,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeProxy impl.
|
||||||
|
func (n *GraphNodeConfigOutput) Proxy() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDestroyEdgeInclude impl.
|
||||||
|
func (n *GraphNodeConfigOutput) DestroyEdgeInclude() 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)
|
||||||
|
}
|
131
terraform/graph_config_node_provider.go
Normal file
131
terraform/graph_config_node_provider.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
"github.com/hashicorp/terraform/dot"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 *GraphDotOpts) *dot.Node {
|
||||||
|
return dot.NewNode(name, 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())
|
||||||
|
}
|
475
terraform/graph_config_node_resource.go
Normal file
475
terraform/graph_config_node_resource.go
Normal file
@ -0,0 +1,475 @@
|
|||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
"github.com/hashicorp/terraform/dot"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GraphNodeConfigResource represents a resource within the config graph.
|
||||||
|
type GraphNodeConfigResource struct {
|
||||||
|
Resource *config.Resource
|
||||||
|
|
||||||
|
// If this is set to anything other than destroyModeNone, then this
|
||||||
|
// resource represents a resource that will be destroyed in some way.
|
||||||
|
DestroyMode GraphNodeDestroyMode
|
||||||
|
|
||||||
|
// Used during DynamicExpand to target indexes
|
||||||
|
Targets []ResourceAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *GraphNodeConfigResource) ConfigType() GraphNodeConfigType {
|
||||||
|
return GraphNodeConfigTypeResource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *GraphNodeConfigResource) DependableName() []string {
|
||||||
|
return []string{n.Resource.Id()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
switch n.DestroyMode {
|
||||||
|
case DestroyNone:
|
||||||
|
case DestroyPrimary:
|
||||||
|
result += " (destroy)"
|
||||||
|
case DestroyTainted:
|
||||||
|
result += " (destroy tainted)"
|
||||||
|
default:
|
||||||
|
result += " (unknown destroy type)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDotter impl.
|
||||||
|
func (n *GraphNodeConfigResource) DotNode(name string, opts *GraphDotOpts) *dot.Node {
|
||||||
|
if n.DestroyMode != DestroyNone && !opts.Verbose {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return dot.NewNode(name, 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)
|
||||||
|
|
||||||
|
// Primary and non-destroy modes are responsible for creating/destroying
|
||||||
|
// all the nodes, expanding counts.
|
||||||
|
switch n.DestroyMode {
|
||||||
|
case DestroyNone:
|
||||||
|
fallthrough
|
||||||
|
case DestroyPrimary:
|
||||||
|
steps = append(steps, &ResourceCountTransformer{
|
||||||
|
Resource: n.Resource,
|
||||||
|
Destroy: n.DestroyMode != DestroyNone,
|
||||||
|
Targets: n.Targets,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional destroy modifications.
|
||||||
|
switch n.DestroyMode {
|
||||||
|
case DestroyPrimary:
|
||||||
|
// If we're destroying the primary instance, then we want to
|
||||||
|
// expand orphans, which have all the same semantics in a destroy
|
||||||
|
// as a primary.
|
||||||
|
steps = append(steps, &OrphanTransformer{
|
||||||
|
State: state,
|
||||||
|
View: n.Resource.Id(),
|
||||||
|
Targeting: (len(n.Targets) > 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
steps = append(steps, &DeposedTransformer{
|
||||||
|
State: state,
|
||||||
|
View: n.Resource.Id(),
|
||||||
|
})
|
||||||
|
case DestroyTainted:
|
||||||
|
// If we're only destroying tainted resources, then we only
|
||||||
|
// want to find tainted resources and destroy them here.
|
||||||
|
steps = append(steps, &TaintedTransformer{
|
||||||
|
State: state,
|
||||||
|
View: n.Resource.Id(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always end with the root being added
|
||||||
|
steps = append(steps, &RootTransformer{})
|
||||||
|
|
||||||
|
// Build the graph
|
||||||
|
b := &BasicGraphBuilder{Steps: steps}
|
||||||
|
return b.Build(ctx.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeAddressable impl.
|
||||||
|
func (n *GraphNodeConfigResource) ResourceAddress() *ResourceAddress {
|
||||||
|
return &ResourceAddress{
|
||||||
|
// Indicates no specific index; will match on other three fields
|
||||||
|
Index: -1,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Name: n.Resource.Name,
|
||||||
|
Type: n.Resource.Type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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},
|
||||||
|
&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(mode GraphNodeDestroyMode) GraphNodeDestroy {
|
||||||
|
// If we're already a destroy node, then don't do anything
|
||||||
|
if n.DestroyMode != DestroyNone {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &graphNodeResourceDestroy{
|
||||||
|
GraphNodeConfigResource: *n,
|
||||||
|
Original: n,
|
||||||
|
}
|
||||||
|
result.DestroyMode = mode
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(mode GraphNodeDestroyMode) GraphNodeDestroy {
|
||||||
|
// Get our parent destroy node. If we don't have any, just return
|
||||||
|
raw := n.GraphNodeConfigResource.DestroyNode(mode)
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type graphNodeResourceDestroyFlat struct {
|
||||||
|
*graphNodeResourceDestroy
|
||||||
|
|
||||||
|
PathValue []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeResourceDestroyFlat) Name() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeResourceDestroy.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeResourceDestroyFlat) Path() []string {
|
||||||
|
return n.PathValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 in addition to us
|
||||||
|
// being responsible for destroying the primary state. The primary
|
||||||
|
// state destroy node is the only destroy node that needs to be
|
||||||
|
// "shuffled" according to the CBD rules, since tainted resources
|
||||||
|
// don't have the same inverse dependencies.
|
||||||
|
return n.Original.Resource.Lifecycle.CreateBeforeDestroy &&
|
||||||
|
n.DestroyMode == DestroyPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeResourceDestroy) CreateNode() dag.Vertex {
|
||||||
|
return n.Original
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeResourceDestroy) DestroyInclude(d *ModuleDiff, s *ModuleState) bool {
|
||||||
|
switch n.DestroyMode {
|
||||||
|
case DestroyPrimary:
|
||||||
|
return n.destroyIncludePrimary(d, s)
|
||||||
|
case DestroyTainted:
|
||||||
|
return n.destroyIncludeTainted(d, s)
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeResourceDestroy) destroyIncludeTainted(
|
||||||
|
d *ModuleDiff, s *ModuleState) bool {
|
||||||
|
// If there is no state, there can't by any tainted.
|
||||||
|
if s == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab the ID which is the prefix (in the case count > 0 at some point)
|
||||||
|
prefix := n.Original.Resource.Id()
|
||||||
|
|
||||||
|
// Go through the resources and find any with our prefix. If there
|
||||||
|
// are any tainted, we need to keep it.
|
||||||
|
for k, v := range s.Resources {
|
||||||
|
if !strings.HasPrefix(k, prefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.Tainted) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We didn't find any tainted nodes, return
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeResourceDestroy) destroyIncludePrimary(
|
||||||
|
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/tainted 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.
|
||||||
|
if d != nil {
|
||||||
|
for k, _ := range d.Resources {
|
||||||
|
if strings.HasPrefix(k, prefix) {
|
||||||
|
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
|
||||||
|
}
|
@ -2,50 +2,18 @@ package terraform
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/dag"
|
"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{
|
|
||||||
&ConfigTransformer{Module: mod},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := strings.TrimSpace(g.Subgraph().String())
|
|
||||||
expected := strings.TrimSpace(testGraphNodeModuleExpandStr)
|
|
||||||
if actual != expected {
|
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGraphNodeConfigOutput_impl(t *testing.T) {
|
func TestGraphNodeConfigOutput_impl(t *testing.T) {
|
||||||
var _ dag.Vertex = new(GraphNodeConfigOutput)
|
var _ dag.Vertex = new(GraphNodeConfigOutput)
|
||||||
var _ dag.NamedVertex = new(GraphNodeConfigOutput)
|
var _ dag.NamedVertex = new(GraphNodeConfigOutput)
|
||||||
var _ graphNodeConfig = new(GraphNodeConfigOutput)
|
var _ graphNodeConfig = new(GraphNodeConfigOutput)
|
||||||
var _ GraphNodeOutput = new(GraphNodeConfigOutput)
|
var _ GraphNodeOutput = new(GraphNodeConfigOutput)
|
||||||
|
var _ GraphNodeProxy = new(GraphNodeConfigOutput)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGraphNodeConfigProvider_impl(t *testing.T) {
|
func TestGraphNodeConfigProvider_impl(t *testing.T) {
|
||||||
@ -140,11 +108,3 @@ func TestGraphNodeConfigResource_ProvisionedBy(t *testing.T) {
|
|||||||
t.Fatalf("bad: %#v", actual)
|
t.Fatalf("bad: %#v", actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const testGraphNodeModuleExpandStr = `
|
|
||||||
aws_instance.bar
|
|
||||||
aws_instance.foo
|
|
||||||
aws_instance.foo
|
|
||||||
module inputs
|
|
||||||
module inputs
|
|
||||||
`
|
|
||||||
|
@ -12,4 +12,5 @@ const (
|
|||||||
GraphNodeConfigTypeProvider
|
GraphNodeConfigTypeProvider
|
||||||
GraphNodeConfigTypeModule
|
GraphNodeConfigTypeModule
|
||||||
GraphNodeConfigTypeOutput
|
GraphNodeConfigTypeOutput
|
||||||
|
GraphNodeConfigTypeVariable
|
||||||
)
|
)
|
||||||
|
137
terraform/graph_config_node_variable.go
Normal file
137
terraform/graph_config_node_variable.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"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
|
||||||
|
|
||||||
|
depPrefix 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()}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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]string)
|
||||||
|
return &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
&EvalInterpolate{
|
||||||
|
Config: n.Value,
|
||||||
|
Output: &config,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalVariableBlock{
|
||||||
|
Config: &config,
|
||||||
|
Variables: variables,
|
||||||
|
},
|
||||||
|
|
||||||
|
&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
|
||||||
|
}
|
21
terraform/graph_config_node_variable_test.go
Normal file
21
terraform/graph_config_node_variable_test.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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)
|
||||||
|
}
|
7
terraform/graph_interface_subgraph.go
Normal file
7
terraform/graph_interface_subgraph.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package terraform
|
||||||
|
|
||||||
|
// GraphNodeSubPath says that a node is part of a graph with a
|
||||||
|
// different path, and the context should be adjusted accordingly.
|
||||||
|
type GraphNodeSubPath interface {
|
||||||
|
Path() []string
|
||||||
|
}
|
@ -7,8 +7,8 @@ import (
|
|||||||
// GraphWalker is an interface that can be implemented that when used
|
// GraphWalker is an interface that can be implemented that when used
|
||||||
// with Graph.Walk will invoke the given callbacks under certain events.
|
// with Graph.Walk will invoke the given callbacks under certain events.
|
||||||
type GraphWalker interface {
|
type GraphWalker interface {
|
||||||
EnterGraph(*Graph) EvalContext
|
EnterPath([]string) EvalContext
|
||||||
ExitGraph(*Graph)
|
ExitPath([]string)
|
||||||
EnterVertex(dag.Vertex)
|
EnterVertex(dag.Vertex)
|
||||||
ExitVertex(dag.Vertex, error)
|
ExitVertex(dag.Vertex, error)
|
||||||
EnterEvalTree(dag.Vertex, EvalNode) EvalNode
|
EnterEvalTree(dag.Vertex, EvalNode) EvalNode
|
||||||
@ -20,8 +20,8 @@ type GraphWalker interface {
|
|||||||
// implementing all the required functions.
|
// implementing all the required functions.
|
||||||
type NullGraphWalker struct{}
|
type NullGraphWalker struct{}
|
||||||
|
|
||||||
func (NullGraphWalker) EnterGraph(*Graph) EvalContext { return nil }
|
func (NullGraphWalker) EnterPath([]string) EvalContext { return nil }
|
||||||
func (NullGraphWalker) ExitGraph(*Graph) {}
|
func (NullGraphWalker) ExitPath([]string) {}
|
||||||
func (NullGraphWalker) EnterVertex(dag.Vertex) {}
|
func (NullGraphWalker) EnterVertex(dag.Vertex) {}
|
||||||
func (NullGraphWalker) ExitVertex(dag.Vertex, error) {}
|
func (NullGraphWalker) ExitVertex(dag.Vertex, error) {}
|
||||||
func (NullGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode { return n }
|
func (NullGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode { return n }
|
||||||
|
@ -26,6 +26,8 @@ type ContextGraphWalker struct {
|
|||||||
once sync.Once
|
once sync.Once
|
||||||
contexts map[string]*BuiltinEvalContext
|
contexts map[string]*BuiltinEvalContext
|
||||||
contextLock sync.Mutex
|
contextLock sync.Mutex
|
||||||
|
interpolaterVars map[string]map[string]string
|
||||||
|
interpolaterVarLock sync.Mutex
|
||||||
providerCache map[string]ResourceProvider
|
providerCache map[string]ResourceProvider
|
||||||
providerConfigCache map[string]*ResourceConfig
|
providerConfigCache map[string]*ResourceConfig
|
||||||
providerLock sync.Mutex
|
providerLock sync.Mutex
|
||||||
@ -33,29 +35,36 @@ type ContextGraphWalker struct {
|
|||||||
provisionerLock sync.Mutex
|
provisionerLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext {
|
func (w *ContextGraphWalker) EnterPath(path []string) EvalContext {
|
||||||
w.once.Do(w.init)
|
w.once.Do(w.init)
|
||||||
|
|
||||||
w.contextLock.Lock()
|
w.contextLock.Lock()
|
||||||
defer w.contextLock.Unlock()
|
defer w.contextLock.Unlock()
|
||||||
|
|
||||||
// If we already have a context for this path cached, use that
|
// If we already have a context for this path cached, use that
|
||||||
key := PathCacheKey(g.Path)
|
key := PathCacheKey(path)
|
||||||
if ctx, ok := w.contexts[key]; ok {
|
if ctx, ok := w.contexts[key]; ok {
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variables should be our context variables, but these only apply
|
// Setup the variables for this interpolater
|
||||||
// to the root module. As we enter subgraphs, we don't want to set
|
variables := make(map[string]string)
|
||||||
// variables, which is set by the SetVariables EvalContext function.
|
if len(path) <= 1 {
|
||||||
variables := w.Context.variables
|
for k, v := range w.Context.variables {
|
||||||
if len(g.Path) > 1 {
|
variables[k] = v
|
||||||
// We're in a submodule, the variables should be empty
|
}
|
||||||
variables = make(map[string]string)
|
|
||||||
}
|
}
|
||||||
|
w.interpolaterVarLock.Lock()
|
||||||
|
if m, ok := w.interpolaterVars[key]; ok {
|
||||||
|
for k, v := range m {
|
||||||
|
variables[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.interpolaterVars[key] = variables
|
||||||
|
w.interpolaterVarLock.Unlock()
|
||||||
|
|
||||||
ctx := &BuiltinEvalContext{
|
ctx := &BuiltinEvalContext{
|
||||||
PathValue: g.Path,
|
PathValue: path,
|
||||||
Hooks: w.Context.hooks,
|
Hooks: w.Context.hooks,
|
||||||
InputValue: w.Context.uiInput,
|
InputValue: w.Context.uiInput,
|
||||||
Providers: w.Context.providers,
|
Providers: w.Context.providers,
|
||||||
@ -77,6 +86,8 @@ func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext {
|
|||||||
StateLock: &w.Context.stateLock,
|
StateLock: &w.Context.stateLock,
|
||||||
Variables: variables,
|
Variables: variables,
|
||||||
},
|
},
|
||||||
|
InterpolaterVars: w.interpolaterVars,
|
||||||
|
InterpolaterVarLock: &w.interpolaterVarLock,
|
||||||
}
|
}
|
||||||
|
|
||||||
w.contexts[key] = ctx
|
w.contexts[key] = ctx
|
||||||
@ -131,4 +142,5 @@ func (w *ContextGraphWalker) init() {
|
|||||||
w.providerCache = make(map[string]ResourceProvider, 5)
|
w.providerCache = make(map[string]ResourceProvider, 5)
|
||||||
w.providerConfigCache = make(map[string]*ResourceConfig, 5)
|
w.providerConfigCache = make(map[string]*ResourceConfig, 5)
|
||||||
w.provisionerCache = make(map[string]ResourceProvisioner, 5)
|
w.provisionerCache = make(map[string]ResourceProvisioner, 5)
|
||||||
|
w.interpolaterVars = make(map[string]map[string]string, 5)
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@ package terraform
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
const _GraphNodeConfigType_name = "GraphNodeConfigTypeInvalidGraphNodeConfigTypeResourceGraphNodeConfigTypeProviderGraphNodeConfigTypeModuleGraphNodeConfigTypeOutput"
|
const _GraphNodeConfigType_name = "GraphNodeConfigTypeInvalidGraphNodeConfigTypeResourceGraphNodeConfigTypeProviderGraphNodeConfigTypeModuleGraphNodeConfigTypeOutputGraphNodeConfigTypeVariable"
|
||||||
|
|
||||||
var _GraphNodeConfigType_index = [...]uint8{0, 26, 53, 80, 105, 130}
|
var _GraphNodeConfigType_index = [...]uint8{0, 26, 53, 80, 105, 130, 157}
|
||||||
|
|
||||||
func (i GraphNodeConfigType) String() string {
|
func (i GraphNodeConfigType) String() string {
|
||||||
if i < 0 || i+1 >= GraphNodeConfigType(len(_GraphNodeConfigType_index)) {
|
if i < 0 || i+1 >= GraphNodeConfigType(len(_GraphNodeConfigType_index)) {
|
||||||
|
@ -362,6 +362,12 @@ module.child:
|
|||||||
leader = 1
|
leader = 1
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testTerraformApplyModuleDestroyOrderStr = `
|
||||||
|
<no state>
|
||||||
|
module.child:
|
||||||
|
<no state>
|
||||||
|
`
|
||||||
|
|
||||||
const testTerraformApplyMultiProviderStr = `
|
const testTerraformApplyMultiProviderStr = `
|
||||||
aws_instance.bar:
|
aws_instance.bar:
|
||||||
ID = foo
|
ID = foo
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
resource "aws_instance" "a" {}
|
||||||
|
|
||||||
|
output "a_output" {
|
||||||
|
value = "${aws_instance.a.id}"
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "b" {
|
||||||
|
blah = "${module.child.a_output}"
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
variable "value" {}
|
||||||
|
|
||||||
resource "aws_instance" "foo" {
|
resource "aws_instance" "foo" {
|
||||||
num = "2"
|
num = "2"
|
||||||
compute = "dynamical"
|
compute = "dynamical"
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
variable "pass" {}
|
||||||
|
variable "value" {}
|
||||||
|
|
||||||
resource "aws_instance" "foo" {
|
resource "aws_instance" "foo" {
|
||||||
num = "2"
|
num = "2"
|
||||||
compute = "dynamical"
|
compute = "dynamical"
|
||||||
|
@ -1 +1,3 @@
|
|||||||
resource "aws_instance" "server" {}
|
resource "aws_instance" "server" {}
|
||||||
|
|
||||||
|
output "security_group" { value = "" }
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
resource "aws_instance" "foo" {}
|
@ -0,0 +1,3 @@
|
|||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
}
|
@ -2,4 +2,3 @@ module "child" {
|
|||||||
source = "./child"
|
source = "./child"
|
||||||
instance_count = "2"
|
instance_count = "2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,3 +7,5 @@ provider "aws" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_instance" "foo" {}
|
resource "aws_instance" "foo" {}
|
||||||
|
|
||||||
|
variable "foo" {}
|
||||||
|
9
terraform/test-fixtures/transform-flatten/child/main.tf
Normal file
9
terraform/test-fixtures/transform-flatten/child/main.tf
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
variable "var" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "child" {
|
||||||
|
value = "${var.var}"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "output" {
|
||||||
|
value = "${aws_instance.child.value}"
|
||||||
|
}
|
12
terraform/test-fixtures/transform-flatten/main.tf
Normal file
12
terraform/test-fixtures/transform-flatten/main.tf
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
var = "${aws_instance.parent.value}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "parent" {
|
||||||
|
value = "foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "parent-output" {
|
||||||
|
value = "${module.child.output}"
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
resource "aws_instance" "a" { }
|
||||||
|
|
||||||
|
output "output" {
|
||||||
|
value = "${aws_instance.a.id}"
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
variable "input" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "b" {
|
||||||
|
name = "${var.input}"
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
module "a" {
|
||||||
|
source = "./a"
|
||||||
|
}
|
||||||
|
|
||||||
|
module "b" {
|
||||||
|
source = "./b"
|
||||||
|
input = "${module.a.output}"
|
||||||
|
}
|
@ -37,7 +37,16 @@ func (t *ConfigTransformer) Transform(g *Graph) error {
|
|||||||
|
|
||||||
// Create the node list we'll use for the graph
|
// Create the node list we'll use for the graph
|
||||||
nodes := make([]graphNodeConfig, 0,
|
nodes := make([]graphNodeConfig, 0,
|
||||||
(len(config.ProviderConfigs)+len(config.Modules)+len(config.Resources))*2)
|
(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})
|
||||||
|
}
|
||||||
|
|
||||||
// Write all the provider configs out
|
// Write all the provider configs out
|
||||||
for _, pc := range config.ProviderConfigs {
|
for _, pc := range config.ProviderConfigs {
|
||||||
@ -96,9 +105,11 @@ func (t *ConfigTransformer) Transform(g *Graph) error {
|
|||||||
func varNameForVar(raw config.InterpolatedVariable) string {
|
func varNameForVar(raw config.InterpolatedVariable) string {
|
||||||
switch v := raw.(type) {
|
switch v := raw.(type) {
|
||||||
case *config.ModuleVariable:
|
case *config.ModuleVariable:
|
||||||
return fmt.Sprintf("module.%s", v.Name)
|
return fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)
|
||||||
case *config.ResourceVariable:
|
case *config.ResourceVariable:
|
||||||
return v.ResourceId()
|
return v.ResourceId()
|
||||||
|
case *config.UserVariable:
|
||||||
|
return fmt.Sprintf("var.%s", v.Name)
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -111,12 +111,14 @@ func TestConfigTransformer_errMissingDeps(t *testing.T) {
|
|||||||
const testGraphBasicStr = `
|
const testGraphBasicStr = `
|
||||||
aws_instance.web
|
aws_instance.web
|
||||||
aws_security_group.firewall
|
aws_security_group.firewall
|
||||||
|
var.foo
|
||||||
aws_load_balancer.weblb
|
aws_load_balancer.weblb
|
||||||
aws_instance.web
|
aws_instance.web
|
||||||
aws_security_group.firewall
|
aws_security_group.firewall
|
||||||
openstack_floating_ip.random
|
openstack_floating_ip.random
|
||||||
provider.aws
|
provider.aws
|
||||||
openstack_floating_ip.random
|
openstack_floating_ip.random
|
||||||
|
var.foo
|
||||||
`
|
`
|
||||||
|
|
||||||
const testGraphDependsOnStr = `
|
const testGraphDependsOnStr = `
|
||||||
|
@ -45,6 +45,13 @@ type GraphNodeDestroyPrunable interface {
|
|||||||
DestroyInclude(*ModuleDiff, *ModuleState) bool
|
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() bool
|
||||||
|
}
|
||||||
|
|
||||||
// DestroyTransformer is a GraphTransformer that creates the destruction
|
// DestroyTransformer is a GraphTransformer that creates the destruction
|
||||||
// nodes for things that _might_ be destroyed.
|
// nodes for things that _might_ be destroyed.
|
||||||
type DestroyTransformer struct{}
|
type DestroyTransformer struct{}
|
||||||
@ -102,6 +109,12 @@ func (t *DestroyTransformer) transform(
|
|||||||
// Inherit all the edges from the old node
|
// Inherit all the edges from the old node
|
||||||
downEdges := g.DownEdges(v).List()
|
downEdges := g.DownEdges(v).List()
|
||||||
for _, edgeRaw := range downEdges {
|
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() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
g.Connect(dag.BasicEdge(n, edgeRaw.(dag.Vertex)))
|
g.Connect(dag.BasicEdge(n, edgeRaw.(dag.Vertex)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,15 +217,6 @@ type PruneDestroyTransformer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *PruneDestroyTransformer) Transform(g *Graph) error {
|
func (t *PruneDestroyTransformer) Transform(g *Graph) error {
|
||||||
var modDiff *ModuleDiff
|
|
||||||
var modState *ModuleState
|
|
||||||
if t.Diff != nil {
|
|
||||||
modDiff = t.Diff.ModuleByPath(g.Path)
|
|
||||||
}
|
|
||||||
if t.State != nil {
|
|
||||||
modState = t.State.ModuleByPath(g.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range g.Vertices() {
|
for _, v := range g.Vertices() {
|
||||||
// If it is not a destroyer, we don't care
|
// If it is not a destroyer, we don't care
|
||||||
dn, ok := v.(GraphNodeDestroyPrunable)
|
dn, ok := v.(GraphNodeDestroyPrunable)
|
||||||
@ -220,6 +224,20 @@ func (t *PruneDestroyTransformer) Transform(g *Graph) error {
|
|||||||
continue
|
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
|
// Remove it if we should
|
||||||
if !dn.DestroyInclude(modDiff, modState) {
|
if !dn.DestroyInclude(modDiff, modState) {
|
||||||
g.Remove(v)
|
g.Remove(v)
|
||||||
|
101
terraform/transform_flatten.go
Normal file
101
terraform/transform_flatten.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
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() {
|
||||||
|
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 dependend 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
|
||||||
|
}
|
95
terraform/transform_flatten_test.go
Normal file
95
terraform/transform_flatten_test.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFlattenTransformer(t *testing.T) {
|
||||||
|
mod := testModule(t, "transform-flatten")
|
||||||
|
|
||||||
|
var b BasicGraphBuilder
|
||||||
|
b = BasicGraphBuilder{
|
||||||
|
Steps: []GraphTransformer{
|
||||||
|
&ConfigTransformer{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{
|
||||||
|
&ConfigTransformer{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,6 +1,7 @@
|
|||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/hashicorp/terraform/dag"
|
"github.com/hashicorp/terraform/dag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,6 +34,61 @@ func (t *ModuleInputTransformer) Transform(g *Graph) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 ModuleDestroyTransformer struct{}
|
||||||
|
|
||||||
|
func (t *ModuleDestroyTransformer) 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
|
||||||
|
}
|
||||||
|
|
||||||
type graphNodeModuleInput struct {
|
type graphNodeModuleInput struct {
|
||||||
Variables map[string]string
|
Variables map[string]string
|
||||||
}
|
}
|
||||||
@ -45,3 +101,8 @@ func (n *graphNodeModuleInput) Name() string {
|
|||||||
func (n *graphNodeModuleInput) EvalTree() EvalNode {
|
func (n *graphNodeModuleInput) EvalTree() EvalNode {
|
||||||
return &EvalSetVariables{Variables: n.Variables}
|
return &EvalSetVariables{Variables: n.Variables}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// graphNodeModuleSkippable impl.
|
||||||
|
func (n *graphNodeModuleInput) FlattenSkip() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
@ -32,7 +32,7 @@ func (t *DisableProviderTransformer) Transform(g *Graph) error {
|
|||||||
for _, v := range g.Vertices() {
|
for _, v := range g.Vertices() {
|
||||||
// We only care about providers
|
// We only care about providers
|
||||||
pn, ok := v.(GraphNodeProvider)
|
pn, ok := v.(GraphNodeProvider)
|
||||||
if !ok {
|
if !ok || pn.ProviderName() == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ type PruneProviderTransformer struct{}
|
|||||||
func (t *PruneProviderTransformer) Transform(g *Graph) error {
|
func (t *PruneProviderTransformer) Transform(g *Graph) error {
|
||||||
for _, v := range g.Vertices() {
|
for _, v := range g.Vertices() {
|
||||||
// We only care about the providers
|
// We only care about the providers
|
||||||
if _, ok := v.(GraphNodeProvider); !ok {
|
if pn, ok := v.(GraphNodeProvider); !ok || pn.ProviderName() == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,6 +190,21 @@ func (n *graphNodeDisabledProvider) DotOrigin() bool {
|
|||||||
return true
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
type graphNodeMissingProvider struct {
|
type graphNodeMissingProvider struct {
|
||||||
ProviderNameValue string
|
ProviderNameValue string
|
||||||
}
|
}
|
||||||
@ -203,6 +218,11 @@ func (n *graphNodeMissingProvider) EvalTree() EvalNode {
|
|||||||
return ProviderEvalTree(n.ProviderNameValue, nil)
|
return ProviderEvalTree(n.ProviderNameValue, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GraphNodeDependable impl.
|
||||||
|
func (n *graphNodeMissingProvider) DependableName() []string {
|
||||||
|
return []string{n.Name()}
|
||||||
|
}
|
||||||
|
|
||||||
func (n *graphNodeMissingProvider) ProviderName() string {
|
func (n *graphNodeMissingProvider) ProviderName() string {
|
||||||
return n.ProviderNameValue
|
return n.ProviderNameValue
|
||||||
}
|
}
|
||||||
@ -224,6 +244,14 @@ func (n *graphNodeMissingProvider) DotOrigin() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GraphNodeFlattenable impl.
|
||||||
|
func (n *graphNodeMissingProvider) Flatten(p []string) (dag.Vertex, error) {
|
||||||
|
return &graphNodeMissingProviderFlat{
|
||||||
|
graphNodeMissingProvider: n,
|
||||||
|
PathValue: p,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func providerVertexMap(g *Graph) map[string]dag.Vertex {
|
func providerVertexMap(g *Graph) map[string]dag.Vertex {
|
||||||
m := make(map[string]dag.Vertex)
|
m := make(map[string]dag.Vertex)
|
||||||
for _, v := range g.Vertices() {
|
for _, v := range g.Vertices() {
|
||||||
@ -234,3 +262,48 @@ func providerVertexMap(g *Graph) map[string]dag.Vertex {
|
|||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same as graphNodeMissingProvider, but for flattening
|
||||||
|
type graphNodeMissingProviderFlat struct {
|
||||||
|
*graphNodeMissingProvider
|
||||||
|
|
||||||
|
PathValue []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeMissingProviderFlat) Name() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeMissingProvider.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeMissingProviderFlat) Path() []string {
|
||||||
|
return n.PathValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeMissingProviderFlat) ProviderName() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s.%s", modulePrefixStr(n.PathValue),
|
||||||
|
n.graphNodeMissingProvider.ProviderName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDependable impl.
|
||||||
|
func (n *graphNodeMissingProviderFlat) DependableName() []string {
|
||||||
|
return []string{n.Name()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeMissingProviderFlat) 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])
|
||||||
|
if prefix != "" {
|
||||||
|
prefix += "."
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, fmt.Sprintf(
|
||||||
|
"%s%s",
|
||||||
|
prefix, n.graphNodeMissingProvider.Name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -218,7 +218,9 @@ provider.foo
|
|||||||
const testTransformDisableProviderBasicStr = `
|
const testTransformDisableProviderBasicStr = `
|
||||||
module.child
|
module.child
|
||||||
provider.aws (disabled)
|
provider.aws (disabled)
|
||||||
|
var.foo
|
||||||
provider.aws (disabled)
|
provider.aws (disabled)
|
||||||
|
var.foo
|
||||||
`
|
`
|
||||||
|
|
||||||
const testTransformDisableProviderKeepStr = `
|
const testTransformDisableProviderKeepStr = `
|
||||||
@ -226,5 +228,7 @@ aws_instance.foo
|
|||||||
provider.aws
|
provider.aws
|
||||||
module.child
|
module.child
|
||||||
provider.aws
|
provider.aws
|
||||||
|
var.foo
|
||||||
provider.aws
|
provider.aws
|
||||||
|
var.foo
|
||||||
`
|
`
|
||||||
|
@ -111,6 +111,14 @@ func (n *graphNodeMissingProvisioner) ProvisionerName() string {
|
|||||||
return n.ProvisionerNameValue
|
return n.ProvisionerNameValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GraphNodeFlattenable impl.
|
||||||
|
func (n *graphNodeMissingProvisioner) Flatten(p []string) (dag.Vertex, error) {
|
||||||
|
return &graphNodeMissingProvisionerFlat{
|
||||||
|
graphNodeMissingProvisioner: n,
|
||||||
|
PathValue: p,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func provisionerVertexMap(g *Graph) map[string]dag.Vertex {
|
func provisionerVertexMap(g *Graph) map[string]dag.Vertex {
|
||||||
m := make(map[string]dag.Vertex)
|
m := make(map[string]dag.Vertex)
|
||||||
for _, v := range g.Vertices() {
|
for _, v := range g.Vertices() {
|
||||||
@ -121,3 +129,25 @@ func provisionerVertexMap(g *Graph) map[string]dag.Vertex {
|
|||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same as graphNodeMissingProvisioner, but for flattening
|
||||||
|
type graphNodeMissingProvisionerFlat struct {
|
||||||
|
*graphNodeMissingProvisioner
|
||||||
|
|
||||||
|
PathValue []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeMissingProvisionerFlat) Name() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeMissingProvisioner.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeMissingProvisionerFlat) Path() []string {
|
||||||
|
return n.PathValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeMissingProvisionerFlat) ProvisionerName() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s.%s", modulePrefixStr(n.PathValue),
|
||||||
|
n.graphNodeMissingProvisioner.ProvisionerName())
|
||||||
|
}
|
||||||
|
62
terraform/transform_proxy.go
Normal file
62
terraform/transform_proxy.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
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
|
||||||
|
}
|
52
terraform/transform_proxy_test.go
Normal file
52
terraform/transform_proxy_test.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
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
|
||||||
|
`
|
@ -2,6 +2,7 @@ package terraform
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/dag"
|
"github.com/hashicorp/terraform/dag"
|
||||||
@ -169,6 +170,27 @@ func (n *graphNodeExpandedResource) ProvidedBy() []string {
|
|||||||
return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)}
|
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.
|
// GraphNodeEvalable impl.
|
||||||
func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
||||||
var diff *InstanceDiff
|
var diff *InstanceDiff
|
||||||
@ -257,7 +279,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
|||||||
Name: n.stateId(),
|
Name: n.stateId(),
|
||||||
ResourceType: n.Resource.Type,
|
ResourceType: n.Resource.Type,
|
||||||
Provider: n.Resource.Provider,
|
Provider: n.Resource.Provider,
|
||||||
Dependencies: n.DependentOn(),
|
Dependencies: n.StateDependencies(),
|
||||||
State: &state,
|
State: &state,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -298,7 +320,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
|||||||
Name: n.stateId(),
|
Name: n.stateId(),
|
||||||
ResourceType: n.Resource.Type,
|
ResourceType: n.Resource.Type,
|
||||||
Provider: n.Resource.Provider,
|
Provider: n.Resource.Provider,
|
||||||
Dependencies: n.DependentOn(),
|
Dependencies: n.StateDependencies(),
|
||||||
State: &state,
|
State: &state,
|
||||||
},
|
},
|
||||||
&EvalDiffTainted{
|
&EvalDiffTainted{
|
||||||
@ -445,7 +467,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
|||||||
Name: n.stateId(),
|
Name: n.stateId(),
|
||||||
ResourceType: n.Resource.Type,
|
ResourceType: n.Resource.Type,
|
||||||
Provider: n.Resource.Provider,
|
Provider: n.Resource.Provider,
|
||||||
Dependencies: n.DependentOn(),
|
Dependencies: n.StateDependencies(),
|
||||||
State: &state,
|
State: &state,
|
||||||
},
|
},
|
||||||
&EvalApplyProvisioners{
|
&EvalApplyProvisioners{
|
||||||
@ -489,7 +511,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
|||||||
Name: n.stateId(),
|
Name: n.stateId(),
|
||||||
ResourceType: n.Resource.Type,
|
ResourceType: n.Resource.Type,
|
||||||
Provider: n.Resource.Provider,
|
Provider: n.Resource.Provider,
|
||||||
Dependencies: n.DependentOn(),
|
Dependencies: n.StateDependencies(),
|
||||||
State: &state,
|
State: &state,
|
||||||
Index: -1,
|
Index: -1,
|
||||||
},
|
},
|
||||||
@ -507,7 +529,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
|||||||
Name: n.stateId(),
|
Name: n.stateId(),
|
||||||
ResourceType: n.Resource.Type,
|
ResourceType: n.Resource.Type,
|
||||||
Provider: n.Resource.Provider,
|
Provider: n.Resource.Provider,
|
||||||
Dependencies: n.DependentOn(),
|
Dependencies: n.StateDependencies(),
|
||||||
State: &state,
|
State: &state,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -618,7 +640,7 @@ func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
|
|||||||
Name: n.stateId(),
|
Name: n.stateId(),
|
||||||
ResourceType: n.Resource.Type,
|
ResourceType: n.Resource.Type,
|
||||||
Provider: n.Resource.Provider,
|
Provider: n.Resource.Provider,
|
||||||
Dependencies: n.DependentOn(),
|
Dependencies: n.StateDependencies(),
|
||||||
State: &state,
|
State: &state,
|
||||||
},
|
},
|
||||||
&EvalApplyPost{
|
&EvalApplyPost{
|
||||||
|
@ -34,3 +34,7 @@ type graphNodeRoot struct{}
|
|||||||
func (n graphNodeRoot) Name() string {
|
func (n graphNodeRoot) Name() string {
|
||||||
return "root"
|
return "root"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n graphNodeRoot) Flatten(p []string) (dag.Vertex, error) {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user