From 6db25a98abd45f26d931f5b0b22f47e461eff15a Mon Sep 17 00:00:00 2001 From: Eric Beard Date: Mon, 26 Feb 2024 16:02:39 -0800 Subject: [PATCH 1/8] Added changeset to rain ls command --- internal/cmd/changeset/README.md | 13 ++++ internal/cmd/ls/ls.go | 120 +++++++++++++++++++++++++++---- internal/ui/colourise.go | 14 ++++ 3 files changed, 133 insertions(+), 14 deletions(-) create mode 100644 internal/cmd/changeset/README.md diff --git a/internal/cmd/changeset/README.md b/internal/cmd/changeset/README.md new file mode 100644 index 00000000..bdc42f5e --- /dev/null +++ b/internal/cmd/changeset/README.md @@ -0,0 +1,13 @@ +# Changeset + +https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-changesets.html + +The `rain deploy` command creates and executes a changeset under the hood, but +doesn't allow you to directly interact with it. The `rain changeset` command in +combination with `rain ls` provide full access to changeset functionality. + +To list all changesets or show details about a single changeset, use the `rain ls` command. + +To create or update a changeset, use the `rain changeset` command. + + diff --git a/internal/cmd/ls/ls.go b/internal/cmd/ls/ls.go index cf8f2f16..bb12d870 100644 --- a/internal/cmd/ls/ls.go +++ b/internal/cmd/ls/ls.go @@ -16,29 +16,117 @@ import ( ) var all = false +var changeset = false // Cmd is the ls command's entrypoint var Cmd = &cobra.Command{ - Use: "ls ", - Short: "List running CloudFormation stacks", - Long: "Displays a list of all running stacks or the contents of if provided.", - Args: cobra.MaximumNArgs(1), + Use: "ls [changeset]", + Short: "List running CloudFormation stacks or changesets", + Long: "Displays a list of all running stacks or the contents of if provided. If the -c arg is supplied, operates on changesets instead of stacks", + Args: cobra.MaximumNArgs(2), Aliases: []string{"list"}, DisableFlagsInUseLine: true, Run: func(cmd *cobra.Command, args []string) { - if len(args) == 1 { - stackName := args[0] + if len(args) > 0 { - spinner.Push("Fetching stack status") - stack, err := cfn.GetStack(stackName) - if err != nil { - panic(ui.Errorf(err, "failed to list stack '%s'", stackName)) - } + if changeset { + if len(args) != 2 { + panic("Usage: rain ls -c stackName changeSetName") + } + stackName := args[0] + changeSetName := args[1] + spinner.Push("Fetching changeset details") + cs, err := cfn.GetChangeSet(stackName, changeSetName) + if err != nil { + panic(ui.Errorf(err, "failed to get changeset '%s'", changeSetName)) + } + out := "" + // TODO + + out += fmt.Sprintf("Arn: %v\n", *cs.ChangeSetId) + out += fmt.Sprintf("Created: %v\n", cs.CreationTime) + descr := "" + if cs.Description != nil { + descr = *cs.Description + } + out += fmt.Sprintf("Description: %v\n", descr) + reason := "" + if cs.StatusReason != nil { + reason = "(" + *cs.StatusReason + ")" + } + out += fmt.Sprintf("Status: %v/%v %v\n", + ui.ColouriseStatus(string(cs.ExecutionStatus)), + ui.ColouriseStatus(string(cs.Status)), + reason) + out += "Parameters: " + if len(cs.Parameters) == 0 { + out += "(None)\n" + } else { + out += "\n" + } + for _, p := range cs.Parameters { + k, v := "", "" + if p.ParameterKey != nil { + k = *p.ParameterKey + } + if p.ParameterValue != nil { + v = *p.ParameterValue + } + out += fmt.Sprintf(" %s: %s", k, v) + } + // TODO: Convert changes to table + out += "Changes: \n" + for _, csch := range cs.Changes { + if csch.ResourceChange == nil { + continue + } + change := csch.ResourceChange + rid := "" + if change.LogicalResourceId != nil { + rid = *change.LogicalResourceId + } + rt := "" + if change.ResourceType != nil { + rt = *change.ResourceType + } + pid := "" + if change.PhysicalResourceId != nil { + pid = *change.PhysicalResourceId + } + replace := "" + switch string(change.Replacement) { + case "True": + replace = " [Replace]" + case "Conditional": + replace = " [Might replace]" + } + out += fmt.Sprintf("%s%s: %s (%s) %s\n", + string(change.Action), + replace, + rid, + rt, + pid) + + } + // TODO: Paging - output := cfn.GetStackSummary(stack, all) - spinner.Pop() + spinner.Pop() - fmt.Println(output) + fmt.Println(out) + + } else { + stackName := args[0] + spinner.Push("Fetching stack status") + stack, err := cfn.GetStack(stackName) + if err != nil { + panic(ui.Errorf(err, "failed to list stack '%s'", stackName)) + } + + output := cfn.GetStackSummary(stack, all) + spinner.Pop() + + fmt.Println(output) + } } else { var err error regions := []string{aws.Config().Region} @@ -55,6 +143,9 @@ var Cmd = &cobra.Command{ origRegion := aws.Config().Region for _, region := range regions { + + // TODO - if changeset + spinner.Push(fmt.Sprintf("Fetching stacks in %s", region)) aws.SetRegion(region) stacks, err := cfn.ListStacks() @@ -95,4 +186,5 @@ var Cmd = &cobra.Command{ func init() { Cmd.Flags().BoolVarP(&all, "all", "a", false, "list stacks in all regions; if you specify a stack, show more details") + Cmd.Flags().BoolVarP(&changeset, "changeset", "c", false, "List changesets instead of stacks") } diff --git a/internal/ui/colourise.go b/internal/ui/colourise.go index 5a467b79..bba225ab 100644 --- a/internal/ui/colourise.go +++ b/internal/ui/colourise.go @@ -61,16 +61,30 @@ func MapStatus(status string) *StatusRep { rep.Category = Complete case strings.HasSuffix(status, "CANCELLED"): + fallthrough case strings.HasSuffix(status, "OUTDATED"): rep.Category = Cancelled case strings.HasSuffix(status, "FAILED"): + fallthrough case strings.HasSuffix(status, "INOPERABLE"): rep.Category = Failed case strings.HasSuffix(status, "RUNNING"): rep.Category = InProgress + // changesets + case strings.HasSuffix(status, "CREATE_COMPLETE"): + fallthrough + case strings.HasSuffix(status, "AVAILABLE"): + rep.Category = Complete + case strings.HasSuffix(status, "OBSOLETE"): + fallthrough + case strings.HasSuffix(status, "UNAVAILABLE"): + rep.Category = Cancelled + case strings.HasSuffix(status, "CREATE_IN_PROGRESS"): + rep.Category = InProgress + default: rep.Category = Pending } From 67d4a71b350d7d45ffc670812952af658caa0907 Mon Sep 17 00:00:00 2001 From: Eric Beard Date: Mon, 26 Feb 2024 16:58:04 -0800 Subject: [PATCH 2/8] ls -c --- internal/aws/cfn/cfn.go | 27 ++++++++++++++++++ internal/cmd/ls/ls.go | 63 +++++++++++++++++++++++++++++++---------- 2 files changed, 75 insertions(+), 15 deletions(-) diff --git a/internal/aws/cfn/cfn.go b/internal/aws/cfn/cfn.go index 21fdab01..56be3adf 100644 --- a/internal/aws/cfn/cfn.go +++ b/internal/aws/cfn/cfn.go @@ -119,6 +119,33 @@ func StackExists(stackName string) (bool, error) { return false, nil } +// List the active change sets associated with a stack +func ListChangeSets(stackName string) ([]types.ChangeSetSummary, error) { + var token *string + retval := make([]types.ChangeSetSummary, 0) + for { + res, err := getClient().ListChangeSets(context.Background(), &cloudformation.ListChangeSetsInput{ + StackName: &stackName, + NextToken: token, + }) + + if err != nil { + return retval, nil + } + + retval = append(retval, res.Summaries...) + + if res.NextToken == nil { + break + } + + token = res.NextToken + } + + return retval, nil + +} + // ListStacks returns a list of all existing stacks func ListStacks() ([]types.StackSummary, error) { stacks := make([]types.StackSummary, 0) diff --git a/internal/cmd/ls/ls.go b/internal/cmd/ls/ls.go index bb12d870..8dfb3cd6 100644 --- a/internal/cmd/ls/ls.go +++ b/internal/cmd/ls/ls.go @@ -4,6 +4,7 @@ import ( "fmt" "sort" + "github.com/aws-cloudformation/rain/internal/config" "github.com/aws-cloudformation/rain/internal/ui" "github.com/aws-cloudformation/rain/internal/aws" @@ -41,8 +42,6 @@ var Cmd = &cobra.Command{ panic(ui.Errorf(err, "failed to get changeset '%s'", changeSetName)) } out := "" - // TODO - out += fmt.Sprintf("Arn: %v\n", *cs.ChangeSetId) out += fmt.Sprintf("Created: %v\n", cs.CreationTime) descr := "" @@ -144,8 +143,6 @@ var Cmd = &cobra.Command{ for _, region := range regions { - // TODO - if changeset - spinner.Push(fmt.Sprintf("Fetching stacks in %s", region)) aws.SetRegion(region) stacks, err := cfn.ListStacks() @@ -159,19 +156,55 @@ var Cmd = &cobra.Command{ } stackNames := make(sort.StringSlice, 0) - stackMap := make(map[string]types.StackSummary) - for _, stack := range stacks { - stackNames = append(stackNames, *stack.StackName) - stackMap[*stack.StackName] = stack - } - sort.Strings(stackNames) - fmt.Println(console.Yellow(fmt.Sprintf("CloudFormation stacks in %s:", region))) - for _, stackName := range stackNames { - stack := stackMap[stackName] + // For changesets, we need to now call ListChangeSets for + // each stack and see if it has any active changesets + if changeset { + out := "" + for _, stack := range stacks { + if stack.StackName == nil { + continue + } + config.Debugf("Checking stack %s", *stack.StackName) + + sets, err := cfn.ListChangeSets(*stack.StackName) + if err != nil { + panic(err) + } + + if len(sets) == 0 { + continue + } + + out += fmt.Sprintf("Stack: %s\n", *stack.StackName) + + for _, cs := range sets { + if cs.ChangeSetName == nil { + continue + } + out += fmt.Sprintf(" %s\n", *cs.ChangeSetName) + } + + } + + fmt.Println(out) + + } else { + + stackMap := make(map[string]types.StackSummary) + for _, stack := range stacks { + stackNames = append(stackNames, *stack.StackName) + stackMap[*stack.StackName] = stack + } + sort.Strings(stackNames) + + fmt.Println(console.Yellow(fmt.Sprintf("CloudFormation stacks in %s:", region))) + for _, stackName := range stackNames { + stack := stackMap[stackName] - if stack.ParentId == nil { - fmt.Println(ui.Indent(" ", formatStack(stack, stackMap))) + if stack.ParentId == nil { + fmt.Println(ui.Indent(" ", formatStack(stack, stackMap))) + } } } } From ce6f0a735fb6f7a8cab5aa67e960e802ac4689ec Mon Sep 17 00:00:00 2001 From: Eric Beard Date: Tue, 27 Feb 2024 14:23:55 -0800 Subject: [PATCH 3/8] ls changesets --- internal/aws/cfn/mock.go | 4 +++ internal/cmd/ls/ls.go | 56 ++++++++++++++++++++++------------ internal/cmd/ls/ls_test.go | 9 +++--- internal/cmd/rain/rain_test.go | 2 +- 4 files changed, 47 insertions(+), 24 deletions(-) diff --git a/internal/aws/cfn/mock.go b/internal/aws/cfn/mock.go index 7d183bfd..531382c4 100644 --- a/internal/aws/cfn/mock.go +++ b/internal/aws/cfn/mock.go @@ -453,3 +453,7 @@ func ListResourceTypes() ([]string, error) { func IsCCAPI(t string) (bool, error) { return true, nil } + +func ListChangeSets(stackName string) ([]types.ChangeSetSummary, error) { + return make([]types.ChangeSetSummary, 0), nil +} diff --git a/internal/cmd/ls/ls.go b/internal/cmd/ls/ls.go index 8dfb3cd6..f0938347 100644 --- a/internal/cmd/ls/ls.go +++ b/internal/cmd/ls/ls.go @@ -19,6 +19,29 @@ import ( var all = false var changeset = false +func ShowChangeSetsForStack(stackName string) error { + sets, err := cfn.ListChangeSets(stackName) + if err != nil { + return err + } + + if len(sets) == 0 { + return nil + } + + for _, cs := range sets { + if cs.ChangeSetName == nil { + continue + } + fmt.Printf(" %s %v/%v\n", + *cs.ChangeSetName, + ui.ColouriseStatus(string(cs.ExecutionStatus)), + ui.ColouriseStatus(string(cs.Status))) + } + + return nil +} + // Cmd is the ls command's entrypoint var Cmd = &cobra.Command{ Use: "ls [changeset]", @@ -31,9 +54,12 @@ var Cmd = &cobra.Command{ if len(args) > 0 { if changeset { + // Get the status of a single changeset + if len(args) != 2 { panic("Usage: rain ls -c stackName changeSetName") } + stackName := args[0] changeSetName := args[1] spinner.Push("Fetching changeset details") @@ -71,7 +97,7 @@ var Cmd = &cobra.Command{ if p.ParameterValue != nil { v = *p.ParameterValue } - out += fmt.Sprintf(" %s: %s", k, v) + out += fmt.Sprintf(" %s: %s\n", k, v) } // TODO: Convert changes to table out += "Changes: \n" @@ -107,13 +133,13 @@ var Cmd = &cobra.Command{ pid) } - // TODO: Paging spinner.Pop() fmt.Println(out) } else { + // Get the status for a single stack stackName := args[0] spinner.Push("Fetching stack status") stack, err := cfn.GetStack(stackName) @@ -125,8 +151,15 @@ var Cmd = &cobra.Command{ spinner.Pop() fmt.Println(output) + fmt.Println(console.Yellow(" ChangeSets:")) + err = ShowChangeSetsForStack(*stack.StackName) + if err != nil { + panic(err) + } } } else { + // List all stacks or changesets + var err error regions := []string{aws.Config().Region} @@ -160,35 +193,20 @@ var Cmd = &cobra.Command{ // For changesets, we need to now call ListChangeSets for // each stack and see if it has any active changesets if changeset { - out := "" for _, stack := range stacks { if stack.StackName == nil { continue } config.Debugf("Checking stack %s", *stack.StackName) - sets, err := cfn.ListChangeSets(*stack.StackName) + fmt.Printf("Stack: %s\n", *stack.StackName) + err := ShowChangeSetsForStack(*stack.StackName) if err != nil { panic(err) } - if len(sets) == 0 { - continue - } - - out += fmt.Sprintf("Stack: %s\n", *stack.StackName) - - for _, cs := range sets { - if cs.ChangeSetName == nil { - continue - } - out += fmt.Sprintf(" %s\n", *cs.ChangeSetName) - } - } - fmt.Println(out) - } else { stackMap := make(map[string]types.StackSummary) diff --git a/internal/cmd/ls/ls_test.go b/internal/cmd/ls/ls_test.go index 48e8799e..6867ffd3 100644 --- a/internal/cmd/ls/ls_test.go +++ b/internal/cmd/ls/ls_test.go @@ -14,15 +14,16 @@ func Example_ls_help() { ls.Cmd.Execute() // Output: - // Displays a list of all running stacks or the contents of if provided. + // Displays a list of all running stacks or the contents of if provided. If the -c arg is supplied, operates on changesets instead of stacks // // Usage: - // ls + // ls [changeset] // // Aliases: // ls, list // // Flags: - // -a, --all list stacks in all regions; if you specify a stack, show more details - // -h, --help help for ls + // -a, --all list stacks in all regions; if you specify a stack, show more details + // -c, --changeset List changesets instead of stacks + // -h, --help help for ls } diff --git a/internal/cmd/rain/rain_test.go b/internal/cmd/rain/rain_test.go index ded5accb..f2ea99c6 100644 --- a/internal/cmd/rain/rain_test.go +++ b/internal/cmd/rain/rain_test.go @@ -23,7 +23,7 @@ func Example_rainhelp() { // cc Interact with templates using Cloud Control API instead of CloudFormation // deploy Deploy a CloudFormation stack from a local template // logs Show the event log for the named stack - // ls List running CloudFormation stacks + // ls List running CloudFormation stacks or changesets // rm Delete a running CloudFormation stack // stackset This command manipulates stack sets. // watch Display an updating view of a CloudFormation stack From 2b32052e035695cf64c469fb677c66e239b34f25 Mon Sep 17 00:00:00 2001 From: Eric Beard Date: Tue, 27 Feb 2024 15:06:41 -0800 Subject: [PATCH 4/8] rm changesets --- internal/cmd/changeset/README.md | 4 ++- internal/cmd/ls/ls.go | 3 +- internal/cmd/rain/rain_test.go | 2 +- internal/cmd/rm/rm.go | 56 +++++++++++++++++++++++++------- internal/cmd/rm/rm_test.go | 5 +-- test/func_test.go | 2 ++ 6 files changed, 55 insertions(+), 17 deletions(-) diff --git a/internal/cmd/changeset/README.md b/internal/cmd/changeset/README.md index bdc42f5e..0944824d 100644 --- a/internal/cmd/changeset/README.md +++ b/internal/cmd/changeset/README.md @@ -4,10 +4,12 @@ https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updatin The `rain deploy` command creates and executes a changeset under the hood, but doesn't allow you to directly interact with it. The `rain changeset` command in -combination with `rain ls` provide full access to changeset functionality. +combination with `rain ls` and `rain rm` provide full access to changeset functionality. To list all changesets or show details about a single changeset, use the `rain ls` command. To create or update a changeset, use the `rain changeset` command. +To delete a changeset, use the `rain rm` command. + diff --git a/internal/cmd/ls/ls.go b/internal/cmd/ls/ls.go index f0938347..bd76c993 100644 --- a/internal/cmd/ls/ls.go +++ b/internal/cmd/ls/ls.go @@ -29,6 +29,7 @@ func ShowChangeSetsForStack(stackName string) error { return nil } + fmt.Printf(" %s\n", stackName) for _, cs := range sets { if cs.ChangeSetName == nil { continue @@ -193,13 +194,13 @@ var Cmd = &cobra.Command{ // For changesets, we need to now call ListChangeSets for // each stack and see if it has any active changesets if changeset { + fmt.Println(console.Yellow(fmt.Sprintf("Stacks with changesets in %s:", region))) for _, stack := range stacks { if stack.StackName == nil { continue } config.Debugf("Checking stack %s", *stack.StackName) - fmt.Printf("Stack: %s\n", *stack.StackName) err := ShowChangeSetsForStack(*stack.StackName) if err != nil { panic(err) diff --git a/internal/cmd/rain/rain_test.go b/internal/cmd/rain/rain_test.go index f2ea99c6..826154da 100644 --- a/internal/cmd/rain/rain_test.go +++ b/internal/cmd/rain/rain_test.go @@ -24,7 +24,7 @@ func Example_rainhelp() { // deploy Deploy a CloudFormation stack from a local template // logs Show the event log for the named stack // ls List running CloudFormation stacks or changesets - // rm Delete a running CloudFormation stack + // rm Delete a CloudFormation stack or changeset // stackset This command manipulates stack sets. // watch Display an updating view of a CloudFormation stack // diff --git a/internal/cmd/rm/rm.go b/internal/cmd/rm/rm.go index 09de688f..9813bc11 100644 --- a/internal/cmd/rm/rm.go +++ b/internal/cmd/rm/rm.go @@ -8,44 +8,79 @@ import ( "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/spf13/cobra" ) var yes bool var detach bool var roleArn string +var changeset bool + +func DeleteChangeSet(stack *types.Stack, changeSetName string) error { + if !yes { + spinner.Push("Fetching changeset details") + cs, err := cfn.GetChangeSet(*stack.StackName, changeSetName) + if err != nil { + panic(ui.Errorf(err, "failed to get changeset '%s'", changeSetName)) + } + spinner.Pop() + fmt.Printf("Arn: %v\n", *cs.ChangeSetId) + fmt.Printf("Created: %v\n", cs.CreationTime) + fmt.Printf("Status: %v/%v\n", + ui.ColouriseStatus(string(cs.ExecutionStatus)), + ui.ColouriseStatus(string(cs.Status))) + + fmt.Println() + if !console.Confirm(false, "Are you sure you want to delete this changeset?") { + panic(fmt.Errorf("user cancelled deletion of changeset '%s'", *cs.ChangeSetName)) + } + } + return cfn.DeleteChangeSet(*stack.StackName, changeSetName) +} // Cmd is the rm command's entrypoint var Cmd = &cobra.Command{ - Use: "rm ", - Short: "Delete a running CloudFormation stack", - Long: "Deletes the CloudFormation stack named and waits for the action to complete.", - Args: cobra.ExactArgs(1), + Use: "rm [changeset]", + Short: "Delete a CloudFormation stack or changeset", + Long: "Deletes the CloudFormation stack named and waits for the action to complete. With -c, deletes a changeset named [changeset].", + Args: cobra.MaximumNArgs(2), Aliases: []string{"remove", "del", "delete"}, DisableFlagsInUseLine: true, Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + panic("at least one argument is required") + } stackName := args[0] spinner.Push("Fetching stack status") stack, err := cfn.GetStack(stackName) if err != nil { - panic(ui.Errorf(err, "unable to delete stack '%s'", stackName)) + panic(ui.Errorf(err, "unable to get stack '%s'", stackName)) + } + spinner.Pop() + + if changeset { + if len(args) != 2 { + panic("expected 2 arguments: stackName changeSetName") + } + if err := DeleteChangeSet(&stack, args[1]); err != nil { + panic(err) + } + return } if !yes { output, _ := cfn.GetStackOutput(stack) - spinner.Pause() fmt.Println(output) if !console.Confirm(false, "Are you sure you want to delete this stack?") { panic(fmt.Errorf("user cancelled deletion of stack '%s'", stackName)) } - spinner.Resume() } if *stack.EnableTerminationProtection { - spinner.Pause() if yes || console.Confirm(false, "This stack has termination protection enabled. Do you wish to disable it?") { spinner.Push("Disabling termination protection") @@ -56,12 +91,8 @@ var Cmd = &cobra.Command{ } else { panic(fmt.Errorf("user cancelled deletion of stack '%s'", stackName)) } - - spinner.Resume() } - spinner.Pop() - err = cfn.DeleteStack(stackName, roleArn) if err != nil { panic(ui.Errorf(err, "unable to delete stack '%s'", stackName)) @@ -96,4 +127,5 @@ func init() { Cmd.Flags().BoolVarP(&detach, "detach", "d", false, "once removal has started, don't wait around for it to finish") Cmd.Flags().BoolVarP(&yes, "yes", "y", false, "don't ask questions; just delete") Cmd.Flags().StringVar(&roleArn, "role-arn", "", "ARN of an IAM role that CloudFormation should assume to remove the stack") + Cmd.Flags().BoolVarP(&changeset, "changeset", "c", false, "delete a changeset") } diff --git a/internal/cmd/rm/rm_test.go b/internal/cmd/rm/rm_test.go index 868eb61d..cd38afe3 100644 --- a/internal/cmd/rm/rm_test.go +++ b/internal/cmd/rm/rm_test.go @@ -14,15 +14,16 @@ func Example_rm_help() { rm.Cmd.Execute() // Output: - // Deletes the CloudFormation stack named and waits for the action to complete. + // Deletes the CloudFormation stack named and waits for the action to complete. With -c, deletes a changeset named [changeset]. // // Usage: - // rm + // rm [changeset] // // Aliases: // rm, remove, del, delete // // Flags: + // -c, --changeset delete a changeset // -d, --detach once removal has started, don't wait around for it to finish // -h, --help help for rm // --role-arn string ARN of an IAM role that CloudFormation should assume to remove the stack diff --git a/test/func_test.go b/test/func_test.go index 799000dc..277afbd8 100644 --- a/test/func_test.go +++ b/test/func_test.go @@ -133,6 +133,7 @@ Resources: }, `Stack success: CREATE_COMPLETE Outputs: MockKey: Mock value # Mock output description (exported as MockExport) + ChangeSets: `, "", 0) // List full stack @@ -148,6 +149,7 @@ Resources: MockPhysicalId Outputs: MockKey: Mock value # Mock output description (exported as MockExport) + ChangeSets: `, "", 0) // Watch stack From 7d4f4ae458d76bb8216475b69b03ce9dac026d22 Mon Sep 17 00:00:00 2001 From: Eric Beard Date: Wed, 28 Feb 2024 16:21:39 -0800 Subject: [PATCH 5/8] Create and execute changesets --- internal/cmd/deploy/deploy.go | 152 +++++++++++++++++++---------- internal/cmd/deploy/deploy_test.go | 61 ------------ internal/cmd/ls/ls.go | 4 +- internal/cmd/rain/rain_test.go | 2 +- 4 files changed, 101 insertions(+), 118 deletions(-) diff --git a/internal/cmd/deploy/deploy.go b/internal/cmd/deploy/deploy.go index 7d9c1c56..645d1d65 100644 --- a/internal/cmd/deploy/deploy.go +++ b/internal/cmd/deploy/deploy.go @@ -11,6 +11,7 @@ import ( "github.com/aws-cloudformation/rain/internal/console/spinner" "github.com/aws-cloudformation/rain/internal/dc" "github.com/aws-cloudformation/rain/internal/ui" + "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" "github.com/spf13/cobra" ) @@ -24,12 +25,15 @@ var terminationProtection bool var keep bool var roleArn string var ignoreUnknownParams bool +var noexec bool +var changeset bool // Cmd is the deploy command's entrypoint var Cmd = &cobra.Command{ Use: "deploy