github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/graph.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/terraform/internal/backend" 8 "github.com/hashicorp/terraform/internal/command/arguments" 9 "github.com/hashicorp/terraform/internal/dag" 10 "github.com/hashicorp/terraform/internal/plans" 11 "github.com/hashicorp/terraform/internal/plans/planfile" 12 "github.com/hashicorp/terraform/internal/terraform" 13 "github.com/hashicorp/terraform/internal/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 }