Files
spicy-automation/docs/DEVELOPMENT.md
Ryan Wilson 68684df471 Initial commit: Spicy CDK automation framework
Jenkins shared library and CDK constructs for AWS infrastructure.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-11-18 22:21:00 -08:00

8.2 KiB

Local Development Guide

Run and test CDK locally without Jenkins.

Prerequisites

  • Node.js 22+
  • pnpm (package manager)
  • AWS CLI (configured with credentials)

Setup

cd spicy-automation

# Install dependencies
pnpm install

Bootstrapping

Before deploying any CDK stacks, you must bootstrap the CDK environment in your AWS account and region. This is a one-time setup per account/region combination.

Bootstrap Command

# Using Docker (same as your deploy command)
docker run -v ~/.aws:/root/.aws:ro spicy-sdk \
  cdk bootstrap \
  aws://ACCOUNT_ID/REGION

# Or locally with npx
npx cdk bootstrap aws://ACCOUNT_ID/REGION

# Example for ca-central-1
npx cdk bootstrap aws://$AWS_ACCOUNT_ID/ca-central-1

What Bootstrap Does

CDK bootstrap creates the following resources in your AWS account:

  • S3 bucket for storing CDK assets
  • IAM roles for deployment
  • SSM parameters for tracking bootstrap version

Verify Bootstrap Status

# Check if environment is bootstrapped
aws ssm get-parameter \
  --name /cdk-bootstrap/hnb659fds/version \
  --region ca-central-1

If the parameter exists, the environment is bootstrapped. If you get a ParameterNotFound error, you need to bootstrap.

Bootstrap Qualifier

This project uses the bootstrap qualifier hnb659fds (configured in cdk.json). This qualifier must match between bootstrap and deploy commands.

Running CDK Commands

CDK runs directly with ts-node - no build step required!

Synthesize a Template

# Minimal VPC
npx cdk synth \
  -c stackType=vpc \
  -c stackName=my-vpc \
  -c ownerTag=MyTeam \
  -c productTag=myapp \
  -c componentTag=vpc

# With custom options
npx cdk synth \
  -c stackType=vpc \
  -c stackName=my-vpc \
  -c ownerTag=MyTeam \
  -c productTag=myapp \
  -c componentTag=vpc \
  -c vpcCidr=10.0.0.0/16 \
  -c numberOfAzs=2 \
  -c createAdditionalPrivateSubnets=false

Show Diff

npx cdk diff \
  -c stackType=vpc \
  -c stackName=my-vpc \
  -c ownerTag=MyTeam \
  -c productTag=myapp \
  -c componentTag=vpc

Deploy

# Requires AWS credentials
export AWS_PROFILE=my-profile
# or
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_REGION=ca-central-1

npx cdk deploy \
  -c stackType=vpc \
  -c stackName=my-vpc \
  -c ownerTag=MyTeam \
  -c productTag=myapp \
  -c componentTag=vpc

Destroy

npx cdk destroy \
  -c stackType=vpc \
  -c stackName=my-vpc \
  -c stackType=vpc

Context Parameters

All VPC parameters can be passed via -c key=value:

Context Key Type Description
stackType string Stack type: vpc, ecs-cluster, ecs-service
stackName string CloudFormation stack name
ownerTag string Owner tag
productTag string Product tag
componentTag string Component tag
build string Build tag (defaults to git SHA)
vpcCidr string VPC CIDR block
vpcTenancy string default or dedicated
numberOfAzs string 2, 3, or 4
availabilityZones string Comma-separated AZ list
createPrivateSubnets string true or false
createAdditionalPrivateSubnets string true or false
publicSubnetTag string Tag for public subnets
privateSubnetATag string Tag for private subnets type 1
privateSubnetBTag string Tag for private subnets type 2

Running Tests

# Run all tests
pnpm test

# Run with coverage
pnpm test -- --coverage

# Run specific test
pnpm test -- --testNamePattern="creates VPC"

# Watch mode
pnpm test -- --watch

Project Structure

spicy-automation/
├── bin/
│   └── spicy-cdk.ts          # CDK app entry point
├── lib/
│   ├── constructs/           # Reusable constructs
│   │   ├── spicy-vpc.ts      # VPC construct
│   │   └── index.ts
│   ├── stacks/               # Stack definitions
│   │   ├── spicy-vpc-stack.ts
│   │   └── index.ts
│   └── index.ts
├── test/
│   └── spicy-cdk.test.ts     # Jest tests
├── jenkins/
│   └── vars/                 # Jenkins shared library
├── cdk.json                  # CDK configuration
├── tsconfig.json             # TypeScript configuration
└── package.json

Adding a New Construct

1. Create the Construct

lib/constructs/my-construct.ts:

import * as cdk from 'aws-cdk-lib/core';
import { Construct } from 'constructs';

export interface MyConstructProps {
  readonly name: string;
  // ... other props
}

export class MyConstruct extends Construct {
  constructor(scope: Construct, id: string, props: MyConstructProps) {
    super(scope, id);

    // Create resources...
  }
}

2. Export from Index

lib/constructs/index.ts:

export * from './spicy-vpc';
export * from './my-construct';

3. Create a Stack

lib/stacks/my-stack.ts:

import * as cdk from 'aws-cdk-lib/core';
import { Construct } from 'constructs';
import { MyConstruct, MyConstructProps } from '../constructs/my-construct';

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: MyConstructProps & cdk.StackProps) {
    super(scope, id, props);

    new MyConstruct(this, 'MyConstruct', props);
  }

  static fromContext(scope: Construct, id: string, stackProps?: cdk.StackProps): MyStack {
    const app = scope.node.root as cdk.App;

    return new MyStack(app, id, {
      ...stackProps,
      name: app.node.tryGetContext('name') ?? 'default',
    });
  }
}

4. Register in App

bin/spicy-cdk.ts:

import { MyStack } from '../lib/stacks';

switch (stackType) {
  case 'vpc':
    SpicyVpcStack.fromContext(app, stackName, { env });
    break;
  case 'my-stack':
    MyStack.fromContext(app, stackName, { env });
    break;
  // ...
}

5. Add Tests

test/my-stack.test.ts:

import { Template } from 'aws-cdk-lib/assertions';
import * as cdk from 'aws-cdk-lib/core';
import { MyStack } from '../lib/stacks/my-stack';

describe('MyStack', () => {
  test('creates resources', () => {
    const app = new cdk.App();
    const stack = new MyStack(app, 'TestStack', {
      name: 'test',
    });

    const template = Template.fromStack(stack);

    // Assert on resources
    template.resourceCountIs('AWS::SomeService::Resource', 1);
  });
});

TypeScript Configuration

The project uses CommonJS modules for compatibility with ts-node:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "strict": true,
    "esModuleInterop": true
  }
}

Debugging

Enable CDK Debug Output

CDK_DEBUG=true npx cdk synth ...

View Synthesized Template

Templates are output to cdk.out/:

npx cdk synth -c stackType=vpc -c stackName=test ...
cat cdk.out/test.template.json | jq .

Check Logical IDs

Verify resource logical IDs are stable:

npx cdk synth ... 2>&1 | grep -E "^  [A-Za-z]+:"

Building (Optional)

While not required (ts-node runs TypeScript directly), you can compile:

# Compile to JavaScript
pnpm run build

# Watch mode
pnpm run watch

Output goes to dist/.

IDE Setup

VS Code

Recommended extensions:

  • ESLint
  • Prettier
  • AWS Toolkit

.vscode/settings.json:

{
  "typescript.tsdk": "node_modules/typescript/lib",
  "editor.formatOnSave": true
}

Cursor

The project works out of the box with Cursor's TypeScript support.