github.com/pmcatominey/terraform@v0.7.0-rc2.0.20160708105029-1401a52a5cc5/terraform/graph_config_node_variable.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hashicorp/terraform/config"
     8  	"github.com/hashicorp/terraform/config/module"
     9  	"github.com/hashicorp/terraform/dag"
    10  )
    11  
    12  // GraphNodeConfigVariable represents a Variable in the config.
    13  type GraphNodeConfigVariable struct {
    14  	Variable *config.Variable
    15  
    16  	// Value, if non-nil, will be used to set the value of the variable
    17  	// during evaluation. If this is nil, evaluation will do nothing.
    18  	//
    19  	// Module is the name of the module to set the variables on.
    20  	Module string
    21  	Value  *config.RawConfig
    22  
    23  	ModuleTree *module.Tree
    24  	ModulePath []string
    25  }
    26  
    27  func (n *GraphNodeConfigVariable) Name() string {
    28  	return fmt.Sprintf("var.%s", n.Variable.Name)
    29  }
    30  
    31  func (n *GraphNodeConfigVariable) ConfigType() GraphNodeConfigType {
    32  	return GraphNodeConfigTypeVariable
    33  }
    34  
    35  func (n *GraphNodeConfigVariable) DependableName() []string {
    36  	return []string{n.Name()}
    37  }
    38  
    39  func (n *GraphNodeConfigVariable) DependentOn() []string {
    40  	// If we don't have any value set, we don't depend on anything
    41  	if n.Value == nil {
    42  		return nil
    43  	}
    44  
    45  	// Get what we depend on based on our value
    46  	vars := n.Value.Variables
    47  	result := make([]string, 0, len(vars))
    48  	for _, v := range vars {
    49  		if vn := varNameForVar(v); vn != "" {
    50  			result = append(result, vn)
    51  		}
    52  	}
    53  
    54  	return result
    55  }
    56  
    57  func (n *GraphNodeConfigVariable) VariableName() string {
    58  	return n.Variable.Name
    59  }
    60  
    61  // GraphNodeDestroyEdgeInclude impl.
    62  func (n *GraphNodeConfigVariable) DestroyEdgeInclude(v dag.Vertex) bool {
    63  	// Only include this variable in a destroy edge if the source vertex
    64  	// "v" has a count dependency on this variable.
    65  	log.Printf("[DEBUG] DestroyEdgeInclude: Checking: %s", dag.VertexName(v))
    66  	cv, ok := v.(GraphNodeCountDependent)
    67  	if !ok {
    68  		log.Printf("[DEBUG] DestroyEdgeInclude: Not GraphNodeCountDependent: %s", dag.VertexName(v))
    69  		return false
    70  	}
    71  
    72  	for _, d := range cv.CountDependentOn() {
    73  		for _, d2 := range n.DependableName() {
    74  			log.Printf("[DEBUG] DestroyEdgeInclude: d = %s : d2 = %s", d, d2)
    75  			if d == d2 {
    76  				return true
    77  			}
    78  		}
    79  	}
    80  
    81  	return false
    82  }
    83  
    84  // GraphNodeNoopPrunable
    85  func (n *GraphNodeConfigVariable) Noop(opts *NoopOpts) bool {
    86  	log.Printf("[DEBUG] Checking variable noop: %s", n.Name())
    87  	// If we have no diff, always keep this in the graph. We have to do
    88  	// this primarily for validation: we want to validate that variable
    89  	// interpolations are valid even if there are no resources that
    90  	// depend on them.
    91  	if opts.Diff == nil || opts.Diff.Empty() {
    92  		log.Printf("[DEBUG] No diff, not a noop")
    93  		return false
    94  	}
    95  
    96  	// We have to find our our module diff since we do funky things with
    97  	// the flat node's implementation of Path() below.
    98  	modDiff := opts.Diff.ModuleByPath(n.ModulePath)
    99  
   100  	// If we're destroying, we have no need of variables unless they are depended
   101  	// on by the count of a resource.
   102  	if modDiff != nil && modDiff.Destroy {
   103  		if n.hasDestroyEdgeInPath(opts, nil) {
   104  			log.Printf("[DEBUG] Variable has destroy edge from %s, not a noop",
   105  				dag.VertexName(opts.Vertex))
   106  			return false
   107  		}
   108  		log.Printf("[DEBUG] Variable has no included destroy edges: noop!")
   109  		return true
   110  	}
   111  
   112  	for _, v := range opts.Graph.UpEdges(opts.Vertex).List() {
   113  		// This is terrible, but I can't think of a better way to do this.
   114  		if dag.VertexName(v) == rootNodeName {
   115  			continue
   116  		}
   117  
   118  		log.Printf("[DEBUG] Found up edge to %s, var is not noop", dag.VertexName(v))
   119  		return false
   120  	}
   121  
   122  	log.Printf("[DEBUG] No up edges, treating variable as a noop")
   123  	return true
   124  }
   125  
   126  // hasDestroyEdgeInPath recursively walks for a destroy edge, ensuring that
   127  // a variable both has no immediate destroy edges or any in its full module
   128  // path, ensuring that links do not get severed in the middle.
   129  func (n *GraphNodeConfigVariable) hasDestroyEdgeInPath(opts *NoopOpts, vertex dag.Vertex) bool {
   130  	if vertex == nil {
   131  		vertex = opts.Vertex
   132  	}
   133  
   134  	log.Printf("[DEBUG] hasDestroyEdgeInPath: Looking for destroy edge: %s - %T", dag.VertexName(vertex), vertex)
   135  	for _, v := range opts.Graph.UpEdges(vertex).List() {
   136  		if len(opts.Graph.UpEdges(v).List()) > 1 {
   137  			if n.hasDestroyEdgeInPath(opts, v) == true {
   138  				return true
   139  			}
   140  		}
   141  
   142  		// Here we borrow the implementation of DestroyEdgeInclude, whose logic
   143  		// and semantics are exactly what we want here. We add a check for the
   144  		// the root node, since we have to always depend on its existance.
   145  		if cv, ok := vertex.(*GraphNodeConfigVariableFlat); ok {
   146  			if dag.VertexName(v) == rootNodeName || cv.DestroyEdgeInclude(v) {
   147  				return true
   148  			}
   149  		}
   150  	}
   151  	return false
   152  }
   153  
   154  // GraphNodeProxy impl.
   155  func (n *GraphNodeConfigVariable) Proxy() bool {
   156  	return true
   157  }
   158  
   159  // GraphNodeEvalable impl.
   160  func (n *GraphNodeConfigVariable) EvalTree() EvalNode {
   161  	// If we have no value, do nothing
   162  	if n.Value == nil {
   163  		return &EvalNoop{}
   164  	}
   165  
   166  	// Otherwise, interpolate the value of this variable and set it
   167  	// within the variables mapping.
   168  	var config *ResourceConfig
   169  	variables := make(map[string]interface{})
   170  	return &EvalSequence{
   171  		Nodes: []EvalNode{
   172  			&EvalInterpolate{
   173  				Config: n.Value,
   174  				Output: &config,
   175  			},
   176  
   177  			&EvalVariableBlock{
   178  				Config:         &config,
   179  				VariableValues: variables,
   180  			},
   181  
   182  			&EvalCoerceMapVariable{
   183  				Variables:  variables,
   184  				ModulePath: n.ModulePath,
   185  				ModuleTree: n.ModuleTree,
   186  			},
   187  
   188  			&EvalTypeCheckVariable{
   189  				Variables:  variables,
   190  				ModulePath: n.ModulePath,
   191  				ModuleTree: n.ModuleTree,
   192  			},
   193  
   194  			&EvalSetVariables{
   195  				Module:    &n.Module,
   196  				Variables: variables,
   197  			},
   198  		},
   199  	}
   200  }
   201  
   202  // GraphNodeFlattenable impl.
   203  func (n *GraphNodeConfigVariable) Flatten(p []string) (dag.Vertex, error) {
   204  	return &GraphNodeConfigVariableFlat{
   205  		GraphNodeConfigVariable: n,
   206  		PathValue:               p,
   207  	}, nil
   208  }
   209  
   210  type GraphNodeConfigVariableFlat struct {
   211  	*GraphNodeConfigVariable
   212  
   213  	PathValue []string
   214  }
   215  
   216  func (n *GraphNodeConfigVariableFlat) Name() string {
   217  	return fmt.Sprintf(
   218  		"%s.%s", modulePrefixStr(n.PathValue), n.GraphNodeConfigVariable.Name())
   219  }
   220  
   221  func (n *GraphNodeConfigVariableFlat) DependableName() []string {
   222  	return []string{n.Name()}
   223  }
   224  
   225  func (n *GraphNodeConfigVariableFlat) DependentOn() []string {
   226  	// We only wrap the dependencies and such if we have a path that is
   227  	// longer than 2 elements (root, child, more). This is because when
   228  	// flattened, variables can point outside the graph.
   229  	prefix := ""
   230  	if len(n.PathValue) > 2 {
   231  		prefix = modulePrefixStr(n.PathValue[:len(n.PathValue)-1])
   232  	}
   233  
   234  	return modulePrefixList(
   235  		n.GraphNodeConfigVariable.DependentOn(),
   236  		prefix)
   237  }
   238  
   239  func (n *GraphNodeConfigVariableFlat) Path() []string {
   240  	if len(n.PathValue) > 2 {
   241  		return n.PathValue[:len(n.PathValue)-1]
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  func (n *GraphNodeConfigVariableFlat) Noop(opts *NoopOpts) bool {
   248  	// First look for provider nodes that depend on this variable downstream
   249  	modDiff := opts.Diff.ModuleByPath(n.ModulePath)
   250  	if modDiff != nil && modDiff.Destroy {
   251  		ds, err := opts.Graph.Descendents(n)
   252  		if err != nil {
   253  			log.Printf("[ERROR] Error looking up descendents of %s: %s", n.Name(), err)
   254  		} else {
   255  			for _, d := range ds.List() {
   256  				if _, ok := d.(GraphNodeProvider); ok {
   257  					log.Printf("[DEBUG] This variable is depended on by a provider, can't be a noop.")
   258  					return false
   259  				}
   260  			}
   261  		}
   262  	}
   263  
   264  	// Then fall back to existing impl
   265  	return n.GraphNodeConfigVariable.Noop(opts)
   266  }