github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/command/graph.go (about)

     1  package command
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/terraform/tfdiags"
     9  
    10  	"github.com/hashicorp/terraform/backend"
    11  	"github.com/hashicorp/terraform/config"
    12  	"github.com/hashicorp/terraform/config/module"
    13  	"github.com/hashicorp/terraform/dag"
    14  	"github.com/hashicorp/terraform/terraform"
    15  )
    16  
    17  // GraphCommand is a Command implementation that takes a Terraform
    18  // configuration and outputs the dependency tree in graphical form.
    19  type GraphCommand struct {
    20  	Meta
    21  }
    22  
    23  func (c *GraphCommand) Run(args []string) int {
    24  	var moduleDepth int
    25  	var verbose bool
    26  	var drawCycles bool
    27  	var graphTypeStr string
    28  
    29  	args, err := c.Meta.process(args, false)
    30  	if err != nil {
    31  		return 1
    32  	}
    33  
    34  	cmdFlags := flag.NewFlagSet("graph", flag.ContinueOnError)
    35  	c.addModuleDepthFlag(cmdFlags, &moduleDepth)
    36  	cmdFlags.BoolVar(&verbose, "verbose", false, "verbose")
    37  	cmdFlags.BoolVar(&drawCycles, "draw-cycles", false, "draw-cycles")
    38  	cmdFlags.StringVar(&graphTypeStr, "type", "", "type")
    39  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    40  	if err := cmdFlags.Parse(args); err != nil {
    41  		return 1
    42  	}
    43  
    44  	configPath, err := ModulePath(cmdFlags.Args())
    45  	if err != nil {
    46  		c.Ui.Error(err.Error())
    47  		return 1
    48  	}
    49  
    50  	// Check if the path is a plan
    51  	plan, err := c.Plan(configPath)
    52  	if err != nil {
    53  		c.Ui.Error(err.Error())
    54  		return 1
    55  	}
    56  	if plan != nil {
    57  		// Reset for backend loading
    58  		configPath = ""
    59  	}
    60  
    61  	var diags tfdiags.Diagnostics
    62  
    63  	// Load the module
    64  	var mod *module.Tree
    65  	if plan == nil {
    66  		var modDiags tfdiags.Diagnostics
    67  		mod, modDiags = c.Module(configPath)
    68  		diags = diags.Append(modDiags)
    69  		if modDiags.HasErrors() {
    70  			c.showDiagnostics(diags)
    71  			return 1
    72  		}
    73  	}
    74  
    75  	var conf *config.Config
    76  	if mod != nil {
    77  		conf = mod.Config()
    78  	}
    79  
    80  	// Load the backend
    81  	b, err := c.Backend(&BackendOpts{
    82  		Config: conf,
    83  		Plan:   plan,
    84  	})
    85  	if err != nil {
    86  		c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
    87  		return 1
    88  	}
    89  
    90  	// We require a local backend
    91  	local, ok := b.(backend.Local)
    92  	if !ok {
    93  		c.Ui.Error(ErrUnsupportedLocalOp)
    94  		return 1
    95  	}
    96  
    97  	// Building a graph may require config module to be present, even if it's
    98  	// empty.
    99  	if mod == nil && plan == nil {
   100  		mod = module.NewEmptyTree()
   101  	}
   102  
   103  	// Build the operation
   104  	opReq := c.Operation()
   105  	opReq.Module = mod
   106  	opReq.Plan = plan
   107  
   108  	// Get the context
   109  	ctx, _, err := local.Context(opReq)
   110  	if err != nil {
   111  		c.Ui.Error(err.Error())
   112  		return 1
   113  	}
   114  
   115  	// Determine the graph type
   116  	graphType := terraform.GraphTypePlan
   117  	if plan != nil {
   118  		graphType = terraform.GraphTypeApply
   119  	}
   120  
   121  	if graphTypeStr != "" {
   122  		v, ok := terraform.GraphTypeMap[graphTypeStr]
   123  		if !ok {
   124  			c.Ui.Error(fmt.Sprintf("Invalid graph type requested: %s", graphTypeStr))
   125  			return 1
   126  		}
   127  
   128  		graphType = v
   129  	}
   130  
   131  	// Skip validation during graph generation - we want to see the graph even if
   132  	// it is invalid for some reason.
   133  	g, err := ctx.Graph(graphType, &terraform.ContextGraphOpts{
   134  		Verbose:  verbose,
   135  		Validate: false,
   136  	})
   137  	if err != nil {
   138  		c.Ui.Error(fmt.Sprintf("Error creating graph: %s", err))
   139  		return 1
   140  	}
   141  
   142  	graphStr, err := terraform.GraphDot(g, &dag.DotOpts{
   143  		DrawCycles: drawCycles,
   144  		MaxDepth:   moduleDepth,
   145  		Verbose:    verbose,
   146  	})
   147  	if err != nil {
   148  		c.Ui.Error(fmt.Sprintf("Error converting graph: %s", err))
   149  		return 1
   150  	}
   151  
   152  	if diags.HasErrors() {
   153  		// For this command we only show diagnostics if there are errors,
   154  		// because printing out naked warnings could upset a naive program
   155  		// consuming our dot output.
   156  		c.showDiagnostics(diags)
   157  		return 1
   158  	}
   159  
   160  	c.Ui.Output(graphStr)
   161  
   162  	return 0
   163  }
   164  
   165  func (c *GraphCommand) Help() string {
   166  	helpText := `
   167  Usage: terraform graph [options] [DIR]
   168  
   169    Outputs the visual execution graph of Terraform resources according to
   170    configuration files in DIR (or the current directory if omitted).
   171  
   172    The graph is outputted in DOT format. The typical program that can
   173    read this format is GraphViz, but many web services are also available
   174    to read this format.
   175  
   176    The -type flag can be used to control the type of graph shown. Terraform
   177    creates different graphs for different operations. See the options below
   178    for the list of types supported. The default type is "plan" if a
   179    configuration is given, and "apply" if a plan file is passed as an
   180    argument.
   181  
   182  Options:
   183  
   184    -draw-cycles   Highlight any cycles in the graph with colored edges.
   185                   This helps when diagnosing cycle errors.
   186  
   187    -no-color      If specified, output won't contain any color.
   188  
   189    -type=plan     Type of graph to output. Can be: plan, plan-destroy, apply,
   190                   validate, input, refresh.
   191  
   192  
   193  `
   194  	return strings.TrimSpace(helpText)
   195  }
   196  
   197  func (c *GraphCommand) Synopsis() string {
   198  	return "Create a visual graph of Terraform resources"
   199  }