Skip to content

Commit

Permalink
Refactor and improved asciinema
Browse files Browse the repository at this point in the history
  • Loading branch information
atorrescogollo committed Jul 24, 2023
1 parent 560dfdc commit c44eafc
Show file tree
Hide file tree
Showing 17 changed files with 412 additions and 172 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,7 @@ terraform.rc
# Samples
/samples/**/tmp/*
/samples/**/.terraform.lock.hcl

# Asciinema
/.asciinema/
/assets/asciinema.sh.cast
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img src="./assets/gopher.png" height="200">

## Demo
[![asciicast](https://asciinema.org/a/598560.svg)](https://asciinema.org/a/598560)
[![asciicast](https://asciinema.org/a/JPYlivXxoZvB5PvjNvOxVQb7O.svg)](https://asciinema.org/a/JPYlivXxoZvB5PvjNvOxVQb7O)

# Overview
**Terraform Cascade** is a terraform-like tool that allows you to manage multiple terraform projects.
Expand Down
24 changes: 24 additions & 0 deletions assets/asciinema.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash -e

# This is the code structure (from a basic example)
cd samples/basic
tree -P backend.tf .

# Each depth level has a dependency on the previous one. For example:
cat dev/base/data.tf
cat dev/base/vpc.tf

# In this case it simulates that the VPC needs first the account where it has to be created
cat base/accounts.tf

# Let's run terraform init through cascade recursively
go run ../.. init --cascade-recursive

# Now, let's run apply
go run ../.. apply --cascade-recursive --auto-approve

# Let's see the created "infra"
tree -a -I .terraform /tmp/cascade

# And the state files
tree -a /tmp/cascade/.terraform/
10 changes: 9 additions & 1 deletion cmd/cascade.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import (
"github.com/spf13/cobra"
)

//var cascadeController = controller.NewCascadeController(
// *runRecursiveTerraformUseCase,
//)

// cascadeCmd represents the cascade command
var cascadeCmd = &cobra.Command{
Use: "cascade",
Expand All @@ -29,7 +33,11 @@ Specific commands for cascade orchestration.
WORK IN PROGRESS
`,
//Run: func(cmd *cobra.Command, args []string) {},
//Run: func(cmd *cobra.Command, args []string) {
// utils.ExitWithErr(
// cascadeController.HandleCascade(),
// )
//},
}

func init() {
Expand Down
17 changes: 17 additions & 0 deletions cmd/dependencies.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cmd

import (
"github.com/atorrescogollo/terraform-cascade/internal/orchestration"
"github.com/atorrescogollo/terraform-cascade/internal/usecases"
)

// Orchestration
var projectExecutor = orchestration.NewTerraformProjectExecutorWithOS()
var projectOrderResolver = orchestration.NewProjectOrderResolver()

// Use cases
var runRawTerraformUseCase = usecases.NewRunRawTerraformUseCase()
var runRecursiveTerraformUseCase = usecases.NewRunRecursiveTerraformUseCase(
*projectExecutor,
*projectOrderResolver,
)
21 changes: 15 additions & 6 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,22 @@ import (
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "terraform-cascade",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Short: "An opinionated terraform project orchestrator",
Long: `
Terraform Cascade is a terraform-like tool that allows you to manage multiple terraform projects.
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
It's made to be fully compatible with terraform, so you can use it as a drop-in replacement. However, it requires the terraform binary to be available in the PATH.
== Design ==
It works with a very opinionated design:
* Every project is inside a deep directory structure.
* To define a project, you only need to place a backend.tf file in that directory.
* In each layer, will be executed in the following order:
* Current directory (only when it has a backend.tf file)
* Whole base directory (with its layer)
* Other directories (with its layer)
`,
// Uncomment the following line if your bare application
// has an action associated with it:
//Run: func(cmd *cobra.Command, args []string) {},
Expand Down
129 changes: 51 additions & 78 deletions cmd/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,70 +19,31 @@ package cmd
import (
"fmt"
"os"
"os/exec"
"strings"

"github.com/atorrescogollo/terraform-cascade/internal/orchestration"
"github.com/atorrescogollo/terraform-cascade/internal/controller"
"github.com/atorrescogollo/terraform-cascade/internal/shared/utils"
"github.com/atorrescogollo/terraform-cascade/internal/terraform"
"github.com/spf13/cobra"
)

var terraformController = controller.NewTerraformController(
*runRawTerraformUseCase,
*runRecursiveTerraformUseCase,
)

// terraformCmd represents the terraform command
var terraformCmd = &cobra.Command{
Use: "terraform",
Short: "Terraform commands",
Long: `Terraform commands through the cascade cli.`,
Run: func(cmd *cobra.Command, args []string) {
cmd.Flags().Parse(args)
help := false
if len(args) == 0 ||
(len(args) == 1 && (args[0] == "-h" ||
args[0] == "--help" ||
args[0] == "-help" ||
args[0] == "help")) {
help = true
}

terraformArgs := utils.ExtractUnknownArgs(cmd.Flags(), args)
if !help {
/*
* Help command is actually there since cobra adds it behind the scenes.
* We add it when it's not the first argument so that it executes
* help as a terraform command instead of the cascade help command.
*
* For example:
*
* cascade terraform --help # Executes cascade help + terraform help
*
* cascade terraform plan --help # Executes terraform-plan help
*
*/
trailingHelp, _ := cmd.Flags().GetBool("help")
if trailingHelp {
terraformArgs = append(terraformArgs, "--help")
}
}
showUsage := false
for _, tfArg := range terraformArgs {
if strings.HasPrefix(tfArg, "--cascade-") {
// This is not a terraform flag. We need to handle
// it here since UnknownFlags is whitelisted
fmt.Println("Error: unknown flag:", tfArg)
showUsage = true
continue
}
}
if help || showUsage {
cmd.Usage()
return
}

exitErr := Run(cmd, terraformArgs)
if exitErr != nil && exitErr.ExitCode() != 0 {
fmt.Println("Error: Terraform exited with code", exitErr.ExitCode())
os.Exit(exitErr.ExitCode())
}
terraformArgs := retrieveTerraformArgsOrExit(cmd, args)
recursive, _ := cmd.Flags().GetBool("cascade-recursive")
utils.ExitWithErr(
terraformController.Handle(recursive, terraformArgs),
)
},
}

Expand Down Expand Up @@ -112,41 +73,53 @@ Global Flags:
Terraform Args:
`)
defaultUsageFunc := terraformCmd.UsageFunc()
terraformCmd.SetUsageFunc(func(cmd *cobra.Command) error {
terraformUsage := terraform.TerraformUsage()
err := defaultUsageFunc(cmd)
if err != nil {
return err
}

fmt.Println()
fmt.Println(terraformUsage)
return nil
})
terraformCmd.SetUsageFunc(terraformController.Usage)

terraformCmd.Flags().Bool("cascade-recursive", false, "Execute terraform projects recursively in order")
}

func Run(cmd *cobra.Command, args []string) *exec.ExitError {
cwd, err := os.Getwd()
if err != nil {
fmt.Println(err)
os.Exit(1)
func retrieveTerraformArgsOrExit(cmd *cobra.Command, args []string) []string {
help := false
if len(args) == 0 ||
(len(args) == 1 && (args[0] == "-h" ||
args[0] == "--help" ||
args[0] == "-help" ||
args[0] == "help")) {
help = true
}

recursive, _ := cmd.Flags().GetBool("cascade-recursive")
if recursive {
orchestrateDir, err := orchestration.OrchestrateProjectDirectory(cwd)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return orchestration.RunTerraformRecursively(*orchestrateDir, args, 0)
} else {
terraformArgs := utils.ExtractUnknownArgs(cmd.Flags(), args)
if !help {
/*
* Simply run terraform in the current directory
* Help command is actually there since cobra adds it behind the scenes.
* We add it when it's not the first argument so that it executes
* help as a terraform command instead of the cascade help command.
*
* For example:
*
* cascade terraform --help # Executes cascade help + terraform help
*
* cascade terraform plan --help # Executes terraform-plan help
*
*/
return terraform.TerraformExecWithOS(cwd, args)
trailingHelp, _ := cmd.Flags().GetBool("help")
if trailingHelp {
terraformArgs = append(terraformArgs, "--help")
}
}
showUsage := false
for _, tfArg := range terraformArgs {
if strings.HasPrefix(tfArg, "--cascade-") {
// This is not a terraform flag. We need to handle
// it here since UnknownFlags is whitelisted
fmt.Println("Error: unknown flag:", tfArg)
showUsage = true
continue
}
}
if help || showUsage {
cmd.Usage()
os.Exit(1)
}
return terraformArgs
}
26 changes: 26 additions & 0 deletions internal/controller/cascade_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package controller

import (
"fmt"

"github.com/atorrescogollo/terraform-cascade/internal/usecases"
)

type CascadeController struct {
RunRecursiveTerraformUseCase usecases.RunRecursiveTerraformUseCase
}

func NewCascadeController(runRecursiveTerraformUseCase usecases.RunRecursiveTerraformUseCase) *CascadeController {
return &CascadeController{
runRecursiveTerraformUseCase,
}
}

func (c CascadeController) HandleCascade() error {
// TODO: Implement cascade logic
//cwd, _ := os.Getwd()
//tfargs := []string{"init"}
//chdir := cwd + "/samples/basic"
//return c.RunRecursiveTerraformUseCase.Execute(chdir, tfargs)
return fmt.Errorf("not implemented")
}
45 changes: 45 additions & 0 deletions internal/controller/terraform_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package controller

import (
"fmt"
"os"

"github.com/atorrescogollo/terraform-cascade/internal/terraform"
"github.com/atorrescogollo/terraform-cascade/internal/usecases"
"github.com/spf13/cobra"
)

type TerraformController struct {
RunRawTerraformUseCase usecases.RunRawTerraformUseCase
RunRecursiveTerraformUseCase usecases.RunRecursiveTerraformUseCase
}

func NewTerraformController(runRawTerraformUseCase usecases.RunRawTerraformUseCase, runRecursiveTerraformUseCase usecases.RunRecursiveTerraformUseCase) *TerraformController {
return &TerraformController{
RunRawTerraformUseCase: runRawTerraformUseCase,
RunRecursiveTerraformUseCase: runRecursiveTerraformUseCase,
}
}

func (c TerraformController) Handle(recursive bool, tfargs []string) error {
cwd, _ := os.Getwd()
if !recursive {
/*
* Simply run terraform in the current directory
*/
return c.RunRawTerraformUseCase.Execute(cwd, tfargs)
}
return c.RunRecursiveTerraformUseCase.Execute(cwd, tfargs)
}

func (c TerraformController) Usage(cmd *cobra.Command) error {
terraformUsage := terraform.TerraformUsage()
err := cmd.UsageFunc()(cmd)
if err != nil {
return err
}

fmt.Println()
fmt.Println(terraformUsage)
return nil
}
Loading

0 comments on commit c44eafc

Please sign in to comment.