Skip to content

Commit

Permalink
Document/Refactor code (#3)
Browse files Browse the repository at this point in the history
* Document/Refactor code.
  • Loading branch information
nirdosh17 authored Aug 21, 2021
1 parent 6aa1055 commit 1f8f6d2
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 91 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# CFN Teardown
[![Go Report Card](https://goreportcard.com/badge/github.com/nirdosh17/cfn-teardown)](https://goreportcard.com/report/github.com/nirdosh17/cfn-teardown)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache-yellow.svg)](https://opensource.org/licenses/Apache-2.0)
![Latest GitHub Release](https://img.shields.io/github/release/nirdosh17/cfn-teardown)

CFN Teardown is a tool to delete matching CloudFormation stacks respecting stack dependencies.

Expand Down
2 changes: 2 additions & 0 deletions cmd/deleteStacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package cmd provides interface to register and define actions for all cli commands
package cmd

import (
Expand Down
2 changes: 2 additions & 0 deletions cmd/listDependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package cmd provides interface to register and define actions for all cli commands
package cmd

import (
Expand Down
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package cmd provides interface to register and define actions for all cli commands
package cmd

import (
Expand Down
4 changes: 4 additions & 0 deletions cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package cmd provides interface to register and define actions for all cli commands
package cmd

import (
Expand All @@ -21,6 +23,8 @@ import (
"github.com/spf13/cobra"
)

// Version is the current CLI version.
// This is overwrriten by semantic version tag while building binaries.
var Version = "development"

// versionCmd represents the version command
Expand Down
103 changes: 75 additions & 28 deletions models/cfn.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,47 +13,94 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package models has definition of entities used in the process of teardown
package models

// StackDetails represents a cloudformation stack, it's state and dependencies.
type StackDetails struct {
StackName string
Status string
StackStatusReason string // useful for failed cases
DeleteStartedAt string
DeleteCompletedAt string // must be fetched from describe status command. Wait time should not be considered
DeletionTimeInMinutes string // total minutes taken to delete the stack
DeleteCompletedAt string
DeletionTimeInMinutes string
DeleteAttempt int16
Exports []string
ActiveImporterStacks map[string]struct{} // active(not deleted) stacks which are importing exports from THIS stack
ActiveImporterStacks map[string]struct{} // active(not deleted) stacks which are importing exports from this stack
CFNConsoleLink string
}

// ---------- Stack statuses and their eligibility for deletion ------------
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-describing-stacks.html
// Stack status and eligibility for deletion
var CREATE_IN_PROGRESS string = "CREATE_IN_PROGRESS" // Wait
var CREATE_FAILED string = "CREATE_FAILED" // Eligible for deletion
var CREATE_COMPLETE string = "CREATE_COMPLETE" // Eligible for deletion
var ROLLBACK_IN_PROGRESS string = "ROLLBACK_IN_PROGRESS" // Wait
var ROLLBACK_FAILED string = "ROLLBACK_FAILED" // Eligible for deletion
var ROLLBACK_COMPLETE string = "ROLLBACK_COMPLETE" // Eligible for deletion
var DELETE_IN_PROGRESS string = "DELETE_IN_PROGRESS" // Wait
var DELETE_FAILED string = "DELETE_FAILED" // Cannot be deleted. Manual Intervention Required. Post Message in RC.
var DELETE_COMPLETE string = "DELETE_COMPLETE" // Skip
var UPDATE_IN_PROGRESS string = "UPDATE_IN_PROGRESS" // Wait
var UPDATE_COMPLETE_CLEANUP_IN_PROGRESS string = "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS" // Wait
var UPDATE_COMPLETE string = "UPDATE_COMPLETE" // Eligible for deletion
var UPDATE_ROLLBACK_IN_PROGRESS string = "UPDATE_ROLLBACK_IN_PROGRESS" // Wait
var UPDATE_ROLLBACK_FAILED string = "UPDATE_ROLLBACK_FAILED" // Cannot be deleted. Manual Intervention Required. Post Message in RC.
var UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS string = "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS" // Wait
var UPDATE_ROLLBACK_COMPLETE string = "UPDATE_ROLLBACK_COMPLETE" // Eligible for deletion
var REVIEW_IN_PROGRESS string = "REVIEW_IN_PROGRESS" // Wait
var IMPORT_IN_PROGRESS string = "IMPORT_IN_PROGRESS" // Wait
var IMPORT_COMPLETE string = "IMPORT_COMPLETE" // Wait
var IMPORT_ROLLBACK_IN_PROGRESS string = "IMPORT_ROLLBACK_IN_PROGRESS" // Wait
var IMPORT_ROLLBACK_FAILED string = "IMPORT_ROLLBACK_FAILED" // Cannot be deleted. Manual Intervention Required. Post Message in RC.
var IMPORT_ROLLBACK_COMPLETE string = "IMPORT_ROLLBACK_COMPLETE" // Wait

// all statuses except DELETE_COMPLETE

// CREATE_IN_PROGRESS stack status requires waiting before sending delete request.
var CREATE_IN_PROGRESS string = "CREATE_IN_PROGRESS"

// CREATE_FAILED stack status is eligible for deletion.
var CREATE_FAILED string = "CREATE_FAILED"

// CREATE_COMPLETE stack status is eligible for deletion.
var CREATE_COMPLETE string = "CREATE_COMPLETE"

// ROLLBACK_IN_PROGRESS stack status requires waiting before sending delete request.
var ROLLBACK_IN_PROGRESS string = "ROLLBACK_IN_PROGRESS"

// ROLLBACK_FAILED stack status is eligible for deletion.
var ROLLBACK_FAILED string = "ROLLBACK_FAILED"

// ROLLBACK_COMPLETE stack status is eligible for deletion.
var ROLLBACK_COMPLETE string = "ROLLBACK_COMPLETE"

// DELETE_IN_PROGRESS stack status requires waiting before taking any action
var DELETE_IN_PROGRESS string = "DELETE_IN_PROGRESS"

// DELETE_FAILED stack status after max delete attempts is unactionable and requires manual intervention
var DELETE_FAILED string = "DELETE_FAILED"

// DELETE_COMPLETE stack status can be skipped as the stack has already been deleted
var DELETE_COMPLETE string = "DELETE_COMPLETE"

// UPDATE_IN_PROGRESS stack status requires waiting before sending delete request.
var UPDATE_IN_PROGRESS string = "UPDATE_IN_PROGRESS"

// UPDATE_COMPLETE_CLEANUP_IN_PROGRESS stack status requires waiting before sending delete request.
var UPDATE_COMPLETE_CLEANUP_IN_PROGRESS string = "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS"

// UPDATE_COMPLETE stack status is eligible for deletion.
var UPDATE_COMPLETE string = "UPDATE_COMPLETE"

// UPDATE_ROLLBACK_IN_PROGRESS stack status requires waiting before sending delete request.
var UPDATE_ROLLBACK_IN_PROGRESS string = "UPDATE_ROLLBACK_IN_PROGRESS"

// UPDATE_ROLLBACK_FAILED stack status is eligible for deletion.
var UPDATE_ROLLBACK_FAILED string = "UPDATE_ROLLBACK_FAILED"

// UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS stack status requires waiting before sending delete request.
var UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS string = "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS"

// UPDATE_ROLLBACK_COMPLETE stack status is eligible for deletion.
var UPDATE_ROLLBACK_COMPLETE string = "UPDATE_ROLLBACK_COMPLETE"

// REVIEW_IN_PROGRESS stack status requires waiting before sending delete request.
var REVIEW_IN_PROGRESS string = "REVIEW_IN_PROGRESS"

// IMPORT_IN_PROGRESS stack status requires waiting before sending delete request.
var IMPORT_IN_PROGRESS string = "IMPORT_IN_PROGRESS"

// IMPORT_COMPLETE stack status requires waiting before sending delete request.
var IMPORT_COMPLETE string = "IMPORT_COMPLETE"

// IMPORT_ROLLBACK_IN_PROGRESS stack status requires waiting before sending delete request.
var IMPORT_ROLLBACK_IN_PROGRESS string = "IMPORT_ROLLBACK_IN_PROGRESS"

// IMPORT_ROLLBACK_FAILED stack status is eligible for deletion.
var IMPORT_ROLLBACK_FAILED string = "IMPORT_ROLLBACK_FAILED"

// IMPORT_ROLLBACK_COMPLETE stack status requires waiting before sending delete request.
var IMPORT_ROLLBACK_COMPLETE string = "IMPORT_ROLLBACK_COMPLETE"

// ActiveStatuses includes all stack statuses except DELETE_COMPLETE
var ActiveStatuses = []*string{
&CREATE_IN_PROGRESS,
&CREATE_FAILED,
Expand Down
2 changes: 2 additions & 0 deletions models/nuke.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package models has definition of entities used in the process of teardown
package models

// Config represents all the parameters supported by cfn-teardown
Expand Down
50 changes: 34 additions & 16 deletions utils/cloudformation.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package utils provides cli specifics methods for interacting with AWS services
package utils

import (
Expand All @@ -26,9 +28,10 @@ import (
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/aws/aws-sdk-go/service/sts"

. "github.com/nirdosh17/cfn-teardown/models"
"github.com/nirdosh17/cfn-teardown/models"
)

// CFNManager exposes methods to interact with CloudFormation via SDK.
type CFNManager struct {
TargetAccountId string
NukeRoleARN string
Expand All @@ -37,6 +40,7 @@ type CFNManager struct {
AWSRegion string
}

// DescribeStack returns description for particular stack.
func (dm CFNManager) DescribeStack(stackName string) (*cloudformation.Stack, error) {
cfn, err := dm.Session()
if err != nil {
Expand All @@ -50,6 +54,7 @@ func (dm CFNManager) DescribeStack(stackName string) (*cloudformation.Stack, err
return resp.Stacks[0], err
}

// ListStackResources lists description of all resources in a stack.
func (dm CFNManager) ListStackResources(stackName string) ([]*cloudformation.StackResourceSummary, error) {
cfn, err := dm.Session()
if err != nil {
Expand Down Expand Up @@ -81,6 +86,7 @@ func (dm CFNManager) ListStackResources(stackName string) ([]*cloudformation.Sta
return resp.StackResourceSummaries, err
}

// ListImports lists all stacks importing given exported names.
func (dm CFNManager) ListImports(exportNames []string) (map[string]struct{}, error) {
importers := make(map[string]struct{})
var err error
Expand All @@ -106,8 +112,8 @@ func (dm CFNManager) ListImports(exportNames []string) (map[string]struct{}, err
return importers, err
}

// No error means, delete request sent to cloudformation
// If the stack we are trying to delete has already been deleted, returns success
// DeleteStack sends delete request for a stack.
// Returns success if the stack we are trying to delete has already been deleted.
func (dm CFNManager) DeleteStack(stackName string) error {
fmt.Printf("Submitting delete request for stack: %v\n", stackName)
cfn, err := dm.Session()
Expand All @@ -117,21 +123,25 @@ func (dm CFNManager) DeleteStack(stackName string) error {
input := cloudformation.DeleteStackInput{StackName: &stackName}
// stack delete output is an empty struct
_, err = cfn.DeleteStack(&input)

// No error only means that the delete request was sent
// It does not gurantee that the stack will be deleted
return err
}

func (dm CFNManager) ListEnvironmentStacks() (map[string]StackDetails, error) {
// ListEnvironmentStacks lists matching stacks for the given regex.
func (dm CFNManager) ListEnvironmentStacks() (map[string]models.StackDetails, error) {
CFNConsoleBaseURL := "https://console.aws.amazon.com/cloudformation/home?region=" + dm.AWSRegion + "#/stacks/stackinfo?stackId="

// using stack name as key for easy traversal
envStacks := map[string]StackDetails{}
envStacks := map[string]models.StackDetails{}

cfn, err := dm.Session()
if err != nil {
return envStacks, err
}

input := cloudformation.ListStacksInput{StackStatusFilter: ActiveStatuses}
input := cloudformation.ListStacksInput{StackStatusFilter: models.ActiveStatuses}
// only returns first 100 stacks. Need to use NextToken
listStackOutput, err := cfn.ListStacks(&input)
if err != nil {
Expand All @@ -142,7 +152,7 @@ func (dm CFNManager) ListEnvironmentStacks() (map[string]StackDetails, error) {
// select stacks of our concern
stackName := *details.StackName
if dm.RegexMatch(stackName) {
sd := StackDetails{
sd := models.StackDetails{
StackName: stackName,
Status: *details.StackStatus,
CFNConsoleLink: (CFNConsoleBaseURL + stackName),
Expand All @@ -159,7 +169,7 @@ func (dm CFNManager) ListEnvironmentStacks() (map[string]StackDetails, error) {
nextToken := listStackOutput.NextToken
for nextToken != nil {
// sending next token for pagination
input = cloudformation.ListStacksInput{NextToken: nextToken, StackStatusFilter: ActiveStatuses}
input = cloudformation.ListStacksInput{NextToken: nextToken, StackStatusFilter: models.ActiveStatuses}
listStackOutput, err = cfn.ListStacks(&input)
if err != nil {
break
Expand All @@ -168,7 +178,7 @@ func (dm CFNManager) ListEnvironmentStacks() (map[string]StackDetails, error) {
// select stacks of our concern
stackName := *details.StackName
if dm.RegexMatch(stackName) {
sd := StackDetails{
sd := models.StackDetails{
StackName: stackName,
Status: *details.StackStatus,
CFNConsoleLink: (CFNConsoleBaseURL + stackName),
Expand All @@ -185,7 +195,11 @@ func (dm CFNManager) ListEnvironmentStacks() (map[string]StackDetails, error) {
return envStacks, err
}

// { "stack-1-name": ["export-1", "export-2"], "stack-2-name": []}
// ListEnvironmentExports finds all exported values for our matching stacks in this format:
// {
// "stack-1-name": ["export-1", "export-2"],
// "stack-2-name": []
// }
func (dm CFNManager) ListEnvironmentExports() (map[string][]string, error) {
exports := map[string][]string{}

Expand Down Expand Up @@ -232,12 +246,15 @@ func (dm CFNManager) ListEnvironmentExports() (map[string][]string, error) {
return exports, err
}

// RegexMatch matches stack name with the supplied regex so that we can filter desired stacks for deletion.
func (dm CFNManager) RegexMatch(stackName string) bool {
match, _ := regexp.MatchString(dm.StackPattern, stackName)
return match
}

// assumes staging nuke role
// Session creates a new aws cloudformation session.
// By default it uses given aws profile and region but it also provides option to assume a different role.
// It also has validation for target account id to ensure we are deleting in the correct aws account.
func (dm CFNManager) Session() (*cloudformation.CloudFormation, error) {
sess := session.Must(session.NewSessionWithOptions(session.Options{
Config: aws.Config{Region: aws.String(dm.AWSRegion)},
Expand Down Expand Up @@ -265,14 +282,15 @@ func (dm CFNManager) Session() (*cloudformation.CloudFormation, error) {
if dm.NukeRoleARN == "" {
// this means, we are using given aws profile
return cloudformation.New(sess), nil
} else {
// Create the credentials from AssumeRoleProvider if nuke role arn is provided
creds := stscreds.NewCredentials(sess, dm.NukeRoleARN)
// Create service client value configured for credentials from assumed role.
return cloudformation.New(sess, &aws.Config{Credentials: creds, MaxRetries: &AWS_SDK_MAX_RETRY}), nil
}

// Create the credentials from AssumeRoleProvider if nuke role arn is provided
creds := stscreds.NewCredentials(sess, dm.NukeRoleARN)
// Create service client value configured for credentials from assumed role.
return cloudformation.New(sess, &aws.Config{Credentials: creds, MaxRetries: &AWS_SDK_MAX_RETRY}), nil
}

// AWSSessionAccountID fetches account id from current aws session
func (dm CFNManager) AWSSessionAccountID(sess *session.Session) (acID string, err error) {
svc := sts.New(sess)
result, err := svc.GetCallerIdentity(&sts.GetCallerIdentityInput{})
Expand Down
Loading

0 comments on commit 1f8f6d2

Please sign in to comment.