From 974b0d33cc7a3b7dec8fd60a9859c05b945a7fb8 Mon Sep 17 00:00:00 2001 From: Matt Ullman Date: Thu, 13 Jun 2024 03:15:03 +0000 Subject: [PATCH 1/2] pkg: Refactor cmd module This implements the following changes and adjustments to the cmd module: * Reduced the amount of global variables to 1. Now the cmd module can either reference a global `runtimeFlags` variable which contains all the flags passed in from the command line as well as some new helper functions and a helper method for reducing the amount of copy/pasted logic. * Implemented a new `GetConfig()` function which caches the result of the first lookup as well as cleans up the amount of copy/pasted logic that was otherwise scattered throughout the module. * Moved each command's implementation in to named functions to improve future testability as well as allow us to tab over the entire function's logic by one. * Moved the definition of the commotion root directory from the template module in to the cmd module. This now means all directories are now pushed down to the templates module instead of being split between the templates module and the cmd module. Additionally, this will also make it easier to potentially allow re-defining the commotion root directory either via a command line argument or environment variable. * Added a new --repo_url flag to override the git URL that Terraform templates are cloned from. Like above, this means that the templates module now simply clones from the URL passed in instead of attempt to set a default on its own if an empty string or nil is passed in, which should never happen. * Various tweaks to logging. This adjusts certain log statements to run log.Printf instead of calling log.Println(... fmt.Sprintf). The rest of the code and locations these are used will be unified in a follow up patch. --- pkg/cmd/cmd.go | 506 ++++++++++++++++++++++---------------- pkg/templates/template.go | 29 +-- 2 files changed, 298 insertions(+), 237 deletions(-) diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index ae113f8..8446ee4 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -14,271 +14,341 @@ import ( "github.com/spf13/cobra" ) -// ASCII Banner -var appName = "Cloud Commotion" -var appVersion = "v0.0.2" -var banner = figure.NewFigure("Cloud Commotion", "larry3d", true).String() -var asciiBanner = fmt.Sprintf("%s\nby Security Runners %s\n", banner, appVersion) - -// Global flags -var terraform_dir string -var terraform_loc string -var resource_name string -var sensitive_content string -var config_file string -var region string -var debug bool +type RuntimeFlags struct { + // Path of the scenario to run. + terraformDir string + // (unused) The location of the Terraform binary. + terraformLoc string + // (unused) The name of the resource. + resourceName string + // (unused) Flag to be discovered by the incident responder. + sensitiveContent string + // Path to the configuration file to use. + configFile string + // Region to manage cloud resources in. + region string + // Whether or not to print debugging information for CloudCommotion. + debug bool + // Git URL to clone Terraform templates from. + repoURL string + + // This is technically not a flag, but we want to reduce the number + // of globals we have. + config *config.Config +} + +// Checks if the set RuntimeFlags.terraformDir directory exists, returns true if it does +// and false if it does not. +func (r *RuntimeFlags) terraformDirExists() bool { + if _, err := os.Stat(r.terraformDir); os.IsNotExist(err) { + return false + } else { + return true + } +} + +var runtimeFlags = RuntimeFlags{} + +// Retrieve a configuration instance from pkg/config/config.go +// +// Everything that needs a reference to the config should prefer this +// function instead of config.GetConfig as this function caches the +// config instance to avoid re-reading and parsing the files after +// each call. +func GetConfig() config.Config { + if (runtimeFlags.config != nil) { + return *runtimeFlags.config + } + + // Retrieve configuration from ~/.commotion/config and create if does not exist + runtimeFlags.config = config.GetConfig(runtimeFlags.configFile) + return *runtimeFlags.config +} + +// Prints a fancy banner to standard out. +func printAsciiBanner() { + var appName = "Cloud Commotion" + var appVersion = "v0.0.2" + var fontName = "larry3d" + var banner = figure.NewFigure(appName, fontName, true).String() + var asciiBanner = fmt.Sprintf("%s\nby Security Runners %s\n", banner, appVersion) + + fmt.Println(asciiBanner) +} + +// Get the absolute path to the root commotion directory. +func getCommotionDirectory() string { + return filepath.Join(os.Getenv("HOME"), ".commotion") +} + +// Returns the absolute path to ``name`` relative to the default commotion +// install directory. +func getRelativeToCommotionDirectory(name string) string { + return filepath.Join(os.Getenv("HOME"), ".commotion", name) +} + +// The implementation for ./CloudCommotion +func rootCmdRun(cmd *cobra.Command, args []string) { + printAsciiBanner() + + cmd.Help() +} var rootCmd = &cobra.Command{ Use: "cloudcommotion", Short: "A CLI tool for causing commotion within your cloud environment", Long: "Cloud Commotion purposefully creates resources that should set off alarm bells within your environment to help you prepare for an incident.", Example: `cloudcommotion -h`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println(asciiBanner) - cmd.Help() - }, + Run: rootCmdRun, +} + +// The implementation for ./CloudCommotion plan +func planCmdRun(cmd *cobra.Command, args []string) { + printAsciiBanner() + + log.Println("Starting commotion planning, prepare for the inevitable!") + + // Download Terraform templates if the directory does not exist + if ! runtimeFlags.terraformDirExists() { + commotionRoot := getCommotionDirectory() + + err := templates.DownloadTerraformTemplates(commotionRoot, runtimeFlags.repoURL, runtimeFlags.debug) + if err != nil { + log.Fatal(err) + } + } + + var tfdir string + for _, mod := range GetConfig().Module { + // Get the tf module directory + if mod.TerraformLoc == "local" { + tfdir = mod.TerraformDir + } else { + // Default to remote if not set + tfdir = getRelativeToCommotionDirectory(mod.TerraformDir) + } + + // Merge config.variables with module.variables + tfvars := config.MergeVariables(GetConfig().Variables, mod.Variables) + + // If region flag is set, use that region + if runtimeFlags.region != "" { + tfvars["region"] = runtimeFlags.region + } else { + tfvars["region"] = GetConfig().Region + } + + // If debug flag in args is set, print the Terraform variables + if runtimeFlags.debug { + log.Printf("Terraform variables: %v\n", tfvars) + log.Printf("Terraform directory: %s\n", tfdir) + } + + // Plan the infrastructure to be created + log.Println("Planning commotion infrastructure for: " + mod.Name) + plan := terraform.PlanTerraform(tfvars, tfdir, runtimeFlags.debug) + + // Log out the results + if plan { + log.Println("Commotion infrastructure has been planned successfully: " + mod.Name) + } else { + log.Println("No changes detected for: " + mod.Name) + } + } + + log.Println("Completed! Now lets see how good your monitoring systems are...") } var planCmd = &cobra.Command{ Use: "plan", Short: "Plan infrastructure to be created through Cloud Commotion.", - Long: "Run a terraform plan on infrastructure to be created through cloud commotion", - Run: func(cmd *cobra.Command, args []string) { - // Welcome banner - fmt.Println(asciiBanner) - log.Println("Starting commotion planning, prepare for the inveitable!") - - // Check if terraform module directory exists - // If not, download the templates - terraform_dir := filepath.Join(os.Getenv("HOME"), ".commotion", "terraform") - if _, err := os.Stat(terraform_dir); os.IsNotExist(err) { - // Define repoURL if not set - var repoURL string - // Download the terraform templates - err := templates.DownloadTerraformTemplates(repoURL, debug) - if err != nil { - log.Fatal(err) - } + Long: "Run a Terraform plan on infrastructure to be created through cloud commotion", + Run: planCmdRun, +} + +// The implementation for ./CloudCommotion apply +func applyCmdRun(cmd *cobra.Command, args []string) { + printAsciiBanner() + + log.Println("Starting commotion engagement, buckle your seatbelt!") + + // Download Terraform templates if we haven't already + if ! runtimeFlags.terraformDirExists() { + commotionRoot := getCommotionDirectory() + + err := templates.DownloadTerraformTemplates(commotionRoot, runtimeFlags.repoURL, runtimeFlags.debug) + if err != nil { + log.Fatal(err) } + } - var tfdir string - for _, mod := range config.GetConfig(config_file).Module { - // Get the tf module directory - if mod.TerraformLoc == "local" { - tfdir = mod.TerraformDir - } else { - // Default to remote if not set - tfdir = filepath.Join(os.Getenv("HOME"), ".commotion", mod.TerraformDir) - } - - // Merge config.variables with module.variables - tfvars := config.MergeVariables(config.GetConfig(config_file).Variables, mod.Variables) - - // If region flag is set, use that region - if region != "" { - tfvars["region"] = region - } else { - tfvars["region"] = config.GetConfig(config_file).Region - } - - // If debug flag in args is set, print the terraform variables - if debug { - log.Println("Terraform variables: " + fmt.Sprintf("%v", tfvars)) - log.Println("Terraform directory: " + tfdir) - } - - // Plan the infrastructure to be created - log.Println("Planning commotion infrastructure for: " + mod.Name) - plan := terraform.PlanTerraform(tfvars, tfdir, debug) - - // Log out the results - if plan { - log.Println("Commotion infrastructure has been planned successfully: " + mod.Name) - } else { - log.Println("No changes detected for: " + mod.Name) - } + var tfdir string + for _, mod := range GetConfig().Module { + // Get the tf module directory + if mod.TerraformLoc == "local" { + tfdir = mod.TerraformDir + } else { + // Default to remote if not set + tfdir = getRelativeToCommotionDirectory(mod.TerraformDir) + } + + // Merge config.variables with module.variables + tfvars := config.MergeVariables(GetConfig().Variables, mod.Variables) + + // If region flag is set, use that region + if runtimeFlags.region != "" { + tfvars["region"] = runtimeFlags.region + } else { + tfvars["region"] = GetConfig().Region } - log.Println("Completed! Now lets see how good your monitoring systems are...") - }, + // If debug flag in args is set, print the Terraform variables + if runtimeFlags.debug { + log.Printf("Terraform variables: %v\n", tfvars) + log.Println("Terraform directory: " + tfdir) + log.Println("Terraform mod:" + mod.TerraformDir) + } + + // Plan the infrastructure to be created + log.Println("Planning and applying commotion infrastructure for: " + mod.Name) + plan := terraform.PlanTerraform(tfvars, tfdir, runtimeFlags.debug) + + // Apply only if plan detects changes to be made + if plan { + terraform.ApplyTerraform(tfvars, tfdir, runtimeFlags.debug) + } + + // Retrieve the commotion asset + output := terraform.OutputTerraform(tfdir) + + // Extract the exposed_asset output variable + exposed_asset := output["exposed_asset"].Value + raw := json.RawMessage(exposed_asset) + asset, err := json.Marshal(&raw) + if err != nil { + panic(err) + } + + // Success + log.Println("Commotion infrastructure has been applied/updated successfully: " + string(asset)) + } + + log.Println("Completed! Now lets see how good your monitoring systems are...") } var applyCmd = &cobra.Command{ Use: "apply", Short: "Executes individual modules", Long: "Execute commotion modules located within the terraform directory", - Run: func(cmd *cobra.Command, args []string) { - // Welcome banner for the application - fmt.Println(asciiBanner) - log.Println("Starting commotion engagement, buckle your seatbelt!") - - // Check if terraform module directory exists - // If not, download the templates - terraform_dir := filepath.Join(os.Getenv("HOME"), ".commotion", "terraform") - if _, err := os.Stat(terraform_dir); os.IsNotExist(err) { - // Define repoURL if not set - var repoURL string - // Download the terraform templates - err := templates.DownloadTerraformTemplates(repoURL, debug) - if err != nil { - log.Fatal(err) - } - } + Run: applyCmdRun, +} - var tfdir string - for _, mod := range config.GetConfig(config_file).Module { - // Get the tf module directory - if mod.TerraformLoc == "local" { - tfdir = mod.TerraformDir - } else { - // Default to remote if not set - tfdir = filepath.Join(os.Getenv("HOME"), ".commotion", mod.TerraformDir) - } - - // Merge config.variables with module.variables - tfvars := config.MergeVariables(config.GetConfig(config_file).Variables, mod.Variables) - - // If region flag is set, use that region - if region != "" { - tfvars["region"] = region - } else { - tfvars["region"] = config.GetConfig(config_file).Region - } - - // If debug flag in args is set, print the terraform variables - if debug { - log.Println("Terraform variables: " + fmt.Sprintf("%v", tfvars)) - log.Println("Terraform directory: " + tfdir) - log.Println("Terraform mod:" + mod.TerraformDir) - } - - // Plan the infrastructure to be created - log.Println("Planning and applying commotion infrastructure for: " + mod.Name) - plan := terraform.PlanTerraform(tfvars, tfdir, debug) - - // Apply only if plan detects changes to be made - if plan { - terraform.ApplyTerraform(tfvars, tfdir, debug) - } - - // Retrieve the commotion asset - output := terraform.OutputTerraform(tfdir) - - // Extract the exposed_asset output variable - exposed_asset := output["exposed_asset"].Value - raw := json.RawMessage(exposed_asset) - asset, err := json.Marshal(&raw) - if err != nil { - panic(err) - } - - // Success - log.Println("Commotion infrastructure has been applied/updated successfully: " + string(asset)) - } +// The implementation for ./CloudCommotion update +func updateCmdRun(cmd *cobra.Command, args []string) { + printAsciiBanner() + + // Update the Terraform templates + commotionRoot := getCommotionDirectory() - log.Println("Completed! Now lets see how good your monitoring systems are...") - }, + err := templates.UpdateTerraformTemplates(commotionRoot, runtimeFlags.repoURL, runtimeFlags.debug) + if err != nil { + log.Fatal(err) + } } var updateCmd = &cobra.Command{ Use: "update", - Long: "Update the terraform templates", - Run: func(cmd *cobra.Command, args []string) { - // Print banner - fmt.Println(asciiBanner) - - // Update the terraform templates - var repoURL string - err := templates.UpdateTerraformTemplates(repoURL, debug) - if err != nil { - log.Fatal(err) + Short: "Update the Terraform templates", + Long: "Update the Terraform templates", + Run: updateCmdRun, +} + +// The implementation for ./CloudCommotion destroy +func destroyCmdRun(cmd *cobra.Command, args []string) { + printAsciiBanner() + + // Loop through modules and destroy Terraform module + var tfdir string + for _, mod := range GetConfig().Module { + // Get the tf module directory + if mod.TerraformLoc == "local" { + tfdir = mod.TerraformDir + } else { + // Default to remote if not set + tfdir = getRelativeToCommotionDirectory(mod.TerraformDir) } - }, + + // Merge variables + tfvars := config.MergeVariables(GetConfig().Variables, mod.Variables) + + // If region flag is set, use that region + if runtimeFlags.region != "" { + tfvars["region"] = runtimeFlags.region + } else { + tfvars["region"] = GetConfig().Region + } + + // If debug flag in args is set, print the Terraform variables + if runtimeFlags.debug { + log.Printf("Terraform variables: %v\n", tfvars) + log.Println("Terraform directory: " + tfdir) + } + + // Destroy the infrastructure + log.Println("Destroying commotion infrastructure for: " + mod.Name) + terraform.DestroyTerraform(tfvars, tfdir, runtimeFlags.debug) + } } var destroyCmd = &cobra.Command{ Use: "destroy", Short: "Destroy infrastructure created through Cloud Commotion.", Long: "Run a terraform destroy on infrastructure created through cloud commotion", - Run: func(cmd *cobra.Command, args []string) { - // Print banner - fmt.Println(asciiBanner) - - // Loop through modules and destroy terraform module - var tfdir string - for _, mod := range config.GetConfig(config_file).Module { - // Get the tf module directory - if mod.TerraformLoc == "local" { - tfdir = mod.TerraformDir - } else { - // Default to remote if not set - tfdir = filepath.Join(os.Getenv("HOME"), ".commotion", mod.TerraformDir) - } - // Merge variables - tfvars := config.MergeVariables(config.GetConfig(config_file).Variables, mod.Variables) - - // If region flag is set, use that region - if region != "" { - tfvars["region"] = region - } else { - tfvars["region"] = config.GetConfig(config_file).Region - } - - // If debug flag in args is set, print the terraform variables - if debug { - log.Println("Terraform variables: " + fmt.Sprintf("%v", tfvars)) - log.Println("Terraform directory: " + tfdir) - } - - // Destroy the infrastructure - log.Println("Destroying commotion infrastructure for: " + mod.Name) - terraform.DestroyTerraform(tfvars, tfdir, debug) - } - }, -} - -// Functino to retrieve the configuration from pkg/config/config.go -func GetConfig() config.Config { - // Retrieve configuration from ~/.commotion/config and create if does not exist - configStruct := config.GetConfig(config_file) - return *configStruct + Run: destroyCmdRun, } +// This defines the CLI argument flags that are passed to our global runtime cache. func init() { - // Region flag - rootCmd.PersistentFlags().StringVarP(®ion, "region", "r", "", "AWS region to deploy resources") - if region == "" { + rootCmd.PersistentFlags().StringVarP(&runtimeFlags.region, "region", "r", "", "AWS region to deploy resources") + if runtimeFlags.region == "" { region, ok := os.LookupEnv("AWS_REGION") - if !ok { - rootCmd.MarkFlagRequired("region") + runtimeFlags.region = region + if ok { + rootCmd.Flags().Set("region", runtimeFlags.region) } else { - rootCmd.Flags().Set("region", region) + rootCmd.MarkFlagRequired("region") } } - // Based on the value of provider(aws, azure, gcp) if the region is set to random, then set the region to a random region - if region == "random" { - // Use config.GetRandomRegion() to get a random region and process error - randregion, err := config.GetRandomRegion(config.GetConfig(config_file).Provider) + if runtimeFlags.region == "random" { + // Generate a random region based on the cloud provider specified in the config file + randregion, err := config.GetRandomRegion(GetConfig().Provider) if err != nil { log.Fatal(err) } - region = randregion + runtimeFlags.region = randregion } - // Global variables - rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "enable debug mode") - rootCmd.PersistentFlags().StringVarP(&config_file, "config", "c", "", "the config file to use") - rootCmd.PersistentFlags().StringVarP(&sensitive_content, "flag", "f", "", "the flag to be discovered by the incident responder") - rootCmd.PersistentFlags().StringVarP(&resource_name, "resource_name", "a", "", "the name of the resource") - rootCmd.PersistentFlags().StringVarP(&terraform_loc, "terraform_loc", "l", "", "the location of the terraform binary") - rootCmd.Flags().StringVarP(&terraform_dir, "terraform_dir", "t", "", "the scenario in which to run") - - // Variables for create cmd - // applyCmd.Flags().StringVarP(&resource_name, "resource_name", "a", "", "the name of the resource") - // applyCmd.Flags().StringVarP(&sensitive_content, "sensitive_content", "c", "", "the flag to be discovered by the incident responder") - // applyCmd.Flags().StringVarP(&terraform_dir, "terraform_dir", "t", "", "the scenario in which to create") + rootCmd.PersistentFlags().BoolVarP(&runtimeFlags.debug, "debug", "d", false, "enable debug mode") + rootCmd.PersistentFlags().StringVarP(&runtimeFlags.configFile, "config", "c", "", "the config file to use") + rootCmd.PersistentFlags().StringVarP(&runtimeFlags.sensitiveContent, "flag", "f", "", "the flag to be discovered by the incident responder") + rootCmd.PersistentFlags().StringVarP(&runtimeFlags.resourceName, "resource_name", "a", "", "the name of the resource") + rootCmd.PersistentFlags().StringVarP(&runtimeFlags.terraformLoc, "terraform_loc", "l", "", "the location of the terraform binary") + rootCmd.Flags().StringVarP(&runtimeFlags.terraformDir, "terraform_dir", "t", "", "the scenario in which to run") + + // Always ensure a terraform directory is set + if runtimeFlags.terraformDir == "" { + runtimeFlags.terraformDir = getRelativeToCommotionDirectory("terraform") + } + + var defaultGitUrl = "git@github.com:SecurityRunners/CloudCommotion.git" + + rootCmd.PersistentFlags().StringVarP(&runtimeFlags.repoURL, "repo_url", "u", defaultGitUrl, "git repository to download Terraform templates from") + + // Always ensure a repository URL is set + if runtimeFlags.repoURL == "" { + runtimeFlags.repoURL = defaultGitUrl + } } // Execute executes the root command. diff --git a/pkg/templates/template.go b/pkg/templates/template.go index aec3b22..3f593a4 100644 --- a/pkg/templates/template.go +++ b/pkg/templates/template.go @@ -6,20 +6,11 @@ import ( "os" "os/exec" "path/filepath" + "strings" ) -var repoURL string - -// DownloadTerraformTemplates downloads Terraform templates from a remote GitHub repository. -func DownloadTerraformTemplates(repoURL string, debug bool) error { - // Define repoURL if not set - if repoURL == "" { - repoURL = "git@github.com:SecurityRunners/CloudCommotion.git" - } - - // Define the target directory where Terraform templates will be stored - targetDir := filepath.Join(os.Getenv("HOME"), ".commotion") - +// Download Terraform templates from a remote GitHub repository in to commotion root. +func DownloadTerraformTemplates(targetDir string, repoURL string, debug bool) error { // Check if the target directory already exists; if not, create it if _, err := os.Stat(targetDir); os.IsNotExist(err) { err := os.MkdirAll(targetDir, 0755) @@ -49,14 +40,14 @@ func DownloadTerraformTemplates(repoURL string, debug bool) error { return nil } -// UpdateTerraformTemplates updates Terraform templates in the target directory. -func UpdateTerraformTemplates(repoURL string, debug bool) error { - // Define the target directory where Terraform templates are stored - targetDir := filepath.Join(os.Getenv("HOME"), ".commotion") - +// Run git pull within commotion root to download new Terraform templates. +// +// This function will implicitly run DownloadTerraformTemplates() if the +// commotion root directory does not exist. +func UpdateTerraformTemplates(targetDir string, repoURL string, debug bool) error { // Check if the target directory exists if _, err := os.Stat(targetDir); os.IsNotExist(err) { - DownloadTerraformTemplates("", debug) + DownloadTerraformTemplates(targetDir, repoURL, debug) return nil } @@ -71,7 +62,7 @@ func UpdateTerraformTemplates(repoURL string, debug bool) error { // Debug logging if debug { - log.Println("Running command: " + fmt.Sprintf("%v", cmd.Args)) + log.Printf("Running command %s in %s.\n", strings.Join(cmd.Args, " "), targetDir) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr } From 3b2d473b7b14b84ea68cdc46821bff7776009303 Mon Sep 17 00:00:00 2001 From: Matt Ullman Date: Tue, 18 Jun 2024 01:55:23 +0000 Subject: [PATCH 2/2] cmd: Simplify the process for bootstrapping command runs This unifies the basic bootstrapping process when running any commands that interact with the commotion directory. This also cleans up the way that CleanupTerraformTemplatesDirectory scrubs through the commotion directory as well. --- pkg/cmd/cmd.go | 136 ++++++++++++++------------------------ pkg/config/config.go | 21 ++++++ pkg/templates/template.go | 101 ++++++++++++++-------------- 3 files changed, 118 insertions(+), 140 deletions(-) diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 8446ee4..1b1d08c 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "os" - "path/filepath" "github.com/SecurityRunners/CloudCommotion/pkg/config" "github.com/SecurityRunners/CloudCommotion/pkg/templates" @@ -47,6 +46,16 @@ func (r *RuntimeFlags) terraformDirExists() bool { } } +// Get the terraform region as specified from the CLI, otherwise default to +// the region specified in the config file. +func (r *RuntimeFlags) terraformRegion() string { + if r.region != "" { + return r.region + } else { + return GetConfig().Region + } +} + var runtimeFlags = RuntimeFlags{} // Retrieve a configuration instance from pkg/config/config.go @@ -76,15 +85,18 @@ func printAsciiBanner() { fmt.Println(asciiBanner) } -// Get the absolute path to the root commotion directory. -func getCommotionDirectory() string { - return filepath.Join(os.Getenv("HOME"), ".commotion") -} +// Download Terraform templates if the directory does not exist +func ensureTerraformDirExists() { + if runtimeFlags.terraformDirExists() { + return + } + + commotionRoot := config.GetCommotionDirectory() -// Returns the absolute path to ``name`` relative to the default commotion -// install directory. -func getRelativeToCommotionDirectory(name string) string { - return filepath.Join(os.Getenv("HOME"), ".commotion", name) + err := templates.DownloadTerraformTemplates(commotionRoot, runtimeFlags.repoURL, runtimeFlags.debug) + if err != nil { + log.Fatal(err) + } } // The implementation for ./CloudCommotion @@ -108,40 +120,21 @@ func planCmdRun(cmd *cobra.Command, args []string) { log.Println("Starting commotion planning, prepare for the inevitable!") - // Download Terraform templates if the directory does not exist - if ! runtimeFlags.terraformDirExists() { - commotionRoot := getCommotionDirectory() + ensureTerraformDirExists() - err := templates.DownloadTerraformTemplates(commotionRoot, runtimeFlags.repoURL, runtimeFlags.debug) - if err != nil { - log.Fatal(err) - } - } + cfg := GetConfig() - var tfdir string - for _, mod := range GetConfig().Module { - // Get the tf module directory - if mod.TerraformLoc == "local" { - tfdir = mod.TerraformDir - } else { - // Default to remote if not set - tfdir = getRelativeToCommotionDirectory(mod.TerraformDir) - } + for _, mod := range cfg.Module { + tfdir := mod.TfDir() - // Merge config.variables with module.variables - tfvars := config.MergeVariables(GetConfig().Variables, mod.Variables) - - // If region flag is set, use that region - if runtimeFlags.region != "" { - tfvars["region"] = runtimeFlags.region - } else { - tfvars["region"] = GetConfig().Region - } + // Merge config.variables, module.variables, and configured region + tfvars := config.MergeVariables(cfg.Variables, mod.Variables) + tfvars["region"] = runtimeFlags.terraformRegion() - // If debug flag in args is set, print the Terraform variables if runtimeFlags.debug { log.Printf("Terraform variables: %v\n", tfvars) log.Printf("Terraform directory: %s\n", tfdir) + log.Printf("Terraform mod: %s\n", mod.TerraformDir) } // Plan the infrastructure to be created @@ -172,41 +165,21 @@ func applyCmdRun(cmd *cobra.Command, args []string) { log.Println("Starting commotion engagement, buckle your seatbelt!") - // Download Terraform templates if we haven't already - if ! runtimeFlags.terraformDirExists() { - commotionRoot := getCommotionDirectory() + ensureTerraformDirExists() - err := templates.DownloadTerraformTemplates(commotionRoot, runtimeFlags.repoURL, runtimeFlags.debug) - if err != nil { - log.Fatal(err) - } - } + cfg := GetConfig() - var tfdir string - for _, mod := range GetConfig().Module { - // Get the tf module directory - if mod.TerraformLoc == "local" { - tfdir = mod.TerraformDir - } else { - // Default to remote if not set - tfdir = getRelativeToCommotionDirectory(mod.TerraformDir) - } + for _, mod := range cfg.Module { + tfdir := mod.TfDir() - // Merge config.variables with module.variables - tfvars := config.MergeVariables(GetConfig().Variables, mod.Variables) - - // If region flag is set, use that region - if runtimeFlags.region != "" { - tfvars["region"] = runtimeFlags.region - } else { - tfvars["region"] = GetConfig().Region - } + // Merge config.variables, module.variables, and configured region + tfvars := config.MergeVariables(cfg.Variables, mod.Variables) + tfvars["region"] = runtimeFlags.terraformRegion() - // If debug flag in args is set, print the Terraform variables if runtimeFlags.debug { log.Printf("Terraform variables: %v\n", tfvars) - log.Println("Terraform directory: " + tfdir) - log.Println("Terraform mod:" + mod.TerraformDir) + log.Printf("Terraform directory: %s\n", tfdir) + log.Printf("Terraform mod: %s\n", mod.TerraformDir) } // Plan the infrastructure to be created @@ -248,7 +221,7 @@ func updateCmdRun(cmd *cobra.Command, args []string) { printAsciiBanner() // Update the Terraform templates - commotionRoot := getCommotionDirectory() + commotionRoot := config.GetCommotionDirectory() err := templates.UpdateTerraformTemplates(commotionRoot, runtimeFlags.repoURL, runtimeFlags.debug) if err != nil { @@ -267,31 +240,20 @@ var updateCmd = &cobra.Command{ func destroyCmdRun(cmd *cobra.Command, args []string) { printAsciiBanner() - // Loop through modules and destroy Terraform module - var tfdir string - for _, mod := range GetConfig().Module { - // Get the tf module directory - if mod.TerraformLoc == "local" { - tfdir = mod.TerraformDir - } else { - // Default to remote if not set - tfdir = getRelativeToCommotionDirectory(mod.TerraformDir) - } + cfg := GetConfig() - // Merge variables - tfvars := config.MergeVariables(GetConfig().Variables, mod.Variables) + // Loop through modules and destroy Terraform module + for _, mod := range cfg.Module { + tfdir := mod.TfDir() - // If region flag is set, use that region - if runtimeFlags.region != "" { - tfvars["region"] = runtimeFlags.region - } else { - tfvars["region"] = GetConfig().Region - } + // Merge config.variables, module.variables, and configured region + tfvars := config.MergeVariables(cfg.Variables, mod.Variables) + tfvars["region"] = runtimeFlags.terraformRegion() - // If debug flag in args is set, print the Terraform variables if runtimeFlags.debug { log.Printf("Terraform variables: %v\n", tfvars) - log.Println("Terraform directory: " + tfdir) + log.Printf("Terraform directory: %s\n", tfdir) + log.Printf("Terraform mod: %s\n", mod.TerraformDir) } // Destroy the infrastructure @@ -338,7 +300,7 @@ func init() { // Always ensure a terraform directory is set if runtimeFlags.terraformDir == "" { - runtimeFlags.terraformDir = getRelativeToCommotionDirectory("terraform") + runtimeFlags.terraformDir = config.GetRelativeToCommotionDirectory("terraform") } var defaultGitUrl = "git@github.com:SecurityRunners/CloudCommotion.git" diff --git a/pkg/config/config.go b/pkg/config/config.go index 2dca486..a87918e 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -27,6 +27,27 @@ type Module struct { Variables map[string]interface{} `yaml:"variables"` } +// Get the absolute path to the root commotion directory. +func GetCommotionDirectory() string { + return filepath.Join(os.Getenv("HOME"), ".commotion") +} + +// Returns the absolute path to ``name`` relative to the default commotion +// install directory. +func GetRelativeToCommotionDirectory(name string) string { + return filepath.Join(os.Getenv("HOME"), ".commotion", name) +} + +// Get the tf module directory +func (mod Module) TfDir() string { + if mod.TerraformLoc == "local" { + return mod.TerraformDir + } else { + // Default to remote if not set + return GetRelativeToCommotionDirectory(mod.TerraformDir) + } +} + func GetConfig(configfile string) *Config { var configFilePath string diff --git a/pkg/templates/template.go b/pkg/templates/template.go index 3f593a4..0005029 100644 --- a/pkg/templates/template.go +++ b/pkg/templates/template.go @@ -87,58 +87,59 @@ func CleanupTerraformTemplatesDirectory(targetDir string, debug bool) error { return fmt.Errorf("failed to read directory: %v", err) } - // Initialize a flag to track if config.yml was moved - configMoved := false - // Remove all files and directories except "terraform" for _, entry := range entries { - if entry.Name() != "terraform" { - // Preserve the config file - if entry.Name() == "config" || entry.Name() == "config.yml" { - configSourcePath := filepath.Join(targetDir, "config", "config.yml") - configDestPath := filepath.Join(targetDir, "config.yml") - - if entry.Name() == "config" { - if err := os.Rename(configSourcePath, configDestPath); err != nil { - log.Printf("failed to move config.yml: %v", err) - } else { - if debug { - log.Println("config.yml has been moved to the target directory.") - } - } - - // remove the config directory - err := os.RemoveAll(filepath.Join(targetDir, "config")) - if err != nil { - log.Printf("failed to remove directory %s: %v", filepath.Join(targetDir, "config"), err) - } - } - - continue + if entry.Name() == "terraform" { + continue + } + + // Preserve the .git directory + if entry.Name() == ".git" { + continue + } + + // Preserve the config file + if entry.Name() == "config.yml" { + continue + } + + // Pull the config file from the config directory, then remove the config directory + if entry.Name() == "config" { + configSourcePath := filepath.Join(targetDir, "config", "config.yml") + configDestPath := filepath.Join(targetDir, "config.yml") + + if err := os.Rename(configSourcePath, configDestPath); err != nil { + log.Printf("failed to move config.yml: %v", err) + } else if debug { + log.Printf("config.yml has been moved to %s.", configDestPath) } - // Preserve the .git directory - if entry.Name() == ".git" { - continue + // remove the config directory + err := os.RemoveAll(filepath.Join(targetDir, "config")) + if err != nil { + log.Printf("failed to remove directory %s: %v", filepath.Join(targetDir, "config"), err) } - entryPath := filepath.Join(targetDir, entry.Name()) - if entry.IsDir() { - err := os.RemoveAll(entryPath) - if debug { - log.Println("Removing directory: " + entryPath) - } - if err != nil { - log.Printf("failed to remove directory %s: %v", entryPath, err) - } - } else { - err := os.Remove(entryPath) - if debug { - log.Println("Removing file: " + entryPath) - } - if err != nil { - log.Printf("failed to remove file %s: %v", entryPath, err) - } + continue + } + + // Remove everything else + entryPath := filepath.Join(targetDir, entry.Name()) + if entry.IsDir() { + err := os.RemoveAll(entryPath) + if debug { + log.Println("Removing directory: " + entryPath) + } + if err != nil { + log.Printf("failed to remove directory %s: %v", entryPath, err) + } + } else { + err := os.Remove(entryPath) + if debug { + log.Println("Removing file: " + entryPath) + } + if err != nil { + log.Printf("failed to remove file %s: %v", entryPath, err) } } } @@ -150,13 +151,7 @@ func CleanupTerraformTemplatesDirectory(targetDir string, debug bool) error { if _, err := os.Stat(configSourcePath); err == nil { if err := os.Rename(configSourcePath, configDestPath); err != nil { log.Printf("failed to move config.yml: %v", err) - } else { - configMoved = true - } - } - - if configMoved { - if debug { + } else if debug { log.Println("config.yml has been moved to the target directory.") } }