Skip to content

Commit

Permalink
feat: release 0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
nlopezm committed Feb 15, 2021
1 parent f355d3a commit 18497fd
Show file tree
Hide file tree
Showing 15 changed files with 1,200 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
/lib
/test-reports/
build/Release
cdk.out
coverage
jspm_packages/
junit.xml
Expand Down
10 changes: 10 additions & 0 deletions .projen/deps.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@
"version": "^3.9.5",
"type": "build"
},
{
"name": "@aws-cdk/aws-certificatemanager",
"version": "^1.73.0",
"type": "peer"
},
{
"name": "@aws-cdk/aws-cloudfront-origins",
"version": "^1.73.0",
Expand Down Expand Up @@ -118,6 +123,11 @@
"version": "^3.2.27",
"type": "peer"
},
{
"name": "@aws-cdk/aws-certificatemanager",
"version": "^1.73.0",
"type": "runtime"
},
{
"name": "@aws-cdk/aws-cloudfront-origins",
"version": "^1.73.0",
Expand Down
2 changes: 2 additions & 0 deletions .projenrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ const project = new AwsCdkConstructLibrary({
repositoryUrl: 'https://github.com/nlopezm/aws-cdk-image-enhancer.git',
cdkDependencies: [
'@aws-cdk/aws-cloudfront-origins',
'@aws-cdk/aws-certificatemanager',
'@aws-cdk/aws-cloudfront',
'@aws-cdk/aws-lambda',
'@aws-cdk/aws-s3',
'@aws-cdk/core',
],
eslint: true,
keywords: ['aws-cdk', 'aws', 'cdk', 'cloudfront', 'formatter', 'images', 'lambda', 'lambda@edge', 'resize'],
gitignore: ['cdk.out'],

/* AwsCdkConstructLibraryOptions */
// cdkAssert: true, /* Install the @aws-cdk/assert library? */
Expand Down
141 changes: 141 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# API Reference

**Classes**

Name|Description
----|-----------
[ImageEnhancer](#aws-cdk-image-enhancer-imageenhancer)|*No description*


**Structs**

Name|Description
----|-----------
[DistributionProps](#aws-cdk-image-enhancer-distributionprops)|Properties for a Distribution.
[FunctionProps](#aws-cdk-image-enhancer-functionprops)|*No description*


**Interfaces**

Name|Description
----|-----------
[IImageEnhancerProps](#aws-cdk-image-enhancer-iimageenhancerprops)|*No description*



## class ImageEnhancer <a id="aws-cdk-image-enhancer-imageenhancer"></a>



__Implements__: [IConstruct](#constructs-iconstruct), [IConstruct](#aws-cdk-core-iconstruct), [IConstruct](#constructs-iconstruct), [IDependable](#aws-cdk-core-idependable)
__Extends__: [Construct](#aws-cdk-core-construct)

### Initializer




```ts
new ImageEnhancer(scope: Construct, id: string, props?: IImageEnhancerProps)
```

* **scope** (<code>[Construct](#aws-cdk-core-construct)</code>) *No description*
* **id** (<code>string</code>) *No description*
* **props** (<code>[IImageEnhancerProps](#aws-cdk-image-enhancer-iimageenhancerprops)</code>) *No description*




## struct DistributionProps <a id="aws-cdk-image-enhancer-distributionprops"></a>


Properties for a Distribution.



Name | Type | Description
-----|------|-------------
**additionalBehaviors**? | <code>Map<string, [BehaviorOptions](#aws-cdk-aws-cloudfront-behavioroptions)></code> | Additional behaviors for the distribution, mapped by the pathPattern that specifies which requests to apply the behavior to.<br/>__*Default*__: no additional behaviors are added.
**certificate**? | <code>[ICertificate](#aws-cdk-aws-certificatemanager-icertificate)</code> | A certificate to associate with the distribution.<br/>__*Default*__: the CloudFront wildcard certificate (*.cloudfront.net) will be used.
**comment**? | <code>string</code> | Any comments you want to include about the distribution.<br/>__*Default*__: no comment
**defaultBehavior**? | <code>[BehaviorOptions](#aws-cdk-aws-cloudfront-behavioroptions)</code> | The default behavior for the distribution.<br/>__*Optional*__
**defaultRootObject**? | <code>string</code> | The object that you want CloudFront to request from your origin (for example, index.html) when a viewer requests the root URL for your distribution. If no default object is set, the request goes to the origin's root (e.g., example.com/).<br/>__*Default*__: no default root object
**domainNames**? | <code>Array<string></code> | Alternative domain names for this distribution.<br/>__*Default*__: The distribution will only support the default generated name (e.g., d111111abcdef8.cloudfront.net)
**enableIpv6**? | <code>boolean</code> | Whether CloudFront will respond to IPv6 DNS requests with an IPv6 address.<br/>__*Default*__: true
**enableLogging**? | <code>boolean</code> | Enable access logging for the distribution.<br/>__*Default*__: false, unless `logBucket` is specified.
**enabled**? | <code>boolean</code> | Enable or disable the distribution.<br/>__*Default*__: true
**errorResponses**? | <code>Array<[ErrorResponse](#aws-cdk-aws-cloudfront-errorresponse)></code> | How CloudFront should handle requests that are not successful (e.g., PageNotFound).<br/>__*Default*__: No custom error responses.
**geoRestriction**? | <code>[GeoRestriction](#aws-cdk-aws-cloudfront-georestriction)</code> | Controls the countries in which your content is distributed.<br/>__*Default*__: No geographic restrictions
**httpVersion**? | <code>[HttpVersion](#aws-cdk-aws-cloudfront-httpversion)</code> | Specify the maximum HTTP version that you want viewers to use to communicate with CloudFront.<br/>__*Default*__: HttpVersion.HTTP2
**logBucket**? | <code>[IBucket](#aws-cdk-aws-s3-ibucket)</code> | The Amazon S3 bucket to store the access logs in.<br/>__*Default*__: A bucket is created if `enableLogging` is true
**logFilePrefix**? | <code>string</code> | An optional string that you want CloudFront to prefix to the access log filenames for this distribution.<br/>__*Default*__: no prefix
**logIncludesCookies**? | <code>boolean</code> | Specifies whether you want CloudFront to include cookies in access logs.<br/>__*Default*__: false
**minimumProtocolVersion**? | <code>[SecurityPolicyProtocol](#aws-cdk-aws-cloudfront-securitypolicyprotocol)</code> | The minimum version of the SSL protocol that you want CloudFront to use for HTTPS connections.<br/>__*Default*__: SecurityPolicyProtocol.TLS_V1_2_2019
**priceClass**? | <code>[PriceClass](#aws-cdk-aws-cloudfront-priceclass)</code> | The price class that corresponds with the maximum price that you want to pay for CloudFront service.<br/>__*Default*__: PriceClass.PRICE_CLASS_ALL
**webAclId**? | <code>string</code> | Unique identifier that specifies the AWS WAF web ACL to associate with this CloudFront distribution.<br/>__*Default*__: No AWS Web Application Firewall web access control list (web ACL).



## struct FunctionProps <a id="aws-cdk-image-enhancer-functionprops"></a>






Name | Type | Description
-----|------|-------------
**allowAllOutbound**? | <code>boolean</code> | Whether to allow the Lambda to send all network traffic.<br/>__*Default*__: true
**allowPublicSubnet**? | <code>boolean</code> | Lambda Functions in a public subnet can NOT access the internet.<br/>__*Default*__: false
**code**? | <code>[Code](#aws-cdk-aws-lambda-code)</code> | The source code of your Lambda function.<br/>__*Optional*__
**currentVersionOptions**? | <code>[VersionOptions](#aws-cdk-aws-lambda-versionoptions)</code> | Options for the `lambda.Version` resource automatically created by the `fn.currentVersion` method.<br/>__*Default*__: default options as described in `VersionOptions`
**deadLetterQueue**? | <code>[IQueue](#aws-cdk-aws-sqs-iqueue)</code> | The SQS queue to use if DLQ is enabled.<br/>__*Default*__: SQS queue with 14 day retention period if `deadLetterQueueEnabled` is `true`
**deadLetterQueueEnabled**? | <code>boolean</code> | Enabled DLQ.<br/>__*Default*__: false unless `deadLetterQueue` is set, which implies DLQ is enabled.
**description**? | <code>string</code> | A description of the function.<br/>__*Default*__: No description.
**environment**? | <code>Map<string, string></code> | Key-value pairs that Lambda caches and makes available for your Lambda functions.<br/>__*Default*__: No environment variables.
**environmentEncryption**? | <code>[IKey](#aws-cdk-aws-kms-ikey)</code> | The AWS KMS key that's used to encrypt your function's environment variables.<br/>__*Default*__: AWS Lambda creates and uses an AWS managed customer master key (CMK).
**events**? | <code>Array<[IEventSource](#aws-cdk-aws-lambda-ieventsource)></code> | Event sources for this function.<br/>__*Default*__: No event sources.
**filesystem**? | <code>[FileSystem](#aws-cdk-aws-lambda-filesystem)</code> | The filesystem configuration for the lambda function.<br/>__*Default*__: will not mount any filesystem
**functionName**? | <code>string</code> | A name for the function.<br/>__*Default*__: AWS CloudFormation generates a unique physical ID and uses that ID for the function's name. For more information, see Name Type.
**handler**? | <code>string</code> | The name of the method within your code that Lambda calls to execute your function.<br/>__*Optional*__
**initialPolicy**? | <code>Array<[PolicyStatement](#aws-cdk-aws-iam-policystatement)></code> | Initial policy statements to add to the created Lambda Role.<br/>__*Default*__: No policy statements are added to the created Lambda role.
**layers**? | <code>Array<[ILayerVersion](#aws-cdk-aws-lambda-ilayerversion)></code> | A list of layers to add to the function's execution environment.<br/>__*Default*__: No layers.
**logRetention**? | <code>[RetentionDays](#aws-cdk-aws-logs-retentiondays)</code> | The number of days log events are kept in CloudWatch Logs.<br/>__*Default*__: logs.RetentionDays.INFINITE
**logRetentionRetryOptions**? | <code>[LogRetentionRetryOptions](#aws-cdk-aws-lambda-logretentionretryoptions)</code> | When log retention is specified, a custom resource attempts to create the CloudWatch log group.<br/>__*Default*__: Default AWS SDK retry options.
**logRetentionRole**? | <code>[IRole](#aws-cdk-aws-iam-irole)</code> | The IAM role for the Lambda function associated with the custom resource that sets the retention policy.<br/>__*Default*__: A new role is created.
**maxEventAge**? | <code>[Duration](#aws-cdk-core-duration)</code> | The maximum age of a request that Lambda sends to a function for processing.<br/>__*Default*__: Duration.hours(6)
**memorySize**? | <code>number</code> | The amount of memory, in MB, that is allocated to your Lambda function.<br/>__*Default*__: 128
**onFailure**? | <code>[IDestination](#aws-cdk-aws-lambda-idestination)</code> | The destination for failed invocations.<br/>__*Default*__: no destination
**onSuccess**? | <code>[IDestination](#aws-cdk-aws-lambda-idestination)</code> | The destination for successful invocations.<br/>__*Default*__: no destination
**profiling**? | <code>boolean</code> | Enable profiling.<br/>__*Default*__: No profiling.
**profilingGroup**? | <code>[IProfilingGroup](#aws-cdk-aws-codeguruprofiler-iprofilinggroup)</code> | Profiling Group.<br/>__*Default*__: A new profiling group will be created if `profiling` is set.
**reservedConcurrentExecutions**? | <code>number</code> | The maximum of concurrent executions you want to reserve for the function.<br/>__*Default*__: No specific limit - account limit.
**retryAttempts**? | <code>number</code> | The maximum number of times to retry when the function returns an error.<br/>__*Default*__: 2
**role**? | <code>[IRole](#aws-cdk-aws-iam-irole)</code> | Lambda execution role.<br/>__*Default*__: A unique role will be generated for this lambda function. Both supplied and generated roles can always be changed by calling `addToRolePolicy`.
**runtime**? | <code>[Runtime](#aws-cdk-aws-lambda-runtime)</code> | The runtime environment for the Lambda function that you are uploading.<br/>__*Optional*__
**securityGroup**?⚠️ | <code>[ISecurityGroup](#aws-cdk-aws-ec2-isecuritygroup)</code> | What security group to associate with the Lambda's network interfaces. This property is being deprecated, consider using securityGroups instead.<br/>__*Default*__: If the function is placed within a VPC and a security group is not specified, either by this or securityGroups prop, a dedicated security group will be created for this function.
**securityGroups**? | <code>Array<[ISecurityGroup](#aws-cdk-aws-ec2-isecuritygroup)></code> | The list of security groups to associate with the Lambda's network interfaces.<br/>__*Default*__: If the function is placed within a VPC and a security group is not specified, either by this or securityGroup prop, a dedicated security group will be created for this function.
**timeout**? | <code>[Duration](#aws-cdk-core-duration)</code> | The function execution time (in seconds) after which Lambda terminates the function.<br/>__*Default*__: Duration.seconds(3)
**tracing**? | <code>[Tracing](#aws-cdk-aws-lambda-tracing)</code> | Enable AWS X-Ray Tracing for Lambda Function.<br/>__*Default*__: Tracing.Disabled
**vpc**? | <code>[IVpc](#aws-cdk-aws-ec2-ivpc)</code> | VPC network to place Lambda network interfaces.<br/>__*Default*__: Function is not placed within a VPC.
**vpcSubnets**? | <code>[SubnetSelection](#aws-cdk-aws-ec2-subnetselection)</code> | Where to place the network interfaces within the VPC.<br/>__*Default*__: the Vpc default strategy if not specified



## interface IImageEnhancerProps <a id="aws-cdk-image-enhancer-iimageenhancerprops"></a>




### Properties


Name | Type | Description
-----|------|-------------
**cloudfrontDistributionProps**? | <code>[DistributionProps](#aws-cdk-image-enhancer-distributionprops)</code> | __*Optional*__
**originResponseLambdaProps**? | <code>[FunctionProps](#aws-cdk-image-enhancer-functionprops)</code> | __*Optional*__
**s3BucketProps**? | <code>[BucketProps](#aws-cdk-aws-s3-bucketprops)</code> | __*Optional*__
**viewerRequestLambdaProps**? | <code>[FunctionProps](#aws-cdk-image-enhancer-functionprops)</code> | __*Optional*__



111 changes: 111 additions & 0 deletions lambda/image-origin-response-function/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const sharp = require('sharp');
const AWS = require('aws-sdk');
const s3 = new AWS.S3({ signatureVersion: 'v4' });

/**
* @param {{uri: String}} request
* @returns {{key: String, prefix: String, extension: String, width?: Number, height?: Number}}
*/
exports.extractDataFromUri = request => {
const uri = request.uri;
// AWS key is the URI without the initial '/'
const key = uri.substring(1);

// Try to match dimensions first
// e.g.: /path/to/file-100wx100h.webp
const dimensionMatch = uri.match(/\/(.*)-([0-9]+)wx([0-9]+)h\.([^.]*)$/);
if (dimensionMatch)
return {
key,
prefix: dimensionMatch[1],
width: parseInt(dimensionMatch[2]),
height: parseInt(dimensionMatch[3]),
extension: dimensionMatch[4],
};

// If no dimensions included, we just care about the prefix and the extension
const simpleMatch = uri.match(/\/(.*)\.([^.]*)$/);

return { key, prefix: simpleMatch[1], extension: simpleMatch[2] };
};

exports.handler = async (event, _context, callback) => {
const response = event.Records[0].cf.response;

const request = event.Records[0].cf.request;

// Extracting bucket name. domainName looks like this: bucket-name.s3.region.amazonaws.com"
const [, Bucket] = request.origin.s3.domainName.match(/(.*).s3./);

if (Number(response.status) !== 404) {
if (Number(response.status) !== 200) response.status = 400;
callback(null, response);
return;
}

// Image not found in bucket
let params;
try {
params = this.extractDataFromUri(request);
} catch (e) {
callback(null, response);
return;
}

const { Contents } = await s3
.listObjects({
Bucket,
// List all keys starting with path/to/file.
Prefix: params.prefix + '.',
})
.promise();

if (!Contents.length) {
callback(null, response);
return;
}

const baseImageKey = (() => {
/**
* Try to find an existent image for the requested extension.
* If there isn't one, the use as base image the first from the Contents array
*/
const found = Contents.find(({ Key }) => Key.split(`${params.prefix}.`)[1] === params.extension);
if (found) return found.Key;
return Contents[0].Key;
})();

// Use the found key to get the image from the s3 bucket
const { Body, ContentType } = await s3.getObject({ Key: baseImageKey, Bucket }).promise();

const sharpPromise = sharp(Body);

// If dimensions passed, resize base image
if (params.width || params.height) {
// Allow to pass only one of width or height
sharpPromise.resize(params.width || undefined, params.height || undefined);
}
// If the requested extension is different than the base image extension, then
// format it to the new extension
if (ContentType !== `image/${params.extension}`) sharpPromise.toFormat(params.extension);

const buffer = await sharpPromise.toBuffer();

// Save the new image to s3 bucket. Don't await for this to finish.
// Even if the upload fails we return the converted image
s3.putObject({
Body: buffer,
Bucket,
ContentType: 'image/' + params.extension,
CacheControl: 'max-age=31536000',
Key: params.key,
StorageClass: 'STANDARD',
}).promise();

response.status = 200;
response.body = buffer.toString('base64');
response.bodyEncoding = 'base64';
response.headers['content-type'] = [{ key: 'Content-Type', value: 'image/' + params.extension }];
callback(null, response);
};
Loading

0 comments on commit 18497fd

Please sign in to comment.