github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/command/graph.go (about)

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