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 }