Jenkins shared library and CDK constructs for AWS infrastructure. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
366 lines
8.2 KiB
Markdown
366 lines
8.2 KiB
Markdown
# 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.
|