Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix rendering and parsing #65

Merged
merged 8 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
},
RoseSecurity marked this conversation as resolved.
Show resolved Hide resolved
}

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)
}
RoseSecurity marked this conversation as resolved.
Show resolved Hide resolved

// 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