Skip to content

Commit

Permalink
feat(aws-s3objectlambda): add L2 construct for S3 Object Lambda
Browse files Browse the repository at this point in the history
  • Loading branch information
duarten committed Feb 17, 2022
1 parent 9d01cfc commit 6823642
Show file tree
Hide file tree
Showing 6 changed files with 581 additions and 5 deletions.
8 changes: 8 additions & 0 deletions packages/@aws-cdk/aws-s3objectlambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
>
> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib
![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge)

> The APIs of higher level constructs in this module are experimental and under active development.
> They are subject to non-backward compatible changes or removal in any future version. These are
> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be
> announced in the release notes. 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-->
Expand Down
263 changes: 263 additions & 0 deletions packages/@aws-cdk/aws-s3objectlambda/lib/access-point.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import * as s3 from '@aws-cdk/aws-s3';
import * as core from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnAccessPoint } from './s3objectlambda.generated';

/**
* The interface that represents the AccessPoint resource.
*/
export interface IAccessPoint extends core.IResource {
/**
* The ARN of the access point.
* @attribute
*/
readonly accessPointArn: string

/**
* The creation data of the access point.
* @attribute
*/
readonly accessPointCreationDate: string;

/**
* The IPv4 DNS name of the access point.
*/
readonly domainName: string;

/**
* The regional domain name of the access point.
*/
readonly regionalDomainName: string;

/**
* The virtual hosted-style URL of an S3 object through this access point.
* Specify `regional: false` at the options for non-regional URL.
* @param key The S3 key of the object. If not specified, the URL of the
* bucket is returned.
* @param options Options for generating URL.
* @returns an ObjectS3Url token
*/
virtualHostedUrlForObject(key?: string, options?: s3.VirtualHostedStyleUrlOptions): string;
}

/**
* Creates an S3 Object Lambda Access Point, which can intercept
* and transform `GetObject` requests.
*
* @param fn The Lambda function
* @param props Configuration for this Access Point
*/
export interface AccessPointProps {
/**
* The bucket to which this access point belongs.
*/
readonly bucket: s3.IBucket

/**
* The Lambda function used to transform objects.
*/
readonly fn: lambda.IFunction

/**
* The name of the access point access point.
*/
readonly accessPointName: string

/**
* Whether CloudWatch metrics are enabled for the access point.
*
* @default false
*/
readonly cloudWatchMetricsEnabled?: boolean

/**
* Whether the Lambda function can process `GetObject-Range` requests.
*
* @default false
*/
readonly supportsGetObjectRange?: boolean

/**
* Whether the Lambda function can process `GetObject-PartNumber` requests.
*
* @default false
*/
readonly supportsGetObjectPartNumber?: boolean

/**
* Additional JSON that provides supplemental data passed to the
* Lambda function on every request.
*
* @default - No data.
*/
readonly payload?: string
}

abstract class AccessPointBase extends core.Resource implements IAccessPoint {
public abstract readonly accessPointArn: string
public abstract readonly accessPointCreationDate: string

protected abstract readonly name: string;

/** Implement the {@link IAccessPoint.domainName} field. */
get domainName(): string {
const urlSuffix = this.stack.urlSuffix;
return `${this.name}-${this.stack.account}.s3-object-lambda.${urlSuffix}`;
}

/** Implement the {@link IAccessPoint.regionalDomainName} field. */
get regionalDomainName(): string {
const urlSuffix = this.stack.urlSuffix;
const region = this.stack.region;
return `${this.name}-${this.stack.account}.s3-object-lambda.${region}.${urlSuffix}`;
}

/** Implement the {@link IAccessPoint.virtualHostedUrlForObject} method. */
public virtualHostedUrlForObject(key?: string, options?: s3.VirtualHostedStyleUrlOptions): string {
const domainName = options?.regional ?? true ? this.regionalDomainName : this.domainName;
const prefix = `https://${domainName}`;
if (typeof key !== 'string') {
return prefix;
}
if (key.startsWith('/')) {
key = key.slice(1);
}
if (key.endsWith('/')) {
key = key.slice(0, -1);
}
return `${prefix}/${key}`;
}
}

/**
* The access point resource attributes.
*/
export interface AccessPointAttributes {
/**
* The ARN of the access point.
*/
readonly accessPointArn: string

/**
* The creation data of the access point.
*/
readonly accessPointCreationDate: string;
}

/**
* An S3 Object Lambda Access Point for intercepting and
* transforming `GetObject` requests.
*/
export class AccessPoint extends AccessPointBase {
/**
* Reference an existing AccessPoint defined outside of the CDK code.
*/
public static fromAccessPointAttributes(scope: Construct, id: string, attrs: AccessPointAttributes): IAccessPoint {
const arn = core.Arn.split(attrs.accessPointArn, core.ArnFormat.SLASH_RESOURCE_NAME);
if (!arn.resourceName) {
throw new Error('Unable to parse acess point name');
}
const name = arn.resourceName;
class Import extends AccessPointBase {
public readonly accessPointArn: string = attrs.accessPointArn;
public readonly accessPointCreationDate: string = attrs.accessPointCreationDate;
protected name: string = name;
}
return new Import(scope, id);
}

private readonly accessPoint: CfnAccessPoint
protected readonly name: string

/**
* The ARN of the access point.
* @attribute
*/
public readonly accessPointArn: string

/**
* The creation data of the access point.
* @attribute
*/
public readonly accessPointCreationDate: string

constructor(scope: Construct, id: string, props: AccessPointProps) {
super(scope, id);

const supporting = new s3.CfnAccessPoint(this, 'AccessPoint', {
bucket: props.bucket.bucketName,
});
supporting.addPropertyOverride('Name', `${props.accessPointName}-access-point`);

const allowedFeatures = [];
if (props.supportsGetObjectPartNumber) {
allowedFeatures.push('GetObject-PartNumber');
}
if (props.supportsGetObjectRange) {
allowedFeatures.push('GetObject-Range');
}

this.name = props.accessPointName.toLowerCase();
this.accessPoint = new CfnAccessPoint(this, 'LambdaAccessPoint', {
name: this.name,
objectLambdaConfiguration: {
allowedFeatures,
cloudWatchMetricsEnabled: props.cloudWatchMetricsEnabled,
supportingAccessPoint: supporting.getAtt('Arn').toString(),
transformationConfigurations: [
{
actions: ['GetObject'],
contentTransformation: {
AwsLambda: {
FunctionArn: props.fn.functionArn,
FunctionPayload: props.payload ?? '',
},
},
},
],
},
});
this.accessPoint.addDependsOn(supporting);

this.accessPointArn = this.accessPoint.attrArn;
this.accessPointCreationDate = this.accessPoint.attrCreationDate;

props.fn.addToRolePolicy(
new iam.PolicyStatement({
actions: ['s3-object-lambda:WriteGetObjectResponse'],
resources: ['*'],
}),
);
}

/** Implement the {@link IAccessPoint.domainName} field. */
get domainName(): string {
const urlSuffix = this.stack.urlSuffix;
return `${this.accessPoint.name}-${this.stack.account}.s3-object-lambda.${urlSuffix}`;
}

/** Implement the {@link IAccessPoint.regionalDomainName} field. */
get regionalDomainName(): string {
const urlSuffix = this.stack.urlSuffix;
const region = this.stack.region;
return `${this.accessPoint.name}-${this.stack.account}.s3-object-lambda.${region}.${urlSuffix}`;
}

/** Implement the {@link IAccessPoint.virtualHostedUrlForObject} method. */
public virtualHostedUrlForObject(key?: string, options?: s3.VirtualHostedStyleUrlOptions): string {
const domainName = options?.regional ?? true ? this.regionalDomainName : this.domainName;
const prefix = `https://${domainName}`;
if (typeof key !== 'string') {
return prefix;
}
if (key.startsWith('/')) {
key = key.slice(1);
}
if (key.endsWith('/')) {
key = key.slice(0, -1);
}
return `${prefix}/${key}`;
}
}
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-s3objectlambda/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './access-point';

// AWS::S3ObjectLambda CloudFormation Resources:
export * from './s3objectlambda.generated';
20 changes: 15 additions & 5 deletions packages/@aws-cdk/aws-s3objectlambda/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,25 +85,35 @@
"devDependencies": {
"@aws-cdk/assertions": "0.0.0",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/cdk-integ-tools": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.4.0"
"@types/jest": "^27.4.0",
"jest": "^27.5.1"
},
"dependencies": {
"@aws-cdk/core": "0.0.0"
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^3.3.69"
},
"peerDependencies": {
"@aws-cdk/core": "0.0.0"
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^3.3.69"
},
"engines": {
"node": ">= 10.13.0 <13 || >=13.7.0"
},
"stability": "experimental",
"maturity": "cfn-only",
"maturity": "experimental",
"awscdkio": {
"announce": false
},
"publishConfig": {
"tag": "latest"
}
}
}
Loading

0 comments on commit 6823642

Please sign in to comment.