package deploy import ( "fmt" "strings" "github.com/aws-cloudformation/rain/cft" "github.com/aws-cloudformation/rain/cft/pkg" "github.com/aws-cloudformation/rain/internal/aws/cfn" "github.com/aws-cloudformation/rain/internal/aws/s3" "github.com/aws-cloudformation/rain/internal/config" "github.com/aws-cloudformation/rain/internal/console" "github.com/aws-cloudformation/rain/internal/console/spinner" "github.com/aws-cloudformation/rain/internal/ui" "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" "github.com/aws/smithy-go/ptr" ) func formatChangeSet(stackName, changeSetName string) string { status, err := cfn.GetChangeSet(stackName, changeSetName) if err != nil { panic(ui.Errorf(err, "error getting changeset '%s' for stack '%s'", changeSetName, stackName)) } out := strings.Builder{} out.WriteString(fmt.Sprintf("%s:\n", console.Yellow(fmt.Sprintf("Stack %s", ptr.ToString(status.StackName))))) // Non-stack resources for _, change := range status.Changes { if change.ResourceChange.ChangeSetId != nil { // Bunch up nested stacks to the end continue } line := fmt.Sprintf("%s %s", *change.ResourceChange.ResourceType, *change.ResourceChange.LogicalResourceId, ) switch change.ResourceChange.Action { case types.ChangeAction("Add"): out.WriteString(console.Green(" + " + line)) case types.ChangeAction("Modify"): out.WriteString(console.Blue(" > " + line)) case types.ChangeAction("Remove"): out.WriteString(console.Red(" - " + line)) } out.WriteString("\n") } // Nested stacks for _, change := range status.Changes { if change.ResourceChange.ChangeSetId == nil { continue } child := formatChangeSet("", ptr.ToString(change.ResourceChange.ChangeSetId)) parts := strings.SplitN(child, "\n", 2) header := parts[0] body := console.Grey(" (no changes in resources)\n") if len(parts) == 2 { body = parts[1] } switch change.ResourceChange.Action { case types.ChangeAction("Add"): out.WriteString(console.Green(" + " + header)) case types.ChangeAction("Modify"): out.WriteString(console.Blue(" > " + header)) case types.ChangeAction("Remove"): out.WriteString(console.Red(" - " + header)) } out.WriteString("\n") out.WriteString(ui.Indent(" ", body)) out.WriteString("\n") } return strings.TrimSpace(out.String()) } func PackageTemplate(fn string, yes bool) cft.Template { // Call RainBucket for side-effects in case we want to force bucket creation s3.RainBucket(yes) t, err := pkg.File(fn) if err != nil { panic(ui.Errorf(err, "error packaging template '%s'", fn)) } return t } func CheckStack(stackName string) (types.Stack, bool) { // Find out if stack exists already // If it does and it's not in a good state, offer to wait/delete stack, err := cfn.GetStack(stackName) stackExists := false if err == nil { config.Debugf("Stack exists") stackExists = true } spinner.Pause() if stackExists { switch { case stack.StackStatus == types.StackStatusRollbackComplete, stack.StackStatus == types.StackStatusReviewInProgress, stack.StackStatus == types.StackStatusCreateFailed: message := "Existing stack is empty; deleting it." fmt.Println(message) err := cfn.DeleteStack(stackName, "") if err != nil { panic(ui.Errorf(err, "unable to delete stack '%s'", stackName)) } status, _ := cfn.WaitForStackToSettle(stackName) if status != "DELETE_COMPLETE" { panic(fmt.Errorf("failed to delete stack '%s'", stackName)) } console.ClearLines(console.CountLines(message) + 1) fmt.Println("Deleted existing, empty stack.") stackExists = false case !strings.HasSuffix(string(stack.StackStatus), "_COMPLETE"): // Can't update panic(fmt.Errorf("stack '%s' could not be updated: %s", stackName, ui.ColouriseStatus(string(stack.StackStatus)))) } } spinner.Resume() return stack, stackExists }