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>
This commit is contained in:
2025-11-18 22:21:00 -08:00
commit 68684df471
51 changed files with 15587 additions and 0 deletions

365
docs/DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,365 @@
# 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.