Skip to content

Commit

Permalink
Merge pull request #65 from RoseSecurity/fix-rendering-and-parsing
Browse files Browse the repository at this point in the history
Fix rendering and parsing
  • Loading branch information
RoseSecurity authored Nov 29, 2024
2 parents cd4c999 + 4b5c57c commit 7cdae66
Show file tree
Hide file tree
Showing 20 changed files with 412 additions and 284 deletions.
19 changes: 16 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
cache: false
- run: |
make build
build/terramaid -w test/aws
build/terramaid run -w test/aws
cat Terramaid.md
test_gcp:
runs-on: ubuntu-latest
Expand All @@ -31,7 +31,7 @@ jobs:
cache: false
- run: |
make build
build/terramaid -w test/gcp
build/terramaid run -w test/gcp
cat Terramaid.md
test_az:
runs-on: ubuntu-latest
Expand All @@ -44,5 +44,18 @@ jobs:
cache: false
- run: |
make build
build/terramaid -w test/az
build/terramaid run -w test/az
cat Terramaid.md
test_multi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
cache: false
- run: |
make build
build/terramaid run -w test/multi
cat Terramaid.md
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM golang:alpine AS builder
WORKDIR /usr/src/terramaid
# Terraform version
ARG TERRAFORM_VERSION=1.9.2
ARG TERRAFORM_VERSION=1.10.0

# Install necessary dependencies
RUN apk update && apk add --no-cache \
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ make build
The following configuration options are available:

```sh
> terramaid -h
> terramaid run -h
A utility for generating Mermaid diagrams from Terraform

Usage:
Expand Down Expand Up @@ -142,7 +142,7 @@ Use "terramaid [command] --help" for more information about a command.
Run the following command to utilize the Terramaid Docker image:

```sh
docker run -it -v $(pwd):/usr/src/terramaid rosesecurity/terramaid:latest
docker run -it -v $(pwd):/usr/src/terramaid rosesecurity/terramaid:latest run
```

## CI/CD Integrations
Expand Down
28 changes: 12 additions & 16 deletions cmd/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,17 @@ import (
)

// Generate documentation for Terramaid commands and output to docs directory
func docsCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "docs",
Short: "Generate documentation for the CLI",
SilenceUsage: true,
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
err := doc.GenMarkdownTree(cmd.Root(), "./docs")
if err != nil {
return err
}
var docsCmd = &cobra.Command{
Use: "docs",
Short: "Generate documentation for the CLI",
SilenceUsage: true,
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
err := doc.GenMarkdownTree(cmd.Root(), "./docs")
if err != nil {
return err
}

return nil
},
}

return cmd
return nil
},
}
143 changes: 26 additions & 117 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,131 +2,40 @@ package cmd

import (
"fmt"
"log"
"os"
"os/exec"

"github.com/RoseSecurity/terramaid/internal"
"github.com/RoseSecurity/terramaid/pkg/utils"
"github.com/caarlos0/env/v11"
tuiUtils "github.com/RoseSecurity/terramaid/internal/tui/utils"
u "github.com/RoseSecurity/terramaid/pkg/utils"
"github.com/spf13/cobra"
)

var Version string

type options struct {
WorkingDir string `env:"WORKING_DIR" envDefault:"."`
TFDir string `env:"TF_DIR" envDefault:"."`
TFPlan string `env:"TF_PLAN"`
TFBinary string `env:"TF_BINARY"`
Output string `env:"OUTPUT" envDefault:"Terramaid.md"`
Direction string `env:"DIRECTION" envDefault:"TD"`
SubgraphName string `env:"SUBGRAPH_NAME" envDefault:"Terraform"`
ChartType string `env:"CHART_TYPE" envDefault:"flowchart"`
var rootCmd = &cobra.Command{
Use: "terramaid",
Short: "A utility for generating Mermaid diagrams from Terraform configurations",
SilenceUsage: true,
SilenceErrors: true,
Run: func(cmd *cobra.Command, args []string) {
var err error
fmt.Println()
err = tuiUtils.PrintStyledText("TERRAMAID")
if err != nil {
u.LogErrorAndExit(err)
}
cmd.Help()
},
}

func TerramaidCmd() *cobra.Command {
options := &options{}

// Parse Envs
if err := env.ParseWithOptions(options, env.Options{Prefix: "TERRAMAID_"}); err != nil {
log.Fatalf("error parsing envs: %s", err.Error())
}

cmd := &cobra.Command{
Use: "terramaid",
Short: "A utility for generating Mermaid diagrams from Terraform configurations",
SilenceUsage: true,
SilenceErrors: true,
PreRunE: func(cmd *cobra.Command, args []string) error {
if options.TFDir != "" && !utils.DirExists(options.TFDir) {
return fmt.Errorf("Terraform directory \"%s\" does not exist", options.TFDir)
}

if options.TFDir != "" {
exists, err := utils.TerraformFilesExist(options.TFDir)
if err != nil {
return fmt.Errorf("error checking Terraform files in directory \"%s\": %v", options.TFDir, err)
}
if !exists {
return fmt.Errorf("Terraform files do not exist in directory \"%s\"", options.TFDir)
}
}

if options.WorkingDir != "" && !utils.DirExists(options.WorkingDir) {
return fmt.Errorf("Working directory \"%s\" does not exist", options.WorkingDir)
}

if options.TFPlan != "" && !utils.DirExists(options.TFPlan) {
return fmt.Errorf("Terraform planfile \"%s\" does not exist", options.TFPlan)
}

if options.TFBinary == "" {
tfBinary, err := exec.LookPath("terraform")
if err != nil {
return fmt.Errorf("error finding Terraform binary: %w", err)
}

options.TFBinary = tfBinary
}

return nil
},

RunE: func(cmd *cobra.Command, args []string) error {
sp := utils.NewSpinner("Generating Terramaid Diagrams")
sp.Start()
graph, err := internal.ParseTerraform(options.WorkingDir, options.TFBinary, options.TFPlan)
if err != nil {
return fmt.Errorf("error parsing Terraform: %w", err)
}
func init() {
// Add subcommands
rootCmd.AddCommand(runCmd)
rootCmd.AddCommand(docsCmd)
rootCmd.AddCommand(versionCmd)

// Convert the graph to a Mermaid diagram
var mermaidDiagram string

switch options.ChartType {
case "flowchart":
mermaidDiagram, err = internal.ConvertToMermaidFlowchart(graph, options.Direction, options.SubgraphName)
if err != nil {
return fmt.Errorf("error converting to Mermaid flowchart: %w", err)
}
default:
return fmt.Errorf("unsupported chart type: %s", options.ChartType)
}

// Write the Mermaid diagram to the specified output file
if err := os.WriteFile(options.Output, []byte(mermaidDiagram), 0o644); err != nil {
return fmt.Errorf("error writing to file: %w", err)
}

sp.Stop()
fmt.Printf("Mermaid diagram successfully written to %s\n", options.Output)

return nil
},
}

cmd.Flags().StringVarP(&options.Output, "output", "o", options.Output, "Output file for Mermaid diagram (env: TERRAMAID_OUTPUT)")
cmd.Flags().StringVarP(&options.Direction, "direction", "r", options.Direction, "Specify the direction of the diagram (env: TERRAMAID_DIRECTION)")
cmd.Flags().StringVarP(&options.SubgraphName, "subgraph-name", "s", options.SubgraphName, "Specify the subgraph name of the diagram (env: TERRAMAID_SUBGRAPH_NAME)")
cmd.Flags().StringVarP(&options.ChartType, "chart-type", "c", options.ChartType, "Specify the type of Mermaid chart to generate (env: TERRAMAID_CHART_TYPE)")
cmd.Flags().StringVarP(&options.TFDir, "tf-dir", "d", options.TFDir, "Path to Terraform directory (env: TERRAMAID_TF_DIR)")
cmd.Flags().StringVarP(&options.TFPlan, "tf-plan", "p", options.TFPlan, "Path to Terraform plan file (env: TERRAMAID_TF_PLAN)")
cmd.Flags().StringVarP(&options.TFBinary, "tf-binary", "b", options.TFBinary, "Path to Terraform binary (env: TERRAMAID_TF_BINARY)")
cmd.Flags().StringVarP(&options.WorkingDir, "working-dir", "w", options.WorkingDir, "Working directory for Terraform (env: TERRAMAID_WORKING_DIR)")

cmd.AddCommand(docsCmd(), versionCmd(Version))

// Disable auto generated string from documentation so that documentation is cleanly built and updated
cmd.DisableAutoGenTag = true

return cmd
// Disable auto-generated string from documentation so that documentation is cleanly built and updated
rootCmd.DisableAutoGenTag = true
}

func Execute() error {
if err := TerramaidCmd().Execute(); err != nil {
return err
func Execute() {
if err := rootCmd.Execute(); err != nil {
u.LogErrorAndExit(err)
}

return nil
}
108 changes: 108 additions & 0 deletions cmd/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package cmd

import (
"fmt"
"os"
"os/exec"

"github.com/RoseSecurity/terramaid/internal"
"github.com/RoseSecurity/terramaid/pkg/utils"
"github.com/caarlos0/env/v11"
"github.com/spf13/cobra"
)

type options struct {
WorkingDir string `env:"WORKING_DIR" envDefault:"."`
TFPlan string `env:"TF_PLAN"`
TFBinary string `env:"TF_BINARY"`
Output string `env:"OUTPUT" envDefault:"Terramaid.md"`
Direction string `env:"DIRECTION" envDefault:"TD"`
SubgraphName string `env:"SUBGRAPH_NAME" envDefault:"Terraform"`
ChartType string `env:"CHART_TYPE" envDefault:"flowchart"`
}

var opts options // Global variable for flags and env variables

var runCmd = &cobra.Command{
Use: "run",
Short: "Generate Mermaid diagrams from Terraform configurations",
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
// The opts variable is automatically populated with flags here
return generateDiagrams(&opts)
},
}

func generateDiagrams(opts *options) error {
if opts.WorkingDir != "" {
exists, err := utils.TerraformFilesExist(opts.WorkingDir)
if err != nil {
return fmt.Errorf("error checking Terraform files in directory \"%s\": %v", opts.WorkingDir, err)
}
if !exists {
return fmt.Errorf("Terraform files do not exist in directory \"%s\"", opts.WorkingDir)
}
}

// Validate directories and files
if opts.WorkingDir != "" && !utils.DirExists(opts.WorkingDir) {
return fmt.Errorf("terraform directory \"%s\" does not exist", opts.WorkingDir)
}

// Check for Terraform binary
if opts.TFBinary == "" {
tfBinary, err := exec.LookPath("terraform")
if err != nil {
return fmt.Errorf("error finding Terraform binary: %w", err)
}
opts.TFBinary = tfBinary
}

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

graph, err := internal.ParseTerraform(opts.WorkingDir, opts.TFBinary, opts.TFPlan)
if err != nil {
sp.Stop()
return fmt.Errorf("error parsing Terraform: %w", err)
}

// Generate the Mermaid diagram
mermaidDiagram, err := internal.GenerateMermaidFlowchart(graph, opts.Direction, opts.SubgraphName)
if err != nil {
sp.Stop()
return fmt.Errorf("error generating Mermaid diagram: %w", err)
}

// Write the Mermaid diagram to the specified output file
if err := os.WriteFile(opts.Output, []byte(mermaidDiagram), 0o644); err != nil {
sp.Stop()
return fmt.Errorf("error writing to file: %w", err)
}

sp.Stop()
fmt.Printf("Mermaid diagram successfully written to %s\n", opts.Output)

return nil
}

func init() {
// Parse environment variables first, then bind flags to the opts struct
if err := env.ParseWithOptions(&opts, env.Options{Prefix: "TERRAMAID_"}); err != nil {
fmt.Printf("Error parsing environment variables: %s\n", err.Error())
}

// Bind flags to the opts struct
runCmd.Flags().StringVarP(&opts.Output, "output", "o", opts.Output, "Output file for Mermaid diagram (env: TERRAMAID_OUTPUT)")
runCmd.Flags().StringVarP(&opts.Direction, "direction", "r", opts.Direction, "Specify the direction of the diagram (env: TERRAMAID_DIRECTION)")
runCmd.Flags().StringVarP(&opts.SubgraphName, "subgraph-name", "s", opts.SubgraphName, "Specify the subgraph name of the diagram (env: TERRAMAID_SUBGRAPH_NAME)")
runCmd.Flags().StringVarP(&opts.ChartType, "chart-type", "c", opts.ChartType, "Specify the type of Mermaid chart to generate (env: TERRAMAID_CHART_TYPE)")
runCmd.Flags().StringVarP(&opts.TFPlan, "tf-plan", "p", opts.TFPlan, "Path to Terraform plan file (env: TERRAMAID_TF_PLAN)")
runCmd.Flags().StringVarP(&opts.TFBinary, "tf-binary", "b", opts.TFBinary, "Path to Terraform binary (env: TERRAMAID_TF_BINARY)")
runCmd.Flags().StringVarP(&opts.WorkingDir, "working-dir", "w", opts.WorkingDir, "Working directory for Terraform (env: TERRAMAID_WORKING_DIR)")

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

0 comments on commit 7cdae66

Please sign in to comment.