Skip to content

Commit

Permalink
Merge pull request #31 from FalcoSuessgott/envs
Browse files Browse the repository at this point in the history
Support configuration via env vars
  • Loading branch information
RoseSecurity authored Aug 3, 2024
2 parents fe0d724 + 6dda755 commit 5b6dfa8
Show file tree
Hide file tree
Showing 22 changed files with 432 additions and 175 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Test

on:
pull_request:
push:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- uses: actions/setup-go@v5
with:
go-version: '1.22.2'
cache: false
- run: |
make build
build/terramaid -w test/
cat Terramaid.md
3 changes: 1 addition & 2 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ before:
builds:
- env:
- CGO_ENABLED=0
ldflags:
- -s -w
ldflags: -s -w -X main.version={{ .Version }}
goos:
- linux
- windows
Expand Down
3 changes: 2 additions & 1 deletion Brewfile
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
brew "go"
brew "go"
brew "gofumpt"
20 changes: 14 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,30 @@ BINARY_NAME=terramaid
VERSION=v1
GO=go

default: help

help: ## List Makefile targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

all: build

build:
fmt: ## Format Go files
gofumpt -w .

build: ## Build Terramaid
$(GO) build -ldflags="-s -w" -o build/$(BINARY_NAME) main.go

install:
install: ## Install dependencies
$(GO) install ./...@latest

clean:
clean: ## Clean up build artifacts
$(GO) clean
rm ./build/$(BINARY_NAME)

run: build
run: build ## Run Terramaid
./build/$(BINARY_NAME)

docs: build
docs: build ## Generate documentation
./build/$(BINARY_NAME) docs

.PHONY: all build install clean run
.PHONY: all build install clean run fmt help
59 changes: 48 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,19 @@ Terramaid transforms your Terraform resources and plans into visually appealing
### Output

```mermaid
flowchart TD;
subgraph Terraform
aws_db_instance.dev_example_db_instance["aws_db_instance.dev_example_db_instance"]
aws_instance.dev_example_instance["aws_instance.dev_example_instance"]
aws_s3_bucket.dev_logs_bucket["aws_s3_bucket.dev_logs_bucket"]
aws_s3_bucket.dev_test_bucket["aws_s3_bucket.dev_test_bucket"]
aws_s3_bucket_policy.dev_logs_bucket_policy["aws_s3_bucket_policy.dev_logs_bucket_policy"]
aws_s3_bucket_policy.dev_test_bucket_policy["aws_s3_bucket_policy.dev_test_bucket_policy"]
aws_s3_bucket_policy.dev_logs_bucket_policy --> aws_s3_bucket.dev_logs_bucket
aws_s3_bucket_policy.dev_test_bucket_policy --> aws_s3_bucket.dev_test_bucket
end
flowchart TD
subgraph Hashicorp
subgraph Terraform
aws_db_instance.example_db["aws_db_instance.example_db"]
aws_instance.example_instance["aws_instance.example_instance"]
aws_s3_bucket.logs["aws_s3_bucket.logs"]
aws_s3_bucket.test["aws_s3_bucket.test"]
aws_s3_bucket_policy.logs_policy["aws_s3_bucket_policy.logs_policy"]
aws_s3_bucket_policy.test_policy["aws_s3_bucket_policy.test_policy"]
aws_s3_bucket_policy.logs_policy --> aws_s3_bucket.logs
aws_s3_bucket_policy.test_policy --> aws_s3_bucket.test
end
end
```

## Installation
Expand All @@ -56,6 +58,41 @@ cd terramaid
make build
```

### Usage

`terramaid` can be configured using CLI parameters and environment variables.

> [!NOTE]
> CLI parameters take precedence over environment variables.
The following configuration options are available:

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

Usage:
terramaid [flags]
terramaid [command]

Available Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
version Print the CLI version

Flags:
-r, --direction string Specify the direction of the flowchart (env: TERRAMAID_DIRECTION) (default "TD")
-h, --help help for terramaid
-o, --output string Output file for Mermaid diagram (env: TERRAMAID_OUTPUT) (default "Terramaid.md")
-s, --subgraph-name string Specify the subgraph name of the flowchart (env: TERRAMAID_SUBGRAPH_NAME) (default "Terraform")
-b, --tf-binary string Path to Terraform binary (env: TERRAMAID_TF_BINARY)
-d, --tf-dir string Path to Terraform directory (env: TERRAMAID_TF_DIR) (default ".")
-p, --tf-plan string Path to Terraform plan file (env: TERRAMAID_TF_PLAN)
-w, --working-wir string Working directory for Terraform (env: TERRAMAID_WORKING_DIR) (default ".")

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

### Docker Image

Run the following command to utilize the Terramaid Docker image:
Expand Down
31 changes: 16 additions & 15 deletions cmd/docs.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
package cmd

import (
"log"

"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)

var docsCmd = &cobra.Command{
Use: "docs",
Short: "Generate documentation for the CLI",
SilenceUsage: true,
Run: func(cmd *cobra.Command, args []string) {
err := doc.GenMarkdownTree(RootCmd, "./docs")
if err != nil {
log.Fatal(err)
}
},
}
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
}

return nil
},
}

func init() {
RootCmd.AddCommand(docsCmd)
return cmd
}
151 changes: 99 additions & 52 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,117 @@ package cmd

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

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

var (
workingDir string
tfDir string
tfPlan string
tfBinary string
output string
direction string
subgraphName string
)
var Version = "0.0.1"

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"`
}

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",
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 != "" && !utils.TerraformFilesExist(options.TFDir) {
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)
}

var RootCmd = &cobra.Command{
Use: "terramaid",
Short: "A utility for generating Mermaid diagrams from Terraform",
SilenceUsage: true,
Run: func(cmd *cobra.Command, args []string) {
var err error
if tfBinary == "" {
tfBinary, err = exec.LookPath("terraform")
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 {
graph, err := internal.ParseTerraform(options.WorkingDir, options.TFBinary, options.TFPlan)
if err != nil {
fmt.Printf("Error finding Terraform binary: %v\n", err)
os.Exit(1)
return fmt.Errorf("error parsing Terraform: %w", err)
}

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

graph, err := internal.ParseTerraform(workingDir, tfBinary, tfPlan)
if err != nil {
fmt.Printf("Error parsing Terraform: %v\n", err)
os.Exit(1)
}

// Convert the graph to a Mermaid diagram
mermaidDiagram, err := internal.ConvertToMermaid(graph, direction, subgraphName)
if err != nil {
fmt.Printf("Error converting to Mermaid: %v\n", err)
os.Exit(1)
}

// Write the Mermaid diagram to the specified output file
err = os.WriteFile(output, []byte(mermaidDiagram), 0644)
if err != nil {
fmt.Printf("Error writing to file: %v\n", err)
os.Exit(1)
}
fmt.Printf("Mermaid diagram successfully written to %s\n", output)
},

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())

return cmd
}

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

func init() {
RootCmd.Flags().StringVarP(&output, "output", "o", "Terramaid.md", "Output file for Mermaid diagram")
RootCmd.Flags().StringVarP(&direction, "direction", "r", "TD", "Specify the direction of the flowchart")
RootCmd.Flags().StringVarP(&subgraphName, "subgraphName", "s", "Terraform", "Specify the subgraph name of the flowchart")
RootCmd.Flags().StringVarP(&tfDir, "tfDir", "d", ".", "Path to Terraform directory")
RootCmd.Flags().StringVarP(&tfPlan, "tfPlan", "p", "", "Path to Terraform plan file")
RootCmd.Flags().StringVarP(&tfBinary, "tfBinary", "b", "", "Path to Terraform binary")
RootCmd.Flags().StringVarP(&workingDir, "workingDir", "w", ".", "Working directory for Terraform")
return nil
}
Loading

0 comments on commit 5b6dfa8

Please sign in to comment.