# Local Development Guide Run and test CDK locally without Jenkins. ## Prerequisites - **Node.js 22+** - **pnpm** (package manager) - **AWS CLI** (configured with credentials) ## Setup ```bash 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash npx cdk diff \ -c stackType=vpc \ -c stackName=my-vpc \ -c ownerTag=MyTeam \ -c productTag=myapp \ -c componentTag=vpc ``` ### Deploy ```bash # 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 ```bash 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 ```bash # 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`: ```typescript 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`: ```typescript export * from './spicy-vpc'; export * from './my-construct'; ``` ### 3. Create a Stack `lib/stacks/my-stack.ts`: ```typescript 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`: ```typescript 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`: ```typescript 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: ```json { "compilerOptions": { "target": "ES2022", "module": "CommonJS", "strict": true, "esModuleInterop": true } } ``` ## Debugging ### Enable CDK Debug Output ```bash CDK_DEBUG=true npx cdk synth ... ``` ### View Synthesized Template Templates are output to `cdk.out/`: ```bash 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: ```bash npx cdk synth ... 2>&1 | grep -E "^ [A-Za-z]+:" ``` ## Building (Optional) While not required (ts-node runs TypeScript directly), you can compile: ```bash # 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`: ```json { "typescript.tsdk": "node_modules/typescript/lib", "editor.formatOnSave": true } ``` ### Cursor The project works out of the box with Cursor's TypeScript support.