Repository: aws-cloudformation/rain
Pull request #238 (2024-01-13 17:15:33)

Display the config file (Parameters and Tags) of an existing stack #237


*Issue #237*

*Description of changes:*
* `--config` flag added to the cat command, with a bit of documentation
* new method `ConfigFromStack(stack types.Stack)` added into the dc package , it returns a yaml string containing the tags and parameters of the given stack
    * tests added for the new method.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.



Patch:
diff --git a/docs/bash_completion.sh b/docs/bash_completion.sh
index 366864f4..c2689ee9 100644
--- a/docs/bash_completion.sh
+++ b/docs/bash_completion.sh
@@ -459,6 +459,10 @@ _rain_cat()
     flags_with_completion=()
     flags_completion=()
 
+    flags+=("--config")
+    flags+=("-c")
+    local_nonpersistent_flags+=("--config")
+    local_nonpersistent_flags+=("-c")
     flags+=("--help")
     flags+=("-h")
     local_nonpersistent_flags+=("--help")
diff --git a/docs/index.md b/docs/index.md
index c3438246..a3f64a77 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -36,4 +36,4 @@ Rain is a command line tool for working with AWS CloudFormation templates and st
 * [rain tree](rain_tree.md)	 - Find dependencies of Resources and Outputs in a local template
 * [rain watch](rain_watch.md)	 - Display an updating view of a CloudFormation stack
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_bootstrap.md b/docs/rain_bootstrap.md
index d0dda6bb..9c35d8e1 100644
--- a/docs/rain_bootstrap.md
+++ b/docs/rain_bootstrap.md
@@ -30,4 +30,4 @@ rain bootstrap
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_build.md b/docs/rain_build.md
index 1c5658dc..60fad612 100644
--- a/docs/rain_build.md
+++ b/docs/rain_build.md
@@ -31,4 +31,4 @@ rain build [<resource type> or <prompt>]
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_cat.md b/docs/rain_cat.md
index 73b8a801..9ae9e5c4 100644
--- a/docs/rain_cat.md
+++ b/docs/rain_cat.md
@@ -4,7 +4,10 @@ Get the CloudFormation template from a running stack
 
 ### Synopsis
 
-Downloads the template used to deploy <stack> and prints it to stdout.
+Downloads the template or the configuration file used to deploy <stack> and prints it to stdout.
+
+The  `--config` flag can be used to get the rain config file for the stack instead of the template.
+
 
 ```
 rain cat <stack>
@@ -13,6 +16,7 @@ rain cat <stack>
 ### Options
 
 ```
+  -c, --config           output the config file for the existing stack
   -h, --help             help for cat
   -p, --profile string   AWS profile name; read from the AWS CLI configuration file
   -r, --region string    AWS region to use
@@ -31,4 +35,4 @@ rain cat <stack>
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_ccdeploy.md b/docs/rain_ccdeploy.md
index c5afa180..b28e7f21 100644
--- a/docs/rain_ccdeploy.md
+++ b/docs/rain_ccdeploy.md
@@ -37,4 +37,4 @@ rain ccdeploy <template> <name>
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_ccrm.md b/docs/rain_ccrm.md
index ab7d6de3..4d35b6f8 100644
--- a/docs/rain_ccrm.md
+++ b/docs/rain_ccrm.md
@@ -35,4 +35,4 @@ rain ccrm <name>
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_console.md b/docs/rain_console.md
index cdc3156a..9c15b024 100644
--- a/docs/rain_console.md
+++ b/docs/rain_console.md
@@ -38,4 +38,4 @@ rain console [stack]
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_deploy.md b/docs/rain_deploy.md
index 67d52a96..eba66548 100644
--- a/docs/rain_deploy.md
+++ b/docs/rain_deploy.md
@@ -70,4 +70,4 @@ rain deploy <template> [stack]
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_diff.md b/docs/rain_diff.md
index 3e7e4833..801bfef6 100644
--- a/docs/rain_diff.md
+++ b/docs/rain_diff.md
@@ -28,4 +28,4 @@ rain diff <from> <to>
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_fmt.md b/docs/rain_fmt.md
index f662ff1f..11129072 100644
--- a/docs/rain_fmt.md
+++ b/docs/rain_fmt.md
@@ -32,4 +32,4 @@ rain fmt <filename>...
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_forecast.md b/docs/rain_forecast.md
index 6a14c4cf..0c738ada 100644
--- a/docs/rain_forecast.md
+++ b/docs/rain_forecast.md
@@ -59,4 +59,4 @@ rain forecast --experimental <template> [stackName]
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_info.md b/docs/rain_info.md
index eac705e2..9f8aaf1c 100644
--- a/docs/rain_info.md
+++ b/docs/rain_info.md
@@ -30,4 +30,4 @@ rain info
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_logs.md b/docs/rain_logs.md
index e7873eb9..b6b3f77a 100644
--- a/docs/rain_logs.md
+++ b/docs/rain_logs.md
@@ -36,4 +36,4 @@ rain logs <stack> (<resource>)
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_ls.md b/docs/rain_ls.md
index 2d4b69d5..204a4bf9 100644
--- a/docs/rain_ls.md
+++ b/docs/rain_ls.md
@@ -30,4 +30,4 @@ rain ls <stack>
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_merge.md b/docs/rain_merge.md
index 930bb047..71c23336 100644
--- a/docs/rain_merge.md
+++ b/docs/rain_merge.md
@@ -29,4 +29,4 @@ rain merge <template> <template> ...
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_pkg.md b/docs/rain_pkg.md
index 60272103..6ec733cf 100644
--- a/docs/rain_pkg.md
+++ b/docs/rain_pkg.md
@@ -66,4 +66,4 @@ rain pkg <template>
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_rm.md b/docs/rain_rm.md
index 12fb592a..0f759a80 100644
--- a/docs/rain_rm.md
+++ b/docs/rain_rm.md
@@ -32,4 +32,4 @@ rain rm <stack>
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_stackset.md b/docs/rain_stackset.md
index bcb9d1a3..31fd4992 100644
--- a/docs/rain_stackset.md
+++ b/docs/rain_stackset.md
@@ -28,4 +28,4 @@ This command manipulates stack sets. It has no action if specific stack set comm
 * [rain stackset ls](rain_stackset_ls.md)	 - List a CloudFormation stack sets in a given region
 * [rain stackset rm](rain_stackset_rm.md)	 - Delete a CloudFormation stack set and/or its instances.
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_stackset_deploy.md b/docs/rain_stackset_deploy.md
index 3ee665f5..ef7e3800 100644
--- a/docs/rain_stackset_deploy.md
+++ b/docs/rain_stackset_deploy.md
@@ -64,4 +64,4 @@ rain stackset deploy <template> [stackset] [flags]
 
 * [rain stackset](rain_stackset.md)	 - This command manipulates stack sets.
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_stackset_ls.md b/docs/rain_stackset_ls.md
index fd81bfb6..597a8004 100644
--- a/docs/rain_stackset_ls.md
+++ b/docs/rain_stackset_ls.md
@@ -30,4 +30,4 @@ rain stackset ls <stack set>
 
 * [rain stackset](rain_stackset.md)	 - This command manipulates stack sets.
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_stackset_rm.md b/docs/rain_stackset_rm.md
index 7cff1961..c8703f76 100644
--- a/docs/rain_stackset_rm.md
+++ b/docs/rain_stackset_rm.md
@@ -30,4 +30,4 @@ rain stackset rm <stackset>
 
 * [rain stackset](rain_stackset.md)	 - This command manipulates stack sets.
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_tree.md b/docs/rain_tree.md
index 7787c025..d719e881 100644
--- a/docs/rain_tree.md
+++ b/docs/rain_tree.md
@@ -30,4 +30,4 @@ rain tree [template]
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/docs/rain_watch.md b/docs/rain_watch.md
index 2d9f7b1c..688e82b1 100644
--- a/docs/rain_watch.md
+++ b/docs/rain_watch.md
@@ -30,4 +30,4 @@ rain watch <stack>
 
 * [rain](index.md)	 - 
 
-###### Auto generated by spf13/cobra on 13-Dec-2023
+###### Auto generated by spf13/cobra on 16-Jan-2024
diff --git a/internal/cmd/cat/cat.go b/internal/cmd/cat/cat.go
index 9683a1a0..520eff9d 100644
--- a/internal/cmd/cat/cat.go
+++ b/internal/cmd/cat/cat.go
@@ -2,29 +2,50 @@ package cat
 
 import (
 	"fmt"
-
 	"github.com/aws-cloudformation/rain/cft/format"
-	"github.com/aws-cloudformation/rain/internal/ui"
-
 	"github.com/aws-cloudformation/rain/cft/parse"
 	"github.com/aws-cloudformation/rain/internal/aws/cfn"
 	"github.com/aws-cloudformation/rain/internal/console/spinner"
+	"github.com/aws-cloudformation/rain/internal/dc"
+	"github.com/aws-cloudformation/rain/internal/ui"
 	"github.com/spf13/cobra"
 )
 
 var transformed = false
 var unformatted = false
+var config = false
 
 // Cmd is the cat command's entrypoint
 var Cmd = &cobra.Command{
-	Use:                   "cat <stack>",
-	Short:                 "Get the CloudFormation template from a running stack",
-	Long:                  "Downloads the template used to deploy <stack> and prints it to stdout.",
+	Use:   "cat <stack>",
+	Short: "Get the CloudFormation template from a running stack",
+	Long: `Downloads the template or the configuration file used to deploy <stack> and prints it to stdout.
+
+The  ` + "`" + `--config` + "`" + ` flag can be used to get the rain config file for the stack instead of the template.
+`,
 	Args:                  cobra.ExactArgs(1),
 	DisableFlagsInUseLine: true,
 	Run: func(cmd *cobra.Command, args []string) {
 		stackName := args[0]
 
+		// Output the config file if requested instead of the template
+		if config {
+			spinner.Push(fmt.Sprintf("Getting config from stack '%s'", stackName))
+			stack, err := cfn.GetStack(stackName)
+			if err != nil {
+				panic(ui.Errorf(err, "failed to get stack '%s'", stackName))
+			}
+			spinner.Pop()
+
+			deployedConfig, err := dc.ConfigFromStack(stack)
+			if err != nil {
+				panic(ui.Errorf(err, "unable to get configuration for stack : '%s'", stackName))
+			}
+			fmt.Print(deployedConfig)
+
+			return
+		}
+
 		spinner.Push(fmt.Sprintf("Getting template from stack '%s'", stackName))
 		template, err := cfn.GetStackTemplate(stackName, transformed)
 		if err != nil {
@@ -48,4 +69,5 @@ var Cmd = &cobra.Command{
 func init() {
 	Cmd.Flags().BoolVarP(&transformed, "transformed", "t", false, "get the template with transformations applied by CloudFormation")
 	Cmd.Flags().BoolVarP(&unformatted, "unformatted", "u", false, "output the template in its raw form; do not attempt to format it")
+	Cmd.Flags().BoolVarP(&config, "config", "c", false, "output the config file for the existing stack")
 }
diff --git a/internal/dc/dc.go b/internal/dc/dc.go
index c98772ae..42e6a726 100644
--- a/internal/dc/dc.go
+++ b/internal/dc/dc.go
@@ -183,6 +183,24 @@ func GetStackName(stackName string, base string) string {
 	return stackName
 }
 
+// ConfigFromStack returns a yaml string containing the tags and parameters of the given stack
+func ConfigFromStack(stack types.Stack) (string, error) {
+	configFile := &configFileFormat{
+		Parameters: make(map[string]string),
+		Tags:       make(map[string]string),
+	}
+
+	for _, tag := range stack.Tags {
+		configFile.Tags[*tag.Key] = *tag.Value
+	}
+	for _, parameter := range stack.Parameters {
+		configFile.Parameters[*parameter.ParameterKey] = *parameter.ParameterValue
+	}
+
+	configFileContent, err := yaml.Marshal(configFile)
+	return string(configFileContent), err
+}
+
 // GetDeployConfig populates an instance of DeployConfig based on user-supplied values
 func GetDeployConfig(
 	tags []string,


Test patch:
diff --git a/internal/cmd/cat/cat_test.go b/internal/cmd/cat/cat_test.go
index 0e3e12ea..5627b1ee 100644
--- a/internal/cmd/cat/cat_test.go
+++ b/internal/cmd/cat/cat_test.go
@@ -14,12 +14,15 @@ func Example_cat_help() {
 
 	cat.Cmd.Execute()
 	// Output:
-	// Downloads the template used to deploy <stack> and prints it to stdout.
+	// Downloads the template or the configuration file used to deploy <stack> and prints it to stdout.
+	//
+	// The  `--config` flag can be used to get the rain config file for the stack instead of the template.
 	//
 	// Usage:
 	//   cat <stack>
 	//
 	// Flags:
+	//   -c, --config        output the config file for the existing stack
 	//   -h, --help          help for cat
 	//   -t, --transformed   get the template with transformations applied by CloudFormation
 	//   -u, --unformatted   output the template in its raw form; do not attempt to format it
diff --git a/internal/dc/dc_test.go b/internal/dc/dc_test.go
index 508faccb..0c95a53d 100644
--- a/internal/dc/dc_test.go
+++ b/internal/dc/dc_test.go
@@ -1,9 +1,12 @@
-package dc_test
+package dc
 
 import (
+	"fmt"
+	"github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
+	"github.com/aws/smithy-go/ptr"
+	"strings"
 	"testing"
 
-	"github.com/aws-cloudformation/rain/internal/dc"
 	"github.com/google/go-cmp/cmp"
 )
 
@@ -21,10 +24,73 @@ func TestListToMap(t *testing.T) {
 	}
 
 	for _, testCase := range testCases {
-		actual := dc.ListToMap("test", testCase.input)
+		actual := ListToMap("test", testCase.input)
 
 		if d := cmp.Diff(testCase.expected, actual); d != "" {
 			t.Errorf(d)
 		}
 	}
 }
+
+func TestConfigFromStack(t *testing.T) {
+	// Prepare test cases with a stack name and a map of parameters and tags
+	testCases := []struct {
+		testCaseName     string            // The name of the stack
+		params           map[string]string // The expected parameters
+		tags             map[string]string // The expected tags
+		yamlString       string            // The expected yaml string
+		exactStringMatch bool              // If true, the config must match the yamlString exactly. Otherwise, the config is validated tag by tag, and param by param
+	}{
+		{"test-stack-one-tag-one-param", map[string]string{"Foo": "bar"}, map[string]string{"Baz": "quux"}, "Parameters:\n  Foo: bar\nTags:\n  Baz: quux\n", true},
+		{"test-stack-multiple-tags-and-params-1", map[string]string{"Foo": "bar", "Baz": "quux"}, map[string]string{"Mooz": "xyzzy", "Garply": "thud"}, "Parameters:\n  Foo: bar\n  Baz: quux\nTags:\n  Mooz: xyzzy\n  Garply: thud\n", false},
+		{"test-stack-multiple-tags-and-params-2", map[string]string{"Foo": "bar", "Baz": "quux", "Mooz": "xyzzy"}, map[string]string{"Garply": "thud"}, "Parameters:\n  Foo: bar\n  Baz: quux\n  Mooz: xyzzy\nTags:\n  Garply: thud\n", false},
+		{"test-stack-multiple-tags-and-params-3", map[string]string{"Foo": "bar", "Baz": "quux", "Mooz": "xyzzy", "Garply": "thud"}, map[string]string{}, "Parameters:\n  Foo: bar\n  Baz: quux\n  Mooz: xyzzy\n  Garply: thud\nTags: {}\n", false},
+		{"test-stack-with-no-tags", map[string]string{"Foo": "bar"}, map[string]string{}, "Parameters:\n  Foo: bar\nTags: {}\n", false},
+		{"test-stack-with-no-params", map[string]string{}, map[string]string{"Foo": "bar", "Baz": "quux", "Mooz": "xyzzy", "Garply": "thud"}, "Parameters: {}\nTags:\n  Foo: bar\n  Baz: quux\n  Mooz: xyzzy\n  Garply: thud\n", false},
+		{"test-stack-with-no-tags-and-params", map[string]string{}, map[string]string{}, "Parameters: {}\nTags: {}\n", true},
+	}
+	// Iterate over test cases
+	for _, testCase := range testCases {
+		// Create a stack with the given name, parameters and tags
+		stack := types.Stack{
+			Tags:       []types.Tag{},
+			Parameters: []types.Parameter{},
+		}
+		for key, value := range testCase.tags {
+			stack.Tags = append(stack.Tags, types.Tag{Key: ptr.String(key), Value: ptr.String(value)})
+		}
+		for key, value := range testCase.params {
+			stack.Parameters = append(stack.Parameters, types.Parameter{ParameterKey: ptr.String(key), ParameterValue: ptr.String(value)})
+		}
+
+		// Get the config from the stack
+		config, err := ConfigFromStack(stack)
+		if err != nil {
+			t.Errorf("case %s - expected no error, got '%s'", testCase.testCaseName, err)
+		}
+
+		// Check if the config matches the expected string
+		if testCase.exactStringMatch {
+			if config != testCase.yamlString {
+				t.Errorf("case %s - expected '%s', got '%s'", testCase.testCaseName, testCase.yamlString, config)
+			}
+		} else {
+			// For each tag in testcase, check if it is present in the config
+			for key, value := range testCase.tags {
+				expectedString := fmt.Sprintf("%s: %s", key, value)
+				if strings.Contains(config, fmt.Sprintf("%s: %s", key, value)) == false {
+					t.Errorf("case %s - expected '%s' in '%s'", testCase.testCaseName, expectedString, testCase.yamlString)
+				}
+			}
+
+			// For each param in testcase, check if it is present in the config
+			for key, value := range testCase.params {
+				expectedString := fmt.Sprintf("%s: %s", key, value)
+				if strings.Contains(config, expectedString) == false {
+					t.Errorf("case %s - expected '%s' in '%s'", testCase.testCaseName, expectedString, testCase.yamlString)
+				}
+			}
+		}
+
+	}
+}