github.com/hugorut/terraform@v1.1.3/src/command/graph.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hugorut/terraform/src/backend" 8 "github.com/hugorut/terraform/src/dag" 9 "github.com/hugorut/terraform/src/plans" 10 "github.com/hugorut/terraform/src/plans/planfile" 11 "github.com/hugorut/terraform/src/terraform" 12 "github.com/hugorut/terraform/src/tfdiags" 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.ignoreRemoteVersionConflict(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 lr, _, ctxDiags := local.LocalRun(opReq) 107 diags = diags.Append(ctxDiags) 108 if ctxDiags.HasErrors() { 109 c.showDiagnostics(diags) 110 return 1 111 } 112 113 if graphTypeStr == "" { 114 switch { 115 case lr.Plan != nil: 116 graphTypeStr = "apply" 117 default: 118 graphTypeStr = "plan" 119 } 120 } 121 122 var g *terraform.Graph 123 var graphDiags tfdiags.Diagnostics 124 switch graphTypeStr { 125 case "plan": 126 g, graphDiags = lr.Core.PlanGraphForUI(lr.Config, lr.InputState, plans.NormalMode) 127 case "plan-refresh-only": 128 g, graphDiags = lr.Core.PlanGraphForUI(lr.Config, lr.InputState, plans.RefreshOnlyMode) 129 case "plan-destroy": 130 g, graphDiags = lr.Core.PlanGraphForUI(lr.Config, lr.InputState, plans.DestroyMode) 131 case "apply": 132 plan := lr.Plan 133 134 // Historically "terraform graph" would allow the nonsensical request to 135 // render an apply graph without a plan, so we continue to support that 136 // here, though perhaps one day this should be an error. 137 if lr.Plan == nil { 138 plan = &plans.Plan{ 139 Changes: plans.NewChanges(), 140 UIMode: plans.NormalMode, 141 PriorState: lr.InputState, 142 PrevRunState: lr.InputState, 143 } 144 } 145 146 g, graphDiags = lr.Core.ApplyGraphForUI(plan, lr.Config) 147 case "eval", "validate": 148 // Terraform v0.12 through v1.0 supported both of these, but the 149 // graph variants for "eval" and "validate" are purely implementation 150 // details and don't reveal anything (user-model-wise) that you can't 151 // see in the plan graph. 152 graphDiags = graphDiags.Append(tfdiags.Sourceless( 153 tfdiags.Error, 154 "Graph type no longer available", 155 fmt.Sprintf("The graph type %q is no longer available. Use -type=plan instead to get a similar result.", graphTypeStr), 156 )) 157 default: 158 graphDiags = graphDiags.Append(tfdiags.Sourceless( 159 tfdiags.Error, 160 "Unsupported graph type", 161 `The -type=... argument must be either "plan", "plan-refresh-only", "plan-destroy", or "apply".`, 162 )) 163 } 164 diags = diags.Append(graphDiags) 165 if graphDiags.HasErrors() { 166 c.showDiagnostics(diags) 167 return 1 168 } 169 170 graphStr, err := terraform.GraphDot(g, &dag.DotOpts{ 171 DrawCycles: drawCycles, 172 MaxDepth: moduleDepth, 173 Verbose: verbose, 174 }) 175 if err != nil { 176 c.Ui.Error(fmt.Sprintf("Error converting graph: %s", err)) 177 return 1 178 } 179 180 if diags.HasErrors() { 181 // For this command we only show diagnostics if there are errors, 182 // because printing out naked warnings could upset a naive program 183 // consuming our dot output. 184 c.showDiagnostics(diags) 185 return 1 186 } 187 188 c.Ui.Output(graphStr) 189 190 return 0 191 } 192 193 func (c *GraphCommand) Help() string { 194 helpText := ` 195 Usage: terraform [global options] graph [options] 196 197 Produces a representation of the dependency graph between different 198 objects in the current configuration and state. 199 200 The graph is presented in the DOT language. The typical program that can 201 read this format is GraphViz, but many web services are also available 202 to read this format. 203 204 Options: 205 206 -plan=tfplan Render graph using the specified plan file instead of the 207 configuration in the current directory. 208 209 -draw-cycles Highlight any cycles in the graph with colored edges. 210 This helps when diagnosing cycle errors. 211 212 -type=plan Type of graph to output. Can be: plan, plan-refresh-only, 213 plan-destroy, or apply. By default Terraform chooses 214 "plan", or "apply" if you also set the -plan=... option. 215 216 -module-depth=n (deprecated) In prior versions of Terraform, specified the 217 depth of modules to show in the output. 218 ` 219 return strings.TrimSpace(helpText) 220 } 221 222 func (c *GraphCommand) Synopsis() string { 223 return "Generate a Graphviz graph of the steps in an operation" 224 }