8
votes

I want to deploy AWS CDK stacks from with a Lambda function. The use case is to support similar functionality to that described in Trek10's Serverless CI/CD pipeline, where the relevant code is here.

In a nutshell, AWS CodePipelines only support listening to changes within a single Git branch. I want to listen to GitHub events relating to the creation of repos and branches and create CodePipeline instances in response to these events so that there is a Pipeline for each branch of each Git repository. I want a Lambda to listen to the GitHub events and create CDK stacks. The Trek10 example uses Python and calls CloudFormation directly. I'd like the Lambdas to be much simpler and use the CDK instead.

Here is a simple Lambda using Typescript that is derived from this AWS CDK issue:

// index.ts
import {ScheduledEvent} from 'aws-lambda';
import {CloudFormationDeploymentTarget, DEFAULT_TOOLKIT_STACK_NAME} from 'aws-cdk/lib/api/deployment-target';
import {CdkToolkit} from 'aws-cdk/lib/cdk-toolkit';
import {AppStacks} from 'aws-cdk/lib/api/cxapp/stacks';
import {Configuration} from 'aws-cdk/lib/settings';
import {execProgram} from "aws-cdk/lib/api/cxapp/exec";
import * as yargs from 'yargs';
import {SDK} from 'aws-cdk/lib/api/util/sdk';
export const handleCloudWatchEvent = async (event: ScheduledEvent): Promise<void> => {
    try {
        const aws = new SDK();
        const argv = await yargs.parse(['deploy', '--app', 'bin/pipeline.js', '--staging', '/tmp', '--verbose', '--require-approval', 'never']);

        const configuration = new Configuration(argv);
        await configuration.load();
        const appStacks = new AppStacks({
            configuration,
            aws,
            synthesizer: execProgram,
        });

        const provisioner = new CloudFormationDeploymentTarget({ aws });
        const cli = new CdkToolkit({ appStacks, provisioner });
        const toolkitStackName = configuration.settings.get(['toolkitStackName']) || DEFAULT_TOOLKIT_STACK_NAME;

        await cli.deploy({
            stackNames: [],
            exclusively: argv.exclusively as boolean,
            toolkitStackName,
            roleArn: argv.roleArn as string,
            requireApproval: configuration.settings.get(['requireApproval']),
            ci: true,
            reuseAssets: argv['build-exclude'] as string[],
            sdk: aws
        });

        return;
    } catch (e) {
        console.error(e);
        return;
    }

};

However, I get an error as described in the issue mentioned above:

ERROR   { Error: Cannot find module '../package.json'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:636:15)
    at Function.Module._load (internal/modules/cjs/loader.js:562:25)
    at Module.require (internal/modules/cjs/loader.js:692:17)
    at new SDK (/var/task/node_modules/aws-cdk/lib/api/util/sdk.ts:92:39)
    at Runtime.exports.handleCloudWatchEvent [as handler] (/resources/index.ts:204:21)
    at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)
    at process._tickCallback (internal/process/next_tick.js:68:7) code: 'MODULE_NOT_FOUND' }

I don't want to patch sdk.ts: https://github.com/aws/aws-cdk/blob/master/packages/aws-cdk/lib/api/util/sdk.ts#L92 as that seems to be a very dirty solution so I'm looking for another solution.

What is the simplest working example of calling the CDK from within a Lambda function?

2
@AliHabibzadeh what does a simple example look like please? Does the lambda copy the pipeline files somewhere into some form of npm project structure, run aws sts assume-role to get credentials then execute npm run build && cdk synth && cdk deploy against that structure and using those credentials as env variables for that child process?John

2 Answers

2
votes

How did you end up packaging cdk to run inside lambda? was that done via layers or is there a easier way?

1
votes

Recently I had an ideal use case for a CDK deployer in Lambda but couldn't find any good and full examples of how to do this. Using kadishmal example from CDK Github I managed to put CDK into Lambda layer, delete AWS SDK module from there (Lambda already has it), include it into a Lambda, and run CDK deploy/destroy from there. There is a size restriction for a Lambda and all its layers of 250mb so I also had to use Webpack to optimize the build size.

Here a Github repository with a basic example - a stack template that deploys S3 bucket and destroys it.