github.com/pulumi/terraform@v1.4.0/pkg/command/graph.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/pulumi/terraform/pkg/backend"
     8  	"github.com/pulumi/terraform/pkg/command/arguments"
     9  	"github.com/pulumi/terraform/pkg/dag"
    10  	"github.com/pulumi/terraform/pkg/plans"
    11  	"github.com/pulumi/terraform/pkg/plans/planfile"
    12  	"github.com/pulumi/terraform/pkg/terraform"
    13  	"github.com/pulumi/terraform/pkg/tfdiags"
    14  )
    15  
    16  // GraphCommand is a Command implementation that takes a Terraform
    17  // configuration and outputs the dependency tree in graphical form.
    18  type GraphCommand struct {
    19  	Meta
    20  }
    21  
    22  func (c *GraphCommand) Run(args []string) int {
    23  	var drawCycles bool
    24  	var graphTypeStr string
    25  	var moduleDepth int
    26  	var verbose bool
    27  	var planPath string
    28  
    29  	args = c.Meta.process(args)
    30  	cmdFlags := c.Meta.defaultFlagSet("graph")
    31  	cmdFlags.BoolVar(&drawCycles, "draw-cycles", false, "draw-cycles")
    32  	cmdFlags.StringVar(&graphTypeStr, "type", "", "type")
    33  	cmdFlags.IntVar(&moduleDepth, "module-depth", -1, "module-depth")
    34  	cmdFlags.BoolVar(&verbose, "verbose", false, "verbose")
    35  	cmdFlags.StringVar(&planPath, "plan", "", "plan")
    36  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    37  	if err := cmdFlags.Parse(args); err != nil {
    38  		c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
    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 for user-supplied plugin path
    49  	if c.pluginPath, err = c.loadPluginPath(); err != nil {
    50  		c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err))
    51  		return 1
    52  	}
    53  
    54  	// Try to load plan if path is specified
    55  	var planFile *planfile.Reader
    56  	if planPath != "" {
    57  		planFile, err = c.PlanFile(planPath)
    58  		if err != nil {
    59  			c.Ui.Error(err.Error())
    60  			return 1
    61  		}
    62  	}
    63  
    64  	var diags tfdiags.Diagnostics
    65  
    66  	backendConfig, backendDiags := c.loadBackendConfig(configPath)
    67  	diags = diags.Append(backendDiags)
    68  	if diags.HasErrors() {
    69  		c.showDiagnostics(diags)
    70  		return 1
    71  	}
    72  
    73  	// Load the backend
    74  	b, backendDiags := c.Backend(&BackendOpts{
    75  		Config: backendConfig,
    76  	})
    77  	diags = diags.Append(backendDiags)
    78  	if backendDiags.HasErrors() {
    79  		c.showDiagnostics(diags)
    80  		return 1
    81  	}
    82  
    83  	// We require a local backend
    84  	local, ok := b.(backend.Local)
    85  	if !ok {
    86  		c.showDiagnostics(diags) // in case of any warnings in here
    87  		c.Ui.Error(ErrUnsupportedLocalOp)
    88  		return 1
    89  	}
    90  
    91  	// This is a read-only command
    92  	c.ignoreRemoteVersionConflict(b)
    93  
    94  	// Build the operation
    95  	opReq := c.Operation(b, arguments.ViewHuman)
    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  	lr, _, ctxDiags := local.LocalRun(opReq)
   108  	diags = diags.Append(ctxDiags)
   109  	if ctxDiags.HasErrors() {
   110  		c.showDiagnostics(diags)
   111  		return 1
   112  	}
   113  
   114  	if graphTypeStr == "" {
   115  		switch {
   116  		case lr.Plan != nil:
   117  			graphTypeStr = "apply"
   118  		default:
   119  			graphTypeStr = "plan"
   120  		}
   121  	}
   122  
   123  	var g *terraform.Graph
   124  	var graphDiags tfdiags.Diagnostics
   125  	switch graphTypeStr {
   126  	case "plan":
   127  		g, graphDiags = lr.Core.PlanGraphForUI(lr.Config, lr.InputState, plans.NormalMode)
   128  	case "plan-refresh-only":
   129  		g, graphDiags = lr.Core.PlanGraphForUI(lr.Config, lr.InputState, plans.RefreshOnlyMode)
   130  	case "plan-destroy":
   131  		g, graphDiags = lr.Core.PlanGraphForUI(lr.Config, lr.InputState, plans.DestroyMode)
   132  	case "apply":
   133  		plan := lr.Plan
   134  
   135  		// Historically "terraform graph" would allow the nonsensical request to
   136  		// render an apply graph without a plan, so we continue to support that
   137  		// here, though perhaps one day this should be an error.
   138  		if lr.Plan == nil {
   139  			plan = &plans.Plan{
   140  				Changes:      plans.NewChanges(),
   141  				UIMode:       plans.NormalMode,
   142  				PriorState:   lr.InputState,
   143  				PrevRunState: lr.InputState,
   144  			}
   145  		}
   146  
   147  		g, graphDiags = lr.Core.ApplyGraphForUI(plan, lr.Config)
   148  	case "eval", "validate":
   149  		// Terraform v0.12 through v1.0 supported both of these, but the
   150  		// graph variants for "eval" and "validate" are purely implementation
   151  		// details and don't reveal anything (user-model-wise) that you can't
   152  		// see in the plan graph.
   153  		graphDiags = graphDiags.Append(tfdiags.Sourceless(
   154  			tfdiags.Error,
   155  			"Graph type no longer available",
   156  			fmt.Sprintf("The graph type %q is no longer available. Use -type=plan instead to get a similar result.", graphTypeStr),
   157  		))
   158  	default:
   159  		graphDiags = graphDiags.Append(tfdiags.Sourceless(
   160  			tfdiags.Error,
   161  			"Unsupported graph type",
   162  			`The -type=... argument must be either "plan", "plan-refresh-only", "plan-destroy", or "apply".`,
   163  		))
   164  	}
   165  	diags = diags.Append(graphDiags)
   166  	if graphDiags.HasErrors() {
   167  		c.showDiagnostics(diags)
   168  		return 1
   169  	}
   170  
   171  	graphStr, err := terraform.GraphDot(g, &dag.DotOpts{
   172  		DrawCycles: drawCycles,
   173  		MaxDepth:   moduleDepth,
   174  		Verbose:    verbose,
   175  	})
   176  	if err != nil {
   177  		c.Ui.Error(fmt.Sprintf("Error converting graph: %s", err))
   178  		return 1
   179  	}
   180  
   181  	if diags.HasErrors() {
   182  		// For this command we only show diagnostics if there are errors,
   183  		// because printing out naked warnings could upset a naive program
   184  		// consuming our dot output.
   185  		c.showDiagnostics(diags)
   186  		return 1
   187  	}
   188  
   189  	c.Ui.Output(graphStr)
   190  
   191  	return 0
   192  }
   193  
   194  func (c *GraphCommand) Help() string {
   195  	helpText := `
   196  Usage: terraform [global options] graph [options]
   197  
   198    Produces a representation of the dependency graph between different
   199    objects in the current configuration and state.
   200  
   201    The graph is presented in the DOT language. The typical program that can
   202    read this format is GraphViz, but many web services are also available
   203    to read this format.
   204  
   205  Options:
   206  
   207    -plan=tfplan     Render graph using the specified plan file instead of the
   208                     configuration in the current directory.
   209  
   210    -draw-cycles     Highlight any cycles in the graph with colored edges.
   211                     This helps when diagnosing cycle errors.
   212  
   213    -type=plan       Type of graph to output. Can be: plan, plan-refresh-only,
   214                     plan-destroy, or apply. By default Terraform chooses
   215  				   "plan", or "apply" if you also set the -plan=... option.
   216  
   217    -module-depth=n  (deprecated) In prior versions of Terraform, specified the
   218  				   depth of modules to show in the output.
   219  `
   220  	return strings.TrimSpace(helpText)
   221  }
   222  
   223  func (c *GraphCommand) Synopsis() string {
   224  	return "Generate a Graphviz graph of the steps in an operation"
   225  }