question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to deploy CDK app via Lambda

See original GitHub issue

Hi,

I think this is an unsupported use case for CDK. I am trying to deploy the CDK app via Lambda. The goal is for the Lambda function to call cdk deploy and get the application, included together with the Lambda code, deployed.

Currently, calling cdk deploy via the Node’s exec command fails due to missing AWS credentials. Ideally, the same role that is used to execute the Lambda function should be reused. In my case this function has all the permissions to deploy a CFN template that the underlying CDK generates.

I tried to extract the deployment related code out of the aws-cdk package and call it directly, but found out that it depends on the credential provider which tries to find the credentials either in the env variables or config files.

Is there a way to bypass this credentials check and let it just call the APIs to do the job?

Thanks.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:13
  • Comments:11 (2 by maintainers)

github_iconTop GitHub Comments

25reactions
kadishmalcommented, Jun 26, 2019

AFAIK, Lambda doesn’t provide SDK credentials. The recommended way is to add proper permissions to the Lambda IAM role https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-lambda.html. Setting credentials via env variables is a security concern.

Considering the above, this issue is more of a limitation of CDK, not a question. So closing at will seems not caring about your customer which is one of your leadership principles.

Having said that, I figured out how to deploy a CDK app via Lambda. And, no, CDK will not work out of the box in Lambda. The following are the changes I had made locally and deployed to Lambda.

Overrode SDK class so that it doesn’t require credentials for CloudFormation and S3 in https://github.com/awslabs/aws-cdk/blob/master/packages/aws-cdk/lib/api/util/sdk.ts#L95.

// LambdaSDK.ts
import { Environment } from '@aws-cdk/cx-api';
import { SDK } from 'aws-cdk/lib/api/util/sdk';
import * as CloudFormation from 'aws-sdk/clients/cloudformation';
import * as S3 from 'aws-sdk/clients/s3';

export default class LambdaSDK extends SDK {
  async cloudFormation(environment: Environment) {
    return new CloudFormation({
      region: environment.region,
    });
  }
  async s3(environment: Environment) {
    return new S3({
      region: environment.region
    });
  }
}

Here is how I used LambdaSDK in the Lambda handler:

import { Context } from 'aws-lambda';
import { config } from 'aws-sdk';
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 { parseRenames } from "aws-cdk/lib/renames";
import * as yargs from 'yargs';
import { RequireApproval } from "aws-cdk/lib/diff";
import { DISPLAY_VERSION } from "aws-cdk/lib/version";

import LambdaSDK from './LambdaSDK';

module.exports.handler = async (event: any, context: Context) => {
  const aws = new LambdaSDK();
  const argv = await parseCommandLineArguments();

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

  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: argv.ci,
    reuseAssets: argv['build-exclude'] as string[]
  });
};

Copied over the same command line parsing function from cdk into the lambda handler script and removed all commands except deploy.

async function parseCommandLineArguments() {
  return yargs
    .env('CDK')
    .usage('Usage: cdk -a <cdk-app> COMMAND')
    .option('app', { type: 'string', alias: 'a', desc: 'REQUIRED: Command-line for executing your CDK app (e.g. "node bin/my-app.js")', requiresArg: true })
    .option('context', { type: 'array', alias: 'c', desc: 'Add contextual string parameter (KEY=VALUE)', nargs: 1, requiresArg: true })
    .option('plugin', { type: 'array', alias: 'p', desc: 'Name or path of a node package that extend the CDK features. Can be specified multiple times', nargs: 1 })
    .option('rename', { type: 'string', desc: 'Rename stack name if different from the one defined in the cloud executable ([ORIGINAL:]RENAMED)', requiresArg: true })
    .option('trace', { type: 'boolean', desc: 'Print trace for stack warnings' })
    .option('strict', { type: 'boolean', desc: 'Do not construct stacks with warnings' })
    .option('ignore-errors', { type: 'boolean', default: false, desc: 'Ignores synthesis errors, which will likely produce an invalid output' })
    .option('json', { type: 'boolean', alias: 'j', desc: 'Use JSON output instead of YAML', default: false })
    .option('verbose', { type: 'boolean', alias: 'v', desc: 'Show debug logs', default: false })
    .option('profile', { type: 'string', desc: 'Use the indicated AWS profile as the default environment', requiresArg: true })
    .option('proxy', { type: 'string', desc: 'Use the indicated proxy. Will read from HTTPS_PROXY environment variable if not specified.', requiresArg: true })
    .option('ec2creds', { type: 'boolean', alias: 'i', default: undefined, desc: 'Force trying to fetch EC2 instance credentials. Default: guess EC2 instance status.' })
    .option('version-reporting', { type: 'boolean', desc: 'Include the "AWS::CDK::Metadata" resource in synthesized templates (enabled by default)', default: undefined })
    .option('path-metadata', { type: 'boolean', desc: 'Include "aws:cdk:path" CloudFormation metadata for each resource (enabled by default)', default: true })
    .option('asset-metadata', { type: 'boolean', desc: 'Include "aws:asset:*" CloudFormation metadata for resources that user assets (enabled by default)', default: true })
    .option('role-arn', { type: 'string', alias: 'r', desc: 'ARN of Role to use when invoking CloudFormation', default: undefined, requiresArg: true })
    .option('toolkit-stack-name', { type: 'string', desc: 'The name of the CDK toolkit stack', requiresArg: true })
    .option('staging', { type: 'string', desc: 'directory name for staging assets (use --no-asset-staging to disable)', default: '.cdk.staging' })
    .command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', yargs => yargs
      .option('build-exclude', { type: 'array', alias: 'E', nargs: 1, desc: 'do not rebuild asset with the given ID. Can be specified multiple times.', default: [] })
      .option('exclusively', { type: 'boolean', alias: 'e', desc: 'only deploy requested stacks, don\'t include dependencies' })
      .option('require-approval', { type: 'string', choices: [RequireApproval.Never, RequireApproval.AnyChange, RequireApproval.Broadening], desc: 'what security-sensitive changes need manual approval' }))
    .option('ci', { type: 'boolean', desc: 'Force CI detection. Use --no-ci to disable CI autodetection.', default: process.env.CI !== undefined })
    .version(DISPLAY_VERSION)
    .demandCommand(1, '') // just print help
    .help()
    .alias('h', 'help')
    .epilogue([
      'If your app has a single stack, there is no need to specify the stack name',
      'If one of cdk.json or ~/.cdk.json exists, options specified there will be used as defaults. Settings in cdk.json take precedence.'
    ].join('\n\n'))
    .parse(['deploy', '--app', 'bin/LambdaStacksApp.js', '--staging', '/tmp', '--verbose', '--require-approval', 'never']);
}

Note the last parse() command I added:

    .parse(['deploy', '--app', 'bin/LambdaStacksApp.js', '--staging', '/tmp', '--verbose', '--require-approval', 'never']);

That’s to make sure which app to use as well as override the staging directory since Lambda allows to write only to /tmp. Also, disable require-approval since the execution is not supervised.

Finally had to fix this line https://github.com/awslabs/aws-cdk/blob/master/packages/aws-cdk/lib/api/util/sdk.ts#L71:

const pkg = (require.main as any).require('../package.json');

When running from Lambda require.main is Lambda, meaning the above code will fail with:

{"errorMessage":"Cannot find module '../package.json'","errorType":"Error","stackTrace":[
"Function.Module._resolveFilename (module.js:547:15)",
"Function.Module._load (module.js:474:25)",
"Module.require (module.js:596:17)",
"new SDK (/var/task/node_modules/aws-cdk/lib/api/util/sdk.ts:71:39)",
"new LambdaSDK (/var/task/LambdaSDK.ts:10:1)",
"module.exports.handler (/var/task/scheduler.ts:24:15)",
"invoke (/var/runtime/node_modules/awslambda/index.js:288:20)",
"InvokeManager.start (/var/runtime/node_modules/awslambda/index.js:151:9)",
"Object.awslambda.waitForInvoke (/var/runtime/node_modules/awslambda/index.js:499:52)"
]}

The fixed code is:

const pkg = require('../../../package.json');

As a reminder, because I didn’t appreciate your response, this was not a question but a report that a certain functionality is not supported. If you reopen, I will, perhaps, create a PR to make CDK work from within Lambda.

8reactions
denizhoxhacommented, Jun 8, 2020

@kadishmal can you please provide an example how to provision CDK App from Lambda?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Creating a serverless application using the AWS CDK
Create an AWS CDK app · Create a Lambda function that gets a list of widgets with HTTP GET / · Create the...
Read more >
Automate AWS lambda function deployments with AWS CDK
Define CDK Constructs for the application · Create an AWS S3 bucket to hold the CSV files uploaded by the AWS Lambda function....
Read more >
Running AWS CDK from a Lambda function
Deploying. You have to set your AWS credentials first. You can do this in the ~/.aws/credentials file or using environment variables.
Read more >
AWS CDK: Create and Deploy Lambda | Serverless Guru
Run the below command to install the CDK node package to use the AWS CDK command through the CLI. Command to install AWS...
Read more >
How to Deploy a Python Lambda Using AWS CDK? - Medium
Defining Lambda in the CDK app. Update the imports in the lib/cdk-sample-app-stack.ts file to add PythonFunction . import ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found