Skip to content

Commit

Permalink
EREGCSC-2860 - Deploy Redirect to ephemeral environment (#1473)
Browse files Browse the repository at this point in the history
* Deploy Redirect to ephmeral env

* Update ssm packages

* Update ssm packages

* Update ssm packages

* Add Remove stage for stack
  • Loading branch information
addis-samtek authored Nov 12, 2024
1 parent 0647cc4 commit 92f6bf9
Show file tree
Hide file tree
Showing 25 changed files with 2,385 additions and 1,618 deletions.
74 changes: 59 additions & 15 deletions .github/workflows/deploy-experimental.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,51 @@ jobs:
npm install
serverless deploy --stage dev${PR}
popd
deploy-hello-world-cdk:
# deploy-hello-world-cdk:
# environment:
# name: "dev"
# runs-on: ubuntu-22.04
# steps:
# # Checkout the code
# - name: Checkout
# uses: actions/checkout@v3
# with:
# submodules: true
# # Find the PR number. This is not always trivial which is why this uses an existign action
# - name: Find PR number
# uses: jwalton/gh-find-current-pr@v1
# id: findPr
# with:
# # Can be "open", "closed", or "all". Defaults to "open".
# state: open
# # Configure AWS credentials for GitHub Actions
# - name: Configure AWS credentials for GitHub Actions
# uses: aws-actions/configure-aws-credentials@v2
# with:
# role-to-assume: ${{ secrets.AWS_OIDC_ROLE_TO_ASSUME }}
# aws-region: us-east-1
# # Deploy the text extractor lambda to AWS
# - name: Deploy the hello world test lambda via CDK
# id: deploy-redirect-api-test
# if: success() && steps.findPr.outputs.number
# env:
# PR_NUMBER: ${{ steps.findPr.outputs.pr }}
# RUN_ID: ${{ github.run_id }}
# AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
# AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
# run: |
# pushd cdk-eregs
# npm install -g aws-cdk
# npm install
# STAGE=dev${PR_NUMBER} cdk synth -c stage=dev${PR_NUMBER} -c account=${AWS_ACCOUNT_ID} -c region=${AWS_DEFAULT_REGION}
# STAGE=dev${PR_NUMBER} cdk deploy -c stage=dev${PR_NUMBER} -c account=${AWS_ACCOUNT_ID} -c region=${AWS_DEFAULT_REGION} \
# dev${PR_NUMBER}-HelloWorldStack --require-approval never
# popd
deploy-Redirect-Api-CDK:
environment:
name: "dev"
env:
ENVIRONMENT_NAME: "dev"
runs-on: ubuntu-22.04
steps:
# Checkout the code
Expand All @@ -97,23 +139,25 @@ jobs:
with:
role-to-assume: ${{ secrets.AWS_OIDC_ROLE_TO_ASSUME }}
aws-region: us-east-1
# Deploy the text extractor lambda to AWS
- name: Deploy the hello world test lambda via CDK
id: deploy-redirect-api-test

- name: Deploy Redirect Lambda via CDK
id: deploy-redirect-api
if: success() && steps.findPr.outputs.number
env:
PR_NUMBER: ${{ steps.findPr.outputs.pr }}
RUN_ID: ${{ github.run_id }}
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
PR_NUMBER: ${{ steps.findPr.outputs.pr }}
RUN_ID: ${{ github.run_id }}
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
CDK_DEBUG: true
ENVIRONMENT_NAME: ${{ env.ENVIRONMENT_NAME }}
run: |
pushd cdk-eregs
npm install -g aws-cdk
npm install
STAGE=dev${PR_NUMBER} cdk synth -c stage=dev${PR_NUMBER} -c account=${AWS_ACCOUNT_ID} -c region=${AWS_DEFAULT_REGION}
STAGE=dev${PR_NUMBER} cdk deploy -c stage=dev${PR_NUMBER} -c account=${AWS_ACCOUNT_ID} -c region=${AWS_DEFAULT_REGION} \
dev${PR_NUMBER}-HelloWorldStack --require-approval never
popd
pushd cdk-eregs
npm install -g aws-cdk@latest @aws-sdk/client-ssm
npm install
cdk deploy "*redirect-api" \
-c environment=${{ env.ENVIRONMENT_NAME }} \
--require-approval never
popd
deploy-text-extractor:
environment:
name: "dev"
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/remove-experimental.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,25 @@ jobs:
npm install
serverless remove --stage dev${PR}
popd
- name: Destroy PR-Specific Stack
env:
PR_NUMBER: ${{ github.event.number }}
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
CDK_DEBUG: true
ENVIRONMENT_NAME: ${{ env.ENVIRONMENT_NAME }}
run: |
pushd cdk-eregs
npm install -g aws-cdk@latest @aws-sdk/client-ssm
npm install
# Generate the exact stack name for this PR
STACK_NAME="cms-eregs-eph-${PR_NUMBER}-redirect-api"
echo "Destroying PR-specific stack: ${STACK_NAME}"
cdk destroy "${STACK_NAME}" \
-c environment=${{ env.ENVIRONMENT_NAME }} \
--force
echo "Cleanup completed for stack: ${STACK_NAME}"
popd
217 changes: 207 additions & 10 deletions cdk-eregs/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,211 @@
# Welcome to your CDK TypeScript project
CDK Infrastructure Technical Documentation
Overview
This document outlines the CDK infrastructure implementation focusing on environment-aware deployments, custom synthesizers, AWS Parameter Store integration, and stack creation patterns.
Core Components
// app.ts
const synthesizerConfigJson = await getParameterValue('/cms/cloud/cdkSynthesizerConfig');
const synthesizerConfig = JSON.parse(synthesizerConfigJson);

This is a blank project for CDK development with TypeScript.
// Development Environment
const devConfig = await StageConfig.create(
'dev', // environment
undefined, // no ephemeralId
synthesizerConfig.iamPermissionsBoundary // from synthesizer config
);

The `cdk.json` file tells the CDK Toolkit how to execute your app.
// Example outputs:
devConfig.getResourceName('api') // "cms-eregs-dev-api"
devConfig.aws.lambda('function') // "/aws/lambda/cms-eregs-dev-function"
devConfig.isEphemeral() // false
devConfig.getStackTags() // { Environment: 'dev', Project: 'cms-eregs', ... }

// Production Environment
const prodConfig = await StageConfig.create(
'prod',
undefined,
synthesizerConfig.iamPermissionsBoundary
);

// Example outputs:
prodConfig.getResourceName('api') // "cms-eregs-prod-api"
prodConfig.aws.lambda('function') // "/aws/lambda/cms-eregs-prod-function"
prodConfig.isEphemeral() // false
1. Stage Configuration (lib/config/stage-config.ts)
typescriptCopyexport class StageConfig {
public static readonly projectName = 'cms-eregs';

public static async create(
environment: string,
ephemeralId?: string,
synthesizerPermissionsBoundary?: string
): Promise<StageConfig>
Key Features:

Environment-aware resource naming
Ephemeral environment support (PR-based deployments)
AWS service-specific naming conventions
Integrated IAM permissions boundary
Automatic tagging system

Resource Naming Patterns:
typescriptCopy// Regular environments:
{project}-{environment}-{resource} // cms-eregs-dev-api
// Ephemeral environments:
{project}-eph-{pr-number}-{resource} // cms-eregs-eph-123-api
// AWS Service Resources:
/aws/{service}/{project}-{environment}-{resource}
2. Application Entry Point (bin/app.ts)
typescriptCopyasync function main() {
// Environment Resolution Chain:
// 1. CDK Context (-c environment=dev)
// 2. Environment Variable (DEPLOY_ENV)
// 3. GitHub Environment (GITHUB_JOB_ENVIRONMENT)
// 4. Default ('dev')
const environment = app.node.tryGetContext('environment') ||
process.env.DEPLOY_ENV ||
process.env.GITHUB_JOB_ENVIRONMENT ||
'dev';
Features:

Custom synthesizer configuration from Parameter Store
Environment context validation
Global tag application
Aspect-based IAM configuration
Debug logging system

3. AWS Parameter Store Integration
typescriptCopy// Synthesizer Configuration
const synthesizerConfigJson = await getParameterValue('/cms/cloud/cdkSynthesizerConfig');
Parameter Structure:
jsonCopy{
"deployRoleArn": "arn:aws:iam::ACCOUNT:role/delegatedadmin/developer/cdk-deploy-role",
"fileAssetPublishingRoleArn": "arn:aws:iam::ACCOUNT:role/delegatedadmin/developer/cdk-file-publishing-role",
"imageAssetPublishingRoleArn": "arn:aws:iam::ACCOUNT:role/delegatedadmin/developer/cdk-image-publishing-role",
"cloudFormationExecutionRole": "arn:aws:iam::ACCOUNT:role/delegatedadmin/developer/cdk-cfn-exec-role",
"lookupRoleArn": "arn:aws:iam::ACCOUNT:role/delegatedadmin/developer/cdk-lookup-role",
"qualifier": "one",
"iamPermissionsBoundary": "arn:aws:iam::ACCOUNT:policy/cms-cloud-admin/ct-ado-poweruser-permissions-boundary-policy"
}
4. Global Aspects System
typescriptCopyasync function applyGlobalAspects(app: cdk.App, stageConfig: StageConfig): Promise<void> {
const iamPath = await getParameterValue(`/account_vars/iam/path`);

cdk.Aspects.of(app).add(new IamPathAspect(iamPath));
cdk.Aspects.of(app).add(new IamPermissionsBoundaryAspect(stageConfig.permissionsBoundaryArn));
cdk.Aspects.of(app).add(new EphemeralRemovalPolicyAspect(stageConfig));
}
Available Aspects:

IamPathAspect: Enforces IAM resource paths
IamPermissionsBoundaryAspect: Applies IAM permissions boundaries
EphemeralRemovalPolicyAspect: Manages resource cleanup for ephemeral environments

5. Environment-Aware Stack Creation
typescriptCopynew RedirectApiStack(app, stageConfig.getResourceName('redirect-api'), {
lambdaConfig: {
runtime: lambda.Runtime.PYTHON_3_12,
memorySize: 1024,
timeout: 30,
},
apiConfig: {
loggingLevel: cdk.aws_apigateway.MethodLoggingLevel.INFO,
},
}, stageConfig);
Deployment Patterns
1. Regular Environment Deployment
bashCopy# Using CDK context
cdk deploy "*redirect-api" -c environment=dev

# Using environment variable
DEPLOY_ENV=dev cdk deploy "*redirect-api"
2. PR/Ephemeral Environment Deployment
bashCopy# Using PR number
PR_NUMBER=123 cdk deploy "*redirect-api" -c environment=dev

# Debug mode
CDK_DEBUG=true PR_NUMBER=123 cdk deploy "*redirect-api" -c environment=dev
3. GitHub Actions Integration
yamlCopydeploy-redirect-api:
environment:
name: "dev"
runs-on: ubuntu-22.04
steps:
- name: Deploy Stack
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
CDK_DEBUG: true
run: |
npx cdk deploy "*redirect-api" \
-c environment=${{ environment.name }} \
--require-approval never
Debug and Logging
1. Debug Output Structure
typescriptCopy// Synthesizer Configuration
{
permissionsBoundary: "arn:aws:iam::ACCOUNT:policy/boundary",
environment: "dev",
ephemeralId: "eph-123"
}

// Stage Configuration
{
environment: "dev",
permissionsBoundary: "arn:aws:iam::ACCOUNT:policy/boundary",
isEphemeral: true,
ephemeralId: "eph-123"
}

// Applied Aspects
{
environment: "dev",
iamPath: "/delegatedadmin/developer/",
permissionsBoundary: "arn:aws:iam::ACCOUNT:policy/boundary",
isEphemeral: true
}
Resource Naming Conventions
1. AWS Service Resources
typescriptCopy// Lambda Functions
stageConfig.aws.lambda('function-name')
// Output: /aws/lambda/cms-eregs-dev-function-name

// API Gateway
stageConfig.aws.apiGateway('api-name')
// Output: /aws/api-gateway/cms-eregs-dev-api-name

// CloudWatch Logs
stageConfig.aws.cloudwatch('log-group-name')
// Output: /aws/cloudwatch/cms-eregs-dev-log-group-name
2. Stack Resources
typescriptCopystageConfig.getResourceName('resource-name')
// Regular: cms-eregs-dev-resource-name
// Ephemeral: cms-eregs-eph-123-resource-name
Best Practices

Environment Management:

Use CDK context for environment specification
Implement fallback chain for environment resolution
Validate environments before deployment


Resource Naming:

Use StageConfig methods for consistent naming
Follow AWS service-specific naming patterns
Include environment/PR identifiers in resource names


IAM Configuration:

Apply permissions boundaries consistently
Use IAM path prefixing for resource organization
Implement least privilege access


Ephemeral Environments:

Implement cleanup policies
Use PR numbers for unique identification
Apply appropriate resource retention policies

## Useful commands

* `npm run build` compile typescript to js
* `npm run watch` watch for changes and compile
* `npm run test` perform the jest unit tests
* `npx cdk deploy` deploy this stack to your default AWS account/region
* `npx cdk diff` compare deployed stack with current state
* `npx cdk synth` emits the synthesized CloudFormation template
Loading

0 comments on commit 92f6bf9

Please sign in to comment.