github.com/posener/terraform@v0.11.0-beta1.0.20171103235147-645df36af025/command/graph.go (about)

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