github.com/rhenning/terraform@v0.8.0-beta2/terraform/graph_builder.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/terraform/config/module"
     9  )
    10  
    11  // GraphBuilder is an interface that can be implemented and used with
    12  // Terraform to build the graph that Terraform walks.
    13  type GraphBuilder interface {
    14  	// Build builds the graph for the given module path. It is up to
    15  	// the interface implementation whether this build should expand
    16  	// the graph or not.
    17  	Build(path []string) (*Graph, error)
    18  }
    19  
    20  // BasicGraphBuilder is a GraphBuilder that builds a graph out of a
    21  // series of transforms and (optionally) validates the graph is a valid
    22  // structure.
    23  type BasicGraphBuilder struct {
    24  	Steps    []GraphTransformer
    25  	Validate bool
    26  	// Optional name to add to the graph debug log
    27  	Name string
    28  }
    29  
    30  func (b *BasicGraphBuilder) Build(path []string) (*Graph, error) {
    31  	g := &Graph{Path: path}
    32  
    33  	debugName := "build-graph.json"
    34  	if b.Name != "" {
    35  		debugName = b.Name + "-" + debugName
    36  	}
    37  	debugBuf := dbug.NewFileWriter(debugName)
    38  	g.SetDebugWriter(debugBuf)
    39  	defer debugBuf.Close()
    40  
    41  	for _, step := range b.Steps {
    42  		if step == nil {
    43  			continue
    44  		}
    45  
    46  		stepName := fmt.Sprintf("%T", step)
    47  		dot := strings.LastIndex(stepName, ".")
    48  		if dot >= 0 {
    49  			stepName = stepName[dot+1:]
    50  		}
    51  
    52  		debugOp := g.DebugOperation(stepName, "")
    53  		err := step.Transform(g)
    54  
    55  		errMsg := ""
    56  		if err != nil {
    57  			errMsg = err.Error()
    58  		}
    59  		debugOp.End(errMsg)
    60  
    61  		// always log the graph state to see what transformations may have happened
    62  		debugName := "build-" + stepName
    63  		if b.Name != "" {
    64  			debugName = b.Name + "-" + debugName
    65  		}
    66  
    67  		log.Printf(
    68  			"[TRACE] Graph after step %T:\n\n%s",
    69  			step, g.StringWithNodeTypes())
    70  
    71  		// TODO: replace entirely with the json logs
    72  		dbug.WriteGraph(debugName, g)
    73  
    74  		if err != nil {
    75  			return g, err
    76  		}
    77  	}
    78  
    79  	// Validate the graph structure
    80  	if b.Validate {
    81  		if err := g.Validate(); err != nil {
    82  			log.Printf("[ERROR] Graph validation failed. Graph:\n\n%s", g.String())
    83  			return nil, err
    84  		}
    85  	}
    86  
    87  	return g, nil
    88  }
    89  
    90  // BuiltinGraphBuilder is responsible for building the complete graph that
    91  // Terraform uses for execution. It is an opinionated builder that defines
    92  // the step order required to build a complete graph as is used and expected
    93  // by Terraform.
    94  //
    95  // If you require a custom graph, you'll have to build it up manually
    96  // on your own by building a new GraphBuilder implementation.
    97  type BuiltinGraphBuilder struct {
    98  	// Root is the root module of the graph to build.
    99  	Root *module.Tree
   100  
   101  	// Diff is the diff. The proper module diffs will be looked up.
   102  	Diff *Diff
   103  
   104  	// State is the global state. The proper module states will be looked
   105  	// up by graph path.
   106  	State *State
   107  
   108  	// Providers is the list of providers supported.
   109  	Providers []string
   110  
   111  	// Provisioners is the list of provisioners supported.
   112  	Provisioners []string
   113  
   114  	// Targets is the user-specified list of resources to target.
   115  	Targets []string
   116  
   117  	// Destroy is set to true when we're in a `terraform destroy` or a
   118  	// `terraform plan -destroy`
   119  	Destroy bool
   120  
   121  	// Determines whether the GraphBuilder should perform graph validation before
   122  	// returning the Graph. Generally you want this to be done, except when you'd
   123  	// like to inspect a problematic graph.
   124  	Validate bool
   125  
   126  	// Verbose is set to true when the graph should be built "worst case",
   127  	// skipping any prune steps. This is used for early cycle detection during
   128  	// Validate and for manual inspection via `terraform graph -verbose`.
   129  	Verbose bool
   130  }
   131  
   132  // Build builds the graph according to the steps returned by Steps.
   133  func (b *BuiltinGraphBuilder) Build(path []string) (*Graph, error) {
   134  	basic := &BasicGraphBuilder{
   135  		Steps:    b.Steps(path),
   136  		Validate: b.Validate,
   137  		Name:     "builtin",
   138  	}
   139  
   140  	return basic.Build(path)
   141  }
   142  
   143  // Steps returns the ordered list of GraphTransformers that must be executed
   144  // to build a complete graph.
   145  func (b *BuiltinGraphBuilder) Steps(path []string) []GraphTransformer {
   146  	steps := []GraphTransformer{
   147  		// Create all our resources from the configuration and state
   148  		&ConfigTransformerOld{Module: b.Root},
   149  		&OrphanTransformer{
   150  			State:  b.State,
   151  			Module: b.Root,
   152  		},
   153  
   154  		// Output-related transformations
   155  		&AddOutputOrphanTransformer{State: b.State},
   156  
   157  		// Provider-related transformations
   158  		&MissingProviderTransformer{Providers: b.Providers},
   159  		&ProviderTransformer{},
   160  		&DisableProviderTransformerOld{},
   161  
   162  		// Provisioner-related transformations
   163  		&MissingProvisionerTransformer{Provisioners: b.Provisioners},
   164  		&ProvisionerTransformer{},
   165  
   166  		// Run our vertex-level transforms
   167  		&VertexTransformer{
   168  			Transforms: []GraphVertexTransformer{
   169  				// Expand any statically expanded nodes, such as module graphs
   170  				&ExpandTransform{
   171  					Builder: b,
   172  				},
   173  			},
   174  		},
   175  
   176  		// Flatten stuff
   177  		&FlattenTransformer{},
   178  
   179  		// Make sure all the connections that are proxies are connected through
   180  		&ProxyTransformer{},
   181  	}
   182  
   183  	// If we're on the root path, then we do a bunch of other stuff.
   184  	// We don't do the following for modules.
   185  	if len(path) <= 1 {
   186  		steps = append(steps,
   187  			// Optionally reduces the graph to a user-specified list of targets and
   188  			// their dependencies.
   189  			&TargetsTransformer{Targets: b.Targets, Destroy: b.Destroy},
   190  
   191  			// Create orphan output nodes
   192  			&OrphanOutputTransformer{Module: b.Root, State: b.State},
   193  
   194  			// Prune the providers. This must happen only once because flattened
   195  			// modules might depend on empty providers.
   196  			&PruneProviderTransformer{},
   197  
   198  			// Create the destruction nodes
   199  			&DestroyTransformer{FullDestroy: b.Destroy},
   200  			b.conditional(&conditionalOpts{
   201  				If:   func() bool { return !b.Destroy },
   202  				Then: &CreateBeforeDestroyTransformer{},
   203  			}),
   204  			b.conditional(&conditionalOpts{
   205  				If:   func() bool { return !b.Verbose },
   206  				Then: &PruneDestroyTransformer{Diff: b.Diff, State: b.State},
   207  			}),
   208  
   209  			// Remove the noop nodes
   210  			&PruneNoopTransformer{Diff: b.Diff, State: b.State},
   211  
   212  			// Insert nodes to close opened plugin connections
   213  			&CloseProviderTransformer{},
   214  			&CloseProvisionerTransformer{},
   215  
   216  			// Perform the transitive reduction to make our graph a bit
   217  			// more sane if possible (it usually is possible).
   218  			&TransitiveReductionTransformer{},
   219  		)
   220  	}
   221  
   222  	// Make sure we have a single root
   223  	steps = append(steps, &RootTransformer{})
   224  
   225  	// Remove nils
   226  	for i, s := range steps {
   227  		if s == nil {
   228  			steps = append(steps[:i], steps[i+1:]...)
   229  		}
   230  	}
   231  
   232  	return steps
   233  }
   234  
   235  type conditionalOpts struct {
   236  	If   func() bool
   237  	Then GraphTransformer
   238  }
   239  
   240  func (b *BuiltinGraphBuilder) conditional(o *conditionalOpts) GraphTransformer {
   241  	if o.If != nil && o.Then != nil && o.If() {
   242  		return o.Then
   243  	}
   244  	return nil
   245  }