Created
March 14, 2021 19:04
-
-
Save yukiarrr/ca846d599d166c750bff5e8c8ac986f8 to your computer and use it in GitHub Desktop.
Check NestedStack diffs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash -e | |
cdk synth > /dev/null | |
color=${1:-'--color'} | |
for stack_name in ${YOUR_PARENT_STACK_NAMES}; do | |
resources=$(aws cloudformation list-stack-resources --stack-name ${stack_name}) | |
length=$(echo ${resources} | jq '.StackResourceSummaries | length') | |
for i in $(seq 0 $(expr ${length} - 1)); do | |
summaries=$(echo ${resources} | jq ".StackResourceSummaries[${i}]") | |
[ "$(echo ${summaries} | jq -r '.ResourceType')" != 'AWS::CloudFormation::Stack' ] && continue | |
aws cloudformation get-template --stack-name $(echo ${summaries} | jq -r '.PhysicalResourceId') | jq '.TemplateBody' > template.json | |
resource_id=$(echo ${summaries} | jq -r '.LogicalResourceId') | |
stack_id=$(echo ${resource_id} | sed -e 's/NestedStack.*//') | |
file=$(find . -name "${stack_name}${stack_id}*.nested.template.json") | |
diff=$(json-diff ${color} template.json ${file} || true) | |
[ "${diff}" ] || continue | |
echo '------------------------------------------------------------------' | |
echo ${resource_id} | |
echo '------------------------------------------------------------------' | |
echo -e "${diff}" | |
done | |
done | |
rm -f template.json |
Many thanks @yukiarrr
For anyone wanting the above translated into JavaScript - I use it as an NPM script with my project.
const { CloudFormation } = require("aws-sdk");
const glob = require("glob");
const path = require("path");
const fs = require("fs");
const jsonDiff = require("json-diff");
const ROOT_STACK_NAME = "foo";
(async () => {
const cfn = new CloudFormation();
const synthed = glob.sync(
path.join(__dirname, "cdk.out", `${ROOT_STACK_NAME}*.nested.template.json`)
);
const summaries = await cfn
.listStackResources({ StackName: ROOT_STACK_NAME })
.promise()
.then(r => r.StackResourceSummaries);
const sleep = seconds =>
new Promise(resolve => {
setTimeout(resolve, seconds * 1000);
});
const nestedStacks = await Promise.all(
summaries
.filter(summary => summary.ResourceType === "AWS::CloudFormation::Stack")
.map(async (stack, i) => {
// prevent throttling
await sleep(i / 2);
const name = stack.LogicalResourceId.split("NestedStack").shift();
const pending = synthed.find(t =>
t.split(ROOT_STACK_NAME).pop().startsWith(name)
);
return {
name,
template: {
pending: fs.readFileSync(pending).toString(),
deployed: await cfn
.getTemplate({ StackName: stack.PhysicalResourceId })
.promise()
.then(r => r.TemplateBody),
},
diff() {
const pending = JSON.parse(this.template.pending);
const deployed = JSON.parse(this.template.deployed);
return jsonDiff.diffString(deployed, pending);
},
};
})
);
for (const stack of nestedStacks) {
const diff = stack.diff();
if (diff.length > 0) {
console.log("---------------------------------------------");
console.log("Stack:", stack.name);
console.log("---------------------------------------------");
console.log(diff);
}
}
})().catch(console.error);
Thanks for the inspiration! Did have a need for a Python version.
import boto3
import json
import glob
import sys
from subprocess import run
import difflib
try:
from colorama import Fore, Back, Style, init
init()
except ImportError: # fallback so that the imported classes always exist
class ColorFallback():
__getattr__ = lambda self, name: ''
Fore = Back = Style = ColorFallback()
def color_diff(diff):
for line in diff:
if line.startswith('+'):
yield Fore.GREEN + line + Fore.RESET
elif line.startswith('-'):
yield Fore.RED + line + Fore.RESET
elif line.startswith('^'):
yield Fore.BLUE + line + Fore.RESET
else:
yield line
def confirm(msg="Do you want to continue?"):
return input(f"{msg} ").lower() in ('yes', 'y')
def cdk_diff_nested_stack(stack_name):
"""
Colorfull diff between local changes to CDK and what is deployed
"""
cf_client = boto3.client("cloudformation")
responce = cf_client.list_stack_resources(StackName=stack_name)
resource_summeries = responce['StackResourceSummaries']
for resource in resource_summeries:
if 'AWS::CloudFormation::Stack' in resource['ResourceType']:
nested_stack_name_arn = resource['PhysicalResourceId']
nested_response = cf_client.get_template(StackName=nested_stack_name_arn)
template = nested_response['TemplateBody']
nested_stack_name = resource['LogicalResourceId'].split('NestedStack')[0]
print_stack_name = f'### {nested_stack_name} ###'
print(print_stack_name)
print('#'*len(print_stack_name))
nested_files = glob.glob(f'./cdk.out/*{nested_stack_name}*.nested.template.json')
nested_file = [ns for ns in nested_files if 'Provider' not in ns][0]
template_json = json.dumps(template, indent=2)
json_file = open(nested_file)
txt = json_file.read()
json_file.close()
#print(txt)
diff = difflib.ndiff(template_json.split('\n'), txt.split('\n'))
diff = color_diff(diff)
print('\n'.join(diff))
print('---')
if not confirm(msg='Continue with next stack diff?'):
sys.exit(1)
print('---')
if __name__ == "__main__":
root_stack_name = "RootCdkStackName"
run(['cdk', 'synth', '--quiet'])
cdk_diff_nested_stack(root_stack_name)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note
Example
Diff
Check