github.com/ljosa/terraform@v0.7.0-rc2.0.20160617205345-fe540b408f59/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  	log.Printf("[DEBUG] hasDestroyEdgeInPath: Looking for destroy edge: %s - %T", dag.VertexName(vertex), vertex)
   134  	for _, v := range opts.Graph.UpEdges(vertex).List() {
   135  		if len(opts.Graph.UpEdges(v).List()) > 1 {
   136  			if n.hasDestroyEdgeInPath(opts, v) == true {
   137  				return true
   138  			}
   139  		}
   140  		// Here we borrow the implementation of DestroyEdgeInclude, whose logic
   141  		// and semantics are exactly what we want here.
   142  		if cv, ok := vertex.(*GraphNodeConfigVariableFlat); ok {
   143  			if cv.DestroyEdgeInclude(v) {
   144  				return true
   145  			}
   146  		}
   147  	}
   148  	return false
   149  }
   150  
   151  // GraphNodeProxy impl.
   152  func (n *GraphNodeConfigVariable) Proxy() bool {
   153  	return true
   154  }
   155  
   156  // GraphNodeEvalable impl.
   157  func (n *GraphNodeConfigVariable) EvalTree() EvalNode {
   158  	// If we have no value, do nothing
   159  	if n.Value == nil {
   160  		return &EvalNoop{}
   161  	}
   162  
   163  	// Otherwise, interpolate the value of this variable and set it
   164  	// within the variables mapping.
   165  	var config *ResourceConfig
   166  	variables := make(map[string]interface{})
   167  	return &EvalSequence{
   168  		Nodes: []EvalNode{
   169  			&EvalInterpolate{
   170  				Config: n.Value,
   171  				Output: &config,
   172  			},
   173  
   174  			&EvalVariableBlock{
   175  				Config:         &config,
   176  				VariableValues: variables,
   177  			},
   178  
   179  			&EvalTypeCheckVariable{
   180  				Variables:  variables,
   181  				ModulePath: n.ModulePath,
   182  				ModuleTree: n.ModuleTree,
   183  			},
   184  
   185  			&EvalSetVariables{
   186  				Module:    &n.Module,
   187  				Variables: variables,
   188  			},
   189  		},
   190  	}
   191  }
   192  
   193  // GraphNodeFlattenable impl.
   194  func (n *GraphNodeConfigVariable) Flatten(p []string) (dag.Vertex, error) {
   195  	return &GraphNodeConfigVariableFlat{
   196  		GraphNodeConfigVariable: n,
   197  		PathValue:               p,
   198  	}, nil
   199  }
   200  
   201  type GraphNodeConfigVariableFlat struct {
   202  	*GraphNodeConfigVariable
   203  
   204  	PathValue []string
   205  }
   206  
   207  func (n *GraphNodeConfigVariableFlat) Name() string {
   208  	return fmt.Sprintf(
   209  		"%s.%s", modulePrefixStr(n.PathValue), n.GraphNodeConfigVariable.Name())
   210  }
   211  
   212  func (n *GraphNodeConfigVariableFlat) DependableName() []string {
   213  	return []string{n.Name()}
   214  }
   215  
   216  func (n *GraphNodeConfigVariableFlat) DependentOn() []string {
   217  	// We only wrap the dependencies and such if we have a path that is
   218  	// longer than 2 elements (root, child, more). This is because when
   219  	// flattened, variables can point outside the graph.
   220  	prefix := ""
   221  	if len(n.PathValue) > 2 {
   222  		prefix = modulePrefixStr(n.PathValue[:len(n.PathValue)-1])
   223  	}
   224  
   225  	return modulePrefixList(
   226  		n.GraphNodeConfigVariable.DependentOn(),
   227  		prefix)
   228  }
   229  
   230  func (n *GraphNodeConfigVariableFlat) Path() []string {
   231  	if len(n.PathValue) > 2 {
   232  		return n.PathValue[:len(n.PathValue)-1]
   233  	}
   234  
   235  	return nil
   236  }
   237  
   238  func (n *GraphNodeConfigVariableFlat) Noop(opts *NoopOpts) bool {
   239  	// First look for provider nodes that depend on this variable downstream
   240  	modDiff := opts.Diff.ModuleByPath(n.ModulePath)
   241  	if modDiff != nil && modDiff.Destroy {
   242  		ds, err := opts.Graph.Descendents(n)
   243  		if err != nil {
   244  			log.Printf("[ERROR] Error looking up descendents of %s: %s", n.Name(), err)
   245  		} else {
   246  			for _, d := range ds.List() {
   247  				if _, ok := d.(GraphNodeProvider); ok {
   248  					log.Printf("[DEBUG] This variable is depended on by a provider, can't be a noop.")
   249  					return false
   250  				}
   251  			}
   252  		}
   253  	}
   254  
   255  	// Then fall back to existing impl
   256  	return n.GraphNodeConfigVariable.Noop(opts)
   257  }