terraform: redo how flattening works
This commit is contained in:
parent
f2e7f505d4
commit
86d07d3b5b
|
@ -134,6 +134,12 @@ 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
|
||||
// their dependencies.
|
||||
&TargetsTransformer{Targets: b.Targets, Destroy: b.Destroy},
|
||||
|
|
|
@ -2,7 +2,6 @@ package terraform
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
|
@ -45,60 +44,6 @@ type GraphNodeTargetable interface {
|
|||
SetTargets([]ResourceAddress)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -161,382 +106,3 @@ func (n *GraphNodeConfigProvider) DotNode(name string, opts *GraphDotOpts) *dot.
|
|||
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
|
||||
}
|
||||
|
|
|
@ -166,22 +166,6 @@ func (n *graphNodeModuleExpanded) FlattenGraph() *Graph {
|
|||
graph := n.Subgraph()
|
||||
input := n.Original.Module.RawConfig
|
||||
|
||||
// Build the string that represents the path. We do this once here
|
||||
// so that we only have to compute it once. The block below is in {}
|
||||
// so that parts drops out of scope immediately.
|
||||
var pathStr, parentPathStr string
|
||||
{
|
||||
parts := make([]string, 0, len(graph.Path)*2)
|
||||
for _, p := range graph.Path[1:] {
|
||||
parts = append(parts, "module", p)
|
||||
}
|
||||
|
||||
pathStr = strings.Join(parts, ".")
|
||||
if len(parts) > 2 {
|
||||
parentPathStr = strings.Join(parts[0:len(parts)-2], ".")
|
||||
}
|
||||
}
|
||||
|
||||
// Go over each vertex in the graph and wrap the configuration
|
||||
// items so that the dependencies properly map to the modules.
|
||||
// See the docs for graphNodeModuleWrappable for more info.
|
||||
|
@ -191,15 +175,6 @@ func (n *graphNodeModuleExpanded) FlattenGraph() *Graph {
|
|||
continue
|
||||
}
|
||||
|
||||
wn, ok := v.(graphNodeModuleWrappable)
|
||||
if !ok {
|
||||
panic("unwrappable node: " + dag.VertexName(v))
|
||||
}
|
||||
|
||||
// Prefix for dependencies. We set this to blank for variables
|
||||
// (see below).
|
||||
depPrefix := pathStr
|
||||
|
||||
// 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.(GraphNodeVariable); ok && input != nil {
|
||||
|
@ -217,20 +192,7 @@ func (n *graphNodeModuleExpanded) FlattenGraph() *Graph {
|
|||
// Set the variable value so it is interpolated properly
|
||||
vn.SetVariableValue(config)
|
||||
}
|
||||
|
||||
// We set the dependency prefix to the parent path
|
||||
// since variables can reference outside of our own graph
|
||||
// to the parent graph.
|
||||
depPrefix = parentPathStr
|
||||
}
|
||||
|
||||
graph.Replace(v, &graphNodeModuleFlatWrap{
|
||||
graphNodeModuleWrappable: wn,
|
||||
|
||||
PathValue: graph.Path,
|
||||
NamePrefix: pathStr,
|
||||
DependentOnPrefix: depPrefix,
|
||||
})
|
||||
}
|
||||
|
||||
return graph
|
||||
|
@ -247,58 +209,57 @@ type graphNodeModuleSkippable interface {
|
|||
FlattenSkip() bool
|
||||
}
|
||||
|
||||
// This is the interface that all nodes within a module graph must
|
||||
// implement to be wrapped for flattening.
|
||||
type graphNodeModuleWrappable interface {
|
||||
graphNodeConfig
|
||||
}
|
||||
|
||||
/*
|
||||
// graphNodeModuleFlatWrap wraps elements of the module graph
|
||||
// when they are flattened so that the dependency information is
|
||||
// correct.
|
||||
type graphNodeModuleFlatWrap struct {
|
||||
graphNodeModuleWrappable
|
||||
|
||||
Vertex dag.Vertex
|
||||
PathValue []string
|
||||
NamePrefix string
|
||||
DependentOnPrefix string
|
||||
}
|
||||
|
||||
func (n *graphNodeModuleFlatWrap) Name() string {
|
||||
return fmt.Sprintf("%s.%s", n.NamePrefix, n.graphNodeModuleWrappable.Name())
|
||||
}
|
||||
|
||||
// GraphNodeSubPath impl.
|
||||
func (n *graphNodeModuleFlatWrap) Path() []string {
|
||||
return n.PathValue
|
||||
}
|
||||
|
||||
func (n *graphNodeModuleFlatWrap) DependableName() []string {
|
||||
result := n.graphNodeModuleWrappable.DependableName()
|
||||
n.prefixList(result, n.NamePrefix)
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *graphNodeModuleFlatWrap) DependentOn() []string {
|
||||
result := n.graphNodeModuleWrappable.DependentOn()
|
||||
if n.DependentOnPrefix != "" {
|
||||
n.prefixList(result, n.DependentOnPrefix)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *graphNodeModuleFlatWrap) Proxy() bool {
|
||||
pn, ok := n.graphNodeModuleWrappable.(GraphNodeProxy)
|
||||
// GraphNodeProvider impl.
|
||||
func (n *graphNodeModuleFlatWrap) ProviderName() string {
|
||||
pn, ok := n.Vertex.(GraphNodeProvider)
|
||||
if !ok {
|
||||
return false
|
||||
return ""
|
||||
}
|
||||
|
||||
return pn.Proxy()
|
||||
return fmt.Sprintf("%s.%s", n.NamePrefix, pn.ProviderName())
|
||||
}
|
||||
|
||||
func (n *graphNodeModuleFlatWrap) prefixList(result []string, prefix string) {
|
||||
// GraphNodeProviderConsumer impl.
|
||||
func (n *graphNodeModuleFlatWrap) ProvidedBy() []string {
|
||||
pn, ok := n.Vertex.(GraphNodeProviderConsumer)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]string, 0, 2)
|
||||
for _, v := range pn.ProvidedBy() {
|
||||
result = append(result, fmt.Sprintf("%s.%s", n.NamePrefix, v))
|
||||
}
|
||||
return result
|
||||
}
|
||||
*/
|
||||
|
||||
func modulePrefixStr(p []string) string {
|
||||
parts := make([]string, 0, len(p)*2)
|
||||
for _, p := range p[1:] {
|
||||
parts = append(parts, "module", p)
|
||||
}
|
||||
|
||||
return strings.Join(parts, ".")
|
||||
}
|
||||
|
||||
func modulePrefixList(result []string, prefix string) []string {
|
||||
if prefix != "" {
|
||||
for i, v := range result {
|
||||
result[i] = fmt.Sprintf("%s.%s", prefix, v)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -59,7 +58,7 @@ func TestGraphNodeConfigModuleExpandFlatten(t *testing.T) {
|
|||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
fg := g.(GraphNodeFlattenable)
|
||||
fg := g.(GraphNodeFlatGraph)
|
||||
|
||||
actual := strings.TrimSpace(fg.FlattenGraph().String())
|
||||
expected := strings.TrimSpace(testGraphNodeModuleExpandFlattenStr)
|
||||
|
@ -68,73 +67,6 @@ func TestGraphNodeConfigModuleExpandFlatten(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGraphNodeModulFlatWrap_Name(t *testing.T) {
|
||||
n := &graphNodeModuleFlatWrap{
|
||||
graphNodeModuleWrappable: &testGraphNodeModuleWrappable{
|
||||
NameValue: "foo",
|
||||
},
|
||||
|
||||
NamePrefix: "module.bar",
|
||||
}
|
||||
|
||||
if v := n.Name(); v != "module.bar.foo" {
|
||||
t.Fatalf("bad: %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraphNodeModulFlatWrap_DependentOn(t *testing.T) {
|
||||
n := &graphNodeModuleFlatWrap{
|
||||
graphNodeModuleWrappable: &testGraphNodeModuleWrappable{
|
||||
NameValue: "foo",
|
||||
},
|
||||
|
||||
NamePrefix: "module.bar",
|
||||
DependentOnPrefix: "module.bar",
|
||||
}
|
||||
|
||||
actual := n.DependentOn()
|
||||
expected := []string{"module.bar.foo"}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraphNodeModulFlatWrap_DependableName(t *testing.T) {
|
||||
n := &graphNodeModuleFlatWrap{
|
||||
graphNodeModuleWrappable: &testGraphNodeModuleWrappable{
|
||||
NameValue: "foo",
|
||||
},
|
||||
|
||||
NamePrefix: "module.bar",
|
||||
}
|
||||
|
||||
actual := n.DependableName()
|
||||
expected := []string{"module.bar.foo"}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
type testGraphNodeModuleWrappable struct {
|
||||
NameValue string
|
||||
}
|
||||
|
||||
func (n *testGraphNodeModuleWrappable) ConfigType() GraphNodeConfigType {
|
||||
return GraphNodeConfigTypeInvalid
|
||||
}
|
||||
|
||||
func (n *testGraphNodeModuleWrappable) Name() string {
|
||||
return n.NameValue
|
||||
}
|
||||
|
||||
func (n *testGraphNodeModuleWrappable) DependableName() []string {
|
||||
return []string{"foo"}
|
||||
}
|
||||
|
||||
func (n *testGraphNodeModuleWrappable) DependentOn() []string {
|
||||
return []string{"foo"}
|
||||
}
|
||||
|
||||
const testGraphNodeModuleExpandStr = `
|
||||
aws_instance.bar
|
||||
aws_instance.foo
|
||||
|
@ -144,5 +76,5 @@ module inputs
|
|||
`
|
||||
|
||||
const testGraphNodeModuleExpandFlattenStr = `
|
||||
module.child.aws_instance.foo
|
||||
aws_instance.foo
|
||||
`
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
|
@ -0,0 +1,426 @@
|
|||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
|
@ -108,11 +108,3 @@ func TestGraphNodeConfigResource_ProvisionedBy(t *testing.T) {
|
|||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraphNodeConfigVariable_impl(t *testing.T) {
|
||||
var _ dag.Vertex = new(GraphNodeConfigVariable)
|
||||
var _ dag.NamedVertex = new(GraphNodeConfigVariable)
|
||||
var _ graphNodeConfig = new(GraphNodeConfigVariable)
|
||||
var _ GraphNodeVariable = new(GraphNodeConfigVariable)
|
||||
var _ GraphNodeProxy = new(GraphNodeConfigVariable)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
// GraphNodeVariable is the interface that must be implemented by anything
|
||||
|
@ -20,6 +21,8 @@ type GraphNodeConfigVariable struct {
|
|||
// Value, if non-nil, will be used to set the value of the variable
|
||||
// during evaluation. If this is nil, evaluation will do nothing.
|
||||
Value *config.RawConfig
|
||||
|
||||
depPrefix string
|
||||
}
|
||||
|
||||
func (n *GraphNodeConfigVariable) Name() string {
|
||||
|
@ -96,3 +99,44 @@ func (n *GraphNodeConfigVariable) EvalTree() EvalNode {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return n.PathValue
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
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 _ GraphNodeVariable = 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 _ GraphNodeVariable = new(GraphNodeConfigVariableFlat)
|
||||
var _ GraphNodeProxy = new(GraphNodeConfigVariableFlat)
|
||||
}
|
|
@ -1,15 +1,27 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
// GraphNodeFlattenable must be implemented by nodes that can be flattened
|
||||
// into the graph.
|
||||
type GraphNodeFlattenable interface {
|
||||
// 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.
|
||||
|
@ -17,7 +29,7 @@ type FlattenTransformer struct{}
|
|||
|
||||
func (t *FlattenTransformer) Transform(g *Graph) error {
|
||||
for _, v := range g.Vertices() {
|
||||
fn, ok := v.(GraphNodeFlattenable)
|
||||
fn, ok := v.(GraphNodeFlatGraph)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
@ -39,8 +51,31 @@ func (t *FlattenTransformer) Transform(g *Graph) error {
|
|||
// Remove the old node
|
||||
g.Remove(v)
|
||||
|
||||
// Flatten the subgraph into this one. Keep any existing
|
||||
// connections that existed.
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ func (t *DisableProviderTransformer) Transform(g *Graph) error {
|
|||
for _, v := range g.Vertices() {
|
||||
// We only care about providers
|
||||
pn, ok := v.(GraphNodeProvider)
|
||||
if !ok {
|
||||
if !ok || pn.ProviderName() == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -130,7 +130,7 @@ type PruneProviderTransformer struct{}
|
|||
func (t *PruneProviderTransformer) Transform(g *Graph) error {
|
||||
for _, v := range g.Vertices() {
|
||||
// We only care about the providers
|
||||
if _, ok := v.(GraphNodeProvider); !ok {
|
||||
if pn, ok := v.(GraphNodeProvider); !ok || pn.ProviderName() == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue