Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Production deployment - prepared #22

Merged
merged 7 commits into from
Aug 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,29 @@ repos:
- id: detect-aws-credentials
- id: end-of-file-fixer
- repo: https://github.com/psf/black
rev: 23.3.0
rev: 24.8.0
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
- id: isort
- repo: https://github.com/pycqa/flake8
rev: 7.1.1
hooks:
- id: flake8
args: [--max-line-length=88]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.3.0
rev: v1.11.2
hooks:
- id: mypy
additional_dependencies: ['types-requests']
args: [--strict]
additional_dependencies:
- aws_lambda_powertools
- boto3
- types-boto3
- types-markdown
- httpx
- jinja2
- pydantic
- pytest
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ To work on CDK (Cloud Development Kit) for AWS Cloud you should install:

Additionally you will probably want to set up your [AWS CLI](https://aws.amazon.com/cli/) and credentials.

**More instructions available in `cdk/README.md` file.**

## Python paths and modules

Expand Down
21 changes: 20 additions & 1 deletion cdk/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Welcome to your CDK TypeScript project

This is a blank project for CDK development with TypeScript.
This is a CDK project with TypeScript for AMY Email Worker.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

Expand All @@ -12,3 +12,22 @@ The `cdk.json` file tells the CDK Toolkit how to execute your app.
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk synth` emits the synthesized CloudFormation template


## Deploying the stack

### Staging environment

```bash
$ npx cdk deploy EmailWorkerVpc EmailWorkerLambda EmailWorkerCron --profile carpentries
```
`--profile` can be a default profile or a named profile in `~/.aws/credentials` file.

### Production environment

In this case, the stage is changed to `production`, and the stacks have slightly different names:

```bash
$ npx cdk deploy EmailWorkerVpcProd EmailWorkerLambdaProd EmailWorkerCronProd --profile carpentries
```
`--profile` can be a default profile or a named profile in `~/.aws/credentials` file.
87 changes: 65 additions & 22 deletions cdk/bin/amy-email-worker.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,82 @@
#!/usr/bin/env node
import 'source-map-support/register';
import { App, Tags } from 'aws-cdk-lib';
import { App } from 'aws-cdk-lib';
import { VpcStack } from '../lib/vpc-stack';
import { LambdaStack } from '../lib/lambda-stack';
import { CronStack } from '../lib/cron-stack';
import { Settings, StandardTags } from '../lib/types';
import { applyStandardTags } from '../lib/utils';

const env = { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION };
const stagingSettings: Settings = {
stage: 'staging',
apiBaseUrl: 'https://test-amy2.carpentries.org/api',
};

const stagingTags: StandardTags = {
applicationTag: 'amy-email-worker',
billingServiceTag: 'AMY',
stage: stagingSettings.stage,
};

const productionSettings: Settings = {
stage: 'production',
apiBaseUrl: 'https://amy.carpentries.org/api',
};

const productionTags: StandardTags = {
applicationTag: 'amy-email-worker',
billingServiceTag: 'AMY',
stage: productionSettings.stage,
};

const env = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
};

const APPLICATION_TAG = 'amy-email-worker';
const BILLING_SERVICE_TAG = 'AMY';
const STAGE = 'staging';
const API_BASE_URL = 'https://test-amy2.carpentries.org/api'
const app = new App();

const vpcStack = new VpcStack(app, 'EmailWorkerVpc', {
/*******************/
/** S T A G I N G **/
/*******************/
const vpcStackStaging = new VpcStack(app, 'EmailWorkerVpc', {
env: env
});
applyStandardTags(vpcStackStaging, stagingTags);

const lambdaStackStaging = new LambdaStack(app, 'EmailWorkerLambda', {
env: env,
vpc: vpcStackStaging.vpc,
stage: stagingSettings.stage,
api_base_url: stagingSettings.apiBaseUrl,
});
applyStandardTags(lambdaStackStaging, stagingTags);

const cronStackStaging = new CronStack(app, 'EmailWorkerCron', {
env: env,
lambdaFunction: lambdaStackStaging.lambdaFunction,
});
applyStandardTags(cronStackStaging, stagingTags);


/*************************/
/** P R O D U C T I O N **/
/*************************/
const vpcStackProduction = new VpcStack(app, 'EmailWorkerVpcProd', {
env: env
});
Tags.of(vpcStack).add('ApplicationID', APPLICATION_TAG);
Tags.of(vpcStack).add('Billing-Service', BILLING_SERVICE_TAG);
Tags.of(vpcStack).add('Environment', STAGE);
applyStandardTags(vpcStackProduction, productionTags);

const lambdaStack = new LambdaStack(app, 'EmailWorkerLambda', {
const lambdaStackProduction = new LambdaStack(app, 'EmailWorkerLambdaProd', {
env: env,
vpc: vpcStack.vpc,
stage: STAGE,
api_base_url: API_BASE_URL,
vpc: vpcStackProduction.vpc,
stage: productionSettings.stage,
api_base_url: productionSettings.apiBaseUrl,
});
Tags.of(lambdaStack).add('ApplicationID', APPLICATION_TAG);
Tags.of(lambdaStack).add('Billing-Service', BILLING_SERVICE_TAG);
Tags.of(lambdaStack).add('Environment', STAGE);
applyStandardTags(lambdaStackProduction, productionTags);

const cronStack = new CronStack(app, 'EmailWorkerCron', {
const cronStackProduction = new CronStack(app, 'EmailWorkerCronProd', {
env: env,
lambdaFunction: lambdaStack.lambdaFunction,
lambdaFunction: lambdaStackProduction.lambdaFunction,
});
Tags.of(cronStack).add('ApplicationID', APPLICATION_TAG);
Tags.of(cronStack).add('Billing-Service', BILLING_SERVICE_TAG);
Tags.of(cronStack).add('Environment', STAGE);
applyStandardTags(cronStackProduction, productionTags);
5 changes: 3 additions & 2 deletions cdk/lib/lambda-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { IVpc } from "aws-cdk-lib/aws-ec2";
import { Architecture, IFunction, Runtime } from "aws-cdk-lib/aws-lambda";
import { PythonFunction } from "@aws-cdk/aws-lambda-python-alpha";
import { ManagedPolicy, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import type { Stage } from './types';

interface AdditionalProps extends StackProps {
vpc: IVpc;
stage: string;
stage: Stage;
api_base_url: string;
}

Expand Down Expand Up @@ -38,7 +39,7 @@ export class LambdaStack extends Stack {
});

this.lambdaFunction = new PythonFunction(this, 'EmailWorker', {
functionName: 'amy-email-worker',
functionName: `amy-email-worker-${stage}`,
architecture: Architecture.X86_64, // more expensive than ARM
runtime: Runtime.PYTHON_3_11,
entry: '../worker',
Expand Down
14 changes: 14 additions & 0 deletions cdk/lib/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type Stage = 'production' | 'staging';

type Settings = {
stage: Stage;
apiBaseUrl: string;
}

type StandardTags = {
applicationTag: string;
billingServiceTag: string;
stage: Stage;
}

export { Stage, Settings, StandardTags };
11 changes: 11 additions & 0 deletions cdk/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Tags } from 'aws-cdk-lib';
import { IConstruct } from 'constructs';
import type { StandardTags } from './types';

function applyStandardTags(scope: IConstruct, tags: StandardTags): void {
Tags.of(scope).add('ApplicationID', tags.applicationTag);
Tags.of(scope).add('Billing-Service', tags.billingServiceTag);
Tags.of(scope).add('Environment', tags.stage);
}

export { applyStandardTags };
8 changes: 4 additions & 4 deletions cdk/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"devDependencies": {
"@types/jest": "^29.2.3",
"@types/node": "18.11.9",
"aws-cdk": "^2.137.0",
"aws-cdk": "^2.154.1",
"ts-node": "^10.9.1",
"typescript": "~4.9.3"
},
Expand Down
29 changes: 0 additions & 29 deletions worker/.pre-commit-config.yaml

This file was deleted.

17 changes: 0 additions & 17 deletions worker/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,6 @@
logger.setLevel(logging.INFO) # use logging.DEBUG to see boto3 logs


# TODO:
# 1. rewrite to async ✅
# 2. make sure tests pass (even with async on) ✅
# 3. add mapping for API endpoints ✅
# 4. fetch and render emails ✅
# 5. write unit tests! ✅
# 6. create new simplified API in AMY ✅
# 7. use authentication in the new API ✅
# 8. use authentication in the worker ✅
# 9. limit access only for accounts with special permission ✅
# 10. update CDK with envvars ✅ / secrets (created by hand) ✅
# 11. create schemas for the endpoints ❌ (doesn't seem to be needed)
# 12. add endpoints for managing emails ✅
# 13. rewrite email logic from handler below to use the new email endpoints ✅
# 14. add tests for the new email management logic ✅


async def main(event: dict[Any, Any], context: LambdaContext) -> WorkerOutput:
logger.info(f"Start handler with arguments: {event=}, {context=}")

Expand Down
Loading