Skip to content

Commit

Permalink
feat: initial implementation of wso2-application (not tested)
Browse files Browse the repository at this point in the history
  • Loading branch information
flaviostutz committed Jan 27, 2024
1 parent 7eb3238 commit c71af29
Show file tree
Hide file tree
Showing 25 changed files with 1,152 additions and 435 deletions.
107 changes: 11 additions & 96 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,111 +2,26 @@

A collection of CDK constructs and utilities for making the development of AWS based applications easier and safer in a practical way.

See examples for BaseNodeJsLambda and OpenApiGatewayLambda below.

## Construct BaseNodeJsLambda

Based on [AWS Construct NodeJsFunction](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html) and adds the following capabilities:
- creates a default security group. See property 'defaultSecurityGroup' of this construct
- creates an alias called "live" pointing to the latest lambda version and replaced versions are deleted automatically. See property 'liveAlias' of this construct
- typed configuration props for common usage scenarios
- autoscaling of provisioned concurrent invocations (so you lower cold starts). You can also use automatic scheduling to tweak min/max depending on a cron expression. See props.provisionedConcurrentExecutions
- explicit private VPC configuration (see props.network)
- source code path standardization to "[basePath]/[lambdaEventType]/[lambdaName]/index.ts" (can be overwritten by explicit props.entry)
- custom CA support for HTTP calls (NodeJS NODE_EXTRA_CA_CERTS). See props.extraCaPubCert
- option to subscribe an Lambda Arn to the log group related to the Lambda function. See props.logGroupSubscriberLambdaArn
- adds environment STAGE to Lambda. See props.stage
Creates a Lambda with common utilities for default live alias, auto scaling of provisioned concurrency, log subscription, default security group, custom CA certificate config etc

### Usage
- Check [Construct BaseNodeJsLambda](base-nodejs-lambda.md) for more details

#### Simple Typescript Lambda Function

```ts
// instantiate Lambda construct
// this will bundle your ts code using esbuild
const func = new BaseNodeJsFunction(stack, 'test-lambda', {
stage: 'dev',
eventType: EventType.Http
});
```
## Construct OpenapiGatewayLambda

Creates an AWS APIGateway from definitions in an Openapi document based on Zod schemas and connects routes defined in Openapi to Lambda functions.

#### Complex Typescript Lambda Function
- Check [Construct OpenapiGatewayLambda](openapigateway-lambda.md) for more details

```ts
// this can be reused in various lambda definitions
const globalLambdaConfig = {
eventType: EventType.Http,
runtime: Runtime.NODEJS_18_X,
extraCaPubCert: 'ABCXxxxyz123123123' // add private CA pub certificate to NodeJS
}

const lambdaConfig: BaseNodeJsProps = {
// merge config with global defaults
...globalLambdaConfig,
sourceMap: true, // add code source map to esbuild and configure Node. This might impose severe performance penauties
provisionedConcurrentExecutions: {
minCapacity: 1, // min instances in auto-scaling of provisioned lambdas
maxCapacity: 5, // max instances in auto-scaling. if empty, the number of provisioned instances will be fixed to "minCapacity"
schedules: [ // for automatically changing min/max on certain hours
{
minCapacity: 0,
maxCapacity: 2,
schedule: Schedule.cron({ hour: '22' }),
name: 'Lower provisioned instances during the night'
},
{
minCapacity: 1,
maxCapacity: 5,
schedule: Schedule.cron({ hour: '7' }),
name: 'Keep at minimum one provisioned instance during the day'
}
]
}
}
## WSO2

// register an external Lambda to receive all Cloudwatch log events
// created by this Lambda (used to forward logs to Datadog, Splunk etc)
lambdaConfig.logGroupSubscriberLambdaArn =
'arn:aws:lambda:eu-west-1:012345678:function:datadogForwarder';
### Construct WSO2Api

Creates an WSO2 API from definitions in an Openapi document.

// instantiate Lambda construct
const func = new BaseNodeJsFunction(stack, 'test-lambda', lambdaConfig);

// add custom network access rules to default security group created for this lambda
func.defaultSecurityGroup.addEgressRule(
Peer.ipv4('10.20.30.40/32'),
Port.tcp(8888),
'Allow lambda to access api X',
);

```

## Construct OpenApiGatewayLambda

Rest API built from Openapi specs and aws extensions for running on Lambda functions with the following characteristics:
- Uses API Gateway extensions for OpenAPI for connecting api routes to Lambdas (https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions.html)
- Creates an AWS API Gateway along with multiple Lambdas for each route defined in OpenAPI spec
- Uses api operations that supports Zod schemas in definitions (prop.openapiOperations)
- An opiniated set of defaults that can be overwritten in props

Partialy inspired on https://blog.serverlessadvocate.com/serverless-openapi-amazon-api-gateway-with-the-aws-cdk-part-1-8a90477ebc24

AWS labs has a similar construct, but it relies on openapi specs written in yml and deployed to S3 buckets and CustomResources. We want to avoid S3 buckets and CustomResources to keep things faster/simpler and want to write our openapi specs in pure TS for better typing.

Check https://github.com/awslabs/aws-solutions-constructs/blob/main/source/patterns/%40aws-solutions-constructs/aws-openapigateway-lambda/lib/index.ts

### Usage

TODO

## Construct WSO2 API



### Usage

Supported output attributes of this Custom Resource (you can use GetAtt on these):
- ApiEndpointUrl: returns the endpoint that can be used to invoke this API in WSO2

For this construct, lots of experiences were extracted from [serverless-wso2-apim](https://github.com/ramgrandhi/serverless-wso2-apim). Thanks for the good work, Ram!
- Check [Construct Wso2Api](wso2-api.md) for more details

76 changes: 76 additions & 0 deletions docs/base-nodejs-lambda.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
## Construct BaseNodeJsLambda

Based on [AWS Construct NodeJsFunction](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html) and adds the following capabilities:
- creates a default security group. See property 'defaultSecurityGroup' of this construct
- creates an alias called "live" pointing to the latest lambda version and replaced versions are deleted automatically. See property 'liveAlias' of this construct
- typed configuration props for common usage scenarios
- autoscaling of provisioned concurrent invocations (so you lower cold starts). You can also use automatic scheduling to tweak min/max depending on a cron expression. See props.provisionedConcurrentExecutions
- explicit private VPC configuration (see props.network)
- source code path standardization to "[basePath]/[lambdaEventType]/[lambdaName]/index.ts" (can be overwritten by explicit props.entry)
- custom CA support for HTTP calls (NodeJS NODE_EXTRA_CA_CERTS). See props.extraCaPubCert
- option to subscribe an Lambda Arn to the log group related to the Lambda function. See props.logGroupSubscriberLambdaArn
- adds environment STAGE to Lambda. See props.stage

### Usage

#### Simple Typescript Lambda Function

```ts
// instantiate Lambda construct
// this will bundle your ts code using esbuild
const func = new BaseNodeJsFunction(stack, 'test-lambda', {
stage: 'dev',
eventType: EventType.Http
});
```

#### Complex Typescript Lambda Function

```ts
// this can be reused in various lambda definitions
const globalLambdaConfig = {
eventType: EventType.Http,
runtime: Runtime.NODEJS_18_X,
extraCaPubCert: 'ABCXxxxyz123123123' // add private CA pub certificate to NodeJS
}

const lambdaConfig: BaseNodeJsProps = {
// merge config with global defaults
...globalLambdaConfig,
sourceMap: true, // add code source map to esbuild and configure Node. This might impose severe performance penauties
provisionedConcurrentExecutions: {
minCapacity: 1, // min instances in auto-scaling of provisioned lambdas
maxCapacity: 5, // max instances in auto-scaling. if empty, the number of provisioned instances will be fixed to "minCapacity"
schedules: [ // for automatically changing min/max on certain hours
{
minCapacity: 0,
maxCapacity: 2,
schedule: Schedule.cron({ hour: '22' }),
name: 'Lower provisioned instances during the night'
},
{
minCapacity: 1,
maxCapacity: 5,
schedule: Schedule.cron({ hour: '7' }),
name: 'Keep at minimum one provisioned instance during the day'
}
]
}
}

// register an external Lambda to receive all Cloudwatch log events
// created by this Lambda (used to forward logs to Datadog, Splunk etc)
lambdaConfig.logGroupSubscriberLambdaArn =
'arn:aws:lambda:eu-west-1:012345678:function:datadogForwarder';

// instantiate Lambda construct
const func = new BaseNodeJsFunction(stack, 'test-lambda', lambdaConfig);

// add custom network access rules to default security group created for this lambda
func.defaultSecurityGroup.addEgressRule(
Peer.ipv4('10.20.30.40/32'),
Port.tcp(8888),
'Allow lambda to access api X',
);

```
18 changes: 18 additions & 0 deletions docs/openapigateway-lambda.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## Construct OpenApiGatewayLambda

Rest API built from Openapi specs and aws extensions for running on Lambda functions with the following characteristics:
- Uses API Gateway extensions for OpenAPI for connecting api routes to Lambdas (https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions.html)
- Creates an AWS API Gateway along with multiple Lambdas for each route defined in OpenAPI spec
- Uses api operations that supports Zod schemas in definitions (prop.openapiOperations)
- An opiniated set of defaults that can be overwritten in props

Partialy inspired on https://blog.serverlessadvocate.com/serverless-openapi-amazon-api-gateway-with-the-aws-cdk-part-1-8a90477ebc24

AWS labs has a similar construct, but it relies on openapi specs written in yml and deployed to S3 buckets and CustomResources. We want to avoid S3 buckets and CustomResources to keep things faster/simpler and want to write our openapi specs in pure TS for better typing.

Check https://github.com/awslabs/aws-solutions-constructs/blob/main/source/patterns/%40aws-solutions-constructs/aws-openapigateway-lambda/lib/index.ts

### Usage

TODO

44 changes: 44 additions & 0 deletions docs/wso2-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## Construct WSO2 API

Creates a new WSO2 API in a WSO2 server based on api definitions and an Openapi document.

Check type *Wso2ApiProps* for a complete definition of the props for this construct.

Supported output attributes for this CustomResource in CFN via GetAtt are:
- EndpointUrl: Endpoint URL of this API in WSO2
- Wso2ApiId: Id of the API in WSO2

### Usage

```ts
const wso2Props: Wso2ApiProps = {
wso2Config: {
baseApiUrl: 'https://mywso2.com',
credentialsSecretId: 'myWso2Creds',
},
apiDefinition: {
version: 'v1',
type: 'HTTP',
endpointConfig: {
production_endpoints: {
url: 'http://serverabc.com',
},
endpoint_type: 'http',
},
context: '/petstore',
name: 'petstore-sample',
gatewayEnvironments: ['public'],
corsConfiguration: {
accessControlAllowOrigins: ['testwebsite.com'],
},
},
openapiDocument: mypetstoreOpenapiDoc,
};

// instantiate cdk construct
new Wso2Api(scope, `wso2-petstore`, wso2Props);
```

See a complete example at [/examples/src/wso2](/examples/src/wso2)

For this construct, lots of experiences were extracted from [serverless-wso2-apim](https://github.com/ramgrandhi/serverless-wso2-apim). Thanks for the good work, Ram!
2 changes: 1 addition & 1 deletion examples/pnpm-lock.yaml

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

2 changes: 1 addition & 1 deletion examples/src/wso2/petstore-openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const petstoreOpenapi: OpenAPIObject = {
paths: {
'/pets': {
get: {
summary: 'List all pets',
summary: 'List all pets 1',
operationId: 'listPets',
tags: ['pets'],
parameters: [
Expand Down
3 changes: 3 additions & 0 deletions lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ export * from './lambda/types';

export { Wso2Api } from './wso2/wso2-api/wso2-api';
export * from './wso2/wso2-api/types';

export { Wso2Application } from './wso2/wso2-application/wso2-application';
export * from './wso2/wso2-application/types';
4 changes: 3 additions & 1 deletion lib/src/lambda/lambda-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,9 @@ const addSecurityGroups = (
const defaultSG = new SecurityGroup(scope, `sg-default-${scope.node.id}`, {
vpc: props.vpc,
description: `Default security group for Lambda ${scope.node.id}`,
allowAllOutbound: typeof props.allowAllOutbound !== 'undefined' ?? props.allowAllOutbound,
allowAllOutbound:
(typeof props.allowAllOutbound !== 'undefined' && props.allowAllOutbound) ??
props.allowAllOutbound,
});
if (props.allowOutboundTo) {
props.allowOutboundTo.forEach((ato) => {
Expand Down
95 changes: 95 additions & 0 deletions lib/src/wso2/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { RemovalPolicy } from 'aws-cdk-lib/core';
import { BackoffOptions } from 'exponential-backoff';

import { LambdaConfig } from '../lambda/types';

/**
* Configurations used on the Lambda that receives events
* from Cloudformation to invoke WSO2 server. So if your
* WSO2 is accessible only via an specific network, or needs special rules
* or internal CA certificates, configure it using this property
*/
export type Wso2LambdaConfig = Pick<
LambdaConfig,
| 'allowOutboundTo'
| 'securityGroups'
| 'extraCaPubCert'
| 'network'
| 'logGroupSubscriberLambdaArn'
| 'logGroupRetention'
| 'logGroupRemovalPolicy'
>;

export type Wso2BaseProperties = {
/**
* Configurations related to WSO2 APIM host, credentials tenant etc
*/
wso2Config: Wso2Config;
/**
* If true, during the creation of this CFN Resource, if an API in WSO2 already exists with the same tenant/name/version, it will fail.
* If false, an existing API in WSO2 can be used. This means that an API that wasn't created by this construct can
* be updated or even deleted by this Custom Resource (if 'Retain' is 'DESTROY' for this resource).
* @default true
*/
failIfExists?: boolean;
/**
* Automatic retry for checks and mutations
* This is a best effort to make the deployment successful even when WSO2 cluster is unstable,
* but if you use long retries your CFN stack might take too long to fail when the WSO2 server
* is unavailable, as it will continue retrying for minutes.
*/
retryOptions?: RetryOptions;
/**
* Removes or retains API in WSO2 APIM server when this application is removed from CFN
* https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.RemovalPolicy.html
* Defaults to RETAIN, which means that the API will be kept in WSO2 when this resource is deleted in CFN
*/
removalPolicy?: RemovalPolicy;
/**
* Lambda config for the CustomResource that will be used for running the WSO2 API calls
* By default this Lambda will have access to all networks so it's able to invoke the WSO2 APIs.
* You can add custom CA pub certs (extraCaPubCert) or configure a specific VPC to run it (network) for example.
*/
customResourceConfig?: Wso2LambdaConfig;
};

/**
* Retry options for API requests to WSO2 server
*/
export type RetryOptions = {
/**
* Retry options for check operations such as getting API to compare with desired contents.
*
* @default "{ startingDelay: 500, timeMultiple: 1.5, numOfAttempts: 10, maxDelay: 10000 }" which means retries on [500ms, 750ms, 1125ms, 1687ms (elapsed: 4s), 2531, 3796, 5696, 8542 (elapsed: 24s), 10000, 10000, 10000, 10000, 10000 (elapsed: 74s max)]
*/
checkRetries?: BackoffOptions;
/**
* Retry options for operations such as create/update API on WSO2, change api lifecycle, publish Openapi docs etc
* @default "{ startingDelay: 2000, timeMultiple: 1.5, numOfAttempts: 3, maxDelay: 5000 }"
*/
mutationRetries?: BackoffOptions;
};

export type Wso2Config = {
/**
* WSO2 server API base URL. This is the base URL from which the API calls to WSO2 will be sent.
* @example https://mywso2.com/
*/
baseApiUrl: string;
/**
* Tenant identification (when using multi tenant setups)
* @example mypublic.com
*/
tenant?: string;
/**
* Secret id in Secret Manager with credentials for accessing WSO2 API. It will be used for
* listing APIs, creating client credentials, publishing APIs etc
* @example 'wso2/customers/credentials' - with json contents "{ user: 'myuser', pwd: 'mypass' }"
*/
credentialsSecretId: string;
/**
* Version of the WSO2 server API
* @default v1
*/
apiVersion?: 'v1';
};
Loading

0 comments on commit c71af29

Please sign in to comment.