Skip to content

Commit

Permalink
refactor purge to ensure all stacks are deleted for a namespace
Browse files Browse the repository at this point in the history
  • Loading branch information
cplee committed Oct 16, 2018
1 parent 40ce29b commit fa785ac
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 117 deletions.
1 change: 1 addition & 0 deletions cli/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func NewApp() *cli.App {
return nil
}
}
context.Config.DryRun = c.Bool("dryrun")

// Allow overriding the `DisableIAM` in config via `--disable-iam` or `-I`
if c.Bool("disable-iam") {
Expand Down
4 changes: 2 additions & 2 deletions common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Context struct {

// Config defines the structure of the yml file for the mu config
type Config struct {
DryRun bool `yaml:"-"`
Namespace string `yaml:"namespace,omitempty" validate:"validateAlphaNumericDash"`
Environments []Environment `yaml:"environments,omitempty"`
Service Service `yaml:"service,omitempty"`
Expand Down Expand Up @@ -512,8 +513,7 @@ func StringValue(v *string) string {
return ""
}

// StringRef returns the string pointer to the string passed in or
// "" if the pointer is nil.
// StringRef returns the string pointer to the string passed in
func StringRef(v string) *string {
return &v
}
Expand Down
8 changes: 6 additions & 2 deletions provider/aws/cloudformation.go
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ func (cfnMgr *cloudformationStackManager) ListStacks(stackType common.StackType,

var stacks []*common.Stack

log.Debugf("Searching for stacks of type '%s'", stackType)
log.Debugf("Searching for stacks of type '%s' in namespace '%s'", stackType, namespace)

err := cfnAPI.DescribeStacksPages(params,
func(page *cloudformation.DescribeStacksOutput, lastPage bool) bool {
Expand Down Expand Up @@ -641,7 +641,11 @@ func (cfnMgr *cloudformationStackManager) DeleteStack(stackName string) error {
log.Debugf("Deleting stack named '%s'", stackName)

_, err := cfnAPI.DeleteStack(params)
return err
if err != nil {
return err
}
cfnMgr.logInfo(" Deleted stack '%s'", stackName)
return nil
}

func writeTemplateAndConfig(cfnDirectory string, stackName string, templateBodyBytes *bytes.Buffer, parameters map[string]string) error {
Expand Down
224 changes: 111 additions & 113 deletions workflows/purge_confirm.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,150 +6,148 @@ import (
"github.com/stelligent/mu/common"
)

type purgeWorkflow struct{}
type purgeWorkflow struct {
context *common.Context
}

// NewPurge create a new workflow for purging mu resources
func NewPurge(ctx *common.Context) Executor {
workflow := new(purgeWorkflow)
workflow.context = ctx

iamCommonStackName := fmt.Sprintf("%s-iam-common", ctx.Config.Namespace)

return newPipelineExecutor(
workflow.terminatePipelines(ctx),
workflow.terminateEnvironments(ctx),
workflow.terminateRepos(ctx),
workflow.terminateApps(ctx),
workflow.terminateBuckets(ctx),
workflow.terminateIAM(ctx),
ctx.RolesetManager.UpsertCommonRoleset,
workflow.newStackStream(common.StackTypePipeline).foreach(workflow.terminatePipeline),
workflow.newStackStream(common.StackTypeEnv).foreach(workflow.terminateEnvironment),
workflow.newStackStream(common.StackTypeSchedule).foreach(workflow.deleteStack),
workflow.newStackStream(common.StackTypeService).foreach(workflow.deleteStack),
workflow.newStackStream(common.StackTypeDatabase).foreach(workflow.deleteStack),
workflow.newStackStream(common.StackTypeLoadBalancer).foreach(workflow.deleteStack),
workflow.newStackStream(common.StackTypeEnv).foreach(workflow.deleteStack),
workflow.newStackStream(common.StackTypeVpc).foreach(workflow.deleteStack),
workflow.newStackStream(common.StackTypeTarget).foreach(workflow.deleteStack),
workflow.newStackStream(common.StackTypeRepo).foreach(workflow.cleanupRepo, workflow.deleteStack),
workflow.newStackStream(common.StackTypeApp).foreach(workflow.deleteStack),
workflow.newStackStream(common.StackTypeBucket).foreach(workflow.cleanupBucket, workflow.deleteStack),
workflow.newStackStream(common.StackTypeIam).filter(excludeStackName(iamCommonStackName)).foreach(workflow.deleteStack),
workflow.terminateCommonRoleset(),
)
}

func (workflow *purgeWorkflow) terminateIAM(ctx *common.Context) Executor {
func (workflow *purgeWorkflow) terminatePipeline(stack *common.Stack) Executor {
return NewPipelineTerminator(workflow.context, stack.Tags["service"])
}
func (workflow *purgeWorkflow) terminateEnvironment(stack *common.Stack) Executor {
return NewEnvironmentsTerminator(workflow.context, []string{stack.Tags["environment"]})
}
func (workflow *purgeWorkflow) terminateCommonRoleset() Executor {
return func() error {
workflow.context.StackManager.AllowDataLoss(true)
return workflow.context.RolesetManager.DeleteCommonRoleset()
}
}
func excludeStackName(stackName string) stackFilter {
return func(stack *common.Stack) bool {
return stack.Name != stackName
}
}

func (workflow *purgeWorkflow) deleteStack(stack *common.Stack) Executor {
stackName := stack.Name
return func() error {
namespace := ctx.Config.Namespace
stacks, err := ctx.StackManager.ListStacks(common.StackTypeIam, namespace)
err := workflow.context.StackManager.DeleteStack(stackName)
if err != nil {
return err
}
executors := make([]Executor, 0)
for _, stack := range stacks {
stackName := stack.Name
if stackName != fmt.Sprintf("%s-iam-common", namespace) {
executors = append(executors, func() error {
err = ctx.StackManager.DeleteStack(stackName)
if err != nil {
return err
}
ctx.StackManager.AwaitFinalStatus(stackName)
return nil
})
}
}
iamExecutors := newParallelExecutor(executors...)
err = iamExecutors()
if err != nil {
return err
status := workflow.context.StackManager.AwaitFinalStatus(stackName)
if status != nil && !workflow.context.Config.DryRun {
return fmt.Errorf("Unable to delete stack '%s'", stackName)
}

ctx.StackManager.AllowDataLoss(true)
return ctx.RolesetManager.DeleteCommonRoleset()
return nil
}
}

func (workflow *purgeWorkflow) terminatePipelines(ctx *common.Context) Executor {
namespace := ctx.Config.Namespace
stacks, err := ctx.StackManager.ListStacks(common.StackTypePipeline, namespace)
if err != nil {
return newErrorExecutor(err)
}
executors := make([]Executor, 0)
for _, stack := range stacks {
executors = append(executors, NewPipelineTerminator(ctx, stack.Tags["service"]))
func (workflow *purgeWorkflow) cleanupBucket(stack *common.Stack) Executor {
bucketName := stack.Outputs["Bucket"]
return func() error {
return workflow.context.ArtifactManager.EmptyBucket(bucketName)
}
return newParallelExecutor(executors...)
}

func (workflow *purgeWorkflow) terminateEnvironments(ctx *common.Context) Executor {
stackLister := ctx.StackManager
namespace := ctx.Config.Namespace
stacks, err := stackLister.ListStacks(common.StackTypeEnv, namespace)
if err != nil {
return newErrorExecutor(err)
}
envNames := make([]string, 0)
for _, stack := range stacks {
envNames = append(envNames, stack.Tags["environment"])
func (workflow *purgeWorkflow) cleanupRepo(stack *common.Stack) Executor {
repoName := stack.Parameters["RepoName"]
return func() error {
return workflow.context.ClusterManager.DeleteRepository(repoName)
}
return NewEnvironmentsTerminator(ctx, envNames)
}

func (workflow *purgeWorkflow) terminateRepos(ctx *common.Context) Executor {
namespace := ctx.Config.Namespace
stacks, err := ctx.StackManager.ListStacks(common.StackTypeRepo, namespace)
if err != nil {
return newErrorExecutor(err)
}
executors := make([]Executor, 0)
for _, stack := range stacks {
stackName := stack.Name
repoName := stack.Parameters["RepoName"]
executors = append(executors, func() error {
err := ctx.ClusterManager.DeleteRepository(repoName)
if err != nil {
return err
}
err = ctx.StackManager.DeleteStack(stackName)
if err != nil {
return err
}
ctx.StackManager.AwaitFinalStatus(stackName)
return nil
})
type stackStream struct {
namespace string
stackType common.StackType
stackLister common.StackLister
filters []stackFilter
}

func (workflow *purgeWorkflow) newStackStream(stackType common.StackType) *stackStream {
return &stackStream{
namespace: workflow.context.Config.Namespace,
stackType: stackType,
stackLister: workflow.context.StackManager,
filters: make([]stackFilter, 0),
}
return newParallelExecutor(executors...)
}

func (workflow *purgeWorkflow) terminateApps(ctx *common.Context) Executor {
namespace := ctx.Config.Namespace
stacks, err := ctx.StackManager.ListStacks(common.StackTypeApp, namespace)
if err != nil {
return newErrorExecutor(err)
type stackExecutor func(stack *common.Stack) Executor
type stackFilter func(stack *common.Stack) bool

func (stream *stackStream) filter(filter stackFilter) *stackStream {
stream.filters = append(stream.filters, filter)
return stream
}

// Check if the filters for the stream to see if the stack should be included
func (stream *stackStream) included(stack *common.Stack) bool {
for _, filter := range stream.filters {
if !filter(stack) {
return false
}
}
return true
}

// Create a pipeline executor consiting of the resolved stackExecutors for a given stack
func applyStackExecutors(stack *common.Stack, stackExecutors ...stackExecutor) Executor {
executors := make([]Executor, 0)
for _, stack := range stacks {
stackName := stack.Name
executors = append(executors, func() error {
err = ctx.StackManager.DeleteStack(stackName)
if err != nil {
return err
}
ctx.StackManager.AwaitFinalStatus(stackName)
return nil
})
for _, stackExecutor := range stackExecutors {
executor := stackExecutor(stack)
if executor != nil {
executors = append(executors, executor)
}
}
return newParallelExecutor(executors...)
return newPipelineExecutor(executors...)
}

func (workflow *purgeWorkflow) terminateBuckets(ctx *common.Context) Executor {
namespace := ctx.Config.Namespace
stacks, err := ctx.StackManager.ListStacks(common.StackTypeBucket, namespace)
if err != nil {
return newErrorExecutor(err)
}
executors := make([]Executor, 0)
for _, stack := range stacks {
stackName := stack.Name
bucketName := stack.Outputs["Bucket"]
executors = append(executors, func() error {
err := ctx.ArtifactManager.EmptyBucket(bucketName)
if err != nil {
return err
}
err = ctx.StackManager.DeleteStack(stackName)
if err != nil {
return err
// Create an executor that can iterate over all stacks and run executors against the stacks
func (stream *stackStream) foreach(stackExecutors ...stackExecutor) Executor {
return func() error {
log.Noticef("Purging '%s' stacks in namespace '%s'", stream.stackType, stream.namespace)
stacks, err := stream.stackLister.ListStacks(stream.stackType, stream.namespace)
if err != nil {
return err
}
executors := make([]Executor, 0)
for _, stack := range stacks {
if !stream.included(stack) {
log.Debugf("skipping %s", stack.Name)
continue
}
ctx.StackManager.AwaitFinalStatus(stackName)
return nil
})
log.Debugf("adding %s", stack.Name)

executors = append(executors, applyStackExecutors(stack, stackExecutors...))
}
executor := newParallelExecutor(executors...)
return executor()
}
return newParallelExecutor(executors...)
}

0 comments on commit fa785ac

Please sign in to comment.