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 }