Skip to content

Commit

Permalink
feat (lambda): adding option to disable liveAlias and defaultLogGroup
Browse files Browse the repository at this point in the history
  • Loading branch information
flaviostutz committed Jan 24, 2024
1 parent 4c2f1f2 commit 069a641
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 28 deletions.
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.

6 changes: 6 additions & 0 deletions lib/src/apigateway/openapi-gateway-lambda.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,9 @@ describe('openapi-gateway-lambda', () => {

const testUserGetOperation = (scope: Construct, lambdaConfig: BaseNodeJsProps): LambdaOperation => {
const lambdaFunction = new BaseNodeJsFunction(scope, 'user-get-lambda', lambdaConfig);
if (!lambdaFunction.liveAlias) {
throw new Error('alias should be created');
}
return {
lambdaAlias: lambdaFunction.liveAlias,
routeConfig: testUserGetRouteConfig,
Expand All @@ -318,6 +321,9 @@ const testUserPostOperation = (
lambdaConfig: BaseNodeJsProps,
): LambdaOperation => {
const lambdaFunction = new BaseNodeJsFunction(scope, 'user-post-lambda', lambdaConfig);
if (!lambdaFunction.liveAlias) {
throw new Error('alias should be created');
}
return {
lambdaAlias: lambdaFunction.liveAlias,
routeConfig: testUserPostRouteConfig,
Expand Down
2 changes: 1 addition & 1 deletion lib/src/lambda/lambda-base.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ describe('lambda-base', () => {
baseCodePath: 'src/lambda/__tests__',
});
};
expect(f2).toThrow('eventType is required if entry is not defined');
expect(f2).toThrow(`'eventType' is required if 'entry' is not defined`);

expect(func.nodeJsFunction.isBoundToVpc).toBe(false);

Expand Down
76 changes: 60 additions & 16 deletions lib/src/lambda/lambda-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export class BaseNodeJsFunction extends Construct {

public readonly defaultSecurityGroup?: ISecurityGroup;

public readonly liveAlias: IAlias;
public readonly defaultLogGroup?: LogGroup;

public readonly liveAlias?: IAlias;

public readonly provisionedConcurrencyScalableTarget?: IScalableTarget;

Expand All @@ -54,39 +56,49 @@ export class BaseNodeJsFunction extends Construct {
const { defaultSG, securityGroups } = addSecurityGroups(this, propsWithDefaults);
this.defaultSecurityGroup = defaultSG;

// Don't use logRetention from NodeJsFunction because it's a deprecated attribute.
// We are creating a log group with retention policies in this construct instead
// that uses native log retention mechanisms
let defaultLogGroupProp: { logGroup?: LogGroup } = {};
const defaultLogGroup = addDefaultLogGroup(this, propsWithDefaults);
if (defaultLogGroup) {
defaultLogGroupProp = {
logGroup: defaultLogGroup,
};
}

// create NodeJsFunction
const nodeJsProps = {
...propsWithDefaults,
logGroup: defaultLogGroup,
...defaultLogGroupProp,
environment,
securityGroups,
};

if (!nodeJsProps.functionName) {
throw new Error('functionName should be defined');
}

// Delete logRetention from props to avoid it's usage by the NodeJsFunction (it's a deprecated attribute).
// We are creating a log group with retention policies in this construct instead
// eslint-disable-next-line fp/no-delete

const nodeJsFunc = new NodejsFunction(this, nodeJsProps.functionName, nodeJsProps);
this.nodeJsFunction = nodeJsFunc;

// create log subscriber
if (defaultLogGroupProp.logGroup) {
addLogSubscriber(this, nodeJsFunc, propsWithDefaults);
this.defaultLogGroup = defaultLogGroupProp.logGroup;
}

// create alias and autoscaling
const { liveAlias, scalableTarget } = addAliasAndAutoscaling(
this,
nodeJsFunc,
propsWithDefaults,
);

this.liveAlias = liveAlias;
if (liveAlias) {
this.liveAlias = liveAlias;
}
if (scalableTarget) {
this.provisionedConcurrencyScalableTarget = scalableTarget;
}

addLogSubscriber(this, nodeJsFunc, propsWithDefaults);

this.nodeJsFunction = nodeJsFunc;
}
}

Expand All @@ -104,17 +116,41 @@ export const getPropsWithDefaults = (
vpc = vpcFromConfig(scope, props.network);
}

let createLiveAlias = true;
if (typeof props.createLiveAlias !== 'undefined') {
// eslint-disable-next-line prefer-destructuring
createLiveAlias = props.createLiveAlias;
}
if (!createLiveAlias && props.provisionedConcurrentExecutions) {
throw new Error(
`'provisionedConcurrentExecutions' cannot be used if 'createLiveAlias' is false`,
);
}

let createDefaultLogGroup = true;
if (typeof props.createDefaultLogGroup !== 'undefined') {
// eslint-disable-next-line prefer-destructuring
createDefaultLogGroup = props.createDefaultLogGroup;
}
if (!createDefaultLogGroup && props.logGroupSubscriberLambdaArn) {
throw new Error(
`'logGroupSubscriberLambdaArn' cannot be used if 'createDefaultLogGroup' is false`,
);
}

let { entry } = props;
if (!entry) {
if (!props.eventType) {
throw new Error('eventType is required if entry is not defined');
throw new Error(`'eventType' is required if 'entry' is not defined`);
}
const eventTypeStr = props.eventType.toLowerCase();
entry = `${props.baseCodePath || 'src/handlers'}/${eventTypeStr}/${id}/index.ts`;
}

return {
...props,
createDefaultLogGroup,
createLiveAlias,
vpc,
runtime: props.runtime ?? Runtime.NODEJS_18_X,
entry,
Expand Down Expand Up @@ -147,9 +183,13 @@ const addAliasAndAutoscaling = (
scope: Construct,
nodeJsFunc: NodejsFunction,
props: LambdaConfig,
): { liveAlias: IAlias; scalableTarget?: IScalableTarget | null } => {
): { liveAlias?: IAlias; scalableTarget?: IScalableTarget | null } => {
const version = nodeJsFunc.currentVersion;

if (!props.createLiveAlias) {
return {};
}

const liveAlias = new Alias(scope, 'LiveAlias', {
aliasName: 'live',
version,
Expand Down Expand Up @@ -240,7 +280,11 @@ const addLogSubscriber = (
* LogRetention from NodeJsFunction is deprecated and uses custom resources, which are not
* necessary anymore.
*/
const addDefaultLogGroup = (scope: Construct, props: BaseNodeJsProps): LogGroup => {
const addDefaultLogGroup = (scope: Construct, props: BaseNodeJsProps): LogGroup | undefined => {
if (!props.createDefaultLogGroup) {
// eslint-disable-next-line no-undefined
return undefined;
}
return new LogGroup(scope, 'default-log-group', {
removalPolicy: props.logGroupRemovalPolicy ?? RemovalPolicy.RETAIN,
retention: props.logGroupRetention ?? RetentionDays.INFINITE,
Expand Down
47 changes: 47 additions & 0 deletions lib/src/lambda/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,63 @@ export type ScheduledProvisionedConcurrentExecution = {
name?: string;
};

/**
* Lambda configurations
*/
export type LambdaConfig = Omit<
NodejsFunctionProps,
'logGroupRetentionRole' | 'logRetentionRetryOptions' | 'logRetention'
> & {
/**
* Cidr for a egress rule allowing connections from this Lambda to TLS services in port 443
* @default none
*/
allowTLSOutboundTo?: string;
/**
* Add these security groups to the Lambda function
* @default none
*/
securityGroups?: ISecurityGroup[];
/**
* Define an event type that will be used for naming the path of the handler.
* Ignored if "entry" is used
* @default none
*/
eventType?: EventType;
/**
* Base code path for looking for handler entry point
* Default entry point is "[baseCodePath]/[eventType]/[functionName]/index.ts"
* @defaul src/handlers
*/
baseCodePath?: string;
/**
* Min and max auto-scaling of provisioned concurrent executions for Lambda
* If only min is defined, a fixed provisioned concurrent will be set
* If min and max is set, auto-scaling is configured
* @default none - No provisioned concurrent executions is set
*/
provisionedConcurrentExecutions?: {
minCapacity?: number;
maxCapacity?: number;
target?: number;
schedules?: ScheduledProvisionedConcurrentExecution[];
};
/**
* String with contents of a public certificate of a CA to be added to the Lambda
* function filesystem so that HTTPS calls uses it
* @default none
*/
extraCaPubCert?: string;
/**
* Static network configuration for private VPCs
* @default none - will use default vpc if available
*/
network?: NetworkConfig;
/**
* Create a log group with name /aws/lambda/[function-name] and associate to this function
* @default true
*/
createDefaultLogGroup?: boolean;
/**
* Retention days for default log group for this Lambda
* @default RetentionDays.INFINITE
Expand All @@ -47,7 +85,16 @@ export type LambdaConfig = Omit<
* @default RemovalPolicy.RETAIN
*/
logGroupRemovalPolicy?: RemovalPolicy;
/**
* Register this lambda Arn as a subscriber of the default log group.
* @default none
*/
logGroupSubscriberLambdaArn?: string;
/**
* Create an alias named "live" that points to the latest version of this function
* @defaults true
*/
createLiveAlias?: boolean;
};

export enum EventType {
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion lib/src/wso2/wso2-api/handler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const handler = async (
}
throw new Error('Unrecognized RequestType');
} catch (error) {
console.log(`An error occurred. err=${error}`);
console.log(`An error has occurred. err=${error}`);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const err = error as any;
if (err.stack) {
Expand Down
19 changes: 10 additions & 9 deletions lib/src/wso2/wso2-api/wso2-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Peer, Port } from 'aws-cdk-lib/aws-ec2';
import { OpenAPIObject } from 'openapi3-ts/oas30';
import { RetentionDays } from 'aws-cdk-lib/aws-logs';
import { Provider } from 'aws-cdk-lib/custom-resources';

import { EventType } from '../../lambda/types';
import { lintOpenapiDocument } from '../../utils/openapi-lint';
import { BaseNodeJsFunction } from '../../lambda/lambda-base';

import { Wso2ApiDefinition, Wso2ApiProps } from './types';
import { applyDefaultsWso2ApiDefinition, validateWso2ApiDefs } from './wso2-api-defs';
import { applyDefaultsWso2ApiDefinition, validateWso2ApiDefs } from './api-defs';

/**
* WSO2 API CDK construct for creating WSO2 APIs based on Openapi and WSO2-specific configurations
Expand Down Expand Up @@ -58,11 +59,13 @@ export class Wso2Api extends Construct {
}

// lambda function used for invoking WSO2 APIs during CFN operations
const customResourceFunction = new BaseNodeJsFunction(this, 'Wso2ApiCustomResourceFunction', {
const customResourceFunction = new BaseNodeJsFunction(this, `${id}-wso2api-custom-lambda`, {
stage: 'dev',
timeout: Duration.seconds(120),
runtime: Runtime.NODEJS_18_X,
eventType: EventType.CustomResource,
createLiveAlias: false,
createDefaultLogGroup: true, // TODO change to false?
entry: wso2LambdaEntry,
initialPolicy: [
PolicyStatement.fromJson({
Expand All @@ -80,17 +83,15 @@ export class Wso2Api extends Construct {
customResourceFunction.defaultSecurityGroup?.addEgressRule(Peer.anyIpv4(), Port.allTraffic());
}

// const customResourceProvider = new Provider(this, 'Wso2ApiCustomResourceProvider', {
// onEventHandler: customResourceFunction.nodeJsFunction,
// logRetention,
// totalTimeout: Duration.minutes(10),
// });
const customResourceProvider = new Provider(this, `${id}-wso2api-custom-provider`, {
onEventHandler: customResourceFunction.nodeJsFunction,
});

// TODO check if large open api documents can be passed by Custom Resource properties

// eslint-disable-next-line no-new
new CustomResource(this, 'Wso2ApiCustomResource', {
serviceToken: customResourceFunction.nodeJsFunction.functionArn,
new CustomResource(this, `${id}-wso2api-custom-resource`, {
serviceToken: customResourceProvider.serviceToken,
properties: {
wso2Config: props.wso2Config,
apiDefinition: wso2ApiDefs,
Expand Down

0 comments on commit 069a641

Please sign in to comment.