diff --git a/cmd/deleteStacks.go b/cmd/deleteStacks.go index 9d0fac1..f22e4fc 100644 --- a/cmd/deleteStacks.go +++ b/cmd/deleteStacks.go @@ -18,6 +18,7 @@ package cmd import ( "fmt" + "github.com/gookit/color" "github.com/nirdosh17/cfn-teardown/utils" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -39,7 +40,7 @@ Supply stack pattern as: 'qa-' return validateConfigs(config) }, Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Executing command: deleteStacks") + color.Red.Println("Executing command: deleteStacks") fmt.Println() if config.DryRun != "false" { fmt.Println("Running in dry run mode. Set dry run to 'false' to actually delete stacks.") diff --git a/cmd/listDependencies.go b/cmd/listDependencies.go index 003333c..48d86fd 100644 --- a/cmd/listDependencies.go +++ b/cmd/listDependencies.go @@ -18,6 +18,7 @@ package cmd import ( "fmt" + "github.com/gookit/color" "github.com/nirdosh17/cfn-teardown/utils" "github.com/spf13/cobra" ) @@ -40,7 +41,7 @@ Supply stack pattern as: 'qa-' }, Run: func(cmd *cobra.Command, args []string) { fmt.Println() - fmt.Println("Executing command: listDependencies") + color.Green.Println("Executing command: listDependencies") fmt.Println() // for safety config.DryRun = "true" diff --git a/go.mod b/go.mod index 3f04898..2b24e40 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( github.com/aws/aws-sdk-go v1.40.22 + github.com/gookit/color v1.4.2 github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/cobra v0.0.5 github.com/spf13/viper v1.8.1 diff --git a/go.sum b/go.sum index 492e2ac..78f215b 100644 --- a/go.sum +++ b/go.sum @@ -191,6 +191,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= @@ -350,6 +352,8 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/utils/deleter.go b/utils/deleter.go index 5bcd121..342c5b1 100644 --- a/utils/deleter.go +++ b/utils/deleter.go @@ -19,10 +19,11 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" + "os" "strings" "time" + "github.com/gookit/color" . "github.com/nirdosh17/cfn-teardown/models" ) @@ -59,7 +60,8 @@ func InitiateTearDown(config Config) { UpdateNukeStats(dependencyTree) msg := fmt.Sprintf("Unable to prepare dependencies. Error: %v", err.Error()) notifier.ErrorAlert(AlertMessage{Message: msg}) - log.Fatal(msg) + color.Error.Println(msg) + os.Exit(1) } dependencyTree = dt // need to do this for global scope writeToJSON(config.StackPattern, dependencyTree) @@ -69,30 +71,29 @@ func InitiateTearDown(config Config) { if ACTIVE_STACK_COUNT == 0 { UpdateNukeStats(dependencyTree) - fmt.Printf("\nNo matching stacks to delete! Stack count: %v\n", TOTAL_STACK_COUNT) + color.Yellow.Printf("\nNo matching stacks to delete! Stack count: %v\n", TOTAL_STACK_COUNT) notifier.SuccessAlert(AlertMessage{}) return } fmt.Println() - fmt.Println() - fmt.Printf("Following %v stacks are eligible for deletion:\n", ACTIVE_STACK_COUNT) + fmt.Printf("Following stacks are eligible for deletion | Stack count: %v\n", ACTIVE_STACK_COUNT) for stackName, _ := range dependencyTree { - fmt.Println(" -", stackName) + color.Gray.Println(" -", stackName) } - fmt.Println("\nCheck 'stack_teardown_details.json' file for more details.") + color.Style{color.Yellow, color.OpItalic}.Println("\nCheck 'stack_teardown_details.json' file for more details.") + fmt.Println() // safety check for accidental run if config.DryRun != "false" { return } - msg := fmt.Sprintf("Waiting for `%v minutes` before initiating deletion...", config.AbortWaitTimeMinutes) + msg := fmt.Sprintf("Waiting for `%v minutes` before starting deletion. Abort if necessary.", config.AbortWaitTimeMinutes) notifier.StartAlert(AlertMessage{Message: msg}) - fmt.Println() - fmt.Println(msg) + color.Red.Println(msg) time.Sleep(time.Duration(config.AbortWaitTimeMinutes) * time.Minute) - fmt.Println("\n\n------------------------- Deletion Started ----------------------------------") + color.Green.Println("\n\n---------------------------- Deletion Started -------------------------------") for { // Algorithm: // 1. Scan stacks who has zero importing stacks i.e. last leaf in the dependency tree @@ -112,7 +113,8 @@ func InitiateTearDown(config Config) { UpdateNukeStats(dependencyTree) msg := fmt.Sprintf("Unable to empty bucket from stack '%v'", sName) notifier.ErrorAlert(AlertMessage{Message: msg, FailedStack: stack}) - log.Fatalln(msg) // abort! + color.Error.Println(msg) + os.Exit(1) } err := cfn.DeleteStack(sName) @@ -121,7 +123,8 @@ func InitiateTearDown(config Config) { msg = fmt.Sprintf("Unable to send delete request for stack '%v' Error: %v", sName, err) stack.StackStatusReason = msg notifier.ErrorAlert(AlertMessage{Message: msg, FailedStack: stack}) - log.Fatalln(msg) + color.Error.Println(msg) + os.Exit(1) } stack.Status = DELETE_IN_PROGRESS stack.DeleteStartedAt = CurrentUTCDateTime() @@ -158,7 +161,8 @@ func InitiateTearDown(config Config) { msg := fmt.Sprintf("Unable to describe stack '%v'", sName) stack.StackStatusReason = msg notifier.ErrorAlert(AlertMessage{Message: msg, FailedStack: stack}) - log.Fatal(msg) + color.Error.Println(msg) + os.Exit(1) } } @@ -198,7 +202,8 @@ func InitiateTearDown(config Config) { UpdateNukeStats(dependencyTree) msg := fmt.Sprintf("Failed to delete stack `%v`. Reason: %v", sName, statusReason) notifier.ErrorAlert(AlertMessage{Message: msg, FailedStack: stack}) - log.Fatal(msg) + color.Error.Println(msg) + os.Exit(1) } else { // In some cases cloud9 stacks can't be deleted due to security group being manually attached to other resources like elastic search or redis // In such case it is better to wait for dependent resource's(mostly datastore or cache) stack and security group to get deleted and retry again @@ -210,7 +215,8 @@ func InitiateTearDown(config Config) { msg = fmt.Sprintf("Unable to send delete retry request for stack '%v' Error: %v", sName, err) stack.StackStatusReason = msg notifier.ErrorAlert(AlertMessage{Message: msg, FailedStack: stack}) - log.Fatalln(msg) + color.Error.Println(msg) + os.Exit(1) } stack.Status = DELETE_IN_PROGRESS stack.DeleteStartedAt = CurrentUTCDateTime() @@ -224,7 +230,7 @@ func InitiateTearDown(config Config) { // 5. If all stacks have already been deleted, stop execution. Else Go to step 1 if isEnvNuked(dependencyTree) { UpdateNukeStats(dependencyTree) - fmt.Printf("\nStack Teardown Successful! Deleted Stacks: %v\n", DELETED_STACK_COUNT) + color.Green.Printf("\n---------- STACK TEARDOWN SUCCESSFUL! STACKS DELETED: (%v) ----------\n\n", DELETED_STACK_COUNT) notifier.SuccessAlert(AlertMessage{}) break } @@ -232,9 +238,11 @@ func InitiateTearDown(config Config) { // 6. Check if nuke is stuck if isNukeStuck(dependencyTree) { UpdateNukeStats(dependencyTree) + // TODO: better messaging msg := "No stacks are eligible for deletion. Please find and delete stacks which do not have follow given pattern: " + config.StackPattern notifier.StuckAlert(AlertMessage{Message: msg}) - log.Fatal(msg) + color.Error.Println(msg) + os.Exit(1) break } } @@ -321,24 +329,25 @@ func isEnvNuked(dt map[string]StackDetails) bool { func prepareDependencyTree(envLabel string, cfn CFNManager) (map[string]StackDetails, error) { CFNConsoleBaseURL := "https://console.aws.amazon.com/cloudformation/home?region=" + cfn.AWSRegion + "#/stacks/stackinfo?stackId=" - fmt.Printf("Listing stacks matching with '%v'...\n", envLabel) + fmt.Printf("-------------- Listing Stacks | Match Pattern: [%v] --------------\n", color.Gray.Render(envLabel)) + dependencyTree, err := cfn.ListEnvironmentStacks() totalStackCount := len(dependencyTree) if err != nil { UpdateNukeStats(dependencyTree) - fmt.Printf("Failed listing stacks! Error: %v\n", err) + color.Error.Printf(" Failed listing stacks! Error: %v\n", err) return dependencyTree, err } - fmt.Println("Listing all exports...") + color.Gray.Println(" Listing all exports...") stackExports, err := cfn.ListEnvironmentExports() if err != nil { - fmt.Printf("Failed listing exports! Error: %v", err) + color.Error.Printf(" Failed listing exports! Error: %v", err) return dependencyTree, err } - fmt.Println("Listing all imports...") + color.Gray.Println(" Listing all imports...") stackCount := 0 var listImportErr error for stackName, stack := range dependencyTree { @@ -352,14 +361,14 @@ func prepareDependencyTree(envLabel string, cfn CFNManager) (map[string]StackDet // listing all importers. making single api call at a time to avoid rate limiting importingStacks, listImportErr := cfn.ListImports(stack.Exports) if listImportErr != nil { - fmt.Printf("Failed listing imports! Error: %v", listImportErr) + color.Error.Printf(" Failed listing imports! Error: %v", listImportErr) break } stack.ActiveImporterStacks = importingStacks dependencyTree[stackName] = stack stackCount++ - fmt.Println("Listing imports | ", stackCount, "/", totalStackCount, " stacks complete") + color.Gray.Println(" Listing imports | ", stackCount, "/", totalStackCount, " stacks complete") } if listImportErr != nil { @@ -379,7 +388,7 @@ func prepareDependencyTree(envLabel string, cfn CFNManager) (map[string]StackDet if err != nil { dne := strings.Contains(err.Error(), "does not exist") if !dne { - fmt.Printf("Error describing stack %v", mStk) + color.Error.Printf(" Error describing stack %v", mStk) break // real error. } dependencyTree[mStk] = StackDetails{ @@ -397,7 +406,7 @@ func prepareDependencyTree(envLabel string, cfn CFNManager) (map[string]StackDet // list imports importingStacks, listImportErr := cfn.ListImports(exports) if listImportErr != nil { - fmt.Println("Failed listing imports!") + color.Error.Println(" Failed listing imports!") break }