Skip to content

Commit

Permalink
First implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
biffgaut committed Oct 19, 2021
1 parent 98592c7 commit 5740661
Show file tree
Hide file tree
Showing 22 changed files with 6,869 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
lib/*.js
test/*.js
*.d.ts
coverage
test/lambda/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
lib/*.js
test/*.js
!test/lambda/*
*.js.map
*.d.ts
node_modules
*.generated.ts
dist
.jsii

.LAST_BUILD
.nyc_output
coverage
.nycrc
.LAST_PACKAGE
*.snk
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Exclude typescript source and config
*.ts
tsconfig.json
coverage
.nyc_output
*.tgz
*.snk
*.tsbuildinfo

# Include javascript files and typescript declarations
!*.js
!*.d.ts

# Exclude jsii outdir
dist

# Include .jsii
!.jsii

# Include .jsii
!.jsii
108 changes: 108 additions & 0 deletions source/patterns/@aws-solutions-constructs/aws-alb-lambda/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# aws-route53-alb module
<!--BEGIN STABILITY BANNER-->

---

![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge)

> All classes are under active development and subject to non-backward compatible changes or removal in any
> future version. These are not subject to the [Semantic Versioning](https://semver.org/) model.
> This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package.
---
<!--END STABILITY BANNER-->

| **Reference Documentation**:| <span style="font-weight: normal">https://docs.aws.amazon.com/solutions/latest/constructs/</span>|
|:-------------|:-------------|
<div style="height:8px"></div>

| **Language** | **Package** |
|:-------------|-----------------|
|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_alb_lambda`|
|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-alb-lambda`|
|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.alblambda`|

This AWS Solutions Construct implements an an Application Load Balancer to an AWS Lambda function

Here is a minimal deployable pattern definition in Typescript:

``` typescript

// Obtain a pre-existing certificate from your account
const certificate = acm.Certificate.fromCertificateArn(
scope,
'existing-cert',
"arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012"
);
const props: AlbToLambdaProps = {
lambdaFunctionProps: {
code: lambda.Code.fromAsset(`${__dirname}/lambda`),
runtime: lambda.Runtime.NODEJS_12_X,
handler: 'index.handler'
},
listenerProps: {
certificates: [ certificate ]
},
publicApi: true
};
new AlbToLambda(stack, 'new-construct', props);

```

## Initializer

``` text
new AlbToLambda(scope: Construct, id: string, props: AlbToLambdaProps);
```

_Parameters_

* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html)
* id `string`
* props [`AlbToLambdaProps`](#pattern-construct-props)

## Pattern Construct Props

| **Name** | **Type** | **Description** |
|:-------------|:----------------|-----------------|
| loadBalancerProps? | [elasticloadbalancingv2.ApplicationLoadBalancerProps](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticloadbalancingv2.ApplicationLoadBalancerProps.html) | Optional custom properties for a new loadBalancer. Providing both this and existingLoadBalancer is an error. This cannot specify a VPC, it will use the VPC in existingVpc or the VPC created by the construct. |
| existingLoadBalancerObj? | [elasticloadbalancingv2.ApplicationLoadBalancer](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticloadbalancingv2.ApplicationLoadBalancer.html) | Existing Application Load Balancer to incorporate into the construct architecture. Providing both this and loadBalancerProps is an error. The VPC containing this loadBalancer must match the VPC provided in existingVpc. |
| listenerProps? | [ApplicationListenerProps](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticloadbalancingv2.ApplicationListenerProps.html) | |
| targetProps? | [ApplicationTargetGroupProps](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticloadbalancingv2.ApplicationTargetGroupProps.html) | |
| ruleProps? | [AddRuleProps](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticloadbalancingv2.AddRuleProps.html) | |
| vpcProps? | [ec2.VpcProps](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.VpcProps.html) | Optional custom properties for a VPC the construct will create. This VPC will be used by the new ALB and any Private Hosted Zone the construct creates (that's why loadBalancerProps and privateHostedZoneProps can't include a VPC). Providing both this and existingVpc is an error. |
|existingLambdaObj?|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Existing instance of Lambda Function object, providing both this and `lambdaFunctionProps` will cause an error.|
|lambdaFunctionProps?|[`lambda.FunctionProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.FunctionProps.html)|Optional user provided props to override the default props for the Lambda function.|
| existingVpc? | [ec2.IVpc](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.IVpc.html) | An existing VPC in which to deploy the construct. Providing both this and vpcProps is an error. If the client provides an existing load balancer and/or existing Private Hosted Zone, those constructs must exist in this VPC. |
| logAccessLogs? | boolean| Whether to turn on Access Logs for the Application Load Balancer. Uses an S3 bucket with associated storage costs.Enabling Access Logging is a best practice. default - true |
| loggingBucketProps? | [s3.BucketProps](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.BucketProps.html) | Optional properties to customize the bucket used to store the ALB Access Logs. Supplying this and setting logAccessLogs to false is an error. @default - none |
| publicApi | boolean | Whether the construct is deploying a private or public API. This has implications for the VPC and ALB. |

## Pattern Properties

| **Name** | **Type** | **Description** |
|:-------------|:----------------|-----------------|
| vpc | [ec2.IVpc](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.IVpc.html) | The VPC used by the construct (whether created by the construct or providedb by the client) |
| loadBalancer | [elasticloadbalancingv2.ApplicationLoadBalancer](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticloadbalancingv2.ApplicationLoadBalancer.html) | The Load Balancer used by the construct (whether created by the construct or providedb by the client) |

## Default settings

Out of the box implementation of the Construct without any override will set the following defaults:

### Application Load Balancer
* Creates or configures an Application Load Balancer with:
* Required listeners
* New target group with routing rules if appropriate

### AWS Lambda Function
* Configure limited privilege access IAM role for Lambda function
* Enable reusing connections with Keep-Alive for NodeJs Lambda function
* Enable X-Ray Tracing
* Set Environment Variables
* AWS_NODEJS_CONNECTION_REUSE_ENABLED (for Node 10.x and higher functions)

## Architecture
![Architecture Diagram](architecture.png)

***
&copy; Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
198 changes: 198 additions & 0 deletions source/patterns/@aws-solutions-constructs/aws-alb-lambda/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/**
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
* with the License. A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

import * as elb from "@aws-cdk/aws-elasticloadbalancingv2";
import * as ec2 from "@aws-cdk/aws-ec2";
import * as s3 from "@aws-cdk/aws-s3";
import * as lambda from "@aws-cdk/aws-lambda";
import { Construct } from "@aws-cdk/core";
import * as defaults from "@aws-solutions-constructs/core";
import { CfnListener, CfnTargetGroup } from "@aws-cdk/aws-elasticloadbalancingv2";

export interface AlbToLambdaProps {
readonly loadBalancerProps?: elb.ApplicationLoadBalancerProps | any;
readonly existingLoadBalancerObj?: elb.ApplicationLoadBalancer;

/**
* Existing instance of Lambda Function object, providing both this and `lambdaFunctionProps` will cause an error.
*
* @default - None
*/
readonly existingLambdaObj?: lambda.Function;
/**
* User provided props to override the default props for the Lambda function.
*
* @default - Default props are used
*/
readonly lambdaFunctionProps?: lambda.FunctionProps;

readonly listenerProps?: elb.ApplicationListenerProps | any;

readonly targetProps?: elb.ApplicationTargetGroupProps;
readonly ruleProps?: elb.AddRuleProps;

readonly vpcProps?: ec2.VpcProps;
readonly existingVpc?: ec2.IVpc;
/**
* Whether to turn on Access Logs for the Application Load Balancer. Uses an S3 bucket
* with associated storage costs. Enabling Access Logging is a best practice.
*
* @default - true
*/
readonly logAlbAccessLogs?: boolean,
/**
* Optional properties to customize the bucket used to store the ALB Access
* Logs. Supplying this and setting logAccessLogs to false is an error.
*
* @default - none
*/
readonly albLoggingBucketProps?: s3.BucketProps,

readonly publicApi: boolean;
}

export class AlbToLambda extends Construct {
public readonly loadBalancer: elb.ApplicationLoadBalancer;
public readonly vpc: ec2.IVpc;
public readonly lambdaFunction: lambda.Function;
public readonly listener: elb.ApplicationListener;

constructor(scope: Construct, id: string, props: AlbToLambdaProps) {
super(scope, id);
defaults.CheckProps(props);

if (props.listenerProps?.certificateArns) {
throw new Error('certificateArns is deprecated. Please supply certificates using props.listenerProps.certificates');
}

if (
(props.existingLoadBalancerObj && (props.existingLoadBalancerObj.listeners.length === 0) || !props.existingLoadBalancerObj)
&& !props.listenerProps
) {
throw new Error(
"When adding the first listener and target to a load balancer, listenerProps must be specified and include at least a certificate or protocol: HTTP"
);
}

if (
((props.existingLoadBalancerObj) && (props.existingLoadBalancerObj.listeners.length > 0)) &&
props.listenerProps
) {
throw new Error(
"This load balancer already has a listener, listenerProps may not be specified"
);
}

if (((props.existingLoadBalancerObj) && (props.existingLoadBalancerObj.listeners.length > 0)) && !props.ruleProps) {
throw new Error(
"When adding a second target to an existing listener, there must be rules provided"
);
}

// Check construct specific invalid inputs
if (props.existingLoadBalancerObj && !props.existingVpc) {
throw new Error(
"An existing ALB already exists in a VPC, that VPC must be provided in props.existingVpc for the rest of the construct to use."
);
}

if ( props.existingLoadBalancerObj ) {
defaults.printWarning(
"The public/private property of an exisng ALB must match the props.publicApi setting provided."
);
}

// Obtain VPC for construct (existing or created)
// Determine all the resources to use (existing or launch new)
if (props.existingVpc) {
this.vpc = props.existingVpc;
} else {
this.vpc = defaults.buildVpc(scope, {
defaultVpcProps: props.publicApi
? defaults.DefaultPublicPrivateVpcProps()
: defaults.DefaultIsolatedVpcProps(),
userVpcProps: props.vpcProps,
constructVpcProps: props.publicApi
? undefined
: { enableDnsHostnames: true, enableDnsSupport: true, },
});
}

this.loadBalancer = defaults.ObtainAlb(
this,
id,
this.vpc,
props.publicApi,
props.existingLoadBalancerObj,
props.loadBalancerProps,
props.logAlbAccessLogs,
props.albLoggingBucketProps
);

// Obtain Lambda function for construct (existing or created)
this.lambdaFunction = defaults.buildLambdaFunction(this, {
existingLambdaObj: props.existingLambdaObj,
lambdaFunctionProps: props.lambdaFunctionProps,
vpc: this.vpc,
});

if (this.loadBalancer.listeners.length === 0) {
// This is a new listener, we need to create it along with the default target
const newTargetGroup = defaults.CreateLambdaTargetGroup(this, 'first-tg', this.lambdaFunction, props.targetProps);
this.listener = defaults.AddListener(
this,
this.loadBalancer,
newTargetGroup,
props.listenerProps
);
// Testing occasionally caused a TargetGroup not found error, this
// code ensures the Group will be complete before the Listener tries
// to access it.
const newListener = this.listener.node.defaultChild as CfnListener;
const cfnTargetGroup = newTargetGroup.node.defaultChild as CfnTargetGroup;
newListener.addDependsOn(cfnTargetGroup);
} else {
// We're adding a target to an existing listener. If this.loadBalancer.listeners.length
// is >0, then this.loadBalancer was set from existingLoadBalancer
this.listener = GetActiveListener(this.loadBalancer.listeners);
defaults.AddTarget(
this,
defaults.CreateLambdaTargetGroup(
this,
`tg${this.loadBalancer.listeners.length}`,
this.lambdaFunction,
props.targetProps
),
this.listener,
props.ruleProps
);
}
}
}

function GetActiveListener(listeners: elb.ApplicationListener[]): elb.ApplicationListener {
let listener: elb.ApplicationListener;

if (listeners.length === 1 ) {
listener = listeners[0];
} else {
const correctListener = listeners.find(i => (i.node.children[0] as elb.CfnListener).protocol === "HTTPS");
if (correctListener) {
listener = correctListener;
} else {
// This line should be unreachable
throw new Error(`Two listeners in the ALB, but neither are HTTPS`);
}
}
return listener;
}
Loading

0 comments on commit 5740661

Please sign in to comment.