diff --git a/cli/app.go b/cli/app.go index d983d5ce..3a2358c7 100644 --- a/cli/app.go +++ b/cli/app.go @@ -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") { diff --git a/common/types.go b/common/types.go index a6417e19..d76d2ee4 100644 --- a/common/types.go +++ b/common/types.go @@ -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"` @@ -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 } diff --git a/provider/aws/cloudformation.go b/provider/aws/cloudformation.go index ba255199..3a8a368c 100644 --- a/provider/aws/cloudformation.go +++ b/provider/aws/cloudformation.go @@ -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 { @@ -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 { diff --git a/workflows/purge_confirm.go b/workflows/purge_confirm.go index 3a9b680e..2dbf8740 100644 --- a/workflows/purge_confirm.go +++ b/workflows/purge_confirm.go @@ -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...) }