github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/command/graph.go (about)

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