From 1031242ffa992f7be57b3192fcae2bd3dda05be2 Mon Sep 17 00:00:00 2001 From: Max Schenkelberg Date: Sat, 10 Jan 2026 15:06:40 -0600 Subject: [PATCH 1/2] Adding README to repo --- README.md | 375 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 367 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9f56340..54fc63a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,371 @@ -# Welcome to your CDK TypeScript Construct Library project! +# @papio/cdk-constructs -You should explore the contents of this project. It demonstrates a CDK Construct Library that includes a construct (`CdkConstructs`) -which contains an Amazon SQS queue that is subscribed to an Amazon SNS topic. +A production-ready AWS CDK TypeScript construct library providing reusable components for AWS infrastructure-as-code deployments. This library specializes in networking, compute, and email constructs with Lambda-backed custom resources for runtime AWS API operations. -The construct defines an interface (`CdkConstructsProps`) to configure the visibility timeout of the queue. +**CDK Version:** v1.204.0 +**Node.js Runtime:** 16.x -## Useful commands +## Installation - * `npm run build` compile typescript to js - * `npm run watch` watch for changes and compile - * `npm run test` perform the jest unit tests \ No newline at end of file +```bash +npm install @papio/cdk-constructs +# or +yarn add @papio/cdk-constructs +``` + +## Available Constructs + +### Core Module + +#### `singletonResource` + +A generic utility function for creating singleton resources on CDK stacks. Ensures only one instance of a resource exists per stack, preventing duplicate resource creation across deployments. + +```typescript +import { singletonResource } from '@papio/cdk-constructs'; + +// Create a singleton bucket - same uniqueId returns the same instance +const bucket = singletonResource(this, 'MySharedBucket', (scope, id) => { + return new s3.Bucket(scope, id, { + bucketName: 'my-shared-bucket', + }); +}); +``` + +**How it works:** +- Uses SHA-256 hashing of unique IDs for deterministic naming +- Resolves CloudFormation tokens before hashing to ensure consistency +- Stores singleton instances on the stack and returns existing instances for duplicate requests + +--- + +### Networking Module + +#### `HostedZoneLookup` + +Looks up existing Route53 hosted zones by domain name at deploy time using a Lambda-backed custom resource. + +```typescript +import { HostedZoneLookup } from '@papio/cdk-constructs'; + +const hostedZone = new HostedZoneLookup(this, 'MyHostedZone', { + domainName: 'example.com', +}); + +// Use the hosted zone ID +console.log(hostedZone.hostedZoneId); +``` + +**What it does:** +- Creates a Lambda function that calls the Route53 API to find a hosted zone matching the domain name +- Returns an object implementing `IHostedZone` that can be used with other Route53 constructs +- Uses the singleton pattern internally - multiple lookups for the same domain share the same Lambda +- Handles multi-part domain names (e.g., `sub.example.com` falls back to `example.com`) + +--- + +#### `SharedCertificate` + +Creates or retrieves existing ACM certificates with automatic DNS validation via Route53. + +```typescript +import { SharedCertificate } from '@papio/cdk-constructs'; + +const certificate = new SharedCertificate(this, 'MyCert', { + domainName: 'example.com', + subjectAlternativeNames: ['*.example.com'], + hostedZoneId: 'Z1234567890ABC', +}); + +// Use with CloudFront, ALB, etc. +console.log(certificate.certificateArn); +``` + +**What it does:** +- First checks if a certificate for the domain already exists in ACM +- If found, returns the existing certificate ARN (prevents duplicate certificates) +- If not found, creates a new certificate with DNS validation +- Automatically creates Route53 validation records in the specified hosted zone +- Waits for the certificate to be validated before completing +- Implements `ICertificate` interface for use with CloudFront, ALB, API Gateway, etc. + +--- + +#### `AliasRecord` + +Creates both IPv4 (A) and IPv6 (AAAA) alias records for dual-stack DNS configuration. + +```typescript +import { AliasRecord } from '@papio/cdk-constructs'; +import * as targets from '@aws-cdk/aws-route53-targets'; + +new AliasRecord(this, 'MyAliasRecord', { + zone: hostedZone, + recordName: 'www', + target: route53.RecordTarget.fromAlias( + new targets.CloudFrontTarget(distribution) + ), +}); +``` + +**What it does:** +- Creates both an A record (IPv4) and AAAA record (IPv6) pointing to the same alias target +- Simplifies dual-stack DNS configuration for AWS resources like CloudFront, ALB, and API Gateway +- Wrapper around Route53 `ARecord` and `AaaaRecord` constructs + +--- + +### EC2 Module + +#### `MachineImageLookup` + +Performs runtime AMI lookups at deploy time using flexible filtering criteria. + +```typescript +import { MachineImageLookup } from '@papio/cdk-constructs'; + +const ami = new MachineImageLookup(this, 'LatestAmazonLinux', { + name: 'amzn2-ami-hvm-*-x86_64-gp2', + owners: ['amazon'], +}); + +// Use with EC2 instances +new ec2.Instance(this, 'MyInstance', { + machineImage: ami, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO), + vpc: vpc, +}); +``` + +**What it does:** +- Creates a Lambda-backed custom resource that calls EC2 `DescribeImages` API +- Filters images based on provided criteria (name pattern, owners, filters) +- Returns the most recent matching AMI +- Implements `IMachineImage` interface for use with EC2 constructs +- Generates deterministic IDs via hashing to ensure consistent deployments + +**Note:** Currently supports Linux images only. Windows support is planned. + +--- + +### Email Module + +#### `SesDomainVerification` + +Automatically verifies SES domains for sending emails by creating the required DNS records. + +```typescript +import { SesDomainVerification } from '@papio/cdk-constructs'; + +new SesDomainVerification(this, 'VerifyDomain', { + domainName: 'example.com', + hostedZoneId: 'Z1234567890ABC', +}); +``` + +**What it does:** +- Calls the SES API to initiate domain verification +- Automatically creates the required TXT record in Route53 for domain ownership verification +- Handles the verification process asynchronously via Lambda custom resource +- Domain becomes verified for sending emails once DNS propagates + +--- + +#### `SesSmtpUser` + +Creates and manages SMTP credentials for sending emails via SES. + +```typescript +import { SesSmtpUser } from '@papio/cdk-constructs'; + +const smtpUser = new SesSmtpUser(this, 'SmtpUser', { + userName: 'my-app-smtp-user', + secretName: 'my-app/smtp-credentials', +}); + +// Access the SMTP credentials +console.log(smtpUser.smtpUserName); // Reference to username +console.log(smtpUser.smtpUserPassword); // Reference to password +``` + +**What it does:** +1. Creates an IAM user with SES send permissions (`ses:SendRawEmail`) +2. Generates IAM access keys for the user +3. Converts the secret access key to SMTP credentials using the SES signing algorithm +4. Stores the SMTP username and password in AWS Secrets Manager +5. Exposes references to the credentials for use in your application + +**Secrets Manager Structure:** +```json +{ + "username": "SMTP_USERNAME", + "password": "SMTP_PASSWORD" +} +``` + +--- + +## Development Commands + +| Command | Description | +|---------|-------------| +| `yarn build` | Compiles TypeScript to JavaScript and bundles Lambda functions via webpack | +| `yarn watch` | Watches for TypeScript changes and recompiles automatically | +| `yarn test` | Runs Jest unit tests | +| `yarn lint` | Runs ESLint with auto-fix enabled | +| `yarn test-release` | Performs a dry-run of semantic-release without publishing | +| `yarn release` | Publishes a new version using semantic-release | + +### Build Process + +The build process consists of two steps: + +1. **TypeScript Compilation (`tsc`)**: Compiles all TypeScript files to JavaScript with declaration files (`.d.ts`) + +2. **Webpack Bundling**: Bundles all Lambda functions in `lib/lambdas/` into optimized single-file bundles: + - Automatically discovers all `.ts` files in the lambdas directory + - Excludes `aws-sdk` (already available in Lambda runtime) + - Uses Terser for minification + - Outputs bundled files alongside the compiled JavaScript + +### Testing + +Tests are written using Jest and located in the `test/` directory: + +```bash +# Run all tests +yarn test + +# Run tests in watch mode +yarn test --watch + +# Run tests with coverage +yarn test --coverage +``` + +### Linting + +The project uses ESLint with TypeScript support and Prettier for formatting: + +```bash +# Run linter with auto-fix +yarn lint + +# Key lint rules enforced: +# - Max line width: 120 characters +# - JSDoc required for public APIs +# - Max nesting depth: 3 +# - Sorted imports +# - No TODO/FIXME comments allowed +``` + +### Releasing + +The project uses [semantic-release](https://semantic-release.gitbook.io/) for automated versioning and publishing: + +```bash +# Dry-run to preview the next release +yarn test-release + +# Perform actual release (typically run by CI) +yarn release +``` + +**Commit Convention:** The project follows [Conventional Commits](https://www.conventionalcommits.org/). Commit messages are validated by commitlint: + +- `feat:` - New features (triggers minor version bump) +- `fix:` - Bug fixes (triggers patch version bump) +- `feat!:` or `BREAKING CHANGE:` - Breaking changes (triggers major version bump) +- `chore:`, `docs:`, `style:`, `refactor:`, `test:` - No version bump + +--- + +## Project Structure + +``` +cdk-constructs/ +├── lib/ # Main library source code +│ ├── index.ts # Main entry point (barrel exports) +│ ├── core/ +│ │ ├── index.ts +│ │ └── singleton-resource.ts # Singleton resource utility +│ ├── networking/ +│ │ ├── index.ts +│ │ ├── alias-record.ts # IPv4/IPv6 alias records +│ │ ├── hosted-zone-lookup.ts # Route53 hosted zone lookup +│ │ └── shared-certificate.ts # Shared ACM certificates +│ ├── ec2/ +│ │ ├── index.ts +│ │ └── MachineImageLookup.ts # EC2 AMI lookup utility +│ ├── email/ +│ │ ├── index.ts +│ │ ├── ses-domain-verification.ts +│ │ └── ses-smtp-user.ts +│ └── lambdas/ # Lambda handlers for custom resources +│ ├── get-or-create-cert.ts +│ ├── lookup-hosted-zone.ts +│ ├── machine-image-lookup.ts +│ ├── setup-ses-user.ts +│ ├── verify-ses-domain.ts +│ └── utility/ +│ └── hosted-zone.ts +├── test/ # Unit tests +├── package.json +├── tsconfig.json +├── jest.config.js +├── webpack.config.js +├── .eslintrc.js +└── Jenkinsfile # CI/CD pipeline +``` + +--- + +## CI/CD Pipeline + +The project uses Jenkins for continuous integration and deployment: + +| Stage | Description | +|-------|-------------| +| **Init** | Installs dependencies with `yarn` | +| **Build** | Compiles TypeScript and bundles Lambdas | +| **Test** | Runs the Jest test suite | +| **Release** | Publishes to npm (master branch only) | + +Required credentials: +- `NPM_TOKEN` - npm authentication token for publishing +- `GITHUB_TOKEN` - GitHub token for creating releases + +--- + +## Peer Dependencies + +This library requires the following AWS CDK packages as peer dependencies (v1.204.0): + +- `@aws-cdk/core` +- `@aws-cdk/aws-lambda` +- `@aws-cdk/aws-iam` +- `@aws-cdk/aws-route53` +- `@aws-cdk/aws-certificatemanager` +- `@aws-cdk/aws-secretsmanager` +- `@aws-cdk/custom-resources` +- `@aws-cdk/aws-ec2` +- `@aws-cdk/aws-ses` + +--- + +## Contributing + +1. Fork the repository +2. Create a feature branch: `git checkout -b feat/my-feature` +3. Make your changes following the coding standards +4. Write tests for new functionality +5. Ensure all tests pass: `yarn test` +6. Ensure linting passes: `yarn lint` +7. Commit using conventional commits: `git commit -m "feat: add new feature"` +8. Push and create a pull request + +--- + +## License + +ISC From 19f3f765223d86f9f3851e7f7d031a8af8148742 Mon Sep 17 00:00:00 2001 From: Max Schenkelberg Date: Sat, 10 Jan 2026 15:20:02 -0600 Subject: [PATCH 2/2] migrating to github actions --- .github/workflows/ci.yml | 59 ++++++++++++++++++++++++++++++++++++++++ Jenkinsfile | 28 ------------------- README.md | 34 +++++++++++++++-------- 3 files changed, 81 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 Jenkinsfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f36f356 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,59 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'yarn' + + - name: Install dependencies + run: yarn + + - name: Build + run: yarn build + + - name: Test + run: yarn test + + release: + needs: build-and-test + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'yarn' + + - name: Install dependencies + run: yarn + + - name: Build + run: yarn build + + - name: Release + env: + NPM_TOKEN: ${{ secrets.NPM_JS_TOKEN }} + run: yarn release diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 7d24bfa..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,28 +0,0 @@ -pipeline { - agent any - - stages { - stage("Init") { - steps { - sh "yarn" - } - } - stage("Build") { - steps { sh "yarn build" } - } - stage("Test") { - steps { sh "yarn test" } - } - stage("Release") { - when { branch "master" } - environment { - NPM_TOKEN = credentials("NPM_TOKEN") - } - steps { - withGitHubToken { - sh 'export GITHUB_TOKEN="x-access-token:$GITHUB_TOKEN" && yarn release' - } - } - } - } -} diff --git a/README.md b/README.md index 54fc63a..e11e17a 100644 --- a/README.md +++ b/README.md @@ -310,30 +310,40 @@ cdk-constructs/ │ └── utility/ │ └── hosted-zone.ts ├── test/ # Unit tests +├── .github/ +│ └── workflows/ +│ └── ci.yml # GitHub Actions CI/CD pipeline ├── package.json ├── tsconfig.json ├── jest.config.js ├── webpack.config.js -├── .eslintrc.js -└── Jenkinsfile # CI/CD pipeline +└── .eslintrc.js ``` --- ## CI/CD Pipeline -The project uses Jenkins for continuous integration and deployment: +The project uses GitHub Actions for continuous integration and deployment (`.github/workflows/ci.yml`): -| Stage | Description | -|-------|-------------| -| **Init** | Installs dependencies with `yarn` | -| **Build** | Compiles TypeScript and bundles Lambdas | -| **Test** | Runs the Jest test suite | -| **Release** | Publishes to npm (master branch only) | +**Jobs:** -Required credentials: -- `NPM_TOKEN` - npm authentication token for publishing -- `GITHUB_TOKEN` - GitHub token for creating releases +| Job | Trigger | Description | +|-----|---------|-------------| +| **build-and-test** | All pushes and PRs to master | Installs dependencies, builds, and runs tests | +| **release** | Push to master only | Publishes to npm using semantic-release | + +**Pipeline Steps:** +1. **Checkout** - Clones the repository +2. **Setup Node.js** - Configures Node.js 18 with yarn caching +3. **Install dependencies** - Runs `yarn` +4. **Build** - Compiles TypeScript and bundles Lambdas +5. **Test** - Runs the Jest test suite +6. **Release** (master only) - Publishes to npm via semantic-release + +**Required secrets:** +- `NPM_JS_TOKEN` - npm authentication token for publishing (stored as GitHub secret) +- `GITHUB_TOKEN` - Automatically provided by GitHub Actions for creating releases ---