Repository: DontShaveTheYak/cf2tf (language: python)

Issue:
Allow cidr function to be nested inside of select function
Fixes #221 


Discussion / hints:

Fix patch:
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index fcf9c14..ba6acff 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -49,11 +49,12 @@ ENV PATH $PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH
 
 # Install the required python versions (this takes a bit usually) so we do lots of cacheing
 RUN --mount=type=cache,target=/home/vscode/.pyenv/cache,uid=1000 \
-    pyenv install -s 3.7.13 && \
-    pyenv install -s 3.8.13 && \
-    pyenv install -s 3.9.12 && \
-    pyenv install -s 3.10.4 && \
-    pyenv global 3.9.12 && \
+    pyenv install -s 3.8.18 && \
+    pyenv install -s 3.9.18 && \
+    pyenv install -s 3.10.13 && \
+    pyenv install -s 3.11.6 && \
+    pyenv install -s 3.12.0 && \
+    pyenv global 3.11.6 && \
     pyenv rehash
 
 # Install poetry to manage our python project
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2b2fdee..406e745 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -17,14 +17,14 @@ jobs:
     steps:
 
       - name: Checkout Code
-        uses: actions/checkout@v3.5.3
+        uses: actions/checkout@v4.1.1
         with:
           fetch-depth: 0
 
-      - name: Setup Python 3.9
-        uses: actions/setup-python@v4.7.0
+      - name: Setup Python 3.11
+        uses: actions/setup-python@v4.7.1
         with:
-          python-version: '3.9'
+          python-version: '3.11'
           architecture: x64
 
       - name: Install dependencies
@@ -72,12 +72,12 @@ jobs:
     needs: tag
     steps:
       - name: Checkout Code
-        uses: actions/checkout@v3.5.3
+        uses: actions/checkout@v4.1.1
 
-      - name: Setup Python 3.9
-        uses: actions/setup-python@v4.7.0
+      - name: Setup Python 3.11
+        uses: actions/setup-python@v4.7.1
         with:
-          python-version: '3.9'
+          python-version: '3.11'
           architecture: x64
 
       - name: Setup Poetry
@@ -104,7 +104,7 @@ jobs:
     needs: [tag, publish]
     steps:
       - name: Create Draft Release
-        uses: release-drafter/release-drafter@v5.24.0
+        uses: release-drafter/release-drafter@v5.25.0
         if: github.base_ref == 'develop'
         with:
           tag: ${{needs.tag.outputs.tag}}
@@ -114,7 +114,7 @@ jobs:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
       - name: Create Release
-        uses: release-drafter/release-drafter@v5.24.0
+        uses: release-drafter/release-drafter@v5.25.0
         if: github.base_ref == 'master'
         with:
           tag: ${{needs.tag.outputs.tag}}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index cb0a00a..db606a7 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -9,7 +9,7 @@ repos:
       language: python
       language_version: python3
       types: ["python"]
-      args: ["-rs", "black", "-p", "3.9"]
+      args: ["-rs", "black", "-p", "3.11"]
       require_serial: true
       additional_dependencies: []
     - id: lint
@@ -18,7 +18,7 @@ repos:
       language: python
       language_version: python3
       types: ["python"]
-      args: ["-rs", "lint", "-p", "3.9"]
+      args: ["-rs", "lint", "-p", "3.11"]
       require_serial: true
       additional_dependencies: []
     - id: mypy
@@ -27,7 +27,7 @@ repos:
       language: python
       language_version: python3
       types: ["python"]
-      args: ["-rs", "mypy", "-p", "3.9"]
+      args: ["-rs", "mypy", "-p", "3.11"]
       require_serial: true
       additional_dependencies: []
     - id: tests
@@ -36,6 +36,6 @@ repos:
       language: python
       language_version: python3
       types: ["python"]
-      args: ["-rs", "tests", "-p", "3.9"]
+      args: ["-rs", "tests", "-p", "3.11"]
       require_serial: true
       additional_dependencies: []
\ No newline at end of file
diff --git a/.python-version b/.python-version
index 4dd242e..9453937 100644
--- a/.python-version
+++ b/.python-version
@@ -1,4 +1,5 @@
-3.9.12
-3.7.13
-3.8.13
-3.10.4
+3.11.6
+3.12.0
+3.10.13
+3.9.18
+3.8.18
\ No newline at end of file
diff --git a/.scripts/requirements.txt b/.scripts/requirements.txt
index a24a926..bdd688e 100644
--- a/.scripts/requirements.txt
+++ b/.scripts/requirements.txt
@@ -1,2 +1,2 @@
 requests==2.31.0
-semver==3.0.1
+semver==3.0.2
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 447b2ff..2cf2f2a 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,7 +5,7 @@
   "version": "0.2.0",
   "configurations": [
     {
-      "name": "Python: Current File",
+      "name": "cf2tf: Test Template",
       "type": "python",
       "request": "launch",
       "program": "${workspaceFolder}/src/cf2tf/app.py",
@@ -13,9 +13,9 @@
       "args": [
         "-v",
         "debug",
-        "tests/data/log_bucket.yaml"
+        "tests/data/templates/log_bucket.yaml"
       ],
       "justMyCode": true
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/README.md b/README.md
index c0d05d2..068e492 100644
--- a/README.md
+++ b/README.md
@@ -76,7 +76,7 @@
 
 ### Prerequisites
 
-Cloudformation 2 Terraform requires python >= 3.7
+Cloudformation 2 Terraform requires python >= 3.8
 
 ### Installation
 
@@ -85,6 +85,12 @@ cf2tf is available as an easy to install pip package.
 pip install cf2tf
 ```
 
+If you are a [Homebrew](https://brew.sh/) user, can you install via brew:
+
+```
+$ brew install cf2tf
+```
+
 ## Usage
 
 To convert a template to terraform.
@@ -119,13 +125,13 @@ Contributions are what make the open-source community such an amazing place to l
 
 
 
-This project uses poetry to manage dependencies and pre-commit to run formatting, linting and tests. You will need to have both installed to your system as well as python 3.9.
+This project uses poetry to manage dependencies and pre-commit to run formatting, linting and tests. You will need to have both installed to your system as well as python 3.11.
 
 1. Fork the Project
-2. Setup the environment.  
+2. Setup the environment.
    This project uses vscode devcontainer to provide a completly configured development environment. If you are using vscode and have the remote container extension installed, you should be asked to use the devcontainer when you open this project inside of vscode.
 
-   If you are not using devcontainers then you will need to have python installed. Install the `poetry`, `nox`, `nox_poetry` and `pre-commit` packages. Then run `poetry install` and `pre-commit install` commands. 
+   If you are not using devcontainers then you will need to have python installed. Install the `poetry`, `nox`, `nox_poetry` and `pre-commit` packages. Then run `poetry install` and `pre-commit install` commands.
 
    Most of the steps can be found in the [Dockerfile](.devcontainer/Dockerfile).
 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
diff --git a/noxfile.py b/noxfile.py
index 333de03..441facd 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -17,7 +17,7 @@
     raise SystemExit(dedent(message)) from None
 
 
-python_versions = ["3.10", "3.9", "3.8", "3.7"]
+python_versions = ["3.12", "3.11", "3.10", "3.9", "3.8"]
 
 nox.options.sessions = (
     # "pre-commit",
diff --git a/poetry.lock b/poetry.lock
index 7e51b20..9bbe40e 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2,33 +2,29 @@
 
 [[package]]
 name = "black"
-version = "23.7.0"
+version = "23.11.0"
 description = "The uncompromising code formatter."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"},
-    {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"},
-    {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"},
-    {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"},
-    {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"},
-    {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"},
-    {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"},
-    {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"},
-    {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"},
-    {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"},
-    {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"},
-    {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"},
-    {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"},
-    {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"},
-    {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"},
-    {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"},
-    {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"},
-    {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"},
-    {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"},
-    {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"},
-    {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"},
-    {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"},
+    {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"},
+    {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"},
+    {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"},
+    {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"},
+    {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"},
+    {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"},
+    {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"},
+    {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"},
+    {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"},
+    {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"},
+    {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"},
+    {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"},
+    {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"},
+    {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"},
+    {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"},
+    {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"},
+    {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"},
+    {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"},
 ]
 
 [package.dependencies]
@@ -38,7 +34,7 @@ packaging = ">=22.0"
 pathspec = ">=0.9.0"
 platformdirs = ">=2"
 tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
-typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
+typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
 
 [package.extras]
 colorama = ["colorama (>=0.4.3)"]
@@ -89,13 +85,13 @@ unicode-backport = ["unicodedata2"]
 
 [[package]]
 name = "click"
-version = "8.1.6"
+version = "8.1.7"
 description = "Composable command line interface toolkit"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"},
-    {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"},
+    {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
+    {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
 ]
 
 [package.dependencies]
@@ -128,71 +124,63 @@ files = [
 
 [[package]]
 name = "coverage"
-version = "7.2.7"
+version = "7.3.2"
 description = "Code coverage measurement for Python"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 files = [
-    {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"},
-    {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"},
-    {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"},
-    {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"},
-    {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"},
-    {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"},
-    {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"},
-    {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"},
-    {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"},
-    {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"},
-    {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"},
-    {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"},
-    {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"},
-    {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"},
-    {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"},
-    {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"},
-    {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"},
-    {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"},
-    {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"},
-    {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"},
-    {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"},
-    {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"},
-    {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"},
-    {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"},
-    {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"},
-    {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"},
-    {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"},
-    {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"},
-    {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"},
-    {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"},
-    {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"},
-    {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"},
-    {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"},
-    {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"},
-    {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"},
-    {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"},
-    {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"},
-    {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"},
-    {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"},
-    {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"},
-    {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"},
-    {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"},
-    {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"},
-    {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"},
-    {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"},
-    {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"},
-    {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"},
-    {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"},
-    {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"},
-    {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"},
-    {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"},
-    {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"},
-    {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"},
-    {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"},
-    {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"},
-    {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"},
-    {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"},
-    {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"},
-    {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"},
-    {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"},
+    {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"},
+    {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"},
+    {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"},
+    {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"},
+    {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"},
+    {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"},
+    {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"},
+    {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"},
+    {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"},
+    {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"},
+    {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"},
+    {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"},
+    {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"},
+    {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"},
+    {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"},
+    {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"},
+    {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"},
+    {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"},
+    {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"},
+    {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"},
+    {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"},
+    {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"},
+    {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"},
+    {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"},
+    {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"},
+    {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"},
+    {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"},
+    {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"},
+    {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"},
+    {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"},
+    {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"},
+    {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"},
+    {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"},
+    {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"},
+    {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"},
+    {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"},
+    {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"},
+    {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"},
+    {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"},
+    {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"},
+    {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"},
+    {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"},
+    {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"},
+    {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"},
+    {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"},
+    {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"},
+    {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"},
+    {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"},
+    {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"},
+    {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"},
+    {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"},
+    {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"},
 ]
 
 [package.dependencies]
@@ -217,19 +205,19 @@ test = ["pytest (>=6)"]
 
 [[package]]
 name = "flake8"
-version = "6.0.0"
+version = "6.1.0"
 description = "the modular source code checker: pep8 pyflakes and co"
 optional = false
 python-versions = ">=3.8.1"
 files = [
-    {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"},
-    {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"},
+    {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"},
+    {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"},
 ]
 
 [package.dependencies]
 mccabe = ">=0.7.0,<0.8.0"
-pycodestyle = ">=2.10.0,<2.11.0"
-pyflakes = ">=3.0.0,<3.1.0"
+pycodestyle = ">=2.11.0,<2.12.0"
+pyflakes = ">=3.1.0,<3.2.0"
 
 [[package]]
 name = "flake8-black"
@@ -266,18 +254,21 @@ smmap = ">=3.0.1,<6"
 
 [[package]]
 name = "gitpython"
-version = "3.1.32"
+version = "3.1.40"
 description = "GitPython is a Python library used to interact with Git repositories"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "GitPython-3.1.32-py3-none-any.whl", hash = "sha256:e3d59b1c2c6ebb9dfa7a184daf3b6dd4914237e7488a1730a6d8f6f5d0b4187f"},
-    {file = "GitPython-3.1.32.tar.gz", hash = "sha256:8d9b8cb1e80b9735e8717c9362079d3ce4c6e5ddeebedd0361b228c3a67a62f6"},
+    {file = "GitPython-3.1.40-py3-none-any.whl", hash = "sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a"},
+    {file = "GitPython-3.1.40.tar.gz", hash = "sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4"},
 ]
 
 [package.dependencies]
 gitdb = ">=4.0.1,<5"
 
+[package.extras]
+test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-sugar"]
+
 [[package]]
 name = "idna"
 version = "3.3"
@@ -313,37 +304,38 @@ files = [
 
 [[package]]
 name = "mypy"
-version = "1.4.1"
+version = "1.7.1"
 description = "Optional static typing for Python"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 files = [
-    {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"},
-    {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"},
-    {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"},
-    {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"},
-    {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"},
-    {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"},
-    {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"},
-    {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"},
-    {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"},
-    {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"},
-    {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"},
-    {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"},
-    {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"},
-    {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"},
-    {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"},
-    {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"},
-    {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"},
-    {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"},
-    {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"},
-    {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"},
-    {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"},
-    {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"},
-    {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"},
-    {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"},
-    {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"},
-    {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"},
+    {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"},
+    {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"},
+    {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"},
+    {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"},
+    {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"},
+    {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"},
+    {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"},
+    {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"},
+    {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"},
+    {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"},
+    {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"},
+    {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"},
+    {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"},
+    {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"},
+    {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"},
+    {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"},
+    {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"},
+    {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"},
+    {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"},
+    {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"},
+    {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"},
+    {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"},
+    {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"},
+    {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"},
+    {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"},
+    {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"},
+    {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"},
 ]
 
 [package.dependencies]
@@ -354,7 +346,7 @@ typing-extensions = ">=4.1.0"
 [package.extras]
 dmypy = ["psutil (>=4.0)"]
 install-types = ["pip"]
-python2 = ["typed-ast (>=1.4.0,<2)"]
+mypyc = ["setuptools (>=50)"]
 reports = ["lxml"]
 
 [[package]]
@@ -422,35 +414,35 @@ testing = ["pytest", "pytest-benchmark"]
 
 [[package]]
 name = "pycodestyle"
-version = "2.10.0"
+version = "2.11.0"
 description = "Python style guide checker"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
 files = [
-    {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"},
-    {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"},
+    {file = "pycodestyle-2.11.0-py2.py3-none-any.whl", hash = "sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8"},
+    {file = "pycodestyle-2.11.0.tar.gz", hash = "sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0"},
 ]
 
 [[package]]
 name = "pyflakes"
-version = "3.0.1"
+version = "3.1.0"
 description = "passive checker of Python programs"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
 files = [
-    {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"},
-    {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"},
+    {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"},
+    {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"},
 ]
 
 [[package]]
 name = "pytest"
-version = "7.4.0"
+version = "7.4.3"
 description = "pytest: simple powerful testing with Python"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"},
-    {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"},
+    {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"},
+    {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"},
 ]
 
 [package.dependencies]
@@ -482,19 +474,6 @@ pytest = ">=4.6"
 [package.extras]
 testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
 
-[[package]]
-name = "python-levenshtein"
-version = "0.12.2"
-description = "Python extension for computing string edit distances and similarities."
-optional = false
-python-versions = "*"
-files = [
-    {file = "python-Levenshtein-0.12.2.tar.gz", hash = "sha256:dc2395fbd148a1ab31090dd113c366695934b9e85fe5a4b2a032745efd0346f6"},
-]
-
-[package.dependencies]
-setuptools = "*"
-
 [[package]]
 name = "pyyaml"
 version = "6.0.1"
@@ -507,6 +486,7 @@ files = [
     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
+    {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
     {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
     {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
     {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
@@ -514,8 +494,15 @@ files = [
     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
+    {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
     {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
     {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
+    {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
+    {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
+    {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
+    {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
+    {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
+    {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
     {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
     {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
     {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
@@ -532,6 +519,7 @@ files = [
     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
+    {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
     {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
     {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
     {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
@@ -539,11 +527,114 @@ files = [
     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
+    {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
     {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
     {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
     {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
 ]
 
+[[package]]
+name = "rapidfuzz"
+version = "3.5.2"
+description = "rapid fuzzy string matching"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "rapidfuzz-3.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1a047d6e58833919d742bbc0dfa66d1de4f79e8562ee195007d3eae96635df39"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22877c027c492b7dc7e3387a576a33ed5aad891104aa90da2e0844c83c5493ef"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e0f448b0eacbcc416feb634e1232a48d1cbde5e60f269c84e4fb0912f7bbb001"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05146497672f869baf41147d5ec1222788c70e5b8b0cfcd6e95597c75b5b96b"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f2df3968738a38d2a0058b5e721753f5d3d602346a1027b0dde31b0476418f3"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5afc1fcf1830f9bb87d3b490ba03691081b9948a794ea851befd2643069a30c1"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84be69ea65f64fa01e5c4976be9826a5aa949f037508887add42da07420d65d6"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8658c1045766e87e0038323aa38b4a9f49b7f366563271f973c8890a98aa24b5"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:852b3f93c15fce58b8dc668bd54123713bfdbbb0796ba905ea5df99cfd083132"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:12424a06ad9bd0cbf5f7cea1015e78d924a0034a0e75a5a7b39c0703dcd94095"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b4e9ded8e80530bd7205a7a2b01802f934a4695ca9e9fbe1ce9644f5e0697864"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:affb8fe36157c2dc8a7bc45b6a1875eb03e2c49167a1d52789144bdcb7ab3b8c"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1d33a622572d384f4c90b5f7a139328246ab5600141e90032b521c2127bd605"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-win32.whl", hash = "sha256:2cf9f2ed4a97b388cffd48d534452a564c2491f68f4fd5bc140306f774ceb63a"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:6541ffb70097885f7302cd73e2efd77be99841103023c2f9408551f27f45f7a5"},
+    {file = "rapidfuzz-3.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:1dd2542e5103fb8ca46500a979ae14d1609dcba11d2f9fe01e99eec03420e193"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bff7d3127ebc5cd908f3a72f6517f31f5247b84666137556a8fcc5177c560939"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fdfdb3685b631d8efbb6d6d3d86eb631be2b408d9adafcadc11e63e3f9c96dec"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97b043fe8185ec53bb3ff0e59deb89425c0fc6ece6e118939963aab473505801"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a4a7832737f87583f3863dc62e6f56dd4a9fefc5f04a7bdcb4c433a0f36bb1b"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d876dba9a11fcf60dcf1562c5a84ef559db14c2ceb41e1ad2d93cd1dc085889"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa4c0612893716bbb6595066ca9ecb517c982355abe39ba9d1f4ab834ace91ad"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:120316824333e376b88b284724cfd394c6ccfcb9818519eab5d58a502e5533f0"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cdbe8e80cc186d55f748a34393533a052d855357d5398a1ccb71a5021b58e8d"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1062425c8358a547ae5ebad148f2e0f02417716a571b803b0c68e4d552e99d32"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66be181965aff13301dd5f9b94b646ce39d99c7fe2fd5de1656f4ca7fafcb38c"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:53df7aea3cf301633cfa2b4b2c2d2441a87dfc878ef810e5b4eddcd3e68723ad"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:76639dca5eb0afc6424ac5f42d43d3bd342ac710e06f38a8c877d5b96de09589"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:27689361c747b5f7b8a26056bc60979875323f1c3dcaaa9e2fec88f03b20a365"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-win32.whl", hash = "sha256:99c9fc5265566fb94731dc6826f43c5109e797078264e6389a36d47814473692"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:666928ee735562a909d81bd2f63207b3214afd4ca41f790ab3025d066975c814"},
+    {file = "rapidfuzz-3.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:d55de67c48f06b7772541e8d4c062a2679205799ce904236e2836cb04c106442"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:04e1e02b182283c43c866e215317735e91d22f5d34e65400121c04d5ed7ed859"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:365e544aba3ac13acf1a62cb2e5909ad2ba078d0bfc7d69b1f801dfd673b9782"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b61f77d834f94b0099fa9ed35c189b7829759d4e9c2743697a130dd7ba62259f"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43fb368998b9703fa8c63db292a8ab9e988bf6da0c8a635754be8e69da1e7c1d"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25510b5d142c47786dbd27cfd9da7cae5bdea28d458379377a3644d8460a3404"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf3093443751e5a419834162af358d1e31dec75f84747a91dbbc47b2c04fc085"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fbaf546f15a924613f89d609ff66b85b4f4c2307ac14d93b80fe1025b713138"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32d580df0e130ed85400ff77e1c32d965e9bc7be29ac4072ab637f57e26d29fb"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:358a0fbc49343de20fee8ebdb33c7fa8f55a9ff93ff42d1ffe097d2caa248f1b"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fb379ac0ddfc86c5542a225d194f76ed468b071b6f79ff57c4b72e635605ad7d"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7fb21e182dc6d83617e88dea002963d5cf99cf5eabbdbf04094f503d8fe8d723"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c04f9f1310ce414ab00bdcbf26d0906755094bfc59402cb66a7722c6f06d70b2"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6da61cc38c1a95efc5edcedf258759e6dbab73191651a28c5719587f32a56ad"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-win32.whl", hash = "sha256:f823fd1977071486739f484e27092765d693da6beedaceece54edce1dfeec9b2"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:a8162d81486de85ab1606e48e076431b66d44cf431b2b678e9cae458832e7147"},
+    {file = "rapidfuzz-3.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:dfc63fabb7d8da8483ca836bae7e55766fe39c63253571e103c034ba8ea80950"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:df8fae2515a1e4936affccac3e7d506dd904de5ff82bc0b1433b4574a51b9bfb"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dd6384780c2a16097d47588844cd677316a90e0f41ef96ff485b62d58de79dcf"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:467a4d730ae3bade87dba6bd769e837ab97e176968ce20591fe8f7bf819115b1"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54576669c1502b751b534bd76a4aeaaf838ed88b30af5d5c1b7d0a3ca5d4f7b5"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abafeb82f85a651a9d6d642a33dc021606bc459c33e250925b25d6b9e7105a2e"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73e14617a520c0f1bc15eb78c215383477e5ca70922ecaff1d29c63c060e04ca"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7cdf92116e9dfe40da17f921cdbfa0039dde9eb158914fa5f01b1e67a20b19cb"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1962d5ccf8602589dbf8e85246a0ee2b4050d82fade1568fb76f8a4419257704"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:db45028eae2fda7a24759c69ebeb2a7fbcc1a326606556448ed43ee480237a3c"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b685abb8b6d97989f6c69556d7934e0e533aa8822f50b9517ff2da06a1d29f23"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:40139552961018216b8cd88f6df4ecbbe984f907a62a5c823ccd907132c29a14"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0fef4705459842ef8f79746d6f6a0b5d2b6a61a145d7d8bbe10b2e756ea337c8"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6b2ad5516f7068c7d9cbcda8ac5906c589e99bc427df2e1050282ee2d8bc2d58"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-win32.whl", hash = "sha256:2da3a24c2f7dfca7f26ba04966b848e3bbeb93e54d899908ff88dfe3e1def9dc"},
+    {file = "rapidfuzz-3.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:e3f2be79d4114d01f383096dbee51b57df141cb8b209c19d0cf65f23a24e75ba"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:089a7e96e5032821af5964d8457fcb38877cc321cdd06ad7c5d6e3d852264cb9"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75d8a52bf8d1aa2ac968ae4b21b83b94fc7e5ea3dfbab34811fc60f32df505b2"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2bacce6bbc0362f0789253424269cc742b1f45e982430387db3abe1d0496e371"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5fd627e604ddc02db2ddb9ddc4a91dd92b7a6d6378fcf30bb37b49229072b89"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2e8b369f23f00678f6e673572209a5d3b0832f4991888e3df97af7b8b9decf3"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c29958265e4c2b937269e804b8a160c027ee1c2627d6152655008a8b8083630e"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00be97f9219355945c46f37ac9fa447046e6f7930f7c901e5d881120d1695458"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0d8d57e0f556ef38c24fee71bfe8d0db29c678bff2acd1819fc1b74f331c2"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de89585268ed8ee44e80126814cae63ff6b00d08416481f31b784570ef07ec59"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:908ff2de9c442b379143d1da3c886c63119d4eba22986806e2533cee603fe64b"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:54f0061028723c026020f5bb20649c22bc8a0d9f5363c283bdc5901d4d3bff01"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b581107ec0c610cdea48b25f52030770be390db4a9a73ca58b8d70fa8a5ec32e"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1d5a686ea258931aaa38019204bdc670bbe14b389a230b1363d84d6cf4b9dc38"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-win32.whl", hash = "sha256:97f811ca7709c6ee8c0b55830f63b3d87086f4abbcbb189b4067e1cd7014db7b"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:58ee34350f8c292dd24a050186c0e18301d80da904ef572cf5fda7be6a954929"},
+    {file = "rapidfuzz-3.5.2-cp39-cp39-win_arm64.whl", hash = "sha256:c5075ce7b9286624cafcf36720ef1cfb2946d75430b87cb4d1f006e82cd71244"},
+    {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:af5221e4f7800db3e84c46b79dba4112e3b3cc2678f808bdff4fcd2487073846"},
+    {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8501d7875b176930e6ed9dbc1bc35adb37ef312f6106bd6bb5c204adb90160ac"},
+    {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e414e1ca40386deda4291aa2d45062fea0fbaa14f95015738f8bb75c4d27f862"},
+    {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2059cd73b7ea779a9307d7a78ed743f0e3d33b88ccdcd84569abd2953cd859f"},
+    {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:58e3e21f6f13a7cca265cce492bc797425bd4cb2025fdd161a9e86a824ad65ce"},
+    {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b847a49377e64e92e11ef3d0a793de75451526c83af015bdafdd5d04de8a058a"},
+    {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a42c7a8c62b29c4810e39da22b42524295fcb793f41c395c2cb07c126b729e83"},
+    {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51b5166be86e09e011e92d9862b1fe64c4c7b9385f443fb535024e646d890460"},
+    {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f808dcb0088a7a496cc9895e66a7b8de55ffea0eb9b547c75dfb216dd5f76ed"},
+    {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d4b05a8f4ab7e7344459394094587b033fe259eea3a8720035e8ba30e79ab39b"},
+    {file = "rapidfuzz-3.5.2.tar.gz", hash = "sha256:9e9b395743e12c36a3167a3a9fd1b4e11d92fb0aa21ec98017ee6df639ed385e"},
+]
+
+[package.extras]
+full = ["numpy"]
+
 [[package]]
 name = "requests"
 version = "2.31.0"
@@ -565,22 +656,6 @@ urllib3 = ">=1.21.1,<3"
 socks = ["PySocks (>=1.5.6,!=1.5.7)"]
 use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
 
-[[package]]
-name = "setuptools"
-version = "65.4.0"
-description = "Easily download, build, install, upgrade, and uninstall Python packages"
-optional = false
-python-versions = ">=3.7"
-files = [
-    {file = "setuptools-65.4.0-py3-none-any.whl", hash = "sha256:c2d2709550f15aab6c9110196ea312f468f41cd546bceb24127a1be6fdcaeeb1"},
-    {file = "setuptools-65.4.0.tar.gz", hash = "sha256:a8f6e213b4b0661f590ccf40de95d28a177cd747d098624ad3f69c40287297e9"},
-]
-
-[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
-testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
-
 [[package]]
 name = "six"
 version = "1.16.0"
@@ -605,20 +680,17 @@ files = [
 
 [[package]]
 name = "thefuzz"
-version = "0.19.0"
+version = "0.20.0"
 description = "Fuzzy string matching in python"
 optional = false
-python-versions = "*"
+python-versions = ">=3.7"
 files = [
-    {file = "thefuzz-0.19.0-py2.py3-none-any.whl", hash = "sha256:4fcdde8e40f5ca5e8106bc7665181f9598a9c8b18b0a4d38c41a095ba6788972"},
-    {file = "thefuzz-0.19.0.tar.gz", hash = "sha256:6f7126db2f2c8a54212b05e3a740e45f4291c497d75d20751728f635bb74aa3d"},
+    {file = "thefuzz-0.20.0-py3-none-any.whl", hash = "sha256:bd2b657a12bd8518917d2d71c53125368706233b822fac688fca956730154388"},
+    {file = "thefuzz-0.20.0.tar.gz", hash = "sha256:a25e49786b1c4603c7fc6e2d69e6bc660982a2919698b536ff8354e0631cc40d"},
 ]
 
 [package.dependencies]
-python-levenshtein = {version = ">=0.12", optional = true, markers = "extra == \"speedup\""}
-
-[package.extras]
-speedup = ["python-levenshtein (>=0.12)"]
+rapidfuzz = ">=3.0.0,<4.0.0"
 
 [[package]]
 name = "tomli"
@@ -661,4 +733,4 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.8.1"
-content-hash = "5281f68712e0a3107d54649c9b1151836490d110ac7ed6d31e13d2af9d571b14"
+content-hash = "da5a36e7b80e6f434326cb428b6d2438cef595613508c276e290443575567412"
diff --git a/pyproject.toml b/pyproject.toml
index 5977f07..d9b2408 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,7 +17,7 @@ cfn-flip = "^1.3.0"
 PyYAML = "^6.0"
 click = "^8.1.2"
 GitPython = "^3.1.27"
-thefuzz = {extras = ["speedup"], version = "^0.19.0"}
+thefuzz = {extras = ["speedup"], version = "^0.20.0"}
 click-log = "^0.4.0"
 requests = "^2.27.1"
 pytest = "^7.1.2"
diff --git a/src/cf2tf/cloudformation/_template.py b/src/cf2tf/cloudformation/_template.py
index 978511d..a868e92 100644
--- a/src/cf2tf/cloudformation/_template.py
+++ b/src/cf2tf/cloudformation/_template.py
@@ -4,7 +4,7 @@
 from pathlib import Path
 from typing import Any, Dict, Optional, Union
 
-import yaml
+import yaml  # type: ignore
 from cfn_tools import dump_yaml, load_yaml  # type: ignore
 
 log = logging.getLogger("cf2tf")
diff --git a/src/cf2tf/conversion/expressions.py b/src/cf2tf/conversion/expressions.py
index 938e395..f1ea705 100644
--- a/src/cf2tf/conversion/expressions.py
+++ b/src/cf2tf/conversion/expressions.py
@@ -1033,6 +1033,7 @@ def wrap_in_curlys(input: str):
         "Ref": ref,
     },
     "Fn::Select": {
+        "Fn::Cidr": cidr,
         "Fn::FindInMap": find_in_map,
         "Fn::GetAtt": get_att,
         "Fn::GetAZs": get_azs,
diff --git a/src/cf2tf/convert.py b/src/cf2tf/convert.py
index 6526238..8afa334 100644
--- a/src/cf2tf/convert.py
+++ b/src/cf2tf/convert.py
@@ -10,12 +10,17 @@
 import cf2tf.conversion.expressions as functions
 import cf2tf.terraform._configuration as config
 import cf2tf.terraform.doc_file as doc_file
-from cf2tf.conversion.overrides import OVERRIDE_DISPATCH, GLOBAL_OVERRIDES
+from cf2tf.conversion.overrides import GLOBAL_OVERRIDES, OVERRIDE_DISPATCH
 from cf2tf.terraform.blocks import Block, Locals, Output, Resource, Variable
 from cf2tf.terraform.hcl2 import AllTypes
 from cf2tf.terraform.hcl2.complex import ListType, MapType
 from cf2tf.terraform.hcl2.custom import CommentType, LiteralType
-from cf2tf.terraform.hcl2.primitive import NumberType, StringType, TerraformType
+from cf2tf.terraform.hcl2.primitive import (
+    BooleanType,
+    NumberType,
+    StringType,
+    TerraformType,
+)
 
 if TYPE_CHECKING:
     from cf2tf.terraform.code import SearchManager
@@ -145,7 +150,6 @@ def resolve_values(  # noqa: max-complexity=13
         data: Any,
         allowed_func: functions.Dispatch,
         prev_func: Optional[str] = None,
-        inside_function=False,
     ):
         """Recurses through a Cloudformation template. Solving all
         references and variables along the way.
@@ -166,11 +170,33 @@ def resolve_values(  # noqa: max-complexity=13
                 if key == "Ref":
                     return functions.ref(self, value)
 
-                if "Fn::" not in key and not (
-                    key == "Condition" and inside_function is True
-                ):
+                # This takes care of keys that not intrinsic functions,
+                #  except for the condition func
+                if "Fn::" not in key and key != "Condition":
                     data[key] = self.resolve_values(
-                        value, allowed_func, prev_func, inside_function=inside_function
+                        value,
+                        allowed_func,
+                    )
+                    continue
+
+                # Takes care of the tricky 'Condition' key
+                if key == "Condition":
+                    # The real fix is to not resolve every key/value in the entire
+                    # cloudformation template. We should only attempt to resolve what is needed,
+                    # like outputs and resource properties.
+                    # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html
+
+                    if "Properties" in data or "Value" in data:
+                        continue
+
+                    # If it's an intrinsic func
+                    if is_condition_func(value):
+                        return functions.condition(self, value)
+
+                    # Normal key like in an IAM role
+                    data[key] = self.resolve_values(
+                        value,
+                        allowed_func,
                     )
                     continue
 
@@ -178,7 +204,7 @@ def resolve_values(  # noqa: max-complexity=13
                     raise ValueError(f"{key} not allowed to be nested in {prev_func}.")
 
                 value = self.resolve_values(
-                    value, functions.ALLOWED_FUNCTIONS[key], key, inside_function=True
+                    value, functions.ALLOWED_FUNCTIONS[key], key
                 )
 
                 try:
@@ -189,13 +215,12 @@ def resolve_values(  # noqa: max-complexity=13
             return MapType(data)
         elif isinstance(data, list):
             resolved_list_values = [
-                self.resolve_values(
-                    item, allowed_func, prev_func, inside_function=inside_function
-                )
-                for item in data
+                self.resolve_values(item, allowed_func, prev_func) for item in data
             ]
 
             return ListType(resolved_list_values)
+        elif isinstance(data, bool):
+            return BooleanType(data)
         elif isinstance(data, str):
             return StringType(data)
         elif isinstance(data, (int, float)):
@@ -622,3 +647,21 @@ def perform_global_overrides(
             params = override(tc, params)
 
     return params
+
+
+# All the other Cloudformation intrinsic functions start with `Fn:` but for some reason
+# the Condition function does not. This can be problem because
+#  `Condition` is a valid key in an IAM policy but its value is always a Map.
+def is_condition_func(value: Any) -> bool:
+    """Checks if the 'Condition' key is a instrinsic function.
+
+    Args:
+        value (Any): The value of the 'Condition' key.
+
+    Returns:
+        bool: True if we think this `Condition` key is an instrinsic function.
+    """
+    if isinstance(value, str):
+        return True
+
+    return False
diff --git a/src/cf2tf/save.py b/src/cf2tf/save.py
index e013ba2..d7a7300 100644
--- a/src/cf2tf/save.py
+++ b/src/cf2tf/save.py
@@ -8,6 +8,7 @@
 try:
     from typing import Protocol
 except ImportError:
+    # Shouldnt be needed, but just in case
     # Python 3.6 and 3.7
     from typing_extensions import Protocol  # type: ignore
 
diff --git a/src/cf2tf/terraform/doc_file.py b/src/cf2tf/terraform/doc_file.py
index c8d26c5..69fa827 100644
--- a/src/cf2tf/terraform/doc_file.py
+++ b/src/cf2tf/terraform/doc_file.py
@@ -32,16 +32,25 @@ def read_section(docs_path: Path, section_name: str):
 
 
 def parse_section(name: str, file: TextIOWrapper):
-    cur_pos = file.tell()
+    """
+    Parses items from a specific section in a file.
 
-    section_location = find_section(name, file)
+    This function first finds the specified section in the file using the `find_section` function.
+    Then, it parses the items in the found section using the `parse_items` function and returns a list of these items.
 
-    if not section_location:
-        file.seek(cur_pos)
-        return []
+    Args:
+        name (str): The name of the section to parse items from.
+        file (TextIOWrapper): The file to parse items from.
+
+    Returns:
+        List[str]: A list of items found in the section. If no items are found, an empty list is returned.
+    """
+    # Throws an exception if the section is not found
+    find_section(name, file)
 
     items = parse_items(file)
 
+    # sometimes we dont find items and thats ok
     if not items:
         log.debug(f"Unable to find items in section {name} of {file.name}")
 
@@ -49,6 +58,20 @@ def parse_section(name: str, file: TextIOWrapper):
 
 
 def parse_items(file: TextIOWrapper):
+    """
+    Parses attributes from a section in a file.
+
+    This function reads through a file line by line until it finds a line that starts with a "*" character,
+    which indicates an attribute. It then extracts the attribute using a regular expression and adds it to a list.
+    The function stops parsing attributes when it encounters an empty line or a line that starts with a "#" character,
+    which indicates a new section.
+
+    Args:
+        file (TextIOWrapper): The file to parse attributes from.
+
+    Returns:
+        List[str]: A list of attributes found in the section. If no attributes are found, an empty list is returned.
+    """
     attributes: List[str] = []
 
     while True:
@@ -72,7 +95,8 @@ def parse_items(file: TextIOWrapper):
             match = re.search(regex, line)
 
             if not match:
-                raise Exception(f"Did not find an item to parse in {line}")
+                log.debug(f"Did not find an item to parse in {line}")
+                continue
 
             attribute = match.group(1)
 
@@ -104,6 +128,22 @@ def all_sections(docs_path: Path):
 
 
 def find_section(name: str, file: TextIOWrapper):
+    """Searches for a markdown section in a file and returns the position of the section.
+
+    This function reads through a file line by line until it finds a line that starts with a "#" character
+    and contains the specified name. It then returns the position of the next line in the file.
+    If the end of the file is reached without finding the section, it logs a debug message and raises an exception.
+
+    Args:
+        name (str): The name of the section to find
+        file (TextIOWrapper): The file to search
+
+    Raises:
+        Exception: If the section is not found
+
+    Returns:
+        int: The position of the next line after the section
+    """
     while True:
         cur_pos = file.tell()
 
@@ -113,6 +153,4 @@ def find_section(name: str, file: TextIOWrapper):
             return cur_pos + 1
 
         if not line:
-            log.debug(f"Unable to find section {name} in {file.name}")
-            raise Exception()
-            return None
+            raise Exception(f"Unable to find section {name} in {file.name}")
diff --git a/src/cf2tf/terraform/hcl2/primitive.py b/src/cf2tf/terraform/hcl2/primitive.py
index e58be3f..20306fb 100644
--- a/src/cf2tf/terraform/hcl2/primitive.py
+++ b/src/cf2tf/terraform/hcl2/primitive.py
@@ -1,9 +1,10 @@
-from abc import abstractclassmethod
+from abc import abstractmethod
 from typing import Any, Union
 
 try:
     from typing import Protocol
 except ImportError:
+    # Shouldnt be needed, but just in case
     # Python 3.6 and 3.7
     from typing_extensions import Protocol  # type: ignore
 
@@ -20,7 +21,8 @@ class TerraformType(Protocol):
     def __str__(self) -> str:
         return self.render(0)
 
-    @abstractclassmethod
+    @classmethod
+    @abstractmethod
     def render(self, indent: int) -> str:
         raise NotImplementedError
 
@@ -89,4 +91,24 @@ def __eq__(self, __x: object) -> bool:
         return __x == self.value
 
 
-PrimitiveTypes = Union[StringType, NumberType, NullType]
+class BooleanType(int, TerraformType):
+    """A value that represents a boolean."""
+
+    def __init__(self, value: bool) -> None:
+        """Default constructor
+
+        Args:
+            value (bool): The value for this Terraform type.
+        """
+        super().__init__()
+
+        self.value: bool = value
+
+    def __str__(self) -> str:
+        return self.render()
+
+    def render(self, _=0):
+        return str(self.value).lower()
+
+
+PrimitiveTypes = Union[StringType, NumberType, NullType, BooleanType]


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e55cf20..a50b58b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -12,15 +12,15 @@ on:
 jobs:
   single:
     runs-on: ubuntu-latest
-    name: Python 3.9
+    name: Python 3.11
     steps:
       - name: Checkout Code
-        uses: actions/checkout@v3.5.3
+        uses: actions/checkout@v4.1.1
 
       - name: Setup Latest Python
-        uses: actions/setup-python@v4.7.0
+        uses: actions/setup-python@v4.7.1
         with:
-          python-version: 3.9
+          python-version: 3.11
           architecture: x64
 
       - name: Setup Poetry
@@ -32,10 +32,10 @@ jobs:
         run: pip install nox nox_poetry coverage[toml]
 
       - name: Run Tests
-        run: nox -s tests -p 3.9
+        run: nox -s tests -p 3.11
 
       - name: Combine Coverage reports
-        run: nox -s coverage -p 3.9
+        run: nox -s coverage -p 3.11
 
       - name: Convert Coverage
         run: coverage xml --fail-under=0
@@ -52,14 +52,14 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ["3.8", "3.10"] # 3.9 is done in the job above
+        python-version: ["3.8", "3.9", "3.10", "3.12"] # 3.11 is done in the job above
     name: Python ${{ matrix.python-version }}
     steps:
       - name: Checkout Code
-        uses: actions/checkout@v3.5.3
+        uses: actions/checkout@v4.1.1
 
       - name: Setup Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v4.7.0
+        uses: actions/setup-python@v4.7.1
         with:
           python-version: ${{ matrix.python-version }}
           architecture: x64
diff --git a/tests/data/templates/04_cluster_bootstrap.yaml b/tests/data/templates/04_cluster_bootstrap.yaml
new file mode 100644
index 0000000..5c63a2f
--- /dev/null
+++ b/tests/data/templates/04_cluster_bootstrap.yaml
@@ -0,0 +1,215 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: Template for OpenShift Cluster Bootstrap (EC2 Instance, Security Groups and IAM)
+
+Parameters:
+  InfrastructureName:
+    AllowedPattern: ^([a-zA-Z][a-zA-Z0-9\-]{0,26})$
+    MaxLength: 27
+    MinLength: 1
+    ConstraintDescription: Infrastructure name must be alphanumeric, start with a letter, and have a maximum of 27 characters.
+    Description: A short, unique cluster ID used to tag cloud resources and identify items owned or used by the cluster.
+    Type: String
+  RhcosAmi:
+    Description: Current Red Hat Enterprise Linux CoreOS AMI to use for bootstrap.
+    Type: AWS::EC2::Image::Id
+  AllowedBootstrapSshCidr:
+    AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|1[0-9]|2[0-9]|3[0-2]))$
+    ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/0-32.
+    Default: 0.0.0.0/0
+    Description: CIDR block to allow SSH access to the bootstrap node.
+    Type: String
+  PublicSubnet:
+    Description: The public subnet to launch the bootstrap node into.
+    Type: AWS::EC2::Subnet::Id
+  MasterSecurityGroupId:
+    Description: The master security group ID for registering temporary rules.
+    Type: AWS::EC2::SecurityGroup::Id
+  VpcId:
+    Description: The VPC-scoped resources will belong to this VPC.
+    Type: AWS::EC2::VPC::Id
+  BootstrapIgnitionLocation:
+    Default: s3://my-s3-bucket/bootstrap.ign
+    Description: Ignition config file location.
+    Type: String
+  AutoRegisterELB:
+    Default: "yes"
+    AllowedValues:
+    - "yes"
+    - "no"
+    Description: Do you want to invoke NLB registration, which requires a Lambda ARN parameter?
+    Type: String
+  RegisterNlbIpTargetsLambdaArn:
+    Description: ARN for NLB IP target registration lambda.
+    Type: String
+  ExternalApiTargetGroupArn:
+    Description: ARN for external API load balancer target group.
+    Type: String
+  InternalApiTargetGroupArn:
+    Description: ARN for internal API load balancer target group.
+    Type: String
+  InternalServiceTargetGroupArn:
+    Description: ARN for internal service load balancer target group.
+    Type: String
+  BootstrapInstanceType:
+    Description: Instance type for the bootstrap EC2 instance
+    Default: "i3.large"
+    Type: String
+
+Metadata:
+  AWS::CloudFormation::Interface:
+    ParameterGroups:
+    - Label:
+        default: "Cluster Information"
+      Parameters:
+      - InfrastructureName
+    - Label:
+        default: "Host Information"
+      Parameters:
+      - RhcosAmi
+      - BootstrapIgnitionLocation
+      - MasterSecurityGroupId
+    - Label:
+        default: "Network Configuration"
+      Parameters:
+      - VpcId
+      - AllowedBootstrapSshCidr
+      - PublicSubnet
+    - Label:
+        default: "Load Balancer Automation"
+      Parameters:
+      - AutoRegisterELB
+      - RegisterNlbIpTargetsLambdaArn
+      - ExternalApiTargetGroupArn
+      - InternalApiTargetGroupArn
+      - InternalServiceTargetGroupArn
+    ParameterLabels:
+      InfrastructureName:
+        default: "Infrastructure Name"
+      VpcId:
+        default: "VPC ID"
+      AllowedBootstrapSshCidr:
+        default: "Allowed SSH Source"
+      PublicSubnet:
+        default: "Public Subnet"
+      RhcosAmi:
+        default: "Red Hat Enterprise Linux CoreOS AMI ID"
+      BootstrapIgnitionLocation:
+        default: "Bootstrap Ignition Source"
+      MasterSecurityGroupId:
+        default: "Master Security Group ID"
+      AutoRegisterELB:
+        default: "Use Provided ELB Automation"
+
+Conditions:
+  DoRegistration: !Equals ["yes", !Ref AutoRegisterELB]
+
+Resources:
+  BootstrapIamRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: "2012-10-17"
+        Statement:
+        - Effect: "Allow"
+          Principal:
+            Service:
+            - "ec2.amazonaws.com"
+          Action:
+          - "sts:AssumeRole"
+      Path: "/"
+      Policies:
+      - PolicyName: !Join ["-", [!Ref InfrastructureName, "bootstrap", "policy"]]
+        PolicyDocument:
+          Version: "2012-10-17"
+          Statement:
+          - Effect: "Allow"
+            Action: "ec2:Describe*"
+            Resource: "*"
+          - Effect: "Allow"
+            Action: "ec2:AttachVolume"
+            Resource: "*"
+          - Effect: "Allow"
+            Action: "ec2:DetachVolume"
+            Resource: "*"
+          - Effect: "Allow"
+            Action: "s3:GetObject"
+            Resource: "*"
+
+  BootstrapInstanceProfile:
+    Type: "AWS::IAM::InstanceProfile"
+    Properties:
+      Path: "/"
+      Roles:
+      - Ref: "BootstrapIamRole"
+
+  BootstrapSecurityGroup:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      GroupDescription: Cluster Bootstrap Security Group
+      SecurityGroupIngress:
+      - IpProtocol: tcp
+        FromPort: 22
+        ToPort: 22
+        CidrIp: !Ref AllowedBootstrapSshCidr
+      - IpProtocol: tcp
+        ToPort: 19531
+        FromPort: 19531
+        CidrIp: 0.0.0.0/0
+      VpcId: !Ref VpcId
+
+  BootstrapInstance:
+    Type: AWS::EC2::Instance
+    Properties:
+      ImageId: !Ref RhcosAmi
+      IamInstanceProfile: !Ref BootstrapInstanceProfile
+      InstanceType: !Ref BootstrapInstanceType
+      NetworkInterfaces:
+      - AssociatePublicIpAddress: "true"
+        DeviceIndex: "0"
+        GroupSet:
+        - !Ref "BootstrapSecurityGroup"
+        - !Ref "MasterSecurityGroupId"
+        SubnetId: !Ref "PublicSubnet"
+      UserData:
+        Fn::Base64: !Sub
+        - '{"ignition":{"config":{"replace":{"source":"${S3Loc}"}},"version":"3.1.0"}}'
+        - {
+          S3Loc: !Ref BootstrapIgnitionLocation
+        }
+
+  RegisterBootstrapApiTarget:
+    Condition: DoRegistration
+    Type: Custom::NLBRegister
+    Properties:
+      ServiceToken: !Ref RegisterNlbIpTargetsLambdaArn
+      TargetArn: !Ref ExternalApiTargetGroupArn
+      TargetIp: !GetAtt BootstrapInstance.PrivateIp
+
+  RegisterBootstrapInternalApiTarget:
+    Condition: DoRegistration
+    Type: Custom::NLBRegister
+    Properties:
+      ServiceToken: !Ref RegisterNlbIpTargetsLambdaArn
+      TargetArn: !Ref InternalApiTargetGroupArn
+      TargetIp: !GetAtt BootstrapInstance.PrivateIp
+
+  RegisterBootstrapInternalServiceTarget:
+    Condition: DoRegistration
+    Type: Custom::NLBRegister
+    Properties:
+      ServiceToken: !Ref RegisterNlbIpTargetsLambdaArn
+      TargetArn: !Ref InternalServiceTargetGroupArn
+      TargetIp: !GetAtt BootstrapInstance.PrivateIp
+
+Outputs:
+  BootstrapInstanceId:
+    Description: Bootstrap Instance ID.
+    Value: !Ref BootstrapInstance
+
+  BootstrapPublicIp:
+    Description: The bootstrap node public IP address.
+    Value: !GetAtt BootstrapInstance.PublicIp
+
+  BootstrapPrivateIp:
+    Description: The bootstrap node private IP address.
+    Value: !GetAtt BootstrapInstance.PrivateIp
diff --git a/tests/data/templates/05_cluster_master_nodes.yaml b/tests/data/templates/05_cluster_master_nodes.yaml
new file mode 100644
index 0000000..3fb5769
--- /dev/null
+++ b/tests/data/templates/05_cluster_master_nodes.yaml
@@ -0,0 +1,299 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: Template for OpenShift Cluster Node Launch (EC2 master instances)
+
+Parameters:
+  InfrastructureName:
+    AllowedPattern: ^([a-zA-Z][a-zA-Z0-9\-]{0,26})$
+    MaxLength: 27
+    MinLength: 1
+    ConstraintDescription: Infrastructure name must be alphanumeric, start with a letter, and have a maximum of 27 characters.
+    Description: A short, unique cluster ID used to tag nodes for the kubelet cloud provider.
+    Type: String
+  RhcosAmi:
+    Description: Current Red Hat Enterprise Linux CoreOS AMI to use for bootstrap.
+    Type: AWS::EC2::Image::Id
+  AutoRegisterDNS:
+    Default: ""
+    Description: unused
+    Type: String
+  PrivateHostedZoneId:
+    Default: ""
+    Description: unused
+    Type: String
+  PrivateHostedZoneName:
+    Default: ""
+    Description: unused
+    Type: String
+  Master0Subnet:
+    Description: The subnets, recommend private, to launch the master nodes into.
+    Type: AWS::EC2::Subnet::Id
+  Master1Subnet:
+    Description: The subnets, recommend private, to launch the master nodes into.
+    Type: AWS::EC2::Subnet::Id
+  Master2Subnet:
+    Description: The subnets, recommend private, to launch the master nodes into.
+    Type: AWS::EC2::Subnet::Id
+  MasterSecurityGroupId:
+    Description: The master security group ID to associate with master nodes.
+    Type: AWS::EC2::SecurityGroup::Id
+  IgnitionLocation:
+    Default: https://api-int.$CLUSTER_NAME.$DOMAIN:22623/config/master
+    Description: Ignition config file location.
+    Type: String
+  CertificateAuthorities:
+    Default: data:text/plain;charset=utf-8;base64,ABC...xYz==
+    Description: Base64 encoded certificate authority string to use.
+    Type: String
+  MasterInstanceProfileName:
+    Description: IAM profile to associate with master nodes.
+    Type: String
+  MasterInstanceType:
+    Default: m5.xlarge
+    Type: String
+
+  AutoRegisterELB:
+    Default: "yes"
+    AllowedValues:
+    - "yes"
+    - "no"
+    Description: Do you want to invoke NLB registration, which requires a Lambda ARN parameter?
+    Type: String
+  RegisterNlbIpTargetsLambdaArn:
+    Description: ARN for NLB IP target registration lambda. Supply the value from the cluster infrastructure or select "no" for AutoRegisterELB.
+    Type: String
+  ExternalApiTargetGroupArn:
+    Description: ARN for external API load balancer target group. Supply the value from the cluster infrastructure or select "no" for AutoRegisterELB.
+    Type: String
+  InternalApiTargetGroupArn:
+    Description: ARN for internal API load balancer target group. Supply the value from the cluster infrastructure or select "no" for AutoRegisterELB.
+    Type: String
+  InternalServiceTargetGroupArn:
+    Description: ARN for internal service load balancer target group. Supply the value from the cluster infrastructure or select "no" for AutoRegisterELB.
+    Type: String
+
+Metadata:
+  AWS::CloudFormation::Interface:
+    ParameterGroups:
+    - Label:
+        default: "Cluster Information"
+      Parameters:
+      - InfrastructureName
+    - Label:
+        default: "Host Information"
+      Parameters:
+      - MasterInstanceType
+      - RhcosAmi
+      - IgnitionLocation
+      - CertificateAuthorities
+      - MasterSecurityGroupId
+      - MasterInstanceProfileName
+    - Label:
+        default: "Network Configuration"
+      Parameters:
+      - VpcId
+      - AllowedBootstrapSshCidr
+      - Master0Subnet
+      - Master1Subnet
+      - Master2Subnet
+    - Label:
+        default: "Load Balancer Automation"
+      Parameters:
+      - AutoRegisterELB
+      - RegisterNlbIpTargetsLambdaArn
+      - ExternalApiTargetGroupArn
+      - InternalApiTargetGroupArn
+      - InternalServiceTargetGroupArn
+    ParameterLabels:
+      InfrastructureName:
+        default: "Infrastructure Name"
+      VpcId:
+        default: "VPC ID"
+      Master0Subnet:
+        default: "Master-0 Subnet"
+      Master1Subnet:
+        default: "Master-1 Subnet"
+      Master2Subnet:
+        default: "Master-2 Subnet"
+      MasterInstanceType:
+        default: "Master Instance Type"
+      MasterInstanceProfileName:
+        default: "Master Instance Profile Name"
+      RhcosAmi:
+        default: "Red Hat Enterprise Linux CoreOS AMI ID"
+      BootstrapIgnitionLocation:
+        default: "Master Ignition Source"
+      CertificateAuthorities:
+        default: "Ignition CA String"
+      MasterSecurityGroupId:
+        default: "Master Security Group ID"
+      AutoRegisterELB:
+        default: "Use Provided ELB Automation"
+
+Conditions:
+  DoRegistration: !Equals ["yes", !Ref AutoRegisterELB]
+
+Resources:
+  Master0:
+    Type: AWS::EC2::Instance
+    Properties:
+      ImageId: !Ref RhcosAmi
+      BlockDeviceMappings:
+      - DeviceName: /dev/xvda
+        Ebs:
+          VolumeSize: "120"
+          VolumeType: "gp2"
+      IamInstanceProfile: !Ref MasterInstanceProfileName
+      InstanceType: !Ref MasterInstanceType
+      NetworkInterfaces:
+      - AssociatePublicIpAddress: "false"
+        DeviceIndex: "0"
+        GroupSet:
+        - !Ref "MasterSecurityGroupId"
+        SubnetId: !Ref "Master0Subnet"
+      UserData:
+        Fn::Base64: !Sub
+        - '{"ignition":{"config":{"merge":[{"source":"${SOURCE}"}]},"security":{"tls":{"certificateAuthorities":[{"source":"${CA_BUNDLE}"}]}},"version":"3.1.0"}}'
+        - {
+          SOURCE: !Ref IgnitionLocation,
+          CA_BUNDLE: !Ref CertificateAuthorities,
+        }
+      Tags:
+      - Key: !Join ["", ["kubernetes.io/cluster/", !Ref InfrastructureName]]
+        Value: "shared"
+
+  RegisterMaster0:
+    Condition: DoRegistration
+    Type: Custom::NLBRegister
+    Properties:
+      ServiceToken: !Ref RegisterNlbIpTargetsLambdaArn
+      TargetArn: !Ref ExternalApiTargetGroupArn
+      TargetIp: !GetAtt Master0.PrivateIp
+
+  RegisterMaster0InternalApiTarget:
+    Condition: DoRegistration
+    Type: Custom::NLBRegister
+    Properties:
+      ServiceToken: !Ref RegisterNlbIpTargetsLambdaArn
+      TargetArn: !Ref InternalApiTargetGroupArn
+      TargetIp: !GetAtt Master0.PrivateIp
+
+  RegisterMaster0InternalServiceTarget:
+    Condition: DoRegistration
+    Type: Custom::NLBRegister
+    Properties:
+      ServiceToken: !Ref RegisterNlbIpTargetsLambdaArn
+      TargetArn: !Ref InternalServiceTargetGroupArn
+      TargetIp: !GetAtt Master0.PrivateIp
+
+  Master1:
+    Type: AWS::EC2::Instance
+    Properties:
+      ImageId: !Ref RhcosAmi
+      BlockDeviceMappings:
+      - DeviceName: /dev/xvda
+        Ebs:
+          VolumeSize: "120"
+          VolumeType: "gp2"
+      IamInstanceProfile: !Ref MasterInstanceProfileName
+      InstanceType: !Ref MasterInstanceType
+      NetworkInterfaces:
+      - AssociatePublicIpAddress: "false"
+        DeviceIndex: "0"
+        GroupSet:
+        - !Ref "MasterSecurityGroupId"
+        SubnetId: !Ref "Master1Subnet"
+      UserData:
+        Fn::Base64: !Sub
+        - '{"ignition":{"config":{"merge":[{"source":"${SOURCE}"}]},"security":{"tls":{"certificateAuthorities":[{"source":"${CA_BUNDLE}"}]}},"version":"3.1.0"}}'
+        - {
+          SOURCE: !Ref IgnitionLocation,
+          CA_BUNDLE: !Ref CertificateAuthorities,
+        }
+      Tags:
+      - Key: !Join ["", ["kubernetes.io/cluster/", !Ref InfrastructureName]]
+        Value: "shared"
+
+  RegisterMaster1:
+    Condition: DoRegistration
+    Type: Custom::NLBRegister
+    Properties:
+      ServiceToken: !Ref RegisterNlbIpTargetsLambdaArn
+      TargetArn: !Ref ExternalApiTargetGroupArn
+      TargetIp: !GetAtt Master1.PrivateIp
+
+  RegisterMaster1InternalApiTarget:
+    Condition: DoRegistration
+    Type: Custom::NLBRegister
+    Properties:
+      ServiceToken: !Ref RegisterNlbIpTargetsLambdaArn
+      TargetArn: !Ref InternalApiTargetGroupArn
+      TargetIp: !GetAtt Master1.PrivateIp
+
+  RegisterMaster1InternalServiceTarget:
+    Condition: DoRegistration
+    Type: Custom::NLBRegister
+    Properties:
+      ServiceToken: !Ref RegisterNlbIpTargetsLambdaArn
+      TargetArn: !Ref InternalServiceTargetGroupArn
+      TargetIp: !GetAtt Master1.PrivateIp
+
+  Master2:
+    Type: AWS::EC2::Instance
+    Properties:
+      ImageId: !Ref RhcosAmi
+      BlockDeviceMappings:
+      - DeviceName: /dev/xvda
+        Ebs:
+          VolumeSize: "120"
+          VolumeType: "gp2"
+      IamInstanceProfile: !Ref MasterInstanceProfileName
+      InstanceType: !Ref MasterInstanceType
+      NetworkInterfaces:
+      - AssociatePublicIpAddress: "false"
+        DeviceIndex: "0"
+        GroupSet:
+        - !Ref "MasterSecurityGroupId"
+        SubnetId: !Ref "Master2Subnet"
+      UserData:
+        Fn::Base64: !Sub
+        - '{"ignition":{"config":{"merge":[{"source":"${SOURCE}"}]},"security":{"tls":{"certificateAuthorities":[{"source":"${CA_BUNDLE}"}]}},"version":"3.1.0"}}'
+        - {
+          SOURCE: !Ref IgnitionLocation,
+          CA_BUNDLE: !Ref CertificateAuthorities,
+        }
+      Tags:
+      - Key: !Join ["", ["kubernetes.io/cluster/", !Ref InfrastructureName]]
+        Value: "shared"
+
+  RegisterMaster2:
+    Condition: DoRegistration
+    Type: Custom::NLBRegister
+    Properties:
+      ServiceToken: !Ref RegisterNlbIpTargetsLambdaArn
+      TargetArn: !Ref ExternalApiTargetGroupArn
+      TargetIp: !GetAtt Master2.PrivateIp
+
+  RegisterMaster2InternalApiTarget:
+    Condition: DoRegistration
+    Type: Custom::NLBRegister
+    Properties:
+      ServiceToken: !Ref RegisterNlbIpTargetsLambdaArn
+      TargetArn: !Ref InternalApiTargetGroupArn
+      TargetIp: !GetAtt Master2.PrivateIp
+
+  RegisterMaster2InternalServiceTarget:
+    Condition: DoRegistration
+    Type: Custom::NLBRegister
+    Properties:
+      ServiceToken: !Ref RegisterNlbIpTargetsLambdaArn
+      TargetArn: !Ref InternalServiceTargetGroupArn
+      TargetIp: !GetAtt Master2.PrivateIp
+
+Outputs:
+  PrivateIPs:
+    Description: The control-plane node private IP addresses.
+    Value:
+      !Join [
+        ",",
+        [!GetAtt Master0.PrivateIp, !GetAtt Master1.PrivateIp, !GetAtt Master2.PrivateIp]
+      ]
diff --git a/tests/data/templates/aws-ecr-continuouscompliance-v1.yaml b/tests/data/templates/aws-ecr-continuouscompliance-v1.yaml
new file mode 100644
index 0000000..5cf1fc8
--- /dev/null
+++ b/tests/data/templates/aws-ecr-continuouscompliance-v1.yaml
@@ -0,0 +1,372 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Automated Image Scan Compliance for AWS EKS using AWS ECR and AWS Security Hub
+
+# ----------------------------------------------------------------------------------------------------------
+#  CloudFormation Template 1 of 1 - 
+#   
+#  
+#  1- Provisions CloudWatchEvents (EventBridge) Rule:
+#    - CloudWatchEvents Rule is triggered based on a AWS ECR Event for a completed Image Scan
+#  2- Provisions a Compliance Lambda as a target for the CloudWatch Events Rule.
+#  3- Compliance Lambda: 
+#   - Obtains event details from the ECR Complated Image scan event
+#   - Creates a finding in AWS Security Hub
+#  4 - Provisions an AWS Security Hub Custom Action
+#    - Performs remediation by attaching restricted policy to ECR repository
+# 
+# 
+# @kmmahaj
+##
+## License:
+## This code is made available under the MIT-0 license. See the LICENSE file.
+# ------------------------------------------------------------...............................................
+
+ 
+Resources:
+
+# --------------------------------------------------------------------------------------------------
+#  1- Provisions a CloudWatchEvents Rule that is triggered based on ECR Image Scan Event
+#  2- Provisions a Lambda that creates a finding in AWS Security Hub 
+# --------------------------------------------------------------------------------------------------
+
+  CaptureECRImageScanEvents:
+    Type: AWS::Events::Rule
+    Properties:
+      Description: Capture ECR Scan Events and Trigger an Action
+      EventPattern:
+        detail-type:
+          - ECR Image Scan
+        source:
+          - aws.ecr
+      Name: CaptureECRScanEvent
+      State: ENABLED
+      Targets:
+      - Arn: !GetAtt "ECRToSecHubSendFindingsLambda.Arn"
+        Id: IDCaptureECRImageScanEvents
+
+  ECRToSecHubSendFindingsLambda:
+    Type: AWS::Lambda::Function
+    Properties:
+      FunctionName: ECR2SecurityHubSendFindingsLambda
+      Description: Maps ECR Scan Finding into ASFF before importing to Security Hub
+      Handler: index.lambda_handler
+      MemorySize: 384
+      Role: !GetAtt ECRToSecHubSendFindingsLambdaRole.Arn
+      Runtime: python3.7
+      Timeout: 70
+      Environment:
+        Variables:
+          account_num: !Ref 'AWS::AccountId'
+          region: !Ref 'AWS::Region'
+      Code:
+        ZipFile: |
+          import json
+          import boto3
+          import datetime
+          import uuid
+          import os
+          def lambda_handler(event, context):
+              # import Lambda ENV VARs
+              accountId = os.environ['account_num']
+              awsRegion = os.environ['region']
+              # Get ECR event details
+              eventDetails = event['detail']
+              repoName = eventDetails['repository-name']
+              findingsevcounts = eventDetails['finding-severity-counts']
+              numCritical = 0
+              numMedium = 0
+              numHigh = 0
+              if findingsevcounts.get('CRITICAL'):
+                  numCritical = findingsevcounts['CRITICAL']
+              if findingsevcounts.get('MEDIUM'):
+                  numMedium = findingsevcounts['MEDIUM']
+              if findingsevcounts.get('HIGH'):
+                  numHigh = findingsevcounts['HIGH']
+                          
+              # send finding to Security Hub
+              severity = "LOW"
+              title = "ECR Finding"
+              ECRComplianceRating = 'PASSED'
+              if numMedium:
+                  severity = "MEDIUM"
+                  title = "Medium ECR Vulnerability"
+                  ECRComplianceRating = 'FAILED'
+              if numHigh:
+                  severity = "HIGH"
+                  title = "High ECR Vulnerability"
+                  ECRComplianceRating = 'FAILED'
+              if numCritical:
+                  severity = "CRITICAL"
+                  title = "Critical ECR Vulnerability"
+                  ECRComplianceRating = 'FAILED'
+
+
+              # ISO Time
+              iso8061Time = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat()
+              # ASFF BIF Id
+              asffID = str(uuid.uuid4())
+              # import security hub boto3 client
+              sechub = boto3.client('securityhub')
+              # call BIF
+              try:
+                  response = sechub.batch_import_findings(
+                      Findings=[
+                          {
+                              'SchemaVersion': '2018-10-08',
+                              'Id': asffID,
+                              'ProductArn': 'arn:aws:securityhub:' + awsRegion + ':' + accountId + ':product/' + accountId + '/default',
+                              'ProductFields': {
+                                  'ECRRepoName': repoName,
+                              },
+                              'GeneratorId': asffID,
+                              'AwsAccountId': accountId,
+                              'Types': [ 'Software and Configuration Checks' ],
+                              'FirstObservedAt': iso8061Time,
+                              'UpdatedAt': iso8061Time,
+                              'CreatedAt': iso8061Time,
+                              'Severity': {
+                                  'Label': severity
+                              },
+                              'Title': title,
+                              'Description': title,
+                              'Resources': [
+                                  {
+                                      'Type': 'AwsEcr',
+                                      'Id': 'AWS::::Account:' + accountId,
+                                      'Partition': 'aws',
+                                      'Region': awsRegion,
+                                  }
+                              ],
+                              'WorkflowState': 'NEW',
+                              'Compliance': {'Status': ECRComplianceRating},
+                              'RecordState': 'ACTIVE'
+                          }
+                      ]
+                  )
+                  print(response)
+              except Exception as e:
+                  print(e)
+                  print("Submitting finding to Security Hub failed, please troubleshoot further")
+                  raise
+  
+  ECRToSecHubSendFindingsLambdaRole:
+    Type: AWS::IAM::Role
+    Properties:
+      Policies:
+      - PolicyName: ECRToSecHubSendFindingsLambda-Policy
+        PolicyDocument:
+          Version: 2012-10-17
+          Statement:
+          - Effect: Allow
+            Action:
+            - cloudwatch:PutMetricData
+            - securityhub:BatchImportFindings
+            Resource: '*'
+          - Effect: Allow
+            Action:
+            - logs:CreateLogGroup
+            - logs:CreateLogStream
+            - logs:PutLogEvents
+            Resource: '*'
+      AssumeRolePolicyDocument:
+        Version: 2012-10-17
+        Statement:
+        - Effect: Allow
+          Principal: { Service: lambda.amazonaws.com }
+          Action:
+          - sts:AssumeRole
+
+  PermissionForEventsToInvokeLambdachk:
+    Type: AWS::Lambda::Permission
+    Properties:
+      Action: lambda:InvokeFunction
+      FunctionName: !GetAtt "ECRToSecHubSendFindingsLambda.Arn"
+      Principal: events.amazonaws.com
+      SourceArn: !GetAtt "CaptureECRImageScanEvents.Arn"
+
+
+# -------------------------------------------------------------------------------------------------------------------------------------------------------
+# 3. Create Remediation in Security Hub
+# -------------------------------------------------------------------------------------------------------------------------------------------------------
+
+  CreateSecurityHubCustomActionTargetLambda:
+    Type: AWS::Lambda::Function
+    Properties:
+      FunctionName: CreateSecurityHubCustomActionTargetLambda-ECR
+      Description: Custom resource to create an action target in Security Hub
+      Handler: index.lambda_handler
+      MemorySize: 256
+      Role: !GetAtt CreateSecurityHubCustomActionTargetLambdaRole.Arn
+      Runtime: python3.7
+      Timeout: 60
+      Environment:
+        Variables:
+          Region: !Ref 'AWS::Region'
+      Code:
+        ZipFile: |
+          import boto3
+          import cfnresponse
+          import os
+          def lambda_handler(event, context):
+              try:
+                  properties = event['ResourceProperties']
+                  region = os.environ['Region']
+                  client = boto3.client('securityhub', region_name=region)
+                  responseData = {}
+                  if event['RequestType'] == 'Create':
+                      response = client.create_action_target(
+                          Name=properties['Name'],
+                          Description=properties['Description'],
+                          Id=properties['Id']
+                      )
+                      responseData['Arn'] = response['ActionTargetArn']
+                  elif event['RequestType'] == 'Delete':
+                      account_id = context.invoked_function_arn.split(":")[4]
+                      client.delete_action_target(
+                          ActionTargetArn=f"arn:aws:securityhub:{region}:{account_id}:action/custom/{properties['Id']}"
+                      )
+                  cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
+              except Exception as e:
+                  print(e)
+                  cfnresponse.send(event, context, cfnresponse.FAILED, {})
+  
+  CreateSecurityHubCustomActionTargetLambdaRole:
+    Type: AWS::IAM::Role
+    Properties:
+      Policies:
+      - PolicyName: CreateActionTarget-LambdaPolicy-ECR
+        PolicyDocument:
+          Version: 2012-10-17
+          Statement:
+          - Effect: Allow
+            Action:
+            - cloudwatch:PutMetricData
+            Resource: '*'
+          - Effect: Allow
+            Action:
+            - logs:CreateLogGroup
+            - logs:CreateLogStream
+            - logs:PutLogEvents
+            Resource: '*'
+          - Effect: Allow
+            Action:
+            - securityhub:CreateActionTarget
+            - securityhub:DeleteActionTarget
+            Resource: '*'
+      AssumeRolePolicyDocument:
+        Version: 2012-10-17
+        Statement:
+        - Effect: Allow
+          Principal: { Service: lambda.amazonaws.com }
+          Action:
+          - sts:AssumeRole
+
+# -------------------------------------------------------------------------------------------------------------------------------------------------------
+# Create Remediation to deny ECR repository access 
+# -------------------------------------------------------------------------------------------------------------------------------------------------------
+
+  ECRAccessProhibitedRule: 
+    Type: AWS::Events::Rule
+    Properties: 
+      Name: ECRAccessProhibitedRule
+      Description: "ECR1 - Deny Access to ECR due to vulnerability assesment"
+      EventPattern: 
+        source: 
+          - aws.securityhub
+        detail-type: 
+          - Security Hub Findings - Custom Action
+        resources: 
+          - !GetAtt ECRActionTarget.Arn
+      State: "ENABLED"
+      Targets: 
+        - 
+          Arn: 
+            Fn::GetAtt: 
+              - "ECRAccessProhibitedLambda"
+              - "Arn"
+          Id: "ECR1"
+  
+  ECRActionTarget:
+    Type: Custom::ActionTarget
+    Version: 1.0
+    Properties:
+      ServiceToken: !GetAtt CreateSecurityHubCustomActionTargetLambda.Arn
+      Name: ECR1
+      Description: Deny Access to ECR
+      Id: ECR11
+
+  ECRAccessProhibitedLambdaPermission: 
+    Type: AWS::Lambda::Permission
+    Properties: 
+      FunctionName: 
+        Ref: "ECRAccessProhibitedLambda"
+      Action: "lambda:InvokeFunction"
+      Principal: "events.amazonaws.com"
+      SourceArn: 
+        Fn::GetAtt: 
+          - "ECRAccessProhibitedRule"
+          - "Arn"
+
+  ECRAccessProhibitedLambda:
+    Type: AWS::Lambda::Function
+    Properties:
+      FunctionName: ECRAccessProhibitedLambda
+      Description: "ECR1 - Deny Access to ECR due to vulnerability assesment"
+      Handler: index.lambda_handler
+      MemorySize: 256
+      Role: !GetAtt  ECRAccessProhibitedLambdaRole.Arn
+      Runtime: python3.7
+      Timeout: 60
+      Code:
+        ZipFile: |
+          import boto3
+          import json
+          import os
+          def lambda_handler(event, context):
+    
+              repoName = str(event['detail']['findings'][0]['ProductFields']['ECRRepoName'])
+              ecr = boto3.client('ecr')
+              try:
+                  policyText = '{\n  "Version" : "2008-10-17",\n  "Statement" : [ {\n    "Sid" : "deny all",\n    "Effect" : "Deny",\n    "Principal" : "*",\n "Action" : [ "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "ecr:BatchCheckLayerAvailability" ]\n } ]\n}'
+                  response = ecr.set_repository_policy(
+                      repositoryName=repoName,
+                      policyText=policyText
+                  )
+        
+              except Exception as e:
+                  print(e)
+                  print("SSM automation execution error")
+                  raise
+
+  ECRAccessProhibitedLambdaRole:
+    Type: AWS::IAM::Role
+    Properties:
+      Policies:
+      - PolicyName: ECRAccessProhibitedLambdaPolicy
+        PolicyDocument:
+          Version: 2012-10-17
+          Statement:
+          - Effect: Allow
+            Action:
+            - cloudwatch:PutMetricData
+            Resource: '*'
+          - Effect: Allow
+            Action:
+            - logs:CreateLogGroup
+            - logs:CreateLogStream
+            - logs:PutLogEvents
+            Resource: '*'
+          - Effect: Allow
+            Action:
+            - ssm:StartAutomationExecution
+            - ecr:*
+            - iam:PassRole
+            - securityhub:UpdateFindings
+            Resource: '*'
+      AssumeRolePolicyDocument:
+        Version: 2012-10-17
+        Statement:
+        - Effect: Allow
+          Principal: { Service: lambda.amazonaws.com }
+          Action:
+          - sts:AssumeRole
diff --git a/tests/data/templates/aws-instance-scheduler.template b/tests/data/templates/aws-instance-scheduler.template
new file mode 100644
index 0000000..9f6d013
--- /dev/null
+++ b/tests/data/templates/aws-instance-scheduler.template
@@ -0,0 +1,2175 @@
+{
+ "Description": "(SO0030) - The AWS CloudFormation template for deployment of the aws-instance-scheduler, version: 1.5.0",
+ "AWSTemplateFormatVersion": "2010-09-09",
+ "Metadata": {
+  "AWS::CloudFormation::Interface": {
+   "ParameterGroups": [
+    {
+     "Label": {
+      "default": "Scheduler (version 1.5.0)"
+     },
+     "Parameters": [
+      "TagName",
+      "ScheduledServices",
+      "ScheduleRdsClusters",
+      "CreateRdsSnapshot",
+      "SchedulingActive",
+      "DefaultTimezone",
+      "CrossAccountRoles",
+      "ScheduleLambdaAccount",
+      "SchedulerFrequency",
+      "MemorySize"
+     ]
+    },
+    {
+     "Label": {
+      "default": "Namespace Configuration"
+     },
+     "Parameters": [
+      "Namespace"
+     ]
+    },
+    {
+     "Label": {
+      "default": "Account Structure"
+     },
+     "Parameters": [
+      "UsingAWSOrganizations",
+      "Principals",
+      "Regions"
+     ]
+    },
+    {
+     "Label": {
+      "default": "Options"
+     },
+     "Parameters": [
+      "UseCloudWatchMetrics",
+      "Trace",
+      "EnableSSMMaintenanceWindows"
+     ]
+    },
+    {
+     "Label": {
+      "default": "Other parameters"
+     },
+     "Parameters": [
+      "LogRetentionDays",
+      "StartedTags",
+      "StoppedTags"
+     ]
+    }
+   ],
+   "ParameterLabels": {
+    "Namespace": {
+     "default": "Provide a unique namespace value."
+    },
+    "LogRetentionDays": {
+     "default": "Log retention days"
+    },
+    "StartedTags": {
+     "default": "Started tags"
+    },
+    "StoppedTags": {
+     "default": "Stopped tags"
+    },
+    "SchedulingActive": {
+     "default": "Scheduling enabled"
+    },
+    "UsingAWSOrganizations": {
+     "default": "Using AWS Organizations?"
+    },
+    "Principals": {
+     "default": "Provide Organization Id OR List of Remote Account Ids"
+    },
+    "ScheduleLambdaAccount": {
+     "default": "This account"
+    },
+    "UseCloudWatchMetrics": {
+     "default": "Enable CloudWatch Metrics"
+    },
+    "Trace": {
+     "default": "Enable CloudWatch Debug Logs"
+    },
+    "EnableSSMMaintenanceWindows": {
+     "default": "Enable SSM Maintenance windows"
+    },
+    "TagName": {
+     "default": "Instance Scheduler tag name"
+    },
+    "ScheduledServices": {
+     "default": "Service(s) to schedule"
+    },
+    "ScheduleRdsClusters": {
+     "default": "Schedule Aurora Clusters"
+    },
+    "CreateRdsSnapshot": {
+     "default": "Create RDS instance snapshot"
+    },
+    "DefaultTimezone": {
+     "default": "Default time zone"
+    },
+    "SchedulerFrequency": {
+     "default": "Frequency"
+    },
+    "Regions": {
+     "default": "Region(s)"
+    },
+    "MemorySize": {
+     "default": "Memory size"
+    }
+   }
+  }
+ },
+ "Parameters": {
+  "SchedulingActive": {
+   "Type": "String",
+   "Default": "Yes",
+   "AllowedValues": [
+    "Yes",
+    "No"
+   ],
+   "Description": "Activate or deactivate scheduling."
+  },
+  "ScheduledServices": {
+   "Type": "String",
+   "Default": "EC2",
+   "AllowedValues": [
+    "EC2",
+    "RDS",
+    "Both"
+   ],
+   "Description": "Scheduled Services."
+  },
+  "ScheduleRdsClusters": {
+   "Type": "String",
+   "Default": "No",
+   "AllowedValues": [
+    "Yes",
+    "No"
+   ],
+   "Description": "Enable scheduling of Aurora clusters for RDS Service."
+  },
+  "CreateRdsSnapshot": {
+   "Type": "String",
+   "Default": "No",
+   "AllowedValues": [
+    "Yes",
+    "No"
+   ],
+   "Description": "Create snapshot before stopping RDS instances (does not apply to Aurora Clusters)."
+  },
+  "MemorySize": {
+   "Type": "Number",
+   "Default": 128,
+   "AllowedValues": [
+    "128",
+    "384",
+    "512",
+    "640",
+    "768",
+    "896",
+    "1024",
+    "1152",
+    "1280",
+    "1408",
+    "1536"
+   ],
+   "Description": "Size of the Lambda function running the scheduler, increase size when processing large numbers of instances."
+  },
+  "UseCloudWatchMetrics": {
+   "Type": "String",
+   "Default": "No",
+   "AllowedValues": [
+    "Yes",
+    "No"
+   ],
+   "Description": "Collect instance scheduling data using CloudWatch metrics."
+  },
+  "LogRetentionDays": {
+   "Type": "Number",
+   "Default": 30,
+   "AllowedValues": [
+    "1",
+    "3",
+    "5",
+    "7",
+    "14",
+    "30",
+    "60",
+    "90",
+    "120",
+    "150",
+    "180",
+    "365",
+    "400",
+    "545",
+    "731",
+    "1827",
+    "3653"
+   ],
+   "Description": "Retention days for scheduler logs."
+  },
+  "Trace": {
+   "Type": "String",
+   "Default": "No",
+   "AllowedValues": [
+    "Yes",
+    "No"
+   ],
+   "Description": "Enable debug-level logging in CloudWatch logs."
+  },
+  "EnableSSMMaintenanceWindows": {
+   "Type": "String",
+   "Default": "No",
+   "AllowedValues": [
+    "Yes",
+    "No"
+   ],
+   "Description": "Enable the solution to load SSM Maintenance Windows, so that they can be used for EC2 instance Scheduling."
+  },
+  "TagName": {
+   "Type": "String",
+   "Default": "Schedule",
+   "Description": "Name of tag to use for associating instance schedule schemas with service instances.",
+   "MaxLength": 127,
+   "MinLength": 1
+  },
+  "DefaultTimezone": {
+   "Type": "String",
+   "Default": "UTC",
+   "AllowedValues": [
+    "Africa/Abidjan",
+    "Africa/Accra",
+    "Africa/Addis_Ababa",
+    "Africa/Algiers",
+    "Africa/Asmara",
+    "Africa/Bamako",
+    "Africa/Bangui",
+    "Africa/Banjul",
+    "Africa/Bissau",
+    "Africa/Blantyre",
+    "Africa/Brazzaville",
+    "Africa/Bujumbura",
+    "Africa/Cairo",
+    "Africa/Casablanca",
+    "Africa/Ceuta",
+    "Africa/Conakry",
+    "Africa/Dakar",
+    "Africa/Dar_es_Salaam",
+    "Africa/Djibouti",
+    "Africa/Douala",
+    "Africa/El_Aaiun",
+    "Africa/Freetown",
+    "Africa/Gaborone",
+    "Africa/Harare",
+    "Africa/Johannesburg",
+    "Africa/Juba",
+    "Africa/Kampala",
+    "Africa/Khartoum",
+    "Africa/Kigali",
+    "Africa/Kinshasa",
+    "Africa/Lagos",
+    "Africa/Libreville",
+    "Africa/Lome",
+    "Africa/Luanda",
+    "Africa/Lubumbashi",
+    "Africa/Lusaka",
+    "Africa/Malabo",
+    "Africa/Maputo",
+    "Africa/Maseru",
+    "Africa/Mbabane",
+    "Africa/Mogadishu",
+    "Africa/Monrovia",
+    "Africa/Nairobi",
+    "Africa/Ndjamena",
+    "Africa/Niamey",
+    "Africa/Nouakchott",
+    "Africa/Ouagadougou",
+    "Africa/Porto-Novo",
+    "Africa/Sao_Tome",
+    "Africa/Tripoli",
+    "Africa/Tunis",
+    "Africa/Windhoek",
+    "America/Adak",
+    "America/Anchorage",
+    "America/Anguilla",
+    "America/Antigua",
+    "America/Araguaina",
+    "America/Argentina/Buenos_Aires",
+    "America/Argentina/Catamarca",
+    "America/Argentina/Cordoba",
+    "America/Argentina/Jujuy",
+    "America/Argentina/La_Rioja",
+    "America/Argentina/Mendoza",
+    "America/Argentina/Rio_Gallegos",
+    "America/Argentina/Salta",
+    "America/Argentina/San_Juan",
+    "America/Argentina/San_Luis",
+    "America/Argentina/Tucuman",
+    "America/Argentina/Ushuaia",
+    "America/Aruba",
+    "America/Asuncion",
+    "America/Atikokan",
+    "America/Bahia",
+    "America/Bahia_Banderas",
+    "America/Barbados",
+    "America/Belem",
+    "America/Belize",
+    "America/Blanc-Sablon",
+    "America/Boa_Vista",
+    "America/Bogota",
+    "America/Boise",
+    "America/Cambridge_Bay",
+    "America/Campo_Grande",
+    "America/Cancun",
+    "America/Caracas",
+    "America/Cayenne",
+    "America/Cayman",
+    "America/Chicago",
+    "America/Chihuahua",
+    "America/Costa_Rica",
+    "America/Creston",
+    "America/Cuiaba",
+    "America/Curacao",
+    "America/Danmarkshavn",
+    "America/Dawson",
+    "America/Dawson_Creek",
+    "America/Denver",
+    "America/Detroit",
+    "America/Dominica",
+    "America/Edmonton",
+    "America/Eirunepe",
+    "America/El_Salvador",
+    "America/Fortaleza",
+    "America/Glace_Bay",
+    "America/Godthab",
+    "America/Goose_Bay",
+    "America/Grand_Turk",
+    "America/Grenada",
+    "America/Guadeloupe",
+    "America/Guatemala",
+    "America/Guayaquil",
+    "America/Guyana",
+    "America/Halifax",
+    "America/Havana",
+    "America/Hermosillo",
+    "America/Indiana/Indianapolis",
+    "America/Indiana/Knox",
+    "America/Indiana/Marengo",
+    "America/Indiana/Petersburg",
+    "America/Indiana/Tell_City",
+    "America/Indiana/Vevay",
+    "America/Indiana/Vincennes",
+    "America/Indiana/Winamac",
+    "America/Inuvik",
+    "America/Iqaluit",
+    "America/Jamaica",
+    "America/Juneau",
+    "America/Kentucky/Louisville",
+    "America/Kentucky/Monticello",
+    "America/Kralendijk",
+    "America/La_Paz",
+    "America/Lima",
+    "America/Los_Angeles",
+    "America/Lower_Princes",
+    "America/Maceio",
+    "America/Managua",
+    "America/Manaus",
+    "America/Marigot",
+    "America/Martinique",
+    "America/Matamoros",
+    "America/Mazatlan",
+    "America/Menominee",
+    "America/Merida",
+    "America/Metlakatla",
+    "America/Mexico_City",
+    "America/Miquelon",
+    "America/Moncton",
+    "America/Monterrey",
+    "America/Montevideo",
+    "America/Montreal",
+    "America/Montserrat",
+    "America/Nassau",
+    "America/New_York",
+    "America/Nipigon",
+    "America/Nome",
+    "America/Noronha",
+    "America/North_Dakota/Beulah",
+    "America/North_Dakota/Center",
+    "America/North_Dakota/New_Salem",
+    "America/Ojinaga",
+    "America/Panama",
+    "America/Pangnirtung",
+    "America/Paramaribo",
+    "America/Phoenix",
+    "America/Port-au-Prince",
+    "America/Port_of_Spain",
+    "America/Porto_Velho",
+    "America/Puerto_Rico",
+    "America/Rainy_River",
+    "America/Rankin_Inlet",
+    "America/Recife",
+    "America/Regina",
+    "America/Resolute",
+    "America/Rio_Branco",
+    "America/Santa_Isabel",
+    "America/Santarem",
+    "America/Santiago",
+    "America/Santo_Domingo",
+    "America/Sao_Paulo",
+    "America/Scoresbysund",
+    "America/Sitka",
+    "America/St_Barthelemy",
+    "America/St_Johns",
+    "America/St_Kitts",
+    "America/St_Lucia",
+    "America/St_Thomas",
+    "America/St_Vincent",
+    "America/Swift_Current",
+    "America/Tegucigalpa",
+    "America/Thule",
+    "America/Thunder_Bay",
+    "America/Tijuana",
+    "America/Toronto",
+    "America/Tortola",
+    "America/Vancouver",
+    "America/Whitehorse",
+    "America/Winnipeg",
+    "America/Yakutat",
+    "America/Yellowknife",
+    "Antarctica/Casey",
+    "Antarctica/Davis",
+    "Antarctica/DumontDUrville",
+    "Antarctica/Macquarie",
+    "Antarctica/Mawson",
+    "Antarctica/McMurdo",
+    "Antarctica/Palmer",
+    "Antarctica/Rothera",
+    "Antarctica/Syowa",
+    "Antarctica/Vostok",
+    "Arctic/Longyearbyen",
+    "Asia/Aden",
+    "Asia/Almaty",
+    "Asia/Amman",
+    "Asia/Anadyr",
+    "Asia/Aqtau",
+    "Asia/Aqtobe",
+    "Asia/Ashgabat",
+    "Asia/Baghdad",
+    "Asia/Bahrain",
+    "Asia/Baku",
+    "Asia/Bangkok",
+    "Asia/Beirut",
+    "Asia/Bishkek",
+    "Asia/Brunei",
+    "Asia/Choibalsan",
+    "Asia/Chongqing",
+    "Asia/Colombo",
+    "Asia/Damascus",
+    "Asia/Dhaka",
+    "Asia/Dili",
+    "Asia/Dubai",
+    "Asia/Dushanbe",
+    "Asia/Gaza",
+    "Asia/Harbin",
+    "Asia/Hebron",
+    "Asia/Ho_Chi_Minh",
+    "Asia/Hong_Kong",
+    "Asia/Hovd",
+    "Asia/Irkutsk",
+    "Asia/Jakarta",
+    "Asia/Jayapura",
+    "Asia/Jerusalem",
+    "Asia/Kabul",
+    "Asia/Kamchatka",
+    "Asia/Karachi",
+    "Asia/Kashgar",
+    "Asia/Kathmandu",
+    "Asia/Khandyga",
+    "Asia/Kolkata",
+    "Asia/Krasnoyarsk",
+    "Asia/Kuala_Lumpur",
+    "Asia/Kuching",
+    "Asia/Kuwait",
+    "Asia/Macau",
+    "Asia/Magadan",
+    "Asia/Makassar",
+    "Asia/Manila",
+    "Asia/Muscat",
+    "Asia/Nicosia",
+    "Asia/Novokuznetsk",
+    "Asia/Novosibirsk",
+    "Asia/Omsk",
+    "Asia/Oral",
+    "Asia/Phnom_Penh",
+    "Asia/Pontianak",
+    "Asia/Pyongyang",
+    "Asia/Qatar",
+    "Asia/Qyzylorda",
+    "Asia/Rangoon",
+    "Asia/Riyadh",
+    "Asia/Sakhalin",
+    "Asia/Samarkand",
+    "Asia/Seoul",
+    "Asia/Shanghai",
+    "Asia/Singapore",
+    "Asia/Taipei",
+    "Asia/Tashkent",
+    "Asia/Tbilisi",
+    "Asia/Tehran",
+    "Asia/Thimphu",
+    "Asia/Tokyo",
+    "Asia/Ulaanbaatar",
+    "Asia/Urumqi",
+    "Asia/Ust-Nera",
+    "Asia/Vientiane",
+    "Asia/Vladivostok",
+    "Asia/Yakutsk",
+    "Asia/Yekaterinburg",
+    "Asia/Yerevan",
+    "Atlantic/Azores",
+    "Atlantic/Bermuda",
+    "Atlantic/Canary",
+    "Atlantic/Cape_Verde",
+    "Atlantic/Faroe",
+    "Atlantic/Madeira",
+    "Atlantic/Reykjavik",
+    "Atlantic/South_Georgia",
+    "Atlantic/St_Helena",
+    "Atlantic/Stanley",
+    "Australia/Adelaide",
+    "Australia/Brisbane",
+    "Australia/Broken_Hill",
+    "Australia/Currie",
+    "Australia/Darwin",
+    "Australia/Eucla",
+    "Australia/Hobart",
+    "Australia/Lindeman",
+    "Australia/Lord_Howe",
+    "Australia/Melbourne",
+    "Australia/Perth",
+    "Australia/Sydney",
+    "Canada/Atlantic",
+    "Canada/Central",
+    "Canada/Eastern",
+    "Canada/Mountain",
+    "Canada/Newfoundland",
+    "Canada/Pacific",
+    "Europe/Amsterdam",
+    "Europe/Andorra",
+    "Europe/Athens",
+    "Europe/Belgrade",
+    "Europe/Berlin",
+    "Europe/Bratislava",
+    "Europe/Brussels",
+    "Europe/Bucharest",
+    "Europe/Budapest",
+    "Europe/Busingen",
+    "Europe/Chisinau",
+    "Europe/Copenhagen",
+    "Europe/Dublin",
+    "Europe/Gibraltar",
+    "Europe/Guernsey",
+    "Europe/Helsinki",
+    "Europe/Isle_of_Man",
+    "Europe/Istanbul",
+    "Europe/Jersey",
+    "Europe/Kaliningrad",
+    "Europe/Kiev",
+    "Europe/Lisbon",
+    "Europe/Ljubljana",
+    "Europe/London",
+    "Europe/Luxembourg",
+    "Europe/Madrid",
+    "Europe/Malta",
+    "Europe/Mariehamn",
+    "Europe/Minsk",
+    "Europe/Monaco",
+    "Europe/Moscow",
+    "Europe/Oslo",
+    "Europe/Paris",
+    "Europe/Podgorica",
+    "Europe/Prague",
+    "Europe/Riga",
+    "Europe/Rome",
+    "Europe/Samara",
+    "Europe/San_Marino",
+    "Europe/Sarajevo",
+    "Europe/Simferopol",
+    "Europe/Skopje",
+    "Europe/Sofia",
+    "Europe/Stockholm",
+    "Europe/Tallinn",
+    "Europe/Tirane",
+    "Europe/Uzhgorod",
+    "Europe/Vaduz",
+    "Europe/Vatican",
+    "Europe/Vienna",
+    "Europe/Vilnius",
+    "Europe/Volgograd",
+    "Europe/Warsaw",
+    "Europe/Zagreb",
+    "Europe/Zaporozhye",
+    "Europe/Zurich",
+    "GMT",
+    "Indian/Antananarivo",
+    "Indian/Chagos",
+    "Indian/Christmas",
+    "Indian/Cocos",
+    "Indian/Comoro",
+    "Indian/Kerguelen",
+    "Indian/Mahe",
+    "Indian/Maldives",
+    "Indian/Mauritius",
+    "Indian/Mayotte",
+    "Indian/Reunion",
+    "Pacific/Apia",
+    "Pacific/Auckland",
+    "Pacific/Chatham",
+    "Pacific/Chuuk",
+    "Pacific/Easter",
+    "Pacific/Efate",
+    "Pacific/Enderbury",
+    "Pacific/Fakaofo",
+    "Pacific/Fiji",
+    "Pacific/Funafuti",
+    "Pacific/Galapagos",
+    "Pacific/Gambier",
+    "Pacific/Guadalcanal",
+    "Pacific/Guam",
+    "Pacific/Honolulu",
+    "Pacific/Johnston",
+    "Pacific/Kiritimati",
+    "Pacific/Kosrae",
+    "Pacific/Kwajalein",
+    "Pacific/Majuro",
+    "Pacific/Marquesas",
+    "Pacific/Midway",
+    "Pacific/Nauru",
+    "Pacific/Niue",
+    "Pacific/Norfolk",
+    "Pacific/Noumea",
+    "Pacific/Pago_Pago",
+    "Pacific/Palau",
+    "Pacific/Pitcairn",
+    "Pacific/Pohnpei",
+    "Pacific/Port_Moresby",
+    "Pacific/Rarotonga",
+    "Pacific/Saipan",
+    "Pacific/Tahiti",
+    "Pacific/Tarawa",
+    "Pacific/Tongatapu",
+    "Pacific/Wake",
+    "Pacific/Wallis",
+    "US/Alaska",
+    "US/Arizona",
+    "US/Central",
+    "US/Eastern",
+    "US/Hawaii",
+    "US/Mountain",
+    "US/Pacific",
+    "UTC"
+   ],
+   "Description": "Choose the default Time Zone. Default is 'UTC'."
+  },
+  "Regions": {
+   "Type": "CommaDelimitedList",
+   "Default": "",
+   "Description": "List of regions in which instances are scheduled, leave blank for current region only."
+  },
+  "UsingAWSOrganizations": {
+   "Type": "String",
+   "Default": "No",
+   "AllowedValues": [
+    "Yes",
+    "No"
+   ],
+   "Description": "Is the hub account member of the AWS Organizations?"
+  },
+  "Principals": {
+   "Type": "CommaDelimitedList",
+   "Default": "",
+   "Description": "(Required) If using AWS Organizations, provide Organization ID. Eg. o-xxxxyyy. Else, comma separated list of remote account ids. Eg.: 1111111111, 2222222222 or {param: ssm-param-name}"
+  },
+  "Namespace": {
+   "Type": "String",
+   "Default": "",
+   "Description": "Provide unique identifier to differentiate between multiple solution deployments (No Spaces). Example: Dev"
+  },
+  "StartedTags": {
+   "Type": "String",
+   "Default": "",
+   "Description": "Comma separated list of tagname and values on the formt name=value,name=value,.. that are set on started instances"
+  },
+  "StoppedTags": {
+   "Type": "String",
+   "Default": "",
+   "Description": "Comma separated list of tagname and values on the formt name=value,name=value,.. that are set on stopped instances"
+  },
+  "SchedulerFrequency": {
+   "Type": "String",
+   "Default": "5",
+   "AllowedValues": [
+    "1",
+    "2",
+    "5",
+    "10",
+    "15",
+    "30",
+    "60"
+   ],
+   "Description": "Scheduler running frequency in minutes."
+  },
+  "ScheduleLambdaAccount": {
+   "Type": "String",
+   "Default": "Yes",
+   "AllowedValues": [
+    "Yes",
+    "No"
+   ],
+   "Description": "Schedule instances in this account."
+  }
+ },
+ "Conditions": {
+  "IsMemberOfOrganization": {
+   "Fn::Equals": [
+    {
+     "Ref": "UsingAWSOrganizations"
+    },
+    "Yes"
+   ]
+  }
+ },
+ "Mappings": {
+  "mappings": {
+   "TrueFalse": {
+    "Yes": "True",
+    "No": "False"
+   },
+   "EnabledDisabled": {
+    "Yes": "ENABLED",
+    "No": "DISABLED"
+   },
+   "Services": {
+    "EC2": "ec2",
+    "RDS": "rds",
+    "Both": "ec2,rds"
+   },
+   "Timeouts": {
+    "1": "cron(0/1 * * * ? *)",
+    "2": "cron(0/2 * * * ? *)",
+    "5": "cron(0/5 * * * ? *)",
+    "10": "cron(0/10 * * * ? *)",
+    "15": "cron(0/15 * * * ? *)",
+    "30": "cron(0/30 * * * ? *)",
+    "60": "cron(0 0/1 * * ? *)"
+   },
+   "Settings": {
+    "MetricsUrl": "https://metrics.awssolutionsbuilder.com/generic",
+    "MetricsSolutionId": "S00030"
+   },
+   "SchedulerRole": {
+    "Name": "Scheduler-Role"
+   },
+   "SchedulerEventBusName": {
+    "Name": "scheduler-event-bus"
+   }
+  },
+  "Send": {
+   "AnonymousUsage": {
+    "Data": "Yes"
+   },
+   "ParameterKey": {
+    "UniqueId": "/Solutions/aws-instance-scheduler/UUID/"
+   }
+  },
+  "AppRegistryForInstanceSchedulerSolution25A90F05": {
+   "Data": {
+    "ID": "SO0030",
+    "Version": "1.5.0",
+    "AppRegistryApplicationName": "instance-scheduler-on-aws",
+    "SolutionName": "aws-instance-scheduler",
+    "ApplicationType": "AWS-Solutions"
+   }
+  }
+ },
+ "Resources": {
+  "AppRegistryForInstanceSchedulerDefaultApplicationAttributes78274898": {
+   "Type": "AWS::ServiceCatalogAppRegistry::AttributeGroup",
+   "Properties": {
+    "Attributes": {
+     "applicationType": {
+      "Fn::FindInMap": [
+       "AppRegistryForInstanceSchedulerSolution25A90F05",
+       "Data",
+       "ApplicationType"
+      ]
+     },
+     "version": {
+      "Fn::FindInMap": [
+       "AppRegistryForInstanceSchedulerSolution25A90F05",
+       "Data",
+       "Version"
+      ]
+     },
+     "solutionID": {
+      "Fn::FindInMap": [
+       "AppRegistryForInstanceSchedulerSolution25A90F05",
+       "Data",
+       "ID"
+      ]
+     },
+     "solutionName": {
+      "Fn::FindInMap": [
+       "AppRegistryForInstanceSchedulerSolution25A90F05",
+       "Data",
+       "SolutionName"
+      ]
+     }
+    },
+    "Name": {
+     "Fn::Join": [
+      "-",
+      [
+       {
+        "Ref": "AWS::Region"
+       },
+       {
+        "Ref": "AWS::StackName"
+       }
+      ]
+     ]
+    },
+    "Description": "Attribute group for solution information"
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/AppRegistryForInstanceScheduler/DefaultApplicationAttributes/Resource"
+   }
+  },
+  "AppRegistry968496A3": {
+   "Type": "AWS::ServiceCatalogAppRegistry::Application",
+   "Properties": {
+    "Name": {
+     "Fn::Join": [
+      "-",
+      [
+       {
+        "Fn::FindInMap": [
+         "AppRegistryForInstanceSchedulerSolution25A90F05",
+         "Data",
+         "AppRegistryApplicationName"
+        ]
+       },
+       {
+        "Ref": "AWS::Region"
+       },
+       {
+        "Ref": "AWS::AccountId"
+       },
+       {
+        "Ref": "AWS::StackName"
+       }
+      ]
+     ]
+    },
+    "Description": {
+     "Fn::Join": [
+      "",
+      [
+       "Service Catalog application to track and manage all your resources for the solution ",
+       {
+        "Fn::FindInMap": [
+         "AppRegistryForInstanceSchedulerSolution25A90F05",
+         "Data",
+         "SolutionName"
+        ]
+       }
+      ]
+     ]
+    },
+    "Tags": {
+     "Solutions:ApplicationType": {
+      "Fn::FindInMap": [
+       "AppRegistryForInstanceSchedulerSolution25A90F05",
+       "Data",
+       "ApplicationType"
+      ]
+     },
+     "Solutions:SolutionID": {
+      "Fn::FindInMap": [
+       "AppRegistryForInstanceSchedulerSolution25A90F05",
+       "Data",
+       "ID"
+      ]
+     },
+     "Solutions:SolutionName": {
+      "Fn::FindInMap": [
+       "AppRegistryForInstanceSchedulerSolution25A90F05",
+       "Data",
+       "SolutionName"
+      ]
+     },
+     "Solutions:SolutionVersion": {
+      "Fn::FindInMap": [
+       "AppRegistryForInstanceSchedulerSolution25A90F05",
+       "Data",
+       "Version"
+      ]
+     }
+    }
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/AppRegistry/Resource"
+   }
+  },
+  "AppRegistryResourceAssociationaf0b8dafb9748CD626E9": {
+   "Type": "AWS::ServiceCatalogAppRegistry::ResourceAssociation",
+   "Properties": {
+    "Application": {
+     "Fn::GetAtt": [
+      "AppRegistry968496A3",
+      "Id"
+     ]
+    },
+    "Resource": {
+     "Ref": "AWS::StackId"
+    },
+    "ResourceType": "CFN_STACK"
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/AppRegistry/ResourceAssociationaf0b8dafb974"
+   }
+  },
+  "AppRegistryAttributeGroupAssociation9b87a9e8202fDED525C6": {
+   "Type": "AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation",
+   "Properties": {
+    "Application": {
+     "Fn::GetAtt": [
+      "AppRegistry968496A3",
+      "Id"
+     ]
+    },
+    "AttributeGroup": {
+     "Fn::GetAtt": [
+      "AppRegistryForInstanceSchedulerDefaultApplicationAttributes78274898",
+      "Id"
+     ]
+    }
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/AppRegistry/AttributeGroupAssociation9b87a9e8202f"
+   }
+  },
+  "SchedulerLogGroup": {
+   "Type": "AWS::Logs::LogGroup",
+   "Properties": {
+    "LogGroupName": {
+     "Fn::Join": [
+      "",
+      [
+       {
+        "Ref": "AWS::StackName"
+       },
+       "-logs"
+      ]
+     ]
+    },
+    "RetentionInDays": {
+     "Ref": "LogRetentionDays"
+    }
+   },
+   "UpdateReplacePolicy": "Delete",
+   "DeletionPolicy": "Delete",
+   "Metadata": {
+    "cfn_nag": {
+     "rules_to_suppress": [
+      {
+       "id": "W84",
+       "reason": "CloudWatch log groups only have transactional data from the Lambda function, this template has to be supported in gov cloud which doesn't yet have the feature to provide kms key id to cloudwatch log group."
+      }
+     ]
+    }
+   }
+  },
+  "SchedulerRole": {
+   "Type": "AWS::IAM::Role",
+   "Properties": {
+    "AssumeRolePolicyDocument": {
+     "Statement": [
+      {
+       "Action": "sts:AssumeRole",
+       "Effect": "Allow",
+       "Principal": {
+        "Service": "events.amazonaws.com"
+       }
+      },
+      {
+       "Action": "sts:AssumeRole",
+       "Effect": "Allow",
+       "Principal": {
+        "Service": "lambda.amazonaws.com"
+       }
+      }
+     ],
+     "Version": "2012-10-17"
+    },
+    "Path": "/"
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/SchedulerRole/Resource"
+   }
+  },
+  "SchedulerRoleDefaultPolicy66F774B8": {
+   "Type": "AWS::IAM::Policy",
+   "Properties": {
+    "PolicyDocument": {
+     "Statement": [
+      {
+       "Action": [
+        "xray:PutTraceSegments",
+        "xray:PutTelemetryRecords"
+       ],
+       "Effect": "Allow",
+       "Resource": "*"
+      },
+      {
+       "Action": [
+        "dynamodb:BatchGetItem",
+        "dynamodb:GetRecords",
+        "dynamodb:GetShardIterator",
+        "dynamodb:Query",
+        "dynamodb:GetItem",
+        "dynamodb:Scan",
+        "dynamodb:ConditionCheckItem",
+        "dynamodb:BatchWriteItem",
+        "dynamodb:PutItem",
+        "dynamodb:UpdateItem",
+        "dynamodb:DeleteItem",
+        "dynamodb:DescribeTable"
+       ],
+       "Effect": "Allow",
+       "Resource": [
+        {
+         "Fn::GetAtt": [
+          "StateTable",
+          "Arn"
+         ]
+        },
+        {
+         "Ref": "AWS::NoValue"
+        }
+       ]
+      },
+      {
+       "Action": [
+        "dynamodb:DeleteItem",
+        "dynamodb:GetItem",
+        "dynamodb:PutItem",
+        "dynamodb:Query",
+        "dynamodb:Scan",
+        "dynamodb:BatchWriteItem",
+        "dynamodb:UpdateItem"
+       ],
+       "Effect": "Allow",
+       "Resource": [
+        {
+         "Fn::GetAtt": [
+          "ConfigTable",
+          "Arn"
+         ]
+        },
+        {
+         "Fn::GetAtt": [
+          "MaintenanceWindowTable",
+          "Arn"
+         ]
+        }
+       ]
+      },
+      {
+       "Action": [
+        "ssm:PutParameter",
+        "ssm:GetParameter"
+       ],
+       "Effect": "Allow",
+       "Resource": {
+        "Fn::Sub": "arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/Solutions/aws-instance-scheduler/UUID/*"
+       }
+      }
+     ],
+     "Version": "2012-10-17"
+    },
+    "PolicyName": "SchedulerRoleDefaultPolicy66F774B8",
+    "Roles": [
+     {
+      "Ref": "SchedulerRole"
+     }
+    ]
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/SchedulerRole/DefaultPolicy/Resource",
+    "cdk_nag": {
+     "rules_to_suppress": [
+      {
+       "reason": "The scheduling lambda must access multiple resources across services",
+       "id": "AwsSolutions-IAM5"
+      }
+     ]
+    }
+   }
+  },
+  "InstanceSchedulerEncryptionKey": {
+   "Type": "AWS::KMS::Key",
+   "Properties": {
+    "KeyPolicy": {
+     "Statement": [
+      {
+       "Action": "kms:*",
+       "Effect": "Allow",
+       "Principal": {
+        "AWS": {
+         "Fn::Join": [
+          "",
+          [
+           "arn:",
+           {
+            "Ref": "AWS::Partition"
+           },
+           ":iam::",
+           {
+            "Ref": "AWS::AccountId"
+           },
+           ":root"
+          ]
+         ]
+        }
+       },
+       "Resource": "*",
+       "Sid": "default"
+      },
+      {
+       "Action": [
+        "kms:GenerateDataKey*",
+        "kms:Decrypt"
+       ],
+       "Effect": "Allow",
+       "Principal": {
+        "AWS": {
+         "Fn::GetAtt": [
+          "SchedulerRole",
+          "Arn"
+         ]
+        }
+       },
+       "Resource": "*",
+       "Sid": "Allows use of key"
+      }
+     ],
+     "Version": "2012-10-17"
+    },
+    "Description": "Key for SNS",
+    "Enabled": true,
+    "EnableKeyRotation": true
+   },
+   "UpdateReplacePolicy": "Delete",
+   "DeletionPolicy": "Delete",
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/InstanceSchedulerEncryptionKey/Resource"
+   }
+  },
+  "InstanceSchedulerEncryptionKeyAlias": {
+   "Type": "AWS::KMS::Alias",
+   "Properties": {
+    "AliasName": {
+     "Fn::Join": [
+      "",
+      [
+       "alias/",
+       {
+        "Ref": "AWS::StackName"
+       },
+       "-instance-scheduler-encryption-key"
+      ]
+     ]
+    },
+    "TargetKeyId": {
+     "Fn::GetAtt": [
+      "InstanceSchedulerEncryptionKey",
+      "Arn"
+     ]
+    }
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/InstanceSchedulerEncryptionKeyAlias/Resource"
+   }
+  },
+  "InstanceSchedulerSnsTopic": {
+   "Type": "AWS::SNS::Topic",
+   "Properties": {
+    "KmsMasterKeyId": {
+     "Fn::GetAtt": [
+      "InstanceSchedulerEncryptionKey",
+      "Arn"
+     ]
+    }
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/InstanceSchedulerSnsTopic/Resource"
+   }
+  },
+  "Main": {
+   "Type": "AWS::Lambda::Function",
+   "Properties": {
+    "Code": {
+     "S3Bucket": {
+      "Fn::Sub": "solutions-${AWS::Region}"
+     },
+     "S3Key": "aws-instance-scheduler/v1.5.0/a9fbd49b1e2bd4f908ebff8ba450278da49ec8bf686be5b493b1a8c1942ae7c7.zip"
+    },
+    "Role": {
+     "Fn::GetAtt": [
+      "SchedulerRole",
+      "Arn"
+     ]
+    },
+    "Description": "EC2 and RDS instance scheduler, version 1.5.0",
+    "Environment": {
+     "Variables": {
+      "SCHEDULER_FREQUENCY": {
+       "Ref": "SchedulerFrequency"
+      },
+      "TAG_NAME": {
+       "Ref": "TagName"
+      },
+      "LOG_GROUP": {
+       "Ref": "SchedulerLogGroup"
+      },
+      "ACCOUNT": {
+       "Ref": "AWS::AccountId"
+      },
+      "ISSUES_TOPIC_ARN": {
+       "Ref": "InstanceSchedulerSnsTopic"
+      },
+      "STACK_NAME": {
+       "Ref": "AWS::StackName"
+      },
+      "SEND_METRICS": {
+       "Fn::FindInMap": [
+        "mappings",
+        "TrueFalse",
+        {
+         "Fn::FindInMap": [
+          "Send",
+          "AnonymousUsage",
+          "Data"
+         ]
+        }
+       ]
+      },
+      "SOLUTION_ID": {
+       "Fn::FindInMap": [
+        "mappings",
+        "Settings",
+        "MetricsSolutionId"
+       ]
+      },
+      "TRACE": {
+       "Fn::FindInMap": [
+        "mappings",
+        "TrueFalse",
+        {
+         "Ref": "Trace"
+        }
+       ]
+      },
+      "ENABLE_SSM_MAINTENANCE_WINDOWS": {
+       "Fn::FindInMap": [
+        "mappings",
+        "TrueFalse",
+        {
+         "Ref": "EnableSSMMaintenanceWindows"
+        }
+       ]
+      },
+      "USER_AGENT": {
+       "Fn::Join": [
+        "",
+        [
+         "InstanceScheduler-",
+         {
+          "Ref": "AWS::StackName"
+         },
+         "-1.5.0"
+        ]
+       ]
+      },
+      "USER_AGENT_EXTRA": "AwsSolution/SO0030/1.5.0",
+      "METRICS_URL": {
+       "Fn::FindInMap": [
+        "mappings",
+        "Settings",
+        "MetricsUrl"
+       ]
+      },
+      "STACK_ID": {
+       "Ref": "AWS::StackId"
+      },
+      "UUID_KEY": {
+       "Fn::FindInMap": [
+        "Send",
+        "ParameterKey",
+        "UniqueId"
+       ]
+      },
+      "START_EC2_BATCH_SIZE": "5",
+      "DDB_TABLE_NAME": {
+       "Ref": "StateTable"
+      },
+      "CONFIG_TABLE": {
+       "Ref": "ConfigTable"
+      },
+      "MAINTENANCE_WINDOW_TABLE": {
+       "Ref": "MaintenanceWindowTable"
+      },
+      "STATE_TABLE": {
+       "Ref": "StateTable"
+      }
+     }
+    },
+    "FunctionName": {
+     "Fn::Join": [
+      "",
+      [
+       {
+        "Ref": "AWS::StackName"
+       },
+       "-InstanceSchedulerMain"
+      ]
+     ]
+    },
+    "Handler": "instance_scheduler.main.lambda_handler",
+    "MemorySize": {
+     "Ref": "MemorySize"
+    },
+    "Runtime": "python3.9",
+    "Timeout": 300,
+    "TracingConfig": {
+     "Mode": "Active"
+    }
+   },
+   "DependsOn": [
+    "EC2DynamoDBPolicy",
+    "Ec2PermissionsB6E87802",
+    "SchedulerPolicy",
+    "SchedulerRDSPolicy2E7C328A",
+    "SchedulerRoleDefaultPolicy66F774B8",
+    "SchedulerRole"
+   ],
+   "Metadata": {
+    "cfn_nag": {
+     "rules_to_suppress": [
+      {
+       "id": "W89",
+       "reason": "This Lambda function does not need to access any resource provisioned within a VPC."
+      },
+      {
+       "id": "W58",
+       "reason": "This Lambda function has permission provided to write to CloudWatch logs using the iam roles."
+      },
+      {
+       "id": "W92",
+       "reason": "Lambda function is only used by the event rule periodically, concurrent calls are very limited."
+      }
+     ]
+    }
+   }
+  },
+  "StateTable": {
+   "Type": "AWS::DynamoDB::Table",
+   "Properties": {
+    "KeySchema": [
+     {
+      "AttributeName": "service",
+      "KeyType": "HASH"
+     },
+     {
+      "AttributeName": "account-region",
+      "KeyType": "RANGE"
+     }
+    ],
+    "AttributeDefinitions": [
+     {
+      "AttributeName": "service",
+      "AttributeType": "S"
+     },
+     {
+      "AttributeName": "account-region",
+      "AttributeType": "S"
+     }
+    ],
+    "BillingMode": "PAY_PER_REQUEST",
+    "PointInTimeRecoverySpecification": {
+     "PointInTimeRecoveryEnabled": true
+    },
+    "SSESpecification": {
+     "KMSMasterKeyId": {
+      "Ref": "InstanceSchedulerEncryptionKey"
+     },
+     "SSEEnabled": true,
+     "SSEType": "KMS"
+    }
+   },
+   "UpdateReplacePolicy": "Delete",
+   "DeletionPolicy": "Delete",
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/instance-scheduler-lambda/DynamoTable/Resource"
+   }
+  },
+  "ConfigTable": {
+   "Type": "AWS::DynamoDB::Table",
+   "Properties": {
+    "KeySchema": [
+     {
+      "AttributeName": "type",
+      "KeyType": "HASH"
+     },
+     {
+      "AttributeName": "name",
+      "KeyType": "RANGE"
+     }
+    ],
+    "AttributeDefinitions": [
+     {
+      "AttributeName": "type",
+      "AttributeType": "S"
+     },
+     {
+      "AttributeName": "name",
+      "AttributeType": "S"
+     }
+    ],
+    "BillingMode": "PAY_PER_REQUEST",
+    "PointInTimeRecoverySpecification": {
+     "PointInTimeRecoveryEnabled": true
+    },
+    "SSESpecification": {
+     "KMSMasterKeyId": {
+      "Ref": "InstanceSchedulerEncryptionKey"
+     },
+     "SSEEnabled": true,
+     "SSEType": "KMS"
+    }
+   },
+   "UpdateReplacePolicy": "Delete",
+   "DeletionPolicy": "Delete",
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/ConfigTable/Resource"
+   }
+  },
+  "MaintenanceWindowTable": {
+   "Type": "AWS::DynamoDB::Table",
+   "Properties": {
+    "KeySchema": [
+     {
+      "AttributeName": "Name",
+      "KeyType": "HASH"
+     },
+     {
+      "AttributeName": "account-region",
+      "KeyType": "RANGE"
+     }
+    ],
+    "AttributeDefinitions": [
+     {
+      "AttributeName": "Name",
+      "AttributeType": "S"
+     },
+     {
+      "AttributeName": "account-region",
+      "AttributeType": "S"
+     }
+    ],
+    "BillingMode": "PAY_PER_REQUEST",
+    "PointInTimeRecoverySpecification": {
+     "PointInTimeRecoveryEnabled": true
+    },
+    "SSESpecification": {
+     "KMSMasterKeyId": {
+      "Ref": "InstanceSchedulerEncryptionKey"
+     },
+     "SSEEnabled": true,
+     "SSEType": "KMS"
+    }
+   },
+   "UpdateReplacePolicy": "Delete",
+   "DeletionPolicy": "Delete",
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/MaintenanceWindowTable/Resource"
+   }
+  },
+  "schedulereventbus": {
+   "Type": "AWS::Events::EventBus",
+   "Properties": {
+    "Name": {
+     "Fn::Join": [
+      "",
+      [
+       {
+        "Ref": "Namespace"
+       },
+       "-",
+       {
+        "Fn::FindInMap": [
+         "mappings",
+         "SchedulerEventBusName",
+         "Name"
+        ]
+       }
+      ]
+     ]
+    }
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/scheduler-event-bus"
+   },
+   "Condition": "IsMemberOfOrganization"
+  },
+  "schedulereventbuspolicy": {
+   "Type": "AWS::Events::EventBusPolicy",
+   "Properties": {
+    "StatementId": {
+     "Fn::GetAtt": [
+      "schedulereventbus",
+      "Name"
+     ]
+    },
+    "Action": "events:PutEvents",
+    "Condition": {
+     "Key": "aws:PrincipalOrgID",
+     "Type": "StringEquals",
+     "Value": {
+      "Fn::Select": [
+       0,
+       {
+        "Ref": "Principals"
+       }
+      ]
+     }
+    },
+    "EventBusName": {
+     "Fn::GetAtt": [
+      "schedulereventbus",
+      "Name"
+     ]
+    },
+    "Principal": "*"
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/scheduler-event-bus-policy"
+   },
+   "Condition": "IsMemberOfOrganization"
+  },
+  "schedulerssmparametercrossaccountevents": {
+   "Type": "AWS::Events::Rule",
+   "Properties": {
+    "Description": "Event rule to invoke Instance Scheduler lambda function to store spoke account id(s) in configuration.",
+    "EventBusName": {
+     "Fn::GetAtt": [
+      "schedulereventbus",
+      "Name"
+     ]
+    },
+    "EventPattern": {
+     "source": [
+      "aws.ssm"
+     ],
+     "detail-type": [
+      "Parameter Store Change"
+     ],
+     "detail": {
+      "name": [
+       "/instance-scheduler/do-not-delete-manually"
+      ],
+      "operation": [
+       "Create",
+       "Delete"
+      ],
+      "type": [
+       "String"
+      ]
+     }
+    },
+    "State": "ENABLED",
+    "Targets": [
+     {
+      "Arn": {
+       "Fn::GetAtt": [
+        "Main",
+        "Arn"
+       ]
+      },
+      "Id": "Scheduler-Lambda-Function"
+     }
+    ]
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/scheduler-ssm-parameter-cross-account-events"
+   },
+   "Condition": "IsMemberOfOrganization"
+  },
+  "EventBusRuleLambdaPermission": {
+   "Type": "AWS::Lambda::Permission",
+   "Properties": {
+    "Action": "lambda:InvokeFunction",
+    "FunctionName": {
+     "Ref": "Main"
+    },
+    "Principal": "events.amazonaws.com",
+    "SourceArn": {
+     "Fn::GetAtt": [
+      "schedulerssmparametercrossaccountevents",
+      "Arn"
+     ]
+    }
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/EventBusRuleLambdaPermission"
+   },
+   "Condition": "IsMemberOfOrganization"
+  },
+  "SchedulerRule": {
+   "Type": "AWS::Events::Rule",
+   "Properties": {
+    "Description": "Instance Scheduler - Rule to trigger instance for scheduler function version 1.5.0",
+    "ScheduleExpression": {
+     "Fn::FindInMap": [
+      "mappings",
+      "Timeouts",
+      {
+       "Ref": "SchedulerFrequency"
+      }
+     ]
+    },
+    "State": {
+     "Fn::FindInMap": [
+      "mappings",
+      "EnabledDisabled",
+      {
+       "Ref": "SchedulingActive"
+      }
+     ]
+    },
+    "Targets": [
+     {
+      "Arn": {
+       "Fn::GetAtt": [
+        "Main",
+        "Arn"
+       ]
+      },
+      "Id": "Target0"
+     }
+    ]
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/SchedulerEventRule/Resource"
+   }
+  },
+  "SchedulerEventRuleAllowEventRuleawsinstanceschedulerschedulerlambda711BB00B6BF6E2FE": {
+   "Type": "AWS::Lambda::Permission",
+   "Properties": {
+    "Action": "lambda:InvokeFunction",
+    "FunctionName": {
+     "Fn::GetAtt": [
+      "Main",
+      "Arn"
+     ]
+    },
+    "Principal": "events.amazonaws.com",
+    "SourceArn": {
+     "Fn::GetAtt": [
+      "SchedulerRule",
+      "Arn"
+     ]
+    }
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/SchedulerEventRule/AllowEventRuleawsinstanceschedulerschedulerlambda711BB00B"
+   }
+  },
+  "SchedulerConfigHelper": {
+   "Type": "Custom::ServiceSetup",
+   "Properties": {
+    "ServiceToken": {
+     "Fn::GetAtt": [
+      "Main",
+      "Arn"
+     ]
+    },
+    "timeout": 120,
+    "config_table": {
+     "Ref": "ConfigTable"
+    },
+    "tagname": {
+     "Ref": "TagName"
+    },
+    "default_timezone": {
+     "Ref": "DefaultTimezone"
+    },
+    "use_metrics": {
+     "Fn::FindInMap": [
+      "mappings",
+      "TrueFalse",
+      {
+       "Ref": "UseCloudWatchMetrics"
+      }
+     ]
+    },
+    "scheduled_services": {
+     "Fn::Split": [
+      ",",
+      {
+       "Fn::FindInMap": [
+        "mappings",
+        "Services",
+        {
+         "Ref": "ScheduledServices"
+        }
+       ]
+      }
+     ]
+    },
+    "schedule_clusters": {
+     "Fn::FindInMap": [
+      "mappings",
+      "TrueFalse",
+      {
+       "Ref": "ScheduleRdsClusters"
+      }
+     ]
+    },
+    "create_rds_snapshot": {
+     "Fn::FindInMap": [
+      "mappings",
+      "TrueFalse",
+      {
+       "Ref": "CreateRdsSnapshot"
+      }
+     ]
+    },
+    "regions": {
+     "Ref": "Regions"
+    },
+    "remote_account_ids": {
+     "Ref": "Principals"
+    },
+    "namespace": {
+     "Ref": "Namespace"
+    },
+    "aws_partition": {
+     "Fn::Sub": "${AWS::Partition}"
+    },
+    "scheduler_role_name": {
+     "Fn::FindInMap": [
+      "mappings",
+      "SchedulerRole",
+      "Name"
+     ]
+    },
+    "schedule_lambda_account": {
+     "Fn::FindInMap": [
+      "mappings",
+      "TrueFalse",
+      {
+       "Ref": "ScheduleLambdaAccount"
+      }
+     ]
+    },
+    "trace": {
+     "Fn::FindInMap": [
+      "mappings",
+      "TrueFalse",
+      {
+       "Ref": "Trace"
+      }
+     ]
+    },
+    "enable_SSM_maintenance_windows": {
+     "Fn::FindInMap": [
+      "mappings",
+      "TrueFalse",
+      {
+       "Ref": "EnableSSMMaintenanceWindows"
+      }
+     ]
+    },
+    "log_retention_days": {
+     "Ref": "LogRetentionDays"
+    },
+    "started_tags": {
+     "Ref": "StartedTags"
+    },
+    "stopped_tags": {
+     "Ref": "StoppedTags"
+    },
+    "stack_version": "1.5.0",
+    "use_aws_organizations": {
+     "Fn::FindInMap": [
+      "mappings",
+      "TrueFalse",
+      {
+       "Ref": "UsingAWSOrganizations"
+      }
+     ]
+    }
+   },
+   "DependsOn": [
+    "SchedulerLogGroup"
+   ],
+   "UpdateReplacePolicy": "Delete",
+   "DeletionPolicy": "Delete",
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/ServiceSetup/Default"
+   }
+  },
+  "Ec2PermissionsB6E87802": {
+   "Type": "AWS::IAM::Policy",
+   "Properties": {
+    "PolicyDocument": {
+     "Statement": [
+      {
+       "Action": "ec2:ModifyInstanceAttribute",
+       "Effect": "Allow",
+       "Resource": {
+        "Fn::Sub": "arn:${AWS::Partition}:ec2:*:${AWS::AccountId}:instance/*"
+       }
+      },
+      {
+       "Action": "sts:AssumeRole",
+       "Effect": "Allow",
+       "Resource": {
+        "Fn::Sub": [
+         "arn:${AWS::Partition}:iam::*:role/${Namespace}-${Name}",
+         {
+          "Name": {
+           "Fn::FindInMap": [
+            "mappings",
+            "SchedulerRole",
+            "Name"
+           ]
+          }
+         }
+        ]
+       }
+      }
+     ],
+     "Version": "2012-10-17"
+    },
+    "PolicyName": "Ec2PermissionsB6E87802",
+    "Roles": [
+     {
+      "Ref": "SchedulerRole"
+     }
+    ]
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/Ec2Permissions/Resource",
+    "cdk_nag": {
+     "rules_to_suppress": [
+      {
+       "reason": "This Lambda function needs to be able to modify ec2 instances for scheduling purposes.",
+       "id": "AwsSolutions-IAM5"
+      }
+     ]
+    }
+   }
+  },
+  "EC2DynamoDBPolicy": {
+   "Type": "AWS::IAM::Policy",
+   "Properties": {
+    "PolicyDocument": {
+     "Statement": [
+      {
+       "Action": [
+        "ssm:GetParameter",
+        "ssm:GetParameters"
+       ],
+       "Effect": "Allow",
+       "Resource": {
+        "Fn::Sub": "arn:${AWS::Partition}:ssm:*:${AWS::AccountId}:parameter/*"
+       }
+      },
+      {
+       "Action": [
+        "logs:DescribeLogStreams",
+        "rds:DescribeDBClusters",
+        "rds:DescribeDBInstances",
+        "ec2:DescribeInstances",
+        "ec2:DescribeRegions",
+        "cloudwatch:PutMetricData",
+        "ssm:DescribeMaintenanceWindows",
+        "tag:GetResources"
+       ],
+       "Effect": "Allow",
+       "Resource": "*"
+      },
+      {
+       "Action": [
+        "logs:CreateLogGroup",
+        "logs:CreateLogStream",
+        "logs:PutLogEvents",
+        "logs:PutRetentionPolicy"
+       ],
+       "Effect": "Allow",
+       "Resource": [
+        {
+         "Fn::Sub": "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*"
+        },
+        {
+         "Fn::GetAtt": [
+          "SchedulerLogGroup",
+          "Arn"
+         ]
+        }
+       ]
+      }
+     ],
+     "Version": "2012-10-17"
+    },
+    "PolicyName": "EC2DynamoDBPolicy",
+    "Roles": [
+     {
+      "Ref": "SchedulerRole"
+     }
+    ]
+   },
+   "Metadata": {
+    "cfn_nag": {
+     "rules_to_suppress": [
+      {
+       "id": "W12",
+       "reason": "All policies have been scoped to be as restrictive as possible. This solution needs to access ec2/rds resources across all regions."
+      }
+     ]
+    },
+    "cdk_nag": {
+     "rules_to_suppress": [
+      {
+       "reason": "All policies have been scoped to be as restrictive as possible. This solution needs to access ec2/rds resources across all regions.",
+       "id": "AwsSolutions-IAM5"
+      }
+     ]
+    }
+   }
+  },
+  "SchedulerPolicy": {
+   "Type": "AWS::IAM::Policy",
+   "Properties": {
+    "PolicyDocument": {
+     "Statement": [
+      {
+       "Action": [
+        "rds:AddTagsToResource",
+        "rds:RemoveTagsFromResource",
+        "rds:DescribeDBSnapshots",
+        "rds:StartDBInstance",
+        "rds:StopDBInstance"
+       ],
+       "Effect": "Allow",
+       "Resource": {
+        "Fn::Sub": "arn:${AWS::Partition}:rds:*:${AWS::AccountId}:db:*"
+       }
+      },
+      {
+       "Action": [
+        "ec2:StartInstances",
+        "ec2:StopInstances",
+        "ec2:CreateTags",
+        "ec2:DeleteTags"
+       ],
+       "Effect": "Allow",
+       "Resource": {
+        "Fn::Sub": "arn:${AWS::Partition}:ec2:*:${AWS::AccountId}:instance/*"
+       }
+      },
+      {
+       "Action": "sns:Publish",
+       "Effect": "Allow",
+       "Resource": {
+        "Ref": "InstanceSchedulerSnsTopic"
+       }
+      },
+      {
+       "Action": "lambda:InvokeFunction",
+       "Effect": "Allow",
+       "Resource": {
+        "Fn::Sub": "arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${AWS::StackName}-InstanceSchedulerMain"
+       }
+      },
+      {
+       "Action": [
+        "kms:GenerateDataKey*",
+        "kms:Decrypt"
+       ],
+       "Effect": "Allow",
+       "Resource": {
+        "Fn::GetAtt": [
+         "InstanceSchedulerEncryptionKey",
+         "Arn"
+        ]
+       }
+      }
+     ],
+     "Version": "2012-10-17"
+    },
+    "PolicyName": "SchedulerPolicy",
+    "Roles": [
+     {
+      "Ref": "SchedulerRole"
+     }
+    ]
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/SchedulerPolicy/Resource",
+    "cdk_nag": {
+     "rules_to_suppress": [
+      {
+       "reason": "All policies have been scoped to be as restrictive as possible. This solution needs to access ec2/rds resources across all regions.",
+       "id": "AwsSolutions-IAM5"
+      }
+     ]
+    }
+   }
+  },
+  "SchedulerRDSPolicy2E7C328A": {
+   "Type": "AWS::IAM::Policy",
+   "Properties": {
+    "PolicyDocument": {
+     "Statement": [
+      {
+       "Action": [
+        "rds:DeleteDBSnapshot",
+        "rds:DescribeDBSnapshots",
+        "rds:StopDBInstance"
+       ],
+       "Effect": "Allow",
+       "Resource": {
+        "Fn::Sub": "arn:${AWS::Partition}:rds:*:${AWS::AccountId}:snapshot:*"
+       }
+      },
+      {
+       "Action": [
+        "rds:AddTagsToResource",
+        "rds:RemoveTagsFromResource",
+        "rds:StartDBCluster",
+        "rds:StopDBCluster"
+       ],
+       "Effect": "Allow",
+       "Resource": {
+        "Fn::Sub": "arn:${AWS::Partition}:rds:*:${AWS::AccountId}:cluster:*"
+       }
+      }
+     ],
+     "Version": "2012-10-17"
+    },
+    "PolicyName": "SchedulerRDSPolicy2E7C328A",
+    "Roles": [
+     {
+      "Ref": "SchedulerRole"
+     }
+    ]
+   },
+   "Metadata": {
+    "aws:cdk:path": "aws-instance-scheduler/SchedulerRDSPolicy/Resource",
+    "cdk_nag": {
+     "rules_to_suppress": [
+      {
+       "reason": "All policies have been scoped to be as restrictive as possible. This solution needs to access ec2/rds resources across all regions.",
+       "id": "AwsSolutions-IAM5"
+      }
+     ]
+    }
+   }
+  }
+ },
+ "Outputs": {
+  "AppRegistryApplicationManagerUrl775D5C3D": {
+   "Description": "Application manager url for the application created.",
+   "Value": {
+    "Fn::Join": [
+     "",
+     [
+      "https://",
+      {
+       "Ref": "AWS::Region"
+      },
+      ".console.aws.amazon.com/systems-manager/appmanager/application/AWS_AppRegistry_Application-",
+      {
+       "Fn::Join": [
+        "-",
+        [
+         {
+          "Fn::FindInMap": [
+           "AppRegistryForInstanceSchedulerSolution25A90F05",
+           "Data",
+           "AppRegistryApplicationName"
+          ]
+         },
+         {
+          "Ref": "AWS::Region"
+         },
+         {
+          "Ref": "AWS::AccountId"
+         },
+         {
+          "Ref": "AWS::StackName"
+         }
+        ]
+       ]
+      }
+     ]
+    ]
+   }
+  },
+  "AccountId": {
+   "Description": "Account to give access to when creating cross-account access role for cross account scenario ",
+   "Value": {
+    "Ref": "AWS::AccountId"
+   }
+  },
+  "ConfigurationTable": {
+   "Description": "Name of the DynamoDB configuration table",
+   "Value": {
+    "Fn::GetAtt": [
+     "ConfigTable",
+     "Arn"
+    ]
+   }
+  },
+  "IssueSnsTopicArn": {
+   "Description": "Topic to subscribe to for notifications of errors and warnings",
+   "Value": {
+    "Ref": "InstanceSchedulerSnsTopic"
+   }
+  },
+  "SchedulerRoleArn": {
+   "Description": "Role for the instance scheduler lambda function",
+   "Value": {
+    "Fn::GetAtt": [
+     "SchedulerRole",
+     "Arn"
+    ]
+   }
+  },
+  "ServiceInstanceScheduleServiceToken": {
+   "Description": "Arn to use as ServiceToken property for custom resource type Custom::ServiceInstanceSchedule",
+   "Value": {
+    "Fn::GetAtt": [
+     "Main",
+     "Arn"
+    ]
+   }
+  }
+ }
+}
\ No newline at end of file
diff --git a/tests/data/templates/aws-trusted-advisor-explorer.template b/tests/data/templates/aws-trusted-advisor-explorer.template
new file mode 100644
index 0000000..fdcb394
--- /dev/null
+++ b/tests/data/templates/aws-trusted-advisor-explorer.template
@@ -0,0 +1,4127 @@
+{
+    "AWSTemplateFormatVersion": "2010-09-09",
+    "Description": "(SO0082) - AWS Trusted Advisor Explorer Solution template",
+    "Parameters": {
+        "CrossAccountRoleName": {
+            "Description": "(Required) The CrossAccount Role Name that exists in all of the Member Accounts (ex: OrganizationAccountAccessRole)",
+            "Type": "String"
+        },
+        "Language": {
+            "AllowedValues": [
+                "en"
+            ],
+            "Description": "AWS Trusted Advisor Explorer currently supports English ('en') only.",
+            "Type": "String",
+            "Default": "en"
+        },
+        "ReportSchedule": {
+            "Description": "(Required) Schedule for gathering trusted advisor recommendations and/or tag data, ex: cron(0 9 1 * ? *) see Link:https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions",
+            "Type": "String",
+            "Default": "cron(0 9 1 * ? *)"
+        },
+        "GlueCrawlerSchedule": {
+            "Description": "(Required) Schedule for updating the trusted advisor recommendations data lake with new data; Please set this to 2 hours post the ReportSchedule, ex: cron(0 11 1 * ? *) see Link:https://docs.aws.amazon.com/glue/latest/dg/monitor-data-warehouse-schedule.html",
+            "Type": "String",
+            "Default": "cron(0 11 1 * ? *)"
+        },
+        "InterestedTagKeys": {
+            "Description": "(Optional) Tags that you would like to extract Ex: env,costcenter,asset_id,etc ..",
+            "Type": "String"
+        },
+        "SNSEmail": {
+            "Description": "(Required) The email address to alert when Trusted Advisor Data is refreshed.",
+            "Type": "String"
+        },
+        "LogLevel": {
+            "AllowedValues": [
+                "ERROR",
+                "INFO"
+            ],
+            "Description": "Log Level on Lambda Functions. Use INFO for Troubleshooting purposes only.",
+            "Type": "String",
+            "Default": "ERROR"
+        },
+        "MaskAccountInformation": {
+            "AllowedValues": [
+                "true",
+                "false"
+            ],
+            "Description": "Setting this to true will mask Account Id, Account Name & Email saved to Logs",
+            "Type": "String",
+            "Default": "true"
+        }
+    },
+    "Mappings": {
+        "SourceCode": {
+            "General": {
+                "S3Bucket": "%%BUCKET_NAME%%",
+                "KeyPrefix": "%%SOLUTION_NAME%%/%%VERSION%%",
+                "Version": "%%VERSION%%"
+            }
+        },
+        "Send": {
+            "AnonymousUsage": {
+                "Data": "yes"
+            }
+        }
+    },
+    "Resources": {
+        "SolutionHelper": {
+            "Type": "AWS::Lambda::Function",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W58",
+                            "reason": "This lambda has permissions to write to CW Logs."
+                        }
+                    ]
+                }
+            },
+            "Properties": {
+                "Handler": "solution-helper.lambda_handler",
+                "Role": {
+                    "Fn::GetAtt": [
+                        "SolutionHelperLambdaExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Description": "This function creates a CloudFormation custom lambda resource that creates custom lambda functions by finding and replacing specific values from existing lambda function code.",
+                "Code": {
+                    "S3Bucket": {
+                        "Fn::Join": [
+                            "-",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "S3Bucket"
+                                    ]
+                                },
+                                {
+                                    "Ref": "AWS::Region"
+                                }
+                            ]
+                        ]
+                    },
+                    "S3Key": {
+                        "Fn::Join": [
+                            "/",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "KeyPrefix"
+                                    ]
+                                },
+                                "solution-helper.zip"
+                            ]
+                        ]
+                    }
+                },
+                "Runtime": "python3.8",
+                "Timeout": 300
+            }
+        },
+        "SolutionHelperLambdaExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "lambda.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "SolutionHelperLambdaExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "DependsOn": [
+                "SolutionHelper"
+            ],
+            "Properties": {
+                "PolicyName": "AWSTrustedAdEx-SolutionHelperLambdaExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "SolutionHelperLambdaExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": "logs: CreateLogGroup",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:logs:",
+                                        {
+                                            "Ref": "AWS::Region"
+                                        },
+                                        ":",
+                                        {
+                                            "Ref": "AWS::AccountId"
+                                        },
+                                        ":*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "logs: CreateLogStream",
+                                "logs: PutLogEvents"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "SolutionHelper"
+                                            },
+                                            ":*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "SolutionHelper"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:GetObject",
+                                "s3:GetObjectTagging",
+                                "s3:ListBucket",
+                                "s3:GetObjectAcl"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "UUIDGenerator": {
+            "Type": "Custom::UUIDGenerator",
+            "Properties": {
+                "ServiceToken": {
+                    "Fn::GetAtt": [
+                        "SolutionHelper",
+                        "Arn"
+                    ]
+                },
+                "Region": {
+                    "Ref": "AWS::Region"
+                }
+            }
+        },
+        "AWSTrustedAdvisorExplorerBucketAccessLogging": {
+            "Type": "AWS::S3::Bucket",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W51",
+                            "reason": "This bucket already has PublicAccessBlockConfiguration applied."
+                        },
+                        {
+                            "id": "W35",
+                            "reason": "This bucket itself is primarily for access logging"
+                        }
+                    ]
+                }
+            },
+            "DeletionPolicy": "Retain",
+            "Properties": {
+                "Tags": [
+                    {
+                        "Key": "ProjectName",
+                        "Value": "AWS Trusted Advisor Explorer"
+                    }
+                ],
+                "BucketEncryption": {
+                    "ServerSideEncryptionConfiguration": [
+                        {
+                            "ServerSideEncryptionByDefault": {
+                                "SSEAlgorithm": "AES256"
+                            }
+                        }
+                    ]
+                },
+                "AccessControl": "LogDeliveryWrite",
+                "PublicAccessBlockConfiguration": {
+                    "BlockPublicAcls": true,
+                    "BlockPublicPolicy": true,
+                    "IgnorePublicAcls": true,
+                    "RestrictPublicBuckets": true
+                }
+            }
+        },
+        "S3Bucket": {
+            "Type": "AWS::S3::Bucket",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W51",
+                            "reason": "This bucket already has PublicAccessBlockConfiguration applied."
+                        }
+                    ]
+                }
+            },
+            "DeletionPolicy": "Retain",
+            "Properties": {
+                "Tags": [
+                    {
+                        "Key": "ProjectName",
+                        "Value": "AWS Trusted Advisor Explorer"
+                    }
+                ],
+                "BucketEncryption": {
+                    "ServerSideEncryptionConfiguration": [
+                        {
+                            "ServerSideEncryptionByDefault": {
+                                "SSEAlgorithm": "AES256"
+                            }
+                        }
+                    ]
+                },
+                "LoggingConfiguration": {
+                    "DestinationBucketName": {
+                        "Ref": "AWSTrustedAdvisorExplorerBucketAccessLogging"
+                    }
+                },
+                "AccessControl": "Private",
+                "PublicAccessBlockConfiguration": {
+                    "BlockPublicAcls": true,
+                    "BlockPublicPolicy": true,
+                    "IgnorePublicAcls": true,
+                    "RestrictPublicBuckets": true
+                }
+            }
+        },
+        "ExtractTAData": {
+            "Type": "AWS::Lambda::Function",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W58",
+                            "reason": "This lambda has permissions to write to CW Logs."
+                        }
+                    ]
+                }
+            },
+            "DependsOn": [
+                "ExtractTADataLambdaExecutionRole"
+            ],
+            "Properties": {
+                "Code": {
+                    "S3Bucket": {
+                        "Fn::Join": [
+                            "-",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "S3Bucket"
+                                    ]
+                                },
+                                {
+                                    "Ref": "AWS::Region"
+                                }
+                            ]
+                        ]
+                    },
+                    "S3Key": {
+                        "Fn::Join": [
+                            "/",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "KeyPrefix"
+                                    ]
+                                },
+                                "extract-ta-data-lambda.zip"
+                            ]
+                        ]
+                    }
+                },
+                "Role": {
+                    "Fn::GetAtt": [
+                        "ExtractTADataLambdaExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Environment": {
+                    "Variables": {
+                        "S3BucketName": {
+                            "Ref": "S3Bucket"
+                        },
+                        "IAMRoleName": {
+                            "Ref": "CrossAccountRoleName"
+                        },
+                        "Header_1e93e4c0b5": "Status,Zone,Instance Type,Platform,Instance Count,Current Monthly Cost,Estimated Monthly Savings,Expiration Date,Reserved Instance Id,Reason",
+                        "Header_51fC20e7I2": "Status,Hosted Zone Name,Hosted Zone Id,Resource Record Set Name,Resource Record Set Type",
+                        "Header_DAvU99Dc4C": "Status,Region,Volume Id,Volume Name,Volume Type,Volume Size,Monthly Storage Cost,Snapshot Id,Snapshot Name,Snapshot Age",
+                        "Header_G31sQ1E9U": "Status,Region,Cluster,Instance Type,Reason,Estimated Monthly Savings",
+                        "Header_Qch7DwouX1": "Status,Region,AZ,Instance Id,Instance Name,Instance Type,Estimated Monthly Savings,Day1,Day2,Day3,Day4,Day5,Day6,Day7,Day8,Day9,Day10,Day11,Day12,Day13,Day14 Latest Day,14-Day Average CPU Utilization,14-Day Average Network I/O,Number of Days Low Utilization",
+                        "Header_Ti39halfu8": "Status,Region,DB Instance Name,Multi-AZ,Instance Type,Storage Provisioned GB,Days Since Last Connection,Estimated Monthly Savings On Demand",
+                        "Header_Z4AUBRNSmz": "Status,Region,IP Address",
+                        "Header_cX3c2R1chu": "Status,Region,Instance Type,Platform,Recommended Number of RIs to Purchase,Expected Average RI Utilization,Estimated Savings with Recommendation Monthly,Upfront Cost of RIs,Estimated cost of RIs Monthly,Estimated On-Demand Cost Post Recommended RI Purchase Monthly,Estimated Break Even Months,Lookback Period Days,Term Years",
+                        "Header_hjLMh88uM8": "Status,Region,Load Balancer Name,Reason,Estimated Monthly Savings",
+                        "Schema_1e93e4c0b5": "0,1,2,3,4,5,6,7,8,9",
+                        "Schema_51fC20e7I2": "status,0,1,2,3",
+                        "Schema_DAvU99Dc4C": "status,0,1,2,3,4,5,6,7,8",
+                        "Schema_G31sQ1E9U": "0,1,2,3,4,5",
+                        "Schema_Qch7DwouX1": "status,region,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21",
+                        "Schema_Ti39halfu8": "status,0,1,2,3,4,5,6",
+                        "Schema_Z4AUBRNSmz": "status,0,1",
+                        "Schema_cX3c2R1chu": "status,0,1,2,3,4,5,6,7,8,9,10,11",
+                        "Schema_hjLMh88uM8": "status,0,1,2,3",
+                        "LOG_LEVEL": {
+                            "Ref": "LogLevel"
+                        },
+                        "Header_Summary": "CheckId,Status,ResourcesProcessed,ResourcesFlagged,ResourcesIgnored,ResourcesSuppressed,EstimatedMonthlySavings,EstimatedPercentMonthlySavings",
+                        "MASK_PII": {
+                            "Ref": "MaskAccountInformation"
+                        }
+                    }
+                },
+                "Timeout": 300,
+                "Handler": "extract-ta-data-lambda.lambda_handler",
+                "Runtime": "python3.8",
+                "MemorySize": 256
+            }
+        },
+        "ExtractTADataLambdaExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "lambda.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "ExtractTADataLambdaExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "DependsOn": [
+                "ExtractTAData"
+            ],
+            "Properties": {
+                "PolicyName": "ExtractTADataLambdaExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "ExtractTADataLambdaExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": "logs:CreateLogGroup",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:logs:",
+                                        {
+                                            "Ref": "AWS::Region"
+                                        },
+                                        ":",
+                                        {
+                                            "Ref": "AWS::AccountId"
+                                        },
+                                        ":*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "logs:CreateLogStream",
+                                "logs:PutLogEvents"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "ExtractTAData"
+                                            },
+                                            ":*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "ExtractTAData"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:PutObject",
+                                "s3:PutObjectAcl"
+                            ],
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:s3:::",
+                                        {
+                                            "Ref": "S3Bucket"
+                                        },
+                                        "/*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": "sts:AssumeRole",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:iam::*:role/",
+                                        {
+                                            "Ref": "CrossAccountRoleName"
+                                        }
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:GetObject",
+                                "s3:GetObjectTagging",
+                                "s3:ListBucket",
+                                "s3:GetObjectAcl"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "VerifyTACheckStatusLambda": {
+            "Type": "AWS::Lambda::Function",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W58",
+                            "reason": "This lambda has permissions to write to CW Logs."
+                        }
+                    ]
+                }
+            },
+            "DependsOn": [
+                "VerifyTACheckStatusLambdaExecutionRole"
+            ],
+            "Properties": {
+                "Code": {
+                    "S3Bucket": {
+                        "Fn::Join": [
+                            "-",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "S3Bucket"
+                                    ]
+                                },
+                                {
+                                    "Ref": "AWS::Region"
+                                }
+                            ]
+                        ]
+                    },
+                    "S3Key": {
+                        "Fn::Join": [
+                            "/",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "KeyPrefix"
+                                    ]
+                                },
+                                "verify-ta-check-status-lambda.zip"
+                            ]
+                        ]
+                    }
+                },
+                "Role": {
+                    "Fn::GetAtt": [
+                        "VerifyTACheckStatusLambdaExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Environment": {
+                    "Variables": {
+                        "IAMRoleName": {
+                            "Ref": "CrossAccountRoleName"
+                        },
+                        "LOG_LEVEL": {
+                            "Ref": "LogLevel"
+                        },
+                        "MASK_PII": {
+                            "Ref": "MaskAccountInformation"
+                        }
+                    }
+                },
+                "Timeout": 60,
+                "Handler": "verify-ta-check-status-lambda.lambda_handler",
+                "Runtime": "python3.8",
+                "MemorySize": 128
+            }
+        },
+        "VerifyTACheckStatusLambdaExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "lambda.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "VerifyTACheckStatusLambdaExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "DependsOn": [
+                "VerifyTACheckStatusLambda"
+            ],
+            "Properties": {
+                "PolicyName": "VerifyTACheckStatusLambdaExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "VerifyTACheckStatusLambdaExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": "logs:CreateLogGroup",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:logs:",
+                                        {
+                                            "Ref": "AWS::Region"
+                                        },
+                                        ":",
+                                        {
+                                            "Ref": "AWS::AccountId"
+                                        },
+                                        ":*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "logs:CreateLogStream",
+                                "logs:PutLogEvents"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "VerifyTACheckStatusLambda"
+                                            },
+                                            ":*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "VerifyTACheckStatusLambda"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": "sts:AssumeRole",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:iam::*:role/",
+                                        {
+                                            "Ref": "CrossAccountRoleName"
+                                        }
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:GetObject",
+                                "s3:GetObjectTagging",
+                                "s3:ListBucket",
+                                "s3:GetObjectAcl"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "RefreshTACheckLambda": {
+            "Type": "AWS::Lambda::Function",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W58",
+                            "reason": "This lambda has permissions to write to CW Logs."
+                        }
+                    ]
+                }
+            },
+            "Properties": {
+                "Code": {
+                    "S3Bucket": {
+                        "Fn::Join": [
+                            "-",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "S3Bucket"
+                                    ]
+                                },
+                                {
+                                    "Ref": "AWS::Region"
+                                }
+                            ]
+                        ]
+                    },
+                    "S3Key": {
+                        "Fn::Join": [
+                            "/",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "KeyPrefix"
+                                    ]
+                                },
+                                "refresh-ta-check-lambda.zip"
+                            ]
+                        ]
+                    }
+                },
+                "Role": {
+                    "Fn::GetAtt": [
+                        "RefTACheckLambdaExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Environment": {
+                    "Variables": {
+                        "IAMRoleName": {
+                            "Ref": "CrossAccountRoleName"
+                        },
+                        "LOG_LEVEL": {
+                            "Ref": "LogLevel"
+                        },
+                        "S3BucketName": {
+                            "Ref": "S3Bucket"
+                        },
+                        "MASK_PII": {
+                            "Ref": "MaskAccountInformation"
+                        }
+                    }
+                },
+                "Timeout": 60,
+                "Handler": "refresh-ta-check-lambda.lambda_handler",
+                "Runtime": "python3.8",
+                "MemorySize": 128
+            }
+        },
+        "RefTACheckLambdaExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "lambda.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "RefTACheckLambdaExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "DependsOn": [
+                "RefreshTACheckLambda"
+            ],
+            "Properties": {
+                "PolicyName": "RefTACheckLambdaExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "RefTACheckLambdaExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": "logs:CreateLogGroup",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:logs:",
+                                        {
+                                            "Ref": "AWS::Region"
+                                        },
+                                        ":",
+                                        {
+                                            "Ref": "AWS::AccountId"
+                                        },
+                                        ":*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "logs:CreateLogStream",
+                                "logs:PutLogEvents"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "RefreshTACheckLambda"
+                                            },
+                                            ":*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "RefreshTACheckLambda"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": "sts:AssumeRole",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:iam::*:role/",
+                                        {
+                                            "Ref": "CrossAccountRoleName"
+                                        }
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:PutObject",
+                                "s3:PutObjectAcl"
+                            ],
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:s3:::",
+                                        {
+                                            "Ref": "S3Bucket"
+                                        },
+                                        "/*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:GetObject",
+                                "s3:GetObjectTagging",
+                                "s3:ListBucket",
+                                "s3:GetObjectAcl"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "MapTACheckStepFunction": {
+            "Type": "AWS::StepFunctions::StateMachine",
+            "DependsOn": [
+                "RefreshTACheckLambda",
+                "VerifyTACheckStatusLambda",
+                "ExtractTAData"
+            ],
+            "Properties": {
+                "StateMachineName": "MapTACheck",
+                "DefinitionString": {
+                    "Fn::Join": [
+                        "\n",
+                        [
+                            "{",
+                            "    \"StartAt\": \"MapTACheck\",",
+                            "    \"States\": {",
+                            "        \"MapTACheck\": {",
+                            "            \"Type\": \"Map\",",
+                            "            \"Iterator\": {",
+                            "                \"StartAt\": \"TARefresh\",",
+                            "                \"States\": {",
+                            "                    \"TARefresh\": {",
+                            "                        \"Type\": \"Task\",",
+                            {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "             \"Resource\": \"",
+                                        {
+                                            "Fn::GetAtt": [
+                                                "RefreshTACheckLambda",
+                                                "Arn"
+                                            ]
+                                        },
+                                        "\","
+                                    ]
+                                ]
+                            },
+                            "                        \"Next\": \"VerifyTACheckStatus\",",
+                            "                        \"Retry\": [",
+                            "                          {",
+                            "                           \"ErrorEquals\": [ \"Lambda.TooManyRequestsException\"],",
+                            "                           \"IntervalSeconds\": 2,",
+                            "                           \"MaxAttempts\": 6,",
+                            "                           \"BackoffRate\": 2",
+                            "                           },",
+                            "                          {",
+                            "                            \"ErrorEquals\": [\"States.ALL\"],",
+                            "                            \"IntervalSeconds\": 5,",
+                            "                            \"MaxAttempts\": 2,",
+                            "                            \"BackoffRate\": 2",
+                            "                          }",
+                            "                        ]",
+                            "                    },",
+                            "                    \"wait_X_seconds\": {",
+                            "                        \"Type\": \"Wait\",",
+                            "                        \"SecondsPath\": \"$.WaitTimeInSec\",",
+                            "                        \"Next\": \"VerifyTACheckStatus\"",
+                            "                    },",
+                            "                    \"VerifyTACheckStatus\": {",
+                            "                        \"Type\": \"Task\",",
+                            {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "             \"Resource\": \"",
+                                        {
+                                            "Fn::GetAtt": [
+                                                "VerifyTACheckStatusLambda",
+                                                "Arn"
+                                            ]
+                                        },
+                                        "\","
+                                    ]
+                                ]
+                            },
+                            "                        \"Next\": \"Refresh Complete?\",",
+                            "                        \"Retry\": [",
+                            "                            {",
+                            "                                \"ErrorEquals\": [",
+                            "                                    \"Lambda.TooManyRequestsException\"",
+                            "                                ],",
+                            "                                \"IntervalSeconds\": 2,",
+                            "                                \"MaxAttempts\": 6,",
+                            "                                \"BackoffRate\": 2",
+                            "                            },",
+                            "                          {",
+                            "                            \"ErrorEquals\": [\"States.ALL\"],",
+                            "                            \"IntervalSeconds\": 2,",
+                            "                            \"MaxAttempts\": 2,",
+                            "                            \"BackoffRate\": 2",
+                            "                          }",
+                            "                        ]",
+                            "                    },",
+                            "                    \"Refresh Complete?\": {",
+                            "                        \"Type\": \"Choice\",",
+                            "                        \"Choices\": [",
+                            "                            {",
+                            "                                \"Variable\": \"$.RefreshStatus\",",
+                            "                                \"StringEquals\": \"enqueued\",",
+                            "                                \"Next\": \"wait_X_seconds\"",
+                            "                            },",
+                            "                            {",
+                            "                                \"Variable\": \"$.RefreshStatus\",",
+                            "                                \"StringEquals\": \"processing\",",
+                            "                                \"Next\": \"wait_X_seconds\"",
+                            "                            }",
+                            "                        ],",
+                            "                        \"Default\": \"TACheck\"",
+                            "                    },",
+                            "                    \"TACheck\": {",
+                            "                        \"Type\": \"Task\",",
+                            {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "             \"Resource\": \"",
+                                        {
+                                            "Fn::GetAtt": [
+                                                "ExtractTAData",
+                                                "Arn"
+                                            ]
+                                        },
+                                        "\","
+                                    ]
+                                ]
+                            },
+                            "                        \"End\": true,",
+                            "                        \"Retry\": [",
+                            "                            {",
+                            "                                \"ErrorEquals\": [",
+                            "                                    \"Lambda.TooManyRequestsException\"",
+                            "                                ],",
+                            "                                \"IntervalSeconds\": 2,",
+                            "                                \"MaxAttempts\": 6,",
+                            "                                \"BackoffRate\": 2",
+                            "                            },",
+                            "                            {",
+                            "                            \"ErrorEquals\": [\"States.ALL\"],",
+                            "                            \"IntervalSeconds\": 2,",
+                            "                            \"MaxAttempts\": 2,",
+                            "                            \"BackoffRate\": 2",
+                            "                            }",
+                            "                        ]",
+                            "                    }",
+                            "                }",
+                            "            },",
+                            "            \"End\": true",
+                            "        }",
+                            "    }",
+                            "}"
+                        ]
+                    ]
+                },
+                "RoleArn": {
+                    "Fn::GetAtt": [
+                        "MapTACheckExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Tags": [
+                    {
+                        "Key": "ProjectName",
+                        "Value": "AWS Trusted Advisor Explorer"
+                    }
+                ]
+            }
+        },
+        "MapTACheckExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "states.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "MapTACheckExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "Properties": {
+                "PolicyName": "StepMapTACheckExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "MapTACheckExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "lambda:InvokeFunction"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::GetAtt": [
+                                        "ExtractTAData",
+                                        "Arn"
+                                    ]
+                                },
+                                {
+                                    "Fn::GetAtt": [
+                                        "VerifyTACheckStatusLambda",
+                                        "Arn"
+                                    ]
+                                },
+                                {
+                                    "Fn::GetAtt": [
+                                        "RefreshTACheckLambda",
+                                        "Arn"
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "GetTAChecks": {
+            "Type": "AWS::Lambda::Function",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W58",
+                            "reason": "This lambda has permissions to write to CW Logs."
+                        }
+                    ]
+                }
+            },
+            "DependsOn": [
+                "MapTACheckStepFunction"
+            ],
+            "Properties": {
+                "Code": {
+                    "S3Bucket": {
+                        "Fn::Join": [
+                            "-",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "S3Bucket"
+                                    ]
+                                },
+                                {
+                                    "Ref": "AWS::Region"
+                                }
+                            ]
+                        ]
+                    },
+                    "S3Key": {
+                        "Fn::Join": [
+                            "/",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "KeyPrefix"
+                                    ]
+                                },
+                                "get-ta-checks-lambda.zip"
+                            ]
+                        ]
+                    }
+                },
+                "Role": {
+                    "Fn::GetAtt": [
+                        "GetTAChecksExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Environment": {
+                    "Variables": {
+                        "LANGUAGE": {
+                            "Ref": "Language"
+                        },
+                        "EXTRACT_TA_DATA_PER_CHECK_SFN_ARN": {
+                            "Ref": "MapTACheckStepFunction"
+                        },
+                        "Category": "cost_optimizing",
+                        "MASK_PII": {
+                            "Ref": "MaskAccountInformation"
+                        },
+                        "LOG_LEVEL": {
+                            "Ref": "LogLevel"
+                        },
+                        "SupportedChecks" : "Qch7DwouX1,hjLMh88uM8,DAvU99Dc4C,Z4AUBRNSmz,Ti39halfu8,51fC20e7I2,G31sQ1E9U,1e93e4c0b5"
+                    }
+                },
+                "Timeout": 5,
+                "Handler": "get-ta-checks-lambda.lambda_handler",
+                "Runtime": "python3.8",
+                "MemorySize": 128
+            }
+        },
+        "GetTAChecksExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "lambda.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "GetTAChecksExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W12",
+                            "reason": "This lambda needs permissions to support:* as support API does not currently support specific actions."
+                        },
+                        {
+                            "id": "F4",
+                            "reason": "Support API only accepts * for resource."
+                        }
+                    ]
+                }
+            },
+            "DependsOn": [
+                "GetTAChecks"
+            ],
+            "Properties": {
+                "PolicyName": "GetTAChecksExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "GetTAChecksExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": "states:StartExecution",
+                            "Resource": {
+                                "Ref": "MapTACheckStepFunction"
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "support:*"
+                            ],
+                            "Resource": "*"
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": "logs:CreateLogGroup",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:logs:",
+                                        {
+                                            "Ref": "AWS::Region"
+                                        },
+                                        ":",
+                                        {
+                                            "Ref": "AWS::AccountId"
+                                        },
+                                        ":*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "logs:CreateLogStream",
+                                "logs:PutLogEvents"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "GetTAChecks"
+                                            },
+                                            ":*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "GetTAChecks"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:GetObject",
+                                "s3:GetObjectTagging",
+                                "s3:ListBucket",
+                                "s3:GetObjectAcl"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "MapOrganizationsStepFunction": {
+            "Type": "AWS::StepFunctions::StateMachine",
+            "Properties": {
+                "StateMachineName": "MapOrganizations",
+                "DefinitionString": {
+                    "Fn::Join": [
+                        "\n",
+                        [
+                            "{",
+                            "  \"StartAt\": \"MapOrganizations\",",
+                            "  \"States\": {",
+                            "    \"MapOrganizations\": {",
+                            "      \"Type\": \"Map\",",
+                            "      \"Iterator\": {",
+                            "         \"StartAt\": \"GetTAChecks\",",
+                            "         \"States\": {",
+                            "           \"GetTAChecks\": {",
+                            "             \"Type\": \"Task\",",
+                            {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "             \"Resource\": \"",
+                                        {
+                                            "Fn::GetAtt": [
+                                                "GetTAChecks",
+                                                "Arn"
+                                            ]
+                                        },
+                                        "\","
+                                    ]
+                                ]
+                            },
+                            "             \"End\": true,",
+                            "             \"Retry\": [",
+                            "              {",
+                            "                 \"ErrorEquals\": [ \"Lambda.TooManyRequestsException\"],",
+                            "                 \"IntervalSeconds\": 2,",
+                            "                 \"MaxAttempts\": 6,",
+                            "                 \"BackoffRate\": 2",
+                            "               },",
+                            "              {",
+                            "                \"ErrorEquals\": [\"States.ALL\"],",
+                            "                \"IntervalSeconds\": 5,",
+                            "                \"MaxAttempts\": 2,",
+                            "                \"BackoffRate\": 2",
+                            "              }",
+                            "            ]",
+                            "           }",
+                            "         }",
+                            "      },",
+                            "      \"End\": true",
+                            "    }",
+                            "}",
+                            "}"
+                        ]
+                    ]
+                },
+                "RoleArn": {
+                    "Fn::GetAtt": [
+                        "MapOrganizationsExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Tags": [
+                    {
+                        "Key": "ProjectName",
+                        "Value": "AWS Trusted Advisor Explorer"
+                    }
+                ]
+            }
+        },
+        "MapOrganizationsExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "states.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "MapOrganizationsExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "Properties": {
+                "PolicyName": "StepMapOrganizationsExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "MapOrganizationsExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "lambda:InvokeFunction"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::GetAtt": [
+                                        "GetTAChecks",
+                                        "Arn"
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "TagExtractorLambda": {
+            "Type": "AWS::Lambda::Function",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W58",
+                            "reason": "This lambda has permissions to write to CW Logs."
+                        }
+                    ]
+                }
+            },
+            "DependsOn": [
+                "TagExtractorLambdaExecutionRole"
+            ],
+            "Properties": {
+                "Code": {
+                    "S3Bucket": {
+                        "Fn::Join": [
+                            "-",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "S3Bucket"
+                                    ]
+                                },
+                                {
+                                    "Ref": "AWS::Region"
+                                }
+                            ]
+                        ]
+                    },
+                    "S3Key": {
+                        "Fn::Join": [
+                            "/",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "KeyPrefix"
+                                    ]
+                                },
+                                "extract-tag-data-lambda.zip"
+                            ]
+                        ]
+                    }
+                },
+                "Role": {
+                    "Fn::GetAtt": [
+                        "TagExtractorLambdaExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Environment": {
+                    "Variables": {
+                        "S3BucketName": {
+                            "Ref": "S3Bucket"
+                        },
+                        "IAMRoleName": {
+                            "Ref": "CrossAccountRoleName"
+                        },
+                        "LOG_LEVEL": {
+                            "Ref": "LogLevel"
+                        },
+                        "CustomerKeys": {
+                            "Ref": "InterestedTagKeys"
+                        },
+                        "MASK_PII": {
+                            "Ref": "MaskAccountInformation"
+                        }
+                    }
+                },
+                "Timeout": 300,
+                "Handler": "extract-tag-data-lambda.lambda_handler",
+                "Runtime": "python3.8",
+                "MemorySize": 256
+            }
+        },
+        "TagExtractorLambdaExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "lambda.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "TagExtractorLambdaExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "DependsOn": [
+                "TagExtractorLambda"
+            ],
+            "Properties": {
+                "PolicyName": "TagExtractorLambdaExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "TagExtractorLambdaExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": "logs:CreateLogGroup",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:logs:",
+                                        {
+                                            "Ref": "AWS::Region"
+                                        },
+                                        ":",
+                                        {
+                                            "Ref": "AWS::AccountId"
+                                        },
+                                        ":*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "logs:CreateLogStream",
+                                "logs:PutLogEvents"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "TagExtractorLambda"
+                                            },
+                                            ":*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "TagExtractorLambda"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:PutObject",
+                                "s3:PutObjectAcl"
+                            ],
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:s3:::",
+                                        {
+                                            "Ref": "S3Bucket"
+                                        },
+                                        "/*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": "sts:AssumeRole",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:iam::*:role/",
+                                        {
+                                            "Ref": "CrossAccountRoleName"
+                                        }
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:GetObject",
+                                "s3:GetObjectTagging",
+                                "s3:ListBucket",
+                                "s3:GetObjectAcl"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "TagExtractorStepFunction": {
+            "Type": "AWS::StepFunctions::StateMachine",
+            "DependsOn": [
+                "TagExtractorLambda"
+            ],
+            "Properties": {
+                "StateMachineName": "ExtractTags",
+                "DefinitionString": {
+                    "Fn::Join": [
+                        "\n",
+                        [
+                            "{",
+                            "    \"StartAt\": \"MapTags\",",
+                            "    \"States\": {",
+                            "        \"MapTags\": {",
+                            "            \"Type\": \"Map\",",
+                            "            \"Iterator\": {",
+                            "                \"StartAt\": \"ExtractTags\",",
+                            "                \"States\": {",
+                            "                    \"ExtractTags\": {",
+                            "                        \"Type\": \"Task\",",
+                            {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "             \"Resource\": \"",
+                                        {
+                                            "Fn::GetAtt": [
+                                                "TagExtractorLambda",
+                                                "Arn"
+                                            ]
+                                        },
+                                        "\","
+                                    ]
+                                ]
+                            },
+                            "                        \"End\": true,",
+                            "                        \"Retry\": [",
+                            "                            {",
+                            "                                \"ErrorEquals\": [",
+                            "                                    \"Lambda.TooManyRequestsException\"",
+                            "                                ],",
+                            "                                \"IntervalSeconds\": 2,",
+                            "                                \"MaxAttempts\": 6,",
+                            "                                \"BackoffRate\": 2",
+                            "                            },",
+                            "                          {",
+                            "                            \"ErrorEquals\": [\"States.ALL\"],",
+                            "                            \"IntervalSeconds\": 2,",
+                            "                            \"MaxAttempts\": 2,",
+                            "                            \"BackoffRate\": 2",
+                            "                          }",
+                            "                        ]",
+                            "                    }",
+                            "                }",
+                            "            },",
+                            "            \"End\": true",
+                            "        }",
+                            "    }",
+                            "}"
+                        ]
+                    ]
+                },
+                "RoleArn": {
+                    "Fn::GetAtt": [
+                        "ExtractTagsStepExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Tags": [
+                    {
+                        "Key": "ProjectName",
+                        "Value": "AWS Trusted Advisor Explorer"
+                    }
+                ]
+            }
+        },
+        "ExtractTagsStepExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "states.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "ExtractTagsStepExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "Properties": {
+                "PolicyName": "StepExtractTagsExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "ExtractTagsStepExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "lambda:InvokeFunction"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::GetAtt": [
+                                        "TagExtractorLambda",
+                                        "Arn"
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "GetTags": {
+            "Type": "AWS::Lambda::Function",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W58",
+                            "reason": "This lambda has permissions to write to CW Logs."
+                        }
+                    ]
+                }
+            },
+            "DependsOn": [
+                "TagExtractorStepFunction"
+            ],
+            "Properties": {
+                "Code": {
+                    "S3Bucket": {
+                        "Fn::Join": [
+                            "-",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "S3Bucket"
+                                    ]
+                                },
+                                {
+                                    "Ref": "AWS::Region"
+                                }
+                            ]
+                        ]
+                    },
+                    "S3Key": {
+                        "Fn::Join": [
+                            "/",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "KeyPrefix"
+                                    ]
+                                },
+                                "get-tags-lambda.zip"
+                            ]
+                        ]
+                    }
+                },
+                "Role": {
+                    "Fn::GetAtt": [
+                        "GetTagsExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Environment": {
+                    "Variables": {
+                        "LOG_LEVEL": {
+                            "Ref": "LogLevel"
+                        },
+                        "TAG_DATA_EXTRACT_SFN_ARN": {
+                            "Ref": "TagExtractorStepFunction"
+                        },
+                        "ResourceTypes": "rds:db,ec2:instance,ec2:volume,elasticloadbalancing:loadbalancer,route53:hostedzone,redshift:dbname",
+                        "MASK_PII": {
+                            "Ref": "MaskAccountInformation"
+                        }
+                    }
+                },
+                "Timeout": 15,
+                "Handler": "get-tags-lambda.lambda_handler",
+                "Runtime": "python3.8",
+                "MemorySize": 128
+            }
+        },
+        "GetTagsExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "lambda.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "GetTagsExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W12",
+                            "reason": "* is the only accepted resource for DescribeRegions API call."
+                        }
+                    ]
+                }
+            },
+            "DependsOn": [
+                "GetTags"
+            ],
+            "Properties": {
+                "PolicyName": "GetTagsExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "GetTagsExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": "states:StartExecution",
+                            "Resource": {
+                                "Ref": "TagExtractorStepFunction"
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": "logs:CreateLogGroup",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:logs:",
+                                        {
+                                            "Ref": "AWS::Region"
+                                        },
+                                        ":",
+                                        {
+                                            "Ref": "AWS::AccountId"
+                                        },
+                                        ":*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "logs:CreateLogStream",
+                                "logs:PutLogEvents"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "GetTags"
+                                            },
+                                            ":*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "GetTags"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Action": [
+                                "ec2:DescribeRegions"
+                            ],
+                            "Effect": "Allow",
+                            "Resource": "*"
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:GetObject",
+                                "s3:GetObjectTagging",
+                                "s3:ListBucket",
+                                "s3:GetObjectAcl"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "TagMapOrganizationsStepFunction": {
+            "Type": "AWS::StepFunctions::StateMachine",
+            "Properties": {
+                "StateMachineName": "TagMapOrganizations",
+                "DefinitionString": {
+                    "Fn::Join": [
+                        "\n",
+                        [
+                            "{",
+                            "  \"StartAt\": \"MapOrganizations\",",
+                            "  \"States\": {",
+                            "    \"MapOrganizations\": {",
+                            "      \"Type\": \"Map\",",
+                            "      \"Iterator\": {",
+                            "         \"StartAt\": \"GetTags\",",
+                            "         \"States\": {",
+                            "           \"GetTags\": {",
+                            "             \"Type\": \"Task\",",
+                            {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "             \"Resource\": \"",
+                                        {
+                                            "Fn::GetAtt": [
+                                                "GetTags",
+                                                "Arn"
+                                            ]
+                                        },
+                                        "\","
+                                    ]
+                                ]
+                            },
+                            "             \"End\": true,",
+                            "                        \"Retry\": [",
+                            "                            {",
+                            "                                \"ErrorEquals\": [",
+                            "                                    \"Lambda.TooManyRequestsException\"",
+                            "                                ],",
+                            "                                \"IntervalSeconds\": 2,",
+                            "                                \"MaxAttempts\": 6,",
+                            "                                \"BackoffRate\": 2",
+                            "                            },",
+                            "                          {",
+                            "                            \"ErrorEquals\": [\"States.ALL\"],",
+                            "                            \"IntervalSeconds\": 2,",
+                            "                            \"MaxAttempts\": 2,",
+                            "                            \"BackoffRate\": 2",
+                            "                          }",
+                            "                        ]",
+                            "           }",
+                            "         }",
+                            "      },",
+                            "      \"End\": true",
+                            "    }  ",
+                            "}",
+                            "}"
+                        ]
+                    ]
+                },
+                "RoleArn": {
+                    "Fn::GetAtt": [
+                        "TagMapOrganizationsExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Tags": [
+                    {
+                        "Key": "ProjectName",
+                        "Value": "AWS Trusted Advisor Explorer"
+                    }
+                ]
+            }
+        },
+        "TagMapOrganizationsExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "states.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "TagMapOrganizationsExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "Properties": {
+                "PolicyName": "StepTagMapOrganizationsExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "TagMapOrganizationsExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "lambda:InvokeFunction"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::GetAtt": [
+                                        "GetTags",
+                                        "Arn"
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "GetAccountsLambda": {
+            "Type": "AWS::Lambda::Function",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W58",
+                            "reason": "This lambda has permissions to write to CW Logs."
+                        }
+                    ]
+                }
+            },
+            "Properties": {
+                "Code": {
+                    "S3Bucket": {
+                        "Fn::Join": [
+                            "-",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "S3Bucket"
+                                    ]
+                                },
+                                {
+                                    "Ref": "AWS::Region"
+                                }
+                            ]
+                        ]
+                    },
+                    "S3Key": {
+                        "Fn::Join": [
+                            "/",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "KeyPrefix"
+                                    ]
+                                },
+                                "get-accounts-info-lambda.zip"
+                            ]
+                        ]
+                    }
+                },
+                "Role": {
+                    "Fn::GetAtt": [
+                        "GetAccountsLambdaExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Environment": {
+                    "Variables": {
+                        "BUCKET_NAME": {
+                            "Ref": "S3Bucket"
+                        },
+                        "FILE_OVERRIDE": "false",
+                        "LOG_LEVEL": {
+                            "Ref": "LogLevel"
+                        },
+                        "OBJECT_NAME": "folder/accounts.csv",
+                        "EXTRACT_TA_DATA_SFN_ARN": {
+                            "Ref": "MapOrganizationsStepFunction"
+                        },
+                        "TAG_DATA_EXTRACT_SFN_ARN": {
+                            "Ref": "TagMapOrganizationsStepFunction"
+                        },
+                        "Tags": {
+                            "Ref": "InterestedTagKeys"
+                        },
+                        "MASK_PII": {
+                            "Ref": "MaskAccountInformation"
+                        },
+                        "AnonymousUsage": {
+                            "Fn::FindInMap": [
+                                "Send",
+                                "AnonymousUsage",
+                                "Data"
+                            ]
+                        },
+                        "UUID": {
+                            "Fn::GetAtt": [
+                                "UUIDGenerator",
+                                "UUID"
+                            ]
+                        },
+                        "Version": {
+                            "Fn::FindInMap": [
+                                "SourceCode",
+                                "General",
+                                "Version"
+                            ]
+                        }
+                    }
+                },
+                "Timeout": 60,
+                "Handler": "get-accounts-info-lambda.lambda_handler",
+                "Runtime": "python3.8",
+                "MemorySize": 128
+            }
+        },
+        "GetAccountsLambdaExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "lambda.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "GetAccountsExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W12",
+                            "reason": "* is the only accepted resource for Organizations Describe & List APIs. Also, only one organization can exist per account hence, this should not be an issue."
+                        }
+                    ]
+                }
+            },
+            "DependsOn": [
+                "GetAccountsLambda",
+                "MapOrganizationsStepFunction"
+            ],
+            "Properties": {
+                "PolicyName": "awstrustedadvisorex-GetAccountsExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "GetAccountsLambdaExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": "states:StartExecution",
+                            "Resource": [
+                                {
+                                    "Ref": "MapOrganizationsStepFunction"
+                                },
+                                {
+                                    "Ref": "TagMapOrganizationsStepFunction"
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:Get*",
+                                "s3:List*"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Ref": "S3Bucket"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Ref": "S3Bucket"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "organizations:Describe*",
+                                "organizations:List*"
+                            ],
+                            "Resource": "*"
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": "logs:CreateLogGroup",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:logs:",
+                                        {
+                                            "Ref": "AWS::Region"
+                                        },
+                                        ":",
+                                        {
+                                            "Ref": "AWS::AccountId"
+                                        },
+                                        ":*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "logs:CreateLogStream",
+                                "logs:PutLogEvents"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "GetAccountsLambda"
+                                            },
+                                            ":*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "GetAccountsLambda"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:GetObject",
+                                "s3:GetObjectTagging",
+                                "s3:ListBucket",
+                                "s3:GetObjectAcl"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "CronRule": {
+            "Type": "AWS::Events::Rule",
+            "DependsOn": [
+                "GetAccountsLambda"
+            ],
+            "Properties": {
+                "Description": "TrustedAdvisorExplorer Event Rule",
+                "ScheduleExpression": {
+                    "Ref": "ReportSchedule"
+                },
+                "State": "ENABLED",
+                "Targets": [
+                    {
+                        "Arn": {
+                            "Fn::GetAtt": [
+                                "GetAccountsLambda",
+                                "Arn"
+                            ]
+                        },
+                        "Id": "AWSTrustedAdExReportScheduler"
+                    }
+                ]
+            }
+        },
+        "InvokeLambdaPermissionGetAccounts": {
+            "Type": "AWS::Lambda::Permission",
+            "Properties": {
+                "Action": "lambda:InvokeFunction",
+                "FunctionName": {
+                    "Fn::GetAtt": [
+                        "GetAccountsLambda",
+                        "Arn"
+                    ]
+                },
+                "Principal": "events.amazonaws.com",
+                "SourceArn": {
+                    "Fn::GetAtt": [
+                        "CronRule",
+                        "Arn"
+                    ]
+                }
+            }
+        },
+        "AWSTrustedAdvExDatabase": {
+            "Type": "AWS::Glue::Database",
+            "Properties": {
+                "CatalogId": {
+                    "Ref": "AWS::AccountId"
+                },
+                "DatabaseInput": {
+                    "Description": "AWS Trusted Advisor Explorer Database",
+                    "Name": "aws_trusted_advisor_explorer_db"
+                }
+            }
+        },
+        "AWSTrustedAdvExCrawlerRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "glue.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/",
+                "ManagedPolicyArns": [
+                    "arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole"
+                ]
+            }
+        },
+        "AWSTrustedAdvExCrawlerExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "Properties": {
+                "PolicyName": "AWSTrustedAdvEx-CrawlerExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "AWSTrustedAdvExCrawlerRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "logs:CreateLogGroup",
+                                "logs:CreateLogStream",
+                                "logs:PutLogEvents"
+                            ],
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:logs:",
+                                        {
+                                            "Ref": "AWS::Region"
+                                        },
+                                        ":",
+                                        {
+                                            "Ref": "AWS::AccountId"
+                                        },
+                                        ":*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "glue:UpdateDatabase",
+                                "glue:UpdatePartition",
+                                "glue:CreateTable",
+                                "glue:UpdateTable",
+                                "glue:ImportCatalogToGlue"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:glue:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":table/",
+                                            {
+                                                "Ref": "AWSTrustedAdvExDatabase"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:glue:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":userDefinedFunction/",
+                                            {
+                                                "Ref": "AWSTrustedAdvExDatabase"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:glue:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":database/",
+                                            {
+                                                "Ref": "AWSTrustedAdvExDatabase"
+                                            }
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:glue:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":catalog"
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:GetObject",
+                                "s3:PutObject"
+                            ],
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:s3:::",
+                                        {
+                                            "Ref": "S3Bucket"
+                                        },
+                                        "/*"
+                                    ]
+                                ]
+                            }
+                        }
+                    ]
+                }
+            }
+        },
+        "AWSTrustedAdvExCrawler": {
+            "Type": "AWS::Glue::Crawler",
+            "Properties": {
+                "DatabaseName": {
+                    "Ref": "AWSTrustedAdvExDatabase"
+                },
+                "Description": "This crawler that keeps your AWS Trusted Advisor Explorer table in Athena up-to-date",
+                "Name": "AWSTrustedAdvisorExplorer_Crawler",
+                "Role": {
+                    "Fn::GetAtt": [
+                        "AWSTrustedAdvExCrawlerRole",
+                        "Arn"
+                    ]
+                },
+                "Targets": {
+                    "S3Targets": [
+                        {
+                            "Path": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        {
+                                            "Ref": "S3Bucket"
+                                        },
+                                        "/TA-Reports/cost_optimizing"
+                                    ]
+                                ]
+                            }
+                        }
+                    ]
+                },
+                "SchemaChangePolicy": {
+                    "UpdateBehavior": "UPDATE_IN_DATABASE",
+                    "DeleteBehavior": "DELETE_FROM_DATABASE"
+                }
+            }
+        },
+        "AWSTrustedAdvExTagCrawler": {
+            "Type": "AWS::Glue::Crawler",
+            "Properties": {
+                "DatabaseName": {
+                    "Ref": "AWSTrustedAdvExDatabase"
+                },
+                "Description": "Recurring crawler that keeps your AWS Trusted Advisor Explorer table in Athena up-to-date",
+                "Name": "AWSTrustedAdvisorExplorer_Tags_Crawler",
+                "Role": {
+                    "Fn::GetAtt": [
+                        "AWSTrustedAdvExCrawlerRole",
+                        "Arn"
+                    ]
+                },
+                "Targets": {
+                    "S3Targets": [
+                        {
+                            "Path": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        {
+                                            "Ref": "S3Bucket"
+                                        },
+                                        "/Tags"
+                                    ]
+                                ]
+                            }
+                        }
+                    ]
+                },
+                "SchemaChangePolicy": {
+                    "UpdateBehavior": "UPDATE_IN_DATABASE",
+                    "DeleteBehavior": "DELETE_FROM_DATABASE"
+                },
+                "Schedule": {
+                    "ScheduleExpression": {
+                        "Ref": "GlueCrawlerSchedule"
+                    }
+                }
+            }
+        },
+        "MySNSTopic": {
+            "Type": "AWS::SNS::Topic",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W47",
+                            "reason": "We have included the steps to enable KMS key encryption in the solution documentation. Some the steps in the required functionility has dependency on other services/features that are not supported by CF."
+                        }
+                    ]
+                }
+            },
+            "Properties": {
+                "TopicName": "AWSTrustedAdvisorExplorer-DataRefresh",
+                "Subscription": [
+                    {
+                        "Endpoint": {
+                            "Ref": "SNSEmail"
+                        },
+                        "Protocol": "email"
+                    }
+                ]
+            }
+        },
+        "SNStopicpolicy": {
+            "Type": "AWS::SNS::TopicPolicy",
+            "Properties": {
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Sid": "AWSTrustedAdvisorExplorerSNSpolicy",
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": "events.amazonaws.com"
+                            },
+                            "Action": "sns:Publish",
+                            "Resource": {
+                                "Ref": "MySNSTopic"
+                            }
+                        }
+                    ]
+                },
+                "Topics": [
+                    {
+                        "Ref": "MySNSTopic"
+                    }
+                ]
+            }
+        },
+        "EventRule": {
+            "Type": "AWS::Events::Rule",
+            "Properties": {
+                "Description": "Event Rule for successful Glue Crawler State",
+                "EventPattern": {
+                    "source": [
+                        "aws.glue"
+                    ],
+                    "detail-type": [
+                        "Glue Crawler State Change"
+                    ],
+                    "detail": {
+                        "state": [
+                            "Succeeded"
+                        ],
+                        "crawlerName": [
+                            {
+                                "Ref": "AWSTrustedAdvExCrawler"
+                            }
+                        ]
+                    }
+                },
+                "State": "ENABLED",
+                "Targets": [
+                    {
+                        "Arn": {
+                            "Ref": "MySNSTopic"
+                        },
+                        "Id": "MySNSTopic",
+                        "InputTransformer": {
+                            "InputPathsMap": {
+                                "crawlerName": "$.detail.crawlerName",
+                                "completionDate": "$.detail.completionDate",
+                                "cloudWatchLogLink": "$.detail.cloudWatchLogLink",
+                                "warningMessage": "$.detail.warningMessage",
+                                "tablesCreated": "$.detail.tablesCreated",
+                                "tablesUpdated": "$.detail.tablesUpdated",
+                                "tablesDeleted": "$.detail.tablesDeleted",
+                                "partitionsCreated": "$.detail.partitionsCreated",
+                                "partitionsUpdated": "$.detail.partitionsUpdated"
+                            },
+                            "InputTemplate": "\"Glue Crawler <crawlerName> has successfully refreshed new Trusted Advisor Data\"\n\n\"Event Details:-\"\n\"completionDate: <completionDate>\"\n\"cloudWatchLogLink: <cloudWatchLogLink>\"\n\"warningMessage: <warningMessage>\"\n\"tablesCreated: <tablesCreated>\"\n\"tablesUpdated: <tablesUpdated>\"\n\"tablesDeleted: <tablesDeleted>\"\n\"partitionsCreated: <partitionsCreated>\"\n\"partitionsUpdated: <partitionsUpdated>\""
+                        }
+                    },
+                    {
+                        "Arn": {
+                            "Fn::GetAtt": [
+                                "CreateAthenaViewLambda",
+                                "Arn"
+                            ]
+                        },
+                        "Id": "CreateAthenaViewLambda"
+                    }
+                ]
+            }
+        },
+        "MyAthenaWorkGroup": {
+            "Type": "AWS::Athena::WorkGroup",
+            "Properties": {
+                "Name": "AWSTrustedAvisorExplorer-AthenaWorkGroup",
+                "RecursiveDeleteOption": true,
+                "Description": "This is a custom workgroup created as part of AWS Trusted Advisor Explorer Solution",
+                "State": "ENABLED",
+                "WorkGroupConfiguration": {
+                    "EnforceWorkGroupConfiguration": true,
+                    "PublishCloudWatchMetricsEnabled": true,
+                    "ResultConfiguration": {
+                        "EncryptionConfiguration": {
+                            "EncryptionOption": "SSE_S3"
+                        },
+                        "OutputLocation": {
+                            "Fn::Join": [
+                                "",
+                                [
+                                    "s3://",
+                                    {
+                                        "Ref": "S3Bucket"
+                                    },
+                                    "/AthenaOutputs/"
+                                ]
+                            ]
+                        }
+                    }
+                }
+            }
+        },
+        "CreateAthenaViewLambda": {
+            "Type": "AWS::Lambda::Function",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W58",
+                            "reason": "This lambda has permissions to write to CW Logs."
+                        }
+                    ]
+                }
+            },
+            "DependsOn": [
+                "CreateAthenaViewLambdaExecutionRole"
+            ],
+            "Properties": {
+                "Code": {
+                    "S3Bucket": {
+                        "Fn::Join": [
+                            "-",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "S3Bucket"
+                                    ]
+                                },
+                                {
+                                    "Ref": "AWS::Region"
+                                }
+                            ]
+                        ]
+                    },
+                    "S3Key": {
+                        "Fn::Join": [
+                            "/",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "KeyPrefix"
+                                    ]
+                                },
+                                "create-athena-views-lambda.zip"
+                            ]
+                        ]
+                    }
+                },
+                "Role": {
+                    "Fn::GetAtt": [
+                        "CreateAthenaViewLambdaExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Environment": {
+                    "Variables": {
+                        "AthenaOutput": {
+                            "Ref": "S3Bucket"
+                        },
+                        "LOG_LEVEL": {
+                            "Ref": "LogLevel"
+                        },
+                        "Tags": {
+                            "Ref": "InterestedTagKeys"
+                        },
+                        "AthenaDb": {
+                            "Ref": "AWSTrustedAdvExDatabase"
+                        },
+                        "MASK_PII": {
+                            "Ref": "MaskAccountInformation"
+                        },
+                        "AthenaWorkGroup": {
+                            "Ref": "MyAthenaWorkGroup"
+                        }
+                    }
+                },
+                "Timeout": 10,
+                "Handler": "create-athena-views-lambda.lambda_handler",
+                "Runtime": "python3.8",
+                "MemorySize": 128
+            }
+        },
+        "CreateAthenaViewLambdaExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "lambda.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "CreateAthenaViewLambdaExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "Properties": {
+                "PolicyName": "CreateAthenaViewLambdaExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "CreateAthenaViewLambdaExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": "logs:CreateLogGroup",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:logs:",
+                                        {
+                                            "Ref": "AWS::Region"
+                                        },
+                                        ":",
+                                        {
+                                            "Ref": "AWS::AccountId"
+                                        },
+                                        ":*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "logs:CreateLogStream",
+                                "logs:PutLogEvents"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "CreateAthenaViewLambda"
+                                            },
+                                            ":*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "CreateAthenaViewLambda"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": "athena:StartQueryExecution",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:athena:",
+                                        {
+                                            "Ref": "AWS::Region"
+                                        },
+                                        ":",
+                                        {
+                                            "Ref": "AWS::AccountId"
+                                        },
+                                        ":workgroup/",
+                                        {
+                                            "Ref": "MyAthenaWorkGroup"
+                                        }
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:GetObject",
+                                "s3:GetBucketLocation",
+                                "s3:ListBucketMultipartUploads",
+                                "s3:ListMultipartUploadParts",
+                                "s3:AbortMultipartUpload",
+                                "s3:GetObjectTagging",
+                                "s3:ListBucket",
+                                "s3:CreateBucket",
+                                "s3:GetObjectAcl",
+                                "s3:PutObject",
+                                "s3:PutObjectAcl"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Ref": "S3Bucket"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Ref": "S3Bucket"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "glue:CreateDatabase",
+                                "glue:DeleteDatabase",
+                                "glue:GetDatabase",
+                                "glue:GetDatabases",
+                                "glue:UpdateDatabase",
+                                "glue:CreateTable",
+                                "glue:DeleteTable",
+                                "glue:BatchDeleteTable",
+                                "glue:UpdateTable",
+                                "glue:GetTable",
+                                "glue:GetTables",
+                                "glue:BatchCreatePartition",
+                                "glue:CreatePartition",
+                                "glue:DeletePartition",
+                                "glue:BatchDeletePartition",
+                                "glue:UpdatePartition",
+                                "glue:GetPartition",
+                                "glue:GetPartitions",
+                                "glue:BatchGetPartition"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:glue:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":table/",
+                                            {
+                                                "Ref": "AWSTrustedAdvExDatabase"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:glue:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":userDefinedFunction/",
+                                            {
+                                                "Ref": "AWSTrustedAdvExDatabase"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:glue:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":database/",
+                                            {
+                                                "Ref": "AWSTrustedAdvExDatabase"
+                                            }
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:glue:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":catalog"
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:GetObject",
+                                "s3:GetObjectTagging",
+                                "s3:ListBucket",
+                                "s3:GetObjectAcl"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "PermissionForEventsToInvokeLambda": {
+            "Type": "AWS::Lambda::Permission",
+            "Properties": {
+                "FunctionName": {
+                    "Ref": "CreateAthenaViewLambda"
+                },
+                "Action": "lambda:InvokeFunction",
+                "Principal": "events.amazonaws.com",
+                "SourceArn": {
+                    "Fn::GetAtt": [
+                        "EventRule",
+                        "Arn"
+                    ]
+                }
+            }
+        },
+        "EventRuleTACrawler": {
+            "Type": "AWS::Events::Rule",
+            "Properties": {
+                "Description": "Event Rule to Trigger the TA Data Crawler",
+                "EventPattern": {
+                    "source": [
+                        "aws.glue"
+                    ],
+                    "detail-type": [
+                        "Glue Crawler State Change"
+                    ],
+                    "detail": {
+                        "state": [
+                            "Succeeded"
+                        ],
+                        "crawlerName": [
+                            {
+                                "Ref": "AWSTrustedAdvExTagCrawler"
+                            }
+                        ]
+                    }
+                },
+                "State": "ENABLED",
+                "Targets": [
+                    {
+                        "Arn": {
+                            "Fn::GetAtt": [
+                                "StartGlueCrawlerLambda",
+                                "Arn"
+                            ]
+                        },
+                        "Id": "TriggerGlueCrawler"
+                    }
+                ]
+            }
+        },
+        "StartGlueCrawlerLambda": {
+            "Type": "AWS::Lambda::Function",
+            "Metadata": {
+                "cfn_nag": {
+                    "rules_to_suppress": [
+                        {
+                            "id": "W58",
+                            "reason": "This lambda has permissions to write to CW Logs."
+                        }
+                    ]
+                }
+            },
+            "Properties": {
+                "Code": {
+                    "S3Bucket": {
+                        "Fn::Join": [
+                            "-",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "S3Bucket"
+                                    ]
+                                },
+                                {
+                                    "Ref": "AWS::Region"
+                                }
+                            ]
+                        ]
+                    },
+                    "S3Key": {
+                        "Fn::Join": [
+                            "/",
+                            [
+                                {
+                                    "Fn::FindInMap": [
+                                        "SourceCode",
+                                        "General",
+                                        "KeyPrefix"
+                                    ]
+                                },
+                                "start-crawler-lambda.zip"
+                            ]
+                        ]
+                    }
+                },
+                "Role": {
+                    "Fn::GetAtt": [
+                        "StartGlueCrawlerLambdaExecutionRole",
+                        "Arn"
+                    ]
+                },
+                "Environment": {
+                    "Variables": {
+                        "LOG_LEVEL": {
+                            "Ref": "LogLevel"
+                        },
+                        "MASK_PII": {
+                            "Ref": "MaskAccountInformation"
+                        },
+                        "CrawlerName": {
+                            "Ref": "AWSTrustedAdvExCrawler"
+                        }
+                    }
+                },
+                "Timeout": 60,
+                "Handler": "start-crawler-lambda.lambda_handler",
+                "Runtime": "python3.8",
+                "MemorySize": 128
+            }
+        },
+        "StartGlueCrawlerLambdaExecutionRole": {
+            "Type": "AWS::IAM::Role",
+            "Properties": {
+                "AssumeRolePolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Principal": {
+                                "Service": [
+                                    "lambda.amazonaws.com"
+                                ]
+                            },
+                            "Action": [
+                                "sts:AssumeRole"
+                            ]
+                        }
+                    ]
+                },
+                "Path": "/"
+            }
+        },
+        "StartGlueCrawlerLambdaExecutionPolicy": {
+            "Type": "AWS::IAM::Policy",
+            "DependsOn": [
+                "StartGlueCrawlerLambda"
+            ],
+            "Properties": {
+                "PolicyName": "AWSTrustedAdEx-StartGlueCrawlerLambdaExecutionPolicy",
+                "Roles": [
+                    {
+                        "Ref": "StartGlueCrawlerLambdaExecutionRole"
+                    }
+                ],
+                "PolicyDocument": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": "logs:CreateLogGroup",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:logs:",
+                                        {
+                                            "Ref": "AWS::Region"
+                                        },
+                                        ":",
+                                        {
+                                            "Ref": "AWS::AccountId"
+                                        },
+                                        ":*"
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "logs:CreateLogStream",
+                                "logs:PutLogEvents"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "StartGlueCrawlerLambda"
+                                            },
+                                            ":*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:logs:",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            ":",
+                                            {
+                                                "Ref": "AWS::AccountId"
+                                            },
+                                            ":log-group:/aws/lambda/",
+                                            {
+                                                "Ref": "StartGlueCrawlerLambda"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": "glue:StartCrawler",
+                            "Resource": {
+                                "Fn::Join": [
+                                    "",
+                                    [
+                                        "arn:aws:glue:",
+                                        {
+                                            "Ref": "AWS::Region"
+                                        },
+                                        ":",
+                                        {
+                                            "Ref": "AWS::AccountId"
+                                        },
+                                        ":crawler/",
+                                        {
+                                            "Ref": "AWSTrustedAdvExCrawler"
+                                        }
+                                    ]
+                                ]
+                            }
+                        },
+                        {
+                            "Effect": "Allow",
+                            "Action": [
+                                "s3:GetObject",
+                                "s3:GetObjectTagging",
+                                "s3:ListBucket",
+                                "s3:GetObjectAcl"
+                            ],
+                            "Resource": [
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            },
+                                            "/*"
+                                        ]
+                                    ]
+                                },
+                                {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:s3:::",
+                                            {
+                                                "Fn::FindInMap": [
+                                                    "SourceCode",
+                                                    "General",
+                                                    "S3Bucket"
+                                                ]
+                                            },
+                                            "-",
+                                            {
+                                                "Ref": "AWS::Region"
+                                            }
+                                        ]
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "PermissionForEventsToInvokeGlueLambda": {
+            "Type": "AWS::Lambda::Permission",
+            "Properties": {
+                "FunctionName": {
+                    "Ref": "StartGlueCrawlerLambda"
+                },
+                "Action": "lambda:InvokeFunction",
+                "Principal": "events.amazonaws.com",
+                "SourceArn": {
+                    "Fn::GetAtt": [
+                        "EventRuleTACrawler",
+                        "Arn"
+                    ]
+                }
+            }
+        }
+    },
+    "Outputs": {
+        "RawTADataBucketName": {
+            "Description": "The name of the bucket in which the raw Trusted Advisor check data & Tag information will be stored",
+            "Value": {
+                "Ref": "S3Bucket"
+            }
+        },
+        "SNSTopic": {
+            "Description": "The name of the SNS topic that will be notified after every data refresh.",
+            "Value": {
+                "Ref": "MySNSTopic"
+            }
+        },
+        "AthenaDatabase": {
+            "Description": "The name of the Athena database.",
+            "Value": {
+                "Ref": "AWSTrustedAdvExDatabase"
+            }
+        },
+        "UUID": {
+            "Description": "Newly created random UUID.",
+            "Value": {
+                "Fn::GetAtt": [
+                    "UUIDGenerator",
+                    "UUID"
+                ]
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/templates/cloudtrail.yaml.txt b/tests/data/templates/cloudtrail.yaml.txt
new file mode 100644
index 0000000..b58d1aa
--- /dev/null
+++ b/tests/data/templates/cloudtrail.yaml.txt
@@ -0,0 +1,131 @@
+AWSTemplateFormatVersion: '2010-09-09'
+
+Description: Our CloudTrail v 0.1.0
+
+Parameters:
+  CustomerName:
+    Type: String
+    Description: Follow S3 naming convetion per http://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-s3-bucket-naming-requirements.html
+    AllowedPattern: "^([a-z]|(\\d(?!\\d{0,2}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})))([a-z\\d]|(\\.(?!(\\.|-)))|(-(?!\\.))){1,61}[a-z\\d\\.]$"
+
+Resources:
+  S3Bucket:
+    Type: AWS::S3::Bucket
+    Properties:
+      BucketName:
+        !Sub |-
+          ${CustomerName}-logs
+    DeletionPolicy: Retain
+  BucketPolicy:
+    DependsOn:
+      - S3Bucket
+    Type: "AWS::S3::BucketPolicy"
+    Properties:
+      Bucket: !Ref S3Bucket
+      PolicyDocument:
+        Statement:
+          -
+            Action:
+              - "s3:GetBucketAcl"
+            Effect: "Allow"
+            Resource:
+              Fn::Join:
+                - ""
+                -
+                  - "arn:aws:s3:::"
+                  -
+                    Ref: "S3Bucket"
+            Principal:
+              Service: "cloudtrail.amazonaws.com"
+          -
+            Action:
+              - "s3:PutObject"
+            Effect: "Allow"
+            Resource:
+              Fn::Join:
+                - ""
+                -
+                  - "arn:aws:s3:::"
+                  -
+                    Ref: "S3Bucket"
+                  - "/AWSLogs/"
+                  -
+                    Ref: "AWS::AccountId"
+                  - "/*"
+            Principal:
+              Service: "cloudtrail.amazonaws.com"
+            Condition:
+              StringEquals:
+                s3:x-amz-acl: "bucket-owner-full-control"
+
+  SNSTopic:
+    Type: AWS::SNS::Topic
+    Properties:
+      TopicName:
+        !Sub |-
+          ${CustomerName}-alerts
+
+  CloudTrailLogsRole:
+      Type: "AWS::IAM::Role"
+      Properties:
+        RoleName:
+          !Sub |-
+            ${CustomerName}-cloudtrail
+        AssumeRolePolicyDocument:
+          Version: "2012-10-17"
+          Statement:
+            -
+              Effect: "Allow"
+              Principal:
+                Service:
+                  - "cloudtrail.amazonaws.com"
+              Action:
+                - "sts:AssumeRole"
+        Policies:
+          - PolicyName: CloudTrail
+            PolicyDocument:
+              Version: '2012-10-17'
+              Statement:
+              -
+                Sid: CloudTrailPublish
+                Effect: Allow
+                Action:
+                  - logs:CreateLogStream
+                  - logs:PutLogEvents
+                Resource:
+                  Fn::Join:
+                    - ""
+                    -
+                      - "arn:aws:logs:us-east-1:"
+                      -
+                        Ref: "AWS::AccountId"
+                      - ":log-group:/aws/cloudrail/logs:log-stream:"
+                      -
+                        Ref: "AWS::AccountId"
+                      - "_CloudTrail_us-east-1*"
+
+  CloudTrailLogsGroup:
+    Type: "AWS::Logs::LogGroup"
+    Properties:
+      LogGroupName: /aws/cloudrail/logs
+
+  CloudTrail:
+    DependsOn:
+      - S3Bucket
+      - BucketPolicy
+      - SNSTopic
+      - CloudTrailLogsRole
+      - CloudTrailLogsGroup
+    Type: "AWS::CloudTrail::Trail"
+    Properties:
+      S3BucketName: !Ref S3Bucket
+      CloudWatchLogsLogGroupArn: !GetAtt CloudTrailLogsGroup.Arn
+      CloudWatchLogsRoleArn: !GetAtt CloudTrailLogsRole.Arn
+      IncludeGlobalServiceEvents: true
+      IsLogging: true
+      IsMultiRegionTrail: true
+
+Outputs:
+  SNSArn:
+    Description: SNS Topic ARN
+    Value: !Ref SNSTopic
\ No newline at end of file
diff --git a/tests/data/templates/conversions-api-gateway.eks_multiaccount_dual_nat.yaml b/tests/data/templates/conversions-api-gateway.eks_multiaccount_dual_nat.yaml
new file mode 100644
index 0000000..159b8fc
--- /dev/null
+++ b/tests/data/templates/conversions-api-gateway.eks_multiaccount_dual_nat.yaml
@@ -0,0 +1,2496 @@
+Description: "Conversions API Gateway on EKS - Multiaccount - v1.11.0 | Deleting this stack removes all Conversions API Gateway resources automatically. "
+Metadata:
+  AWS::CloudFormation::Interface:
+    ParameterGroups:
+      - Label:
+          default: Conversions API Gateway Configuration
+        Parameters:
+          - ProvisioningData
+          - SystemHealthInformation
+          - CustomImageIds
+Parameters:
+  ProvisioningData:
+    Type: String
+    Default: MA-DO-NOT-EDIT
+    Description: DO NOT EDIT! Provisioning data for setting up your Conversions API Gateway. This field should already be populated.
+    MinLength: 1
+  SystemHealthInformation:
+    Type: String
+    AllowedValues:
+      - "Yes"
+      - "No"
+    Description: Would you like to periodically send data about the Conversions API Gateway installation to Meta? This data will be used to monitor the effect of each released version and provide support personnel with additional context when troubleshooting problems.
+Mappings:
+  CustomImageIds:
+    af-south-1:
+      ami: ami-0560a3fde1f4658a1
+    ap-northeast-1:
+      ami: ami-0befc222b5788c058
+    ap-northeast-2:
+      ami: ami-0526abfc603ef089a
+    ap-south-1:
+      ami: ami-0631298c98a13839d
+    ap-southeast-1:
+      ami: ami-0483822f0016e2328
+    ap-southeast-2:
+      ami: ami-094d12e9dce12fd56
+    ca-central-1:
+      ami: ami-0eee20d5f863a8bee
+    eu-central-1:
+      ami: ami-07169027ca2613f84
+    eu-north-1:
+      ami: ami-091cf1446d76780ce
+    eu-south-1:
+      ami: ami-0f2b8c580d21a0e6d
+    eu-west-1:
+      ami: ami-0ea4482f5f8fbf6af
+    eu-west-2:
+      ami: ami-07a1a9fbda0c5eabc
+    eu-west-3:
+      ami: ami-0c9a4db422b6e9265
+    me-south-1:
+      ami: ami-04a3c2f441fecce75
+    sa-east-1:
+      ami: ami-0694036ee5e31e45d
+    us-east-1:
+      ami: ami-0966519948fa03b76
+    us-east-2:
+      ami: ami-0a093bbdd2f2c6a6d
+    us-west-1:
+      ami: ami-0799f6a72ab124ccf
+    us-west-2:
+      ami: ami-06aa9f08bc742014c
+Resources:
+  CustomVPCB98CB5C5:
+    Type: AWS::EC2::VPC
+    Properties:
+      CidrBlock: 172.31.0.0/16
+      EnableDnsHostnames: true
+      EnableDnsSupport: true
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+        - Key: Name
+          Value:
+            Fn::Join:
+              - /
+              - - Ref: AWS::StackName
+                - VPC
+  CustomVPCInternetGateway5B787B77:
+    Type: AWS::EC2::InternetGateway
+    Properties:
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+  CustomVPCInternetGatewayAttachment4DE4D032:
+    Type: AWS::EC2::VPCGatewayAttachment
+    Properties:
+      InternetGatewayId:
+        Ref: CustomVPCInternetGateway5B787B77
+      VpcId:
+        Ref: CustomVPCB98CB5C5
+  CustomVPCIPv6CidrBlock2844DCD4:
+    Type: AWS::EC2::VPCCidrBlock
+    Properties:
+      AmazonProvidedIpv6CidrBlock: true
+      VpcId:
+        Ref: CustomVPCB98CB5C5
+  CustomVPCEgressOnlyInternetGateway635B9DCC:
+    Type: AWS::EC2::EgressOnlyInternetGateway
+    Properties:
+      VpcId:
+        Ref: CustomVPCB98CB5C5
+  CustomVPCPublic039347A47:
+    Type: AWS::EC2::Subnet
+    Properties:
+      AssignIpv6AddressOnCreation: true
+      AvailabilityZone:
+        Fn::Select:
+          - 0
+          - Fn::GetAZs:
+              Ref: AWS::Region
+      CidrBlock:
+        Fn::Select:
+          - 0
+          - Fn::Cidr:
+              - 172.31.0.0/16
+              - 4
+              - "12"
+      Ipv6CidrBlock:
+        Fn::Select:
+          - 0
+          - Fn::Cidr:
+              - Fn::Select:
+                  - 0
+                  - Fn::GetAtt:
+                      - CustomVPCB98CB5C5
+                      - Ipv6CidrBlocks
+              - 4
+              - "64"
+      MapPublicIpOnLaunch: true
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: kubernetes.io/role/elb
+          Value: "1"
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+        - Key: Name
+          Value:
+            Fn::Join:
+              - /
+              - - Ref: AWS::StackName
+                - VPC/AZ0/Public
+      VpcId:
+        Ref: CustomVPCB98CB5C5
+    DependsOn:
+      - CustomVPCIPv6CidrBlock2844DCD4
+  CustomVPCPublic0RouteTableC4A2F788:
+    Type: AWS::EC2::RouteTable
+    Properties:
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: kubernetes.io/role/elb
+          Value: "1"
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+        - Key: Name
+          Value:
+            Fn::Join:
+              - /
+              - - Ref: AWS::StackName
+                - VPC/AZ0/Public/RouteTable
+      VpcId:
+        Ref: CustomVPCB98CB5C5
+  CustomVPCPublic0RouteTableAssociationCD395678:
+    Type: AWS::EC2::SubnetRouteTableAssociation
+    Properties:
+      RouteTableId:
+        Ref: CustomVPCPublic0RouteTableC4A2F788
+      SubnetId:
+        Ref: CustomVPCPublic039347A47
+  CustomVPCPublic0DefaultRoute34086041:
+    Type: AWS::EC2::Route
+    Properties:
+      DestinationCidrBlock: 0.0.0.0/0
+      GatewayId:
+        Ref: CustomVPCInternetGateway5B787B77
+      RouteTableId:
+        Ref: CustomVPCPublic0RouteTableC4A2F788
+    DependsOn:
+      - CustomVPCInternetGatewayAttachment4DE4D032
+  CustomVPCPublic0DefaultIPv6Route78451BE4:
+    Type: AWS::EC2::Route
+    Properties:
+      DestinationIpv6CidrBlock: ::/0
+      GatewayId:
+        Ref: CustomVPCInternetGateway5B787B77
+      RouteTableId:
+        Ref: CustomVPCPublic0RouteTableC4A2F788
+    DependsOn:
+      - CustomVPCInternetGatewayAttachment4DE4D032
+  CustomVPCPublic0EIP7301E2D0:
+    Type: AWS::EC2::EIP
+    Properties:
+      Domain: vpc
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: kubernetes.io/role/elb
+          Value: "1"
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+        - Key: Name
+          Value:
+            Fn::Join:
+              - /
+              - - Ref: AWS::StackName
+                - VPC/AZ0/Public/EIP
+  CustomVPCPublic0NatGateway771F3234:
+    Type: AWS::EC2::NatGateway
+    Properties:
+      AllocationId:
+        Fn::GetAtt:
+          - CustomVPCPublic0EIP7301E2D0
+          - AllocationId
+      SubnetId:
+        Ref: CustomVPCPublic039347A47
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: kubernetes.io/role/elb
+          Value: "1"
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+        - Key: Name
+          Value:
+            Fn::Join:
+              - /
+              - - Ref: AWS::StackName
+                - VPC/AZ0/Public/NatGateway
+  CustomVPCPrivate0D2251CEA:
+    Type: AWS::EC2::Subnet
+    Properties:
+      AssignIpv6AddressOnCreation: true
+      AvailabilityZone:
+        Fn::Select:
+          - 0
+          - Fn::GetAZs:
+              Ref: AWS::Region
+      CidrBlock:
+        Fn::Select:
+          - 1
+          - Fn::Cidr:
+              - 172.31.0.0/16
+              - 4
+              - "12"
+      Ipv6CidrBlock:
+        Fn::Select:
+          - 1
+          - Fn::Cidr:
+              - Fn::Select:
+                  - 0
+                  - Fn::GetAtt:
+                      - CustomVPCB98CB5C5
+                      - Ipv6CidrBlocks
+              - 4
+              - "64"
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: kubernetes.io/role/internal-elb
+          Value: "1"
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+        - Key: Name
+          Value:
+            Fn::Join:
+              - /
+              - - Ref: AWS::StackName
+                - VPC/AZ0/Private
+      VpcId:
+        Ref: CustomVPCB98CB5C5
+    DependsOn:
+      - CustomVPCIPv6CidrBlock2844DCD4
+  CustomVPCPrivate0RouteTable409F685C:
+    Type: AWS::EC2::RouteTable
+    Properties:
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: kubernetes.io/role/internal-elb
+          Value: "1"
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+        - Key: Name
+          Value:
+            Fn::Join:
+              - /
+              - - Ref: AWS::StackName
+                - VPC/AZ0/Private/RouteTable
+      VpcId:
+        Ref: CustomVPCB98CB5C5
+  CustomVPCPrivate0RouteTableAssociation1429B372:
+    Type: AWS::EC2::SubnetRouteTableAssociation
+    Properties:
+      RouteTableId:
+        Ref: CustomVPCPrivate0RouteTable409F685C
+      SubnetId:
+        Ref: CustomVPCPrivate0D2251CEA
+  CustomVPCPrivate0DefaultNatRoute647FD84E:
+    Type: AWS::EC2::Route
+    Properties:
+      DestinationCidrBlock: 0.0.0.0/0
+      NatGatewayId:
+        Ref: CustomVPCPublic0NatGateway771F3234
+      RouteTableId:
+        Ref: CustomVPCPrivate0RouteTable409F685C
+  CustomVPCPrivate0DefaultEgressOnlyRoute0CBB8974:
+    Type: AWS::EC2::Route
+    Properties:
+      DestinationIpv6CidrBlock: ::/0
+      EgressOnlyInternetGatewayId:
+        Ref: CustomVPCEgressOnlyInternetGateway635B9DCC
+      RouteTableId:
+        Ref: CustomVPCPrivate0RouteTable409F685C
+  CustomVPCPublic1E2BA9DAA:
+    Type: AWS::EC2::Subnet
+    Properties:
+      AssignIpv6AddressOnCreation: true
+      AvailabilityZone:
+        Fn::Select:
+          - 1
+          - Fn::GetAZs:
+              Ref: AWS::Region
+      CidrBlock:
+        Fn::Select:
+          - 2
+          - Fn::Cidr:
+              - 172.31.0.0/16
+              - 4
+              - "12"
+      Ipv6CidrBlock:
+        Fn::Select:
+          - 2
+          - Fn::Cidr:
+              - Fn::Select:
+                  - 0
+                  - Fn::GetAtt:
+                      - CustomVPCB98CB5C5
+                      - Ipv6CidrBlocks
+              - 4
+              - "64"
+      MapPublicIpOnLaunch: true
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: kubernetes.io/role/elb
+          Value: "1"
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+        - Key: Name
+          Value:
+            Fn::Join:
+              - /
+              - - Ref: AWS::StackName
+                - VPC/AZ1/Public
+      VpcId:
+        Ref: CustomVPCB98CB5C5
+    DependsOn:
+      - CustomVPCIPv6CidrBlock2844DCD4
+  CustomVPCPublic1RouteTable36124A34:
+    Type: AWS::EC2::RouteTable
+    Properties:
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: kubernetes.io/role/elb
+          Value: "1"
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+        - Key: Name
+          Value:
+            Fn::Join:
+              - /
+              - - Ref: AWS::StackName
+                - VPC/AZ1/Public/RouteTable
+      VpcId:
+        Ref: CustomVPCB98CB5C5
+  CustomVPCPublic1RouteTableAssociation4B85F0F6:
+    Type: AWS::EC2::SubnetRouteTableAssociation
+    Properties:
+      RouteTableId:
+        Ref: CustomVPCPublic1RouteTable36124A34
+      SubnetId:
+        Ref: CustomVPCPublic1E2BA9DAA
+  CustomVPCPublic1DefaultRoute4B970118:
+    Type: AWS::EC2::Route
+    Properties:
+      DestinationCidrBlock: 0.0.0.0/0
+      GatewayId:
+        Ref: CustomVPCInternetGateway5B787B77
+      RouteTableId:
+        Ref: CustomVPCPublic1RouteTable36124A34
+    DependsOn:
+      - CustomVPCInternetGatewayAttachment4DE4D032
+  CustomVPCPublic1DefaultIPv6RouteA602F963:
+    Type: AWS::EC2::Route
+    Properties:
+      DestinationIpv6CidrBlock: ::/0
+      GatewayId:
+        Ref: CustomVPCInternetGateway5B787B77
+      RouteTableId:
+        Ref: CustomVPCPublic1RouteTable36124A34
+    DependsOn:
+      - CustomVPCInternetGatewayAttachment4DE4D032
+  CustomVPCPublic1EIP7EE4A9AE:
+    Type: AWS::EC2::EIP
+    Properties:
+      Domain: vpc
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: kubernetes.io/role/elb
+          Value: "1"
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+        - Key: Name
+          Value:
+            Fn::Join:
+              - /
+              - - Ref: AWS::StackName
+                - VPC/AZ1/Public/EIP
+  CustomVPCPublic1NatGateway0E4E03E7:
+    Type: AWS::EC2::NatGateway
+    Properties:
+      AllocationId:
+        Fn::GetAtt:
+          - CustomVPCPublic1EIP7EE4A9AE
+          - AllocationId
+      SubnetId:
+        Ref: CustomVPCPublic1E2BA9DAA
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: kubernetes.io/role/elb
+          Value: "1"
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+        - Key: Name
+          Value:
+            Fn::Join:
+              - /
+              - - Ref: AWS::StackName
+                - VPC/AZ1/Public/NatGateway
+  CustomVPCPrivate1BE7181BD:
+    Type: AWS::EC2::Subnet
+    Properties:
+      AssignIpv6AddressOnCreation: true
+      AvailabilityZone:
+        Fn::Select:
+          - 1
+          - Fn::GetAZs:
+              Ref: AWS::Region
+      CidrBlock:
+        Fn::Select:
+          - 3
+          - Fn::Cidr:
+              - 172.31.0.0/16
+              - 4
+              - "12"
+      Ipv6CidrBlock:
+        Fn::Select:
+          - 3
+          - Fn::Cidr:
+              - Fn::Select:
+                  - 0
+                  - Fn::GetAtt:
+                      - CustomVPCB98CB5C5
+                      - Ipv6CidrBlocks
+              - 4
+              - "64"
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: kubernetes.io/role/internal-elb
+          Value: "1"
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+        - Key: Name
+          Value:
+            Fn::Join:
+              - /
+              - - Ref: AWS::StackName
+                - VPC/AZ1/Private
+      VpcId:
+        Ref: CustomVPCB98CB5C5
+    DependsOn:
+      - CustomVPCIPv6CidrBlock2844DCD4
+  CustomVPCPrivate1RouteTable9ACE58A9:
+    Type: AWS::EC2::RouteTable
+    Properties:
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: kubernetes.io/role/internal-elb
+          Value: "1"
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+        - Key: Name
+          Value:
+            Fn::Join:
+              - /
+              - - Ref: AWS::StackName
+                - VPC/AZ1/Private/RouteTable
+      VpcId:
+        Ref: CustomVPCB98CB5C5
+  CustomVPCPrivate1RouteTableAssociation2A40B82E:
+    Type: AWS::EC2::SubnetRouteTableAssociation
+    Properties:
+      RouteTableId:
+        Ref: CustomVPCPrivate1RouteTable9ACE58A9
+      SubnetId:
+        Ref: CustomVPCPrivate1BE7181BD
+  CustomVPCPrivate1DefaultNatRoute1E916812:
+    Type: AWS::EC2::Route
+    Properties:
+      DestinationCidrBlock: 0.0.0.0/0
+      NatGatewayId:
+        Ref: CustomVPCPublic1NatGateway0E4E03E7
+      RouteTableId:
+        Ref: CustomVPCPrivate1RouteTable9ACE58A9
+  CustomVPCPrivate1DefaultEgressOnlyRoute6E190E56:
+    Type: AWS::EC2::Route
+    Properties:
+      DestinationIpv6CidrBlock: ::/0
+      EgressOnlyInternetGatewayId:
+        Ref: CustomVPCEgressOnlyInternetGateway635B9DCC
+      RouteTableId:
+        Ref: CustomVPCPrivate1RouteTable9ACE58A9
+  S3Bucket:
+    Type: AWS::S3::Bucket
+    Properties:
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+    UpdateReplacePolicy: Retain
+    DeletionPolicy: Retain
+  ConversionsApiGatewayBastionIamRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: "2012-10-17"
+        Statement:
+          - Effect: Allow
+            Principal:
+              Service:
+                - ec2.amazonaws.com
+            Action:
+              - sts:AssumeRole
+      ManagedPolicyArns:
+        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
+      Policies:
+        - PolicyDocument:
+            Version: "2012-10-17"
+            Statement:
+              - Effect: Allow
+                Action:
+                  - iam:CreateInstanceProfile
+                  - iam:DeleteInstanceProfile
+                  - iam:GetInstanceProfile
+                  - iam:RemoveRoleFromInstanceProfile
+                  - iam:GetRole
+                  - iam:CreateRole
+                  - iam:DeleteRole
+                  - iam:AttachRolePolicy
+                  - iam:PutRolePolicy
+                  - iam:ListInstanceProfiles
+                  - iam:AddRoleToInstanceProfile
+                  - iam:ListInstanceProfilesForRole
+                  - iam:PassRole
+                  - iam:DetachRolePolicy
+                  - iam:DeleteRolePolicy
+                  - iam:GetRolePolicy
+                  - iam:GetOpenIDConnectProvider
+                  - iam:CreateOpenIDConnectProvider
+                  - iam:DeleteOpenIDConnectProvider
+                  - iam:TagOpenIDConnectProvider
+                  - iam:ListAttachedRolePolicies
+                  - iam:TagRole
+                  - iam:GetPolicy
+                  - iam:CreatePolicy
+                  - iam:DeletePolicy
+                  - iam:ListPolicyVersions
+                Resource:
+                  - Fn::Join:
+                      - ""
+                      - - "arn:aws:iam::"
+                        - Ref: AWS::AccountId
+                        - :instance-profile/eksctl-*
+                  - Fn::Join:
+                      - ""
+                      - - "arn:aws:iam::"
+                        - Ref: AWS::AccountId
+                        - :role/eksctl-
+                        - Fn::Select:
+                            - 4
+                            - Fn::Split:
+                                - "-"
+                                - Fn::Select:
+                                    - 2
+                                    - Fn::Split:
+                                        - /
+                                        - Ref: AWS::StackId
+                        - -*
+                  - Fn::Join:
+                      - ""
+                      - - "arn:aws:iam::"
+                        - Ref: AWS::AccountId
+                        - :policy/eksctl-*
+                  - Fn::Join:
+                      - ""
+                      - - "arn:aws:iam::"
+                        - Ref: AWS::AccountId
+                        - :oidc-provider/*
+                  - Fn::Join:
+                      - ""
+                      - - "arn:aws:iam::"
+                        - Ref: AWS::AccountId
+                        - :role/aws-service-role/eks-nodegroup.amazonaws.com/AWSServiceRoleForAmazonEKSNodegroup
+                  - Fn::Join:
+                      - ""
+                      - - "arn:aws:iam::"
+                        - Ref: AWS::AccountId
+                        - :role/eksctl-managed-*
+              - Effect: Allow
+                Action:
+                  - iam:GetRole
+                Resource:
+                  - arn:aws:iam::*:role/*
+              - Effect: Allow
+                Action:
+                  - iam:CreateServiceLinkedRole
+                Resource: "*"
+                Condition:
+                  StringEquals:
+                    iam:AWSServiceName:
+                      - eks.amazonaws.com
+                      - eks-nodegroup.amazonaws.com
+                      - elasticloadbalancing.amazonaws.com
+          PolicyName: IAMPolicy
+        - PolicyDocument:
+            Version: "2012-10-17"
+            Statement:
+              - Effect: Allow
+                Action:
+                  - ec2:describe*
+                Resource: "*"
+              - Effect: Allow
+                Action:
+                  - ec2:CreateInternetGateway
+                  - ec2:CreateVPC
+                  - ec2:AllocateAddress
+                  - ec2:CreateTags
+                  - ec2:CreateSecurityGroup
+                  - ec2:CreateSubnet
+                  - ec2:CreateRouteTable
+                  - ec2:CreateNatGateway
+                  - ec2:RevokeSecurityGroupIngress
+                  - ec2:CreateLaunchTemplate
+                  - ec2:RunInstances
+                Resource: "*"
+              - Effect: Allow
+                Action:
+                  - ec2:*
+                Resource: "*"
+                Condition:
+                  StringEquals:
+                    aws:ResourceTag/alpha.eksctl.io/cluster-name:
+                      Fn::Select:
+                        - 4
+                        - Fn::Split:
+                            - "-"
+                            - Fn::Select:
+                                - 2
+                                - Fn::Split:
+                                    - /
+                                    - Ref: AWS::StackId
+              - Effect: Allow
+                Action:
+                  - ec2:*
+                Resource: "*"
+                Condition:
+                  StringEquals:
+                    aws:ResourceTag/aws:eks:cluster-name:
+                      Fn::Select:
+                        - 4
+                        - Fn::Split:
+                            - "-"
+                            - Fn::Select:
+                                - 2
+                                - Fn::Split:
+                                    - /
+                                    - Ref: AWS::StackId
+              - Effect: Allow
+                Action:
+                  - ec2:*
+                Resource: "*"
+                Condition:
+                  StringEquals:
+                    aws:ResourceTag/eks:cluster-name:
+                      Fn::Select:
+                        - 4
+                        - Fn::Split:
+                            - "-"
+                            - Fn::Select:
+                                - 2
+                                - Fn::Split:
+                                    - /
+                                    - Ref: AWS::StackId
+              - Effect: Allow
+                Action:
+                  - ec2:DeleteLaunchTemplate
+                Resource: arn:*:ec2:*:*:launch-template/*
+          PolicyName: Ec2Policy
+        - PolicyDocument:
+            Version: "2012-10-17"
+            Statement:
+              - Effect: Allow
+                Action:
+                  - eks:*
+                Resource: "*"
+                Condition:
+                  StringEquals:
+                    aws:ResourceTag/alpha.eksctl.io/cluster-name:
+                      Fn::Select:
+                        - 4
+                        - Fn::Split:
+                            - "-"
+                            - Fn::Select:
+                                - 2
+                                - Fn::Split:
+                                    - /
+                                    - Ref: AWS::StackId
+              - Effect: Allow
+                Action:
+                  - eks:*
+                Resource: "*"
+                Condition:
+                  StringEquals:
+                    aws:ResourceTag/eks:cluster-name:
+                      Fn::Select:
+                        - 4
+                        - Fn::Split:
+                            - "-"
+                            - Fn::Select:
+                                - 2
+                                - Fn::Split:
+                                    - /
+                                    - Ref: AWS::StackId
+              - Effect: Allow
+                Action:
+                  - eks:*
+                Resource: "*"
+                Condition:
+                  StringLike:
+                    aws:ResourceTag/Name:
+                      Fn::Join:
+                        - ""
+                        - - Fn::Select:
+                              - 4
+                              - Fn::Split:
+                                  - "-"
+                                  - Fn::Select:
+                                      - 2
+                                      - Fn::Split:
+                                          - /
+                                          - Ref: AWS::StackId
+                          - "*"
+              - Effect: Allow
+                Action:
+                  - eks:describe*
+                Resource: "*"
+              - Effect: Allow
+                Action:
+                  - eks:list*
+                Resource: "*"
+          PolicyName: EKSPolicy
+        - PolicyDocument:
+            Version: "2012-10-17"
+            Statement:
+              - Effect: Allow
+                Action:
+                  - logs:*
+                Resource:
+                  Fn::Join:
+                    - ""
+                    - - arn:aws:logs:*:*:log-group:/aws/eks/
+                      - Fn::Select:
+                          - 4
+                          - Fn::Split:
+                              - "-"
+                              - Fn::Select:
+                                  - 2
+                                  - Fn::Split:
+                                      - /
+                                      - Ref: AWS::StackId
+                      - /*:*
+              - Effect: Allow
+                Action:
+                  - logs:*
+                Resource: arn:aws:logs:*:*:log-group:ConversionsAPIGateway/*
+              - Effect: Allow
+                Action:
+                  - cloudwatch:PutMetricData
+                Resource: "*"
+              - Effect: Allow
+                Action:
+                  - elasticloadbalancing:*
+                Resource: "*"
+                Condition:
+                  StringEquals:
+                    aws:ResourceTag/alpha.eksctl.io/cluster-name:
+                      Fn::Select:
+                        - 4
+                        - Fn::Split:
+                            - "-"
+                            - Fn::Select:
+                                - 2
+                                - Fn::Split:
+                                    - /
+                                    - Ref: AWS::StackId
+              - Effect: Allow
+                Action:
+                  - autoscaling:*
+                Resource: "*"
+                Condition:
+                  StringEquals:
+                    aws:ResourceTag/alpha.eksctl.io/cluster-name:
+                      Fn::Select:
+                        - 4
+                        - Fn::Split:
+                            - "-"
+                            - Fn::Select:
+                                - 2
+                                - Fn::Split:
+                                    - /
+                                    - Ref: AWS::StackId
+              - Effect: Allow
+                Action:
+                  - autoscaling:Set*
+                Resource: arn:aws:autoscaling:*:*:autoScalingGroup:*:autoScalingGroupName/*
+              - Effect: Allow
+                Action:
+                  - cloudformation:CreateStack
+                Resource: "*"
+              - Effect: Allow
+                Action:
+                  - cloudformation:Describe*
+                  - cloudformation:List*
+                Resource: "*"
+              - Effect: Allow
+                Action:
+                  - cloudformation:*
+                Resource: "*"
+                Condition:
+                  StringEquals:
+                    aws:ResourceTag/alpha.eksctl.io/cluster-name:
+                      Fn::Select:
+                        - 4
+                        - Fn::Split:
+                            - "-"
+                            - Fn::Select:
+                                - 2
+                                - Fn::Split:
+                                    - /
+                                    - Ref: AWS::StackId
+              - Action:
+                  - ssm:GetParameter
+                  - ssm:GetParameters
+                Resource:
+                  - arn:aws:ssm:*:*:parameter/aws/*
+                  - arn:aws:ssm:*::parameter/aws/*
+                Effect: Allow
+              - Action:
+                  - kms:CreateGrant
+                  - kms:DescribeKey
+                Resource: "*"
+                Effect: Allow
+          PolicyName: MiscPolicy
+        - PolicyDocument:
+            Version: "2012-10-17"
+            Statement:
+              - Effect: Allow
+                Action:
+                  - elasticloadbalancing:DescribeLoadBalancers
+                Resource: "*"
+              - Effect: Allow
+                Action:
+                  - elasticloadbalancing:*
+                Resource: "*"
+                Condition:
+                  StringEquals:
+                    aws:ResourceTag/elbv2.k8s.aws/cluster:
+                      Fn::Select:
+                        - 4
+                        - Fn::Split:
+                            - "-"
+                            - Fn::Select:
+                                - 2
+                                - Fn::Split:
+                                    - /
+                                    - Ref: AWS::StackId
+          PolicyName: ELBPolicy
+        - PolicyDocument:
+            Version: "2012-10-17"
+            Statement:
+              - Effect: Allow
+                Action:
+                  - acm:ImportCertificate
+                  - acm:AddTagsToCertificate
+                Resource:
+                  - Fn::Join:
+                      - ""
+                      - - "arn:aws:acm:"
+                        - Ref: AWS::Region
+                        - ":"
+                        - Ref: AWS::AccountId
+                        - :certificate/*
+          PolicyName: BastionACMPolicy
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+  ALBWaitConditionHandle:
+    Type: AWS::CloudFormation::WaitConditionHandle
+  ALBWaitCondition:
+    Type: AWS::CloudFormation::WaitCondition
+    Properties:
+      Count: 1
+      Handle:
+        Ref: ALBWaitConditionHandle
+      Timeout: "7200"
+    DependsOn:
+      - EKSBastionASG
+  BastionWaitConditionHandle:
+    Type: AWS::CloudFormation::WaitConditionHandle
+  BastionWaitCondition:
+    Type: AWS::CloudFormation::WaitCondition
+    Properties:
+      Count: 1
+      Handle:
+        Ref: BastionWaitConditionHandle
+      Timeout: "3600"
+    DependsOn:
+      - EKSBastionASG
+  SelfSignedCertWaitConditionHandle:
+    Type: AWS::CloudFormation::WaitConditionHandle
+  SelfSignedCertWaitCondition:
+    Type: AWS::CloudFormation::WaitCondition
+    Properties:
+      Count: 1
+      Handle:
+        Ref: SelfSignedCertWaitConditionHandle
+      Timeout: "7200"
+  ConversionsApiGatewayBastionInstanceProfile:
+    Type: AWS::IAM::InstanceProfile
+    Properties:
+      InstanceProfileName:
+        Fn::Join:
+          - "-"
+          - - ConversionsApiGatewayBastionInstanceProfile
+            - Ref: AWS::Region
+            - Fn::Select:
+                - 4
+                - Fn::Split:
+                    - "-"
+                    - Fn::Select:
+                        - 2
+                        - Fn::Split:
+                            - /
+                            - Ref: AWS::StackId
+            - Fn::Select:
+                - 0
+                - Fn::Split:
+                    - "-"
+                    - Fn::Select:
+                        - 2
+                        - Fn::Split:
+                            - /
+                            - Ref: AWS::StackId
+      Roles:
+        - Ref: ConversionsApiGatewayBastionIamRole
+  EKSBastionNoAccessSecurityGroup:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      GroupDescription: EKSBastionSecurityGroup to limit access to the instance
+      GroupName:
+        Fn::Join:
+          - ""
+          - - CAPIGNoAccess
+            - Fn::Select:
+                - 4
+                - Fn::Split:
+                    - "-"
+                    - Fn::Select:
+                        - 2
+                        - Fn::Split:
+                            - /
+                            - Ref: AWS::StackId
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+      VpcId:
+        Ref: CustomVPCB98CB5C5
+  EKSBastionLaunchTemplate:
+    Type: AWS::EC2::LaunchTemplate
+    Properties:
+      LaunchTemplateData:
+        IamInstanceProfile:
+          Arn:
+            Fn::GetAtt:
+              - ConversionsApiGatewayBastionInstanceProfile
+              - Arn
+        ImageId:
+          Fn::FindInMap:
+            - CustomImageIds
+            - Ref: AWS::Region
+            - ami
+        InstanceType:
+          Fn::If:
+            - useT3Nano
+            - t3.nano
+            - t2.micro
+        SecurityGroupIds:
+          - Ref: EKSBastionNoAccessSecurityGroup
+        TagSpecifications:
+          - ResourceType: instance
+            Tags:
+              - Key: Name
+                Value:
+                  Fn::Join:
+                    - ""
+                    - - Fn::Select:
+                          - 4
+                          - Fn::Split:
+                              - "-"
+                              - Fn::Select:
+                                  - 2
+                                  - Fn::Split:
+                                      - /
+                                      - Ref: AWS::StackId
+                      - -EKSBastion
+              - Key: MainStack
+                Value:
+                  Ref: AWS::StackName
+        UserData:
+          Fn::Base64:
+            Fn::Sub:
+              - |-
+                #!/bin/bash
+                # shellcheck disable=SC2016
+                # shellcheck disable=SC2154
+                # This script is imported to CDK app.py file and populated with variables so it can't use double quote
+                # Single curly brackets are build time parameters; double curly brackets are runtime parameters.
+
+                rpm -Uvh https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm
+                /opt/aws/bin/cfn-init -v --stack '${AWS::StackId}' --resource EKSBastionLaunchTemplate --region '${AWS::Region}' --configsets default
+                wget 'https://conversions-api-gateway-release-production.s3.amazonaws.com/conversions-api-gateway-1.11.0-eks.run' -O /root/cloudbridge_release
+                chmod +x /root/cloudbridge_release
+                /root/cloudbridge_release --target /opt/cloudbridge -- --placeholder  --template_name 'multi_tenant' \
+                --image_tag 'multitenant' --release_version '1.11.0' --release_bucket 'conversions-api-gateway-release-production' \
+                --admin_email '${AdminEmail}' --admin_password '${AdminPassword}' --provisioning_data '${ProvisioningData}' \
+                --telemetry_consent '${SystemHealthInformation}' \
+                --eks_cluster_name '${EksClusterName}' --aws_region '${AWS::Region}' --aws_instance_type 'm5.large' --main_stack_name '${AWS::StackName}' \
+                --instance_max_size 1 \
+                --instance_min_size 1 \
+                --bastion_wait_condition_url '${BastionWaitConditionURL}' \
+                --alb_wait_condition_url '${ALBWaitConditionURL}' --self_signed_cert_wait_condition_url '${SelfSignedCertWaitConditionURL}' \
+                --capig_s3_bucket_name '${CapigS3BucketName}' \
+                --release_channel 'production' --instance_id '${InstanceId}'
+              - EksClusterName:
+                  Fn::Select:
+                    - 4
+                    - Fn::Split:
+                        - "-"
+                        - Fn::Select:
+                            - 2
+                            - Fn::Split:
+                                - /
+                                - Ref: AWS::StackId
+                InstanceId:
+                  Fn::Join:
+                    - ""
+                    - - prod-
+                      - Fn::Select:
+                          - 2
+                          - Fn::Split:
+                              - /
+                              - Ref: AWS::StackId
+                ALBWaitConditionURL:
+                  Ref: ALBWaitConditionHandle
+                SelfSignedCertWaitConditionURL:
+                  Ref: SelfSignedCertWaitConditionHandle
+                BastionWaitConditionURL:
+                  Ref: BastionWaitConditionHandle
+                CapigS3BucketName:
+                  Ref: S3Bucket
+                EnableEventEnrichment: "No"
+                AdminEmail:
+                  Fn::GetAtt:
+                    - ConversionsApiGatewayBastionIamRole
+                    - Arn
+                AdminPassword:
+                  Fn::GetAtt:
+                    - ConversionsApiGatewayBastionIamRole
+                    - RoleId
+    Metadata:
+      AWS::CloudFormation::Init:
+        configSets:
+          default:
+            - 01_setupCfnHup
+            - 02_config-amazon-cloudwatch-agent
+            - 03_restart_amazon-cloudwatch-agent
+          UpdateEnvironment:
+            - 02_config-amazon-cloudwatch-agent
+            - 03_restart_amazon-cloudwatch-agent
+        02_config-amazon-cloudwatch-agent:
+          files:
+            /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json:
+              content:
+                Fn::Sub: '{"agent":{"run_as_user":"root"},"logs":{"logs_collected":{"files":{"collect_list":[{"file_path":"/var/log/cloud-init-output.log","log_group_name":"ConversionsAPIGateway/cloud-init-output.log","log_stream_name":"{instance_id}","retention_in_days":14}]}}}}'
+        03_restart_amazon-cloudwatch-agent:
+          commands:
+            01_stop_service:
+              command: /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a stop
+            02_start_service:
+              command: /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s
+        01_setupCfnHup:
+          files:
+            /etc/cfn/cfn-hup.conf:
+              content:
+                Fn::Sub: |-
+                  [main]
+                  stack=${AWS::StackId}
+                  region=${AWS::Region}
+                  interval=1
+              mode: "000400"
+              owner: root
+              group: root
+            /etc/cfn/hooks.d/amazon-cloudwatch-agent-auto-reloader.conf:
+              content:
+                Fn::Sub: |-
+                  [cfn-auto-reloader-hook]
+                  triggers=post.update
+                  path=Resources.EC2Instance.Metadata.AWS::CloudFormation::Init.02_config-amazon-cloudwatch-agent
+                  action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackId} --resource EKSBastionInstance --region ${AWS::Region} --configsets UpdateEnvironment
+                  runas=root
+              mode: "000400"
+              owner: root
+              group: root
+            /lib/systemd/system/cfn-hup.service:
+              content: |-
+                [Unit]
+                Description=cfn-hup daemon
+                [Service]
+                Type=simple
+                ExecStart=/opt/aws/bin/cfn-hup
+                Restart=always
+                [Install]
+                WantedBy=multi-user.target
+            /etc/vpc.json:
+              content:
+                Fn::Sub:
+                  - |
+                    {
+                    	"vpcid":"${vpcid}",
+                    	"available_zones": ["${zone0}", "${zone1}"],
+                    	"public_subnets": ["${public0}", "${public1}"],
+                    	"private_subnets": ["${private0}", "${private1}"]
+                    }
+                  - vpcid:
+                      Ref: CustomVPCB98CB5C5
+                    zone0:
+                      Fn::Select:
+                        - 0
+                        - Fn::GetAZs:
+                            Ref: AWS::Region
+                    zone1:
+                      Fn::Select:
+                        - 1
+                        - Fn::GetAZs:
+                            Ref: AWS::Region
+                    public0:
+                      Ref: CustomVPCPublic039347A47
+                    public1:
+                      Ref: CustomVPCPublic1E2BA9DAA
+                    private0:
+                      Ref: CustomVPCPrivate0D2251CEA
+                    private1:
+                      Ref: CustomVPCPrivate1BE7181BD
+            /opt/cloudbridge/var/cluster_type:
+              content: awseks
+            /opt/cloudbridge/var/cb_product_name:
+              content: Conversions API Gateway
+            /opt/cloudbridge/var/cb_tenant_type:
+              content: multitenant
+          commands:
+            01enable_cfn_hup:
+              command: systemctl enable cfn-hup.service
+            02start_cfn_hup:
+              command: systemctl start cfn-hup.service
+  EKSBastionASG:
+    Type: AWS::AutoScaling::AutoScalingGroup
+    Properties:
+      AutoScalingGroupName:
+        Fn::Join:
+          - ""
+          - - Fn::Select:
+                - 4
+                - Fn::Split:
+                    - "-"
+                    - Fn::Select:
+                        - 2
+                        - Fn::Split:
+                            - /
+                            - Ref: AWS::StackId
+            - -EKSBastionASG
+      LaunchTemplate:
+        LaunchTemplateId:
+          Ref: EKSBastionLaunchTemplate
+        Version:
+          Fn::GetAtt:
+            - EKSBastionLaunchTemplate
+            - LatestVersionNumber
+      MaxSize: "1"
+      MinSize: "1"
+      Tags:
+        - Key: alpha.eksctl.io/cluster-name
+          PropagateAtLaunch: true
+          Value:
+            Fn::Select:
+              - 4
+              - Fn::Split:
+                  - "-"
+                  - Fn::Select:
+                      - 2
+                      - Fn::Split:
+                          - /
+                          - Ref: AWS::StackId
+        - Key: Application
+          PropagateAtLaunch: true
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          PropagateAtLaunch: true
+          Value: ""
+        - Key: InstanceId
+          PropagateAtLaunch: true
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          PropagateAtLaunch: true
+          Value:
+            Ref: AWS::StackName
+      VPCZoneIdentifier:
+        - Ref: CustomVPCPublic039347A47
+        - Ref: CustomVPCPublic1E2BA9DAA
+    DependsOn:
+      - ConversionsApiGatewayUninstallLambdaRole
+      - CustomVPCInternetGatewayAttachment4DE4D032
+  ConversionsApiGatewayUninstallLambdaRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: "2012-10-17"
+        Statement:
+          - Effect: Allow
+            Principal:
+              Service:
+                - lambda.amazonaws.com
+            Action:
+              - sts:AssumeRole
+      Policies:
+        - PolicyDocument:
+            Version: "2012-10-17"
+            Statement:
+              - Effect: Allow
+                Action:
+                  - ssm:SendCommand
+                Resource:
+                  - arn:aws:ssm:*:*:document/*
+              - Effect: Allow
+                Action:
+                  - ssm:SendCommand
+                Resource:
+                  - "*"
+                Condition:
+                  StringEquals:
+                    ssm:resourceTag/Name:
+                      - Fn::Join:
+                          - ""
+                          - - Fn::Select:
+                                - 4
+                                - Fn::Split:
+                                    - "-"
+                                    - Fn::Select:
+                                        - 2
+                                        - Fn::Split:
+                                            - /
+                                            - Ref: AWS::StackId
+                            - -EKSBastion
+              - Effect: Allow
+                Action:
+                  - logs:CreateLogGroup
+                  - ec2:DescribeTags
+                Resource:
+                  - "*"
+              - Effect: Allow
+                Action:
+                  - logs:CreateLogStream
+                  - logs:PutLogEvents
+                Resource: arn:aws:logs:*:*:log-group:/aws/lambda/*:*
+              - Effect: Allow
+                Action:
+                  - acm:DeleteCertificate
+                Resource:
+                  - arn:aws:acm:*:*:certificate/*
+              - Effect: Allow
+                Action:
+                  - eks:DescribeCluster
+                Resource:
+                  - "*"
+              - Effect: Allow
+                Action:
+                  - ec2:*
+                Resource: "*"
+                Condition:
+                  StringEquals:
+                    aws:ResourceTag/alpha.eksctl.io/cluster-name:
+                      Fn::Select:
+                        - 4
+                        - Fn::Split:
+                            - "-"
+                            - Fn::Select:
+                                - 2
+                                - Fn::Split:
+                                    - /
+                                    - Ref: AWS::StackId
+          PolicyName: LambdaPolicy
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+  CAPIGUninstallLambda:
+    Type: AWS::Lambda::Function
+    Properties:
+      Architectures:
+        - x86_64
+      Code:
+        ZipFile: |-
+          import logging
+          import os
+          import time
+
+          import boto3
+          import cfnresponse
+
+
+          def handler(event, context):
+              logger = logging.getLogger()
+              logger.setLevel(logging.INFO)
+
+              logger.info(event)
+              ssm = boto3.client("ssm")
+              ec2 = boto3.client("ec2")
+              eks = boto3.client('eks')
+              acm = boto3.client('acm')
+              responseData = {}
+              region = os.environ["AWS_REGION"]
+              self_signed_cert_arn = os.environ["SELF_SIGNED_CERT_ARN"]
+              # For Non Delete requests, immediately send a SUCCESS response.
+              if event["RequestType"] != "Delete":
+                  cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
+                  logger.info("Skipping non delete request.")
+                  return
+
+              cluster_name = event["StackId"].split("/")[2].split("-")[4]
+
+              logger.info(cluster_name)
+              tag_response = ec2.describe_tags(
+                  Filters=[
+                      {"Name": "tag:Name", "Values": [f"{cluster_name}-EKSBastion"]},
+                  ],
+              )
+
+              get_asg_id_tag_response = ec2.describe_tags(
+                  Filters=[
+                      {"Name": "key", "Values": ["eks:cluster-name"]},
+                      {"Name": "value", "Values": [f"{cluster_name}"]},
+                      {"Name": "resource-type", "Values": ["instance"]},
+                  ],
+              )
+
+
+              if (len(get_asg_id_tag_response["Tags"]) > 1):
+                  asg_one_instance_id = get_asg_id_tag_response["Tags"][0]["ResourceId"]
+                  asg_two_instance_id = get_asg_id_tag_response["Tags"][1]["ResourceId"]
+
+                  asg_one_name_tag_response = ec2.describe_tags(
+                      Filters=[
+                          {"Name": "key", "Values": ["aws:autoscaling:groupName"]},
+                          {"Name": "value", "Values": ["eks-private*"]},
+                          {"Name": "resource-id", "Values": [f"{asg_one_instance_id}"]},
+                      ],
+                  )
+                  asg_one_name = asg_one_name_tag_response["Tags"][0]["Value"]
+
+                  asg_two_name_tag_response = ec2.describe_tags(
+                      Filters=[
+                          {"Name": "key", "Values": ["aws:autoscaling:groupName"]},
+                          {"Name": "value", "Values": ["eks-private*"]},
+                          {"Name": "resource-id", "Values": [f"{asg_two_instance_id}"]},
+                      ],
+                  )
+                  asg_two_name = asg_two_name_tag_response["Tags"][0]["Value"]
+
+                  logger.info("tagone:%s", asg_one_name_tag_response)
+                  logger.info("tagtwo:%s", asg_two_name_tag_response)
+
+                  logger.info("autoscale instance id :%s", asg_one_instance_id)
+                  logger.info("autoscale instance id :%s", asg_two_instance_id)
+                  logger.info("autoscale instance name :%s", asg_one_name)
+                  logger.info("autoscale instance name :%s", asg_two_name)
+
+                  instance_id = tag_response["Tags"][0]["ResourceId"]
+                  ec2.start_instances(InstanceIds=[instance_id])
+                  time.sleep(60)
+                  # Send uninstall telemetry at start
+                  ssm.send_command(
+                      DocumentName="AWS-RunShellScript",
+                      Parameters={
+                          "commands": [
+                              "chmod +x /opt/cloudbridge/bin/uninstall_logging_script.sh",
+                              "/opt/cloudbridge/bin/uninstall_logging_script.sh start",
+                          ]
+                      },
+                      Targets=[{"Key": "tag:Name", "Values": [cluster_name + "-EKSBastion"]}],
+                      CloudWatchOutputConfig={
+                          "CloudWatchLogGroupName": "ConversionsAPIGatewayUninstall",
+                          "CloudWatchOutputEnabled": True,
+                      },
+                  )
+
+                  # Delete cluster and its resources
+                  cluster_delete_response = ssm.send_command(
+                  DocumentName="AWS-RunShellScript",
+                  Parameters={
+                      "commands": [
+                          "chmod +x /opt/cloudbridge/bin/eks_uninstall_script.py",
+                          f"sudo -u ec2-user sudo python3 /opt/cloudbridge/bin/eks_uninstall_script.py \
+                          --cluster_name {cluster_name} \
+                          --autoscale_group_one_name {asg_one_name} \
+                          --autoscale_group_two_name {asg_two_name} \
+                          --autoscale_group_one_id {asg_one_instance_id} \
+                          --autoscale_group_two_id {asg_two_instance_id} \
+                          --region {region}",
+
+                      ]
+                  },
+                  Targets=[{"Key": "tag:Name", "Values": [cluster_name + "-EKSBastion"]}],
+                  CloudWatchOutputConfig={
+                      "CloudWatchOutputEnabled": True,
+                  },
+                  )
+
+                  logger.info("CommandStatus for stack delete is:%s", cluster_delete_response["Command"]["Status"])
+
+
+              #Time it takes to delete EKS cluster resources with an extra buffer
+              time.sleep(820)
+
+              try:
+                  acm.delete_certificate(CertificateArn=self_signed_cert_arn)
+                  logger.info("Deleted self signed certificate.")
+              except acm.exceptions.ResourceNotFoundException:
+                  logger.info("Self signed certificate was already removed.")
+
+              try:
+                  # Get the status of the cluster
+                  cluster_response = eks.describe_cluster(name=cluster_name)
+                  status = cluster_response['cluster']['status']
+                  if status == "DELETING":
+                      logger.info(f"Cluster '{cluster_name}' Deletion was successful.")
+                  else:
+                      logger.info(f"Cluster '{cluster_name}' Deletion was failed.")
+              except eks.exceptions.ResourceNotFoundException:
+                  logger.info(f"Cluster '{cluster_name}' not found. Most likely the cluster was deleted.")
+              except Exception as e :
+                  logger.exception(f"Error while getting cluster status. {e}")
+
+
+              cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
+      Description: Lambda function used to uninstall CAPIG stacks automatically.
+      Environment:
+        Variables:
+          SELF_SIGNED_CERT_ARN:
+            Fn::Select:
+              - 3
+              - Fn::Split:
+                  - '"'
+                  - Fn::GetAtt:
+                      - SelfSignedCertWaitCondition
+                      - Data
+      EphemeralStorage:
+        Size: 512
+      FunctionName:
+        Fn::Join:
+          - ""
+          - - CAPIGUninstall-
+            - Fn::Select:
+                - 4
+                - Fn::Split:
+                    - "-"
+                    - Fn::Select:
+                        - 2
+                        - Fn::Split:
+                            - /
+                            - Ref: AWS::StackId
+      Handler: index.handler
+      MemorySize: 128
+      Role:
+        Fn::GetAtt:
+          - ConversionsApiGatewayUninstallLambdaRole
+          - Arn
+      Runtime: python3.9
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+      Timeout: 900
+    DependsOn:
+      - CustomVPCPrivate0DefaultEgressOnlyRoute0CBB8974
+      - CustomVPCPrivate0DefaultNatRoute647FD84E
+      - CustomVPCPrivate0RouteTableAssociation1429B372
+      - CustomVPCPrivate1DefaultEgressOnlyRoute6E190E56
+      - CustomVPCPrivate1DefaultNatRoute1E916812
+      - CustomVPCPrivate1RouteTableAssociation2A40B82E
+      - CustomVPCPublic0DefaultRoute34086041
+      - CustomVPCPublic0RouteTableC4A2F788
+      - CustomVPCPublic0RouteTableAssociationCD395678
+      - CustomVPCPublic1DefaultRoute4B970118
+      - CustomVPCPublic1RouteTable36124A34
+      - CustomVPCPublic1RouteTableAssociation4B85F0F6
+      - EKSBastionASG
+      - SelfSignedCertWaitCondition
+  CAPIGUninstallCustomResource:
+    Type: AWS::CloudFormation::CustomResource
+    Properties:
+      ServiceToken:
+        Fn::GetAtt:
+          - CAPIGUninstallLambda
+          - Arn
+  CapigDistributionDD131D32:
+    Type: AWS::CloudFront::Distribution
+    Properties:
+      DistributionConfig:
+        DefaultCacheBehavior:
+          AllowedMethods:
+            - GET
+            - HEAD
+            - OPTIONS
+            - PUT
+            - PATCH
+            - POST
+            - DELETE
+          CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
+          Compress: true
+          OriginRequestPolicyId: 216adef6-5c7f-47e4-b989-5492eafa07d3
+          TargetOriginId: eksbastioninstanceCapigDistributionOrigin1031A4667
+          ViewerProtocolPolicy: redirect-to-https
+        Enabled: true
+        HttpVersion: http2
+        IPV6Enabled: true
+        Origins:
+          - CustomOriginConfig:
+              OriginProtocolPolicy: http-only
+              OriginSSLProtocols:
+                - TLSv1.2
+            DomainName:
+              Fn::Select:
+                - 3
+                - Fn::Split:
+                    - '"'
+                    - Fn::GetAtt:
+                        - ALBWaitCondition
+                        - Data
+            Id: eksbastioninstanceCapigDistributionOrigin1031A4667
+            OriginCustomHeaders:
+              - HeaderName: Meta-Capig-Ingress
+                HeaderValue: meta-capig-cloudfront
+        PriceClass: PriceClass_100
+      Tags:
+        - Key: alpha.eksctl.io/cluster-name
+          Value:
+            Fn::Select:
+              - 4
+              - Fn::Split:
+                  - "-"
+                  - Fn::Select:
+                      - 2
+                      - Fn::Split:
+                          - /
+                          - Ref: AWS::StackId
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+    DependsOn:
+      - ALBWaitCondition
+  InfraManagementLambdaFunctionServiceRole44362244:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Statement:
+          - Action: sts:AssumeRole
+            Effect: Allow
+            Principal:
+              Service: lambda.amazonaws.com
+        Version: "2012-10-17"
+      ManagedPolicyArns:
+        - Fn::Join:
+            - ""
+            - - "arn:"
+              - Ref: AWS::Partition
+              - :iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
+        - Fn::Join:
+            - ""
+            - - "arn:"
+              - Ref: AWS::Partition
+              - :iam::aws:policy/AdministratorAccess
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+  InfraManagementLambdaFunction776BD126:
+    Type: AWS::Lambda::Function
+    Properties:
+      Code:
+        ZipFile: |-
+          import os
+          import subprocess
+          from urllib.request import urlretrieve
+
+
+          def handler(event, context):
+              script_url = "https://conversions-api-gateway-release-production.s3.us-west-2.amazonaws.com/" \
+                           "infra-lifecycle/scripts/infra_management_fail_safe.py"
+              download_folder = "/tmp/infra-management-toolkit/"
+              download_file_path = download_folder + "infra_management_fail_safe.py"
+              if not os.path.exists(download_folder):
+                  os.makedirs(download_folder)
+
+              urlretrieve(script_url, download_file_path)
+              os.chmod(download_file_path, 0o755)
+              subprocess.call(['python', download_file_path])
+      Environment:
+        Variables:
+          INSTANCE_ID:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+          JOB_TABLE_NAME:
+            Fn::Join:
+              - ""
+              - - infra_management_prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+          HUB_URL:
+            Fn::GetAtt:
+              - CapigDistributionDD131D32
+              - DomainName
+          STACK_ARN:
+            Ref: AWS::StackId
+      Handler: index.handler
+      Role:
+        Fn::GetAtt:
+          - InfraManagementLambdaFunctionServiceRole44362244
+          - Arn
+      Runtime: python3.8
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+      Timeout: 900
+    DependsOn:
+      - InfraManagementLambdaFunctionServiceRole44362244
+  InfraManagementLambdaScheduleRule6D6F0FF7:
+    Type: AWS::Events::Rule
+    Properties:
+      ScheduleExpression: rate(1 hour)
+      State: ENABLED
+      Targets:
+        - Arn:
+            Fn::GetAtt:
+              - InfraManagementLambdaFunction776BD126
+              - Arn
+          Id: Target0
+  InfraManagementLambdaScheduleRuleAllowEventRuleeksbastioninstanceInfraManagementLambdaFunctionC080AD481C53672B:
+    Type: AWS::Lambda::Permission
+    Properties:
+      Action: lambda:InvokeFunction
+      FunctionName:
+        Fn::GetAtt:
+          - InfraManagementLambdaFunction776BD126
+          - Arn
+      Principal: events.amazonaws.com
+      SourceArn:
+        Fn::GetAtt:
+          - InfraManagementLambdaScheduleRule6D6F0FF7
+          - Arn
+  CodeBuildRole728CBADE:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Statement:
+          - Action: sts:AssumeRole
+            Effect: Allow
+            Principal:
+              Service: codebuild.amazonaws.com
+        Version: "2012-10-17"
+      ManagedPolicyArns:
+        - Fn::Join:
+            - ""
+            - - "arn:"
+              - Ref: AWS::Partition
+              - :iam::aws:policy/AdministratorAccess
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+  CodeBuildRoleDefaultPolicy829527DE:
+    Type: AWS::IAM::Policy
+    Properties:
+      PolicyDocument:
+        Statement:
+          - Action:
+              - logs:CreateLogGroup
+              - logs:CreateLogStream
+              - logs:PutLogEvents
+            Effect: Allow
+            Resource:
+              - Fn::Join:
+                  - ""
+                  - - "arn:"
+                    - Ref: AWS::Partition
+                    - ":logs:"
+                    - Ref: AWS::Region
+                    - ":"
+                    - Ref: AWS::AccountId
+                    - :log-group:/aws/codebuild/
+                    - Ref: InfraManagementDE08CFB8
+              - Fn::Join:
+                  - ""
+                  - - "arn:"
+                    - Ref: AWS::Partition
+                    - ":logs:"
+                    - Ref: AWS::Region
+                    - ":"
+                    - Ref: AWS::AccountId
+                    - :log-group:/aws/codebuild/
+                    - Ref: InfraManagementDE08CFB8
+                    - :*
+          - Action:
+              - codebuild:CreateReportGroup
+              - codebuild:CreateReport
+              - codebuild:UpdateReport
+              - codebuild:BatchPutTestCases
+              - codebuild:BatchPutCodeCoverages
+            Effect: Allow
+            Resource:
+              Fn::Join:
+                - ""
+                - - "arn:"
+                  - Ref: AWS::Partition
+                  - ":codebuild:"
+                  - Ref: AWS::Region
+                  - ":"
+                  - Ref: AWS::AccountId
+                  - :report-group/
+                  - Ref: InfraManagementDE08CFB8
+                  - -*
+        Version: "2012-10-17"
+      PolicyName: CodeBuildRoleDefaultPolicy829527DE
+      Roles:
+        - Ref: CodeBuildRole728CBADE
+  InfraManagementDE08CFB8:
+    Type: AWS::CodeBuild::Project
+    Properties:
+      Artifacts:
+        Type: NO_ARTIFACTS
+      Cache:
+        Type: NO_CACHE
+      EncryptionKey: alias/aws/s3
+      Environment:
+        ComputeType: BUILD_GENERAL1_SMALL
+        EnvironmentVariables:
+          - Name: INSTANCE_ID
+            Type: PLAINTEXT
+            Value:
+              Fn::Join:
+                - ""
+                - - prod-
+                  - Fn::Select:
+                      - 2
+                      - Fn::Split:
+                          - /
+                          - Ref: AWS::StackId
+          - Name: ECR_IMAGE_URL
+            Type: PLAINTEXT
+            Value: public.ecr.aws/fbconvservice/swak-ops:1.10.0
+          - Name: JOB_TABLE_NAME
+            Type: PLAINTEXT
+            Value:
+              Fn::Join:
+                - ""
+                - - infra_management_prod-
+                  - Fn::Select:
+                      - 2
+                      - Fn::Split:
+                          - /
+                          - Ref: AWS::StackId
+          - Name: HUB_URL
+            Type: PLAINTEXT
+            Value:
+              Fn::GetAtt:
+                - CapigDistributionDD131D32
+                - DomainName
+          - Name: STACK_ARN
+            Type: PLAINTEXT
+            Value:
+              Ref: AWS::StackId
+        Image: public.ecr.aws/fbconvservice/swak-ops:1.10.0
+        ImagePullCredentialsType: SERVICE_ROLE
+        PrivilegedMode: false
+        Type: LINUX_CONTAINER
+      ServiceRole:
+        Fn::GetAtt:
+          - CodeBuildRole728CBADE
+          - Arn
+      Source:
+        BuildSpec: |-
+          {
+            "version": "0.2",
+            "phases": {
+              "build": {
+                "commands": [
+                  "echo 'Fetching the init file'",
+                  "curl -o /tmp/init.py https://conversions-api-gateway-release-production.s3.us-west-2.amazonaws.com/infra-lifecycle/scripts/infra_management_code_build_init.py",
+                  "echo 'Executing the init file'",
+                  "python /tmp/init.py"
+                ]
+              }
+            }
+          }
+        Type: NO_SOURCE
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+      TimeoutInMinutes: 120
+  JobTable0F869D99:
+    Type: AWS::DynamoDB::Table
+    Properties:
+      AttributeDefinitions:
+        - AttributeName: pk
+          AttributeType: S
+      BillingMode: PAY_PER_REQUEST
+      KeySchema:
+        - AttributeName: pk
+          KeyType: HASH
+      TableName:
+        Fn::Join:
+          - ""
+          - - infra_management_prod-
+            - Fn::Select:
+                - 2
+                - Fn::Split:
+                    - /
+                    - Ref: AWS::StackId
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+    UpdateReplacePolicy: Retain
+    DeletionPolicy: Retain
+  LambdaCodeBuildRole6A435DEA:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Statement:
+          - Action: sts:AssumeRole
+            Effect: Allow
+            Principal:
+              Service: lambda.amazonaws.com
+        Version: "2012-10-17"
+      Description: IAM role for Lambda function to trigger CodeBuild
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+  LambdaCodeBuildRoleDefaultPolicy91FA4403:
+    Type: AWS::IAM::Policy
+    Properties:
+      PolicyDocument:
+        Statement:
+          - Action: codebuild:StartBuild
+            Effect: Allow
+            Resource:
+              Fn::Join:
+                - ""
+                - - "arn:aws:codebuild:"
+                  - Ref: AWS::Region
+                  - ":"
+                  - Ref: AWS::AccountId
+                  - :project/
+                  - Ref: InfraManagementDE08CFB8
+        Version: "2012-10-17"
+      PolicyName: LambdaCodeBuildRoleDefaultPolicy91FA4403
+      Roles:
+        - Ref: LambdaCodeBuildRole6A435DEA
+  CodeBuildLambdaFunctionBC2828BB:
+    Type: AWS::Lambda::Function
+    Properties:
+      Code:
+        ZipFile: |-
+          import json
+
+          import boto3
+          import os
+          from urllib import request
+
+
+          def handler(event, context):
+              # Access the custom parameters from the SNS message payload
+              message_attributes = event['Records'][0]['Sns']['MessageAttributes']
+              incoming_instance_ids = message_attributes.get('instance_ids', None)
+              if incoming_instance_ids is not None:
+                  incoming_instance_ids = incoming_instance_ids['Value']
+
+              incoming_rollout_bucket = message_attributes.get('rollout_bucket', None)
+              if incoming_rollout_bucket is not None:
+                  incoming_rollout_bucket = str(incoming_rollout_bucket['Value'])
+
+              # Only trigger code pipeline for the configured instance_id or rollout bucket and skip otherwise
+              if is_updates_allowed(incoming_rollout_bucket=incoming_rollout_bucket,
+                                    incoming_instance_ids= incoming_instance_ids):
+                  print('Triggering code build')
+                  # Initialize the CodeBuild client
+                  codebuild = boto3.client('codebuild')
+
+                  # Specify the CodeBuild project name
+                  project_name = os.environ['CODE_BUILD_PROJECT_NAME']
+
+                  # Start the build
+                  response = codebuild.start_build(
+                      projectName=project_name,
+                      artifactsOverride={"type": "NO_ARTIFACTS"}
+                  )
+
+                  # Handle the response
+                  build_id = response['build']['id']
+                  print(f'Started CodeBuild project {project_name} with build ID {build_id}')
+                  return
+
+              print(f'configured instance_id {os.environ["INSTANCE_ID"]} is not present in input parameters')
+
+
+          def is_updates_allowed(incoming_rollout_bucket, incoming_instance_ids) -> bool:
+              try:
+                  hub_url = os.environ['HUB_URL']
+                  if not hub_url.startswith('https://'):
+                      hub_url = f"https://{hub_url}"
+                  hub_health_endpoint = f"{hub_url}/hub/health"
+                  response = request.urlopen(hub_health_endpoint, timeout=20)
+                  data = response.read()
+                  payload = json.loads(data)
+                  for check in payload['checks']:
+                      if check.get('id') == 'liveness':
+                          is_automatic_updates_allowed = str(check['data']['automatic_updates_status']).lower() == 'true'
+                          rollout_bucket = str(check['data']['rollout_bucket'])
+                          return is_automatic_updates_allowed and \
+                              (
+                                      is_rollout_allowed_by_bucket(incoming_rollout_bucket=incoming_rollout_bucket,
+                                                                   instance_rollout_bucket=rollout_bucket)
+
+                                      or is_rollout_allowed_by_instance_id(incoming_instance_ids)
+                              )
+              except Exception as err:
+                  print(err)
+                  return False
+
+
+          def is_rollout_allowed_by_instance_id(incoming_instance_ids) -> bool:
+              configured_instance_id = os.environ['INSTANCE_ID']
+              return incoming_instance_ids is not None \
+                  and (configured_instance_id in incoming_instance_ids
+                       or "ALL" in incoming_instance_ids)
+
+
+          def is_rollout_allowed_by_bucket(incoming_rollout_bucket, instance_rollout_bucket) -> bool:
+              return incoming_rollout_bucket is not None and incoming_rollout_bucket == instance_rollout_bucket
+      Environment:
+        Variables:
+          CODE_BUILD_PROJECT_NAME:
+            Ref: InfraManagementDE08CFB8
+          INSTANCE_ID:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+          HUB_URL:
+            Fn::GetAtt:
+              - CapigDistributionDD131D32
+              - DomainName
+          STACK_ARN:
+            Ref: AWS::StackId
+      Handler: index.handler
+      Role:
+        Fn::GetAtt:
+          - LambdaCodeBuildRole6A435DEA
+          - Arn
+      Runtime: python3.8
+      Tags:
+        - Key: Application
+          Value: Conversions API Gateway
+        - Key: conversions-api-gateway-resource
+          Value: ""
+        - Key: InstanceId
+          Value:
+            Fn::Join:
+              - ""
+              - - prod-
+                - Fn::Select:
+                    - 2
+                    - Fn::Split:
+                        - /
+                        - Ref: AWS::StackId
+        - Key: MainStack
+          Value:
+            Ref: AWS::StackName
+      Timeout: 900
+    DependsOn:
+      - LambdaCodeBuildRoleDefaultPolicy91FA4403
+      - LambdaCodeBuildRole6A435DEA
+  CodeBuildLambdaFunctionAllowInvokeeksbastioninstanceExternalSNSTopicD4C1B676C54485C8:
+    Type: AWS::Lambda::Permission
+    Properties:
+      Action: lambda:InvokeFunction
+      FunctionName:
+        Fn::GetAtt:
+          - CodeBuildLambdaFunctionBC2828BB
+          - Arn
+      Principal: sns.amazonaws.com
+      SourceArn: arn:aws:sns:us-east-2:578985338881:infra-management
+  CodeBuildLambdaFunctionExternalSNSTopicFC1708E4:
+    Type: AWS::SNS::Subscription
+    Properties:
+      Endpoint:
+        Fn::GetAtt:
+          - CodeBuildLambdaFunctionBC2828BB
+          - Arn
+      Protocol: lambda
+      Region: us-east-2
+      TopicArn: arn:aws:sns:us-east-2:578985338881:infra-management
+Conditions:
+  useT3Nano:
+    Fn::Or:
+      - Fn::Equals:
+          - Ref: AWS::Region
+          - eu-south-1
+      - Fn::Equals:
+          - Ref: AWS::Region
+          - eu-north-1
+Outputs:
+  InstallationLogLocation:
+    Description: Installation status and progress will be available in this Cloudwatch Logs group.
+    Value:
+      Fn::Join:
+        - ""
+        - - https://
+          - Ref: AWS::Region
+          - .console.aws.amazon.com/cloudwatch/home?region=
+          - Ref: AWS::Region
+          - "#logsV2:log-groups/log-group/ConversionsAPIGateway$252Fcloud-init-output.log/log-events/"
+          - Fn::Select:
+              - 3
+              - Fn::Split:
+                  - '"'
+                  - Fn::GetAtt:
+                      - BastionWaitCondition
+                      - Data
+  BastionHost:
+    Description: View bastion host in AWS console.
+    Value:
+      Fn::Join:
+        - ""
+        - - https://
+          - Ref: AWS::Region
+          - .console.aws.amazon.com/ec2/home?region=
+          - Ref: AWS::Region
+          - "#Instances:search="
+          - Fn::Select:
+              - 3
+              - Fn::Split:
+                  - '"'
+                  - Fn::GetAtt:
+                      - BastionWaitCondition
+                      - Data
+  CapigSetupURL:
+    Description: Please continue setup for Conversions API Gateway
+    Value:
+      Fn::Join:
+        - ""
+        - - "Use the following url to continue with Conversions API Gateway setup: "
+          - Fn::GetAtt:
+              - CapigDistributionDD131D32
+              - DomainName
+          - /auth/create-system-admin/?invite=
+          - Fn::GetAtt:
+              - ConversionsApiGatewayBastionIamRole
+              - RoleId
+
diff --git a/tests/data/templates/crawler-cfn.yml b/tests/data/templates/crawler-cfn.yml
new file mode 100644
index 0000000..ad40111
--- /dev/null
+++ b/tests/data/templates/crawler-cfn.yml
@@ -0,0 +1,283 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+
+  AWSCURDatabase:
+    Type: 'AWS::Glue::Database'
+    Properties:
+      DatabaseInput:
+        Name: 'athenacurcfn_report_name'
+      CatalogId: !Ref AWS::AccountId
+
+
+
+  AWSCURCrawlerComponentFunction:
+    Type: 'AWS::IAM::Role'
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: 2012-10-17
+        Statement:
+          - Effect: Allow
+            Principal:
+              Service:
+                - glue.amazonaws.com
+            Action:
+              - 'sts:AssumeRole'
+      Path: /
+      ManagedPolicyArns:
+        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSGlueServiceRole'
+      Policies:
+        - PolicyName: AWSCURCrawlerComponentFunction
+          PolicyDocument:
+            Version: 2012-10-17
+            Statement:
+              - Effect: Allow
+                Action:
+                  - 'logs:CreateLogGroup'
+                  - 'logs:CreateLogStream'
+                  - 'logs:PutLogEvents'
+                Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*'
+              - Effect: Allow
+                Action:
+                  - 'glue:UpdateDatabase'
+                  - 'glue:UpdatePartition'
+                  - 'glue:CreateTable'
+                  - 'glue:UpdateTable'
+                  - 'glue:ImportCatalogToGlue'
+                Resource: '*'
+              - Effect: Allow
+                Action:
+                  - 's3:GetObject'
+                  - 's3:PutObject'
+                Resource: !Sub 'arn:${AWS::Partition}:s3:::report-bucket/cur/report-name/report-name*'
+        - PolicyName: AWSCURKMSDecryption
+          PolicyDocument:
+            Version: 2012-10-17
+            Statement:
+              - Effect: Allow
+                Action:
+                  - 'kms:Decrypt'
+                Resource: '*'
+
+
+  AWSCURCrawlerLambdaExecutor:
+    Type: 'AWS::IAM::Role'
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: 2012-10-17
+        Statement:
+          - Effect: Allow
+            Principal:
+              Service:
+                - lambda.amazonaws.com
+            Action:
+              - 'sts:AssumeRole'
+      Path: /
+      Policies:
+        - PolicyName: AWSCURCrawlerLambdaExecutor
+          PolicyDocument:
+            Version: 2012-10-17
+            Statement:
+              - Effect: Allow
+                Action:
+                  - 'logs:CreateLogGroup'
+                  - 'logs:CreateLogStream'
+                  - 'logs:PutLogEvents'
+                Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*'
+              - Effect: Allow
+                Action:
+                  - 'glue:StartCrawler'
+                Resource: '*'
+
+
+  AWSCURCrawler:
+    Type: 'AWS::Glue::Crawler'
+    DependsOn:
+      - AWSCURDatabase
+      - AWSCURCrawlerComponentFunction
+    Properties:
+      Name: AWSCURCrawler-report-name
+      Description: A recurring crawler that keeps your CUR table in Athena up-to-date.
+      Role: !GetAtt AWSCURCrawlerComponentFunction.Arn
+      DatabaseName: !Ref AWSCURDatabase
+      Targets:
+        S3Targets:
+          - Path: 's3://report-bucket/cur/report-name/report-name'
+            Exclusions:
+              - '**.json'
+              - '**.yml'
+              - '**.sql'
+              - '**.csv'
+              - '**.gz'
+              - '**.zip'
+      SchemaChangePolicy:
+        UpdateBehavior: UPDATE_IN_DATABASE
+        DeleteBehavior: DELETE_FROM_DATABASE
+
+
+  AWSCURInitializer:
+    Type: 'AWS::Lambda::Function'
+    DependsOn: AWSCURCrawler
+    Properties:
+      Code:
+        ZipFile: >
+          const AWS = require('aws-sdk');
+          const response = require('./cfn-response');
+          exports.handler = function(event, context, callback) {
+            if (event.RequestType === 'Delete') {
+              response.send(event, context, response.SUCCESS);
+            } else {
+              const glue = new AWS.Glue();
+              glue.startCrawler({ Name: 'AWSCURCrawler-report-name' }, function(err, data) {
+                if (err) {
+                  const responseData = JSON.parse(this.httpResponse.body);
+                  if (responseData['__type'] == 'CrawlerRunningException') {
+                    callback(null, responseData.Message);
+                  } else {
+                    const responseString = JSON.stringify(responseData);
+                    if (event.ResponseURL) {
+                      response.send(event, context, response.FAILED,{ msg: responseString });
+                    } else {
+                      callback(responseString);
+                    }
+                  }
+                }
+                else {
+                  if (event.ResponseURL) {
+                    response.send(event, context, response.SUCCESS);
+                  } else {
+                    callback(null, response.SUCCESS);
+                  }
+                }
+              });
+            }
+          };
+      Handler: 'index.handler'
+      Timeout: 30
+      Runtime: nodejs16.x
+      ReservedConcurrentExecutions: 1
+      Role: !GetAtt AWSCURCrawlerLambdaExecutor.Arn
+
+
+  AWSStartCURCrawler:
+    Type: 'Custom::AWSStartCURCrawler'
+    Properties:
+      ServiceToken: !GetAtt AWSCURInitializer.Arn
+
+
+  AWSS3CUREventLambdaPermission:
+    Type: AWS::Lambda::Permission
+    Properties:
+      Action: 'lambda:InvokeFunction'
+      FunctionName: !GetAtt AWSCURInitializer.Arn
+      Principal: 's3.amazonaws.com'
+      SourceAccount: !Ref AWS::AccountId
+      SourceArn: !Sub 'arn:${AWS::Partition}:s3:::report-bucket'
+
+
+  AWSS3CURLambdaExecutor:
+    Type: 'AWS::IAM::Role'
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: 2012-10-17
+        Statement:
+          - Effect: Allow
+            Principal:
+              Service:
+                - lambda.amazonaws.com
+            Action:
+              - 'sts:AssumeRole'
+      Path: /
+      Policies:
+        - PolicyName: AWSS3CURLambdaExecutor
+          PolicyDocument:
+            Version: 2012-10-17
+            Statement:
+              - Effect: Allow
+                Action:
+                  - 'logs:CreateLogGroup'
+                  - 'logs:CreateLogStream'
+                  - 'logs:PutLogEvents'
+                Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*'
+              - Effect: Allow
+                Action:
+                  - 's3:PutBucketNotification'
+                Resource: !Sub 'arn:${AWS::Partition}:s3:::report-bucket'
+
+
+  AWSS3CURNotification:
+    Type: 'AWS::Lambda::Function'
+    DependsOn:
+    - AWSCURInitializer
+    - AWSS3CUREventLambdaPermission
+    - AWSS3CURLambdaExecutor
+    Properties:
+      Code:
+        ZipFile: >
+          const AWS = require('aws-sdk');
+          const response = require('./cfn-response');
+          exports.handler = function(event, context, callback) {
+            const s3 = new AWS.S3();
+            const putConfigRequest = function(notificationConfiguration) {
+              return new Promise(function(resolve, reject) {
+                s3.putBucketNotificationConfiguration({
+                  Bucket: event.ResourceProperties.BucketName,
+                  NotificationConfiguration: notificationConfiguration
+                }, function(err, data) {
+                  if (err) reject({ msg: this.httpResponse.body.toString(), error: err, data: data });
+                  else resolve(data);
+                });
+              });
+            };
+            const newNotificationConfig = {};
+            if (event.RequestType !== 'Delete') {
+              newNotificationConfig.LambdaFunctionConfigurations = [{
+                Events: [ 's3:ObjectCreated:*' ],
+                LambdaFunctionArn: event.ResourceProperties.TargetLambdaArn || 'missing arn',
+                Filter: { Key: { FilterRules: [ { Name: 'prefix', Value: event.ResourceProperties.ReportKey } ] } }
+              }];
+            }
+            putConfigRequest(newNotificationConfig).then(function(result) {
+              response.send(event, context, response.SUCCESS, result);
+              callback(null, result);
+            }).catch(function(error) {
+              response.send(event, context, response.FAILED, error);
+              console.log(error);
+              callback(error);
+            });
+          };
+      Handler: 'index.handler'
+      Timeout: 30
+      Runtime: nodejs16.x
+      ReservedConcurrentExecutions: 1
+      Role: !GetAtt AWSS3CURLambdaExecutor.Arn
+
+
+  AWSPutS3CURNotification:
+    Type: 'Custom::AWSPutS3CURNotification'
+    Properties:
+      ServiceToken: !GetAtt AWSS3CURNotification.Arn
+      TargetLambdaArn: !GetAtt AWSCURInitializer.Arn
+      BucketName: 'report-bucket'
+      ReportKey: 'cur/report-name/report-name'
+
+
+  AWSCURReportStatusTable:
+    Type: 'AWS::Glue::Table'
+    DependsOn: AWSCURDatabase
+    Properties:
+      DatabaseName: athenacurcfn_report_name
+      CatalogId: !Ref AWS::AccountId
+      TableInput:
+        Name: 'cost_and_usage_data_status'
+        TableType: 'EXTERNAL_TABLE'
+        StorageDescriptor:
+          Columns:
+            - Name: status
+              Type: 'string'
+          InputFormat: 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
+          OutputFormat: 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
+          SerdeInfo:
+            SerializationLibrary: 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
+          Location: 's3://report-bucket/cur/report-name/cost_and_usage_data_status/'
+
+
diff --git a/tests/data/templates/debug.yaml b/tests/data/templates/debug.yaml
new file mode 100644
index 0000000..a1eb005
--- /dev/null
+++ b/tests/data/templates/debug.yaml
@@ -0,0 +1,2099 @@
+---
+AWSTemplateFormatVersion: '2010-09-09'
+Transform: 'AWS::Serverless-2016-10-31'
+
+Description: My Dev Portal Stack
+Metadata:
+  cfn-lint:
+    config:
+      ignore_checks:
+        # There's circular dependencies between the API + backend lambda and API + IAM execution role for
+        # that won't get fixed anytime remotely soon.
+        - E3004
+        # Not useful at all.
+        - I1022
+
+  AWS::CloudFormation::Interface:
+    ParameterGroups:
+      -
+        Label:
+          default: "Dev Portal Content Configuration"
+        Parameters:
+          - ArtifactsS3BucketName
+          - DevPortalSiteS3BucketName
+          - StaticAssetRebuildToken
+          - StaticAssetRebuildMode
+      -
+        Label:
+          default: "Dev Portal Customer Configuration"
+        Parameters:
+          - CognitoIdentityPoolName
+          - DevPortalCustomersTableName
+          - AccountRegistrationMode
+      # Marketplace support is currently broken
+      # -
+      #   Label:
+      #     default: "Subscription Notification Configuration"
+      #   Parameters:
+      #     - MarketplaceSubscriptionTopicProductCode
+      -
+        Label:
+          default: "Custom Domain Configuration"
+        Parameters:
+          - CustomDomainName
+          - CustomDomainNameAcmCertArn
+          - UseRoute53Nameservers
+          - CognitoDomainNameOrPrefix
+          # - CognitoDomainAcmCertArn
+      -
+        Label:
+          default: "Dev Portal Customer Feedback Configuration"
+        Parameters:
+          - DevPortalAdminEmail
+          - DevPortalFeedbackTableName
+
+Parameters:
+  ArtifactsS3BucketName:
+    Type: String
+    Description: The S3 bucket in which the Open API documents are stored. Bucket
+      names are globally unique, so you must change this.
+
+  DevPortalSiteS3BucketName:
+    Type: String
+    Description: The S3 bucket in which the web application code is stored. Bucket
+      names are globally unique, so you must change this.
+
+  DevPortalCustomersTableName:
+    Type: String
+    Description: The name of the DynamoDB Customers table.
+    Default: 'DevPortalCustomers'
+
+  DevPortalPreLoginAccountsTableName:
+    Type: String
+    Description: The name of the DynamoDB PreLoginAccounts table.
+    Default: 'DevPortalPreLoginAccounts'
+
+  DevPortalAdminEmail:
+    Type: String
+    Description: The email address where user submitted feedback notifications get sent.
+    Default: ''
+
+  DevPortalFeedbackTableName:
+    Type: String
+    Description: The name of the DynamoDB table storing feedback submitted by users.
+    Default: 'DevPortalFeedback'
+
+  StaticAssetRebuildToken:
+    Type: String
+    Description: Provide a token different from the last deployment's token to re-upload the dev portal site's static assets. You can provide a timestamp or GUID on each deployment to always re-upload the assets.
+    Default: 'defaultRebuildToken'
+
+  EdgeLambdaRebuildToken:
+    Type: String
+    Description: Provide a token different from the last deployment's token to update the edge lambda. You can provide a timestamp or GUID on each deployment to always update it.
+    Default: 'defaultRebuildToken'
+
+  StaticAssetRebuildMode:
+    Type: String
+    Description: By default, a static asset rebuild doesn't overwrite custom-content. Provide the value `overwrite-content` to replace the custom-content with your local version. Don't do this unless you know what you're doing -- all custom changes in your s3 bucket will be lost. 
+    Default: ''
+    AllowedValues:
+      - 'overwrite-content'
+      - ''
+    ConstraintDescription: Malformed input - Parameter StaticAssetRebuildMode value must be either 'overwrite-content' or left blank.
+
+  # Marketplace support is currently broken
+  # MarketplaceSubscriptionTopicProductCode:
+  #   Type: String
+  #   Description: The marketplace SNS topic suffix for subscription/unsubscription events
+  #   Default: DevPortalMarketplaceSubscriptionTopic
+
+  CognitoIdentityPoolName:
+    Type: String
+    Description: The name for your Cognito Identity Pool.
+    Default: 'DevPortalIdentityPool'
+
+  CognitoDomainNameOrPrefix:
+    Type: String
+    Description: The Domain Name (or Prefix) at which your Cognito Hosted UI is located. This should be regionally unique.
+
+  # CognitoDomainAcmCertArn:
+  #   Type: String
+  #   Description: Doesn't yet do anything. Oh well.
+  #   Default: ''
+
+  CustomDomainName:
+    Type: String
+    Description: Optionally provide a custom domain name associated with an ACM cert to create a developer portal at that domain name (provide with the format foo.bar.net). Leave blank to create a developer portal without a custom domain name. Standing up a developer portal stack with a custom domain name will take significantly longer than without.
+    Default: ''
+
+  CustomDomainNameAcmCertArn:
+    Type: String
+    Description: If you provided a domain name associated with an acm cert, then you must also specify here the acm cert's arn. Leave this blank to create a developer portal without a custom domain name.
+    Default: ''
+
+  UseRoute53Nameservers:
+    Type: String
+    Description: Only applicable if creating a custom domain name for your dev portal. Defaults to false, and you'll need to provide your own nameserver hosting. If set to true, a Route53 HostedZone and RecordSet are created for you.
+    Default: 'false'
+    AllowedValues: 
+      - 'false'
+      - 'true'
+    ConstraintDescription: Malformed input - Parameter UseRoute53Nameservers value must be either 'true' or 'false'
+
+  DevelopmentMode:
+    Type: String
+    Description: Enabling this weakens security features (OAI, SSL, site S3 bucket with public read ACLs, Cognito callback verification, CORS, etc.) for easier development. It also breaks frontend routing (except to /index.html), including deep linking and page refresh. Do not enable this in production! Additionally, do not update a stack that was previously in development mode to be a production stack; instead, make a new stack that has never been in development mode.
+    Default: 'false'
+    AllowedValues: 
+      - 'false'
+      - 'true'
+    ConstraintDescription: Malformed input - Parameter DevelopmentMode value must be either 'true' or 'false'
+
+  LocalDevelopmentMode:
+    Type: String
+    Description: Enabling this disables CORS for easier local development. Do not enable this in production! Additionally, do not update a stack that was previously in development mode to be a production stack; instead, make a new stack that has never been in development mode.
+    Default: 'false'
+    AllowedValues: 
+      - 'false'
+      - 'true'
+    ConstraintDescription: Malformed input - Parameter LocalDevelopmentMode value must be either 'true' or 'false'
+  
+  AccountRegistrationMode:
+    Type: String
+    # Description: Methods allowed for account registration. In 'open' mode, any user may register for an account. In 'request' mode, any user may request an account, but an Admin must approve the request in order for the account to perform any privileged actions (like subscribing to an API). In 'invite' mode, users cannot register or request an account; instead, an Admin must send an invite for the user to accept.
+    Description: Methods allowed for account registration. In 'open' mode, any user may register for an account. In 'invite' mode, users cannot register or request an account; instead, an Admin must send an invite for the user to accept.
+    Default: 'open'
+    AllowedValues:
+      - 'open'
+      # - 'request'
+      - 'invite'
+
+Conditions:
+  UseCustomDomainName: !And [!And [!Not [!Equals [!Ref CustomDomainName, '']], !Not [!Equals [!Ref CustomDomainNameAcmCertArn, '']]], !Condition NotDevelopmentMode]
+  NoCustomDomainName: !And [!Not [ !Condition UseCustomDomainName ], !Condition NotDevelopmentMode]
+  UseRoute53: !And [!Equals [!Ref UseRoute53Nameservers, 'true'], !Condition UseCustomDomainName]
+  EnableFeedbackSubmission: !Not [!Equals [!Ref DevPortalAdminEmail, '']]
+  LocalDevelopmentMode: !Or [!Equals [!Ref DevelopmentMode, 'true'], !Equals [!Ref LocalDevelopmentMode, 'true']]
+  DevelopmentMode: !Equals [!Ref DevelopmentMode, 'true']
+  NotDevelopmentMode: !Not [!Condition DevelopmentMode]
+  InUSEastOne: !Equals [!Ref 'AWS::Region', 'us-east-1']
+  InviteAccountRegistrationMode: !Equals [!Ref AccountRegistrationMode, 'invite']
+
+Resources:
+  ApiGatewayApi:
+    Type: AWS::Serverless::Api
+    Properties:
+      DefinitionBody:
+        swagger: 2.0
+        info:
+          title: !Join ['', [!Ref 'AWS::StackName', '-backend-api']]
+          version: 2016-09-02T22:37:24Z
+        basePath: /prod
+        schemes:
+        - https
+        paths:
+          # Version marker - please don't delete (this is how we track version usage)
+          /version/{v4_1_1}:
+            options:
+              consumes:
+              - application/json
+              produces:
+              - application/json
+              responses:
+                '200':
+                  description: 200 response
+                  schema:
+                    $ref: "#/definitions/Empty"
+                  headers:
+                    Access-Control-Allow-Origin:
+                      type: string
+                    Access-Control-Allow-Methods:
+                      type: string
+                    Access-Control-Allow-Headers:
+                      type: string
+              x-amazon-apigateway-integration:
+                responses:
+                  default:
+                    statusCode: 200
+                    responseParameters:
+                      method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
+                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
+                      method.response.header.Access-Control-Allow-Origin: !If [ 'LocalDevelopmentMode', "'*'", !If [
+                        'UseCustomDomainName',
+                        !Join ['', [ "'https://", !Ref CustomDomainName, "'" ]],
+                        !Join ['', [ "'https://", !GetAtt DefaultCloudfrontDistribution.DomainName, "'" ]]
+                      ]]
+                passthroughBehavior: when_no_match
+                requestTemplates:
+                  application/json: "{\"statusCode\": 200}"
+                type: mock
+          /:
+            x-amazon-apigateway-any-method:
+              security:
+              - sigv4: []
+              produces:
+              - application/json
+              responses:
+                '200':
+                  description: 200 response
+                  schema:
+                    $ref: "#/definitions/Empty"
+              x-amazon-apigateway-integration:
+                responses:
+                  default:
+                    statusCode: 200
+                uri: !Join
+                  - ''
+                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function
+                    - :${stageVariables.DevPortalFunctionName}/invocations
+                passthroughBehavior: when_no_match
+                httpMethod: POST
+                type: aws_proxy
+            options:
+              consumes:
+              - application/json
+              produces:
+              - application/json
+              responses:
+                '200':
+                  description: 200 response
+                  schema:
+                    $ref: "#/definitions/Empty"
+                  headers:
+                    Access-Control-Allow-Origin:
+                      type: string
+                    Access-Control-Allow-Methods:
+                      type: string
+                    Access-Control-Allow-Headers:
+                      type: string
+              x-amazon-apigateway-integration:
+                responses:
+                  default:
+                    statusCode: 200
+                    responseParameters:
+                      method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
+                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
+                      method.response.header.Access-Control-Allow-Origin: !If [ 'LocalDevelopmentMode', "'*'", !If [
+                        'UseCustomDomainName',
+                        !Join ['', [ "'https://", !Ref CustomDomainName, "'" ]],
+                        !Join ['', [ "'https://", !GetAtt DefaultCloudfrontDistribution.DomainName, "'" ]]
+                      ]]
+                passthroughBehavior: when_no_match
+                requestTemplates:
+                  application/json: "{\"statusCode\": 200}"
+                type: mock
+          /register:
+            post:
+              security:
+              - sigv4: []
+              produces:
+              - application/json
+              responses: {}
+              x-amazon-apigateway-integration:
+                uri: !Join
+                  - ''
+                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function
+                    - :${stageVariables.DevPortalFunctionName}/invocations
+                httpMethod: POST
+                type: aws_proxy
+            options:
+              consumes:
+              - application/json
+              produces:
+              - application/json
+              responses:
+                '200':
+                  description: 200 response
+                  schema:
+                    $ref: "#/definitions/Empty"
+                  headers:
+                    Access-Control-Allow-Origin:
+                      type: string
+                    Access-Control-Allow-Methods:
+                      type: string
+                    Access-Control-Allow-Headers:
+                      type: string
+              x-amazon-apigateway-integration:
+                responses:
+                  default:
+                    statusCode: 200
+                    responseParameters:
+                      method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
+                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
+                      method.response.header.Access-Control-Allow-Origin: !If [ 'LocalDevelopmentMode', "'*'", !If [
+                        'UseCustomDomainName',
+                        !Join ['', [ "'https://", !Ref CustomDomainName, "'" ]],
+                        !Join ['', [ "'https://", !GetAtt DefaultCloudfrontDistribution.DomainName, "'" ]]
+                      ]]
+                passthroughBehavior: when_no_match
+                requestTemplates:
+                  application/json: "{\"statusCode\": 200}"
+                type: mock
+          # Marketplace support is currently broken
+          # /marketplace-confirm/{usagePlanId}:
+          #   post:
+          #     security:
+          #     - sigv4: []
+          #     produces:
+          #     - application/json
+          #     responses: {}
+          #     x-amazon-apigateway-integration:
+          #       uri: !Join
+          #         - ''
+          #         - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function
+          #           - :${stageVariables.DevPortalFunctionName}/invocations
+          #       httpMethod: POST
+          #       type: aws_proxy
+          #   options:
+          #     consumes:
+          #     - application/json
+          #     produces:
+          #     - application/json
+          #     responses:
+          #       '200':
+          #         description: 200 response
+          #         schema:
+          #           $ref: "#/definitions/Empty"
+          #         headers:
+          #           Access-Control-Allow-Origin:
+          #             type: string
+          #           Access-Control-Allow-Methods:
+          #             type: string
+          #           Access-Control-Allow-Headers:
+          #             type: string
+          #     x-amazon-apigateway-integration:
+          #       responses:
+          #         default:
+          #           statusCode: 200
+          #           responseParameters:
+          #             method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
+          #             method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
+          #             method.response.header.Access-Control-Allow-Origin: !If [ 'LocalDevelopmentMode', "'*'", !If [
+          #               'UseCustomDomainName',
+          #               !Join ['', [ "'https://", !Ref CustomDomainName, "'" ]],
+          #               !Join ['', [ "'https://", !GetAtt DefaultCloudfrontDistribution.DomainName, "'" ]]
+          #             ]]
+          #       passthroughBehavior: when_no_match
+          #       requestTemplates:
+          #         application/json: "{\"statusCode\": 200}"
+          #       type: mock
+          /catalog:
+            get:
+              security:
+              - sigv4: []
+              produces:
+              - application/json
+              responses: {}
+              x-amazon-apigateway-integration:
+                uri: !Join
+                  - ''
+                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function
+                    - :${stageVariables.DevPortalFunctionName}/invocations
+                httpMethod: POST
+                type: aws_proxy
+            options:
+              consumes:
+              - application/json
+              produces:
+              - application/json
+              responses:
+                '200':
+                  description: 200 response
+                  schema:
+                    $ref: "#/definitions/Empty"
+                  headers:
+                    Access-Control-Allow-Origin:
+                      type: string
+                    Access-Control-Allow-Methods:
+                      type: string
+                    Access-Control-Allow-Headers:
+                      type: string
+              x-amazon-apigateway-integration:
+                responses:
+                  default:
+                    statusCode: 200
+                    responseParameters:
+                      method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
+                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
+                      method.response.header.Access-Control-Allow-Origin: !If [ 'LocalDevelopmentMode', "'*'", !If [
+                        'UseCustomDomainName',
+                        !Join ['', [ "'https://", !Ref CustomDomainName, "'" ]],
+                        !Join ['', [ "'https://", !GetAtt DefaultCloudfrontDistribution.DomainName, "'" ]]
+                      ]]
+                passthroughBehavior: when_no_match
+                requestTemplates:
+                  application/json: "{\"statusCode\": 200}"
+                type: mock
+          /feedback:
+            get:
+              security:
+              - sigv4: []
+              produces:
+              - application/json
+              responses: {}
+              x-amazon-apigateway-integration:
+                uri: !Join
+                  - ''
+                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function
+                    - :${stageVariables.DevPortalFunctionName}/invocations
+                httpMethod: POST
+                type: aws_proxy
+            post:
+              security:
+              - sigv4: []
+              produces:
+              - application/json
+              responses: {}
+              x-amazon-apigateway-integration:
+                uri: !Join
+                  - ''
+                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function
+                    - :${stageVariables.DevPortalFunctionName}/invocations
+                httpMethod: POST
+                type: aws_proxy
+            delete:
+              security:
+              - sigv4: []
+              produces:
+              - application/json
+              responses: {}
+              x-amazon-apigateway-integration:
+                uri: !Join
+                  - ''
+                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function
+                    - :${stageVariables.DevPortalFunctionName}/invocations
+                httpMethod: POST
+                type: aws_proxy
+            options:
+              consumes:
+              - application/json
+              produces:
+              - application/json
+              responses:
+                '200':
+                  description: 200 response
+                  schema:
+                    $ref: "#/definitions/Empty"
+                  headers:
+                    Access-Control-Allow-Origin:
+                      type: string
+                    Access-Control-Allow-Methods:
+                      type: string
+                    Access-Control-Allow-Headers:
+                      type: string
+              x-amazon-apigateway-integration:
+                responses:
+                  default:
+                    statusCode: 200
+                    responseParameters:
+                      method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
+                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
+                      method.response.header.Access-Control-Allow-Origin: !If [ 'LocalDevelopmentMode', "'*'", !If [
+                        'UseCustomDomainName',
+                        !Join ['', [ "'https://", !Ref CustomDomainName, "'" ]],
+                        !Join ['', [ "'https://", !GetAtt DefaultCloudfrontDistribution.DomainName, "'" ]]
+                      ]]
+                passthroughBehavior: when_no_match
+                requestTemplates:
+                  application/json: "{\"statusCode\": 200}"
+                type: mock
+          /admin/catalog/visibility:
+            get:
+              security:
+              - sigv4: []
+              produces:
+              - application/json
+              responses: {}
+              x-amazon-apigateway-integration:
+                uri: !Join
+                  - ''
+                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function
+                    - :${stageVariables.DevPortalFunctionName}/invocations
+                httpMethod: POST
+                type: aws_proxy
+            post:
+              security:
+              - sigv4: []
+              produces:
+              - application/json
+              responses: {}
+              x-amazon-apigateway-integration:
+                uri: !Join
+                  - ''
+                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function
+                    - :${stageVariables.DevPortalFunctionName}/invocations
+                httpMethod: POST
+                type: aws_proxy
+            options:
+              consumes:
+              - application/json
+              produces:
+              - application/json
+              responses:
+                '200':
+                  description: 200 response
+                  schema:
+                    $ref: "#/definitions/Empty"
+                  headers:
+                    Access-Control-Allow-Origin:
+                      type: string
+                    Access-Control-Allow-Methods:
+                      type: string
+                    Access-Control-Allow-Headers:
+                      type: string
+              x-amazon-apigateway-integration:
+                responses:
+                  default:
+                    statusCode: 200
+                    responseParameters:
+                      method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
+                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
+                      method.response.header.Access-Control-Allow-Origin: !If [ 'LocalDevelopmentMode', "'*'", !If [
+                        'UseCustomDomainName',
+                        !Join ['', [ "'https://", !Ref CustomDomainName, "'" ]],
+                        !Join ['', [ "'https://", !GetAtt DefaultCloudfrontDistribution.DomainName, "'" ]]
+                      ]]
+                passthroughBehavior: when_no_match
+                requestTemplates:
+                  application/json: "{\"statusCode\": 200}"
+                type: mock
+          /{proxy+}:
+            x-amazon-apigateway-any-method:
+              security:
+              - sigv4: []
+              produces:
+              - application/json
+              parameters:
+              - name: proxy
+                in: path
+                required: true
+                type: string
+              responses: {}
+              x-amazon-apigateway-integration:
+                uri: !Join
+                  - ''
+                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function
+                    - :${stageVariables.DevPortalFunctionName}/invocations
+                httpMethod: POST
+                type: aws_proxy
+            options:
+              consumes:
+              - application/json
+              produces:
+              - application/json
+              responses:
+                '200':
+                  description: 200 response
+                  schema:
+                    $ref: "#/definitions/Empty"
+                  headers:
+                    Access-Control-Allow-Origin:
+                      type: string
+                    Access-Control-Allow-Methods:
+                      type: string
+                    Access-Control-Allow-Headers:
+                      type: string
+              x-amazon-apigateway-integration:
+                responses:
+                  default:
+                    statusCode: 200
+                    responseParameters:
+                      method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
+                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
+                      method.response.header.Access-Control-Allow-Origin: !If [ 'LocalDevelopmentMode', "'*'", !If [
+                        'UseCustomDomainName',
+                        !Join ['', [ "'https://", !Ref CustomDomainName, "'" ]],
+                        !Join ['', [ "'https://", !GetAtt DefaultCloudfrontDistribution.DomainName, "'" ]]
+                      ]]
+                passthroughBehavior: when_no_match
+                requestTemplates:
+                  application/json: "{\"statusCode\": 200}"
+                type: mock
+        securityDefinitions:
+          sigv4:
+            type: "apiKey"
+            name: "Authorization"
+            in: "header"
+            x-amazon-apigateway-authtype: "awsSigv4"
+        definitions:
+          Empty:
+            type: object
+            title: Empty Schema
+
+      StageName: prod
+      Variables:
+        DevPortalFunctionName: !Ref DevPortalLambdaFunction
+
+  DevPortalSiteS3Bucket:
+    Type: AWS::S3::Bucket
+    Properties:
+      BucketName: !Ref DevPortalSiteS3BucketName
+      PublicAccessBlockConfiguration:
+        BlockPublicAcls: true
+        BlockPublicPolicy: true
+        IgnorePublicAcls: true
+        RestrictPublicBuckets: true
+
+  DevPortalSiteS3BucketPolicy:
+    Type: 'AWS::S3::BucketPolicy'
+    Condition: 'NotDevelopmentMode'
+    Properties:
+      Bucket: !Ref DevPortalSiteS3Bucket
+      PolicyDocument:
+        Statement:
+          - Action: 's3:GetObject'
+            Effect: Allow
+            Resource: !Sub 'arn:aws:s3:::${DevPortalSiteS3Bucket}/*'
+            Principal:
+              AWS: !Sub >-
+                arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity
+                ${CloudFrontOriginAccessIdentity}
+
+  ArtifactsS3Bucket:
+    Type: AWS::S3::Bucket
+    Properties:
+      BucketName: !Ref ArtifactsS3BucketName
+      PublicAccessBlockConfiguration:
+        BlockPublicAcls: true
+        BlockPublicPolicy: true
+        IgnorePublicAcls: true
+        RestrictPublicBuckets: true
+      NotificationConfiguration:
+        LambdaConfigurations:
+          -
+            Function: !GetAtt CatalogUpdaterLambdaFunction.Arn
+            Event: "s3:ObjectCreated:*"
+            Filter:
+              S3Key:
+                Rules:
+                  -
+                    Name: prefix
+                    Value: "catalog/"
+          -
+            Function: !GetAtt CatalogUpdaterLambdaFunction.Arn
+            Event: "s3:ObjectRemoved:*"
+            Filter:
+              S3Key:
+                Rules:
+                  -
+                    Name: prefix
+                    Value: "catalog/"
+
+  CustomersTable:
+    Type: AWS::DynamoDB::Table
+    DeletionPolicy: Retain
+    UpdateReplacePolicy: Retain
+    Properties:
+      TableName: !Ref DevPortalCustomersTableName
+      AttributeDefinitions:
+      - AttributeName: Id
+        AttributeType: S
+      # Marketplace support is currently broken
+      # - AttributeName: MarketplaceCustomerId
+      #   AttributeType: S
+      KeySchema:
+      - AttributeName: Id
+        KeyType: HASH
+      ProvisionedThroughput:
+        ReadCapacityUnits: 5
+        WriteCapacityUnits: 5
+      # Marketplace support is currently broken
+      # GlobalSecondaryIndexes:
+      # - IndexName: MarketplaceCustomerIdIndex
+      #   KeySchema:
+      #   - AttributeName: MarketplaceCustomerId
+      #     KeyType: HASH
+      #   Projection:
+      #     NonKeyAttributes:
+      #     - ApiKeyId
+      #     ProjectionType: INCLUDE
+      #   ProvisionedThroughput:
+      #     ReadCapacityUnits: 5
+      #     WriteCapacityUnits: 5
+
+  PreLoginAccountsTable:
+    Type: AWS::DynamoDB::Table
+    DeletionPolicy: Retain
+    UpdateReplacePolicy: Retain
+    Properties:
+      TableName: !Ref DevPortalPreLoginAccountsTableName
+      AttributeDefinitions:
+      - AttributeName: UserId
+        AttributeType: S
+      KeySchema:
+      - AttributeName: UserId
+        KeyType: HASH
+      ProvisionedThroughput:
+        ReadCapacityUnits: 5
+        WriteCapacityUnits: 5
+
+  FeedbackTable:
+    Type: AWS::DynamoDB::Table
+    Condition: EnableFeedbackSubmission
+    DeletionPolicy: Retain
+    UpdateReplacePolicy: Retain
+    Properties:
+      TableName: !Ref DevPortalFeedbackTableName
+      AttributeDefinitions:
+      - AttributeName: Id
+        AttributeType: S
+      KeySchema:
+      - AttributeName: Id
+        KeyType: HASH
+      ProvisionedThroughput:
+        ReadCapacityUnits: 5
+        WriteCapacityUnits: 5
+      GlobalSecondaryIndexes:
+      - IndexName: FeedbackIdIndex
+        KeySchema:
+        - AttributeName: Id
+          KeyType: HASH
+        Projection:
+          ProjectionType: KEYS_ONLY
+        ProvisionedThroughput:
+          ReadCapacityUnits: 5
+          WriteCapacityUnits: 5
+
+  FeedbackSubmittedSNSTopic:
+    Type: AWS::SNS::Topic
+    Condition: EnableFeedbackSubmission
+    Properties:
+      Subscription:
+      - Endpoint: !Ref DevPortalAdminEmail
+        Protocol: email
+
+  BackendLambdaExecutionRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+          Effect: Allow
+          Principal:
+            Service: lambda.amazonaws.com
+          Action: sts:AssumeRole
+      Path: '/'
+      Policies:
+      - PolicyName: root
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+            - logs:CreateLogGroup
+            - logs:CreateLogStream
+            - logs:PutLogEvents
+            Resource: arn:aws:logs:*:*:*
+          - Effect: Allow
+            Action:
+            - apigateway:*
+            Resource: '*'
+          - Effect: Allow
+            Action:
+            - s3:GetObject
+            - s3:DeleteObject
+            - s3:PutObject
+            Resource: !Join
+              - ''
+              - - 'arn:aws:s3:::'
+                - !Ref ArtifactsS3BucketName
+                - '/*'
+          - Effect: Allow
+            Action:
+            - apigateway:GET
+            Resource: !Join
+              - ''
+              - - 'arn:aws:apigateway:'
+                - !Ref 'AWS::Region'
+                - '::/restapis/'
+                - !Ref ApiGatewayApi
+                - '/*/exports/*'
+          # Marketplace support is currently broken
+          # - Effect: Allow
+          #   Action:
+          #   - aws-marketplace:ResolveCustomer
+          #   Resource: '*'
+          - Effect: Allow
+            Action:
+            - dynamodb:GetItem
+            - dynamodb:Query
+            - dynamodb:Scan
+            - dynamodb:PutItem
+            - dynamodb:UpdateItem
+            - dynamodb:DeleteItem
+            Resource: !Join
+              - ''
+              - - 'arn:aws:dynamodb:'
+                - !Ref 'AWS::Region'
+                - ':'
+                - !Ref 'AWS::AccountId'
+                - :table/
+                - !Ref CustomersTable
+          - Effect: Allow
+            Action:
+            - dynamodb:GetItem
+            - dynamodb:Query
+            - dynamodb:Scan
+            - dynamodb:PutItem
+            - dynamodb:UpdateItem
+            - dynamodb:DeleteItem
+            Resource: !GetAtt PreLoginAccountsTable.Arn
+          # Marketplace support is currently broken
+          # - Effect: Allow
+          #   Action:
+          #   - dynamodb:Query
+          #   Resource: !Join
+          #     - ''
+          #     - - 'arn:aws:dynamodb:'
+          #       - !Ref 'AWS::Region'
+          #       - ':'
+          #       - !Ref 'AWS::AccountId'
+          #       - :table/
+          #       - !Ref CustomersTable
+          #       - /index/MarketplaceCustomerIdIndex
+          - Effect: Allow
+            Action:
+            - lambda:InvokeFunction
+            Resource: !GetAtt CatalogUpdaterLambdaFunction.Arn
+          - !If
+            - EnableFeedbackSubmission
+            - Effect: Allow
+              Action:
+              - dynamodb:Scan
+              - dynamodb:PutItem
+              - dynamodb:UpdateItem
+              - dynamodb:DeleteItem
+              Resource: !Join
+                - ''
+                - - 'arn:aws:dynamodb:'
+                  - !Ref 'AWS::Region'
+                  - ':'
+                  - !Ref 'AWS::AccountId'
+                  - :table/
+                  - !Ref FeedbackTable
+            - !Ref 'AWS::NoValue'
+          - !If
+            - EnableFeedbackSubmission
+            - Effect: Allow
+              Action:
+              - sns:Publish
+              Resource: !Ref FeedbackSubmittedSNSTopic
+            - !Ref 'AWS::NoValue'
+          - Effect: Allow
+            Action:
+              - cognito-idp:ListUsers
+              - cognito-idp:ListUsersInGroup
+              - cognito-idp:AdminAddUserToGroup
+              - cognito-idp:AdminCreateUser
+              - cognito-idp:AdminDeleteUser
+              - cognito-idp:AdminGetUser
+              - cognito-idp:AdminListGroupsForUser
+            Resource: !GetAtt CognitoUserPool.Arn
+
+  CognitoPreSignupTriggerExecutionRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+          Effect: Allow
+          Principal:
+            Service: lambda.amazonaws.com
+          Action: sts:AssumeRole
+      Path: '/'
+      Policies:
+      - PolicyName: root
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+            - logs:CreateLogGroup
+            - logs:CreateLogStream
+            - logs:PutLogEvents
+            Resource: arn:aws:logs:*:*:*
+
+  CognitoPostConfirmationTriggerExecutionRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+          Effect: Allow
+          Principal:
+            Service: lambda.amazonaws.com
+          Action: sts:AssumeRole
+      Path: '/'
+      Policies:
+      - PolicyName: root
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+            - logs:CreateLogGroup
+            - logs:CreateLogStream
+            - logs:PutLogEvents
+            Resource: arn:aws:logs:*:*:*
+          - Effect: Allow
+            Action:
+              - dynamodb:PutItem
+            Resource: !GetAtt PreLoginAccountsTable.Arn
+          - Effect: Allow
+            Action:
+              - cognito-idp:AdminAddUserToGroup
+            Resource: !GetAtt CognitoUserPool.Arn
+
+  CognitoPostAuthenticationTriggerExecutionRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+          Effect: Allow
+          Principal:
+            Service: lambda.amazonaws.com
+          Action: sts:AssumeRole
+      Path: '/'
+      Policies:
+      - PolicyName: root
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+            - logs:CreateLogGroup
+            - logs:CreateLogStream
+            - logs:PutLogEvents
+            Resource: arn:aws:logs:*:*:*
+          - Effect: Allow
+            Action:
+              - dynamodb:Scan
+              - dynamodb:PutItem
+            Resource: !GetAtt CustomersTable.Arn
+          - Effect: Allow
+            Action:
+              - dynamodb:GetItem
+              - dynamodb:PutItem
+            Resource: !GetAtt PreLoginAccountsTable.Arn
+          - Effect: Allow
+            Action:
+              - cognito-idp:AdminAddUserToGroup
+            Resource: !GetAtt CognitoUserPool.Arn
+
+  CatalogUpdaterLambdaExecutionRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+          Effect: Allow
+          Principal:
+            Service: lambda.amazonaws.com
+          Action: sts:AssumeRole
+      Path: '/'
+      Policies:
+      - PolicyName: root
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+            - logs:CreateLogGroup
+            - logs:CreateLogStream
+            - logs:PutLogEvents
+            Resource: arn:aws:logs:*:*:*
+          - Effect: Allow
+            Action:
+            - s3:ListBucket
+            Resource: 
+              - !Join
+                - ''
+                - - 'arn:aws:s3:::'
+                  - !Ref ArtifactsS3BucketName
+              - !Join
+                - ''
+                - - 'arn:aws:s3:::'
+                  - !Ref DevPortalSiteS3BucketName
+          - Effect: Allow
+            Action:
+            - s3:GetObject
+            - s3:PutObject
+            Resource: !Join
+              - ''
+              - - 'arn:aws:s3:::'
+                - !Ref ArtifactsS3BucketName
+                - '/*'
+          - Effect: Allow
+            Action: # scope this down
+            - apigateway:*
+            Resource: '*'
+
+  AssetUploaderLambdaExecutionRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+          Effect: Allow
+          Principal:
+            Service: lambda.amazonaws.com
+          Action: sts:AssumeRole
+      Path: '/'
+      Policies:
+      - PolicyName: root
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+            - logs:CreateLogGroup
+            - logs:CreateLogStream
+            - logs:PutLogEvents
+            Resource: arn:aws:logs:*:*:*
+          - Effect: Allow
+            Action:
+            - s3:PutObject
+            Resource: !Join
+              - ''
+              - - 'arn:aws:s3:::'
+                - !Ref DevPortalSiteS3BucketName
+                - '/*'
+          - Effect: Allow
+            Action:
+            - s3:PutObjectAcl
+            Resource: !Join
+              - ''
+              - - 'arn:aws:s3:::'
+                - !Ref DevPortalSiteS3BucketName
+                - '/*'
+          - Effect: Allow
+            Action:
+            - s3:ListBucket
+            Resource: !Join
+              - ''
+              - - 'arn:aws:s3:::'
+                - !Ref DevPortalSiteS3BucketName
+          - Effect: Allow
+            Action:
+            - s3:DeleteObject
+            Resource: !Join
+              - ''
+              - - 'arn:aws:s3:::'
+                - !Ref DevPortalSiteS3BucketName
+                - '/*'
+          - Effect: Allow
+            Action:
+            - s3:DeleteObject
+            Resource: !Join
+              - ''
+              - - 'arn:aws:s3:::'
+                - !Ref ArtifactsS3BucketName
+                - '/*'
+          - Effect: Allow
+            Action:
+            - s3:ListBucket
+            Resource: !Join
+              - ''
+              - - 'arn:aws:s3:::'
+                - !Ref ArtifactsS3BucketName
+          - Effect: Allow
+            Action:
+            - s3:GetObject
+            - s3:PutObject
+            Resource: !Join
+              - ''
+              - - 'arn:aws:s3:::'
+                - !Ref ArtifactsS3BucketName
+                - '/*'
+
+  # Marketplace support is currently broken
+  # ApiGatewayMarketplaceMeteringRole:
+  #   Type: AWS::IAM::Role
+  #   Properties:
+  #     RoleName: !Join
+  #       - ''
+  #       - - !Ref AWS::StackName
+  #         - 'MeteringRole'
+  #     AssumeRolePolicyDocument:
+  #       Version: '2012-10-17'
+  #       Statement:
+  #         Effect: Allow
+  #         Principal:
+  #           Service: apigateway.amazonaws.com
+  #         Action: sts:AssumeRole
+  #     Policies:
+  #     - PolicyName: ApiGatewayMeteringPolicy
+  #       PolicyDocument:
+  #         Version: '2012-10-17'
+  #         Statement:
+  #         - Action:
+  #           - aws-marketplace:BatchMeterUsage
+  #           - aws-marketplace:ResolveCustomer
+  #           Effect: Allow
+  #           Resource: '*'
+
+  LambdaApiGatewayExecutionPermission:
+    Type: AWS::Lambda::Permission
+    Properties:
+      Action: lambda:InvokeFunction
+      FunctionName: !GetAtt DevPortalLambdaFunction.Arn
+      Principal: apigateway.amazonaws.com
+      SourceArn: !Join
+        - ''
+        - - 'arn:aws:execute-api:'
+          - !Ref 'AWS::Region'
+          - ':'
+          - !Ref 'AWS::AccountId'
+          - ':'
+          - !Ref ApiGatewayApi
+          - '/*/*'
+
+  CognitoPreSignupTriggerFnExecutionPermission:
+    Type: AWS::Lambda::Permission
+    Properties:
+      Action: lambda:InvokeFunction
+      FunctionName: !GetAtt CognitoPreSignupTriggerFn.Arn
+      Principal: cognito-idp.amazonaws.com
+      SourceArn: !Join
+        - ''
+        - - 'arn:aws:cognito-idp:'
+          - !Ref 'AWS::Region'
+          - ':'
+          - !Ref 'AWS::AccountId'
+          - ':userpool/'
+          - !Ref CognitoUserPool
+
+  CognitoPostConfirmationTriggerFnExecutionPermission:
+    Type: AWS::Lambda::Permission
+    Properties:
+      Action: lambda:InvokeFunction
+      FunctionName: !GetAtt CognitoPostConfirmationTriggerFn.Arn
+      Principal: cognito-idp.amazonaws.com
+      SourceArn: !Join
+        - ''
+        - - 'arn:aws:cognito-idp:'
+          - !Ref 'AWS::Region'
+          - ':'
+          - !Ref 'AWS::AccountId'
+          - ':userpool/'
+          - !Ref CognitoUserPool
+
+  CognitoPostAuthenticationTriggerFnExecutionPermission:
+    Type: AWS::Lambda::Permission
+    Properties:
+      Action: lambda:InvokeFunction
+      FunctionName: !GetAtt CognitoPostAuthenticationTriggerFn.Arn
+      Principal: cognito-idp.amazonaws.com
+      SourceArn: !Join
+        - ''
+        - - 'arn:aws:cognito-idp:'
+          - !Ref 'AWS::Region'
+          - ':'
+          - !Ref 'AWS::AccountId'
+          - ':userpool/'
+          - !Ref CognitoUserPool
+
+  # Marketplace support is currently broken
+  # LambdaSNSExecutionPermission:
+  #   Type: AWS::Lambda::Permission
+  #   Properties:
+  #     Action: lambda:InvokeFunction
+  #     FunctionName: !GetAtt SubscriptionListenerLambdaFunction.Arn
+  #     Principal: sns.amazonaws.com
+  #     SourceArn: !Join
+  #       - ''
+  #       - - 'arn:aws:sns:us-east-1:287250355862:aws-mp-subscription-notification-'
+  #         - !Ref MarketplaceSubscriptionTopicProductCode
+  #
+  # LambdaSNSExecutionTestPermission:
+  #   Type: AWS::Lambda::Permission
+  #   Properties:
+  #     Action: lambda:InvokeFunction
+  #     FunctionName: !GetAtt SubscriptionListenerLambdaFunction.Arn
+  #     Principal: sns.amazonaws.com
+  #     SourceArn: !Join
+  #       - ''
+  #       - - 'arn:aws:sns:'
+  #         - !Ref 'AWS::Region'
+  #         - ':'
+  #         - !Ref 'AWS::AccountId'
+  #         - ':marketplace-test-topic'
+
+  LambdaCommonLayer:
+    Type: AWS::Serverless::LayerVersion
+    Properties:
+      Description: 'Common code used by lambdas'
+      ContentUri: ../lambdas/common-layer/
+      CompatibleRuntimes:
+        - nodejs16.x
+
+  DevPortalLambdaFunction:
+    Type: AWS::Serverless::Function
+    Properties:
+      CodeUri: ../lambdas/backend
+      Handler: index.handler
+      MemorySize: 1024
+      Role: !GetAtt BackendLambdaExecutionRole.Arn
+      Runtime: nodejs16.x
+      Timeout: 30
+      Environment:
+        Variables:
+          NODE_ENV: production
+          WEBSITE_BUCKET_NAME: !Ref DevPortalSiteS3BucketName
+          StaticBucketName: !Ref ArtifactsS3BucketName
+          CustomersTableName: !Ref DevPortalCustomersTableName
+          PreLoginAccountsTableName: !Ref DevPortalPreLoginAccountsTableName
+          CatalogUpdaterFunctionArn: !GetAtt CatalogUpdaterLambdaFunction.Arn
+          FeedbackTableName: !Ref DevPortalFeedbackTableName
+          FeedbackSnsTopicArn:
+            !If [EnableFeedbackSubmission, !Ref FeedbackSubmittedSNSTopic, '']
+          UserPoolId: !Ref CognitoUserPool
+          AdminsGroupName: !Join ['', [!Ref 'AWS::StackName', 'AdminsGroup']]
+          RegisteredGroupName: !Sub '${AWS::StackName}-RegisteredGroup'
+          DevelopmentMode: !Ref DevelopmentMode
+          SourceAccount: !Ref AWS::AccountId
+      # Adds the API as a trigger
+      Events:
+        ProxyApiRoot:
+          Type: Api
+          Properties:
+            RestApiId: !Ref ApiGatewayApi
+            Path: /
+            Method: ANY
+        ProxyApiGreedy:
+          Type: Api
+          Properties:
+            RestApiId: !Ref ApiGatewayApi
+            Path: /{proxy+}
+            Method: ANY
+      Layers:
+        - !Ref LambdaCommonLayer
+
+  # Marketplace support is currently broken
+  # SubscriptionListenerLambdaFunction:
+  #   Type: AWS::Serverless::Function
+  #   Properties:
+  #     CodeUri: ../lambdas/listener
+  #     Handler: index.handler
+  #     MemorySize: 128
+  #     Role: !GetAtt BackendLambdaExecutionRole.Arn
+  #     Runtime: nodejs16.x
+  #     Timeout: 30
+  #     Layers:
+  #       - !Ref LambdaCommonLayer
+
+  CognitoPreSignupTriggerFn:
+    Type: AWS::Serverless::Function
+    Properties:
+      FunctionName: !Sub '${AWS::StackName}-CognitoPreSignupTriggerFn'
+      CodeUri: ../lambdas/cognito-pre-signup-trigger
+      Handler: index.handler
+      MemorySize: 128
+      Role: !GetAtt CognitoPreSignupTriggerExecutionRole.Arn
+      Runtime: nodejs16.x
+      Timeout: 3
+      Environment:
+        Variables:
+          AccountRegistrationMode: !Ref AccountRegistrationMode
+      Layers:
+        - !Ref LambdaCommonLayer
+
+  CognitoPostConfirmationTriggerFn:
+    Type: AWS::Serverless::Function
+    Properties:
+      FunctionName: !Sub '${AWS::StackName}-CognitoPostConfirmationTriggerFn'
+      CodeUri: ../lambdas/cognito-post-confirmation-trigger
+      Handler: index.handler
+      MemorySize: 128
+      Role: !GetAtt CognitoPostConfirmationTriggerExecutionRole.Arn
+      Runtime: nodejs16.x
+      Timeout: 3
+      Environment:
+        Variables:
+          AccountRegistrationMode: !Ref AccountRegistrationMode
+          PreLoginAccountsTableName: !Ref DevPortalPreLoginAccountsTableName
+          RegisteredGroupName: !Sub '${AWS::StackName}-RegisteredGroup'
+      Layers:
+        - !Ref LambdaCommonLayer
+
+  CognitoPostAuthenticationTriggerFn:
+    Type: AWS::Serverless::Function
+    Properties:
+      FunctionName: !Sub '${AWS::StackName}-CognitoPostAuthenticationTriggerFn'
+      CodeUri: ../lambdas/cognito-post-authentication-trigger
+      Handler: index.handler
+      MemorySize: 128
+      Role: !GetAtt CognitoPostAuthenticationTriggerExecutionRole.Arn
+      Runtime: nodejs16.x
+      Timeout: 3
+      Environment:
+        Variables:
+          CustomersTableName: !Ref DevPortalCustomersTableName
+          PreLoginAccountsTableName: !Ref DevPortalPreLoginAccountsTableName
+          RegisteredGroupName: !Sub '${AWS::StackName}-RegisteredGroup'
+      Layers:
+        - !Ref LambdaCommonLayer
+
+  CognitoUserPool:
+    Type: AWS::Cognito::UserPool
+    DeletionPolicy: Retain
+    UpdateReplacePolicy: Retain
+    Properties:
+      UserPoolName: !Ref CognitoIdentityPoolName
+      # Lambda trigger caveats:
+      #
+      # - We can't use the functions' ARNs here, because there would be a
+      #   circular dependency: some functions reference either the UserPool or
+      #   UserPoolGroups within it.
+      #
+      # - You must declare an AWS::Lambda::Permission for each lambda here, or
+      #   else calls from Cognito will fail with an AccessDeniedException. See
+      #   `CognitoPreSignupTriggerFnExecutionPermission` as an example. More
+      #   reading: <https://stackoverflow.com/a/42460847> and
+      #   <https://forums.aws.amazon.com/thread.jspa?messageID=748566#748566>
+      LambdaConfig:
+        PreSignUp: !Join
+          - ''
+          - - !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:'
+            - !Sub '${AWS::StackName}-CognitoPreSignupTriggerFn'
+        PostConfirmation: !Join
+          - ''
+          - - !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:'
+            - !Sub '${AWS::StackName}-CognitoPostConfirmationTriggerFn'
+        PostAuthentication: !Join
+          - ''
+          - - !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:'
+            - !Sub '${AWS::StackName}-CognitoPostAuthenticationTriggerFn'
+      Policies:
+        PasswordPolicy:
+          MinimumLength: 12
+          RequireLowercase: true
+          RequireNumbers: true
+      Schema:
+        - AttributeDataType: String
+          Name: email
+          Required: true
+      AdminCreateUserConfig:
+        AllowAdminCreateUserOnly: !If [
+          InviteAccountRegistrationMode, true, false,
+        ]
+        InviteMessageTemplate:
+          EmailSubject: 'Developer Portal - Invitation'
+          EmailMessage: !Join [ '', [
+            '<h2>Developer Portal</h2><p>You have been invited to access the developer portal at <a href="',
+            !If [ 'DevelopmentMode',
+              !Join [ '', [ 'https://', !GetAtt DevPortalSiteS3Bucket.RegionalDomainName, '/index.html' ]],
+              !If [
+                'UseCustomDomainName',
+                !Join [ '', [ 'https://', !Ref CustomDomainName ]],
+                !Join [ '', [ 'https://', !GetAtt DefaultCloudfrontDistribution.DomainName ]]
+              ]
+            ],
+            '">',
+            !If [ 'DevelopmentMode',
+              !Join [ '', [ 'https://', !GetAtt DevPortalSiteS3Bucket.RegionalDomainName, '/index.html' ]],
+              !If [
+                'UseCustomDomainName',
+                !Join [ '', [ 'https://', !Ref CustomDomainName ]],
+                !Join [ '', [ 'https://', !GetAtt DefaultCloudfrontDistribution.DomainName ]]
+              ]
+            ],
+            '</a>.</p><p><b>Username:</b> {username}<br><b>Temporary password:</b> {####}</p>'
+          ] ]
+      AutoVerifiedAttributes: ['email']
+      UsernameAttributes: ['email']
+      EmailVerificationSubject: 'Developer Portal - Verification Code'
+      EmailVerificationMessage: '<h2>Developer Portal</h2><p>Your verification code is <b>{####}</b></p>'
+
+  CognitoUserPoolClient:
+    Type: AWS::Cognito::UserPoolClient
+    # It's really unintuitive, but changing any of the properties here will cause stack updates to deploy non-functionally.
+    # The CognitoUserPoolClientSettings custom resource runs after this resource and adds a bunch of fields.
+    # However, when this is updated and changes, the CUPCS custom resource doesn't re-run, and so a bunch of vital
+    # settings won't be set, e.g., CallbackURL.
+    Properties:
+      UserPoolId: !Ref CognitoUserPool
+      ClientName: CognitoIdentityPool
+      GenerateSecret: false
+      RefreshTokenValidity: 30
+      PreventUserExistenceErrors: ENABLED
+
+  CognitoUserPoolClientSettingsBackingFnRole:
+    Type: 'AWS::IAM::Role'
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+          -
+            Effect: Allow
+            Action: 'sts:AssumeRole'
+            Principal:
+              Service: lambda.amazonaws.com
+      Policies:
+        - PolicyName: WriteCloudWatchLogs
+          PolicyDocument: 
+            Version: '2012-10-17'
+            Statement: 
+              -
+                Effect: Allow
+                Action:
+                  - 'logs:CreateLogGroup'
+                  - 'logs:CreateLogStream'
+                  - 'logs:PutLogEvents'
+                Resource: 'arn:aws:logs:*:*:*'
+        - PolicyName: UpdateUserPoolClient
+          PolicyDocument: 
+            Version: '2012-10-17'
+            Statement: 
+              -
+                Effect: Allow
+                Action: 'cognito-idp:UpdateUserPoolClient'
+                Resource: 'arn:aws:cognito-idp:*:*:userpool/*'
+
+  CognitoUserPoolClientSettingsBackingFn:
+    Type: AWS::Serverless::Function
+    Properties:
+      Runtime: nodejs16.x
+      MemorySize: 128
+      Timeout: 300
+      CodeUri: ../lambdas/cfn-cognito-user-pools-client-settings
+      Handler: index.handler
+      Role: !GetAtt CognitoUserPoolClientSettingsBackingFnRole.Arn
+      Layers:
+        - !Ref LambdaCommonLayer
+
+  CognitoUserPoolClientSettings:
+    Type: AWS::CloudFormation::CustomResource
+    Properties:
+      Timeout: 360
+      ServiceToken: !GetAtt CognitoUserPoolClientSettingsBackingFn.Arn
+      UserPoolId: !Ref CognitoUserPool
+      UserPoolClientId: !Ref CognitoUserPoolClient
+      SupportedIdentityProviders: [ "COGNITO" ] # should (eventually) allow people to add values
+      CallbackURL: !If [ LocalDevelopmentMode,
+        [
+          'http://localhost:3000/index.html?action=login',
+          !Join [ '', [ 'https://', !GetAtt DevPortalSiteS3Bucket.RegionalDomainName, '/index.html?action=login' ]],
+          !Join [ '', [ 'https://', !If [ NoCustomDomainName, !GetAtt DefaultCloudfrontDistribution.DomainName, !Ref CustomDomainName ], '/index.html?action=login' ]]
+        ],
+        [
+          !Join [ '', [ 'https://', !If [ NoCustomDomainName, !GetAtt DefaultCloudfrontDistribution.DomainName, !Ref CustomDomainName ], '/index.html?action=login' ]]
+        ]
+      ]
+      LogoutURL: !If [ LocalDevelopmentMode,
+        [
+          'http://localhost:3000/index.html?action=logout',
+          !Join [ '', [ 'https://', !GetAtt DevPortalSiteS3Bucket.RegionalDomainName, '/index.html?action=logout' ]],
+          !Join [ '', [ 'https://', !If [ NoCustomDomainName, !GetAtt DefaultCloudfrontDistribution.DomainName, !Ref CustomDomainName ], '/index.html?action=logout' ]],
+        ],
+        [
+          !Join [ '', [ 'https://', !If [ NoCustomDomainName, !GetAtt DefaultCloudfrontDistribution.DomainName, !Ref CustomDomainName ], '/index.html?action=logout' ]],
+        ]
+      ]
+      AllowedOAuthFlowsUserPoolClient: true
+      AllowedOAuthFlows: [ "implicit" ]
+      AllowedOAuthScopes: [ "openid" ]
+
+  CognitoUserPoolDomainBackingFnRole:
+    Type: 'AWS::IAM::Role'
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+          -
+            Effect: Allow
+            Action: 'sts:AssumeRole'
+            Principal:
+              Service: lambda.amazonaws.com
+      Policies:
+        - PolicyName: WriteCloudWatchLogs
+          PolicyDocument: 
+            Version: '2012-10-17'
+            Statement: 
+              -
+                Effect: Allow
+                Action:
+                  - 'logs:CreateLogGroup'
+                  - 'logs:CreateLogStream'
+                  - 'logs:PutLogEvents'
+                Resource: 'arn:aws:logs:*:*:*'
+        - PolicyName: ManageUserPoolDomain
+          PolicyDocument: 
+            Version: '2012-10-17'
+            Statement: 
+              -
+                Effect: Allow
+                Action: 'cognito-idp:CreateUserPoolDomain'
+                Resource: 'arn:aws:cognito-idp:*:*:userpool/*'
+              -
+                Effect: Allow
+                Action: 'cognito-idp:DeleteUserPoolDomain'
+                Resource: 'arn:aws:cognito-idp:*:*:userpool/*'
+              -
+                Effect: Allow
+                Action: 'cognito-idp:DescribeUserPoolDomain'
+                Resource: '*'
+
+  CognitoUserPoolDomainBackingFn:
+    Type: AWS::Serverless::Function
+    Properties:
+      Runtime: nodejs16.x
+      MemorySize: 128
+      Timeout: 300
+      CodeUri: ../lambdas/cfn-cognito-user-pools-domain
+      Handler: index.handler
+      Role: !GetAtt CognitoUserPoolDomainBackingFnRole.Arn
+      Layers:
+        - !Ref LambdaCommonLayer
+
+  CognitoUserPoolDomain:
+    Type: AWS::CloudFormation::CustomResource
+    Properties:
+      Timeout: 360
+      ServiceToken: !GetAtt CognitoUserPoolDomainBackingFn.Arn
+      UserPoolId: !Ref CognitoUserPool
+      Domain: !Ref CognitoDomainNameOrPrefix
+  
+  CognitoIdentityPool:
+    Type: AWS::Cognito::IdentityPool
+    Properties:
+      IdentityPoolName: !Ref CognitoIdentityPoolName
+      AllowUnauthenticatedIdentities: false
+      CognitoIdentityProviders:
+        - ClientId: !Ref CognitoUserPoolClient
+          ProviderName: !Join
+            - ''
+            - - cognito-idp.
+              - !Ref 'AWS::Region'
+              - .amazonaws.com/
+              - !Ref CognitoUserPool
+
+  CognitoIdentityPoolRoles:
+    Type: AWS::Cognito::IdentityPoolRoleAttachment
+    Properties:
+      IdentityPoolId: !Ref CognitoIdentityPool
+      Roles:
+        authenticated: !GetAtt CognitoAuthenticatedRole.Arn
+
+  # Every logged-in Cognito user is "authenticated".
+  CognitoAuthenticatedRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+        - Effect: Allow
+          Principal:
+            Federated: cognito-identity.amazonaws.com
+          Action: sts:AssumeRoleWithWebIdentity
+          Condition:
+            StringEquals:
+              'cognito-identity.amazonaws.com:aud': !Ref CognitoIdentityPool
+            'ForAnyValue:StringLike':
+              'cognito-identity.amazonaws.com:amr': authenticated
+      Policies:
+      - PolicyName: CognitoAuthenticatedRole
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+            - execute-api:Invoke
+            Resource: !Join
+              - ''
+              - - 'arn:aws:execute-api:'
+                - !Ref 'AWS::Region'
+                - ':'
+                - !Ref 'AWS::AccountId'
+                - ':'
+                - !Ref ApiGatewayApi
+                - /prod/*/signin
+      Path: '/'
+
+  # A logged-in Cognito user, who is not in a "pending" (invite or request)
+  # state, is "registered".
+  CognitoRegisteredRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+        - Effect: Allow
+          Principal:
+            Federated: cognito-identity.amazonaws.com
+          Action: sts:AssumeRoleWithWebIdentity
+          Condition:
+            StringEquals:
+              'cognito-identity.amazonaws.com:aud': !Ref CognitoIdentityPool
+            'ForAnyValue:StringLike':
+              'cognito-identity.amazonaws.com:amr': authenticated
+      Policies:
+      - PolicyName: CognitoRegisteredRole
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+            - execute-api:Invoke
+            Resource:
+            - !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiGatewayApi}/prod/*'
+          - Effect: Deny
+            Action:
+            - execute-api:Invoke
+            Resource:
+            - !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiGatewayApi}/prod/*/admin/*'
+      Path: '/'
+
+  CognitoAdminRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+        - Effect: Allow
+          Principal:
+            Federated: cognito-identity.amazonaws.com
+          Action: sts:AssumeRoleWithWebIdentity
+          Condition:
+            StringEquals:
+              'cognito-identity.amazonaws.com:aud': !Ref CognitoIdentityPool
+            'ForAnyValue:StringLike':
+              'cognito-identity.amazonaws.com:amr': authenticated
+      Policies:
+      - PolicyName: CognitoAdminRole
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+            - execute-api:Invoke
+            Resource: !Join
+              - ''
+              - - 'arn:aws:execute-api:'
+                - !Ref 'AWS::Region'
+                - ':'
+                - !Ref 'AWS::AccountId'
+                - ':'
+                - !Ref ApiGatewayApi
+                - /prod/*/*
+
+  CognitoAdminsGroup:
+    Type: AWS::Cognito::UserPoolGroup
+    DeletionPolicy: Retain
+    UpdateReplacePolicy: Retain
+    Properties:
+      Description: 'Admin users of the developer portal'
+      GroupName: !Join ['', [!Ref 'AWS::StackName', 'AdminsGroup']]
+      # the role we assume is the role associated with the lowest-precedence users group
+      # since admin group has a precedence of 0, it takes priority
+      Precedence: 0
+      RoleArn: !GetAtt CognitoAdminRole.Arn
+      UserPoolId: !Ref CognitoUserPool
+
+  CognitoRegisteredGroup:
+    Type: AWS::Cognito::UserPoolGroup
+    DeletionPolicy: Retain
+    UpdateReplacePolicy: Retain
+    Properties:
+      Description: 'Registered users in the developer portal'
+      GroupName: !Sub '${AWS::StackName}-RegisteredGroup'
+      Precedence: 1
+      RoleArn: !GetAtt CognitoRegisteredRole.Arn
+      UserPoolId: !Ref CognitoUserPool
+
+  CatalogUpdaterLambdaFunction:
+    Type: AWS::Serverless::Function
+    Properties:
+      CodeUri: ../lambdas/catalog-updater
+      Handler: index.handler
+      MemorySize: 128
+      Role: !GetAtt CatalogUpdaterLambdaExecutionRole.Arn
+      Runtime: nodejs16.x
+      Timeout: 20
+      Environment:
+        Variables:
+          BucketName: !Ref ArtifactsS3BucketName
+          SourceAccount: !Ref AWS::AccountId
+      Layers:
+        - !Ref LambdaCommonLayer
+
+  CatalogUpdaterLambdaPermissions:
+    Type: AWS::Lambda::Permission
+    Properties:
+      Action: "lambda:InvokeFunction"
+      FunctionName: !GetAtt CatalogUpdaterLambdaFunction.Arn
+      Principal: "s3.amazonaws.com"
+      SourceAccount: !Ref AWS::AccountId
+      #SourceArn: !GetAtt MyBucket.Arn
+
+  StaticAssetUploaderLambdaFunction:
+    Type: AWS::Serverless::Function
+    Properties:
+      CodeUri: ../lambdas/static-asset-uploader
+      Handler: index.handler
+      MemorySize: 512
+      Role: !GetAtt AssetUploaderLambdaExecutionRole.Arn
+      Runtime: nodejs16.x
+      Timeout: 300
+      Environment:
+        Variables:
+          StaticBucketName: !Ref ArtifactsS3BucketName
+          SourceAccount: !Ref AWS::AccountId
+      Layers:
+        - !Ref LambdaCommonLayer
+
+  StaticAssetUploader:
+    Type: AWS::CloudFormation::CustomResource
+    DependsOn: ArtifactsS3Bucket
+    Properties:
+      ServiceToken: !GetAtt StaticAssetUploaderLambdaFunction.Arn
+      BucketName: !Ref DevPortalSiteS3Bucket
+      RestApiId: !Ref ApiGatewayApi
+      Region: !Ref 'AWS::Region'
+      IdentityPoolId: !Ref CognitoIdentityPool
+      UserPoolId: !Ref CognitoUserPool
+      UserPoolClientId: !Ref CognitoUserPoolClient
+      UserPoolDomain: !GetAtt CognitoUserPoolDomain.FullUrl
+      # Marketplace support is currently broken
+      # MarketplaceSuffix: !Ref MarketplaceSubscriptionTopicProductCode
+      RebuildToken: !Ref StaticAssetRebuildToken
+      RebuildMode: !Ref StaticAssetRebuildMode
+      DevelopmentMode: !Ref DevelopmentMode
+      FeedbackEnabled: !If [ EnableFeedbackSubmission, 'true', 'false' ]
+
+  LambdaEdgeFunctionRole:
+    Type: AWS::IAM::Role
+    Properties:
+      Path: "/"
+      ManagedPolicyArns:
+        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+          -
+            Sid: "AllowLambdaServiceToAssumeRole"
+            Effect: "Allow"
+            Action: 
+              - "sts:AssumeRole"
+            Principal:
+              Service: 
+                - "lambda.amazonaws.com"
+                - "edgelambda.amazonaws.com"
+
+  CloudFrontSecurityHeadersLambda:
+    Type: AWS::Serverless::Function
+    Properties:
+      CodeUri: ../lambdas/cloudfront-security
+      Handler: replicator.handler
+      MemorySize: 128
+      Role: !GetAtt CloudFrontEdgeReplicatorRole.Arn
+      Runtime: nodejs16.x
+      Timeout: 300
+      AutoPublishAlias: Live
+      Environment:
+        Variables:
+          Bucket: !Ref ArtifactsS3BucketName
+      Layers:
+        - !Ref LambdaCommonLayer
+
+  CloudFrontSecurityHeadersSetup:
+    Type: AWS::CloudFormation::CustomResource
+    Properties:
+      ServiceToken: !GetAtt CloudFrontSecurityHeadersLambda.Arn
+      Name: !Ref CloudFrontSecurityHeadersLambda
+      RoleArn: !GetAtt LambdaEdgeFunctionRole.Arn
+      RebuildToken: !Ref EdgeLambdaRebuildToken
+  
+  CloudFrontEdgeReplicatorRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+          Effect: Allow
+          Principal:
+            Service: lambda.amazonaws.com
+          Action: sts:AssumeRole
+      Path: '/'
+      Policies:
+      - PolicyName: root
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+            - logs:CreateLogGroup
+            - logs:CreateLogStream
+            - logs:PutLogEvents
+            Resource: arn:aws:logs:*:*:*
+          - Effect: Allow
+            Action:
+              - lambda:CreateFunction
+              - lambda:GetFunction
+              - lambda:GetFunctionConfiguration
+              - lambda:UpdateFunctionCode
+              - lambda:PublishVersion
+            Resource: arn:aws:lambda:*:*:*
+          - Effect: Allow
+            Action:
+              - iam:PassRole
+            Resource: !Sub 'arn:aws:iam::${AWS::AccountId}:role/*'
+          - Effect: Allow
+            Action:
+            - s3:GetObject
+            - s3:DeleteObject
+            - s3:PutObject
+            Resource: !Join
+              - ''
+              - - 'arn:aws:s3:::'
+                - !Ref ArtifactsS3BucketName
+                - '/*'
+
+  CloudFrontOriginAccessIdentity:
+    Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity'
+    Condition: 'NotDevelopmentMode'
+    Properties:
+      CloudFrontOriginAccessIdentityConfig:
+        Comment: !Ref 'AWS::StackName'
+
+  CustomDomainCloudfrontDistribution:
+    Type: AWS::CloudFront::Distribution
+    DependsOn: DevPortalSiteS3Bucket
+    Condition: UseCustomDomainName
+    Properties:
+      DistributionConfig:
+        Aliases:
+          - !Ref CustomDomainName
+        CustomErrorResponses:
+          - ErrorCode: 403
+            ResponseCode: 403
+            ResponsePagePath: '/index.html'
+        DefaultCacheBehavior:
+          ForwardedValues:
+            QueryString: true
+          TargetOriginId: 'dev-portal-site-s3-bucket'
+          ViewerProtocolPolicy: redirect-to-https
+          LambdaFunctionAssociations:
+            -
+              EventType: origin-response
+              LambdaFunctionARN: !GetAtt CloudFrontSecurityHeadersSetup.EdgeArn
+        DefaultRootObject: index.html
+        Enabled: true
+        Comment: !Sub '${AWS::StackName} distribution'
+        Origins:
+          - Id: 'dev-portal-site-s3-bucket'
+            DomainName: !If ['InUSEastOne',
+              !Join ['', [!Ref DevPortalSiteS3BucketName, '.s3.amazonaws.com']],
+              !Join ['', [!Ref DevPortalSiteS3BucketName, '.s3-', !Ref 'AWS::Region', '.amazonaws.com']]]
+            S3OriginConfig:
+             OriginAccessIdentity: !Sub >-
+               origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}
+        ViewerCertificate:
+          AcmCertificateArn: !Ref CustomDomainNameAcmCertArn
+          SslSupportMethod: 'sni-only'
+
+  DefaultCloudfrontDistribution:
+    Type: AWS::CloudFront::Distribution
+    Condition: NoCustomDomainName
+    Properties:
+      DistributionConfig:
+        CustomErrorResponses:
+          - ErrorCode: 403
+            ResponseCode: 403
+            ResponsePagePath: '/index.html'
+        DefaultCacheBehavior:
+          ForwardedValues:
+            QueryString: true
+          TargetOriginId: 'dev-portal-site-s3-bucket'
+          ViewerProtocolPolicy: redirect-to-https
+          LambdaFunctionAssociations:
+            -
+              EventType: origin-response
+              LambdaFunctionARN: !GetAtt CloudFrontSecurityHeadersSetup.EdgeArn
+        DefaultRootObject: index.html
+        Enabled: true
+        Comment: !Sub '${AWS::StackName} distribution'
+        Origins:
+          - Id: 'dev-portal-site-s3-bucket'
+            DomainName: !If ['InUSEastOne',
+              !Join ['', [!Ref DevPortalSiteS3BucketName, '.s3.amazonaws.com']],
+              !Join ['', [!Ref DevPortalSiteS3BucketName, '.s3-', !Ref 'AWS::Region', '.amazonaws.com']]]
+            S3OriginConfig:
+             OriginAccessIdentity: !Sub >-
+               origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}
+
+  CustomDomainDistributionAccessIdentity:
+    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
+    Condition: UseCustomDomainName
+    Properties:
+      CloudFrontOriginAccessIdentityConfig:
+        Comment: !Sub 'CloudFront OAI for ${CustomDomainName}'
+
+  CustomDomainHostedZone:
+    Type: AWS::Route53::HostedZone
+    Condition: UseRoute53
+    Properties:
+      Name: !Join [ '', [ !Ref CustomDomainName, '.' ] ]
+
+  CustomDomainRecordSet:
+    Type: AWS::Route53::RecordSetGroup
+    Condition: UseRoute53
+    Properties:
+      HostedZoneName: !Join [ '', [ !Ref CustomDomainName, '.' ] ]
+      RecordSets:
+        - Name: !Join [ '', [ !Ref CustomDomainName, '.' ] ]
+          Type: A
+          AliasTarget:
+            DNSName: !Join [ '', [ !GetAtt CustomDomainCloudfrontDistribution.DomainName, '.' ] ]
+            # this is a "magic string" for using CFN aliases; see this link:
+            # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html
+            HostedZoneId: 'Z2FDTNDATAQYW2'
+
+  DumpV3AccountDataFn:
+    Type: AWS::Serverless::Function
+    Properties:
+      CodeUri: ../lambdas/dump-v3-account-data
+      Handler: index.handler
+      MemorySize: 512
+      Role: !GetAtt DumpV3AccountDataExecutionRole.Arn
+      Runtime: nodejs16.x
+      Timeout: 300
+      Environment:
+        Variables:
+          CustomersTableName: !Ref DevPortalCustomersTableName
+          UserPoolId: !Ref CognitoUserPool
+          AdminsGroupName: !Ref CognitoAdminsGroup
+      Layers:
+        - !Ref LambdaCommonLayer
+
+  DumpV3AccountDataExecutionRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+          Effect: Allow
+          Principal:
+            Service: lambda.amazonaws.com
+          Action: sts:AssumeRole
+      Policies:
+      - PolicyName: WriteCloudWatchLogs
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+            - logs:CreateLogGroup
+            - logs:CreateLogStream
+            - logs:PutLogEvents
+            Resource: arn:aws:logs:*:*:*
+      - PolicyName: ReadCustomersTable
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action: dynamodb:Scan
+            Resource: !GetAtt CustomersTable.Arn
+      - PolicyName: ListUserPool
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+              - cognito-idp:ListUsers
+              - cognito-idp:ListUsersInGroup
+            Resource: !GetAtt CognitoUserPool.Arn
+
+  UserGroupImporterExecutionRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+          Effect: Allow
+          Principal:
+            Service: lambda.amazonaws.com
+          Action: sts:AssumeRole
+      Policies:
+      - PolicyName: WriteCloudWatchLogs
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+            - logs:CreateLogGroup
+            - logs:CreateLogStream
+            - logs:PutLogEvents
+            Resource: arn:aws:logs:*:*:*
+      - PolicyName: S3GetObject
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action: s3:getObject
+            Resource: arn:aws:s3:::*/*
+      - PolicyName: RestoreTables
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+              - dynamodb:Scan
+            Resource: !If [
+              EnableFeedbackSubmission,
+              [
+                !GetAtt CustomersTable.Arn,
+                !GetAtt FeedbackTable.Arn,
+              ],
+              [
+                !GetAtt CustomersTable.Arn,
+              ],
+            ]
+      - PolicyName: UpdateCognitoUserList
+        PolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+          - Effect: Allow
+            Action:
+              - cognito-idp:AdminCreateUser
+              - cognito-idp:AdminAddUserToGroup
+            Resource: !GetAtt CognitoUserPool.Arn
+  
+  UserGroupImporter:
+    Type: AWS::Serverless::Function
+    Properties:
+      CodeUri: ../lambdas/user-group-importer
+      Handler: index.handler
+      MemorySize: 512
+      Role: !GetAtt UserGroupImporterExecutionRole.Arn
+      Runtime: nodejs16.x
+      # Use the default max timeout duration.
+      Timeout: 900
+      Environment:
+        Variables:
+          UserPoolId: !Ref CognitoUserPool
+          AdminsGroup: !Ref CognitoAdminsGroup
+          RegisteredGroup: !Ref CognitoRegisteredGroup
+          CustomersTable: !Ref CustomersTable
+          FeedbackTable: !If [EnableFeedbackSubmission, !Ref FeedbackTable, !Ref AWS::NoValue]
+          SourceAccount: !Ref AWS::AccountId
+      
+
+Outputs:
+  WebsiteURL:
+    Value: !If [ 'DevelopmentMode',
+      !Join [ '', [ 'https://', !GetAtt DevPortalSiteS3Bucket.RegionalDomainName, '/index.html' ]],
+      !If [
+        'UseCustomDomainName',
+        !Join [ '', [ 'https://', !Ref CustomDomainName ]],
+        !Join [ '', [ 'https://', !GetAtt DefaultCloudfrontDistribution.DomainName ]]
+      ]
+    ]
+    Description: CloudFront URL for website
+
+  CustomWebsiteURL:
+    Condition: UseCustomDomainName
+    Value: !Ref CustomDomainName
+    Description: Custom URL for website
diff --git a/tests/data/templates/depopry.yaml b/tests/data/templates/depopry.yaml
new file mode 100644
index 0000000..881b1b0
--- /dev/null
+++ b/tests/data/templates/depopry.yaml
@@ -0,0 +1,552 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Main CloudFormation template that builds shared resources and modules stacks
+Metadata:
+  AWS::CloudFormation::Interface:
+    ParameterGroups:
+      - Label:
+          default: 'Deployment parameters'
+        Parameters:
+          - DestinationBucket
+          - ManagementAccountRole
+          - ManagementAccountID
+          - MultiAccountRoleName
+          - Schedule
+          - RolePrefix
+          - CFNTemplateSourceBucket
+      - Label:
+          default: 'Available modules'
+        Parameters:
+          - IncludeTAModule
+          - IncludeRightsizingModule
+          - IncludeInventoryCollectorModule
+          - IncludeComputeOptimizerModule
+          - ComputeOptimizerRegions
+          - IncludeECSChargebackModule
+          - IncludeRDSUtilizationModule
+          - IncludeOrgDataModule
+          - IncludeBudgetsModule
+          - IncludeTransitGatewayModule
+    ParameterLabels:
+      DestinationBucket:
+        default: 'Destination S3 bucket'
+      ManagementAccountRole:
+        default: 'Management account role'
+      ManagementAccountID:
+        default: 'Comma Delimited list of Account IDs for all Management Account IDs'
+      MultiAccountRoleName:
+        default: 'Multi Account Role Name'
+      Schedule:
+        default: "Schedule swap to cron(0 8 1 * ? *) for 1st day of the month"
+      RolePrefix:
+        default: "Role Prefix"
+      CFNTemplateSourceBucket:
+        default: "DO NOT CHANGE - A bucket that contains WA-Labs CloudFormation templates. Must be allways 'aws-well-architected-labs'"
+      IncludeTAModule:
+        default: 'Include AWS Trusted Advisor Data Collection Module'
+      IncludeRightsizingModule:
+        default: 'Include Rightsizing Recommendations Data Collection Module'
+      IncludeInventoryCollectorModule:
+        default: 'Include Inventory Collector Module'
+      IncludeComputeOptimizerModule:
+        default: 'Include AWS Compute Optimizer Data Collection Module'
+      ComputeOptimizerRegions:
+        default: "Comma Delimited list of AWS regions where AWS Compute Optimizer data will be collected."
+      IncludeECSChargebackModule:
+        default: 'Include ECS Chargeback Data Collection Module'
+      IncludeRDSUtilizationModule:
+        default: 'Include RDS Utilization Data Collection Module'
+      IncludeOrgDataModule:
+        default: 'Include AWS Organization Data Collection Module'
+      IncludeBudgetsModule:
+        default: 'Include AWS Budgets Collection Module'
+      IncludeTransitGatewayModule:
+        default: 'Include AWS TransitGateway Collection Module'
+Mappings:
+  RegionMap:
+    eu-west-1:        {CodeBucket: aws-well-architected-labs-ireland }
+    us-east-2:        {CodeBucket: aws-well-architected-labs-ohio }
+    us-east-1:        {CodeBucket: aws-well-architected-labs-virginia }
+    us-west-1:        {CodeBucket: aws-well-architected-labs-california }
+    us-west-2:        {CodeBucket: aws-well-architected-labs-oregon }
+    ap-southeast-1:   {CodeBucket: aws-well-architected-labs-singapore }
+    eu-central-1:     {CodeBucket: aws-well-architected-labs-frankfurt }
+    eu-west-2:        {CodeBucket: aws-well-architected-labs-london }
+    eu-north-1:       {CodeBucket: aws-well-architected-labs-stockholm }
+    ap-southeast-2:   {CodeBucket: aws-well-architected-labs-ap-sydney }
+    ap-south-1:       {CodeBucket: aws-well-architected-labs-mumbai }
+    ap-northeast-3:   {CodeBucket: aws-well-architected-labs-osaka }
+    ap-northeast-2:   {CodeBucket: aws-well-architected-labs-seoul }
+    ap-northeast-1:   {CodeBucket: aws-well-architected-labs-tokyo }
+    ca-central-1:     {CodeBucket: aws-well-architected-labs-canada }
+    eu-west-3:        {CodeBucket: aws-well-architected-labs-paris }
+    sa-east-1:        {CodeBucket: aws-well-architected-labs-san-paulo }
+Parameters:
+  DestinationBucket:
+    Type: String
+    Description: Name of the S3 Bucket that needs to be created to hold information
+    AllowedPattern: (?=^.{3,63}$)(?!^(\d+\.)+\d+$)(^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9\-])$)
+    Default: costoptimizationdata
+  ManagementAccountRole:
+    Type: String
+    Description: The name of the IAM role that will be deployed in the management account which can retrieve AWS Organization data. KEEP THE SAME AS WHAT IS DEPLOYED INTO MANAGEMENT ACCOUNT
+    Default: Lambda-Assume-Role-Management-Account
+  ManagementAccountID:
+    Type: String
+    AllowedPattern: ([a-z0-9\-, ]*?$)
+    Description: "(Ex: 123456789,098654321,789054312) List of Payer IDs you wish to collect data for. Can just be one Accounts"
+  MultiAccountRoleName:
+    Type: String
+    Description: The name of the IAM role that will be deployed from the management account to linked accounts as a read only role. KEEP THE SAME AS WHAT IS DEPLOYED INTO MANAGEMENT ACCOUNT
+    Default: "Optimization-Data-Multi-Account-Role"
+  Schedule:
+    Type: String
+    Description: Cron job to trigger the lambda using cloudwatch event
+    Default: "rate(14 days)"
+  RolePrefix:
+    Type: String
+    Description: This prefix will be placed in front of all roles created. Note you may wish to add a dash at the end to make more readable e.g. prefix-
+    Default: "WA-"
+  CFNTemplateSourceBucket:
+    Type: String
+    Description: "DO NOT CHANGE - A bucket that contains WA-Labs CloudFormation templates. Must be allways 'aws-well-architected-labs'"
+    Default: "aws-well-architected-labs"
+  IncludeTAModule:
+    Type: String
+    Description: Collects AWS Trusted Advisor recommendations data
+    AllowedValues:
+      - "yes"
+      - "no"
+  IncludeRightsizingModule:
+    Type: String
+    Description: "Collects AWS Cost Explorer Rightsizing Recommendations"
+    AllowedValues:
+      - "yes"
+      - "no"
+  IncludeInventoryCollectorModule:
+    Type: String
+    Description: Collects data about AMIs, EBS volumes and snapshots
+    AllowedValues:
+      - "yes"
+      - "no"
+  IncludeComputeOptimizerModule:
+    Type: String
+    Description: Collects AWS Compute Optimizer service recommendations
+    AllowedValues:
+      - "yes"
+      - "no"
+  ComputeOptimizerRegions:
+    Default: ""
+    Type: String
+    Description: "Ex: us-east-1,us-east-2,us-west-1,us-west-2,eu-central-1,eu-west-1,eu-west-2,eu-west-3  if empty, the current region will be used. You can add regions later by updating the stack."
+    AllowedPattern: ([a-z0-9\-, ]*?$)
+  IncludeECSChargebackModule:
+    Type: String
+    Description: Collects data which shows costs associated with ECS Tasks leveraging EC2 instances within a Cluster
+    AllowedValues:
+      - "yes"
+      - "no"
+  IncludeRDSUtilizationModule:
+    Type: String
+    Description: Collects RDS CloudWatch metrics from your accounts
+    AllowedValues:
+      - "yes"
+      - "no"
+  IncludeOrgDataModule:
+    Type: String
+    Description: Collects AWS Organizations data such as account Id, account name, organization parent and specified tags
+    AllowedValues:
+      - "yes"
+      - "no"
+  IncludeBudgetsModule:
+    Type: String
+    Description: Collects AWS Budgets
+    AllowedValues:
+      - "yes"
+      - "no"
+  IncludeTransitGatewayModule:
+    Type: String
+    Description: Collects AWS TransitGateway data
+    AllowedValues:
+      - "yes"
+      - "no"
+Outputs:
+  S3Bucket:
+    Description: Name of S3 Bucket which will store the AWS Cost Explorer Rightsizing recommendations
+    Value:
+      Ref: S3Bucket
+  S3BucketARN:
+    Description: ARN of S3 Bucket which will store the AWS Cost Explorer Rightsizing recommendations
+    Value:
+      Fn::GetAtt:
+        - S3Bucket
+        - Arn
+  RoleARN:
+    Description: "The arn of the IAM role that deployed in the management account which can retrieve AWS Organization data"
+    Value: !Sub "arn:aws:iam::${ManagementAccountID}:role/${ManagementAccountRole}"
+  TaskQueuesUrl:
+    Description: "SQS topics created for deployed modules"
+    Value:
+      Fn::Join:
+        - ','
+        - - !If [DeployTAModule, !Sub "${TrustedAdvisorModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
+          - !If [DeployInventoryCollectorModule, !Sub "${InventoryCollectorModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
+          - !If [DeployEcsChargebackModule, !Sub "${EcsChargebackModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
+          - !If [DeployRDSUtilizationModule, !Sub "${RDSUtilizationModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
+          - !If [DeployBudgetsModule, !Sub "${BudgetsModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
+          - !If [DeployTransitGatewayModule, !Sub "${TransitGatewayModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
+Conditions:
+  DeployTAModule: !Equals
+    - !Ref IncludeTAModule
+    - "yes"
+  DeployRightsizingModule: !Equals
+    - !Ref IncludeRightsizingModule
+    - "yes"
+  DeployInventoryCollectorModule: !Equals
+    - !Ref IncludeInventoryCollectorModule
+    - "yes"
+  DeployComputeOptimizerModule: !Equals
+    - !Ref IncludeComputeOptimizerModule
+    - "yes"
+  DeployEcsChargebackModule: !Equals
+    - !Ref IncludeECSChargebackModule
+    - "yes"
+  DeployRDSUtilizationModule: !Equals
+    - !Ref IncludeRDSUtilizationModule
+    - "yes"
+  DeployOrgDataModule: !Equals
+    - !Ref IncludeOrgDataModule
+    - "yes"
+  DeployBudgetsModule: !Equals
+    - !Ref IncludeBudgetsModule
+    - "yes"
+  DeployTransitGatewayModule: !Equals
+    - !Ref IncludeTransitGatewayModule
+    - "yes"
+  DeployPricingModule: !Or
+    - !Condition DeployInventoryCollectorModule
+    - !Condition DeployRDSUtilizationModule
+  DeployAccountCollector: !Or
+    - !Condition DeployTAModule
+    - !Condition DeployInventoryCollectorModule
+    - !Condition DeployRDSUtilizationModule
+    - !Condition DeployEcsChargebackModule
+    - !Condition DeployBudgetsModule
+    - !Condition DeployRDSUtilizationModule
+    - !Condition DeployTransitGatewayModule
+  ComputeOptimizerRegionsIsEmpty: !Equals
+    - !Join [ '', !Split [ ' ', !Ref ComputeOptimizerRegions  ] ] # remove spaces
+    - ""
+  ProdCFNTemplateUsed: !Equals [ !Ref CFNTemplateSourceBucket,  'aws-well-architected-labs' ]
+#Reusable Resources:
+Resources:
+  S3CrawlerQue:
+    Type: AWS::SQS::Queue
+    Properties:
+      VisibilityTimeout: 300
+      ReceiveMessageWaitTimeSeconds: 20
+      DelaySeconds: 2
+      QueueName: S3CrawlerQue
+  CrawlerSQSPolicy:
+    DependsOn:
+      - S3CrawlerQue
+    Type: AWS::SQS::QueuePolicy
+    Properties:
+      Queues:
+        - Ref: S3CrawlerQue
+      PolicyDocument:
+        Id: SQSPolicy
+        Statement:
+          - Sid: SQSEventPolicy
+            Effect: Allow
+            Principal: {"Service": "s3.amazonaws.com"}
+            Action: sqs:SendMessage
+            Resource: !GetAtt S3CrawlerQue.Arn
+            Condition:
+              ArnLike:
+                aws:SourceArn: !Sub "arn:aws:s3:::${DestinationBucket}${AWS::AccountId}"
+              StringEquals:
+                'aws:SourceAccount': !Sub '${AWS::AccountId}'
+  S3Bucket:
+    DependsOn:
+      - CrawlerSQSPolicy
+      - S3CrawlerQue
+    Type: 'AWS::S3::Bucket'
+    Properties:
+      BucketName:
+        !Sub "${DestinationBucket}${AWS::AccountId}"
+      VersioningConfiguration:
+        Status: Enabled
+      BucketEncryption:
+        ServerSideEncryptionConfiguration:
+          - ServerSideEncryptionByDefault:
+              SSEAlgorithm: AES256
+      PublicAccessBlockConfiguration:
+        BlockPublicAcls : true
+        BlockPublicPolicy : true
+        IgnorePublicAcls : true
+        RestrictPublicBuckets : true
+      NotificationConfiguration:
+        QueueConfigurations:
+          - Event: s3:ObjectCreated:*
+            Queue: !Sub "arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:S3CrawlerQue"
+            Filter:
+              S3Key:
+                Rules:
+                  - Name: prefix
+                    Value: Compute_Optimizer/
+  GlueRole:
+    Type: AWS::IAM::Role
+    Properties:
+      RoleName: !Sub "${RolePrefix}AWS-OPTICS-Glue-Crawler"
+      AssumeRolePolicyDocument:
+        Statement:
+          - Action:
+              - sts:AssumeRole
+            Effect: Allow
+            Principal:
+              Service:
+                - glue.amazonaws.com
+        Version: 2012-10-17
+      ManagedPolicyArns:
+        - arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole
+      Path: /
+      Policies:
+        - PolicyName: "Put-S3"
+          PolicyDocument:
+            Version: "2012-10-17"
+            Statement:
+              - Effect: "Allow"
+                Action:
+                  - "s3:PutObject"
+                  - "s3:GetObject"
+                Resource: !Join
+                  - ''
+                  - - !GetAtt S3Bucket.Arn
+                    - '*'
+  LambdaAnalyticsRole: #Execution role for the custom resource
+    Type: AWS::IAM::Role
+    Properties:
+      Path:
+        Fn::Sub: /${RolePrefix}/
+      AssumeRolePolicyDocument:
+        Version: 2012-10-17
+        Statement:
+          - Effect: Allow
+            Principal:
+              Service:
+                - lambda.amazonaws.com
+            Action:
+              - sts:AssumeRole
+      ManagedPolicyArns:
+        - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
+  LambdaAnalytics:
+    Type: AWS::Lambda::Function
+    Properties:
+      Runtime: python3.9
+      FunctionName: !Sub ${RolePrefix}DataCollectionLab-Analytics
+      Handler: index.lambda_handler
+      MemorySize: 128
+      Role: !GetAtt LambdaAnalyticsRole.Arn
+      Timeout: 15
+      Environment:
+        Variables:
+          WA_ANALYTICS_ENDPOINT: https://okakvoavfg.execute-api.eu-west-1.amazonaws.com/
+      Code:
+        ZipFile: |
+          import os
+          import json
+          import uuid
+          import urllib3
+          import boto3
+
+          endpoint = os.environ['WA_ANALYTICS_ENDPOINT']
+          account_id = boto3.client("sts").get_caller_identity()["Account"]
+
+          def lambda_handler(event, context):
+              print(json.dumps(event))
+              try:
+                  if event['RequestType'].upper() not in ['CREATE', 'UPDATE', 'UPDATE']:
+                      raise Exception(f"Unknown RequestType {event['RequestType']}")
+                  action = event['RequestType'].upper()
+                  name = event['ResourceProperties']['Name']
+                  method = {'CREATE':'PUT', 'UPDATE': 'PATCH', 'DELETE': 'DELETE'}.get(action)
+                  via_key = {'CREATE':'created_via', 'UPDATE': 'updated_via', 'DELETE': 'deleted_via'}.get(action)
+                  payload = {'dashboard_id': 'data-collection-lab/' + name, 'account_id': account_id, via_key: 'CFN'}
+                  r =  urllib3.PoolManager().request(method, endpoint, body=json.dumps(payload).encode('utf-8'), headers={'Content-Type': 'application/json'})
+                  if r.status != 200:
+                      raise Exception(f"There has been an issue logging action, server did not respond with a 200 response, actual status: {r.status}, response data {r.data.decode('utf-8')}. This issue will be ignored")
+                  res, reason = 'SUCCESS', 'success'
+              except Exception as exc:
+                  res, reason = 'SUCCESS', f"{exc} . This issue will be ignored"
+              body = {
+                  'Status': res,
+                  'Reason': reason,
+                  'PhysicalResourceId': event.get('PhysicalResourceId', str(uuid.uuid1())),
+                  'StackId': event.get('StackId'),
+                  'RequestId': event.get('RequestId'),
+                  'LogicalResourceId': event.get('LogicalResourceId'),
+                  'NoEcho': False,
+                  'Data':  {'Reason': reason},
+              }
+              json_body=json.dumps(body)
+              print(json_body)
+              url = event.get('ResponseURL')
+              if not url: return
+              try:
+                  response = urllib3.PoolManager().request('PUT', url, body=json_body, headers={'content-type' : '', 'content-length' : str(len(json_body))}, retries=False)
+                  print(f"Status code: {response}")
+              except Exception as exc:
+                  print("Failed sending PUT to CFN: " + str(exc))
+  TrustedAdvisorModule:
+    Type: AWS::CloudFormation::Stack
+    Condition: DeployTAModule
+    Properties:
+      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-trusted-advisor.yaml"
+      Parameters:
+        DestinationBucket: !Ref S3Bucket
+        DestinationBucketARN: !GetAtt S3Bucket.Arn
+        GlueRoleARN: !GetAtt GlueRole.Arn
+        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
+        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNTemplateSourceBucket ]
+        RolePrefix: !Ref RolePrefix
+        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
+  RightsizeModule:
+    Type: AWS::CloudFormation::Stack
+    Condition: DeployRightsizingModule
+    Properties:
+      Parameters:
+        DestinationBucket: !Ref S3Bucket
+        DestinationBucketARN: !GetAtt S3Bucket.Arn
+        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
+        ManagementRoleName: !Sub "${RolePrefix}${ManagementAccountRole}"
+        ManagementAccountID: !Ref ManagementAccountID
+        GlueRoleARN: !GetAtt GlueRole.Arn
+        RolePrefix: !Ref RolePrefix
+        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
+      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-cost-explorer-rightsizing.yaml"
+  InventoryCollectorModule:
+    Type: AWS::CloudFormation::Stack
+    Condition: DeployInventoryCollectorModule
+    Properties:
+      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-inventory.yaml"
+      Parameters:
+        DestinationBucket: !Ref S3Bucket
+        DestinationBucketARN: !GetAtt S3Bucket.Arn
+        GlueRoleARN: !GetAtt GlueRole.Arn
+        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
+        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNTemplateSourceBucket ]
+        RolePrefix: !Ref RolePrefix
+        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
+  PricingModule:
+    Type: AWS::CloudFormation::Stack
+    Condition: DeployPricingModule
+    Properties:
+      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-pricing.yaml"
+      Parameters:
+        DestinationBucket: !Ref S3Bucket
+        DestinationBucketARN: !GetAtt S3Bucket.Arn
+        RolePrefix: !Ref RolePrefix
+        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNTemplateSourceBucket ]
+        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
+  ComputeOptimizerModule:
+    Type: AWS::CloudFormation::Stack
+    Condition: DeployComputeOptimizerModule
+    Properties:
+      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-compute-optimizer.yaml"
+      Parameters:
+        DestinationBucketARN: !GetAtt S3Bucket.Arn
+        DestinationBucket: !Ref S3Bucket
+        GlueRoleARN: !GetAtt GlueRole.Arn
+        ManagementRoleName: !Sub "${RolePrefix}${ManagementAccountRole}"
+        ManagementAccountID: !Ref ManagementAccountID
+        S3CrawlerQue: !GetAtt S3CrawlerQue.Arn
+        Schedule: !Ref Schedule
+        RolePrefix: !Ref RolePrefix
+        BucketPrefix:  !Ref DestinationBucket
+        DeployRegions:
+          Fn::If:
+            - ComputeOptimizerRegionsIsEmpty
+            - !Sub "${AWS::Region}"
+            - !Join [ '', !Split [ ' ', !Ref ComputeOptimizerRegions  ] ] # remove spaces
+        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
+  EcsChargebackModule:
+    Type: AWS::CloudFormation::Stack
+    Condition: DeployEcsChargebackModule
+    Properties:
+      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-ecs-chargeback.yaml"
+      Parameters:
+        DestinationBucket: !Ref S3Bucket
+        GlueRoleArn: !GetAtt GlueRole.Arn
+        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
+        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNTemplateSourceBucket ]
+        RolePrefix: !Ref RolePrefix
+        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
+  RDSUtilizationModule:
+    Type: AWS::CloudFormation::Stack
+    Condition: DeployRDSUtilizationModule
+    Properties:
+      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-rds-usage.yaml"
+      Parameters:
+        DestinationBucket: !Ref S3Bucket
+        DestinationBucketARN: !GetAtt S3Bucket.Arn
+        GlueRoleArn: !GetAtt GlueRole.Arn
+        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
+        RolePrefix: !Ref RolePrefix
+        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
+  OrgDataModule:
+    Type: AWS::CloudFormation::Stack
+    Condition: DeployOrgDataModule
+    Properties:
+      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-organization.yaml"
+      Parameters:
+        DestinationBucket: !Ref S3Bucket
+        GlueRoleARN: !GetAtt GlueRole.Arn
+        Schedule: !Ref Schedule
+        ManagementRoleName: !Sub "${RolePrefix}${ManagementAccountRole}"
+        ManagementAccountID: !Ref ManagementAccountID
+        RolePrefix: !Ref RolePrefix
+        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
+  BudgetsModule:
+    Type: AWS::CloudFormation::Stack
+    Condition: DeployBudgetsModule
+    Properties:
+      Parameters:
+        DestinationBucket: !Ref S3Bucket
+        DestinationBucketARN: !GetAtt S3Bucket.Arn
+        GlueRoleArn: !GetAtt GlueRole.Arn
+        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
+        RolePrefix: !Ref RolePrefix
+        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
+      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-budgets.yaml"
+  TransitGatewayModule:
+    Type: AWS::CloudFormation::Stack
+    Condition: DeployTransitGatewayModule
+    Properties:
+      Parameters:
+        DestinationBucket: !Ref S3Bucket
+        DestinationBucketARN: !GetAtt S3Bucket.Arn
+        GlueRoleArn: !GetAtt GlueRole.Arn
+        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
+        RolePrefix: !Ref RolePrefix
+        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
+      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-transit-gateway.yaml"
+  AccountCollector:
+    Type: AWS::CloudFormation::Stack
+    Condition: DeployAccountCollector
+    Properties:
+      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/account-collector.yaml"
+      Parameters:
+        RoleARN: !Sub "arn:aws:iam::${ManagementAccountID}:role/${RolePrefix}${ManagementAccountRole}"
+        ManagementRoleName: !Sub "${RolePrefix}${ManagementAccountRole}"
+        ManagementAccountID: !Ref ManagementAccountID
+        RolePrefix: !Ref RolePrefix
+        Schedule: !Ref Schedule
+        TaskQueuesUrl:
+          Fn::Join:
+            - ','
+            - - !If [DeployTAModule, !Sub "${TrustedAdvisorModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
+              - !If [DeployInventoryCollectorModule, !Sub "${InventoryCollectorModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
+              - !If [DeployEcsChargebackModule, !Sub "${EcsChargebackModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
+              - !If [DeployRDSUtilizationModule, !Sub "${RDSUtilizationModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
+              - !If [DeployBudgetsModule, !Sub "${BudgetsModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
+              - !If [DeployTransitGatewayModule, !Sub "${TransitGatewayModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
\ No newline at end of file
diff --git a/tests/data/templates/iam.yaml b/tests/data/templates/iam.yaml
new file mode 100644
index 0000000..bb85022
--- /dev/null
+++ b/tests/data/templates/iam.yaml
@@ -0,0 +1,25 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: "Creates an IAM policy"
+
+Conditions:
+  RetainBucket: !Equals [ "TRUE", "TRUE" ]
+
+Resources:
+
+  TestPolicy:
+    Type: AWS::IAM::Policy
+    Properties:
+      PolicyName: TestPolicy
+      PolicyDocument:
+        Version: 2012-10-17
+        Statement:
+          - !If
+            - RetainBucket
+            - Effect: Allow
+              Action:
+                - s3:*
+              Resource: "*"
+              Condition:
+                StringEquals:
+                  s3:x-amz-acl: "bucket-owner-full-control"
+            - !Ref "AWS::NoValue"
\ No newline at end of file
diff --git a/tests/data/templates/instance_state.yaml b/tests/data/templates/instance_state.yaml
new file mode 100644
index 0000000..6bc3c23
--- /dev/null
+++ b/tests/data/templates/instance_state.yaml
@@ -0,0 +1,26 @@
+Resources:
+  MyParameter:
+    Type: AWS::SSM::Parameter
+    Properties:
+      Name: /MyStack/MyParameter
+      Type: String
+      Value: 'default-value'
+  MyInstance:
+    Type: AWS::EC2::Instance
+    Properties:
+      ImageId: ami-01010101010101
+      InstanceType: t2.micro
+      UserData:
+        Fn::Base64: !Sub |
+          #!/bin/bash
+          echo "MyParameter value is ${MyParameter}" >> /var/log/user-data.log
+    Metadata:
+      AWS::CloudFormation::Init:
+        configSets:
+          default:
+            - myConfig
+        myConfig:
+          commands:
+            01_set_param:
+              command: !Sub |
+                aws ssm put-parameter --name /MyStack/MyParameter --value "new-value" --type "String"
\ No newline at end of file
diff --git a/tests/data/templates/latest.yaml b/tests/data/templates/latest.yaml
new file mode 100644
index 0000000..95d8846
--- /dev/null
+++ b/tests/data/templates/latest.yaml
@@ -0,0 +1,1099 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: Pushes logs, metrics and traces from AWS to Datadog.
+Mappings:
+  Constants:
+    DdForwarder:
+      Version: 3.73.0
+      LayerVersion: 30
+Parameters:
+  DdApiKey:
+    Type: String
+    NoEcho: true
+    Default: ""
+    Description: The Datadog API key, which can be found from the APIs page (/account/settings#api). It will be stored in AWS Secrets Manager securely. If DdApiKeySecretArn is also set, this value is ignored.
+  DdApiKeySecretArn:
+    Type: String
+    AllowedPattern: "arn:.*:secretsmanager:.*"
+    Default: "arn:aws:secretsmanager:DEFAULT"
+    Description: The ARN of the secret storing the Datadog API key, if you already have it stored in Secrets Manager. You must store the secret as a plaintext, rather than a key-value pair.
+  DdSite:
+    Type: String
+    Default: datadoghq.com
+    Description: Define your Datadog Site to send data to. Possible values are `datadoghq.com`, `datadoghq.eu`, `us3.datadoghq.com`, `us5.datadoghq.com` and `ddog-gov.com`.
+    AllowedPattern: .+
+    ConstraintDescription: DdSite is required
+  FunctionName:
+    Type: String
+    Default: DatadogForwarder
+    Description: The Datadog Forwarder Lambda function name. DO NOT change when updating an existing CloudFormation stack, otherwise the current forwarder function will be replaced and all the triggers will be lost.
+  MemorySize:
+    Type: Number
+    Default: 1024
+    MinValue: 128
+    MaxValue: 3008
+    Description: Memory size for the Datadog Forwarder Lambda function
+  Timeout:
+    Type: Number
+    Default: 120
+    Description: Timeout for the Datadog Forwarder Lambda function
+  TagsCacheTTLSeconds:
+    Type: Number
+    Default: 300
+    Description: TTL (in seconds) for the Datadog tags cache
+  ReservedConcurrency:
+    Type: String
+    AllowedPattern: "[0-9]*"
+    ConstraintDescription: ReservedConcurrency must be an integer number
+    Default: ""
+    Description: >-
+      Reserved concurrency for the Datadog Forwarder Lambda function. If empty, use unreserved account concurrency. 
+      We recommend using at least 10 reserved concurrency, but default to 0 as you may need to increase your limits for this.
+      If using unreserved account concurrency you may limit other lambda functions in your environment.
+  LogRetentionInDays:
+    Type: Number
+    Default: 90
+    Description: CloudWatch log retention for logs generated by the Datadog Forwarder Lambda function
+  SourceZipUrl:
+    Type: String
+    Default: ""
+    Description: DO NOT CHANGE unless you know what you are doing. Override the default location of the function source code.
+  InstallAsLayer:
+    Type: String
+    Default: true
+    Description: Whether to use the layer-based installation flow. Set to false to use our legacy installation flow, which installs a second function that copies the forwarder code from Github to an S3 bucket. Defaults to true.
+    AllowedValues:
+      - true
+      - false
+  LayerARN:
+    Type: String
+    Default: ""
+    Description: ARN for the layer containing the forwarder code. If empty, the script will use the version of the layer the forwarder was published with.
+  DdTags:
+    Type: String
+    Default: ""
+    Description: Add custom tags to forwarded logs, comma-delimited string, no trailing comma, e.g., env:prod,stack:classic
+  DdFetchLambdaTags:
+    Type: String
+    Default: true
+    AllowedValues:
+      - true
+      - false
+    Description: Let the forwarder fetch Lambda tags using GetResources API calls and apply them to logs, metrics and traces. If set to true, permission tag:GetResources will be automatically added to the Lambda execution IAM role. The tags are cached in memory and S3 so that they'll only be fetched when the function cold starts or when the TTL (1 hour) expires. The forwarder increments the aws.lambda.enhanced.get_resources_api_calls metric for each API call made.
+  DdFetchLogGroupTags:
+    Type: String
+    Default: true
+    AllowedValues:
+      - true
+      - false
+    Description: Let the forwarder fetch Log Group tags using ListTagsLogGroup and apply them to logs, metrics and traces. If set to true, permission logs:ListTagsLogGroup will be automatically added to the Lambda execution IAM role. The tags are cached in memory and S3 so that they'll only be fetched when the function cold starts or when the TTL (1 hour) expires. The forwarder increments the aws.lambda.enhanced.list_tags_log_group_api_call metric for each API call made.
+  DdFetchStepFunctionsTags:
+    Type: String
+    Default: true
+    AllowedValues:
+      - true
+      - false
+    Description: Let the forwarder fetch Step Functions tags using GetResources API calls and apply them to logs, metrics and traces. If set to true, permission tag:GetResources will be automatically added to the Lambda execution IAM role. The tags are cached in memory and S3 so that they'll only be fetched when the function cold starts or when the TTL (1 hour) expires. The forwarder increments the aws.lambda.enhanced.get_resources_api_calls metric for each API call made.
+  DdUseTcp:
+    Type: String
+    Default: false
+    AllowedValues:
+      - true
+      - false
+    Description: By default, the forwarder sends logs using HTTPS through the port 443. To send logs over an SSL encrypted TCP connection, set this parameter to true.
+  DdNoSsl:
+    Type: String
+    Default: false
+    AllowedValues:
+      - true
+      - false
+    Description: Disable SSL when forwarding logs, set to true when forwarding logs through a proxy.
+  DdUrl:
+    Type: String
+    Default: ""
+    Description: The endpoint URL to forward the logs to, useful for forwarding logs through a proxy
+  DdPort:
+    Type: String
+    Default: ""
+    Description: The endpoint port to forward the logs to, useful for forwarding logs through a proxy
+  DdSkipSslValidation:
+    Type: String
+    Default: false
+    AllowedValues:
+      - true
+      - false
+    Description: Send logs over HTTPS, while NOT validating the certificate provided by the endpoint. This will still encrypt the traffic between the forwarder and the log intake endpoint, but will not verify if the destination SSL certificate is valid.
+  RedactIp:
+    Type: String
+    Default: false
+    AllowedValues:
+      - true
+      - false
+    Description: Replace text matching \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} with xxx.xxx.xxx.xxx
+  RedactEmail:
+    Type: String
+    Default: false
+    AllowedValues:
+      - true
+      - false
+    Description: Replace text matching [a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+ with xxxxx@xxxxx.com
+  DdScrubbingRule:
+    Type: String
+    Default: ""
+    Description: Replace text matching the supplied regular expression with xxxxx (default) or DdScrubbingRuleReplacement (if supplied). Log scrubbing rule is applied to the full JSON-formatted log, including any metadata that is automatically added by the Lambda function. Each instance of a pattern match is replaced until no more matches are found in each log. Note, using inefficient regular expression, such as `.*`, may slow down the Lambda function.
+  DdScrubbingRuleReplacement:
+    Type: String
+    Default: ""
+    Description: Replace text matching DdScrubbingRule with the supplied text
+  ExcludeAtMatch:
+    Type: String
+    Default: ""
+    Description: DO NOT send logs matching the supplied regular expression. If a log matches both the ExcludeAtMatch and IncludeAtMatch, it is excluded. Filtering rules are applied to the full JSON-formatted log, including any metadata that is automatically added by the function. Note, using inefficient regular expression, such as `.*`, may slow down the Lambda function.
+  IncludeAtMatch:
+    Type: String
+    Default: ""
+    Description: Only send logs matching the supplied regular expression and not excluded by ExcludeAtMatch. Note, using inefficient regular expression, such as `.*`, may slow down the Lambda function.
+  DdMultilineLogRegexPattern:
+    Type: String
+    Default: ""
+    Description: Use the supplied regular expression to detect for a new log line for multiline logs from S3, e.g., use expression "\d{2}\/\d{2}\/\d{4}" for multiline logs beginning with pattern "11/10/2014".
+  DdForwardLog:
+    Type: String
+    Default: true
+    AllowedValues:
+      - true
+      - false
+    Description: Set to false to disable log forwarding, while continuing to forward other observability data, such as metrics and traces from Lambda functions.
+  DdUseCompression:
+    Type: String
+    Default: true
+    AllowedValues:
+      - true
+      - false
+    Description: Set to false to disable log compression. Only valid when sending logs over HTTP.
+  DdUsePrivateLink:
+    Type: String
+    Default: false
+    AllowedValues:
+      - true
+      - false
+    Description: DEPRECATED, DO NOT CHANGE. See README.md for details. Set to true to deploy the Forwarder to a VPC and send logs, metrics, and traces via AWS PrivateLink. When set to true, must also set VPCSecurityGroupIds and VPCSubnetIds.
+  DdUseVPC:
+    Type: String
+    Default: false
+    AllowedValues:
+      - true
+      - false
+    Description: Set to true to deploy the Forwarder to a VPC and send logs, metrics, and traces via a proxy. When set to true, must also set VPCSecurityGroupIds and VPCSubnetIds.
+  DdHttpProxyURL:
+    Type: String
+    Default: ""
+    Description: "Sets the standard web proxy environment variables HTTP_PROXY and HTTPS_PROXY. These are the url endpoints your proxy server exposes. Don't use this in combination with AWS Private Link. Make sure to also set DdSkipSslValidation to true."
+  DdNoProxy:
+    Type: String
+    Default: ""
+    Description: "Sets the standard web proxy environment variable NO_PROXY. It is a comma-separated list of domain names that should be excluded from the web proxy."
+  VPCSecurityGroupIds:
+    Type: CommaDelimitedList
+    Default: ""
+    Description: Comma separated list of VPC Security Group Ids. Used when DdUsePrivateLink or DdUseVPC is enabled.
+  VPCSubnetIds:
+    Type: CommaDelimitedList
+    Default: ""
+    Description: Comma separated list of VPC Subnet Ids. Used when DdUsePrivateLink or DdUseVPC is enabled.
+  DdCompressionLevel:
+    Type: Number
+    Default: 6
+    AllowedValues: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+    Description: Set the compression level from 0 (no compression) to 9 (best compression) when sending logs.
+  DdMaxWorkers:
+    Type: Number
+    Default: 20
+    Description: Set the max number of workers sending logs concurrently.
+  PermissionsBoundaryArn:
+    Type: String
+    Default: ""
+    Description: ARN for the Permissions Boundary Policy
+  AdditionalTargetLambdaArns:
+    Type: CommaDelimitedList
+    Default: ""
+    Description: Comma separated list of lambda ARNs that get invoked asynchronously with the same input event
+  DdApiUrl:
+    Type: String
+    Default: ""
+    Description: The endpoint URL to forward the metrics to, useful for forwarding metrics through a proxy
+  DdTraceIntakeUrl:
+    Type: String
+    Default: ""
+    Description: The endpoint URL to forward the traces to, useful for forwarding traces through a proxy
+  DdForwarderBucketName:
+    Type: String
+    Default: ""
+    Description: The name of the forwarder bucket to create. If not provided, AWS will generate a unique name.
+Conditions:
+  IsAWSChina:
+    Fn::Equals:
+      - Ref: AWS::Partition
+      - "aws-cn"
+  IsGovCloud:
+    Fn::Equals:
+      - Ref: AWS::Partition
+      - "aws-us-gov"
+  UseZipCopier:
+    Fn::Or:
+      - Condition: IsAWSChina
+      - Fn::And:
+          - Fn::Equals: [!Ref InstallAsLayer, "false"]
+          - Fn::Not:
+              - Condition: SetLayerARN
+  CreateDdApiKeySecret:
+    Fn::Equals:
+      - Ref: DdApiKeySecretArn
+      - "arn:aws:secretsmanager:DEFAULT"
+  SetFunctionName:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: FunctionName
+          - "DatadogForwarder"
+  SetSourceZipUrl:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: SourceZipUrl
+          - ""
+  SetS3SourceZip:
+    Fn::Equals:
+      - !Select [0, !Split ["/", !Ref SourceZipUrl]]
+      - "s3:"
+  SetDdTags:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: DdTags
+          - ""
+  SetDdUseTcp:
+    Fn::Equals:
+      - Ref: DdUseTcp
+      - true
+  SetDdNoSsl:
+    Fn::Equals:
+      - Ref: DdNoSsl
+      - true
+  SetDdUrl:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: DdUrl
+          - ""
+  SetDdPort:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: DdPort
+          - ""
+  SetRedactIp:
+    Fn::Equals:
+      - Ref: RedactIp
+      - true
+  SetRedactEmail:
+    Fn::Equals:
+      - Ref: RedactEmail
+      - true
+  SetDdScrubbingRule:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: DdScrubbingRule
+          - ""
+  SetDdScrubbingRuleReplacement:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: DdScrubbingRuleReplacement
+          - ""
+  SetExcludeAtMatch:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: ExcludeAtMatch
+          - ""
+  SetIncludeAtMatch:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: IncludeAtMatch
+          - ""
+  SetDdMultilineLogRegexPattern:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: DdMultilineLogRegexPattern
+          - ""
+  SetDdSkipSslValidation:
+    Fn::Equals:
+      - Ref: DdSkipSslValidation
+      - true
+  SetDdFetchLambdaTags:
+    Fn::Equals:
+      - Ref: DdFetchLambdaTags
+      - true
+  SetDdFetchLogGroupTags:
+    Fn::Equals:
+      - Ref: DdFetchLogGroupTags
+      - true
+  SetDdFetchStepFunctionsTags:
+    Fn::Equals:
+      - Ref: DdFetchStepFunctionsTags
+      - true
+  CreateS3BucketForTags:
+    Fn::Or:
+      - Fn::Equals:
+          - Ref: DdFetchLogGroupTags
+          - true
+      - Fn::Equals:
+          - Ref: DdFetchLambdaTags
+          - true
+  SetDdUsePrivateLink:
+    Fn::Equals:
+      - Ref: DdUsePrivateLink
+      - true
+  SetDdUseVPC:
+    Fn::Equals:
+      - Ref: DdUseVPC
+      - true
+  SetDdHttpProxyURL:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: DdHttpProxyURL
+          - ""
+  SetDdNoProxy:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: DdNoProxy
+          - ""
+  SetLayerARN:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: LayerARN
+          - ""
+  UseVPC:
+    Fn::Or:
+      - Condition: SetDdUsePrivateLink
+      - Condition: SetDdUseVPC
+  SetDdForwardLog:
+    Fn::Equals:
+      - Ref: DdForwardLog
+      - false
+  SetDdUseCompression:
+    Fn::Equals:
+      - Ref: DdUseCompression
+      - false
+  SetDdCompressionLevel:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: DdCompressionLevel
+          - 6
+  SetDdMaxWorkers:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: DdMaxWorkers
+          - 20
+  SetPermissionsBoundary:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: PermissionsBoundaryArn
+          - ""
+  SetAdditionalTargetLambdas:
+    Fn::Not:
+      - Fn::Equals:
+          - Fn::Join: ["", !Ref AdditionalTargetLambdaArns]
+          - ""
+  SetDdApiUrl:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: DdApiUrl
+          - ""
+  SetDdTraceIntakeUrl:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: DdTraceIntakeUrl
+          - ""
+  SetDdForwarderBucketName:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: DdForwarderBucketName
+          - ""
+  SetReservedConcurrentExecutions:
+    Fn::Not:
+      - Fn::Equals:
+          - Ref: ReservedConcurrency
+          - ""
+Rules:
+  MustSetDdApiKey:
+    Assertions:
+      - Assert:
+          Fn::Or:
+            - Fn::Not:
+                - Fn::Equals:
+                    - Ref: DdApiKey
+                    - ""
+            - Fn::Not:
+                - Fn::Equals:
+                    - Ref: DdApiKeySecretArn
+                    - "arn:aws:secretsmanager:DEFAULT"
+        AssertDescription: DdApiKey or DdApiKeySecretArn must be set
+Resources:
+  Forwarder:
+    Type: AWS::Lambda::Function
+    DependsOn: ForwarderZipReady
+    Properties:
+      FunctionName:
+        Fn::If:
+          - SetFunctionName
+          - Ref: FunctionName
+          - Ref: AWS::NoValue
+      Description: Pushes logs, metrics and traces from AWS to Datadog.
+      Role: !GetAtt "ForwarderRole.Arn"
+      Handler: lambda_function.lambda_handler
+      Layers:
+        Fn::If:
+          - UseZipCopier
+          - []
+          - - Fn::If:
+                - SetLayerARN
+                - !Ref LayerARN
+                - Fn::Join:
+                    - ":"
+                    - - arn
+                      - !Ref AWS::Partition
+                      - lambda
+                      - !Ref AWS::Region
+                      - Fn::If: [IsGovCloud, "002406178527", "464622532012"]
+                      - layer
+                      - Datadog-Forwarder
+                      - Fn::FindInMap: [Constants, DdForwarder, LayerVersion]
+
+      Code:
+        Fn::If:
+          - UseZipCopier
+          - S3Bucket: !Ref ForwarderBucket
+            S3Key:
+              Fn::Sub:
+                - "aws-dd-forwarder-${DdForwarderVersion}.zip"
+                - {
+                    DdForwarderVersion:
+                      !FindInMap [Constants, DdForwarder, Version],
+                  }
+          - ZipFile: " "
+
+      MemorySize:
+        Ref: MemorySize
+      Runtime: python3.8
+      Timeout:
+        Ref: Timeout
+      Tags:
+        - Key: "dd_forwarder_version"
+          Value: !FindInMap [Constants, DdForwarder, Version]
+      Environment:
+        Variables:
+          DD_ENHANCED_METRICS: "false"
+          DD_API_KEY_SECRET_ARN:
+            Fn::If:
+              - CreateDdApiKeySecret
+              - Ref: DdApiKeySecret
+              - Ref: DdApiKeySecretArn
+          DD_S3_BUCKET_NAME:
+            Fn::If:
+              - CreateS3BucketForTags
+              - Ref: ForwarderBucket
+              - Ref: AWS::NoValue
+          DD_SITE:
+            Ref: DdSite
+          DD_TAGS:
+            Fn::If:
+              - SetDdTags
+              - Ref: DdTags
+              - Ref: AWS::NoValue
+          DD_TAGS_CACHE_TTL_SECONDS:
+            Ref: TagsCacheTTLSeconds
+          DD_FETCH_LAMBDA_TAGS:
+            Fn::If:
+              - SetDdFetchLambdaTags
+              - Ref: DdFetchLambdaTags
+              - Ref: AWS::NoValue
+          DD_FETCH_LOG_GROUP_TAGS:
+            Fn::If:
+              - SetDdFetchLogGroupTags
+              - Ref: DdFetchLogGroupTags
+              - Ref: AWS::NoValue
+          DD_FETCH_STEP_FUNCTIONS_TAGS:
+            Fn::If:
+              - SetDdFetchStepFunctionsTags
+              - Ref: DdFetchStepFunctionsTags
+              - Ref: AWS::NoValue
+          DD_USE_TCP:
+            Fn::If:
+              - SetDdUseTcp
+              - Ref: DdUseTcp
+              - Ref: AWS::NoValue
+          DD_NO_SSL:
+            Fn::If:
+              - SetDdNoSsl
+              - Ref: DdNoSsl
+              - Ref: AWS::NoValue
+          DD_URL:
+            Fn::If:
+              - SetDdUrl
+              - Ref: DdUrl
+              - Ref: AWS::NoValue
+          DD_PORT:
+            Fn::If:
+              - SetDdPort
+              - Ref: DdPort
+              - Ref: AWS::NoValue
+          REDACT_IP:
+            Fn::If:
+              - SetRedactIp
+              - Ref: RedactIp
+              - Ref: AWS::NoValue
+          REDACT_EMAIL:
+            Fn::If:
+              - SetRedactEmail
+              - Ref: RedactEmail
+              - Ref: AWS::NoValue
+          DD_SCRUBBING_RULE:
+            Fn::If:
+              - SetDdScrubbingRule
+              - Ref: DdScrubbingRule
+              - Ref: AWS::NoValue
+          DD_SCRUBBING_RULE_REPLACEMENT:
+            Fn::If:
+              - SetDdScrubbingRuleReplacement
+              - Ref: DdScrubbingRuleReplacement
+              - Ref: AWS::NoValue
+          EXCLUDE_AT_MATCH:
+            Fn::If:
+              - SetExcludeAtMatch
+              - Ref: ExcludeAtMatch
+              - Ref: AWS::NoValue
+          INCLUDE_AT_MATCH:
+            Fn::If:
+              - SetIncludeAtMatch
+              - Ref: IncludeAtMatch
+              - Ref: AWS::NoValue
+          DD_MULTILINE_LOG_REGEX_PATTERN:
+            Fn::If:
+              - SetDdMultilineLogRegexPattern
+              - Ref: DdMultilineLogRegexPattern
+              - Ref: AWS::NoValue
+          DD_SKIP_SSL_VALIDATION:
+            Fn::If:
+              - SetDdSkipSslValidation
+              - Ref: DdSkipSslValidation
+              - Ref: AWS::NoValue
+          DD_FORWARD_LOG:
+            Fn::If:
+              - SetDdForwardLog
+              - Ref: DdForwardLog
+              - Ref: AWS::NoValue
+          DD_USE_COMPRESSION:
+            Fn::If:
+              - SetDdUseCompression
+              - Ref: DdUseCompression
+              - Ref: AWS::NoValue
+          DD_COMPRESSION_LEVEL:
+            Fn::If:
+              - SetDdCompressionLevel
+              - Ref: DdCompressionLevel
+              - Ref: AWS::NoValue
+          DD_MAX_WORKERS:
+            Fn::If:
+              - SetDdMaxWorkers
+              - Ref: DdMaxWorkers
+              - Ref: AWS::NoValue
+          DD_USE_PRIVATE_LINK:
+            Fn::If:
+              - SetDdUsePrivateLink
+              - true
+              - false
+          DD_USE_VPC:
+            Fn::If:
+              - UseVPC
+              - true
+              - false
+          HTTP_PROXY:
+            Fn::If:
+              - SetDdHttpProxyURL
+              - Ref: DdHttpProxyURL
+              - Ref: AWS::NoValue
+          HTTPS_PROXY:
+            Fn::If:
+              - SetDdHttpProxyURL
+              - Ref: DdHttpProxyURL
+              - Ref: AWS::NoValue
+          NO_PROXY:
+            Fn::If:
+              - SetDdNoProxy
+              - Ref: DdNoProxy
+              - Ref: AWS::NoValue
+          DD_ADDITIONAL_TARGET_LAMBDAS:
+            Fn::If:
+              - SetAdditionalTargetLambdas
+              - !Join
+                - ","
+                - !Ref AdditionalTargetLambdaArns
+              - !Ref AWS::NoValue
+          DD_API_URL:
+            Fn::If:
+              - SetDdApiUrl
+              - Ref: DdApiUrl
+              - Ref: AWS::NoValue
+          DD_TRACE_INTAKE_URL:
+            Fn::If:
+              - SetDdTraceIntakeUrl
+              - Ref: DdTraceIntakeUrl
+              - Ref: AWS::NoValue
+      ReservedConcurrentExecutions:
+        Fn::If:
+          - SetReservedConcurrentExecutions
+          - Ref: ReservedConcurrency
+          - Ref: AWS::NoValue
+      VpcConfig:
+        Fn::If:
+          - UseVPC
+          - SecurityGroupIds: !Ref VPCSecurityGroupIds
+            SubnetIds: !Ref VPCSubnetIds
+          - Ref: AWS::NoValue
+
+  ForwarderRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: "2012-10-17"
+        Statement:
+          - Action:
+              - sts:AssumeRole
+            Effect: Allow
+            Principal:
+              Service:
+                - lambda.amazonaws.com
+      ManagedPolicyArns:
+        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
+        - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
+      PermissionsBoundary:
+        Fn::If:
+          - SetPermissionsBoundary
+          - Ref: PermissionsBoundaryArn
+          - Ref: AWS::NoValue
+      Policies:
+        - PolicyName: ForwarderRolePolicy0
+          PolicyDocument:
+            Version: "2012-10-17"
+            Statement:
+              # Access the s3 bucket that is used by the forwarder as a datastore
+              - Action:
+                  - s3:GetObject
+                  - s3:PutObject
+                  - s3:DeleteObject
+                  - s3:ListBucket
+                Resource:
+                  - Fn::Join:
+                      - "/"
+                      - - Fn::GetAtt: ForwarderBucket.Arn
+                        - "*"
+                Effect: Allow
+              # Get the actual log content from the s3 bucket based on the received s3 event.
+              # Use PermissionsBoundaryArn to limit (allow/deny) access if needed.
+              - Action:
+                  - s3:GetObject
+                Resource: "*"
+                Effect: Allow
+              # To get object from encrypted s3 buckets. Use PermissionsBoundaryArn to limit access if needed.
+              # https://aws.amazon.com/premiumsupport/knowledge-center/s3-troubleshoot-403/#AWS_KMS_encryption
+              - Action:
+                  - kms:Decrypt
+                Resource: "*"
+                Effect: Allow
+              # Access the Datadog API key from Secrets Manager
+              - Action:
+                  - secretsmanager:GetSecretValue
+                Resource:
+                  Fn::If:
+                    - CreateDdApiKeySecret
+                    - Ref: DdApiKeySecret
+                    - Fn::Sub: "${DdApiKeySecretArn}*"
+                Effect: Allow
+              # Fetch Lambda resource tags for data enrichment
+              - Fn::If:
+                  - SetDdFetchLambdaTags
+                  - Action:
+                      - tag:GetResources
+                    Resource: "*"
+                    Effect: Allow
+                  - Ref: AWS::NoValue
+              # Get tags for log groups and attach them to the logs sent to Datadog
+              - Fn::If:
+                  - SetDdFetchLogGroupTags
+                  - Action:
+                      - logs:ListTagsLogGroup
+                    Resource: "*"
+                    Effect: Allow
+                  - Ref: AWS::NoValue
+              # Fetch Step Functions resource tags for data enrichment
+              - Fn::If:
+                  - SetDdFetchStepFunctionsTags
+                  - Action:
+                      - tag:GetResources
+                    Resource: "*"
+                    Effect: Allow
+                  - Ref: AWS::NoValue
+              # Required for Lambda deployed in VPC
+              - Fn::If:
+                  - UseVPC
+                  - Action:
+                      - ec2:CreateNetworkInterface
+                      - ec2:DescribeNetworkInterfaces
+                      - ec2:DeleteNetworkInterface
+                    Resource: "*"
+                    Effect: Allow
+                  - Ref: AWS::NoValue
+              # To invoke a follower Lambda with the same event received by the forwarder for dual-shipping
+              - Fn::If:
+                  - SetAdditionalTargetLambdas
+                  - Action:
+                      - lambda:InvokeFunction
+                    Resource:
+                      Ref: AdditionalTargetLambdaArns
+                    Effect: Allow
+                  - Ref: AWS::NoValue
+      Tags:
+        - Value:
+            Fn::FindInMap:
+              - Constants
+              - DdForwarder
+              - Version
+          Key: dd_forwarder_version
+
+  CloudWatchLogsPermission:
+    Type: AWS::Lambda::Permission
+    Properties:
+      FunctionName: !Ref "Forwarder"
+      Action: lambda:InvokeFunction
+      Principal:
+        Fn::If:
+          - IsAWSChina
+          - !Sub "logs.${AWS::Region}.amazonaws.com.cn"
+          - !Sub "logs.${AWS::Region}.amazonaws.com"
+      SourceAccount: !Ref "AWS::AccountId"
+  S3Permission:
+    Type: AWS::Lambda::Permission
+    Properties:
+      FunctionName: !Ref "Forwarder"
+      Action: lambda:InvokeFunction
+      Principal: "s3.amazonaws.com"
+      SourceAccount: !Ref "AWS::AccountId"
+  SNSPermission:
+    Type: AWS::Lambda::Permission
+    Properties:
+      FunctionName: !Ref "Forwarder"
+      Action: lambda:InvokeFunction
+      Principal: "sns.amazonaws.com"
+      SourceAccount: !Ref "AWS::AccountId"
+  CloudWatchEventsPermission:
+    Type: AWS::Lambda::Permission
+    Properties:
+      FunctionName: !Ref "Forwarder"
+      Action: lambda:InvokeFunction
+      Principal:
+        Fn::If:
+          - IsAWSChina
+          - "events.amazonaws.com.cn"
+          - "events.amazonaws.com"
+      SourceAccount: !Ref "AWS::AccountId"
+  LogGroup:
+    Type: AWS::Logs::LogGroup
+    Properties:
+      LogGroupName:
+        Fn::Sub: /aws/lambda/${Forwarder}
+      RetentionInDays:
+        Ref: LogRetentionInDays
+  DdApiKeySecret:
+    Type: AWS::SecretsManager::Secret
+    Condition: CreateDdApiKeySecret
+    Properties:
+      Description: Datadog API Key
+      SecretString:
+        Ref: DdApiKey
+  # A s3 bucket used by the Forwarder as a datastore
+  ForwarderBucket:
+    Type: AWS::S3::Bucket
+    Properties:
+      BucketName:
+        Fn::If:
+          - SetDdForwarderBucketName
+          - Ref: DdForwarderBucketName
+          - Ref: AWS::NoValue
+      BucketEncryption:
+        ServerSideEncryptionConfiguration:
+          - ServerSideEncryptionByDefault:
+              SSEAlgorithm: "aws:kms"
+      PublicAccessBlockConfiguration:
+        BlockPublicAcls: true
+        BlockPublicPolicy: true
+        IgnorePublicAcls: true
+        RestrictPublicBuckets: true
+  ForwarderBucketPolicy:
+    Type: "AWS::S3::BucketPolicy"
+    Properties:
+      Bucket: !Ref ForwarderBucket
+      PolicyDocument:
+        Version: "2012-10-17"
+        Statement:
+          - Sid: AllowSSLRequestsOnly
+            Action:
+              - s3:*
+            Effect: Deny
+            Resource:
+              - !Sub "${ForwarderBucket.Arn}"
+              - !Sub "${ForwarderBucket.Arn}/*"
+            Condition:
+              Bool:
+                "aws:SecureTransport": "false"
+            Principal: "*"
+  ForwarderZip:
+    Type: Custom::ForwarderZip
+    Condition: UseZipCopier
+    Properties:
+      ServiceToken: !GetAtt "ForwarderZipCopier.Arn"
+      DestZipsBucket: !Ref "ForwarderBucket"
+      SourceZipUrl:
+        Fn::If:
+          - SetSourceZipUrl
+          - !Ref SourceZipUrl
+          - Fn::Sub:
+              - "https://github.com/DataDog/datadog-serverless-functions/releases/download/aws-dd-forwarder-${DdForwarderVersion}/aws-dd-forwarder-${DdForwarderVersion}.zip"
+              - {
+                  DdForwarderVersion:
+                    !FindInMap [Constants, DdForwarder, Version],
+                }
+  # The Forwarder's source code is too big to fit the inline code size limit for CloudFormation. In most of AWS
+  # partitions and regions, the Forwarder is able to load its source code from a Lambda layer attached to it.
+  # In places where Datadog can't/doesn't yet publish Lambda layers, use another Lambda to copy the source code
+  # from github to a s3 bucket in the partition & region where the forwarder is deployed to.
+  ForwarderZipCopier:
+    Type: AWS::Lambda::Function
+    Condition: UseZipCopier
+    Properties:
+      Description: Copies Datadog Forwarder zip to the destination S3 bucket
+      Handler: index.handler
+      Runtime: python3.8
+      Timeout: 300
+      Code:
+        ZipFile: |
+          import json
+          import logging
+          import threading
+          import boto3
+          import urllib.request
+          import os
+
+          def send_cfn_resp(event, context, response_status):
+              resp_body = json.dumps({
+                  'Status': response_status,
+                  'Reason': f'See reasons in CloudWatch Logs - group: {context.log_group_name}, stream:{context.log_stream_name}',
+                  'PhysicalResourceId': context.log_stream_name,
+                  'StackId': event['StackId'],
+                  'RequestId': event['RequestId'],
+                  'LogicalResourceId': event['LogicalResourceId'],
+                  'Data': {}
+              }).encode('utf-8')
+              req = urllib.request.Request(url=event['ResponseURL'], data=resp_body, method='PUT')
+              with urllib.request.urlopen(req) as f:
+                  logging.info(f'Sent response to CloudFormation: {f.status}, {f.reason}')
+          def delete_zips(bucket):
+              s3 = boto3.resource('s3')
+              bucket = s3.Bucket(bucket)
+              bucket.objects.all().delete()
+          def copy_zip(source_zip_url, dest_zips_bucket):
+              s3 = boto3.client('s3')
+              s3_prelude = "s3://"
+              filename = "aws-dd-forwarder-{}.zip".format(os.environ.get("DD_FORWARDER_VERSION"))
+              if source_zip_url.startswith(s3_prelude):
+                  parts = source_zip_url[len(s3_prelude):].split('/')
+                  bucket = parts[0]
+                  key = '/'.join(parts[1:])
+                  response = s3.get_object(Bucket=bucket, Key=key)
+                  data = response["Body"]
+                  s3.upload_fileobj(data, dest_zips_bucket, filename)
+              else:
+                  with urllib.request.urlopen(source_zip_url) as data:
+                      s3.upload_fileobj(data, dest_zips_bucket, filename)
+          def timeout(event, context):
+              logging.error('Execution is about to time out, sending failure response to CloudFormation')
+              send_cfn_resp(event, context, 'FAILED')
+          def handler(event, context):
+              # make sure we send a failure to CloudFormation if the function
+              # is going to timeout
+              timer = threading.Timer((context.get_remaining_time_in_millis()
+                        / 1000.00) - 0.5, timeout, args=[event, context])
+              timer.start()
+              logging.info(f'Received event: {json.dumps(event)}')
+              try:
+                  source_zip_url = event['ResourceProperties']['SourceZipUrl']
+                  dest_zips_bucket = event['ResourceProperties']['DestZipsBucket']
+                  if event['RequestType'] == 'Delete':
+                      delete_zips(dest_zips_bucket)
+                  else:
+                      copy_zip(source_zip_url, dest_zips_bucket)
+              except Exception as e:
+                  logging.exception(f'Exception when copying zip from {source_zip_url} to {dest_zips_bucket}')
+                  send_cfn_resp(event, context, 'FAILED')
+              else:
+                  send_cfn_resp(event, context, 'SUCCESS')
+              finally:
+                  timer.cancel()
+      Environment:
+        Variables:
+          DD_FORWARDER_VERSION: !FindInMap [Constants, DdForwarder, Version]
+      Role: !GetAtt "ForwarderZipCopierRole.Arn"
+  ForwarderZipReady:
+    Type: AWS::CloudFormation::WaitConditionHandle
+    Metadata:
+      ForwarderZipCopierReady: !If [UseZipCopier, !Ref ForwarderZip, ""]
+  ForwarderZipCopierRole:
+    Type: AWS::IAM::Role
+    Condition: UseZipCopier
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: "2012-10-17"
+        Statement:
+          - Action:
+              - sts:AssumeRole
+            Effect: Allow
+            Principal:
+              Service:
+                - lambda.amazonaws.com
+      ManagedPolicyArns:
+        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
+        - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
+      PermissionsBoundary:
+        Fn::If:
+          - SetPermissionsBoundary
+          - Ref: PermissionsBoundaryArn
+          - Ref: AWS::NoValue
+      Policies:
+        - PolicyName: ForwarderZipCopierRolePolicy0
+          PolicyDocument:
+            Version: "2012-10-17"
+            Statement:
+              - Effect: Allow
+                Action:
+                  - s3:PutObject
+                  - s3:DeleteObject
+                Resource:
+                  - Fn::Join:
+                      - "/"
+                      - - Fn::GetAtt: "ForwarderBucket.Arn"
+                        - "*"
+              - Effect: Allow
+                Action:
+                  - s3:ListBucket
+                Resource:
+                  - Fn::GetAtt: "ForwarderBucket.Arn"
+              - !If
+                - SetS3SourceZip
+                - Effect: Allow
+                  Action:
+                    - s3:GetObject
+                  Resource:
+                    - Fn::Join:
+                        - ""
+                        - - "arn:*:s3:::"
+                          - !Select [1, !Split ["s3://", !Ref SourceZipUrl]]
+                - Ref: AWS::NoValue
+Outputs:
+  DatadogForwarderArn:
+    Description: Datadog Forwarder Lambda Function ARN
+    Value:
+      Fn::GetAtt:
+        - Forwarder
+        - Arn
+    Export:
+      Name:
+        Fn::Sub: ${AWS::StackName}-ForwarderArn
+  DdApiKeySecretArn:
+    Description: ARN of SecretsManager Secret with Datadog API Key
+    Value:
+      Ref: DdApiKeySecret
+    Export:
+      Name:
+        Fn::Sub: ${AWS::StackName}-ApiKeySecretArn
+    Condition: CreateDdApiKeySecret
+  ForwarderBucketName:
+    Description: Name of the S3 bucket used by the Forwarder
+    Value:
+      Ref: ForwarderBucket
+    Export:
+      Name:
+        Fn::Sub: ${AWS::StackName}-ForwarderBucketName
+Metadata:
+  AWS::CloudFormation::Interface:
+    ParameterGroups:
+      - Label:
+          default: Required
+        Parameters:
+          - DdApiKey
+          - DdApiKeySecretArn
+          - DdSite
+      - Label:
+          default: Lambda Function (Optional)
+        Parameters:
+          - FunctionName
+          - MemorySize
+          - Timeout
+          - ReservedConcurrency
+          - LogRetentionInDays
+      - Label:
+          default: Log Forwarding (Optional)
+        Parameters:
+          - DdTags
+          - DdMultilineLogRegexPattern
+          - DdUseTcp
+          - DdNoSsl
+          - DdUrl
+          - DdPort
+          - DdSkipSslValidation
+          - DdUseCompression
+          - DdCompressionLevel
+          - DdMaxWorkers
+          - DdForwardLog
+      - Label:
+          default: Log Scrubbing (Optional)
+        Parameters:
+          - RedactIp
+          - RedactEmail
+          - DdScrubbingRule
+          - DdScrubbingRuleReplacement
+      - Label:
+          default: Log Filtering (Optional)
+        Parameters:
+          - ExcludeAtMatch
+          - IncludeAtMatch
+      - Label:
+          default: Advanced (Optional)
+        Parameters:
+          - DdFetchLambdaTags
+          - DdFetchLogGroupTags
+          - DdFetchStepFunctionsTags
+          - TagsCacheTTLSeconds
+          - SourceZipUrl
+          - InstallAsLayer
+          - LayerARN
+          - PermissionsBoundaryArn
+          - DdUsePrivateLink
+          - DdUseVPC
+          - DdHttpProxyURL
+          - DdNoProxy
+          - VPCSecurityGroupIds
+          - VPCSubnetIds
+          - DdApiUrl
+          - DdTraceIntakeUrl
+          - AdditionalTargetLambdaArns
+          - DdForwarderBucketName
+    ParameterLabels:
+      DdApiKey:
+        default: "DdApiKey *"
+      DdApiKeySecretArn:
+        default: "DdApiKeySecretArn *"
+      DdSite:
+        default: "DdSite *"
diff --git a/tests/data/templates/live-streaming-on-aws.template b/tests/data/templates/live-streaming-on-aws.template
new file mode 100644
index 0000000..a910260
--- /dev/null
+++ b/tests/data/templates/live-streaming-on-aws.template
@@ -0,0 +1,2885 @@
+{
+  "Description": "(SO0013) Live Streaming on AWS Solution v4.2.1",
+  "Metadata": {
+    "AWS::CloudFormation::Interface": {
+      "ParameterGroups": [
+        {
+          "Label": {
+            "default": "LIVE STREAM SOURCE"
+          },
+          "Parameters": [
+            "InputType"
+          ]
+        },
+        {
+          "Label": {
+            "default": "URL_PULL and RTMP_PULL CONFIGURATION"
+          },
+          "Parameters": [
+            "PriPullUrl",
+            "PriPullUser",
+            "PriPullPass",
+            "SecPullUrl",
+            "SecPullUser",
+            "SecPullPass"
+          ]
+        },
+        {
+          "Label": {
+            "default": "RTP_PUSH / RTMP_PUSH CONFIGURATION"
+          },
+          "Parameters": [
+            "InputCIDR"
+          ]
+        },
+        {
+          "Label": {
+            "default": "MEDIACONNECT CONFIGURATION"
+          },
+          "Parameters": [
+            "PriMediaConnectArn",
+            "SecMediaConnectArn"
+          ]
+        },
+        {
+          "Label": {
+            "default": "ENCODING OPTIONS"
+          },
+          "Parameters": [
+            "EncodingProfile",
+            "ChannelStart"
+          ]
+        }
+      ],
+      "ParameterLabels": {
+        "InputType": {
+          "default": "Source Input Type"
+        },
+        "EncodingProfile": {
+          "default": "Encoding Profile"
+        },
+        "InputCIDR": {
+          "default": "Input CIDR Block"
+        },
+        "PriPullUrl": {
+          "default": "Primary Source URL"
+        },
+        "PriPullUser": {
+          "default": "Primary Source Username"
+        },
+        "PriPullPass": {
+          "default": "Primary Source Password"
+        },
+        "SecPullUrl": {
+          "default": "Secondary Source URL"
+        },
+        "SecPullUser": {
+          "default": "Secondary Source Username"
+        },
+        "SecPullPass": {
+          "default": "Secondary Source Password"
+        },
+        "PriMediaConnectArn": {
+          "default": "Primary MediaConnect Arn"
+        },
+        "SecMediaConnectArn": {
+          "default": "Secondary MediaConnect Arn"
+        },
+        "ChannelStart": {
+          "default": "Start MediaLive Channel"
+        }
+      }
+    }
+  },
+  "Parameters": {
+    "InputType": {
+      "Type": "String",
+      "Default": "URL_PULL",
+      "AllowedValues": [
+        "RTP_PUSH",
+        "RTMP_PUSH",
+        "RTMP_PULL",
+        "URL_PULL",
+        "MEDIACONNECT"
+      ],
+      "Description": "Specify the input type for MediaLive. Detailed instructions for each input type can be found here https://docs.aws.amazon.com/solutions/latest/live-streaming-on-aws/appendix-a.html"
+    },
+    "EncodingProfile": {
+      "Type": "String",
+      "Default": "HD-720p",
+      "AllowedValues": [
+        "HD-1080p",
+        "HD-720p",
+        "SD-540p"
+      ],
+      "Description": "Select an encoding profile. HD 1080p [1920x1080, 1280x720, 960x540, 768x432, 640x360, 512x288] HD 720p [1280x720, 960x540, 768x432, 640x360, 512x288] SD 540p [960x540, 768x432, 640x360, 512x288]  See the implementation guide for details https://docs.aws.amazon.com/solutions/latest/live-streaming/considerations.html"
+    },
+    "InputCIDR": {
+      "Type": "String",
+      "Default": "",
+      "Description": "For RTP and RTMP PUSH input types ONLY, specify the CIDR Block for the MediaLive SecurityGroup. Input security group restricts access to the input and prevents unauthorized third parties from pushing content into a channel that is associated with that input."
+    },
+    "PriPullUrl": {
+      "Type": "String",
+      "Default": "https://d15an60oaeed9r.cloudfront.net/live_stream_v2/sports_reel_with_markers.m3u8",
+      "Description": "For URL_PULL and RTMP_PULL input types ONLY, specify the primary source URL."
+    },
+    "PriPullUser": {
+      "Type": "String",
+      "Default": "",
+      "Description": "For URL_PULL and RTMP_PULL input types ONLY, specify a username for the primary source URL if basic authentication is enabled on the source."
+    },
+    "PriPullPass": {
+      "Type": "String",
+      "Default": "",
+      "Description": "For URL_PULL and RTMP_PULL input types ONLY, specify a password for the primary source URL if basic authentication is enabled on the source.",
+      "NoEcho": true
+    },
+    "SecPullUrl": {
+      "Type": "String",
+      "Default": "https://d3h5srgm8b0t83.cloudfront.net/live_stream_v2/sports_reel_with_markers.m3u8",
+      "Description": "For URL_PULL and RTMP_PULL input types ONLY, specify the secondary source URL, this should be a HTTP or HTTPS link to the stream manifest file."
+    },
+    "SecPullUser": {
+      "Type": "String",
+      "Default": "",
+      "Description": "For URL_PULL and RTMP_PULL input types ONLY, specify a username for the secondary source URL if basic authentication is enabled on the source."
+    },
+    "SecPullPass": {
+      "Type": "String",
+      "Default": "",
+      "Description": "For URL_PULL and RTMP_PULL input types ONLY, specify a password for the secondary source URL if basic authentication is enabled on the source.",
+      "NoEcho": true
+    },
+    "PriMediaConnectArn": {
+      "Type": "String",
+      "Default": "",
+      "Description": "For MediaConnect Input type ONLY, specify the ARN for MediaConnect as the primary source. This flow has to be in a different availability zone as the secondary source."
+    },
+    "SecMediaConnectArn": {
+      "Type": "String",
+      "Default": "",
+      "Description": "For MediaConnect Input type ONLY, specify the ARN for MediaConnect as the secondary source. This flow has to be in a different availability zone as the primary source."
+    },
+    "ChannelStart": {
+      "Type": "String",
+      "Default": "No",
+      "AllowedValues": [
+        "Yes",
+        "No"
+      ],
+      "Description": "If your source is ready to stream, select true. This will start the MediaLive Channel as part of the deployment. If you select false, you will need to manually start the MediaLive Channel when your source is ready."
+    }
+  },
+  "Mappings": {
+    "AnonymizedData": {
+      "SendAnonymizedData": {
+        "Data": "Yes"
+      }
+    }
+  },
+  "Resources": {
+    "MediaLiveRole1149D189": {
+      "Type": "AWS::IAM::Role",
+      "Properties": {
+        "AssumeRolePolicyDocument": {
+          "Statement": [
+            {
+              "Action": "sts:AssumeRole",
+              "Effect": "Allow",
+              "Principal": {
+                "Service": "medialive.amazonaws.com"
+              }
+            }
+          ],
+          "Version": "2012-10-17"
+        },
+        "Tags": [
+          {
+            "Key": "SolutionId",
+            "Value": "SO0013"
+          }
+        ]
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/MediaLiveRole/Resource"
+      }
+    },
+    "MediaLivePolicy09DE3C9B": {
+      "Type": "AWS::IAM::Policy",
+      "Properties": {
+        "PolicyDocument": {
+          "Statement": [
+            {
+              "Action": [
+                "mediastore:DeleteObject",
+                "mediastore:DescribeObject",
+                "mediastore:GetObject",
+                "mediastore:ListContainers",
+                "mediastore:PutObject"
+              ],
+              "Effect": "Allow",
+              "Resource": {
+                "Fn::Join": [
+                  "",
+                  [
+                    "arn:",
+                    {
+                      "Ref": "AWS::Partition"
+                    },
+                    ":mediastore:",
+                    {
+                      "Ref": "AWS::Region"
+                    },
+                    ":",
+                    {
+                      "Ref": "AWS::AccountId"
+                    },
+                    ":*"
+                  ]
+                ]
+              }
+            },
+            {
+              "Action": [
+                "mediaconnect:ManagedDescribeFlow",
+                "mediaconnect:ManagedAddOutput",
+                "mediaconnect:ManagedRemoveOutput"
+              ],
+              "Effect": "Allow",
+              "Resource": {
+                "Fn::Join": [
+                  "",
+                  [
+                    "arn:",
+                    {
+                      "Ref": "AWS::Partition"
+                    },
+                    ":mediaconnect:",
+                    {
+                      "Ref": "AWS::Region"
+                    },
+                    ":",
+                    {
+                      "Ref": "AWS::AccountId"
+                    },
+                    ":*"
+                  ]
+                ]
+              }
+            },
+            {
+              "Action": [
+                "ec2:DescribeSubnets",
+                "ec2:DescribeNetworkInterfaces",
+                "ec2:CreateNetworkInterface",
+                "ec2:CreateNetworkInterfacePermission",
+                "ec2:DeleteNetworkInterface",
+                "ec2:DeleteNetworkInterfacePermission",
+                "ec2:DescribeSecurityGroups"
+              ],
+              "Effect": "Allow",
+              "Resource": {
+                "Fn::Join": [
+                  "",
+                  [
+                    "arn:",
+                    {
+                      "Ref": "AWS::Partition"
+                    },
+                    ":ec2:",
+                    {
+                      "Ref": "AWS::Region"
+                    },
+                    ":",
+                    {
+                      "Ref": "AWS::AccountId"
+                    },
+                    ":*"
+                  ]
+                ]
+              }
+            },
+            {
+              "Action": [
+                "logs:CreateLogGroup",
+                "logs:CreateLogStream",
+                "logs:PutLogEvents",
+                "logs:DescribeLogStreams",
+                "logs:DescribeLogGroups"
+              ],
+              "Effect": "Allow",
+              "Resource": {
+                "Fn::Join": [
+                  "",
+                  [
+                    "arn:",
+                    {
+                      "Ref": "AWS::Partition"
+                    },
+                    ":logs:*:*:*"
+                  ]
+                ]
+              }
+            },
+            {
+              "Action": "mediapackage:DescribeChannel",
+              "Effect": "Allow",
+              "Resource": {
+                "Fn::Join": [
+                  "",
+                  [
+                    "arn:",
+                    {
+                      "Ref": "AWS::Partition"
+                    },
+                    ":mediapackage:",
+                    {
+                      "Ref": "AWS::Region"
+                    },
+                    ":",
+                    {
+                      "Ref": "AWS::AccountId"
+                    },
+                    ":channels/*"
+                  ]
+                ]
+              }
+            }
+          ],
+          "Version": "2012-10-17"
+        },
+        "PolicyName": "MediaLivePolicy09DE3C9B",
+        "Roles": [
+          {
+            "Ref": "MediaLiveRole1149D189"
+          }
+        ]
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/MediaLivePolicy/Resource",
+        "cdk_nag": {
+          "rules_to_suppress": [
+            {
+              "reason": "Resource ARNs are not generated at the time of policy creation",
+              "id": "AwsSolutions-IAM5"
+            }
+          ]
+        }
+      }
+    },
+    "MediaPackageRoleEFC5C05A": {
+      "Type": "AWS::IAM::Role",
+      "Properties": {
+        "AssumeRolePolicyDocument": {
+          "Statement": [
+            {
+              "Action": "sts:AssumeRole",
+              "Effect": "Allow",
+              "Principal": {
+                "Service": "mediapackage.amazonaws.com"
+              }
+            }
+          ],
+          "Version": "2012-10-17"
+        },
+        "Tags": [
+          {
+            "Key": "SolutionId",
+            "Value": "SO0013"
+          }
+        ]
+      },
+      "Metadata": {
+        "cfn_nag": {
+          "rules_to_suppress": [
+            {
+              "id": "F38",
+              "reason": "Resource level permission is not supported by getRole"
+            },
+            {
+              "id": "W11",
+              "reason": "* is required for MediaPackage CDN Authorization: https://docs.aws.amazon.com/mediapackage/latest/ug/setting-up-create-trust-rel-policy-cdn.html"
+            }
+          ]
+        }
+      }
+    },
+    "CustomResourceRoleAB1EF463": {
+      "Type": "AWS::IAM::Role",
+      "Properties": {
+        "AssumeRolePolicyDocument": {
+          "Statement": [
+            {
+              "Action": "sts:AssumeRole",
+              "Effect": "Allow",
+              "Principal": {
+                "Service": "lambda.amazonaws.com"
+              }
+            }
+          ],
+          "Version": "2012-10-17"
+        },
+        "Tags": [
+          {
+            "Key": "SolutionId",
+            "Value": "SO0013"
+          }
+        ]
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/CustomResourceRole/Resource"
+      }
+    },
+    "CustomResourcePolicy79526710": {
+      "Type": "AWS::IAM::Policy",
+      "Properties": {
+        "PolicyDocument": {
+          "Statement": [
+            {
+              "Action": [
+                "medialive:DescribeInputSecurityGroup",
+                "medialive:CreateInputSecurityGroup",
+                "medialive:DescribeInput",
+                "medialive:CreateInput",
+                "medialive:DeleteInput",
+                "medialive:StopChannel",
+                "medialive:CreateChannel",
+                "medialive:DeleteChannel",
+                "medialive:DeleteInputSecurityGroup",
+                "medialive:DescribeChannel",
+                "medialive:StartChannel",
+                "medialive:CreateTags",
+                "medialive:DeleteTags"
+              ],
+              "Effect": "Allow",
+              "Resource": {
+                "Fn::Join": [
+                  "",
+                  [
+                    "arn:",
+                    {
+                      "Ref": "AWS::Partition"
+                    },
+                    ":medialive:",
+                    {
+                      "Ref": "AWS::Region"
+                    },
+                    ":",
+                    {
+                      "Ref": "AWS::AccountId"
+                    },
+                    ":*"
+                  ]
+                ]
+              }
+            },
+            {
+              "Action": [
+                "mediapackage:CreateChannel",
+                "mediapackage:DeleteChannel",
+                "mediapackage:ListOriginEndpoints",
+                "mediapackage:DeleteOriginEndpoint",
+                "mediapackage:CreateOriginEndpoint",
+                "mediapackage:TagResource",
+                "mediapackage:UntagResource"
+              ],
+              "Effect": "Allow",
+              "Resource": {
+                "Fn::Join": [
+                  "",
+                  [
+                    "arn:",
+                    {
+                      "Ref": "AWS::Partition"
+                    },
+                    ":mediapackage:",
+                    {
+                      "Ref": "AWS::Region"
+                    },
+                    ":",
+                    {
+                      "Ref": "AWS::AccountId"
+                    },
+                    ":*"
+                  ]
+                ]
+              }
+            },
+            {
+              "Action": [
+                "logs:CreateLogGroup",
+                "logs:CreateLogStream",
+                "logs:PutLogEvents"
+              ],
+              "Effect": "Allow",
+              "Resource": {
+                "Fn::Join": [
+                  "",
+                  [
+                    "arn:",
+                    {
+                      "Ref": "AWS::Partition"
+                    },
+                    ":logs:",
+                    {
+                      "Ref": "AWS::Region"
+                    },
+                    ":",
+                    {
+                      "Ref": "AWS::AccountId"
+                    },
+                    ":log-group:/aws/lambda/*"
+                  ]
+                ]
+              }
+            },
+            {
+              "Action": "ssm:PutParameter",
+              "Effect": "Allow",
+              "Resource": {
+                "Fn::Join": [
+                  "",
+                  [
+                    "arn:",
+                    {
+                      "Ref": "AWS::Partition"
+                    },
+                    ":ssm:",
+                    {
+                      "Ref": "AWS::Region"
+                    },
+                    ":",
+                    {
+                      "Ref": "AWS::AccountId"
+                    },
+                    ":parameter/*"
+                  ]
+                ]
+              }
+            },
+            {
+              "Action": "iam:PassRole",
+              "Effect": "Allow",
+              "Resource": {
+                "Fn::GetAtt": [
+                  "MediaLiveRole1149D189",
+                  "Arn"
+                ]
+              }
+            },
+            {
+              "Action": "iam:PassRole",
+              "Effect": "Allow",
+              "Resource": {
+                "Fn::GetAtt": [
+                  "MediaPackageRoleEFC5C05A",
+                  "Arn"
+                ]
+              }
+            }
+          ],
+          "Version": "2012-10-17"
+        },
+        "PolicyName": "CustomResourcePolicy79526710",
+        "Roles": [
+          {
+            "Ref": "CustomResourceRoleAB1EF463"
+          }
+        ]
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/CustomResourcePolicy/Resource",
+        "cdk_nag": {
+          "rules_to_suppress": [
+            {
+              "reason": "Resource ARNs are not generated at the time of policy creation",
+              "id": "AwsSolutions-IAM5"
+            }
+          ]
+        }
+      }
+    },
+    "CustomResource8CDCD7A7": {
+      "Type": "AWS::Lambda::Function",
+      "Properties": {
+        "Code": {
+          "S3Bucket": {
+            "Fn::Sub": "solutions-${AWS::Region}"
+          },
+          "S3Key": "live-streaming-on-aws/v4.2.1/assete5cb04e3258d0d09befa44c70b305dc5f1cea7680ece701e4175fa6798a489e1.zip"
+        },
+        "Role": {
+          "Fn::GetAtt": [
+            "CustomResourceRoleAB1EF463",
+            "Arn"
+          ]
+        },
+        "Description": "Used to deploy custom resources and send AnonymizedData",
+        "Environment": {
+          "Variables": {
+            "SOLUTION_IDENTIFIER": "AwsSolution/SO0013/v4.2.1"
+          }
+        },
+        "Handler": "index.handler",
+        "Runtime": "nodejs18.x",
+        "Tags": [
+          {
+            "Key": "SolutionId",
+            "Value": "SO0013"
+          }
+        ],
+        "Timeout": 30
+      },
+      "DependsOn": [
+        "CustomResourcePolicy79526710",
+        "CustomResourceRoleAB1EF463"
+      ],
+      "Metadata": {
+        "cfn_nag": {
+          "rules_to_suppress": [
+            {
+              "id": "W58",
+              "reason": "Invalid warning: function has access to cloudwatch"
+            },
+            {
+              "id": "W89",
+              "reason": "This CustomResource does not need to be deployed inside a VPC"
+            },
+            {
+              "id": "W92",
+              "reason": "This CustomResource does not need to define ReservedConcurrentExecutions to reserve simultaneous executions"
+            }
+          ]
+        }
+      }
+    },
+    "UUID": {
+      "Type": "AWS::CloudFormation::CustomResource",
+      "Properties": {
+        "ServiceToken": {
+          "Fn::GetAtt": [
+            "CustomResource8CDCD7A7",
+            "Arn"
+          ]
+        },
+        "Resource": "UUID"
+      },
+      "UpdateReplacePolicy": "Delete",
+      "DeletionPolicy": "Delete",
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/UUID/Default"
+      }
+    },
+    "CdnSecret32008E6A": {
+      "Type": "AWS::SecretsManager::Secret",
+      "Properties": {
+        "Description": {
+          "Fn::Join": [
+            "",
+            [
+              "CDN authorization string value for ",
+              {
+                "Ref": "AWS::StackName"
+              },
+              " Live Streaming Deployment"
+            ]
+          ]
+        },
+        "GenerateSecretString": {
+          "GenerateStringKey": "password",
+          "SecretStringTemplate": {
+            "Fn::Join": [
+              "",
+              [
+                "{\"MediaPackageCDNIdentifier\":\"",
+                {
+                  "Fn::GetAtt": [
+                    "UUID",
+                    "UUID"
+                  ]
+                },
+                "\"}"
+              ]
+            ]
+          }
+        },
+        "Tags": [
+          {
+            "Key": "SolutionId",
+            "Value": "SO0013"
+          }
+        ]
+      },
+      "UpdateReplacePolicy": "Delete",
+      "DeletionPolicy": "Delete",
+      "Metadata": {
+        "cfn_nag": {
+          "rules_to_suppress": [
+            {
+              "id": "W77",
+              "reason": "Using default keys as this is uuid and not a password. MediaPackage CDN Authorization: https://docs.aws.amazon.com/mediapackage/latest/ug/setting-up-create-trust-rel-policy-cdn.html"
+            }
+          ]
+        },
+        "cdk_nag": {
+          "rules_to_suppress": [
+            {
+              "reason": "MediaPackage requires a static value and is not integrated with CDN for automatic rotation: https://docs.aws.amazon.com/mediapackage/latest/ug/cdn-auth-setup.html#cdn-aut-setup-cdn",
+              "id": "AwsSolutions-SMG4"
+            }
+          ]
+        }
+      }
+    },
+    "MediaPackagePolicyCBD9468D": {
+      "Type": "AWS::IAM::Policy",
+      "Properties": {
+        "PolicyDocument": {
+          "Statement": [
+            {
+              "Action": [
+                "secretsmanager:GetSecretValue",
+                "secretsmanager:DescribeSecret",
+                "secretsmanager:ListSecrets",
+                "secretsmanager:ListSecretVersionIds"
+              ],
+              "Effect": "Allow",
+              "Resource": {
+                "Ref": "CdnSecret32008E6A"
+              }
+            },
+            {
+              "Action": [
+                "iam:GetRole",
+                "iam:PassRole"
+              ],
+              "Effect": "Allow",
+              "Resource": "*"
+            }
+          ],
+          "Version": "2012-10-17"
+        },
+        "PolicyName": "MediaPackagePolicyCBD9468D",
+        "Roles": [
+          {
+            "Ref": "MediaPackageRoleEFC5C05A"
+          }
+        ]
+      },
+      "Metadata": {
+        "cfn_nag": {
+          "rules_to_suppress": [
+            {
+              "id": "F39",
+              "reason": "Resource level permission is not supported by getRole"
+            },
+            {
+              "id": "W12",
+              "reason": "* is required for MediaPackage CDN Authorization: https://docs.aws.amazon.com/mediapackage/latest/ug/setting-up-create-trust-rel-policy-cdn.html"
+            }
+          ]
+        },
+        "cdk_nag": {
+          "rules_to_suppress": [
+            {
+              "reason": "* is required for MediaPackage CDN Authorization: https://docs.aws.amazon.com/mediapackage/latest/ug/setting-up-create-trust-rel-policy-cdn.html",
+              "id": "AwsSolutions-IAM5"
+            }
+          ]
+        }
+      }
+    },
+    "MediaLiveInput": {
+      "Type": "AWS::CloudFormation::CustomResource",
+      "Properties": {
+        "ServiceToken": {
+          "Fn::GetAtt": [
+            "CustomResource8CDCD7A7",
+            "Arn"
+          ]
+        },
+        "Resource": "MediaLiveInput",
+        "StreamName": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-livestream"
+            ]
+          ]
+        },
+        "Type": {
+          "Ref": "InputType"
+        },
+        "Cidr": {
+          "Ref": "InputCIDR"
+        },
+        "PriUrl": {
+          "Ref": "PriPullUrl"
+        },
+        "PriUser": {
+          "Ref": "PriPullUser"
+        },
+        "PriPass": {
+          "Ref": "PriPullPass"
+        },
+        "SecUrl": {
+          "Ref": "SecPullUrl"
+        },
+        "SecUser": {
+          "Ref": "SecPullUser"
+        },
+        "SecPass": {
+          "Ref": "SecPullPass"
+        },
+        "RoleArn": {
+          "Fn::GetAtt": [
+            "MediaLiveRole1149D189",
+            "Arn"
+          ]
+        },
+        "PriMediaConnectArn": {
+          "Ref": "PriMediaConnectArn"
+        },
+        "SecMediaConnectArn": {
+          "Ref": "SecMediaConnectArn"
+        }
+      },
+      "UpdateReplacePolicy": "Delete",
+      "DeletionPolicy": "Delete",
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/MediaLiveInput/Default"
+      }
+    },
+    "MediaLiveChannel": {
+      "Type": "AWS::CloudFormation::CustomResource",
+      "Properties": {
+        "ServiceToken": {
+          "Fn::GetAtt": [
+            "CustomResource8CDCD7A7",
+            "Arn"
+          ]
+        },
+        "Resource": "MediaLiveChannel",
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-livestream"
+            ]
+          ]
+        },
+        "EncodingProfile": {
+          "Ref": "EncodingProfile"
+        },
+        "Codec": "AVC",
+        "Role": {
+          "Fn::GetAtt": [
+            "MediaLiveRole1149D189",
+            "Arn"
+          ]
+        },
+        "InputId": {
+          "Fn::GetAtt": [
+            "MediaLiveInput",
+            "Id"
+          ]
+        },
+        "Type": {
+          "Ref": "InputType"
+        },
+        "MediaPackageChannelId": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-livestream"
+            ]
+          ]
+        }
+      },
+      "UpdateReplacePolicy": "Delete",
+      "DeletionPolicy": "Delete",
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/MediaLiveChannel/Default"
+      }
+    },
+    "MediaLiveChannelStart": {
+      "Type": "AWS::CloudFormation::CustomResource",
+      "Properties": {
+        "ServiceToken": {
+          "Fn::GetAtt": [
+            "CustomResource8CDCD7A7",
+            "Arn"
+          ]
+        },
+        "Resource": "MediaLiveChannelStart",
+        "ChannelId": {
+          "Fn::GetAtt": [
+            "MediaLiveChannel",
+            "ChannelId"
+          ]
+        },
+        "ChannelStart": {
+          "Ref": "ChannelStart"
+        }
+      },
+      "UpdateReplacePolicy": "Delete",
+      "DeletionPolicy": "Delete",
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/MediaLiveChannelStart/Default"
+      }
+    },
+    "MediaPackageChannel": {
+      "Type": "AWS::CloudFormation::CustomResource",
+      "Properties": {
+        "ServiceToken": {
+          "Fn::GetAtt": [
+            "CustomResource8CDCD7A7",
+            "Arn"
+          ]
+        },
+        "Resource": "MediaPackageChannel",
+        "ChannelId": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-livestream"
+            ]
+          ]
+        }
+      },
+      "UpdateReplacePolicy": "Delete",
+      "DeletionPolicy": "Delete",
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/MediaPackageChannel/Default"
+      }
+    },
+    "MediaPackageHlsEndpoint": {
+      "Type": "AWS::CloudFormation::CustomResource",
+      "Properties": {
+        "ServiceToken": {
+          "Fn::GetAtt": [
+            "CustomResource8CDCD7A7",
+            "Arn"
+          ]
+        },
+        "Resource": "MediaPackageEndPoint",
+        "EndPoint": "HLS",
+        "ChannelId": {
+          "Fn::GetAtt": [
+            "MediaPackageChannel",
+            "ChannelId"
+          ]
+        },
+        "SecretsRoleArn": {
+          "Fn::GetAtt": [
+            "MediaPackageRoleEFC5C05A",
+            "Arn"
+          ]
+        },
+        "CdnIdentifierSecret": {
+          "Ref": "CdnSecret32008E6A"
+        }
+      },
+      "DependsOn": [
+        "MediaPackagePolicyCBD9468D"
+      ],
+      "UpdateReplacePolicy": "Delete",
+      "DeletionPolicy": "Delete",
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/MediaPackageHlsEndpoint/Default"
+      }
+    },
+    "MediaPackageDashEndpoint": {
+      "Type": "AWS::CloudFormation::CustomResource",
+      "Properties": {
+        "ServiceToken": {
+          "Fn::GetAtt": [
+            "CustomResource8CDCD7A7",
+            "Arn"
+          ]
+        },
+        "Resource": "MediaPackageEndPoint",
+        "EndPoint": "DASH",
+        "ChannelId": {
+          "Fn::GetAtt": [
+            "MediaPackageChannel",
+            "ChannelId"
+          ]
+        },
+        "SecretsRoleArn": {
+          "Fn::GetAtt": [
+            "MediaPackageRoleEFC5C05A",
+            "Arn"
+          ]
+        },
+        "CdnIdentifierSecret": {
+          "Ref": "CdnSecret32008E6A"
+        }
+      },
+      "DependsOn": [
+        "MediaPackagePolicyCBD9468D"
+      ],
+      "UpdateReplacePolicy": "Delete",
+      "DeletionPolicy": "Delete",
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/MediaPackageDashEndpoint/Default"
+      }
+    },
+    "MediaPackageCmafEndpoint": {
+      "Type": "AWS::CloudFormation::CustomResource",
+      "Properties": {
+        "ServiceToken": {
+          "Fn::GetAtt": [
+            "CustomResource8CDCD7A7",
+            "Arn"
+          ]
+        },
+        "Resource": "MediaPackageEndPoint",
+        "EndPoint": "CMAF",
+        "ChannelId": {
+          "Fn::GetAtt": [
+            "MediaPackageChannel",
+            "ChannelId"
+          ]
+        },
+        "SecretsRoleArn": {
+          "Fn::GetAtt": [
+            "MediaPackageRoleEFC5C05A",
+            "Arn"
+          ]
+        },
+        "CdnIdentifierSecret": {
+          "Ref": "CdnSecret32008E6A"
+        }
+      },
+      "DependsOn": [
+        "MediaPackagePolicyCBD9468D"
+      ],
+      "UpdateReplacePolicy": "Delete",
+      "DeletionPolicy": "Delete",
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/MediaPackageCmafEndpoint/Default"
+      }
+    },
+    "LogsBucket9C4D8843": {
+      "Type": "AWS::S3::Bucket",
+      "Properties": {
+        "BucketEncryption": {
+          "ServerSideEncryptionConfiguration": [
+            {
+              "ServerSideEncryptionByDefault": {
+                "SSEAlgorithm": "AES256"
+              }
+            }
+          ]
+        },
+        "OwnershipControls": {
+          "Rules": [
+            {
+              "ObjectOwnership": "ObjectWriter"
+            }
+          ]
+        },
+        "PublicAccessBlockConfiguration": {
+          "BlockPublicAcls": true,
+          "BlockPublicPolicy": true,
+          "IgnorePublicAcls": true,
+          "RestrictPublicBuckets": true
+        },
+        "Tags": [
+          {
+            "Key": "SolutionId",
+            "Value": "SO0013"
+          }
+        ],
+        "VersioningConfiguration": {
+          "Status": "Enabled"
+        }
+      },
+      "UpdateReplacePolicy": "Retain",
+      "DeletionPolicy": "Retain",
+      "Metadata": {
+        "cfn_nag": {
+          "rules_to_suppress": [
+            {
+              "id": "W35",
+              "reason": "Used to store access logs for other buckets"
+            },
+            {
+              "id": "W51",
+              "reason": "Bucket is private and does not need a bucket policy"
+            }
+          ]
+        },
+        "cdk_nag": {
+          "rules_to_suppress": [
+            {
+              "reason": "Used to store access logs for other buckets",
+              "id": "AwsSolutions-S1"
+            },
+            {
+              "reason": "Bucket is private and is not using HTTP",
+              "id": "AwsSolutions-S10"
+            }
+          ]
+        }
+      }
+    },
+    "LogsBucketPolicyD70D9252": {
+      "Type": "AWS::S3::BucketPolicy",
+      "Properties": {
+        "Bucket": {
+          "Ref": "LogsBucket9C4D8843"
+        },
+        "PolicyDocument": {
+          "Statement": [
+            {
+              "Action": "s3:*",
+              "Condition": {
+                "Bool": {
+                  "aws:SecureTransport": "false"
+                }
+              },
+              "Effect": "Deny",
+              "Principal": {
+                "AWS": "*"
+              },
+              "Resource": [
+                {
+                  "Fn::GetAtt": [
+                    "LogsBucket9C4D8843",
+                    "Arn"
+                  ]
+                },
+                {
+                  "Fn::Join": [
+                    "",
+                    [
+                      {
+                        "Fn::GetAtt": [
+                          "LogsBucket9C4D8843",
+                          "Arn"
+                        ]
+                      },
+                      "/*"
+                    ]
+                  ]
+                }
+              ]
+            }
+          ],
+          "Version": "2012-10-17"
+        }
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/LogsBucket/Policy/Resource"
+      }
+    },
+    "CachePolicy26D8A535": {
+      "Type": "AWS::CloudFront::CachePolicy",
+      "Properties": {
+        "CachePolicyConfig": {
+          "DefaultTTL": 86400,
+          "MaxTTL": 31536000,
+          "MinTTL": 0,
+          "Name": {
+            "Fn::Join": [
+              "",
+              [
+                "CachePolicy-",
+                {
+                  "Ref": "AWS::StackName"
+                },
+                "-",
+                {
+                  "Ref": "AWS::Region"
+                }
+              ]
+            ]
+          },
+          "ParametersInCacheKeyAndForwardedToOrigin": {
+            "CookiesConfig": {
+              "CookieBehavior": "all"
+            },
+            "EnableAcceptEncodingBrotli": false,
+            "EnableAcceptEncodingGzip": false,
+            "HeadersConfig": {
+              "HeaderBehavior": "whitelist",
+              "Headers": [
+                "Access-Control-Allow-Origin",
+                "Access-Control-Request-Method",
+                "Access-Control-Request-Header",
+                "Origin"
+              ]
+            },
+            "QueryStringsConfig": {
+              "QueryStringBehavior": "all"
+            }
+          }
+        }
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/CachePolicy/Resource"
+      }
+    },
+    "CloudFrontD198EC06": {
+      "Type": "AWS::CloudFront::Distribution",
+      "Properties": {
+        "DistributionConfig": {
+          "CustomErrorResponses": [
+            {
+              "ErrorCachingMinTTL": 1,
+              "ErrorCode": 400
+            },
+            {
+              "ErrorCachingMinTTL": 1,
+              "ErrorCode": 403
+            },
+            {
+              "ErrorCachingMinTTL": 1,
+              "ErrorCode": 404
+            },
+            {
+              "ErrorCachingMinTTL": 1,
+              "ErrorCode": 405
+            },
+            {
+              "ErrorCachingMinTTL": 1,
+              "ErrorCode": 414
+            },
+            {
+              "ErrorCachingMinTTL": 1,
+              "ErrorCode": 416
+            },
+            {
+              "ErrorCachingMinTTL": 1,
+              "ErrorCode": 500
+            },
+            {
+              "ErrorCachingMinTTL": 1,
+              "ErrorCode": 501
+            },
+            {
+              "ErrorCachingMinTTL": 1,
+              "ErrorCode": 502
+            },
+            {
+              "ErrorCachingMinTTL": 1,
+              "ErrorCode": 503
+            },
+            {
+              "ErrorCachingMinTTL": 1,
+              "ErrorCode": 504
+            }
+          ],
+          "DefaultCacheBehavior": {
+            "AllowedMethods": [
+              "GET",
+              "HEAD",
+              "OPTIONS"
+            ],
+            "CachePolicyId": {
+              "Ref": "CachePolicy26D8A535"
+            },
+            "CachedMethods": [
+              "GET",
+              "HEAD",
+              "OPTIONS"
+            ],
+            "Compress": true,
+            "TargetOriginId": "LiveStreamingCloudFrontOrigin15ADC200A",
+            "ViewerProtocolPolicy": "allow-all"
+          },
+          "Enabled": true,
+          "HttpVersion": "http2",
+          "IPV6Enabled": true,
+          "Logging": {
+            "Bucket": {
+              "Fn::GetAtt": [
+                "LogsBucket9C4D8843",
+                "RegionalDomainName"
+              ]
+            },
+            "Prefix": "cloudfront-logs/"
+          },
+          "Origins": [
+            {
+              "CustomOriginConfig": {
+                "OriginProtocolPolicy": "https-only",
+                "OriginSSLProtocols": [
+                  "TLSv1.2"
+                ]
+              },
+              "DomainName": {
+                "Fn::GetAtt": [
+                  "MediaPackageHlsEndpoint",
+                  "DomainName"
+                ]
+              },
+              "Id": "LiveStreamingCloudFrontOrigin15ADC200A",
+              "OriginCustomHeaders": [
+                {
+                  "HeaderName": "X-MediaPackage-CDNIdentifier",
+                  "HeaderValue": {
+                    "Fn::GetAtt": [
+                      "UUID",
+                      "UUID"
+                    ]
+                  }
+                }
+              ]
+            }
+          ]
+        },
+        "Tags": [
+          {
+            "Key": "mediapackage:cloudfront_assoc",
+            "Value": {
+              "Fn::GetAtt": [
+                "MediaPackageChannel",
+                "Arn"
+              ]
+            }
+          },
+          {
+            "Key": "SolutionId",
+            "Value": "SO0013"
+          }
+        ]
+      },
+      "Metadata": {
+        "cfn_nag": {
+          "rules_to_suppress": [
+            {
+              "id": "W70",
+              "reason": "CloudFront automatically sets the security policy to TLSv1 when the distribution uses the CloudFront domain name (CloudFrontDefaultCertificate=true)"
+            }
+          ]
+        },
+        "cdk_nag": {
+          "rules_to_suppress": [
+            {
+              "reason": "Use case does not warrant CloudFront Geo restriction",
+              "id": "AwsSolutions-CFR1"
+            },
+            {
+              "reason": "Use case does not warrant CloudFront integration with AWS WAF",
+              "id": "AwsSolutions-CFR2"
+            },
+            {
+              "reason": "CloudFront automatically sets the security policy to TLSv1 when the distribution uses the CloudFront domain name",
+              "id": "AwsSolutions-CFR4"
+            },
+            {
+              "reason": "CloudFront automatically sets the security policy to TLSv1 when the distribution uses the CloudFront domain name",
+              "id": "AwsSolutions-CFR5"
+            }
+          ]
+        }
+      }
+    },
+    "CloudFrontToS3S3LoggingBucketEF5CD8B2": {
+      "Type": "AWS::S3::Bucket",
+      "Properties": {
+        "BucketEncryption": {
+          "ServerSideEncryptionConfiguration": [
+            {
+              "ServerSideEncryptionByDefault": {
+                "SSEAlgorithm": "AES256"
+              }
+            }
+          ]
+        },
+        "PublicAccessBlockConfiguration": {
+          "BlockPublicAcls": true,
+          "BlockPublicPolicy": true,
+          "IgnorePublicAcls": true,
+          "RestrictPublicBuckets": true
+        },
+        "Tags": [
+          {
+            "Key": "SolutionId",
+            "Value": "SO0013"
+          }
+        ]
+      },
+      "UpdateReplacePolicy": "Retain",
+      "DeletionPolicy": "Retain",
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/CloudFrontToS3/S3LoggingBucket/Resource",
+        "cfn_nag": {
+          "rules_to_suppress": [
+            {
+              "id": "W35",
+              "reason": "This S3 bucket is used as the access logging bucket for another bucket"
+            }
+          ]
+        },
+        "cdk_nag": {
+          "rules_to_suppress": [
+            {
+              "reason": "Used to store access logs for other buckets",
+              "id": "AwsSolutions-S1"
+            }
+          ]
+        }
+      }
+    },
+    "CloudFrontToS3S3LoggingBucketPolicy360F3875": {
+      "Type": "AWS::S3::BucketPolicy",
+      "Properties": {
+        "Bucket": {
+          "Ref": "CloudFrontToS3S3LoggingBucketEF5CD8B2"
+        },
+        "PolicyDocument": {
+          "Statement": [
+            {
+              "Action": "s3:*",
+              "Condition": {
+                "Bool": {
+                  "aws:SecureTransport": "false"
+                }
+              },
+              "Effect": "Deny",
+              "Principal": {
+                "AWS": "*"
+              },
+              "Resource": [
+                {
+                  "Fn::GetAtt": [
+                    "CloudFrontToS3S3LoggingBucketEF5CD8B2",
+                    "Arn"
+                  ]
+                },
+                {
+                  "Fn::Join": [
+                    "",
+                    [
+                      {
+                        "Fn::GetAtt": [
+                          "CloudFrontToS3S3LoggingBucketEF5CD8B2",
+                          "Arn"
+                        ]
+                      },
+                      "/*"
+                    ]
+                  ]
+                }
+              ]
+            },
+            {
+              "Action": "s3:PutObject",
+              "Condition": {
+                "ArnLike": {
+                  "aws:SourceArn": {
+                    "Fn::GetAtt": [
+                      "CloudFrontToS3S3Bucket9CE6AB04",
+                      "Arn"
+                    ]
+                  }
+                },
+                "StringEquals": {
+                  "aws:SourceAccount": {
+                    "Ref": "AWS::AccountId"
+                  }
+                }
+              },
+              "Effect": "Allow",
+              "Principal": {
+                "Service": "logging.s3.amazonaws.com"
+              },
+              "Resource": {
+                "Fn::Join": [
+                  "",
+                  [
+                    {
+                      "Fn::GetAtt": [
+                        "CloudFrontToS3S3LoggingBucketEF5CD8B2",
+                        "Arn"
+                      ]
+                    },
+                    "/*"
+                  ]
+                ]
+              }
+            }
+          ],
+          "Version": "2012-10-17"
+        }
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/CloudFrontToS3/S3LoggingBucket/Policy/Resource"
+      }
+    },
+    "CloudFrontToS3S3Bucket9CE6AB04": {
+      "Type": "AWS::S3::Bucket",
+      "Properties": {
+        "BucketEncryption": {
+          "ServerSideEncryptionConfiguration": [
+            {
+              "ServerSideEncryptionByDefault": {
+                "SSEAlgorithm": "AES256"
+              }
+            }
+          ]
+        },
+        "LoggingConfiguration": {
+          "DestinationBucketName": {
+            "Ref": "CloudFrontToS3S3LoggingBucketEF5CD8B2"
+          }
+        },
+        "PublicAccessBlockConfiguration": {
+          "BlockPublicAcls": true,
+          "BlockPublicPolicy": true,
+          "IgnorePublicAcls": true,
+          "RestrictPublicBuckets": true
+        },
+        "Tags": [
+          {
+            "Key": "SolutionId",
+            "Value": "SO0013"
+          }
+        ]
+      },
+      "UpdateReplacePolicy": "Retain",
+      "DeletionPolicy": "Retain",
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/CloudFrontToS3/S3Bucket/Resource"
+      }
+    },
+    "CloudFrontToS3S3BucketPolicy2495300D": {
+      "Type": "AWS::S3::BucketPolicy",
+      "Properties": {
+        "Bucket": {
+          "Ref": "CloudFrontToS3S3Bucket9CE6AB04"
+        },
+        "PolicyDocument": {
+          "Statement": [
+            {
+              "Action": "s3:*",
+              "Condition": {
+                "Bool": {
+                  "aws:SecureTransport": "false"
+                }
+              },
+              "Effect": "Deny",
+              "Principal": {
+                "AWS": "*"
+              },
+              "Resource": [
+                {
+                  "Fn::GetAtt": [
+                    "CloudFrontToS3S3Bucket9CE6AB04",
+                    "Arn"
+                  ]
+                },
+                {
+                  "Fn::Join": [
+                    "",
+                    [
+                      {
+                        "Fn::GetAtt": [
+                          "CloudFrontToS3S3Bucket9CE6AB04",
+                          "Arn"
+                        ]
+                      },
+                      "/*"
+                    ]
+                  ]
+                }
+              ]
+            },
+            {
+              "Action": "s3:GetObject",
+              "Effect": "Allow",
+              "Principal": {
+                "CanonicalUser": {
+                  "Fn::GetAtt": [
+                    "CloudFrontToS3CloudFrontDistributionOrigin1S3OriginB0637B8F",
+                    "S3CanonicalUserId"
+                  ]
+                }
+              },
+              "Resource": {
+                "Fn::Join": [
+                  "",
+                  [
+                    {
+                      "Fn::GetAtt": [
+                        "CloudFrontToS3S3Bucket9CE6AB04",
+                        "Arn"
+                      ]
+                    },
+                    "/*"
+                  ]
+                ]
+              }
+            }
+          ],
+          "Version": "2012-10-17"
+        }
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/CloudFrontToS3/S3Bucket/Policy/Resource",
+        "cfn_nag": {
+          "rules_to_suppress": [
+            {
+              "id": "F16",
+              "reason": "Public website bucket policy requires a wildcard principal"
+            }
+          ]
+        }
+      }
+    },
+    "CloudFrontToS3CloudfrontLoggingBucket8350BE9B": {
+      "Type": "AWS::S3::Bucket",
+      "Properties": {
+        "AccessControl": "LogDeliveryWrite",
+        "BucketEncryption": {
+          "ServerSideEncryptionConfiguration": [
+            {
+              "ServerSideEncryptionByDefault": {
+                "SSEAlgorithm": "AES256"
+              }
+            }
+          ]
+        },
+        "OwnershipControls": {
+          "Rules": [
+            {
+              "ObjectOwnership": "ObjectWriter"
+            }
+          ]
+        },
+        "PublicAccessBlockConfiguration": {
+          "BlockPublicAcls": true,
+          "BlockPublicPolicy": true,
+          "IgnorePublicAcls": true,
+          "RestrictPublicBuckets": true
+        },
+        "Tags": [
+          {
+            "Key": "SolutionId",
+            "Value": "SO0013"
+          }
+        ]
+      },
+      "UpdateReplacePolicy": "Retain",
+      "DeletionPolicy": "Retain",
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/CloudFrontToS3/CloudfrontLoggingBucket/Resource",
+        "cfn_nag": {
+          "rules_to_suppress": [
+            {
+              "id": "W35",
+              "reason": "This S3 bucket is used as the access logging bucket for CloudFront Distribution"
+            }
+          ]
+        },
+        "cdk_nag": {
+          "rules_to_suppress": [
+            {
+              "reason": "Used to store access logs for other buckets",
+              "id": "AwsSolutions-S1"
+            }
+          ]
+        }
+      }
+    },
+    "CloudFrontToS3CloudfrontLoggingBucketPolicy416B82D9": {
+      "Type": "AWS::S3::BucketPolicy",
+      "Properties": {
+        "Bucket": {
+          "Ref": "CloudFrontToS3CloudfrontLoggingBucket8350BE9B"
+        },
+        "PolicyDocument": {
+          "Statement": [
+            {
+              "Action": "s3:*",
+              "Condition": {
+                "Bool": {
+                  "aws:SecureTransport": "false"
+                }
+              },
+              "Effect": "Deny",
+              "Principal": {
+                "AWS": "*"
+              },
+              "Resource": [
+                {
+                  "Fn::GetAtt": [
+                    "CloudFrontToS3CloudfrontLoggingBucket8350BE9B",
+                    "Arn"
+                  ]
+                },
+                {
+                  "Fn::Join": [
+                    "",
+                    [
+                      {
+                        "Fn::GetAtt": [
+                          "CloudFrontToS3CloudfrontLoggingBucket8350BE9B",
+                          "Arn"
+                        ]
+                      },
+                      "/*"
+                    ]
+                  ]
+                }
+              ]
+            }
+          ],
+          "Version": "2012-10-17"
+        }
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/CloudFrontToS3/CloudfrontLoggingBucket/Policy/Resource"
+      }
+    },
+    "CloudFrontToS3CloudFrontDistributionOrigin1S3OriginB0637B8F": {
+      "Type": "AWS::CloudFront::CloudFrontOriginAccessIdentity",
+      "Properties": {
+        "CloudFrontOriginAccessIdentityConfig": {
+          "Comment": "Identity for LiveStreamingCloudFrontToS3CloudFrontDistributionOrigin1940508AB"
+        }
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/CloudFrontToS3/CloudFrontDistribution/Origin1/S3Origin/Resource"
+      }
+    },
+    "CloudFrontToS3CloudFrontDistribution241D9866": {
+      "Type": "AWS::CloudFront::Distribution",
+      "Properties": {
+        "DistributionConfig": {
+          "CustomErrorResponses": [
+            {
+              "ErrorCode": 404,
+              "ResponseCode": 200,
+              "ResponsePagePath": "/index.html"
+            },
+            {
+              "ErrorCode": 403,
+              "ResponseCode": 200,
+              "ResponsePagePath": "/index.html"
+            }
+          ],
+          "DefaultCacheBehavior": {
+            "AllowedMethods": [
+              "GET",
+              "HEAD",
+              "OPTIONS",
+              "PUT",
+              "PATCH",
+              "POST",
+              "DELETE"
+            ],
+            "CachePolicyId": {
+              "Ref": "CachePolicy26D8A535"
+            },
+            "CachedMethods": [
+              "GET",
+              "HEAD",
+              "OPTIONS"
+            ],
+            "Compress": true,
+            "TargetOriginId": "LiveStreamingCloudFrontToS3CloudFrontDistributionOrigin1940508AB",
+            "ViewerProtocolPolicy": "redirect-to-https"
+          },
+          "DefaultRootObject": "index.html",
+          "Enabled": true,
+          "HttpVersion": "http2",
+          "IPV6Enabled": true,
+          "Logging": {
+            "Bucket": {
+              "Fn::GetAtt": [
+                "CloudFrontToS3CloudfrontLoggingBucket8350BE9B",
+                "RegionalDomainName"
+              ]
+            }
+          },
+          "Origins": [
+            {
+              "DomainName": {
+                "Fn::GetAtt": [
+                  "CloudFrontToS3S3Bucket9CE6AB04",
+                  "RegionalDomainName"
+                ]
+              },
+              "Id": "LiveStreamingCloudFrontToS3CloudFrontDistributionOrigin1940508AB",
+              "S3OriginConfig": {
+                "OriginAccessIdentity": {
+                  "Fn::Join": [
+                    "",
+                    [
+                      "origin-access-identity/cloudfront/",
+                      {
+                        "Ref": "CloudFrontToS3CloudFrontDistributionOrigin1S3OriginB0637B8F"
+                      }
+                    ]
+                  ]
+                }
+              }
+            }
+          ]
+        },
+        "Tags": [
+          {
+            "Key": "SolutionId",
+            "Value": "SO0013"
+          }
+        ]
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/CloudFrontToS3/CloudFrontDistribution/Resource",
+        "cfn_nag": {
+          "rules_to_suppress": [
+            {
+              "id": "W70",
+              "reason": "Since the distribution uses the CloudFront domain name, CloudFront automatically sets the security policy to TLSv1 regardless of the value of MinimumProtocolVersion"
+            }
+          ]
+        },
+        "cdk_nag": {
+          "rules_to_suppress": [
+            {
+              "reason": "Use case does not warrant CloudFront Geo restriction",
+              "id": "AwsSolutions-CFR1"
+            },
+            {
+              "reason": "Use case does not warrant CloudFront integration with AWS WAF",
+              "id": "AwsSolutions-CFR2"
+            },
+            {
+              "reason": "CloudFront automatically sets the security policy to TLSv1 when the distribution uses the CloudFront domain name",
+              "id": "AwsSolutions-CFR4"
+            }
+          ]
+        }
+      }
+    },
+    "DemoIAMPolicy2174894A": {
+      "Type": "AWS::IAM::Policy",
+      "Properties": {
+        "PolicyDocument": {
+          "Statement": [
+            {
+              "Action": [
+                "s3:putObject",
+                "s3:getObject",
+                "s3:deleteObject",
+                "s3:listBucket"
+              ],
+              "Effect": "Allow",
+              "Resource": [
+                {
+                  "Fn::GetAtt": [
+                    "CloudFrontToS3S3Bucket9CE6AB04",
+                    "Arn"
+                  ]
+                },
+                {
+                  "Fn::Join": [
+                    "",
+                    [
+                      "arn:",
+                      {
+                        "Ref": "AWS::Partition"
+                      },
+                      ":s3:::",
+                      {
+                        "Ref": "CloudFrontToS3S3Bucket9CE6AB04"
+                      },
+                      "/*"
+                    ]
+                  ]
+                }
+              ]
+            },
+            {
+              "Action": "s3:getObject",
+              "Effect": "Allow",
+              "Resource": [
+                {
+                  "Fn::Join": [
+                    "",
+                    [
+                      "arn:",
+                      {
+                        "Ref": "AWS::Partition"
+                      },
+                      ":s3:::solutions-",
+                      {
+                        "Ref": "AWS::Region"
+                      }
+                    ]
+                  ]
+                },
+                {
+                  "Fn::Join": [
+                    "",
+                    [
+                      "arn:",
+                      {
+                        "Ref": "AWS::Partition"
+                      },
+                      ":s3:::solutions-",
+                      {
+                        "Ref": "AWS::Region"
+                      },
+                      "/*"
+                    ]
+                  ]
+                }
+              ]
+            }
+          ],
+          "Version": "2012-10-17"
+        },
+        "PolicyName": "DemoIAMPolicy2174894A",
+        "Roles": [
+          {
+            "Ref": "CustomResourceRoleAB1EF463"
+          }
+        ]
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/DemoIAMPolicy/Resource",
+        "cdk_nag": {
+          "rules_to_suppress": [
+            {
+              "reason": "Lambda role needs access to all contents within the buckets to load files for hosting the web player",
+              "id": "AwsSolutions-IAM5"
+            }
+          ]
+        }
+      }
+    },
+    "DemoConsole": {
+      "Type": "AWS::CloudFormation::CustomResource",
+      "Properties": {
+        "ServiceToken": {
+          "Fn::GetAtt": [
+            "CustomResource8CDCD7A7",
+            "Arn"
+          ]
+        },
+        "Resource": "DemoConsole",
+        "srcBucket": {
+          "Fn::Join": [
+            "",
+            [
+              "solutions-",
+              {
+                "Ref": "AWS::Region"
+              }
+            ]
+          ]
+        },
+        "srcPath": "live-streaming-on-aws/v4.2.1",
+        "manifestFile": "console-manifest.json",
+        "destBucket": {
+          "Ref": "CloudFrontToS3S3Bucket9CE6AB04"
+        },
+        "awsExports": {
+          "Fn::Join": [
+            "",
+            [
+              "//Configuration file generated by cloudformation\n        const awsExports = {\n          mediaLiveConsole: 'https://console.aws.amazon.com/medialive/home?region=",
+              {
+                "Ref": "AWS::Region"
+              },
+              "#/channels/",
+              {
+                "Fn::GetAtt": [
+                  "MediaLiveChannel",
+                  "ChannelId"
+                ]
+              },
+              "',\n          hls_manifest: 'https://",
+              {
+                "Fn::GetAtt": [
+                  "CloudFrontD198EC06",
+                  "DomainName"
+                ]
+              },
+              "/out/v1",
+              {
+                "Fn::GetAtt": [
+                  "MediaPackageHlsEndpoint",
+                  "Manifest"
+                ]
+              },
+              "',\n          dash_manifest: 'https://",
+              {
+                "Fn::GetAtt": [
+                  "CloudFrontD198EC06",
+                  "DomainName"
+                ]
+              },
+              "/out/v1",
+              {
+                "Fn::GetAtt": [
+                  "MediaPackageDashEndpoint",
+                  "Manifest"
+                ]
+              },
+              "',\n          cmaf_manifest: 'https://",
+              {
+                "Fn::GetAtt": [
+                  "CloudFrontD198EC06",
+                  "DomainName"
+                ]
+              },
+              "/out/v1",
+              {
+                "Fn::GetAtt": [
+                  "MediaPackageCmafEndpoint",
+                  "Manifest"
+                ]
+              },
+              "'\n        }"
+            ]
+          ]
+        }
+      },
+      "DependsOn": [
+        "CloudFrontToS3CloudFrontDistributionOrigin1S3OriginB0637B8F",
+        "CloudFrontToS3CloudFrontDistribution241D9866",
+        "CloudFrontToS3CloudfrontLoggingBucketPolicy416B82D9",
+        "CloudFrontToS3CloudfrontLoggingBucket8350BE9B",
+        "CloudFrontToS3S3BucketPolicy2495300D",
+        "CloudFrontToS3S3Bucket9CE6AB04",
+        "CloudFrontToS3S3LoggingBucketPolicy360F3875",
+        "CloudFrontToS3S3LoggingBucketEF5CD8B2",
+        "DemoIAMPolicy2174894A"
+      ],
+      "UpdateReplacePolicy": "Delete",
+      "DeletionPolicy": "Delete",
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/DemoConsole/Default"
+      }
+    },
+    "AppRegistryAttributeIdDF43F316": {
+      "Type": "AWS::ServiceCatalogAppRegistry::AttributeGroup",
+      "Properties": {
+        "Attributes": {
+          "ApplicationType": "AWS-Solutions",
+          "SolutionVersion": "v4.2.1",
+          "SolutionID": "SO0013",
+          "SolutionName": "Live Streaming on AWS"
+        },
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              "A30-",
+              {
+                "Ref": "AWS::Region"
+              },
+              "-",
+              {
+                "Ref": "AWS::StackName"
+              }
+            ]
+          ]
+        },
+        "Description": "Attribute group for solution information.",
+        "Tags": {
+          "SolutionId": "SO0013"
+        }
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/AppRegistryAttributeId/Resource"
+      }
+    },
+    "AppRegistryApp5349BE86": {
+      "Type": "AWS::ServiceCatalogAppRegistry::Application",
+      "Properties": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              "live-streaming-on-aws-",
+              {
+                "Ref": "AWS::StackName"
+              }
+            ]
+          ]
+        },
+        "Description": "Service Catalog application to track and manage all your resources. The SolutionId is SO0013 and SolutionVersion is v4.2.1.",
+        "Tags": {
+          "ApplicationType": "AWS-Solutions",
+          "appRegistryApplicationName": "live-streaming-on-aws-stack",
+          "SolutionDomain": "CloudFoundations",
+          "solutionId": "SO0013",
+          "SolutionId": "SO0013",
+          "SolutionName": "Live Streaming on AWS",
+          "SolutionVersion": "v4.2.1"
+        }
+      },
+      "DependsOn": [
+        "AppRegistryAttributeIdDF43F316"
+      ],
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/AppRegistryApp/Resource"
+      }
+    },
+    "AppRegistryAppResourceAssociationbb30b2b6ffac2CF098B8": {
+      "Type": "AWS::ServiceCatalogAppRegistry::ResourceAssociation",
+      "Properties": {
+        "Application": {
+          "Fn::GetAtt": [
+            "AppRegistryApp5349BE86",
+            "Id"
+          ]
+        },
+        "Resource": {
+          "Ref": "AWS::StackId"
+        },
+        "ResourceType": "CFN_STACK"
+      },
+      "DependsOn": [
+        "AppRegistryAttributeIdDF43F316"
+      ],
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/AppRegistryApp/ResourceAssociationbb30b2b6ffac"
+      }
+    },
+    "AppRegistryAppAttributeGroupAssociatione6a1c2e3176a77F7002D": {
+      "Type": "AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation",
+      "Properties": {
+        "Application": {
+          "Fn::GetAtt": [
+            "AppRegistryApp5349BE86",
+            "Id"
+          ]
+        },
+        "AttributeGroup": {
+          "Fn::GetAtt": [
+            "AppRegistryAttributeIdDF43F316",
+            "Id"
+          ]
+        }
+      },
+      "DependsOn": [
+        "AppRegistryAttributeIdDF43F316"
+      ],
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/AppRegistryApp/AttributeGroupAssociatione6a1c2e3176a"
+      }
+    },
+    "AnonymousMetric": {
+      "Type": "AWS::CloudFormation::CustomResource",
+      "Properties": {
+        "ServiceToken": {
+          "Fn::GetAtt": [
+            "CustomResource8CDCD7A7",
+            "Arn"
+          ]
+        },
+        "Resource": "AnonymousMetric",
+        "SolutionId": "SO0013",
+        "UUID": {
+          "Fn::GetAtt": [
+            "UUID",
+            "UUID"
+          ]
+        },
+        "Version": "v4.2.1",
+        "InputType": {
+          "Ref": "InputType"
+        },
+        "EncodingProfile": {
+          "Ref": "EncodingProfile"
+        },
+        "Cidr": {
+          "Ref": "InputCIDR"
+        },
+        "ChannelStart": {
+          "Ref": "ChannelStart"
+        },
+        "SendAnonymousMetric": {
+          "Fn::FindInMap": [
+            "AnonymizedData",
+            "SendAnonymizedData",
+            "Data"
+          ]
+        }
+      },
+      "UpdateReplacePolicy": "Delete",
+      "DeletionPolicy": "Delete",
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/AnonymousMetric/Default"
+      }
+    },
+    "CDKMetadata": {
+      "Type": "AWS::CDK::Metadata",
+      "Properties": {
+        "Analytics": "v2:deflate64:H4sIAAAAAAAA/3VSTW/CMAz9LbungY193laYmHaYQLA7MqkpgTap8sGEqv73OSmFMm2X2C+2X5xn3/H66Z4Pb+DbJiLbJ4Vc83rpQOzZZKPmYKBEhyaAT6gqqXJGqataQsnrhS4wRKKd60KKY6yKXsMKKNcZ8HrqlXBSqxDr/IbZ0QqsRWd5GgxhXo+92KMbg0XWuqHk5LXm8kofExsKQ1wlKMjR0BciDnmt1zBRaJ9tjFaO1xMQW7xQ9eGbtM7Ite8avsIzI3OpUiHQ2o8MlZOuJQjc08D9V0bozhykQAEOCp2TjgbzwHukVjYqde0L+G60rwJfWlXUDHQ9LNBqbwSSUFrI8/V1XS/YNKwrYVFcGmgeRjfx1unyHOtRB3/mXeWjZhOtMtmOafRM2/Ea1sPqIqpAi0KHM144O4h7c1Y2oSFetPjSyxEbPfBhAkW1hY6H1iyW/SvKKb3+pUtPFPqg0hnynR0cbh/57Qtx76yUifGkeIl80dofczBnw94CAAA="
+      },
+      "Metadata": {
+        "aws:cdk:path": "LiveStreaming/CDKMetadata/Default"
+      },
+      "Condition": "CDKMetadataAvailable"
+    }
+  },
+  "Outputs": {
+    "AnonymousMetricUUID": {
+      "Description": "AnonymousMetric UUID",
+      "Value": {
+        "Fn::GetAtt": [
+          "UUID",
+          "UUID"
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-AnonymousMetricUUID"
+            ]
+          ]
+        }
+      }
+    },
+    "MediaLiveChannelConsole": {
+      "Description": "MediaLive Channel",
+      "Value": {
+        "Fn::Join": [
+          "",
+          [
+            "https://",
+            {
+              "Ref": "AWS::Region"
+            },
+            ".console.aws.amazon.com/medialive/home?region=",
+            {
+              "Ref": "AWS::Region"
+            },
+            "#!/channels/",
+            {
+              "Fn::GetAtt": [
+                "MediaLiveChannel",
+                "ChannelId"
+              ]
+            }
+          ]
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-MediaLiveChannel"
+            ]
+          ]
+        }
+      }
+    },
+    "MediaLiveMetrics": {
+      "Description": "MediaLive Metrics",
+      "Value": {
+        "Fn::Join": [
+          "",
+          [
+            "https://",
+            {
+              "Ref": "AWS::Region"
+            },
+            ".console.aws.amazon.com/medialive/home?region=",
+            {
+              "Ref": "AWS::Region"
+            },
+            "#!/channels/",
+            {
+              "Fn::GetAtt": [
+                "MediaLiveChannel",
+                "ChannelId"
+              ]
+            },
+            "/health"
+          ]
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-MediaLiveMetrics"
+            ]
+          ]
+        }
+      }
+    },
+    "MediaLivePrimaryEndpoint": {
+      "Description": "Primary MediaLive input URL",
+      "Value": {
+        "Fn::GetAtt": [
+          "MediaLiveInput",
+          "EndPoint1"
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-MediaLivePrimaryEndpoint"
+            ]
+          ]
+        }
+      }
+    },
+    "MediaLiveSecondaryEndpoint": {
+      "Description": "Secondary MediaLive input URL",
+      "Value": {
+        "Fn::GetAtt": [
+          "MediaLiveInput",
+          "EndPoint2"
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-MediaLiveSecondaryEndpoint"
+            ]
+          ]
+        }
+      }
+    },
+    "MediaPackageMetrics": {
+      "Description": "MediaPackage Metrics",
+      "Value": {
+        "Fn::Join": [
+          "",
+          [
+            "https://",
+            {
+              "Ref": "AWS::Region"
+            },
+            ".console.aws.amazon.com/mediapackage/home?region=",
+            {
+              "Ref": "AWS::Region"
+            },
+            "#/channels/",
+            {
+              "Fn::GetAtt": [
+                "MediaPackageChannel",
+                "ChannelId"
+              ]
+            },
+            "?tabId=metrics"
+          ]
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-MediaPackageMetrics"
+            ]
+          ]
+        }
+      }
+    },
+    "CloudFrontHlsEndpoint": {
+      "Description": "HLS CloudFront URL",
+      "Value": {
+        "Fn::Join": [
+          "",
+          [
+            "https://",
+            {
+              "Fn::GetAtt": [
+                "CloudFrontD198EC06",
+                "DomainName"
+              ]
+            },
+            "/out/v1",
+            {
+              "Fn::GetAtt": [
+                "MediaPackageHlsEndpoint",
+                "Manifest"
+              ]
+            }
+          ]
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-CloudFrontHlsEndpoint"
+            ]
+          ]
+        }
+      }
+    },
+    "CloudFrontDashEndpoint": {
+      "Description": "DASH CloudFront URL",
+      "Value": {
+        "Fn::Join": [
+          "",
+          [
+            "https://",
+            {
+              "Fn::GetAtt": [
+                "CloudFrontD198EC06",
+                "DomainName"
+              ]
+            },
+            "/out/v1",
+            {
+              "Fn::GetAtt": [
+                "MediaPackageDashEndpoint",
+                "Manifest"
+              ]
+            }
+          ]
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-CloudFrontDashEndpoint"
+            ]
+          ]
+        }
+      }
+    },
+    "CloudFrontCmafEndpoint": {
+      "Description": "CMAF CloudFront URL",
+      "Value": {
+        "Fn::Join": [
+          "",
+          [
+            "https://",
+            {
+              "Fn::GetAtt": [
+                "CloudFrontD198EC06",
+                "DomainName"
+              ]
+            },
+            "/out/v1",
+            {
+              "Fn::GetAtt": [
+                "MediaPackageCmafEndpoint",
+                "Manifest"
+              ]
+            }
+          ]
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-CloudFrontCmafEndpoint"
+            ]
+          ]
+        }
+      }
+    },
+    "DemoPlayer": {
+      "Description": "Demo Player URL",
+      "Value": {
+        "Fn::Join": [
+          "",
+          [
+            "https://",
+            {
+              "Fn::GetAtt": [
+                "CloudFrontToS3CloudFrontDistribution241D9866",
+                "DomainName"
+              ]
+            },
+            "/index.html"
+          ]
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-DemoPlayer"
+            ]
+          ]
+        }
+      }
+    },
+    "DemoBucketConsole": {
+      "Description": "Demo bucket",
+      "Value": {
+        "Fn::Join": [
+          "",
+          [
+            "https://",
+            {
+              "Ref": "AWS::Region"
+            },
+            ".console.aws.amazon.com/s3/buckets/",
+            {
+              "Ref": "CloudFrontToS3S3Bucket9CE6AB04"
+            },
+            "?region=",
+            {
+              "Ref": "AWS::Region"
+            }
+          ]
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-DemoBucket"
+            ]
+          ]
+        }
+      }
+    },
+    "LogsBucketConsole": {
+      "Description": "Logs bucket",
+      "Value": {
+        "Fn::Join": [
+          "",
+          [
+            "https://",
+            {
+              "Ref": "AWS::Region"
+            },
+            ".console.aws.amazon.com/s3/buckets/",
+            {
+              "Ref": "LogsBucket9C4D8843"
+            },
+            "?region=",
+            {
+              "Ref": "AWS::Region"
+            }
+          ]
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-LogsBucket"
+            ]
+          ]
+        }
+      }
+    },
+    "AppRegistryConsole": {
+      "Description": "AppRegistry",
+      "Value": {
+        "Fn::Join": [
+          "",
+          [
+            "https://",
+            {
+              "Ref": "AWS::Region"
+            },
+            ".console.aws.amazon.com/servicecatalog/home?#applications/",
+            {
+              "Fn::GetAtt": [
+                "AppRegistryApp5349BE86",
+                "Id"
+              ]
+            }
+          ]
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-AppRegistry"
+            ]
+          ]
+        }
+      }
+    },
+    "MediaLiveChannelId": {
+      "Description": "MediaLive Channel Id",
+      "Value": {
+        "Fn::GetAtt": [
+          "MediaLiveChannel",
+          "ChannelId"
+        ]
+      },
+      "Export": {
+        "Name": {
+          "Fn::Join": [
+            "",
+            [
+              {
+                "Ref": "AWS::StackName"
+              },
+              "-MediaLiveChannelId"
+            ]
+          ]
+        }
+      }
+    }
+  },
+  "Conditions": {
+    "CDKMetadataAvailable": {
+      "Fn::Or": [
+        {
+          "Fn::Or": [
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "af-south-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "ap-east-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "ap-northeast-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "ap-northeast-2"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "ap-south-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "ap-southeast-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "ap-southeast-2"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "ca-central-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "cn-north-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "cn-northwest-1"
+              ]
+            }
+          ]
+        },
+        {
+          "Fn::Or": [
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "eu-central-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "eu-north-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "eu-south-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "eu-west-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "eu-west-2"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "eu-west-3"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "me-south-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "sa-east-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "us-east-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "us-east-2"
+              ]
+            }
+          ]
+        },
+        {
+          "Fn::Or": [
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "us-west-1"
+              ]
+            },
+            {
+              "Fn::Equals": [
+                {
+                  "Ref": "AWS::Region"
+                },
+                "us-west-2"
+              ]
+            }
+          ]
+        }
+      ]
+    }
+  }
+}
\ No newline at end of file
diff --git a/tests/data/templates/minecraft.yml b/tests/data/templates/minecraft.yml
new file mode 100644
index 0000000..622baa4
--- /dev/null
+++ b/tests/data/templates/minecraft.yml
@@ -0,0 +1,770 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: Minecraft Spot Price Server via Docker / ECS
+Parameters:
+
+  ECSAMI:
+    Description: AWS ECS AMI ID
+    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
+    Default: /aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id
+
+  ServerState:
+    Type: String
+    Description: "Running: A spot instance will launch shortly after setting this parameter; your Minecraft server should start within 5-10 minutes of changing this parameter (once UPDATE_IN_PROGRESS becomes UPDATE_COMPLETE). Stopped: Your spot instance (and thus Minecraft container) will be terminated shortly after setting this parameter."
+    Default: Running
+    AllowedValues:
+    - Running
+    - Stopped
+
+  InstanceType:
+    Type: String
+    Description: "t3.medium is a good cost-effective instance, 2 vCPUs and 3.75 GB of RAM with moderate network performance. Change at your discretion. https://aws.amazon.com/ec2/instance-types/."
+    Default: t3.medium
+
+  SpotPrice:
+    Type: String
+    Description: "A t3.medium shouldn't cost much more than a cent per hour. Note: Leave this blank to use on-demand pricing."
+    Default: "0.05"
+
+  ContainerInsights:
+    Type: String
+    Description: "Enable/Disable ECS Container Insights for ECS Cluster"
+    Default: disabled
+    AllowedValues:
+    - enabled
+    - disabled
+
+  EntryPoint:
+    Type: CommaDelimitedList
+    Description: "Task entrypoint (Optional - image default is script /start)"
+
+  Command:
+    Type: CommaDelimitedList
+    Description: "Task command (Optional - image default is empty)"
+
+  LogGroupName:
+    Type: String
+    Description: (Optional - An empty value disables this feature)
+    Default: ''
+
+  LogGroupRetentionInDays:
+    Type: Number
+    Description: (Log retention in days)
+    Default: 7
+
+  LogStreamPrefix:
+    Type: String
+    Description: (Optional)
+    Default: 'minecraft-server'
+
+  KeyPairName:
+    Type: String
+    Description: (Optional - An empty value disables this feature)
+    Default: ''
+
+  YourIPv4:
+    Type: String
+    Description: (Optional - An empty value disables this feature)
+    Default: ''
+
+  YourIPv6:
+    Type: String
+    Description: (Optional - An empty value disables this feature)
+    Default: ''
+
+  HostedZoneId:
+    Type: String
+    Description: (Optional - An empty value disables this feature) If you have a hosted zone in Route 53 and wish to set a DNS record whenever your Minecraft instance starts, supply the hosted zone ID here.
+    Default: ''
+
+  RecordName:
+    Type: String
+    Description: (Optional - An empty value disables this feature) If you have a hosted zone in Route 53 and wish to set a DNS record whenever your Minecraft instance starts, supply the name of the record here (e.g. minecraft.mydomain.com).
+    Default: ''
+
+  MinecraftImageTag:
+    Type: String
+    Description: "Java version (Examples include latest, adopt13, openj9, etc) Refer to tag descriptions available here: https://github.com/itzg/docker-minecraft-server)"
+    Default: 'latest'
+
+  MinecraftTypeTag:
+    Type: String
+    Description: "(Examples include SPIGOT, BUKKIT, TUINITY, etc) Refer to tag descriptions available here: https://github.com/itzg/docker-minecraft-server)"
+
+  AdminPlayerNames:
+    Type: String
+    Description: Op/Administrator Players
+    Default: ''
+
+  Difficulty:
+    Type: String
+    Description: "The game's difficulty"
+    Default: normal
+    AllowedValues:
+    - peaceful
+    - easy
+    - normal
+    - hard
+
+  Whitelist:
+    Type: String
+    Description: Usernames of your friends
+    Default: ''
+
+  MinecraftVersion:
+    Type: String
+    Description: Server minecraft version
+    Default: ''
+
+  Memory:
+    Type: String
+    Description: How much Memory to allocate for the JVM
+    Default: '1G'
+
+  Seed:
+    Type: String
+    Description: The seed used to generate the world
+
+  MaxPlayers:
+    Type: Number
+    Description: Max number of players that can connect simultaneously (default 20)
+    Default: -1
+
+  ViewDistance:
+    Type: Number
+    Description: Max view radius (in chunks) the server will send to the client (default 10)
+    Default: -1
+
+  GameMode:
+    Type: String
+    Description: "Options: creative, survival (default), adventure, spectator (v1.8+)"
+    Default: survival
+    AllowedValues:
+    - creative
+    - survival
+    - adventure
+    - spectator
+
+  LevelType:
+    Type: String
+    Description: "Options: DEFAULT, FLAT, LARGEBIOMES, AMPLIFIED, CUSTOMIZED, BUFFET, BIOMESOP (v1.12-), BIOMESOPLENTY (v1.15+)"
+    Default: DEFAULT
+    AllowedValues:
+    - DEFAULT
+    - FLAT
+    - LARGEBIOMES
+    - AMPLIFIED
+    - CUSTOMIZED
+    - BUFFET
+    - BIOMESOP
+    - BIOMESOPLENTY
+
+  EnableRollingLogs:
+    Type: String
+    Description: "By default the log file will grow without limit. Set to true to use a rolling log strategy."
+    Default: false
+    AllowedValues:
+    - true
+    - false
+
+  Timezone:
+    Type: String
+    Description: "Change the server's timezone. Use the canonical name of the format: Area/Location (e.g. America/New_York)"
+
+Metadata:
+  AWS::CloudFormation::Interface:
+    ParameterGroups:
+      - Label:
+          default: Essential Configuration
+        Parameters:
+        - ServerState
+        - InstanceType
+        - SpotPrice
+      - Label:
+          default: Server Settings
+        Parameters:
+        - MinecraftImageTag
+      - Label:
+          default: Optional Server Settings
+        Parameters:
+        - MinecraftTypeTag
+        - AdminPlayerNames
+        - Difficulty
+        - Whitelist
+        - MinecraftVersion
+        - Memory
+        - Seed
+        - MaxPlayers
+        - ViewDistance
+        - GameMode
+        - LevelType
+        - EnableRollingLogs
+        - Timezone
+      - Label:
+          default: Optional ECS Cluster and Task Configuration
+        Parameters:
+        - ContainerInsights
+        - EntryPoint
+        - Command
+        - LogGroupName
+        - LogGroupRetentionInDays
+        - LogStreamPrefix
+      - Label:
+          default: Optional Remote Access (SSH) Configuration
+        Parameters:
+        - KeyPairName
+        - YourIPv4
+        - YourIPv6
+      - Label:
+          default: Optional DNS Configuration
+        Parameters:
+        - HostedZoneId
+        - RecordName
+    ParameterLabels:
+      ServerState:
+        default: "Update this parameter to shut down / start up your Minecraft server as required to save on cost. Takes a few minutes to take effect."
+      InstanceType:
+        default: "Which instance type? You must make sure this is available in your region! https://aws.amazon.com/ec2/pricing/on-demand/"
+      SpotPrice:
+        default: "Maximum spot price per hour? Leave blank to disable spot pricing."
+      MinecraftImageTag:
+        default: "Which version of Minecraft do you want to launch?"
+      MinecraftTypeTag:
+        default: "Which type of Minecraft do you want to launch?"
+      AdminPlayerNames:
+        default: "A comma delimited list (no spaces) of player names to be admins"
+      Difficulty:
+        default: "Which difficulty?"
+      Whitelist:
+        default: "A comma delimited list (no spaces) of player names"
+      MinecraftVersion:
+        default: "Minecraft version ie 1.16.3"
+      ContainerInsights:
+        default: "ECS Container Insights provide additional container metrics, and supports collection of Prometheus metrics.  Additional AWS charges may apply."
+      EntryPoint:
+        default: "Task/container --entrypoint, comma separated e.g. /bin/bash,-c"
+      Command:
+        default: "Task/container command, comma separated arguments passed to entrypoint"
+      LogGroupName:
+        default: "Create CloudWatch Log Group with this name e.g. /Minecraft or /ecs/minecraft, and direct container logs there"
+      LogGroupRetentionInDays:
+        default: "Number of days to retain CloudWatch logs"
+      LogStreamPrefix:
+        default: "Prefix for container log stream e.g. minecraft-server"
+      KeyPairName:
+        default: "If you wish to access the instance via SSH, select a Key Pair to use. https://console.aws.amazon.com/ec2/v2/home?#KeyPairs:sort=keyName"
+      YourIPv4:
+        default: "If you wish to access the instance via SSH and using IPv4, provide it."
+      YourIPv6:
+        default: "If you wish to access the instance via SSH and using IPv6, provide it."
+      HostedZoneId:
+        default: "If you have a hosted zone in Route 53 and wish to update a DNS record whenever your Minecraft instance starts, supply the hosted zone ID here."
+      RecordName:
+        default: "If you have a hosted zone in Route 53 and wish to set a DNS record whenever your Minecraft instance starts, supply a record name here (e.g. minecraft.mydomain.com)."
+      Memory:
+        default: "If you wish to increase the Java memory-heap limit of 1GB. Format: <size>[g|G|m|M|k|K]"
+      Seed:
+        default: "Seed for world generation"
+      MaxPlayers:
+        default: "Max simultaneous players"
+      ViewDistance:
+        default: "Max view distance"
+      GameMode:
+        default: "The Minecraft game mode"
+      LevelType:
+        default: "Level type for world generation"
+      EnableRollingLogs:
+        default: "Whether to enable rolling logs"
+      Timezone:
+        default: "The server's timezone"
+
+Conditions:
+  MinecraftTypeTagProvided: !Not [ !Equals [ !Ref MinecraftTypeTag, '' ] ]
+  AdminPlayerNamesProvided: !Not [ !Equals [ !Ref AdminPlayerNames, '' ] ]
+  DifficultyProvided: !Not [ !Equals [ !Ref Difficulty, '' ] ]
+  WhitelistProvided: !Not [ !Equals [ !Ref Whitelist, '' ] ]
+  MinecraftVersionProvided: !Not [ !Equals [ !Ref MinecraftVersion, '' ] ]
+  EntryPointProvided: !Not [ !Equals [ !Join [ "", !Ref EntryPoint ], '' ] ]
+  CommandProvided: !Not [ !Equals [ !Join [ "", !Ref Command ], '' ] ]
+  LogGroupNameProvided: !Not [ !Equals [ !Ref LogGroupName, '' ] ]
+  LogStreamPrefixProvided:  !Not [ !Equals [ !Ref LogStreamPrefix, '' ] ]
+  KeyPairNameProvided: !Not [ !Equals [ !Ref KeyPairName, '' ] ]
+  IPv4AddressProvided: !Not [ !Equals [ !Ref YourIPv4, '' ] ]
+  IPv6AddressProvided: !Not [ !Equals [ !Ref YourIPv6, '' ] ]
+  DnsConfigEnabled: !And [ !Not [ !Equals [ !Ref HostedZoneId, '' ] ], !Not [ !Equals [ !Ref RecordName, '' ] ] ]
+  SpotPriceProvided: !Not [ !Equals [ !Ref SpotPrice, '' ] ]
+  MemoryProvided: !Not [ !Equals [ !Ref Memory, '' ] ]
+  SeedProvided: !Not [ !Equals [ !Ref Seed, '' ] ]
+  MaxPlayersProvided: !Not [ !Equals [ !Ref MaxPlayers, -1 ] ]
+  ViewDistanceProvided: !Not [ !Equals [ !Ref ViewDistance, -1 ] ]
+  GameModeProvided: !Not [ !Equals [ !Ref GameMode, '' ] ]
+  LevelTypeProvided: !Not [ !Equals [ !Ref LevelType, '' ] ]
+  EnableRollingLogsProvided: !Not [ !Equals [ !Ref EnableRollingLogs, '' ] ]
+  TimezoneProvided: !Not [ !Equals [ !Ref Timezone, '' ] ]
+
+
+Mappings:
+  ServerState:
+    Running:
+      DesiredCapacity: 1
+    Stopped:
+      DesiredCapacity: 0
+
+Resources:
+
+  # ====================================================
+  # BASIC VPC
+  # ====================================================
+
+  Vpc:
+    Type: AWS::EC2::VPC
+    Properties:
+      CidrBlock: 10.100.0.0/26
+      EnableDnsSupport: true
+      EnableDnsHostnames: true
+
+  SubnetA:
+    Type: AWS::EC2::Subnet
+    Properties:
+      AvailabilityZone: !Select
+      - 0
+      - !GetAZs
+        Ref: 'AWS::Region'
+      CidrBlock: !Cidr [ 10.100.0.0/26, 4, 4 ]
+      VpcId: !Ref Vpc
+
+  SubnetARoute:
+    Type: AWS::EC2::SubnetRouteTableAssociation
+    Properties:
+      RouteTableId: !Ref RouteTable
+      SubnetId: !Ref SubnetA
+
+  SubnetBRoute:
+    Type: AWS::EC2::SubnetRouteTableAssociation
+    Properties:
+      RouteTableId: !Ref RouteTable
+      SubnetId: !Ref SubnetB
+
+  SubnetB:
+    Type: AWS::EC2::Subnet
+    Properties:
+      AvailabilityZone: !Select
+      - 1
+      - !GetAZs
+        Ref: 'AWS::Region'
+      CidrBlock: !Cidr [ 10.100.0.0/26, 4, 4 ]
+      VpcId: !Ref Vpc
+
+  InternetGateway:
+    Type: AWS::EC2::InternetGateway
+    Properties: {}
+
+  InternetGatewayAttachment:
+    Type: AWS::EC2::VPCGatewayAttachment
+    Properties:
+      InternetGatewayId: !Ref InternetGateway
+      VpcId: !Ref Vpc
+
+  RouteTable:
+    Type: AWS::EC2::RouteTable
+    Properties:
+      VpcId: !Ref Vpc
+
+  Route:
+    Type: AWS::EC2::Route
+    Properties:
+      DestinationCidrBlock: 0.0.0.0/0
+      GatewayId: !Ref InternetGateway
+      RouteTableId: !Ref RouteTable
+
+  # ====================================================
+  # EFS FOR PERSISTENT DATA
+  # ====================================================
+
+  Efs:
+    Type: AWS::EFS::FileSystem
+    Properties: {}
+
+  MountA:
+    Type: AWS::EFS::MountTarget
+    Properties:
+      FileSystemId: !Ref Efs
+      SecurityGroups:
+      - !Ref EfsSg
+      SubnetId: !Ref SubnetA
+
+  MountB:
+    Type: AWS::EFS::MountTarget
+    Properties:
+      FileSystemId: !Ref Efs
+      SecurityGroups:
+      - !Ref EfsSg
+      SubnetId: !Ref SubnetB
+
+  EfsSg:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      GroupName: !Sub "${AWS::StackName}-efs"
+      GroupDescription: !Sub "${AWS::StackName}-efs"
+      SecurityGroupIngress:
+      - FromPort: 2049
+        ToPort: 2049
+        IpProtocol: tcp
+        SourceSecurityGroupId: !Ref Ec2Sg
+      VpcId: !Ref Vpc
+
+  # ====================================================
+  # INSTANCE CONFIG
+  # ====================================================
+
+  Ec2Sg:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      GroupName: !Sub "${AWS::StackName}-ec2"
+      GroupDescription: !Sub "${AWS::StackName}-ec2"
+      SecurityGroupIngress:
+      - !If
+        - IPv4AddressProvided
+        - FromPort: 22
+          ToPort: 22
+          IpProtocol: tcp
+          CidrIp: !Sub "${YourIPv4}/32"
+        - !Ref 'AWS::NoValue'
+      - !If
+        - IPv6AddressProvided
+        - FromPort: 22
+          ToPort: 22
+          IpProtocol: tcp
+          CidrIpv6: !Sub "${YourIPv6}/128"
+        - !Ref 'AWS::NoValue'
+      - FromPort: 25565
+        ToPort: 25565
+        IpProtocol: tcp
+        CidrIp: 0.0.0.0/0
+      VpcId: !Ref Vpc
+
+  LaunchConfiguration:
+    Type: AWS::AutoScaling::LaunchConfiguration
+    Properties:
+      AssociatePublicIpAddress: true
+      IamInstanceProfile: !Ref InstanceProfile
+      ImageId: !Ref ECSAMI
+      InstanceType: !Ref InstanceType
+      KeyName:
+        !If [ KeyPairNameProvided, !Ref KeyPairName, !Ref 'AWS::NoValue' ]
+      SecurityGroups:
+      - !Ref Ec2Sg
+      SpotPrice: !If [ SpotPriceProvided, !Ref SpotPrice, !Ref 'AWS::NoValue' ]
+      UserData:
+        Fn::Base64: !Sub |
+          #!/bin/bash -xe
+          echo ECS_CLUSTER=${EcsCluster} >> /etc/ecs/ecs.config
+          yum install -y amazon-efs-utils
+          mkdir /opt/minecraft
+          mount -t efs ${Efs}:/ /opt/minecraft
+          chown 845:845 /opt/minecraft
+
+  AutoScalingGroup:
+    Type: AWS::AutoScaling::AutoScalingGroup
+    DependsOn:
+    - MountA
+    - MountB
+    Properties:
+      AutoScalingGroupName: !Sub "${AWS::StackName}-asg"
+      DesiredCapacity: !FindInMap [ ServerState, !Ref ServerState, DesiredCapacity ]
+      LaunchConfigurationName: !Ref LaunchConfiguration
+      NewInstancesProtectedFromScaleIn: true
+      MaxSize: 1
+      MinSize: 0
+      VPCZoneIdentifier:
+        - !Ref SubnetA
+        - !Ref SubnetB
+    Metadata:
+      LaunchEvent: !If [ DnsConfigEnabled, !GetAtt LaunchEvent.Arn, "" ]
+
+  InstanceRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+        - Effect: Allow
+          Principal:
+            Service:
+            - ec2.amazonaws.com
+          Action:
+          - sts:AssumeRole
+      ManagedPolicyArns:
+        - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role
+
+  InstanceProfile:
+    Type: AWS::IAM::InstanceProfile
+    Properties:
+      Roles:
+        - !Ref InstanceRole
+
+  EcsCluster:
+    Type: AWS::ECS::Cluster
+    Properties:
+      ClusterName: !Sub "${AWS::StackName}-cluster"
+      ClusterSettings:
+        - Name: containerInsights
+          Value: !Ref ContainerInsights
+
+  ECSCapacityProvider:
+    Type: AWS::ECS::CapacityProvider
+    Properties:
+      AutoScalingGroupProvider:
+        AutoScalingGroupArn: !Ref AutoScalingGroup
+        ManagedScaling:
+          MaximumScalingStepSize: 1
+          MinimumScalingStepSize: 1
+          Status: ENABLED
+          TargetCapacity: 100
+        ManagedTerminationProtection: ENABLED
+
+  EcsClusterCapacityProviderAssociation:
+    Type: AWS::ECS::ClusterCapacityProviderAssociations
+    Properties:
+      Cluster: !Ref EcsCluster
+      CapacityProviders:
+        - !Ref ECSCapacityProvider
+      DefaultCapacityProviderStrategy:
+        - CapacityProvider: !Ref ECSCapacityProvider
+          Weight: 1
+
+  EcsService:
+    Type: AWS::ECS::Service
+    Properties:
+      Cluster: !Ref EcsCluster
+      DesiredCount: !FindInMap [ ServerState, !Ref ServerState, DesiredCapacity ]
+      ServiceName: !Sub "${AWS::StackName}-ecs-service"
+      TaskDefinition: !Ref EcsTask
+      CapacityProviderStrategy:
+        - CapacityProvider: !Ref ECSCapacityProvider
+          Weight: 1
+          Base: 0
+      DeploymentConfiguration:
+        MaximumPercent: 100
+        MinimumHealthyPercent: 0
+
+  EcsTask:
+    Type: AWS::ECS::TaskDefinition
+    Properties:
+      Volumes:
+      - Host:
+          SourcePath: /opt/minecraft
+        Name: minecraft
+      NetworkMode: "bridge"
+      ContainerDefinitions:
+        - Name: minecraft
+          MemoryReservation: 1024
+          Image: !Sub "itzg/minecraft-server:${MinecraftImageTag}"
+          EntryPoint: !If
+              - EntryPointProvided
+              - !Ref EntryPoint
+              - !Ref 'AWS::NoValue'
+          Command: !If
+              - CommandProvided
+              - !Ref Command
+              - !Ref 'AWS::NoValue'
+          PortMappings:
+          - ContainerPort: 25565
+            HostPort: 25565
+            Protocol: tcp
+          LogConfiguration:
+            !If
+            - LogGroupNameProvided
+            - LogDriver: awslogs
+              Options:
+                awslogs-group: !Ref CloudWatchLogGroup
+                awslogs-stream-prefix: !If
+                - LogStreamPrefixProvided
+                - !Sub ${LogStreamPrefix}
+                - !Ref 'AWS::NoValue'
+                awslogs-region: !Ref AWS::Region
+                awslogs-create-group: true
+            - !Ref 'AWS::NoValue'
+          MountPoints:
+          - ContainerPath: /data
+            SourceVolume: minecraft
+            ReadOnly: false
+          Environment:
+            - Name: "EULA"
+              Value: "TRUE"
+            - !If
+              - MinecraftTypeTagProvided
+              - Name: "TYPE"
+                Value: !Sub "${MinecraftTypeTag}"
+              - !Ref 'AWS::NoValue'
+            - !If
+              - AdminPlayerNamesProvided
+              - Name: "OPS"
+                Value: !Sub "${AdminPlayerNames}"
+              - !Ref 'AWS::NoValue'
+            - !If
+              - DifficultyProvided
+              - Name: "DIFFICULTY"
+                Value: !Sub "${Difficulty}"
+              - !Ref 'AWS::NoValue'
+            - !If
+              - WhitelistProvided
+              - Name: "WHITELIST"
+                Value: !Sub "${Whitelist}"
+              - !Ref 'AWS::NoValue'
+            - !If
+              - MinecraftVersionProvided
+              - Name: "VERSION"
+                Value: !Sub "${MinecraftVersion}"
+              - !Ref 'AWS::NoValue'
+            - !If
+              - MemoryProvided
+              - Name: "MEMORY"
+                Value: !Sub "${Memory}"
+              - !Ref 'AWS::NoValue'
+            - !If
+              - SeedProvided
+              - Name: "SEED"
+                Value: !Sub "${Seed}"
+              - !Ref 'AWS::NoValue'
+            - !If
+              - MaxPlayersProvided
+              - Name: "MAX_PLAYERS"
+                Value: !Sub "${MaxPlayers}"
+              - !Ref 'AWS::NoValue'
+            - !If
+              - ViewDistanceProvided
+              - Name: "VIEW_DISTANCE"
+                Value: !Sub "${ViewDistance}"
+              - !Ref 'AWS::NoValue'
+            - !If
+              - GameModeProvided
+              - Name: "MODE"
+                Value: !Sub "${GameMode}"
+              - !Ref 'AWS::NoValue'
+            - !If
+              - LevelTypeProvided
+              - Name: "LEVEL_TYPE"
+                Value: !Sub "${LevelType}"
+              - !Ref 'AWS::NoValue'
+            - !If
+              - EnableRollingLogsProvided
+              - Name: "ENABLE_ROLLING_LOGS"
+                Value: !Sub "${EnableRollingLogs}"
+              - !Ref 'AWS::NoValue'
+            - !If
+              - TimezoneProvided
+              - Name: "TZ"
+                Value: !Sub "${Timezone}"
+              - !Ref 'AWS::NoValue'
+
+  CloudWatchLogGroup:
+    Condition: LogGroupNameProvided
+    Type: AWS::Logs::LogGroup
+    Properties:
+      LogGroupName: !Sub "${LogGroupName}"
+      RetentionInDays: !Sub "${LogGroupRetentionInDays}"
+
+  # ====================================================
+  # SET DNS RECORD
+  # ====================================================
+
+  SetDNSRecordLambdaRole:
+    Type: AWS::IAM::Role
+    Condition: DnsConfigEnabled
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+        - Effect: Allow
+          Principal:
+            Service:
+            - lambda.amazonaws.com
+          Action:
+          - sts:AssumeRole
+      ManagedPolicyArns:
+        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
+      Policies:
+        - PolicyName: root
+          PolicyDocument:
+            Version: "2012-10-17"
+            Statement:
+              - Effect: "Allow"
+                Action: "route53:ChangeResourceRecordSets"
+                Resource: !Sub "arn:aws:route53:::hostedzone/${HostedZoneId}"
+              - Effect: "Allow"
+                Action: "ec2:DescribeInstance*"
+                Resource: "*"
+
+  SetDNSRecordLambda:
+    Type: "AWS::Lambda::Function"
+    Condition: DnsConfigEnabled
+    Properties:
+      Environment:
+        Variables:
+          HostedZoneId: !Ref HostedZoneId
+          RecordName: !Ref RecordName
+      Code:
+        ZipFile: |
+          import boto3
+          import os
+          def handler(event, context):
+            new_instance = boto3.resource('ec2').Instance(event['detail']['EC2InstanceId'])
+            boto3.client('route53').change_resource_record_sets(
+              HostedZoneId= os.environ['HostedZoneId'],
+              ChangeBatch={
+                  'Comment': 'updating',
+                  'Changes': [
+                      {
+                          'Action': 'UPSERT',
+                          'ResourceRecordSet': {
+                              'Name': os.environ['RecordName'],
+                              'Type': 'A',
+                              'TTL': 60,
+                              'ResourceRecords': [
+                                  {
+                                      'Value': new_instance.public_ip_address
+                                  },
+                              ]
+                          }
+                      },
+                  ]
+              })
+      Description: Sets Route 53 DNS Record for Minecraft
+      FunctionName: !Sub "${AWS::StackName}-set-dns"
+      Handler: index.handler
+      MemorySize: 128
+      Role: !GetAtt SetDNSRecordLambdaRole.Arn
+      Runtime: python3.7
+      Timeout: 20
+
+  LaunchEvent:
+    Type: AWS::Events::Rule
+    Condition: DnsConfigEnabled
+    Properties:
+      EventPattern:
+        source:
+        - aws.autoscaling
+        detail-type:
+        - EC2 Instance Launch Successful
+        detail:
+          AutoScalingGroupName:
+          - !Sub "${AWS::StackName}-asg"
+      Name: !Sub "${AWS::StackName}-instance-launch"
+      State: ENABLED
+      Targets:
+        - Arn: !GetAtt SetDNSRecordLambda.Arn
+          Id: !Sub "${AWS::StackName}-set-dns"
+
+  LaunchEventLambdaPermission:
+    Type: AWS::Lambda::Permission
+    Condition: DnsConfigEnabled
+    Properties:
+      Action: lambda:InvokeFunction
+      FunctionName: !GetAtt SetDNSRecordLambda.Arn
+      Principal: events.amazonaws.com
+      SourceArn: !GetAtt LaunchEvent.Arn
+
+Outputs:
+  CheckInstanceIp:
+    Description: To find your Minecraft instance IP address, visit the following link. Click on the instance to find its Public IP address.
+    Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/ec2/v2/home?region=${AWS::Region}#Instances:tag:aws:autoscaling:groupName=${AutoScalingGroup};sort=tag:Name"
diff --git a/tests/data/templates/platform-aurora-cluster.cf.txt b/tests/data/templates/platform-aurora-cluster.cf.txt
new file mode 100644
index 0000000..9147277
--- /dev/null
+++ b/tests/data/templates/platform-aurora-cluster.cf.txt
@@ -0,0 +1,351 @@
+AWSTemplateFormatVersion: "2010-09-09"
+
+Description: "CloudFormation template for creating an Aurora Serverless V2 Cluster"
+# For referenfce there's a substantial difference between Aurora V1 (Serverless) and Aurora V2 (Provisioned).
+# Serverless allows massive scaling, up and down, very quickly depending on needs. Aurora V2 is like a traditional EC2 Autoscaling group, your DB will scale with actual RDS instances so it can take a few minutes to scale up and down.
+# There are substantial differences between V1 & V2 Aurora clusters documented here:
+# 1. Aurora V1 Serverless: https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.html
+# 2. Aurora V2 Provisoned: https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.html
+
+Metadata:
+  AWS::CloudFormation::Interface:
+    ParameterGroups:
+      - Label: "Tagging"
+        Parameters:
+          - "RegionTag"
+          - "CustomerTag"
+          - "EnvironmentTag"
+          - "ServiceLayerTag"
+          - "StackInteger"
+          - "InfrastructureReleaseTag"
+      - Label:
+          default: "VPC Configuration"
+        Parameters:
+          - VpcId
+          - SubnetPrivate1
+          - SubnetPrivate2
+      - Label:
+          default: "RDS Configuration"
+        Parameters:
+          - DBSnapshotIdentifier
+          - DBMasterUsername
+          - EngineVersion
+          - AuroraMinCapacity
+          - AuroraMaxCapacity
+          - AuroraBackupsBucketName
+
+  cfn-lint:
+    config:
+      ignore_checks:
+        - "W2001"
+        - "I3042"
+
+Parameters:
+  # Tagging Parameters
+  RegionTag:
+    Type: "String"
+    Description: "Region tag used for StackName and tagging"
+
+  StackInteger:
+    Type: "String"
+    Description: "StackInteger tag"
+    AllowedPattern: ^\d+$
+
+
+  EnvironmentTag:
+    Type: 'String'
+    Description: "Environment tag used to create the StackName and tagging"
+    AllowedValues: [ 'IN', 'BR', 'DV', 'BU', 'UA', 'PR' ]
+
+  CustomerTag:
+    Type: "String"
+    Description: "Customer tag used to create StackName and resource tagging"
+    AllowedValues:
+      - "DA"
+      - "GL"
+      - "DG"
+
+  # Semantic versioning should be used for infrastructure release tagging:
+  # increment the patch version (e.g. from 2.3.4 to 2.3.5) when you only release bug fixes and the like
+  # increment the minor version (e.g. from 1.3.2 to 1.4.0) when you add new features
+  # increment the major version (e.g. from 3.2.9 to 4.0.0) when you introduce breaking changes
+
+  InfrastructureReleaseTag:
+    Type: "String"
+    Description: "Release tag (n.n.n) used to signify the stack/version or release an environment is running on, optionally used in a StackName but is not mandatory"
+    AllowedPattern: ^(\d+).(\d+).(\d+)$
+
+  ServiceLayerTag:
+    Type: "String"
+    Description: "Tag to describe where in the service architecture a particular resource exists"
+    AllowedValues: [ "FabricLayer", "Application", "LegacyPlatform", "NewPlatform", "Proxy" ]
+    Default: "NewPlatform"
+
+  AuroraBackupsBucketName:
+    Description: "The bucket used to store Aurora snapshots/backups"
+    Type: "String"
+
+  AuroraMinCapacity:
+    Description: "The aurora scaling max capacity value, refer to here for doco: https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.setting-capacity.html#aurora-serverless-v2.max_capacity_considerations"
+    Type: "String"
+    Default: 1
+
+  AuroraMaxCapacity:
+    Description: "The aurora scaling max capacity value, refer to here for doco: https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.setting-capacity.html#aurora-serverless-v2.max_capacity_considerations"
+    Type: "String"
+    Default: 10
+
+  AuroraSQLSecretARN:
+    Description: "The ARN of the secrets resource that contains the password for the master user in the Aurora PostgreSQL cluster"
+    Type: "String"
+
+  DBName:
+    Description: "The name of the DB in the PostgreSQL Aurora cluster"
+    Type: "String"
+    Default: "DingoPlatformDatabase"
+
+  DBSnapshotIdentifier:
+    Description: 'Optional name or Amazon Resource Name (ARN) of the DB snapshot from which you want to restore (leave blank to create an empty database).'
+    Type: 'String'
+    Default: ''
+
+  DBMasterUsername:
+    Description: 'The master user name for the DB instance (ignored when DBSnapshotIdentifier is set, value used from snapshot).'
+    Type: 'String'
+    Default: 'master'
+
+  EngineVersion:
+    Description: 'The DB engine to use for the Aurora Serverless cluster'
+    Type: 'String'
+
+  SubnetPrivate1:
+    Type: "AWS::EC2::Subnet::Id"
+    Description: "Backend subnet (AZ1)"
+    AllowedPattern: ^subnet-[0-9A-Za-z]+$
+
+  SubnetPrivate2:
+    Type: "AWS::EC2::Subnet::Id"
+    Description: "Backend subnet (AZ2)"
+    AllowedPattern: ^subnet-[0-9A-Za-z]+$
+
+  SubnetPrivate1AvailabilityZone:
+    Type: "String"
+    Description: "The availability zone of the private subnet 1"
+
+  SubnetPrivate2AvailabilityZone:
+    Type: "String"
+    Description: "The availability zone of the private subnet 1"
+
+  VpcId:
+    Type: "AWS::EC2::VPC::Id"
+    Description: "VPC into which the cluster should be deployed"
+    AllowedPattern: ^vpc-[0-9A-Za-z]+$
+
+  VpcCidr:
+    Type: "String"
+    Description: "The CIDR of the VPC that these resources will reside in, used for security groups"
+
+Conditions:
+  DBSnapshotIdentifierIsBlank: !Equals [!Ref DBSnapshotIdentifier, '']
+  HasDBSnapshotIdentifier: !Not [!Condition DBSnapshotIdentifierIsBlank]
+  IsFakedBranchEnvironment:
+    !Or
+      - !Equals
+        - !Ref EnvironmentTag
+        - "BR"
+      - !Equals
+        - !Ref EnvironmentTag
+        - "UB"
+
+Resources:
+  ################################################################################
+  # RDS Resources
+  ################################################################################
+
+  PostgreSQLRDSSecurityGroup:
+    Type: 'AWS::EC2::SecurityGroup'
+    Properties:
+      GroupDescription: RDS Group
+      VpcId: !Ref VpcId
+      SecurityGroupIngress:
+        - Description: "PostgreSQL DB Connection, inbound"
+          IpProtocol: tcp
+          FromPort: 5432
+          ToPort: 5432
+          CidrIp: !Ref VpcCidr
+
+  RDSDBCluster:
+    DeletionPolicy: !If
+      - IsFakedBranchEnvironment
+      - "Delete"
+      - "Retain"
+    UpdateReplacePolicy: !If
+      - IsFakedBranchEnvironment
+      - "Delete"
+      - "Retain"
+    Type: 'AWS::RDS::DBCluster'
+    Properties:
+      AssociatedRoles:
+        - FeatureName: s3Export
+          RoleArn: !GetAtt AuroraRole.Arn
+        #- FeatureName: s3Import
+        #  RoleArn: !GetAtt AuroraRole.Arn
+      Port: 5432
+      DeletionProtection: false #!TODO change to true when ready for PR
+      StorageEncrypted: true
+      DatabaseName: !Ref DBName
+      Engine: aurora-postgresql
+      EngineMode: provisioned
+      EngineVersion: !Ref EngineVersion
+      DBClusterIdentifier: !Sub ${AWS::StackName}
+      MasterUserSecret:
+        SecretArn: !Ref AuroraSQLSecretARN
+      #SecondsBeforeTimeout: 300 # default value is 300
+      BackupRetentionPeriod: 35 # max value is 35, if backups are wanted for longer, we need to leverape Auroras ability to backup snapshots to AWS S3
+      StorageType: "aurora" # or aurora-iopt1 for IO intensive workloads
+      SnapshotIdentifier: !If [HasDBSnapshotIdentifier, !Ref 'DBSnapshotIdentifier', !Ref AWS::NoValue] # Never remove this value! If you do so, the cluster will be replaced with an empty cluster.
+      # From AWS Docs:After you restore a DB cluster with a SnapshotIdentifier property, you must specify the same SnapshotIdentifier property for any future updates to the DB cluster.
+      # When you specify this property for an update, the DB cluster is not restored from the snapshot again, and the data in the database is not changed.
+      # However, if you don't specify the SnapshotIdentifier property, an empty DB cluster is created, and the original DB cluster is deleted.
+      # If you specify a property that is different from the previous snapshot restore property, a new DB cluster is restored from the specified SnapshotIdentifier property, and the original DB cluster is deleted.
+      DBSubnetGroupName: !Ref DBInstanceSubnetGroup
+      VpcSecurityGroupIds: [!Ref PostgreSQLRDSSecurityGroup]
+      #EnableIAMDatabaseAuthentication: true # might be worth investigating later
+      #EnableCloudwatchLogsExports: [postgresql] #"Aurora Serverless currently doesn't support CloudWatch Log Export"
+      AutoMinorVersionUpgrade: true
+      PreferredBackupWindow: "00:00-01:00" # UTC time, equivalent to 10:00->11:00 Brisbane Time
+      PreferredMaintenanceWindow: "Mon:02:00-Mon:04:00" # UTC time, equivalent to 12:00 -> 14:00 Brisbane Time
+      CopyTagsToSnapshot: true
+      NetworkType: "IPV4"
+      AvailabilityZones:
+        - !Ref SubnetPrivate1AvailabilityZone
+        - !Ref SubnetPrivate2AvailabilityZone
+      Tags:
+        - Key: "Name"
+          Value: !Sub "${AWS::StackName}-RDS-Password"
+        - Key: "EnvironmentTag"
+          Value: !Ref "EnvironmentTag"
+        - Key: "CustomerTag"
+          Value: !Ref "CustomerTag"
+        - Key: "InfrastructureReleaseTag"
+          Value: !Ref "InfrastructureReleaseTag"
+        - Key: "RegionTag"
+          Value: !Ref "RegionTag"
+        - Key: "ServiceLayerTag"
+          Value: !Ref "ServiceLayerTag"
+        - Key: "StackInteger"
+          Value: !Ref "StackInteger"
+        - Key: "StackName"
+          Value: !Sub "${AWS::StackName}"
+      ServerlessV2ScalingConfiguration:
+        MinCapacity: !Ref AuroraMinCapacity
+        MaxCapacity: !Ref AuroraMaxCapacity
+
+  DBInstanceSubnetGroup:
+    Type: AWS::RDS::DBSubnetGroup
+    Properties:
+      DBSubnetGroupDescription: "Aurora Subnet Group for platform app server stacks"
+      DBSubnetGroupName: !Sub "${AWS::StackName}-DBSubnetGroup"
+      SubnetIds:
+        - !Ref "SubnetPrivate1"
+        - !Ref "SubnetPrivate2"
+      Tags:
+        - Key: "Name"
+          Value: !Sub "${AWS::StackName}-ADO-PAT"
+        - Key: "EnvironmentTag"
+          Value: !Ref "EnvironmentTag"
+        - Key: "CustomerTag"
+          Value: !Ref "CustomerTag"
+        - Key: "InfrastructureReleaseTag"
+          Value: !Ref "InfrastructureReleaseTag"
+        - Key: "RegionTag"
+          Value: !Ref "RegionTag"
+        - Key: "ServiceLayerTag"
+          Value: !Ref "ServiceLayerTag"
+        - Key: "StackInteger"
+          Value: !Ref "StackInteger"
+        - Key: "StackName"
+          Value: !Sub "${AWS::StackName}"
+
+  AuroraRole:
+    Type: "AWS::IAM::Role"
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: "2012-10-17"
+        Statement:
+          - Effect: Allow
+            Principal:
+              Service:
+                - "rds.amazonaws.com"
+            Action:
+              - "sts:AssumeRole"
+      Description: "Use for RDS Proxy"
+      ManagedPolicyArns:
+        - 'arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole'
+      Policies:
+        - PolicyName: !Sub "${AWS::StackName}-cluster-db-policy"
+          PolicyDocument:
+            Version: "2012-10-17"
+            Statement:
+            - Effect: Allow
+              Action:
+                - "secretsmanager:GetSecretValue"
+              Resource: !Ref AuroraSQLSecretARN
+            - Effect: Allow
+              Action:
+                - "kms:Decrypt"
+              Resource: !Ref AuroraSQLSecretARN
+              Condition:
+                StringEquals:
+                  kms:ViaService: !Sub "secretsmanager.${AWS::Region}.amazonaws.com"
+            - Effect: "Allow"
+              Action:
+                - "s3:GetBucketLocation"
+              Resource:
+                - !Sub "arn:aws:s3:::${AuroraBackupsBucketName}"
+            - Effect: "Allow"
+              Action:
+                - "s3:ListBucket"
+              Resource:
+                - !Sub "arn:aws:s3:::${AuroraBackupsBucketName}"
+            - Effect: "Allow"
+              Action:
+                - "s3:GetObject"
+              Resource:
+                - !Sub "arn:aws:s3:::${AuroraBackupsBucketName}/*"
+            - Effect: "Allow"
+              Action:
+                - "s3:PutObject"
+              Resource:
+                - !Sub "arn:aws:s3:::${AuroraBackupsBucketName}/*"
+      Path: /
+      RoleName: !Sub "${AWS::StackName}-AuroraRole"
+
+Outputs:
+  AuroraRoleArn:
+    Description: "Returns the ARN of the Aurora Role used for this cluster"
+    Value: !GetAtt AuroraRole.Arn
+  ClusterName:
+    Description: "Returns the Cluster name"
+    Value: !Ref RDSDBCluster
+  ClusterArn:
+    Description: "The ARN of the DB ClusterL"
+    Value: !GetAtt RDSDBCluster.DBClusterArn
+  DBEndpoint:
+    Description: "The RDS DB enpoint from this stack"
+    Value: !GetAtt RDSDBCluster.Endpoint.Address
+  DBName:
+    Description: 'The name of the Database managed by AWS Aurora'
+    Value: !Ref DBName
+  DBPort:
+    Description: "The RDS DB port from this stack"
+    Value: !GetAtt RDSDBCluster.Endpoint.Port
+  EngineVersion:
+    Description: "The PostgreSQL Engine version we're using"
+    Value: !Ref EngineVersion
+  InstanceName:
+    Description: 'The name of the database instance.'
+    Value: !Ref RDSDBCluster
+  PostgreSQLRDSSecurityGroupId:
+    Description: 'The security group used to manage access to RDS Postgres.'
+    Value: !GetAtt PostgreSQLRDSSecurityGroup.GroupId
\ No newline at end of file
diff --git a/tests/test_convert.py b/tests/test_convert.py
index 2d387c6..3f1c41d 100644
--- a/tests/test_convert.py
+++ b/tests/test_convert.py
@@ -20,6 +20,12 @@ def tc():
     return tc
 
 
+def test_parse_attributes():
+    arguments, attributes = doc_file.parse_attributes(
+        "/tmp/terraform_src/website/docs/r/rds_cluster.html.markdown"
+    )
+
+
 props_to_args_tests = [
     # (props, expected_args, docs_path)
     (
diff --git a/tests/test_functional/test_conversion.py b/tests/test_functional/test_conversion.py
new file mode 100644
index 0000000..8e44a24
--- /dev/null
+++ b/tests/test_functional/test_conversion.py
@@ -0,0 +1,26 @@
+from click.testing import CliRunner
+from cf2tf.app import cli
+
+from pathlib import Path
+
+import pytest
+
+template_dir = (Path(__file__).parent / "../data/templates").resolve()
+
+
+template_names = [template.name for template in template_dir.iterdir()]
+
+tests = [(name) for name in template_names]
+
+
+@pytest.mark.parametrize("template_name", tests)
+def test_templates(template_name: str):
+    global template_dir
+
+    runner = CliRunner()
+
+    template_path = template_dir / template_name
+
+    result = runner.invoke(cli, [str(template_path)])
+
+    assert result.exit_code == 0
diff --git a/tests/test_terraform/test_code.py b/tests/test_terraform/test_code.py
index e91d5f3..d3981e2 100644
--- a/tests/test_terraform/test_code.py
+++ b/tests/test_terraform/test_code.py
@@ -29,9 +29,8 @@ def test_sm_(mock_sm: SearchManager):
 sm_find_tests = [
     ("AWS::ApiGatewayV2::Integration", "apigatewayv2_integration.html.markdown"),
     ("AWS::Lambda::Function", "lambda_function.html.markdown"),
-    ("AWS::EC2::Instance", "instance.html.markdown"),
     ("AWS::Events::Rule", "cloudwatch_event_rule.html.markdown"),
-    ("AWS::Cognito::UserPool", "cognito_user_pool.markdown"),
+    ("AWS::Cognito::UserPool", "cognito_user_pool.html.markdown"),
 ]
 
 
diff --git a/tests/test_terraform/test_configuration.py b/tests/test_terraform/test_configuration.py
index 1c09a6a..ca1affa 100644
--- a/tests/test_terraform/test_configuration.py
+++ b/tests/test_terraform/test_configuration.py
@@ -2,6 +2,11 @@
 
 from cf2tf.convert import TemplateConverter
 from cf2tf.terraform import code
+from cf2tf.terraform.hcl2.primitive import (
+    BooleanType,
+    NumberType,
+    StringType,
+)
 
 
 def tc() -> TemplateConverter:
@@ -26,3 +31,22 @@ def test_resolve_values(input, expected_result, tc: TemplateConverter):
     result = tc.resolve_values(input, {})
 
     assert result == expected_result
+
+
+resolves_types_tests = [
+    # (input, expected_result, rendered_value, tc)
+    ("foo", StringType, '"foo"', tc()),
+    (123, NumberType, 123, tc()),
+    (True, BooleanType, "true", tc()),
+    (False, BooleanType, "false", tc()),
+]
+
+
+@pytest.mark.parametrize(
+    "input, expected_result, rendered_value, tc", resolves_types_tests
+)
+def test_resolve_types(input, expected_result, rendered_value, tc: TemplateConverter):
+    result = tc.resolve_values(input, {})
+
+    assert isinstance(result, expected_result)
+    assert result.render() == rendered_value