Skip to content

Commit b5d8d9f

Browse files
authored
Merge pull request #97 from pergatore/verbose_mode
feat(functionality): add -v, --verbose flag that prints output
2 parents 05ff59b + a89a9a3 commit b5d8d9f

File tree

6 files changed

+116
-5
lines changed

6 files changed

+116
-5
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ Flags:
127127
-b, --tf-binary string Path to Terraform binary (env: TERRAMAID_TF_BINARY)
128128
-p, --tf-plan string Path to Terraform plan file (env: TERRAMAID_TF_PLAN)
129129
-w, --working-dir string Working directory for Terraform (env: TERRAMAID_WORKING_DIR) (default ".")
130+
-v, --verbose bool Verbose output to terminal (env: TERRAMAID_VERBOSE) (default false)
130131

131132
Use "terramaid [command] --help" for more information about a command.
132133
```

cmd/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ func init() {
3333
rootCmd.AddCommand(docsCmd)
3434
rootCmd.AddCommand(versionCmd)
3535

36+
// Add global flags
37+
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Enable verbose output")
38+
3639
// Disable auto-generated string from documentation so that documentation is cleanly built and updated
3740
rootCmd.DisableAutoGenTag = true
3841
}

cmd/run.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type options struct {
2222
Direction string `env:"DIRECTION" envDefault:"TD"`
2323
SubgraphName string `env:"SUBGRAPH_NAME" envDefault:"Terraform"`
2424
ChartType string `env:"CHART_TYPE" envDefault:"flowchart"`
25+
Verbose bool `env:"VERBOSE" envDefault:"false"`
2526
}
2627

2728
var opts options // Global variable for flags and env variables
@@ -38,6 +39,17 @@ var runCmd = &cobra.Command{
3839
}
3940

4041
func generateDiagrams(opts *options) error {
42+
if opts.Verbose {
43+
utils.LogVerbose("Starting Terramaid with the following options:")
44+
utils.LogVerbose("- Working Directory: %s", opts.WorkingDir)
45+
utils.LogVerbose("- Terraform Plan: %s", opts.TFPlan)
46+
utils.LogVerbose("- Terraform Binary: %s", opts.TFBinary)
47+
utils.LogVerbose("- Output File: %s", opts.Output)
48+
utils.LogVerbose("- Direction: %s", opts.Direction)
49+
utils.LogVerbose("- Subgraph Name: %s", opts.SubgraphName)
50+
utils.LogVerbose("- Chart Type: %s", opts.ChartType)
51+
}
52+
4153
if opts.WorkingDir != "" {
4254
exists, err := utils.TerraformFilesExist(opts.WorkingDir)
4355
if err != nil {
@@ -46,6 +58,9 @@ func generateDiagrams(opts *options) error {
4658
if !exists {
4759
return fmt.Errorf("Terraform files do not exist in directory \"%s\"", opts.WorkingDir)
4860
}
61+
if opts.Verbose {
62+
utils.LogVerbose("Confirmed Terraform files exist in %s", opts.WorkingDir)
63+
}
4964
}
5065

5166
// Validate directories and files
@@ -60,26 +75,38 @@ func generateDiagrams(opts *options) error {
6075
return fmt.Errorf("error finding Terraform binary: %w", err)
6176
}
6277
opts.TFBinary = tfBinary
78+
if opts.Verbose {
79+
utils.LogVerbose("Terraform binary found at: %s", opts.TFBinary)
80+
}
6381
}
6482

6583
// Spinner initialization and graph parsing
6684
sp := utils.NewSpinner("Generating Terramaid Diagrams")
6785
sp.Start()
6886

69-
graph, err := internal.ParseTerraform(opts.WorkingDir, opts.TFBinary, opts.TFPlan)
87+
if opts.Verbose {
88+
utils.LogVerbose("Initializing Terraform and building graph...")
89+
}
90+
graph, err := internal.ParseTerraform(opts.WorkingDir, opts.TFBinary, opts.TFPlan, opts.Verbose)
7091
if err != nil {
7192
sp.Stop()
7293
return fmt.Errorf("error parsing Terraform: %w", err)
7394
}
7495

7596
// Generate the Mermaid diagram
76-
mermaidDiagram, err := internal.GenerateMermaidFlowchart(graph, opts.Direction, opts.SubgraphName)
97+
if opts.Verbose {
98+
utils.LogVerbose("Generating Mermaid flowchart...")
99+
}
100+
mermaidDiagram, err := internal.GenerateMermaidFlowchart(graph, opts.Direction, opts.SubgraphName, opts.Verbose)
77101
if err != nil {
78102
sp.Stop()
79103
return fmt.Errorf("error generating Mermaid diagram: %w", err)
80104
}
81105

82106
// Write the Mermaid diagram to the specified output file
107+
if opts.Verbose {
108+
utils.LogVerbose("Writing Mermaid diagram to %s", opts.Output)
109+
}
83110
if err := os.WriteFile(opts.Output, []byte(mermaidDiagram), 0o644); err != nil {
84111
sp.Stop()
85112
return fmt.Errorf("error writing to file: %w", err)
@@ -105,6 +132,7 @@ func init() {
105132
runCmd.Flags().StringVarP(&opts.TFPlan, "tf-plan", "p", opts.TFPlan, "Path to Terraform plan file (env: TERRAMAID_TF_PLAN)")
106133
runCmd.Flags().StringVarP(&opts.TFBinary, "tf-binary", "b", opts.TFBinary, "Path to Terraform binary (env: TERRAMAID_TF_BINARY)")
107134
runCmd.Flags().StringVarP(&opts.WorkingDir, "working-dir", "w", opts.WorkingDir, "Working directory for Terraform (env: TERRAMAID_WORKING_DIR)")
135+
runCmd.Flags().BoolVarP(&opts.Verbose, "verbose", "v", opts.Verbose, "Enable verbose output (env: TERRAMAID_VERBOSE)")
108136

109137
// Disable auto-generated string from documentation so that documentation is cleanly built and updated
110138
runCmd.DisableAutoGenTag = true

internal/flowchart.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"regexp"
99
"strings"
1010

11+
"github.com/RoseSecurity/terramaid/pkg/utils"
1112
"github.com/awalterschulze/gographviz"
1213
)
1314

@@ -39,12 +40,19 @@ func CleanLabel(label string) string {
3940
}
4041

4142
// GenerateMermaidFlowchart generates a Mermaid diagram from a gographviz graph
42-
func GenerateMermaidFlowchart(graph *gographviz.Graph, direction string, subgraphName string) (string, error) {
43+
func GenerateMermaidFlowchart(graph *gographviz.Graph, direction string, subgraphName string, verbose bool) (string, error) {
4344
validDirections := map[string]bool{"TB": true, "TD": true, "BT": true, "RL": true, "LR": true}
4445
if !validDirections[direction] {
4546
return "", fmt.Errorf("invalid direction %s: valid options are TB, TD, BT, RL, LR", direction)
4647
}
4748

49+
if verbose {
50+
utils.LogVerbose("Generating Mermaid flowchart with direction: %s", direction)
51+
if subgraphName != "" {
52+
utils.LogVerbose("Using subgraph name: %s", subgraphName)
53+
}
54+
}
55+
4856
var sb strings.Builder
4957
sb.WriteString(fmt.Sprintf("```mermaid\nflowchart %s\n", direction))
5058

@@ -53,9 +61,12 @@ func GenerateMermaidFlowchart(graph *gographviz.Graph, direction string, subgrap
5361
}
5462

5563
addedNodes := make(map[string]string)
56-
5764
addedProviders := make(map[string]bool)
5865

66+
if verbose {
67+
utils.LogVerbose("Processing %d nodes", len(graph.Nodes.Nodes))
68+
}
69+
5970
for _, node := range graph.Nodes.Nodes {
6071
nodeID := CleanID(node.Name)
6172
nodeLabel := CleanLabel(node.Attrs["label"])
@@ -69,18 +80,28 @@ func GenerateMermaidFlowchart(graph *gographviz.Graph, direction string, subgrap
6980
continue
7081
}
7182
addedProviders[nodeID] = true
83+
if verbose {
84+
utils.LogVerbose("Added provider node: %s", nodeID)
85+
}
7286
}
7387

7488
if _, exists := addedNodes[nodeID]; !exists {
7589
sb.WriteString(fmt.Sprintf(" %s[\"%s\"]\n", nodeID, nodeLabel))
7690
addedNodes[nodeID] = nodeLabel
91+
if verbose && !strings.HasPrefix(nodeLabel, "provider:") {
92+
utils.LogVerbose("Added node: %s", nodeID)
93+
}
7794
}
7895
}
7996

8097
if subgraphName != "" {
8198
sb.WriteString(" end\n")
8299
}
83100

101+
if verbose {
102+
utils.LogVerbose("Processing %d edges", len(graph.Edges.Edges))
103+
}
104+
84105
for _, edge := range graph.Edges.Edges {
85106
fromID := CleanID(edge.Src)
86107
toID := CleanID(edge.Dst)
@@ -90,6 +111,9 @@ func GenerateMermaidFlowchart(graph *gographviz.Graph, direction string, subgrap
90111
if fromLabel != "" {
91112
sb.WriteString(fmt.Sprintf(" %s[\"%s\"]\n", fromID, fromLabel))
92113
addedNodes[fromID] = fromLabel
114+
if verbose {
115+
utils.LogVerbose("Added source node from edge: %s", fromID)
116+
}
93117
}
94118
}
95119

@@ -98,12 +122,25 @@ func GenerateMermaidFlowchart(graph *gographviz.Graph, direction string, subgrap
98122
if toLabel != "" {
99123
sb.WriteString(fmt.Sprintf(" %s[\"%s\"]\n", toID, toLabel))
100124
addedNodes[toID] = toLabel
125+
if verbose {
126+
utils.LogVerbose("Added destination node from edge: %s", toID)
127+
}
101128
}
102129
}
103130

104131
sb.WriteString(fmt.Sprintf(" %s --> %s\n", fromID, toID))
132+
if verbose {
133+
utils.LogVerbose("Added edge: %s --> %s", fromID, toID)
134+
}
105135
}
106136

107137
sb.WriteString("```\n")
138+
139+
if verbose {
140+
nodeCount := len(addedNodes)
141+
edgeCount := len(graph.Edges.Edges)
142+
utils.LogVerbose("Mermaid diagram generation complete with %d nodes and %d edges", nodeCount, edgeCount)
143+
}
144+
108145
return sb.String(), nil
109146
}

internal/parse.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"fmt"
99

10+
"github.com/RoseSecurity/terramaid/pkg/utils"
1011
"github.com/awalterschulze/gographviz"
1112
"github.com/hashicorp/terraform-exec/tfexec"
1213
)
@@ -20,23 +21,42 @@ const emptyGraph = `digraph G {
2021
`
2122

2223
// ParseTerraform parses the Terraform plan and returns the generated graph
23-
func ParseTerraform(workingDir, tfPath, planFile string) (*gographviz.Graph, error) {
24+
func ParseTerraform(workingDir, tfPath, planFile string, verbose bool) (*gographviz.Graph, error) {
2425
ctx := context.Background()
26+
27+
if verbose {
28+
utils.LogVerbose("Initializing Terraform with working directory: %s", workingDir)
29+
utils.LogVerbose("Using Terraform binary: %s", tfPath)
30+
}
31+
2532
tf, err := tfexec.NewTerraform(workingDir, tfPath)
2633
if err != nil {
2734
return nil, err
2835
}
2936

37+
if verbose {
38+
utils.LogVerbose("Running terraform init with upgrade=true")
39+
}
40+
3041
if err := tf.Init(ctx, tfexec.Upgrade(true)); err != nil {
3142
return nil, err
3243
}
3344

3445
opts := &tfexec.GraphPlanOption{}
3546

3647
if planFile != "" {
48+
if verbose {
49+
utils.LogVerbose("Using plan file for graph generation: %s", planFile)
50+
}
3751
opts = tfexec.GraphPlan(planFile)
52+
} else if verbose {
53+
utils.LogVerbose("No plan file specified, using current state")
3854
}
3955

56+
if verbose {
57+
utils.LogVerbose("Running terraform graph command")
58+
}
59+
4060
output, err := tf.Graph(ctx, opts)
4161
if err != nil {
4262
return nil, err
@@ -46,6 +66,11 @@ func ParseTerraform(workingDir, tfPath, planFile string) (*gographviz.Graph, err
4666
return nil, fmt.Errorf("no output from terraform graph")
4767
}
4868

69+
if verbose {
70+
utils.LogVerbose("Successfully retrieved graph output from Terraform")
71+
utils.LogVerbose("Parsing DOT output")
72+
}
73+
4974
// Parse the DOT output
5075
graphAst, err := gographviz.ParseString(string(output))
5176
if err != nil {
@@ -54,9 +79,18 @@ func ParseTerraform(workingDir, tfPath, planFile string) (*gographviz.Graph, err
5479

5580
graph := gographviz.NewGraph()
5681

82+
if verbose {
83+
utils.LogVerbose("Analyzing graph structure")
84+
}
85+
5786
if err := gographviz.Analyse(graphAst, graph); err != nil {
5887
return nil, err
5988
}
6089

90+
if verbose {
91+
utils.LogVerbose("Graph analysis complete")
92+
utils.LogVerbose("Found %d nodes and %d edges", len(graph.Nodes.Nodes), len(graph.Edges.Edges))
93+
}
94+
6195
return graph, nil
6296
}

pkg/utils/logging.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package utils
55

66
import (
77
"errors"
8+
"fmt"
89
"os"
910
"os/exec"
1011

@@ -45,3 +46,10 @@ func LogError(err error) {
4546
}
4647
}
4748
}
49+
50+
// LogVerbose logs messages in verbose mode
51+
func LogVerbose(format string, a ...interface{}) {
52+
c := color.New(color.FgBlue)
53+
message := fmt.Sprintf(format, a...)
54+
c.Fprintf(color.Output, "[VERBOSE] %s\n", message)
55+
}

0 commit comments

Comments
 (0)