Skip to content

Instantly share code, notes, and snippets.

@ottokruse
Last active August 9, 2023 11:06
Show Gist options
  • Save ottokruse/41694de3831d6bfe9080743b352aa2fe to your computer and use it in GitHub Desktop.
Save ottokruse/41694de3831d6bfe9080743b352aa2fe to your computer and use it in GitHub Desktop.
Script to publish CDK assets (e.g. Lambda function code) to S3 and generate parameter files, so you can combine cdk synth with CloudFormation deployments. This is essentially the equivalent of 'sam package' but then for CDK. Tested to work for Lambda and S3-deployments
#!/usr/bin/env ts-node
// This script uploads your assets to the CDK staging bucket in S3 (just as cdk deploy would)
// and writes out two files:
// - parameters.ini to use in CLI deployments (see instructions below)
// - parameters.json to use in AWS CodePipeline for CloudFormation deployments
//
// Installation instructions:
// - Save this script cdk-package.ts to the root of your CDK repo (i.e. next to cdk.json) and make it executable
// - Install script dependencies: npm install jsonpath aws-sdk adm-zip @types/jsonpath @types/adm-zip
//
// Run instructions for CLI deployments:
// 1) cdk synth > template.yml
// 2) AWS_REGION=eu-west-1 ./cdk-package.ts
// 3) aws cloudformation deploy \
// --stack-name <stack-name> \
// --template-file template.yml \
// --parameter-overrides $(cat parameters.ini) \
// --capabilities CAPABILITY_IAM
import { promises as fs } from "fs";
import { join } from "path";
import * as jp from "jsonpath";
import { S3, CloudFormation } from "aws-sdk";
import * as Zip from "adm-zip";
const CFNCLIENT = new CloudFormation();
const S3CLIENT = new S3();
async function main() {
// Determine toolkit stack name, that stack will have an Output revealing the staging bucket
const { toolkitStackName = 'CDKToolkit' } = JSON.parse(
await fs.readFile(join(__dirname, "cdk.json"), "utf8")
);
const { Stacks } = await CFNCLIENT.describeStacks({
StackName: toolkitStackName,
}).promise();
if (!Stacks || !Stacks.length) {
throw new Error(`Toolkit stack ${toolkitStackName} does not exist, please run: cdk bootstrap`);
}
const stagingBucket = Stacks[0].Outputs!.find(
(out) => out.OutputKey === "BucketName"
)!.OutputValue!;
console.log(`Will use staging bucket ${stagingBucket}`);
// Parse manifest file, locate assets and upload them to S3 staging bucket
const manifest = JSON.parse(
await fs.readFile(join(__dirname, "cdk.out", "manifest.json"), "utf8")
);
const assets = jp.query(manifest, `$..[?(@.type=='aws:cdk:asset')]`);
const publishPromises = assets.map(async (asset) => {
let assetBody: Buffer;
let s3key: string;
if (!asset.data.path.endsWith('.zip')) {
const lambdaZip = new Zip();
lambdaZip.addLocalFolder(join(__dirname, "cdk.out", asset.data.path));
s3key = `assets/${asset.data.id}.zip`;
assetBody = lambdaZip.toBuffer();
} else {
assetBody = await fs.readFile(join(__dirname, "cdk.out", asset.data.path));
s3key = `assets/${asset.data.id}`;
}
await S3CLIENT.putObject({
Key: s3key,
Bucket: stagingBucket,
Body: assetBody,
}).promise();
console.log(`Uploaded asset ${asset.data.id} (${asset.data.path})`);
return {
[asset.data.s3BucketParameter]: stagingBucket,
[asset.data.s3KeyParameter]: `${s3key}||`,
[asset.data.artifactHashParameter]: asset.data.sourceHash,
};
});
// Write out parameter files for deployments
const cfnParameters = await Promise.all(publishPromises);
const cfnParametersFlat = cfnParameters.reduce(
(reduced, params) => Object.assign(reduced, params),
{}
);
await fs.writeFile(
"parameters.ini",
Object.entries(cfnParametersFlat)
.map(([k, v]) => `${k}=${v}`)
.join("\n")
);
await fs.writeFile(
"parameters.json",
JSON.stringify(cfnParametersFlat, null, 2)
);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment