4
4
package cmd
5
5
6
6
import (
7
+ "context"
7
8
"fmt"
8
9
"os"
9
10
"os/exec"
11
+ "time"
10
12
11
13
"github.com/RoseSecurity/terramaid/internal"
12
14
"github.com/RoseSecurity/terramaid/pkg/utils"
@@ -15,14 +17,15 @@ import (
15
17
)
16
18
17
19
type options struct {
18
- WorkingDir string `env:"WORKING_DIR" envDefault:"."`
19
- TFPlan string `env:"TF_PLAN"`
20
- TFBinary string `env:"TF_BINARY"`
21
- Output string `env:"OUTPUT" envDefault:"Terramaid.md"`
22
- Direction string `env:"DIRECTION" envDefault:"TD"`
23
- SubgraphName string `env:"SUBGRAPH_NAME" envDefault:"Terraform"`
24
- ChartType string `env:"CHART_TYPE" envDefault:"flowchart"`
25
- Verbose bool `env:"VERBOSE" envDefault:"false"`
20
+ WorkingDir string `env:"WORKING_DIR" envDefault:"."`
21
+ TFPlan string `env:"TF_PLAN"`
22
+ TFBinary string `env:"TF_BINARY"`
23
+ Output string `env:"OUTPUT" envDefault:"Terramaid.md"`
24
+ Direction string `env:"DIRECTION" envDefault:"TD"`
25
+ SubgraphName string `env:"SUBGRAPH_NAME" envDefault:"Terraform"`
26
+ ChartType string `env:"CHART_TYPE" envDefault:"flowchart"`
27
+ Verbose bool `env:"VERBOSE" envDefault:"false"`
28
+ Timeout time.Duration `env:"TIMEOUT" envDefault:"0"`
26
29
}
27
30
28
31
var opts options // Global variable for flags and env variables
@@ -33,12 +36,19 @@ var runCmd = &cobra.Command{
33
36
SilenceUsage : true ,
34
37
SilenceErrors : true ,
35
38
RunE : func (cmd * cobra.Command , args []string ) error {
36
- // The opts variable is automatically populated with flags here
37
- return generateDiagrams (& opts )
39
+ ctx := cmd .Context ()
40
+
41
+ if opts .Timeout > 0 {
42
+ var cancel context.CancelFunc
43
+ ctx , cancel = context .WithTimeout (ctx , opts .Timeout )
44
+ defer cancel ()
45
+ }
46
+
47
+ return generateDiagrams (ctx , & opts )
38
48
},
39
49
}
40
50
41
- func generateDiagrams (opts * options ) error {
51
+ func generateDiagrams (ctx context. Context , opts * options ) error {
42
52
if opts .Verbose {
43
53
utils .LogVerbose ("Starting Terramaid with the following options:" )
44
54
utils .LogVerbose ("- Working Directory: %s" , opts .WorkingDir )
@@ -48,6 +58,16 @@ func generateDiagrams(opts *options) error {
48
58
utils .LogVerbose ("- Direction: %s" , opts .Direction )
49
59
utils .LogVerbose ("- Subgraph Name: %s" , opts .SubgraphName )
50
60
utils .LogVerbose ("- Chart Type: %s" , opts .ChartType )
61
+ if opts .Timeout > 0 {
62
+ utils .LogVerbose ("- Timeout: %s" , opts .Timeout )
63
+ }
64
+ }
65
+
66
+ // Early cancellation check
67
+ select {
68
+ case <- ctx .Done ():
69
+ return ctx .Err ()
70
+ default :
51
71
}
52
72
53
73
if opts .WorkingDir != "" {
@@ -56,7 +76,7 @@ func generateDiagrams(opts *options) error {
56
76
return fmt .Errorf ("error checking Terraform files in directory \" %s\" : %v" , opts .WorkingDir , err )
57
77
}
58
78
if ! exists {
59
- return fmt .Errorf ("Terraform files do not exist in directory \" %s\" " , opts .WorkingDir )
79
+ return fmt .Errorf ("terraform files do not exist in directory \" %s\" " , opts .WorkingDir )
60
80
}
61
81
if opts .Verbose {
62
82
utils .LogVerbose ("Confirmed Terraform files exist in %s" , opts .WorkingDir )
@@ -83,36 +103,43 @@ func generateDiagrams(opts *options) error {
83
103
// Spinner initialization and graph parsing
84
104
sp := utils .NewSpinner ("Generating Terramaid Diagrams" )
85
105
sp .Start ()
106
+ defer sp .Stop ()
86
107
87
108
if opts .Verbose {
88
109
utils .LogVerbose ("Initializing Terraform and building graph..." )
89
110
}
90
- graph , err := internal .ParseTerraform (opts .WorkingDir , opts .TFBinary , opts .TFPlan , opts .Verbose )
111
+
112
+ graph , err := internal .ParseTerraform (ctx , opts .WorkingDir , opts .TFBinary , opts .TFPlan , opts .Verbose )
91
113
if err != nil {
92
- sp .Stop ()
93
114
return fmt .Errorf ("error parsing Terraform: %w" , err )
94
115
}
95
116
117
+ // Respect context cancellation after heavy parsing
118
+ if err := ctx .Err (); err != nil {
119
+ return err
120
+ }
121
+
96
122
// Generate the Mermaid diagram
97
123
if opts .Verbose {
98
124
utils .LogVerbose ("Generating Mermaid flowchart..." )
99
125
}
100
- mermaidDiagram , err := internal .GenerateMermaidFlowchart (graph , opts .Direction , opts .SubgraphName , opts .Verbose )
126
+ mermaidDiagram , err := internal .GenerateMermaidFlowchart (ctx , graph , opts .Direction , opts .SubgraphName , opts .Verbose )
101
127
if err != nil {
102
- sp .Stop ()
103
128
return fmt .Errorf ("error generating Mermaid diagram: %w" , err )
104
129
}
105
130
131
+ if err := ctx .Err (); err != nil {
132
+ return err
133
+ }
134
+
106
135
// Write the Mermaid diagram to the specified output file
107
136
if opts .Verbose {
108
137
utils .LogVerbose ("Writing Mermaid diagram to %s" , opts .Output )
109
138
}
110
139
if err := os .WriteFile (opts .Output , []byte (mermaidDiagram ), 0o644 ); err != nil {
111
- sp .Stop ()
112
140
return fmt .Errorf ("error writing to file: %w" , err )
113
141
}
114
142
115
- sp .Stop ()
116
143
fmt .Printf ("Mermaid diagram successfully written to %s\n " , opts .Output )
117
144
118
145
return nil
@@ -133,6 +160,7 @@ func init() {
133
160
runCmd .Flags ().StringVarP (& opts .TFBinary , "tf-binary" , "b" , opts .TFBinary , "Path to Terraform binary (env: TERRAMAID_TF_BINARY)" )
134
161
runCmd .Flags ().StringVarP (& opts .WorkingDir , "working-dir" , "w" , opts .WorkingDir , "Working directory for Terraform (env: TERRAMAID_WORKING_DIR)" )
135
162
runCmd .Flags ().BoolVarP (& opts .Verbose , "verbose" , "v" , opts .Verbose , "Enable verbose output (env: TERRAMAID_VERBOSE)" )
163
+ runCmd .Flags ().DurationVarP (& opts .Timeout , "timeout" , "t" , opts .Timeout , "Timeout for the entire run (e.g. 5m) (env: TERRAMAID_TIMEOUT)" )
136
164
137
165
// Disable auto-generated string from documentation so that documentation is cleanly built and updated
138
166
runCmd .DisableAutoGenTag = true
0 commit comments