Repository: aws-cloudformation/rain
Pull request #92 (2022-12-06 00:35:19)

Stacksets
This PR introduces StackSets support to rain.


Patch:
diff --git a/.gitignore b/.gitignore
index a27cf611..19bb77f0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,26 @@
+# If you prefer the allow list template instead of the deny list, see community template:
+# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
+#
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+vendor/
+
+# Go workspace file
+go.work
+
+.vscode/
 main
 rain
 local/
diff --git a/README.md b/README.md
index 630b0471..b4987063 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,8 @@ Key features:
 
 * **Build new CloudFormation templates**: `rain build` generates new CloudFormation templates containing skeleton resources that you specify. This saves you having to look up which properties are available and which are required vs. optional.
 
+* **Manipulate CloudFormation stack sets**: `rain stackset deploy` creates a new stackset, updates an existing one or adds a stack instance(s) to an existing stack set. You can list stack sets using `rain stackset ls`, review stack set details with `rain stackset ls <stack set name>` and delete stack set and\or its instances with `rain stackset rm <stack set name>`
+
 ## Getting started
 
 If you have [homebrew](https://brew.sh/) installed, `brew install rain`
@@ -39,6 +41,7 @@ Stack commands:
   logs        Show the event log for the named stack
   ls          List running CloudFormation stacks
   rm          Delete a running CloudFormation stack
+  stackset    This command allows to manipulate stack sets.
   watch       Display an updating view of a CloudFormation stack
 
 Template commands:
diff --git a/cft/format/json.go b/cft/format/json.go
index 4c01cade..75debf1b 100644
--- a/cft/format/json.go
+++ b/cft/format/json.go
@@ -80,3 +80,9 @@ func convertToJSON(in string) string {
 	}
 	return string(out)
 }
+
+// converts struct to a JSON formatted string
+func PrettyPrint(i interface{}) string {
+	s, _ := json.MarshalIndent(i, "", "\t")
+	return string(s)
+}
diff --git a/cmd/rain/main.go b/cmd/rain/main.go
old mode 100644
new mode 100755
diff --git a/go.mod b/go.mod
index 63972d35..a980fa27 100644
--- a/go.mod
+++ b/go.mod
@@ -13,18 +13,19 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/sts v1.16.3
 	github.com/aws/smithy-go v1.11.2
 	github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
-	github.com/google/go-cmp v0.5.7
+	github.com/google/go-cmp v0.5.8
 	github.com/google/uuid v1.3.0
 	github.com/gookit/color v1.5.0
 	github.com/nathan-fiscaletti/consolesize-go v0.0.0-20220204101620-317176b6684d
 	github.com/spf13/cobra v1.4.0
 	github.com/spf13/pflag v1.0.5
-	golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f
-	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
+	golang.org/x/sys v0.2.0
+	golang.org/x/term v0.2.0
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 )
 
 require (
+	github.com/aws/aws-sdk-go v1.44.151 // indirect
 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect
 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9 // indirect
@@ -40,7 +41,11 @@ require (
 	github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
+	golang.org/x/exp v0.0.0-20221230185412-738e83a70c30
+	golang.org/x/net v0.2.0 // indirect
+	golang.org/x/text v0.4.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 42d43e1c..2583ffcf 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,7 @@
 github.com/ake-persson/mapslice-json v0.0.0-20210720081907-22c8edf57807 h1:w3nrGk00TWs/4iZ3Q0k9c0vL0e/wRziArKU4e++d/nA=
 github.com/ake-persson/mapslice-json v0.0.0-20210720081907-22c8edf57807/go.mod h1:fGnnfniJiO/ajHAVHqMSUSL8sE9LmU9rzclCtoeB+y8=
+github.com/aws/aws-sdk-go v1.44.151 h1:2FrJZm3kTcyTtfpE7LEQT9XW+jkoi4KEvBhFWqHEZmo=
+github.com/aws/aws-sdk-go v1.44.151/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
 github.com/aws/aws-sdk-go-v2 v1.16.2 h1:fqlCk6Iy3bnCumtrLz9r3mJ/2gUT0pJ0wLFVIdWh+JA=
 github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU=
@@ -50,6 +52,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
 github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gookit/color v1.5.0 h1:1Opow3+BWDwqor78DcJkJCIwnkviFi+rrOANki9BUFw=
@@ -64,6 +68,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
 github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/nathan-fiscaletti/consolesize-go v0.0.0-20220204101620-317176b6684d h1:NqRhLdNVlozULwM1B3VaHhcXYSgrOAv8V5BE65om+1Q=
 github.com/nathan-fiscaletti/consolesize-go v0.0.0-20220204101620-317176b6684d/go.mod h1:cxIIfNMTwff8f/ZvRouvWYF6wOoO7nj99neWSx2q/Es=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -80,23 +86,34 @@ github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1z
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/exp v0.0.0-20221230185412-738e83a70c30 h1:m9O6OTJ627iFnN2JIWfdqlZCzneRO6EEBsHXI25P8ws=
+golang.org/x/exp v0.0.0-20221230185412-738e83a70c30/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -104,20 +121,33 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY=
 golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/aws/cfn/cfn.go b/internal/aws/cfn/cfn.go
index df89bbc0..540d9216 100644
--- a/internal/aws/cfn/cfn.go
+++ b/internal/aws/cfn/cfn.go
@@ -16,6 +16,7 @@ import (
 	"github.com/aws-cloudformation/rain/internal/aws"
 	"github.com/aws-cloudformation/rain/internal/aws/s3"
 	"github.com/aws-cloudformation/rain/internal/config"
+	"github.com/aws-cloudformation/rain/internal/console/spinner"
 	"github.com/aws/aws-sdk-go-v2/service/cloudformation"
 	"github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
 	"github.com/aws/smithy-go/ptr"
@@ -46,6 +47,8 @@ var liveStatuses = []types.StackStatus{
 	"IMPORT_ROLLBACK_COMPLETE",
 }
 
+const WAIT_PERIOD_IN_SECONDS = 2
+
 func checkTemplate(template cft.Template) (string, error) {
 	templateBody := format.String(template, format.Options{})
 
@@ -132,6 +135,91 @@ func ListStacks() ([]types.StackSummary, error) {
 	return stacks, nil
 }
 
+// ListStackSets returns a list of all existing stack sets
+func ListStackSets() ([]types.StackSetSummary, error) {
+	stackSets := make([]types.StackSetSummary, 0)
+
+	var token *string
+
+	for {
+		res, err := getClient().ListStackSets(context.Background(), &cloudformation.ListStackSetsInput{
+			NextToken: token,
+		})
+
+		if err != nil {
+			return stackSets, err
+		}
+
+		stackSets = append(stackSets, res.Summaries...)
+
+		if res.NextToken == nil {
+			break
+		}
+
+		token = res.NextToken
+	}
+
+	return stackSets, nil
+}
+
+// ListStackSetInstances returns a list of all stack set instances for a given stack set
+func ListStackSetInstances(stackSetName string) ([]types.StackInstanceSummary, error) {
+	instances := make([]types.StackInstanceSummary, 0)
+	var token *string
+
+	for {
+		res, err := getClient().ListStackInstances(context.Background(), &cloudformation.ListStackInstancesInput{
+			NextToken:    token,
+			StackSetName: &stackSetName,
+		})
+
+		if err != nil {
+			return instances, err
+		}
+
+		instances = append(instances, res.Summaries...)
+
+		if res.NextToken == nil {
+			break
+		}
+
+		token = res.NextToken
+	}
+
+	return instances, nil
+}
+
+// ListLast10StackSetOperations returns a list of last 10 operations for a given stack sets
+func ListLast10StackSetOperations(stackSetName string) ([]types.StackSetOperationSummary, error) {
+	stackOps := make([]types.StackSetOperationSummary, 0)
+
+	res, err := getClient().ListStackSetOperations(context.Background(), &cloudformation.ListStackSetOperationsInput{
+		MaxResults:   ptr.Int32(10),
+		StackSetName: &stackSetName,
+	})
+
+	if err != nil {
+		return stackOps, err
+	}
+
+	stackOps = append(stackOps, res.Summaries...)
+	return stackOps, nil
+}
+
+// GetStackSetOperationsResult  returns an operation result for a given stack sets operation id
+func GetStackSetOperationsResult(stackSetName *string, operationId *string) (*types.StackSetOperationResultSummary, error) {
+	res, err := getClient().ListStackSetOperationResults(context.Background(), &cloudformation.ListStackSetOperationResultsInput{
+		MaxResults:   ptr.Int32(1),
+		OperationId:  operationId,
+		StackSetName: stackSetName,
+	})
+
+	if err == nil && res != nil && len(res.Summaries) == 1 {
+		return &res.Summaries[0], err
+	}
+	return nil, nil
+}
+
 // DeleteStack deletes a stack
 func DeleteStack(stackName string, roleArn string) error {
 	input := &cloudformation.DeleteStackInput{
@@ -148,6 +236,62 @@ func DeleteStack(stackName string, roleArn string) error {
 	return err
 }
 
+// DeleteStackSet deletes a stack set
+func DeleteStackSet(stackSetName string) error {
+	_, err := getClient().DeleteStackSet(context.Background(), &cloudformation.DeleteStackSetInput{
+		StackSetName: &stackSetName,
+	})
+
+	return err
+}
+
+// DeleteAllStackSetInstances deletes all instances for a given stack set
+func DeleteAllStackSetInstances(stackSetName string, wait bool, retainStacks bool) error {
+	instances, err := ListStackSetInstances(stackSetName)
+	if err != nil {
+		fmt.Printf("Could not fetch instances for stack set '%s'", stackSetName)
+		return err
+	}
+	accounts := []string{}
+	regions := []string{}
+	for _, i := range instances {
+		if i.StackInstanceStatus.DetailedStatus != types.StackInstanceDetailedStatusRunning { //TODO: do we need to skipp RUNNING only?
+			accounts = append(accounts, *i.Account)
+			regions = append(regions, *i.Region)
+		}
+	}
+	return DeleteStackSetInstances(stackSetName, accounts, regions, wait, retainStacks)
+}
+
+// DeleteStackSetInstances deletes instances for a given stack set in specified accounts and regions
+func DeleteStackSetInstances(stackSetName string, accounts []string, regions []string, wait bool, retainStacks bool) error {
+	_, err := GetStackSet(stackSetName)
+	if err != nil {
+		fmt.Printf("Could not find stack set '%s'", stackSetName)
+		return err
+	}
+
+	var input = &cloudformation.DeleteStackInstancesInput{
+		Accounts:     UniqueStrings(accounts),
+		Regions:      UniqueStrings(regions),
+		RetainStacks: retainStacks,
+		StackSetName: &stackSetName,
+	}
+
+	res, err := getClient().DeleteStackInstances(context.Background(), input)
+	spinner.Pause()
+	if err != nil {
+		fmt.Print("error occurred while tried to delete instances")
+		return err
+	}
+	fmt.Printf("Submitted DELETE instances operation with ID: %s\n", *res.OperationId)
+	spinner.Resume()
+	if wait {
+		WaitUntilStackSetOperationCompleted(*res.OperationId, stackSetName)
+	}
+	return err
+}
+
 // SetTerminationProtection enables or disables termination protection for a stack
 func SetTerminationProtection(stackName string, protectionEnabled bool) error {
 	// Set termination protection
@@ -172,6 +316,19 @@ func GetStack(stackName string) (types.Stack, error) {
 	return res.Stacks[0], nil
 }
 
+// GetStackSet returns a cloudformation.StackSet
+func GetStackSet(stackSetName string) (*types.StackSet, error) {
+	// Get the stack properties
+	res, err := getClient().DescribeStackSet(context.Background(), &cloudformation.DescribeStackSetInput{
+		StackSetName: &stackSetName,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return res.StackSet, nil
+}
+
 // GetStackResources returns a list of the resources in the named stack
 func GetStackResources(stackName string) ([]types.StackResource, error) {
 	// Get the stack resources
@@ -237,7 +394,7 @@ func CreateChangeSet(template cft.Template, params []types.Parameter, tags map[s
 		ChangeSetType:       types.ChangeSetType(changeSetType),
 		ChangeSetName:       ptr.String(changeSetName),
 		StackName:           ptr.String(stackName),
-		Tags:                makeTags(tags),
+		Tags:                MakeTags(tags),
 		IncludeNestedStacks: ptr.Bool(true),
 		Parameters:          params,
 		Capabilities: []types.Capability{
@@ -281,7 +438,7 @@ func CreateChangeSet(template cft.Template, params []types.Parameter, tags map[s
 			break
 		}
 
-		time.Sleep(time.Second * 2)
+		time.Sleep(time.Second * WAIT_PERIOD_IN_SECONDS)
 	}
 
 	return changeSetName, nil
@@ -301,6 +458,186 @@ func GetChangeSet(stackName, changeSetName string) (*cloudformation.DescribeChan
 	return getClient().DescribeChangeSet(context.Background(), input)
 }
 
+// CreateStackSet creates stack set
+func CreateStackSet(conf StackSetConfig) (*string, error) {
+
+	templateBody, err := checkTemplate(conf.Template)
+	if err != nil {
+		return nil, errors.New("error occured while extracting template body")
+	}
+
+	_, err = GetStackSet(conf.StackSetName)
+	if err == nil {
+		return nil, errors.New("can't create stack set. It already exists")
+	}
+
+	input := &cloudformation.CreateStackSetInput{
+		StackSetName:          &conf.StackSetName,
+		Parameters:            conf.Parameters,
+		Tags:                  conf.Tags,
+		Capabilities:          conf.Capabilities,
+		Description:           conf.Description,
+		AdministrationRoleARN: conf.AdministrationRoleARN,
+		AutoDeployment:        conf.AutoDeployment,
+		CallAs:                conf.CallAs,
+		ExecutionRoleName:     conf.ExecutionRoleName,
+		ManagedExecution:      conf.ManagedExecution,
+		PermissionModel:       conf.PermissionModel,
+	}
+
+	if strings.HasPrefix(templateBody, "http://") {
+		input.TemplateURL = ptr.String(templateBody)
+	} else {
+		input.TemplateBody = ptr.String(templateBody)
+	}
+
+	res, err := getClient().CreateStackSet(context.Background(), input)
+
+	return res.StackSetId, err
+}
+
+// UpdateStackSet updates stack set and its instances
+func UpdateStackSet(conf StackSetConfig, instanceConf StackSetInstancesConfig, wait bool) error {
+
+	templateBody, err := checkTemplate(conf.Template)
+	if err != nil {
+		return errors.New("error occured while extracting template body")
+	}
+
+	_, err = GetStackSet(conf.StackSetName)
+	if err != nil {
+		return errors.New("can't update stack set. It does not exists or it is in a wrong state")
+	}
+
+	input := &cloudformation.UpdateStackSetInput{
+		StackSetName:          &conf.StackSetName,
+		Parameters:            conf.Parameters,
+		Tags:                  conf.Tags,
+		Capabilities:          conf.Capabilities,
+		Description:           conf.Description,
+		AdministrationRoleARN: conf.AdministrationRoleARN,
+		AutoDeployment:        conf.AutoDeployment,
+		CallAs:                conf.CallAs,
+		ExecutionRoleName:     conf.ExecutionRoleName,
+		ManagedExecution:      conf.ManagedExecution,
+		PermissionModel:       conf.PermissionModel,
+		// instance configuration
+		Accounts:             instanceConf.Accounts,
+		Regions:              instanceConf.Regions,
+		DeploymentTargets:    instanceConf.DeploymentTargets,
+		OperationPreferences: instanceConf.OperationPreferences,
+	}
+
+	if strings.HasPrefix(templateBody, "http://") {
+		input.TemplateURL = ptr.String(templateBody)
+	} else {
+		input.TemplateBody = ptr.String(templateBody)
+	}
+
+	spinner.Pause()
+	if len(input.Accounts) == 0 {
+		fmt.Println("Updating stack set instances in all previously deployed accounts and regions")
+	} else {
+		fmt.Printf("Updating stack set instances in...\naccounts: %+v\nregions: %+v\n", input.Accounts, input.Regions)
+	}
+	spinner.Resume()
+
+	res, err := getClient().UpdateStackSet(context.Background(), input)
+
+	config.Debugf("Update stack instances API result:\n%s", format.PrettyPrint(res))
+	if err != nil {
+		return err
+	}
+
+	spinner.Pause()
+	fmt.Printf("Submitted UPDATE stack set operation with ID: %s\n", *res.OperationId)
+	spinner.Resume()
+
+	if err != nil {
+		return err
+	}
+
+	if wait {
+		err = WaitUntilStackSetOperationCompleted(*res.OperationId, conf.StackSetName)
+	}
+	return err
+}
+
+// AddStackSetInstances adds instances to a stack set
+func AddStackSetInstances(conf StackSetConfig, instanceConf StackSetInstancesConfig, wait bool) error {
+
+	_, err := GetStackSet(conf.StackSetName)
+	if err != nil {
+		return errors.New("can't update stack set. It does not exists or it is in a wrong state")
+	}
+
+	spinner.Pause()
+	if len(instanceConf.Accounts) == 0 || len(instanceConf.Regions) == 0 {
+		return errors.New("can't update stack set. Account(s) and region(s) must be provided")
+	} else {
+		fmt.Printf("Adding stack set instances in...\naccounts: %+v\nregions: %+v\n", instanceConf.Accounts, instanceConf.Regions)
+	}
+	spinner.Resume()
+
+	input := &cloudformation.CreateStackInstancesInput{
+		StackSetName:         &conf.StackSetName,
+		Accounts:             instanceConf.Accounts,
+		Regions:              instanceConf.Regions,
+		DeploymentTargets:    instanceConf.DeploymentTargets,
+		OperationPreferences: instanceConf.OperationPreferences,
+		CallAs:               conf.CallAs,
+	}
+
+	res, err := getClient().CreateStackInstances(context.Background(), input)
+
+	config.Debugf("CreateStackInstances API result:\n%s", format.PrettyPrint(res))
+	if err != nil {
+		return errors.New("error occurred durin stack set update")
+	}
+
+	spinner.Pause()
+	fmt.Printf("Submitted CREATE stack set instance(s) operation with ID: %s\n", *res.OperationId)
+	spinner.Resume()
+
+	if err != nil {
+		return err
+	}
+
+	if wait {
+		err = WaitUntilStackSetOperationCompleted(*res.OperationId, conf.StackSetName)
+	}
+	return err
+}
+
+func CreateStackSetInstances(conf StackSetInstancesConfig, wait bool) error {
+
+	input := &cloudformation.CreateStackInstancesInput{
+		StackSetName:         &conf.StackSetName,
+		Regions:              conf.Regions,
+		Accounts:             conf.Accounts,
+		DeploymentTargets:    conf.DeploymentTargets,
+		CallAs:               conf.CallAs,
+		OperationPreferences: conf.OperationPreferences,
+	}
+
+	res, err := getClient().CreateStackInstances(context.Background(), input)
+	config.Debugf("Create stack instances API result:\n%s", format.PrettyPrint(res))
+	if err != nil {
+		fmt.Println("error occurred durin stack set instance(s) deployment ")
+		return err
+	}
+
+	spinner.Pause()
+	fmt.Printf("Submitted CREATE instances operation with ID: %s\n", *res.OperationId)
+	spinner.Resume()
+
+	if wait {
+		WaitUntilStackSetOperationCompleted(*res.OperationId, conf.StackSetName)
+	}
+
+	return err
+}
+
 // ExecuteChangeSet executes the named changeset
 func ExecuteChangeSet(stackName, changeSetName string, disableRollback bool) error {
 	_, err := getClient().ExecuteChangeSet(context.Background(), &cloudformation.ExecuteChangeSetInput{
@@ -338,12 +675,37 @@ func WaitUntilStackExists(stackName string) error {
 			return err
 		}
 
-		time.Sleep(time.Second * 2)
+		time.Sleep(time.Second * WAIT_PERIOD_IN_SECONDS)
 	}
 
 	return nil
 }
 
+func WaitUntilStackSetOperationCompleted(operationId string, stacksetName string) error {
+	var operation *cloudformation.DescribeStackSetOperationOutput
+	var err error
+	for {
+		operation, err = getClient().DescribeStackSetOperation(context.Background(), &cloudformation.DescribeStackSetOperationInput{
+			OperationId:  &operationId,
+			StackSetName: &stacksetName,
+		})
+		if err != nil || operation == nil ||
+			operation.StackSetOperation.Status == types.StackSetOperationStatusStopped ||
+			operation.StackSetOperation.Status == types.StackSetOperationStatusSucceeded ||
+			operation.StackSetOperation.Status == types.StackSetOperationStatusFailed {
+			break
+		}
+
+		time.Sleep(time.Second * WAIT_PERIOD_IN_SECONDS)
+	}
+	if err == nil && operation != nil {
+		spinner.Pause()
+		fmt.Printf("Stack set operation resulted with state: %s\n", operation.StackSetOperation.Status)
+		spinner.Resume()
+	}
+	return err
+}
+
 // WaitUntilStackCreateComplete pauses execution until the stack is completed (or fails)
 func WaitUntilStackCreateComplete(stackName string) error {
 	for {
@@ -366,7 +728,7 @@ func WaitUntilStackCreateComplete(stackName string) error {
 			break
 		}
 
-		time.Sleep(time.Second * 2)
+		time.Sleep(time.Second * WAIT_PERIOD_IN_SECONDS)
 	}
 
 	return nil
diff --git a/internal/aws/cfn/cfn_types.go b/internal/aws/cfn/cfn_types.go
new file mode 100644
index 00000000..46668d63
--- /dev/null
+++ b/internal/aws/cfn/cfn_types.go
@@ -0,0 +1,183 @@
+package cfn
+
+import (
+	"github.com/aws-cloudformation/rain/cft"
+	"github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
+)
+
+type StackSetConfig struct {
+
+	// The Amazon Resource Number (ARN) of the IAM role to use to create this stack
+	// set. Specify an IAM role only if you are using customized administrator roles to
+	// control which users or groups can manage specific stack sets within the same
+	// administrator account. For more information, see Prerequisites: Granting
+	// Permissions for Stack Set Operations
+	// (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs.html)
+	// in the CloudFormation User Guide.
+	AdministrationRoleARN *string
+
+	// Describes whether StackSets automatically deploys to Organizations accounts that
+	// are added to the target organization or organizational unit (OU). Specify only
+	// if PermissionModel is SERVICE_MANAGED.
+	AutoDeployment *types.AutoDeployment
+
+	// [Service-managed permissions] Specifies whether you are acting as an account
+	// administrator in the organization's management account or as a delegated
+	// administrator in a member account. By default, SELF is specified. Use SELF for
+	// stack sets with self-managed permissions.
+	//
+	// * To create a stack set with
+	// service-managed permissions while signed in to the management account, specify
+	// SELF.
+	//
+	// * To create a stack set with service-managed permissions while signed in
+	// to a delegated administrator account, specify DELEGATED_ADMIN. Your Amazon Web
+	// Services account must be registered as a delegated admin in the management
+	// account. For more information, see Register a delegated administrator
+	// (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-orgs-delegated-admin.html)
+	// in the CloudFormation User Guide.
+	//
+	// Stack sets with service-managed permissions
+	// are created in the management account, including stack sets that are created by
+	// delegated administrators.
+	CallAs types.CallAs
+
+	// In some cases, you must explicitly acknowledge that your stack set template
+	// contains certain capabilities in order for CloudFormation to create the stack
+	// set and related stack instances.
+	//
+	// * CAPABILITY_IAM and CAPABILITY_NAMED_IAM Some
+	// stack templates might include resources that can affect permissions in your
+	// Amazon Web Services account; for example, by creating new Identity and Access
+	// Management (IAM) users. For those stack sets, you must explicitly acknowledge
+	// this by specifying one of these capabilities. The following IAM resources
+	// require you to specify either the CAPABILITY_IAM or CAPABILITY_NAMED_IAM
+	// capability.
+	//
+	// * If you have IAM resources, you can specify either capability.
+	//
+	// *
+	// If you have IAM resources with custom names, you must specify
+	// CAPABILITY_NAMED_IAM.
+	//
+	// * If you don't specify either of these capabilities,
+	// CloudFormation returns an InsufficientCapabilities error.
+	//
+	// If your stack
+	// template contains these resources, we recommend that you review all permissions
+	// associated with them and edit their permissions if necessary.
+	//
+	// *
+	// AWS::IAM::AccessKey
+	// (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-accesskey.html)
+	//
+	// *
+	// AWS::IAM::Group
+	// (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-group.html)
+	//
+	// *
+	// AWS::IAM::InstanceProfile
+	// (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-instanceprofile.html)
+	//
+	// *
+	// AWS::IAM::Policy
+	// (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html)
+	//
+	// *
+	// AWS::IAM::Role
+	// (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)
+	//
+	// *
+	// AWS::IAM::User
+	// (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html)
+	//
+	// *
+	// AWS::IAM::UserToGroupAddition
+	// (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-addusertogroup.html)
+	//
+	// For
+	// more information, see Acknowledging IAM Resources in CloudFormation Templates
+	// (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#capabilities).
+	//
+	// *
+	// CAPABILITY_AUTO_EXPAND Some templates reference macros. If your stack set
+	// template references one or more macros, you must create the stack set directly
+	// from the processed template, without first reviewing the resulting changes in a
+	// change set. To create the stack set directly, you must acknowledge this
+	// capability. For more information, see Using CloudFormation Macros to Perform
+	// Custom Processing on Templates
+	// (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-macros.html).
+	// Stack sets with service-managed permissions don't currently support the use of
+	// macros in templates. (This includes the AWS::Include
+	// (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/create-reusable-transform-function-snippets-and-add-to-your-template-with-aws-include-transform.html)
+	// and AWS::Serverless
+	// (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-aws-serverless.html)
+	// transforms, which are macros hosted by CloudFormation.) Even if you specify this
+	// capability for a stack set with service-managed permissions, if you reference a
+	// macro in your template the stack set operation will fail.
+	Capabilities []types.Capability
+
+	// A description of the stack set. You can use the description to identify the
+	// stack set's purpose or other important information.
+	Description *string
+
+	// The name of the IAM execution role to use to create the stack set. If you do not
+	// specify an execution role, CloudFormation uses the
+	// AWSCloudFormationStackSetExecutionRole role for the stack set operation. Specify
+	// an IAM role only if you are using customized execution roles to control which
+	// stack resources users and groups can include in their stack sets.
+	ExecutionRoleName *string
+
+	// Describes whether StackSets performs non-conflicting operations concurrently and
+	// queues conflicting operations.
+	ManagedExecution *types.ManagedExecution
+
+	// Describes how the IAM roles required for stack set operations are created. By
+	// default, SELF-MANAGED is specified.
+	//
+	// * With self-managed permissions, you must
+	// create the administrator and execution roles required to deploy to target
+	// accounts. For more information, see Grant Self-Managed Stack Set Permissions
+	// (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-self-managed.html).
+	//
+	// *
+	// With service-managed permissions, StackSets automatically creates the IAM roles
+	// required to deploy to accounts managed by Organizations. For more information,
+	// see Grant Service-Managed Stack Set Permissions
+	// (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-service-managed.html).
+	PermissionModel types.PermissionModels
+
+	// service fields, not to be used in configuration file
+	StackSetName string            `yaml:"-"`
+	Template     cft.Template      `yaml:"-"`
+	Parameters   []types.Parameter `yaml:"-"`
+	Tags         []types.Tag       `yaml:"-"`
+}
+
+// we treat list of provided accounts and regions as requirement to have instances in all the provided
+// accounts whether updated or created(added).
+type StackSetInstancesConfig struct {
+
+	// The names of one or more Amazon Web Services Regions where you want to create
+	// stack instances using the specified Amazon Web Services accounts.
+	//
+	// This member is required.
+	Regions []string
+
+	// [Service-managed permissions] The Organizations accounts for which to create
+	// stack instances in the specified Amazon Web Services Regions. You can specify
+	// Accounts or DeploymentTargets, but not both.
+	Accounts []string
+
+	// [Service-managed permissions] The Organizations accounts for which to create
+	// stack instances in the specified Amazon Web Services Regions. You can specify
+	// Accounts or DeploymentTargets, but not both.
+	DeploymentTargets *types.DeploymentTargets
+
+	// Preferences for how CloudFormation performs this stack set operation.
+	OperationPreferences *types.StackSetOperationPreferences
+
+	// service fields, not to be used in configuration file
+	StackSetName string       `yaml:"-"`
+	CallAs       types.CallAs `yaml:"-"`
+}
diff --git a/internal/aws/cfn/common.go b/internal/aws/cfn/common.go
index cf6bf162..f5730941 100644
--- a/internal/aws/cfn/common.go
+++ b/internal/aws/cfn/common.go
@@ -5,7 +5,8 @@ import (
 	"github.com/aws/smithy-go/ptr"
 )
 
-func makeTags(tags map[string]string) []types.Tag {
+// converts map of strings to a slice of types.Tag
+func MakeTags(tags map[string]string) []types.Tag {
 	out := make([]types.Tag, 0)
 
 	for key, value := range tags {
@@ -17,3 +18,18 @@ func makeTags(tags map[string]string) []types.Tag {
 
 	return out
 }
+
+// uniqueStrings returns a unique subset of the string slice provided.
+func UniqueStrings(input []string) []string {
+	u := make([]string, 0, len(input))
+	m := make(map[string]bool)
+
+	for _, val := range input {
+		if _, ok := m[val]; !ok {
+			m[val] = true
+			u = append(u, val)
+		}
+	}
+
+	return u
+}
diff --git a/internal/aws/cfn/mock.go b/internal/aws/cfn/mock.go
index b7f3e5b7..d336e8fd 100644
--- a/internal/aws/cfn/mock.go
+++ b/internal/aws/cfn/mock.go
@@ -17,6 +17,8 @@ import (
 	"github.com/aws/smithy-go/ptr"
 )
 
+const WAIT_PERIOD_IN_SECONDS = 2
+
 type mockStack struct {
 	name      string
 	template  cft.Template
@@ -225,7 +227,7 @@ func GetChangeSet(stackName, changeSetName string) (*cloudformation.DescribeChan
 		StackName:             ptr.String(stackName),
 		Status:                types.ChangeSetStatusCreateComplete,
 		StatusReason:          ptr.String("Mock status reason"),
-		Tags:                  makeTags(c.tags),
+		Tags:                  MakeTags(c.tags),
 	}, nil
 }
 
@@ -275,7 +277,7 @@ func ExecuteChangeSet(stackName, changeSetName string, disableRollback bool) err
 		},
 		StackId:           ptr.String(stackName),
 		StackStatusReason: ptr.String("Mock status reason"),
-		Tags:              makeTags(c.tags),
+		Tags:              MakeTags(c.tags),
 	}
 
 	s.resources = []types.StackResource{
@@ -327,3 +329,53 @@ func WaitUntilStackCreateComplete(stackName string) error {
 
 	return nil
 }
+
+// TODO - Fill out the mocks for stacksets
+
+func GetStackSet(stackSetName string) (*types.StackSet, error) {
+	return nil, nil
+}
+
+func ListStackSetInstances(stackSetName string) ([]types.StackInstanceSummary, error) {
+	return nil, nil
+}
+
+func CreateStackSet(conf StackSetConfig) (*string, error) {
+	return nil, nil
+}
+
+func UpdateStackSet(conf StackSetConfig, instanceConf StackSetInstancesConfig, wait bool) error {
+	return nil
+}
+
+func ListLast10StackSetOperations(stackSetName string) ([]types.StackSetOperationSummary, error) {
+	return nil, nil
+}
+
+func DeleteStackSet(stackSetName string) error {
+	return nil
+}
+
+func DeleteAllStackSetInstances(stackSetName string, wait bool, retainStacks bool) error {
+	return nil
+}
+
+func CreateStackSetInstances(conf StackSetInstancesConfig, wait bool) error {
+	return nil
+}
+
+func AddStackSetInstances(conf StackSetConfig, instanceConf StackSetInstancesConfig, wait bool) error {
+	return nil
+}
+
+func ListStackSets() ([]types.StackSetSummary, error) {
+	return nil, nil
+}
+
+func GetStackSetOperationsResult(stackSetName *string, operationId *string) (*types.StackSetOperationResultSummary, error) {
+	return nil, nil
+}
+
+func DeleteStackSetInstances(stackSetName string, accounts []string, regions []string, wait bool, retainStacks bool) error {
+	return nil
+}
diff --git a/internal/aws/s3/s3.go b/internal/aws/s3/s3.go
index 8303bf96..cd45ff24 100644
--- a/internal/aws/s3/s3.go
+++ b/internal/aws/s3/s3.go
@@ -121,7 +121,7 @@ func CreateBucket(bucketName string) error {
 // Upload an artefact to the bucket with a unique name
 func Upload(bucketName string, content []byte) (string, error) {
 	isBucketExists, errBucketExists := BucketExists(bucketName)
-	
+
 	if errBucketExists != nil {
 		return "", fmt.Errorf("unable to confirm whether artifact bucket exists: %w", errBucketExists)
 	}
diff --git a/internal/cmd/deploy/deploy.go b/internal/cmd/deploy/deploy.go
index 8c95d6e2..99892fcc 100644
--- a/internal/cmd/deploy/deploy.go
+++ b/internal/cmd/deploy/deploy.go
@@ -84,10 +84,10 @@ YAML:
 			stackName = base[:len(base)-len(filepath.Ext(base))]
 
 			// Now ensure it's a valid cfc name
-			stackName = fixStackNameRe.ReplaceAllString(stackName, "-")
+			stackName = FixStackNameRe.ReplaceAllString(stackName, "-")
 
-			if len(stackName) > maxStackNameLength {
-				stackName = stackName[:maxStackNameLength]
+			if len(stackName) > MaxStackNameLength {
+				stackName = stackName[:MaxStackNameLength]
 			}
 		}
 
@@ -135,7 +135,7 @@ YAML:
 
 		// Package template
 		spinner.Push(fmt.Sprintf("Preparing template '%s'", base))
-		template := packageTemplate(fn, yes)
+		template := PackageTemplate(fn, yes)
 		spinner.Pop()
 
 		// Check current stack status
@@ -145,7 +145,7 @@ YAML:
 
 		// Parse params
 		config.Debugf("Handling parameters")
-		parameters := getParameters(template, combinedParameters, stack.Parameters, stackExists)
+		parameters := GetParameters(template, combinedParameters, stack.Parameters, stackExists)
 
 		if config.Debug {
 			for _, param := range parameters {
@@ -241,7 +241,7 @@ YAML:
 }
 
 func init() {
-	fixStackNameRe = regexp.MustCompile(`[^a-zA-Z0-9]+`)
+	FixStackNameRe = regexp.MustCompile(`[^a-zA-Z0-9]+`)
 
 	Cmd.Flags().BoolVarP(&detach, "detach", "d", false, "once deployment has started, don't wait around for it to finish")
 	Cmd.Flags().BoolVarP(&yes, "yes", "y", false, "don't ask questions; just deploy")
diff --git a/internal/cmd/deploy/util.go b/internal/cmd/deploy/util.go
index e23a5e30..e80bc371 100644
--- a/internal/cmd/deploy/util.go
+++ b/internal/cmd/deploy/util.go
@@ -17,9 +17,9 @@ import (
 	"github.com/aws/smithy-go/ptr"
 )
 
-var fixStackNameRe *regexp.Regexp
+var FixStackNameRe *regexp.Regexp
 
-const maxStackNameLength = 128
+const MaxStackNameLength = 128
 
 func formatChangeSet(stackName, changeSetName string) string {
 	status, err := cfn.GetChangeSet(stackName, changeSetName)
@@ -82,7 +82,8 @@ func formatChangeSet(stackName, changeSetName string) string {
 	return strings.TrimSpace(out.String())
 }
 
-func getParameters(template cft.Template, cliParams map[string]string, old []types.Parameter, stackExists bool) []types.Parameter {
+func GetParameters(template cft.Template, combinedParameters map[string]string, old []types.Parameter, stackExists bool) []types.Parameter {
+
 	newParams := make([]types.Parameter, 0)
 
 	oldMap := make(map[string]types.Parameter)
@@ -95,7 +96,7 @@ func getParameters(template cft.Template, cliParams map[string]string, old []typ
 
 	if params, ok := template.Map()["Parameters"]; ok {
 		// Check we don't have any unknown params
-		for k := range cliParams {
+		for k := range combinedParameters {
 			if _, ok := params.(map[string]interface{})[k]; !ok {
 				panic(fmt.Errorf("unknown parameter: %s", k))
 			}
@@ -110,7 +111,7 @@ func getParameters(template cft.Template, cliParams map[string]string, old []typ
 			usePrevious := false
 
 			// Decide if we have an existing value
-			if cliParam, ok := cliParams[k]; ok {
+			if cliParam, ok := combinedParameters[k]; ok {
 				value = cliParam
 			} else {
 				extra := ""
@@ -200,7 +201,7 @@ func ListToMap(name string, in []string) map[string]string {
 	return out
 }
 
-func packageTemplate(fn string, yes bool) cft.Template {
+func PackageTemplate(fn string, yes bool) cft.Template {
 	// Call RainBucket for side-effects in case we want to force bucket creation
 	s3.RainBucket(yes)
 
@@ -261,5 +262,5 @@ func checkStack(stackName string) (types.Stack, bool) {
 }
 
 func init() {
-	fixStackNameRe = regexp.MustCompile(`[^a-zA-Z0-9]+`)
+	FixStackNameRe = regexp.MustCompile(`[^a-zA-Z0-9]+`)
 }
diff --git a/internal/cmd/rain/rain.go b/internal/cmd/rain/rain.go
index 22784aaa..2b996af5 100644
--- a/internal/cmd/rain/rain.go
+++ b/internal/cmd/rain/rain.go
@@ -19,6 +19,7 @@ import (
 	"github.com/aws-cloudformation/rain/internal/cmd/merge"
 	"github.com/aws-cloudformation/rain/internal/cmd/pkg"
 	"github.com/aws-cloudformation/rain/internal/cmd/rm"
+	"github.com/aws-cloudformation/rain/internal/cmd/stackset"
 	"github.com/aws-cloudformation/rain/internal/cmd/tree"
 	"github.com/aws-cloudformation/rain/internal/cmd/watch"
 	"github.com/aws-cloudformation/rain/internal/console"
@@ -78,6 +79,7 @@ func init() {
 	addCommand(stackGroup, true, ls.Cmd)
 	addCommand(stackGroup, true, rm.Cmd)
 	addCommand(stackGroup, true, watch.Cmd)
+	addCommand(stackGroup, true, stackset.StackSetCmd)
 
 	// Template commands
 	addCommand(templateGroup, false, build.Cmd)
diff --git a/internal/cmd/stackset/stackset.go b/internal/cmd/stackset/stackset.go
new file mode 100644
index 00000000..1c238b1c
--- /dev/null
+++ b/internal/cmd/stackset/stackset.go
@@ -0,0 +1,60 @@
+package stackset
+
+import (
+	"github.com/aws-cloudformation/rain/internal/config"
+	"github.com/aws-cloudformation/rain/internal/console"
+	"github.com/spf13/cobra"
+)
+
+const usageTemplate = `Usage:{{if .Runnable}}
+  <cyan>{{.UseLine}}</>{{end}}{{if .HasAvailableSubCommands}}
+  <cyan>{{.CommandPath}}</> [<gray>command</>]{{end}}{{if gt (len .Aliases) 0}}
+
+Aliases: {{.NameAndAliases}}{{end}}{{if .HasExample}}
+
+Examples:
+{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
+
+Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
+  <cyan>{{rpad .Name .NamePadding }}</> {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
+
+Flags:
+{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
+
+Global Flags:
+{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
+
+Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
+{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
+
+Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
+`
+
+func addCommand(profileOptions bool, c *cobra.Command) {
+	if profileOptions {
+		c.Flags().StringVarP(&config.Profile, "profile", "p", "", "AWS profile name; read from the AWS CLI configuration file")
+		c.Flags().StringVarP(&config.Region, "region", "r", "", "AWS region to use")
+	}
+
+	StackSetCmd.AddCommand(c)
+}
+
+var StackSetCmd = &cobra.Command{
+	Use:   "stackset <stack_set command>",
+	Short: "This command manipulates stack sets.",
+	Long:  "This command manipulates stack sets. It has no action if specific stack set command is not added.",
+}
+
+func init() {
+	addCommand(true, StackSetLsCmd)
+	addCommand(true, StackSetDeployCmd)
+	addCommand(true, StackSetRmCmd)
+
+	oldUsageFunc := StackSetCmd.UsageFunc()
+	StackSetCmd.SetUsageFunc(func(c *cobra.Command) error {
+		StackSetCmd.SetUsageTemplate(console.Sprint(usageTemplate))
+		return oldUsageFunc(c)
+	})
+
+	StackSetCmd.PersistentFlags().BoolVarP(&console.NoColour, "no-colour", "", false, "Disable colour output")
+}
diff --git a/internal/cmd/stackset/stackset_deploy.go b/internal/cmd/stackset/stackset_deploy.go
new file mode 100644
index 00000000..c5876d4f
--- /dev/null
+++ b/internal/cmd/stackset/stackset_deploy.go
@@ -0,0 +1,421 @@
+package stackset
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"regexp"
+
+	"github.com/aws-cloudformation/rain/cft"
+	"github.com/aws-cloudformation/rain/cft/format"
+	"github.com/aws-cloudformation/rain/internal/aws/cfn"
+	"github.com/aws-cloudformation/rain/internal/cmd/deploy"
+	"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"
+	"golang.org/x/exp/constraints"
+	"gopkg.in/yaml.v3"
+
+	"github.com/spf13/cobra"
+)
+
+type configFormat struct {
+	Parameters        map[string]string           `yaml:"Parameters"`
+	Tags              map[string]string           `yaml:"Tags"`
+	StackSet          cfn.StackSetConfig          `yaml:"StackSet"`
+	StackSetInstanses cfn.StackSetInstancesConfig `yaml:"StackSetInstanses"`
+}
+
+var accounts []string
+var regions []string
+var detach bool
+var yes bool
+var params []string
+var tags []string
+var configFilePath string
+
+// StackSetDeployCmd is the deploy command's entrypoint
+var StackSetDeployCmd = &cobra.Command{
+	Use:   "deploy <template> [stackset] [flags]",
+	Short: "Deploy a CloudFormation stack set from a local template",
+	Long: `Creates or updates a CloudFormation stack set <stackset> from the template file <template>.
+If you don't specify a stack set name, rain will use the template filename minus its extension.
+If you do not specify a template file, rain will asume that you want to add a new instance to an existing template,
+If a template needs to be packaged before it can be deployed, rain will package the template first.
+Rain will attempt to create an S3 bucket to store artifacts that it packages and deploys.
+The bucket's name will be of the format rain-artifacts-<AWS account id>-<AWS region>.
+
+The config flags can be used to set accounts, regions to operate and tags with parameters to use.
+Configuration file with extended options can be provided along with '--config' flag in YAML or JSON format (see example file for details).
+
+YAML:
+Parameters:
+	Name: Value
+Tags:
+	Name: Value
+StackSet:
+	description: "test description"
+	...
+StackSetInstanses:
+	accounts:
+		- "123456789123"
+	regions:
+		- us-east-1
+		- us-east-2
+...
+
+Account(s) and region(s) provideed as flags OVERRIDE values from configuration files. Tags and parameters from the configuration file are MERGED with CLI flag values. 
+`,
+	Args:                  cobra.RangeArgs(1, 2),
+	DisableFlagsInUseLine: false,
+	Run: func(cmd *cobra.Command, args []string) {
+
+		templateFilePath := args[0]
+
+		stackSetName := createStackSetName(args)
+
+		// Convert cli flags to maps
+		cliTagFlags := deploy.ListToMap("tag", tags)
+		cliParamFlags := deploy.ListToMap("param", params)
+
+		// Read configuration data from a file
+		configData := readConfiguration(configFilePath)
+
+		//ovveride config data with CLI flag values
+		combineConfigDataWithCliFlags(&configData, cliParamFlags, cliTagFlags, accounts, regions)
+
+		// Get current stack set if exist
+		spinner.Push(fmt.Sprintf("Checking current status of stack set '%s'", stackSetName))
+		existingStackSet, err := cfn.GetStackSet(stackSetName)
+		spinner.Pop()
+		isStacksetExists := false
+		if err == nil && existingStackSet.Status != types.StackSetStatusDeleted {
+			isStacksetExists = true
+		}
+		configData.StackSet.StackSetName = stackSetName
+		configData.StackSetInstanses.StackSetName = stackSetName
+
+		// Package template, if we add new instances templay is not needed
+		spinner.Push(fmt.Sprintf("Preparing template '%s'", templateFilePath))
+		configData.StackSet.Template = deploy.PackageTemplate(templateFilePath, yes)
+		spinner.Pop()
+
+		// Build []types.Parameter from configuration data
+		config.Debugln("Handling parameters")
+		configData.StackSet.Parameters = buildParameterTypes(configData.StackSet.Template, configData.Parameters, existingStackSet)
+
+		// Build []types.Tag from configuration data
+		config.Debugln("Handling tags")
+		configData.StackSet.Tags = cfn.MakeTags(configData.Tags)
+
+		if config.Debug {
+			for _, param := range configData.StackSet.Parameters {
+				val := ptr.ToString(param.ParameterValue)
+				if ptr.ToBool(param.UsePreviousValue) {
+					val = "<previous value>"
+				}
+				config.Debugf("  %s: %s", ptr.ToString(param.ParameterKey), val)
+			}
+		}
+
+		if isStacksetExists {
+			if console.Confirm(true, "Stack set already exists. Do you want to update it?") {
+				updateStackSet(configData)
+				addInstances(configData)
+
+			} else {
+				fmt.Println(console.Yellow("operation was cancelled by user"))
+			}
+		} else {
+			createStackSet(configData)
+		}
+	},
+}
+
+func init() {
+	deploy.FixStackNameRe = regexp.MustCompile(`[^a-zA-Z0-9]+`)
+
+	StackSetDeployCmd.Flags().StringSliceVar(&accounts, "accounts", []string{}, "accounts for which to create stack set instances")
+	StackSetDeployCmd.Flags().StringSliceVar(&regions, "regions", []string{}, "regions where you want to create stack set instances")
+	StackSetDeployCmd.Flags().BoolVarP(&detach, "detach", "d", false, "once deployment has started, don't wait around for it to finish")
+	StackSetDeployCmd.Flags().StringSliceVar(&tags, "tags", []string{}, "add tags to the stack; use the format key1=value1,key2=value2")
+	StackSetDeployCmd.Flags().StringSliceVar(&params, "params", []string{}, "set parameter values; use the format key1=value1,key2=value2")
+	StackSetDeployCmd.Flags().StringVarP(&configFilePath, "config", "c", "", "YAML or JSON file to set additional configuration parameters")
+}
+
+func readConfiguration(configFilePath string) configFormat {
+
+	var configData configFormat
+
+	// Read configuration file
+	if len(configFilePath) != 0 {
+		configFileContent, err := os.ReadFile(configFilePath)
+		if err != nil {
+			panic(ui.Errorf(err, "unable to read config file '%s'", configFilePath))
+		}
+
+		err = yaml.Unmarshal([]byte(configFileContent), &configData)
+		if err != nil {
+			panic(ui.Errorf(err, "unable to parse yaml in '%s'", configFilePath))
+		}
+	}
+	return configData
+}
+
+func combineConfigDataWithCliFlags(configData *configFormat, cliParams map[string]string, cliTags map[string]string, cliAccounts []string, cliRegions []string) {
+	// Merge Tags
+	for k, v := range cliTags {
+		if _, ok := configData.Tags[k]; ok {
+			fmt.Println(console.Yellow(fmt.Sprintf("tags flag overrides tag in config file: %s", k)))
+		}
+		if configData.Tags == nil {
+			configData.Tags = make(map[string]string)
+		}
+		configData.Tags[k] = v
+	}
+
+	// Merge Params
+	for k, v := range cliParams {
+		if _, ok := configData.Parameters[k]; ok {
+			fmt.Println(console.Yellow(fmt.Sprintf("params flag overrides parameter in config file: %s", k)))
+		}
+		if configData.Parameters == nil {
+			configData.Parameters = make(map[string]string)
+		}
+		configData.Parameters[k] = v
+	}
+
+	// Override  accounts with CLI values
+	if len(cliAccounts) > 0 {
+		configData.StackSetInstanses.Accounts = cliAccounts
+	}
+
+	// Override regions with CLI values
+	if len(cliRegions) > 0 {
+		configData.StackSetInstanses.Regions = cliRegions
+	}
+}
+
+// builds stack set name out of the template filename or takes it from the cli args
+func createStackSetName(args []string) string {
+	var stackSetName string
+
+	base := filepath.Base(args[0])
+	if len(args) == 2 {
+		stackSetName = args[1]
+	} else {
+		stackSetName = base[:len(base)-len(filepath.Ext(base))]
+
+		// Now ensure it's a valid cfc name
+		stackSetName = deploy.FixStackNameRe.ReplaceAllString(stackSetName, "-")
+
+		if len(stackSetName) > deploy.MaxStackNameLength {
+			stackSetName = stackSetName[:deploy.MaxStackNameLength]
+		}
+	}
+	return stackSetName
+}
+
+// Validate if we have enough configuration data to create/update stack set instances
+func isInstanceConfigDataValid(c *cfn.StackSetInstancesConfig) bool {
+	if c != nil &&
+		c.Accounts != nil && len(c.Accounts) > 0 &&
+		c.Regions != nil && len(c.Regions) > 0 {
+		config.Debugf("ConfigData is valid\n")
+		return true
+	} else {
+		config.Debugf("ConfigData NOT valid\n")
+		return false
+	}
+}
+
+// removes non existing instances from the StackSetInstancesConfig.
+func removeNonExistingInstances(c *cfn.StackSetInstancesConfig) {
+	// Get current stack set instances
+	instances, err := cfn.ListStackSetInstances(c.StackSetName)
+	if err != nil {
+		panic(ui.Errorf(err, "unable to fetch instances for stack set - '%s'", c.StackSetName))
+	}
+	var existingAccounts []string
+	var existingRegions []string
+
+	for _, instance := range instances {
+		existingAccounts = append(existingAccounts, *instance.Account)
+		existingRegions = append(existingRegions, *instance.Region)
+	}
+
+	c.Accounts = intersection(c.Accounts, existingAccounts)
+	c.Regions = intersection(c.Regions, existingRegions)
+}
+
+// removes existing instances from the StackSetInstancesConfig.
+// We do not remove accounts because we accept list of provided
+// accounts and regions as requirement to have instances in all provided
+// accounts whether updated or created(added)
+func removeExistingInstances(c *cfn.StackSetInstancesConfig) {
+	// Get current stack set instances
+	instances, err := cfn.ListStackSetInstances(c.StackSetName)
+	if err != nil {
+		panic(ui.Errorf(err, "unable to fetch instances for stack set - '%s'", c.StackSetName))
+	}
+
+	var existingRegions []string
+
+	for _, instance := range instances {
+		existingRegions = append(existingRegions, *instance.Region)
+	}
+	c.Regions = difference(c.Regions, existingRegions)
+}
+
+// difference returns the elements in `a` that aren't in `b`.
+func difference(a, b []string) []string {
+	mb := make(map[string]struct{}, len(b))
+	for _, x := range b {
+		mb[x] = struct{}{}
+	}
+	var diff []string
+	for _, x := range a {
+		if _, found := mb[x]; !found {
+			diff = append(diff, x)
+		}
+	}
+	return diff
+}
+
+// returns two slices intersection
+func intersection[T constraints.Ordered](pS ...[]T) []T {
+	hash := make(map[T]*int) // value, counter
+	result := make([]T, 0)
+	for _, slice := range pS {
+		duplicationHash := make(map[T]bool) // duplication checking for individual slice
+		for _, value := range slice {
+			if _, isDup := duplicationHash[value]; !isDup { // is not duplicated in slice
+				if counter := hash[value]; counter != nil { // is found in hash counter map
+					if *counter++; *counter >= len(pS) { // is found in every slice
+						result = append(result, value)
+					}
+				} else { // not found in hash counter map
+					i := 1
+					hash[value] = &i
+				}
+				duplicationHash[value] = true
+			}
+		}
+	}
+	return result
+}
+
+// creates stack set along with stack instances
+func createStackSet(configData configFormat) {
+	stackSetConfig := configData.StackSet
+	stackSetConfig.StackSetName = configData.StackSet.StackSetName
+	stackSetConfig.Parameters = configData.StackSet.Parameters
+	stackSetConfig.Tags = configData.StackSet.Tags
+	config.Debugf("Stack Set Configuration: \n%s\n", format.PrettyPrint(stackSetConfig))
+	stackSetConfig.Template = configData.StackSet.Template
+
+	// Create Stack Set
+	spinner.Push("Creating stack set")
+	stackSetId, err := cfn.CreateStackSet(stackSetConfig)
+	spinner.Pop()
+	if err != nil || stackSetId == nil {
+		panic(ui.Errorf(err, "error while creating stack set '%s' ", configData.StackSet.StackSetName))
+	} else {
+		fmt.Printf("Stack set has been created successfuly with ID: %s\n", *stackSetId)
+	}
+
+	// we create instances only if there is enough configuration data was provided in a config file or as cli arguments
+	if isInstanceConfigDataValid(&configData.StackSetInstanses) {
+		stackSetInstancesConfig := configData.StackSetInstanses
+		stackSetInstancesConfig.StackSetName = configData.StackSet.StackSetName
+		stackSetInstancesConfig.CallAs = configData.StackSet.CallAs
+
+		config.Debugf("Stack Set Instances Configuration: \n%s\n", format.PrettyPrint(stackSetInstancesConfig))
+
+		// Create Stack Set instances
+		spinner.Push("Creating stack set instances")
+		err = cfn.CreateStackSetInstances(stackSetInstancesConfig, !detach)
+		spinner.Pop()
+		if err != nil {
+			panic(ui.Errorf(err, "error while creating stack set instances"))
+		}
+		if !detach {
+			fmt.Println("Stack set instances have been created successfully")
+		} else {
+			fmt.Println("Stack set instances creation was initiated successfuly")
+		}
+	} else {
+		fmt.Println("Not enough information provided to create stack set instance(s). Please use configuration file or provide account(s) and region(s) for deployment as command argiments")
+	}
+}
+
+// converts 'string' parameters to typed objects
+func buildParameterTypes(template cft.Template, combinedParams map[string]string, stackSet *types.StackSet) []types.Parameter {
+
+	defer func() { //catch or finally
+		if err := recover(); err != nil { //catch
+			panic(fmt.Errorf("error occured while handling parameters: %v", err))
+		}
+	}()
+
+	var oldParams []types.Parameter
+	var stackSetExist = false
+	if stackSet != nil {
+		oldParams = stackSet.Parameters
+		stackSetExist = true
+	}
+	return deploy.GetParameters(template, combinedParams, oldParams, stackSetExist)
+}
+
+// updates existing stack set and all its instances
+func updateStackSet(configData configFormat) {
+	config.Debugf("Updating Stack Set: %s\nStack Set Configuration: \n%s\nStack Set Instances Configuration: \n%s\n",
+		configData.StackSet.StackSetName, format.PrettyPrint(configData.StackSet), format.PrettyPrint(configData.StackSetInstanses))
+
+	// remove accounts and regions for the instances that do not exist, removed instances supposed to be created but not updated
+	removeNonExistingInstances(&configData.StackSetInstanses)
+
+	// check if we have instances left to update after filtering
+	if !isInstanceConfigDataValid(&configData.StackSetInstanses) {
+		fmt.Println("There is no instances to update.")
+		return
+	}
+
+	// Update Stack Set with its instances
+	spinner.Push("Updating stack set")
+	err := cfn.UpdateStackSet(configData.StackSet, configData.StackSetInstanses, !detach)
+	spinner.Pop()
+	if err != nil {
+		panic(ui.Errorf(err, "error occurred while updating stack set '%s' ", configData.StackSetInstanses.StackSetName))
+	} else {
+		fmt.Println("Stack set update has been completed.")
+	}
+
+}
+
+// adds stack set instances to an existing stack set
+func addInstances(configData configFormat) {
+	config.Debugf("Adding Stack Set instance(s): %s\nStack Set Configuration: \n%s\nStack Set Instances Configuration: \n%s\n",
+		configData.StackSet.StackSetName, format.PrettyPrint(configData.StackSet), format.PrettyPrint(configData.StackSetInstanses))
+
+	// remove existing instances from configData
+	removeExistingInstances(&configData.StackSetInstanses)
+
+	// check if we have accounts and regions to update
+	if !isInstanceConfigDataValid(&configData.StackSetInstanses) {
+		fmt.Println("There are no new instances to be created.")
+		os.Exit(0)
+	}
+
+	spinner.Push("Adding stack set instances")
+	err := cfn.AddStackSetInstances(configData.StackSet, configData.StackSetInstanses, !detach)
+	spinner.Pop()
+	if err != nil {
+		panic(ui.Errorf(err, "error occurred while adding stack set instances for stack set'%s' ", configData.StackSet.StackSetName))
+	} else {
+		fmt.Println("Stack set update has been completed.")
+	}
+}
diff --git a/internal/cmd/stackset/stackset_ls.go b/internal/cmd/stackset/stackset_ls.go
new file mode 100644
index 00000000..ade1d78d
--- /dev/null
+++ b/internal/cmd/stackset/stackset_ls.go
@@ -0,0 +1,195 @@
+package stackset
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+
+	"github.com/aws-cloudformation/rain/internal/ui"
+
+	"github.com/aws-cloudformation/rain/internal/aws"
+	"github.com/aws-cloudformation/rain/internal/aws/cfn"
+	"github.com/aws-cloudformation/rain/internal/aws/ec2"
+	"github.com/aws-cloudformation/rain/internal/console"
+	"github.com/aws-cloudformation/rain/internal/console/spinner"
+	"github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
+	"github.com/spf13/cobra"
+)
+
+var all = false
+
+// StackSetLsCmd is the ls command's entrypoint
+var StackSetLsCmd = &cobra.Command{
+	Use:                   "ls <stack set>",
+	Short:                 "List a CloudFormation stack sets in a given region",
+	Long:                  "List a CloudFormation stack sets in a given region. If you specify a stack set name it will show all the stack instances and last 10 operations.",
+	Args:                  cobra.MaximumNArgs(1),
+	Aliases:               []string{"list"},
+	DisableFlagsInUseLine: true,
+	Run: func(cmd *cobra.Command, args []string) {
+		if len(args) == 1 {
+			displayStackSetSummaryWithInstancesAndLast10Operations(args[0])
+		} else {
+			var err error
+			regions := []string{aws.Config().Region}
+
+			if all {
+				spinner.Push("Fetching region list")
+				regions, err = ec2.GetRegions()
+				if err != nil {
+					panic(ui.Errorf(err, "unable to get region list"))
+				}
+				spinner.Pop()
+			}
+
+			origRegion := aws.Config().Region
+
+			for _, region := range regions {
+				spinner.Push(fmt.Sprintf("Fetching stack sets in %s", region))
+				aws.SetRegion(region)
+				stackSets, err := cfn.ListStackSets()
+				if err != nil {
+					panic(ui.Errorf(err, "failed to list stack sets"))
+				}
+				spinner.Pop()
+
+				if len(stackSets) == 0 && all {
+					continue
+				}
+
+				stackSetNames := make(sort.StringSlice, 0)
+				stackSetMap := make(map[string]types.StackSetSummary)
+				for _, stack := range stackSets {
+					if stack.Status != types.StackSetStatusDeleted {
+						stackSetNames = append(stackSetNames, *stack.StackSetName)
+						stackSetMap[*stack.StackSetName+region] = stack
+					}
+				}
+				sort.Strings(stackSetNames)
+
+				fmt.Println(console.Yellow(fmt.Sprintf("CloudFormation stack sets in %s:", region)))
+				for _, stackSetName := range stackSetNames {
+					out := strings.Builder{}
+					out.WriteString(fmt.Sprintf("%s: %s\n",
+						stackSetName,
+						ui.ColouriseStatus(string(stackSetMap[stackSetName+region].Status)),
+					))
+					fmt.Println(ui.Indent("  ", out.String()))
+				}
+			}
+
+			aws.SetRegion(origRegion)
+		}
+
+		// Reset flags
+		all = false
+	},
+}
+
+func init() {
+	StackSetLsCmd.Flags().BoolVarP(&all, "all", "a", false, "list stacks in all regions; if you specify a stack set name, show more details")
+}
+
+// returns a string with stack set instances for a given stack set
+func getStackSetInstances(stackSetName string) string {
+	out := strings.Builder{}
+	out.WriteString(console.Yellow("Instances (StackID/Account/Region/Status/Reason):\n"))
+	spinner.Push(fmt.Sprintf("Fetching stack set instances for '%s'", stackSetName))
+	instances, err := cfn.ListStackSetInstances(stackSetName)
+	if err != nil {
+		panic(ui.Errorf(err, "failed to list stack set instancess"))
+	}
+	spinner.Pop()
+
+	if len(instances) == 0 {
+		out.WriteString(" - \n")
+		return out.String()
+	}
+
+	stackId := "N/A"
+	for _, instance := range instances {
+
+		if instance.StackId != nil {
+			stackId = (*instance.StackId)[strings.Index(*instance.StackId, "stack/")+6 : len(*instance.StackId)]
+		} else {
+			stackId = "N/A"
+		}
+		out.WriteString(fmt.Sprintf(" - %s / %s / %s / %s ",
+			stackId,
+			*instance.Account,
+			*instance.Region,
+			ui.ColouriseStatus(string(instance.StackInstanceStatus.DetailedStatus)),
+		))
+		if instance.StatusReason != nil {
+			out.WriteString(fmt.Sprintf("/ %s \n", *instance.StatusReason))
+		} else {
+			out.WriteString("\n")
+		}
+
+	}
+	out.WriteString("\n")
+
+	return out.String()
+}
+
+// returns a display string with last 10 stack set operations for a given stack set
+func getStackSetOperations(stackSetName string) string {
+	out := strings.Builder{}
+	out.WriteString(console.Yellow("Last 10 operations (ID/Type/Status/Created/Completed):\n"))
+	spinner.Push(fmt.Sprintf("Fetching stack set operations for '%s'", stackSetName))
+	stackSetOps, err := cfn.ListLast10StackSetOperations(stackSetName)
+	if err != nil {
+		panic(ui.Errorf(err, "failed to list stack set operations"))
+	}
+	spinner.Pop()
+
+	if len(stackSetOps) == 0 {
+		out.WriteString(" - \n")
+		return out.String()
+	}
+
+	for i, operation := range stackSetOps {
+		if i > 9 {
+			break
+		}
+		endStatus := " - "
+		if operation.EndTimestamp != nil {
+			endStatus = operation.EndTimestamp.Local().String()
+		}
+
+		out.WriteString(fmt.Sprintf(" - %s / %s / %s / %s / %s",
+			*operation.OperationId,
+			operation.Action,
+			ui.ColouriseStatus(string(operation.Status)),
+			operation.CreationTimestamp.Local().String(),
+			endStatus,
+		))
+		if operation.Status == types.StackSetOperationStatusFailed {
+			spinner.Push(fmt.Sprintf("Fetching stack set operation result for operation '%s'", *operation.OperationId))
+			operationResult, err := cfn.GetStackSetOperationsResult(&stackSetName, operation.OperationId)
+			if err != nil {
+				panic(ui.Errorf(err, "failed to list stack set operation results"))
+			}
+			spinner.Pop()
+			if operationResult != nil {
+				out.WriteString(fmt.Sprintf(" - %s ", *operationResult.StatusReason))
+			}
+		}
+		out.WriteString("\n")
+	}
+	out.WriteString("\n")
+
+	return out.String()
+}
+
+func displayStackSetSummaryWithInstancesAndLast10Operations(stackSetName string) {
+	spinner.Push("Fetching stack set status")
+	stackSet, err := cfn.GetStackSet(stackSetName)
+	if err != nil {
+		panic(ui.Errorf(err, "failed to list stack set '%s'", stackSetName))
+	}
+	spinner.Pop()
+	fmt.Println(ui.GetStackSetSummary(stackSet, all))
+	fmt.Println(ui.Indent("  ", getStackSetInstances(stackSetName)))
+	fmt.Println(ui.Indent("  ", getStackSetOperations(stackSetName)))
+}
diff --git a/internal/cmd/stackset/stackset_rm.go b/internal/cmd/stackset/stackset_rm.go
new file mode 100644
index 00000000..c97a1e03
--- /dev/null
+++ b/internal/cmd/stackset/stackset_rm.go
@@ -0,0 +1,141 @@
+package stackset
+
+import (
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/aws-cloudformation/rain/internal/aws/cfn"
+	"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/spf13/cobra"
+)
+
+// StackSetRmCmd is the rm command's entrypoint
+var StackSetRmCmd = &cobra.Command{
+	Use:                   "rm <stackset>",
+	Short:                 "Delete a CloudFormation stack set and/or its instances.",
+	Long:                  "Delete a CloudFormation stack set <stackset> and/or its instances.",
+	Args:                  cobra.ExactArgs(1),
+	Aliases:               []string{"delete", "remove"},
+	DisableFlagsInUseLine: true,
+	Run: func(cmd *cobra.Command, args []string) {
+		stackSetName := args[0]
+		config.Debugf("Deleting stack set: %s\n", stackSetName)
+
+		stackSet, err := cfn.GetStackSet(stackSetName)
+		if err != nil {
+			panic(ui.Errorf(err, "Could not find stack set '%s'", stackSetName))
+		} else if stackSet.Status == types.StackSetStatusDeleted {
+			panic(ui.Errorf(err, "Stack set '%s' is already in DELETED state ", stackSetName))
+		}
+
+		spinner.Push("Deleting stack set..")
+		err = cfn.DeleteStackSet(stackSetName)
+		spinner.Pop()
+		if err != nil {
+			var notEmptyException *types.StackSetNotEmptyException
+			if errors.As(err, &notEmptyException) {
+
+				instancesOut, instances := getStackInstances(stackSetName)
+				fmt.Printf("%s", instancesOut)
+				inputString := console.Ask("Select instances number to delete or 0 to delete all. For multiple selects separate with comma:")
+
+				accounts, regions, deleteAll := convertInputString(inputString, instances)
+
+				spinner.Push("Deleting stack set instances...")
+				if deleteAll {
+					err = cfn.DeleteAllStackSetInstances(stackSetName, !detach, false)
+				} else {
+					err = cfn.DeleteStackSetInstances(stackSetName, accounts, regions, !detach, false)
+				}
+				spinner.Pop()
+
+				if err != nil {
+					panic(ui.Errorf(err, "error while deleting stack set instances "))
+				}
+
+				if deleteAll {
+					spinner.Push("Deleting stack set...")
+					err = cfn.DeleteStackSet(stackSetName)
+					spinner.Pop()
+					if err != nil {
+						panic(ui.Errorf(err, "Could not delete stack set '%s'", stackSetName))
+					} else {
+						fmt.Println("Stack set deletion has been completed.")
+					}
+				}
+			} else {
+				panic(ui.Errorf(err, "Could not delete stack set '%s'", stackSetName))
+			}
+
+		} else {
+			fmt.Println("Success!")
+		}
+
+	},
+}
+
+func init() {
+	StackSetRmCmd.Flags().BoolVarP(&detach, "detach", "d", false, "once delete has started, don't wait around for it to finish")
+}
+
+func getStackInstances(stackSetName string) (string, []types.StackInstanceSummary) {
+	out := strings.Builder{}
+	out.WriteString(console.Yellow("Instances (StackID/Account/Region/Status/Reason):\n"))
+	spinner.Push(fmt.Sprintf("Fetching stack set instances for '%s'", stackSetName))
+	instances, err := cfn.ListStackSetInstances(stackSetName)
+	if err != nil {
+		panic(ui.Errorf(err, "failed to list stack set instancess"))
+	}
+	spinner.Pop()
+
+	if len(instances) == 0 {
+		out.WriteString(" - \n")
+		return out.String(), instances
+	}
+
+	for i, instance := range instances {
+		stackId := (*instance.StackId)[strings.Index(*instance.StackId, "stack/")+6 : len(*instance.StackId)]
+		out.WriteString(fmt.Sprintf(" [%d] - %s / %s / %s / %s ",
+			i+1,
+			stackId,
+			*instance.Account,
+			*instance.Region,
+			ui.ColouriseStatus(string(instance.StackInstanceStatus.DetailedStatus)),
+		))
+		if instance.StatusReason != nil {
+			out.WriteString(fmt.Sprintf("/ %s \n", *instance.StatusReason))
+		} else {
+			out.WriteString("\n")
+		}
+
+	}
+	out.WriteString("\n")
+
+	return out.String(), instances
+}
+
+// Converts user input string to set of accounts and regions associated with selected instances
+func convertInputString(inputString string, instances []types.StackInstanceSummary) ([]string, []string, bool) {
+	accounts := []string{}
+	regions := []string{}
+
+	selectNumbers := strings.Split(inputString, ",")
+	for _, num := range selectNumbers {
+		numInt, err := strconv.Atoi(num)
+		if err != nil || numInt < 0 || numInt > len(instances) {
+			panic(ui.Errorf(err, "Invalid input: '%s'", num))
+		}
+		if numInt == 0 {
+			return []string{}, []string{}, true
+		}
+		accounts = append(accounts, *instances[numInt-1].Account)
+		regions = append(regions, *instances[numInt-1].Region)
+	}
+	return accounts, regions, false
+}
diff --git a/internal/cmd/watch/watch.go b/internal/cmd/watch/watch.go
index a5528fa1..2682a9bf 100644
--- a/internal/cmd/watch/watch.go
+++ b/internal/cmd/watch/watch.go
@@ -54,7 +54,7 @@ var Cmd = &cobra.Command{
 				first = false
 			}
 
-			time.Sleep(time.Second * 2)
+			time.Sleep(time.Second * cfn.WAIT_PERIOD_IN_SECONDS)
 		}
 
 		spinner.Pop()
diff --git a/internal/config/config.go b/internal/config/config.go
index 720f748f..e7ce684e 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -21,3 +21,9 @@ func Debugf(message string, parts ...interface{}) {
 		fmt.Println(console.Grey("DEBUG: " + fmt.Sprintf(message, parts...)))
 	}
 }
+
+func Debugln(message string) {
+	if Debug {
+		fmt.Println(console.Grey("DEBUG: " + fmt.Sprintln(message)))
+	}
+}
diff --git a/internal/ui/colourise.go b/internal/ui/colourise.go
index 31401677..934852cc 100644
--- a/internal/ui/colourise.go
+++ b/internal/ui/colourise.go
@@ -15,6 +15,7 @@ const (
 	complete
 	inProgress
 	pending
+	cancelled
 )
 
 var statusColour = map[statusCategory]func(...interface{}) string{
@@ -22,6 +23,7 @@ var statusColour = map[statusCategory]func(...interface{}) string{
 	inProgress: console.Blue,
 	failed:     console.Red,
 	complete:   console.Green,
+	cancelled:  console.Grey,
 }
 
 type statusRep struct {
@@ -50,6 +52,25 @@ func mapStatus(status string) *statusRep {
 	case strings.HasSuffix(status, "_COMPLETE"):
 		rep.category = complete
 
+	// stack set statuses
+	case strings.HasSuffix(status, "ACTIVE"):
+		rep.category = complete
+
+	// stack set instance statuses
+	case strings.HasSuffix(status, "SUCCEEDED"):
+		rep.category = complete
+
+	case strings.HasSuffix(status, "CANCELLED"):
+	case strings.HasSuffix(status, "OUTDATED"):
+		rep.category = cancelled
+
+	case strings.HasSuffix(status, "FAILED"):
+	case strings.HasSuffix(status, "INOPERABLE"):
+		rep.category = failed
+
+	case strings.HasSuffix(status, "RUNNING"):
+		rep.category = inProgress
+
 	default:
 		rep.category = pending
 	}
diff --git a/internal/ui/stack.go b/internal/ui/stack.go
index e521531f..4590f31c 100644
--- a/internal/ui/stack.go
+++ b/internal/ui/stack.go
@@ -256,7 +256,7 @@ func WaitForStackToSettle(stackName string) (string, []string) {
 			return string(stack.StackStatus), messages
 		}
 
-		time.Sleep(time.Second * 2)
+		time.Sleep(time.Second * cfn.WAIT_PERIOD_IN_SECONDS)
 	}
 }
 
@@ -343,3 +343,34 @@ func GetStackSummary(stack types.Stack, long bool) string {
 
 	return strings.TrimSpace(out.String())
 }
+
+//
+func GetStackSetSummary(stackSet *types.StackSet, long bool) string {
+	out := strings.Builder{}
+
+	stackSetStatus := string(stackSet.Status)
+	stackSetName := ptr.ToString(stackSet.StackSetName)
+
+	// Stack status
+	out.WriteString(fmt.Sprintf("%s: %s %s\n", console.Yellow("StackSet"), stackSetName, ColouriseStatus(stackSetStatus)))
+
+	if long {
+		// Params
+		if len(stackSet.Parameters) > 0 {
+			out.WriteString(fmt.Sprintf("  %s:\n", console.Yellow("Parameters")))
+			for _, param := range stackSet.Parameters {
+				out.WriteString(fmt.Sprintf("    %s: ", console.Yellow(ptr.ToString(param.ParameterKey))))
+
+				if param.ResolvedValue != nil {
+					out.WriteString(ptr.ToString(param.ResolvedValue))
+				} else {
+					out.WriteString(ptr.ToString(param.ParameterValue))
+				}
+
+				out.WriteString("\n")
+			}
+		}
+	}
+
+	return strings.TrimSpace(out.String())
+}


Test patch:
diff --git a/internal/aws/cfn/common_test.go b/internal/aws/cfn/common_test.go
new file mode 100644
index 00000000..c4d427a5
--- /dev/null
+++ b/internal/aws/cfn/common_test.go
@@ -0,0 +1,16 @@
+package cfn_test
+
+import (
+	"testing"
+
+	"github.com/aws-cloudformation/rain/internal/aws/cfn"
+)
+
+func TestUniqueStrings(t *testing.T) {
+	in := []string{"a", "a", "b", "c"}
+	expect := []string{"a", "b", "c"}
+	uq := cfn.UniqueStrings(in)
+	if uq[0] != expect[0] || uq[1] != expect[1] || uq[2] != expect[2] {
+		t.Error()
+	}
+}
diff --git a/internal/cmd/deploy/util_test.go b/internal/cmd/deploy/util_test.go
index 843c1c99..68626a72 100644
--- a/internal/cmd/deploy/util_test.go
+++ b/internal/cmd/deploy/util_test.go
@@ -1,9 +1,10 @@
 package deploy_test
 
 import (
+	"testing"
+
 	"github.com/aws-cloudformation/rain/internal/cmd/deploy"
 	"github.com/google/go-cmp/cmp"
-	"testing"
 )
 
 func TestListToMap(t *testing.T) {
diff --git a/internal/cmd/rain/rain_test.go b/internal/cmd/rain/rain_test.go
index 1f7a8fe2..24a2dca2 100644
--- a/internal/cmd/rain/rain_test.go
+++ b/internal/cmd/rain/rain_test.go
@@ -24,6 +24,7 @@ func Example_rainhelp() {
 	//   logs        Show the event log for the named stack
 	//   ls          List running CloudFormation stacks
 	//   rm          Delete a running CloudFormation stack
+	//   stackset    This command manipulates stack sets.
 	//   watch       Display an updating view of a CloudFormation stack
 	//
 	// Template commands:
diff --git a/test/samples/test-config.yaml b/test/samples/test-config.yaml
new file mode 100644
index 00000000..5ac9dd3c
--- /dev/null
+++ b/test/samples/test-config.yaml
@@ -0,0 +1,170 @@
+Parameters:
+    queueName: testQueue
+Tags:
+    stackset: testStackSet
+StackSet:
+
+    # A description of the stack set. You can use the description to identify the
+	# stack set's purpose or other important information.
+    description: "test description"
+
+    # In some cases, you must explicitly acknowledge that your stack set template
+	# contains certain capabilities in order for CloudFormation to create the stack
+	# set and related stack instances.
+	#
+	# * CAPABILITY_IAM and CAPABILITY_NAMED_IAM Some
+	# stack templates might include resources that can affect permissions in your
+	# Amazon Web Services account; for example, by creating new Identity and Access
+	# Management (IAM) users. For those stack sets, you must explicitly acknowledge
+	# this by specifying one of these capabilities. The following IAM resources
+	# require you to specify either the CAPABILITY_IAM or CAPABILITY_NAMED_IAM
+	# capability.
+	#
+	# * If you have IAM resources, you can specify either capability.
+	#
+	# *
+	# If you have IAM resources with custom names, you must specify
+	# CAPABILITY_NAMED_IAM.
+	#
+	# * If you don't specify either of these capabilities,
+	# CloudFormation returns an InsufficientCapabilities error.
+	#
+	# If your stack
+	# template contains these resources, we recommend that you review all permissions
+	# associated with them and edit their permissions if necessary.
+	#
+	# *
+	# AWS::IAM::AccessKey
+	# (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-accesskey.html)
+	#
+	# *
+	# AWS::IAM::Group
+	# (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-group.html)
+	#
+	# *
+	# AWS::IAM::InstanceProfile
+	# (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-instanceprofile.html)
+	#
+	# *
+	# AWS::IAM::Policy
+	# (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html)
+	#
+	# *
+	# AWS::IAM::Role
+	# (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)
+	#
+	# *
+	# AWS::IAM::User
+	# (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html)
+	#
+	# *
+	# AWS::IAM::UserToGroupAddition
+	# (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-addusertogroup.html)
+	#
+	# For
+	# more information, see Acknowledging IAM Resources in CloudFormation Templates
+	# (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#capabilities).
+	#
+	# *
+	# CAPABILITY_AUTO_EXPAND Some templates reference macros. If your stack set
+	# template references one or more macros, you must create the stack set directly
+	# from the processed template, without first reviewing the resulting changes in a
+	# change set. To create the stack set directly, you must acknowledge this
+	# capability. For more information, see Using CloudFormation Macros to Perform
+	# Custom Processing on Templates
+	# (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-macros.html).
+	# Stack sets with service-managed permissions don't currently support the use of
+	# macros in templates. (This includes the AWS::Include
+	# (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/create-reusable-transform-function-snippets-and-add-to-your-template-with-aws-include-transform.html)
+	# and AWS::Serverless
+	# (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-aws-serverless.html)
+	# transforms, which are macros hosted by CloudFormation.) Even if you specify this
+	# capability for a stack set with service-managed permissions, if you reference a
+	# macro in your template the stack set operation will fail.
+    capabilities:
+        - CAPABILITY_NAMED_IAM
+        - CAPABILITY_AUTO_EXPAND
+    
+    # The name of the IAM execution role to use to create the stack set. If you do not
+	# specify an execution role, CloudFormation uses the
+	# AWSCloudFormationStackSetExecutionRole role for the stack set operation. Specify
+	# an IAM role only if you are using customized execution roles to control which
+	# stack resources users and groups can include in their stack sets.
+    executionrolename: null
+    
+    # The Amazon Resource Number (ARN) of the IAM role to use to create this stack
+	# set. Specify an IAM role only if you are using customized administrator roles to
+	# control which users or groups can manage specific stack sets within the same
+	# administrator account. For more information, see Prerequisites: Granting
+	# Permissions for Stack Set Operations
+	# (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs.html)
+	# in the CloudFormation User Guide.
+    administrationrolearn: null
+
+    # Describes whether StackSets automatically deploys to Organizations accounts that
+	# are added to the target organization or organizational unit (OU). Specify only
+	# if PermissionModel is SERVICE_MANAGED.
+    autodeployment: null
+
+    # [Service-managed permissions] Specifies whether you are acting as an account
+	# administrator in the organization's management account or as a delegated
+	# administrator in a member account. By default, SELF is specified. Use SELF for
+	# stack sets with self-managed permissions.
+	#
+	# * To create a stack set with
+	# service-managed permissions while signed in to the management account, specify
+	# SELF.
+	#
+	# * To create a stack set with service-managed permissions while signed in
+	# to a delegated administrator account, specify DELEGATED_ADMIN. Your Amazon Web
+	# Services account must be registered as a delegated admin in the management
+	# account. For more information, see Register a delegated administrator
+	# (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-orgs-delegated-admin.html)
+	# in the CloudFormation User Guide.
+	#
+	# Stack sets with service-managed permissions
+	# are created in the management account, including stack sets that are created by
+	# delegated administrators.
+    callas: ""
+
+    # Describes whether StackSets performs non-conflicting operations concurrently and
+	# queues conflicting operations.
+    managedexecution: null    
+
+    # Describes how the IAM roles required for stack set operations are created. By
+	# default, SELF-MANAGED is specified.
+	#
+	# * With self-managed permissions, you must
+	# create the administrator and execution roles required to deploy to target
+	# accounts. For more information, see Grant Self-Managed Stack Set Permissions
+	# (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-self-managed.html).
+	#
+	# *
+	# With service-managed permissions, StackSets automatically creates the IAM roles
+	# required to deploy to accounts managed by Organizations. For more information,
+	# see Grant Service-Managed Stack Set Permissions
+	# (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-service-managed.html).
+    permissionmodel: "" 
+
+StackSetInstanses:
+    # [Service-managed permissions] The Organizations accounts for which to create
+	# stack instances in the specified Amazon Web Services Regions. You can specify
+	# Accounts or DeploymentTargets, but not both.
+    accounts:
+        - "577613639135"
+
+    # The names of one or more Amazon Web Services Regions where you want to create
+	# stack instances using the specified Amazon Web Services accounts.
+	#
+	# This member is required.
+    regions:
+        - us-east-1
+        - us-east-2
+
+    # [Service-managed permissions] The Organizations accounts for which to create
+	# stack instances in the specified Amazon Web Services Regions. You can specify
+	# Accounts or DeploymentTargets, but not both.
+    deploymenttargets: null
+
+    # Preferences for how CloudFormation performs this stack set operation.
+    operationpreferences: null
\ No newline at end of file
diff --git a/test/samples/test-template.yaml b/test/samples/test-template.yaml
new file mode 100755
index 00000000..bef64a91
--- /dev/null
+++ b/test/samples/test-template.yaml
@@ -0,0 +1,8 @@
+Parameters:
+  queueName:
+    Type: String
+Resources:
+  TestQueue:
+    Type: AWS::SQS::Queue
+    Properties:
+      QueueName: !Ref queueName