Pablo Fernandez
Pablo Fernandez

Reputation: 287540

How do you use sam to run a cdk application that uses codepipeline to deploy?

I'm building an application with AWS CDK that uses CodePipeline. So there are essentially two stacks, one sets up the code pipeline and the other sets up the application (and it's triggered by the pipeline).

I'm working out of what is built in https://cdkworkshop.com/ so in my project I have a file cdk.json that has an entry app pointing to a specific TypeScript file (example4-be is the application name):

{
  "app": "npx ts-node --prefer-ts-exts bin/example4-be.ts",

This file builds the CodePipeline stack:

#!/usr/bin/env node
import * as cdk from "aws-cdk-lib"
import {PipelineStack} from "../lib/pipeline-stack"

const app = new cdk.App()
new PipelineStack(app, "Example4BePipeline")

so when I try to use sam to run the application locally, it fails saying there are no Lambda functions. I believe it's because it's building the CodePipeline stack and not the application stack. If I change exampe4-be.ts to this:

#!/usr/bin/env node
import * as cdk from "aws-cdk-lib"
import {Example4BeStack} from "../lib/example4-be-stack";

const app = new cdk.App()
new Example4BeStack(app, "Example4BePipeline")

it works. Example4BeStack is the application stack. But obviously if I commit this, the CodePipeline will stop working.

How can I have both things working at the same time?

The commands I run to have sam run the application locally are:~

cdk synth --no-staging | out-file template.yaml -encoding utf8
sam local start-api

Upvotes: 0

Views: 569

Answers (1)

fedonev
fedonev

Reputation: 25679

Create two cdk.App chains in your codebase, one for the pipeline and one for standalone development/testing with sam local or cdk deploy. Your "application" stacks will be part of both chains. Here's a simplified example of the pattern I use:

Pipeline deploy (app-pipeline.ts): ApiStack and DatabaseStack are children of a cdk.Stage, grandchildren of the PipelineStack, and great-granchildren of a cdk.App.

Development deploys (app.ts): ApiStack and DatabaseStack are children of a cdk.App. Use with sam local and cdk deploy for dev and testing.

bin/
  app.ts              # calls makeAppStacks to add the stacks; runs frequently during development
  app-pipeline.ts     # adds the PipelineStack to an App

lib/
  ApiStack.ts
  DatabaseStack.ts
  PipelineStack.ts    # adds DeployStage to the pipeline
  DeployStage.ts      # subclasses cdk.Stage; calls makeAppStacks.ts to add the stacks
  makeAppStacks.ts    # adds the Api and Db stacks to either an App or a Stage

A makeAppStacks wrapper function instantiates the actual stacks.

// makeAppStacks.ts
export const makeAppStacks = (scope: cdk.App | DeployStage, appName: string, account: string, region: string): void => {
  const {table} = new DatabaseStack(scope, 'MyDb', ...)
  new ApiStack(scope, 'MyApi', {table, ...})
};

makeAppStacks gets called in two places. DeployStage.ts and app.ts are generic and rarely change:

// DeployStage.ts
export class DeployStage extends cdk.Stage {
  constructor(scope: Construct, id: string, props: DeployStageProps) {
    super(scope, id, props);
    makeAppStacks(this, props.appName, props.env.account, props.env.region);
  }
}
// app.ts
const app = new cdk.App();
const account = process.env.AWS_ACCOUNT;
makeAppStacks(app, 'MyApp', account, 'us-east-1');

Add some scripts for convenience:

"scripts": {
  "---- app (sandbox env) ----": "",
  "deploy-sandbox:cdk": "AWS_ACCOUNT=<Sandbox Acct> npx cdk deploy '*' --app 'ts-node ./bin/app.ts' --profile sandbox --outputs-file cdk.outputs.json",
  "deploy-sandbox": "build && test && deploy-sandbox:cdk",
  "destroy-sandbox": ...,
  "synth-sandbox": ...,
  "---- app-pipeline (pipeline env) ----": "",
  "deploy-pipeline:cdk": "npx cdk deploy '*' --app 'ts-node ./bin/app-pipeline.ts' --profile pipeline",
  "deploy-pipeline": "build && deploy-pipeline:cdk",
}

Upvotes: 1

Related Questions