├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── bucket └── serverless.yml ├── deploy.sh ├── docker-compose.yml ├── functions ├── package-lock.json ├── package.json ├── resize.js └── serverless.yml └── secrets └── sample.secrets.env /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .serverless 3 | functions/.serverless 4 | functions/node_modules 5 | .vscode -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | jspm_packages 4 | 5 | # Serverless directories 6 | .serverless 7 | 8 | # Secrets 9 | secrets.json 10 | secrets.env 11 | 12 | # VS Code 13 | .vscode -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazonlinux 2 | 3 | # Create deploy directory 4 | WORKDIR /deploy 5 | 6 | # Install system dependencies 7 | RUN yum -y install make gcc* 8 | RUN curl --silent --location https://rpm.nodesource.com/setup_12.x | bash - 9 | RUN yum -y install nodejs 10 | 11 | # Install serverless 12 | RUN npm install -g serverless 13 | 14 | # Copy source 15 | COPY . . 16 | 17 | # Install app dependencies 18 | RUN cd /deploy/functions && npm i --production && cd /deploy 19 | 20 | # Run deploy script 21 | CMD ./deploy.sh ; sleep 2m -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Adnan Rahić 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Image resize on the fly AWS Lambda function with Serverless and Node.js 2 | 3 | A simple Serverless service for creating an image resize-on-the-fly function with AWS Lambda. For Sharp to work correctly it must be installed in the same environment the production is running in. Because AWS Lambda is running on Amazon Linux it must be installed on the same system. 4 | 5 | For this purpose we're using Docker to spin up a container, install the Serverless Framework and deploy the function from inside the container. 6 | 7 | This service will deploy both the AWS Lambda function and AWS S3 bucket from where the images will be grabbed, resized and put back. 8 | 9 | ## Getting Started (Ubuntu-based Linux Systems) 10 | 11 | These instructions will get you up and running. See deployment for notes on how to deploy the project on a live system. 12 | 13 | #### Clone to your local machine: 14 | 15 | ```bash 16 | $ git clone https://github.com/adnanrahic/serverless-docker-image-resize.git 17 | ``` 18 | 19 | #### Change into the cloned dir: 20 | ```bash 21 | $ cd serverless-docker-image-resize 22 | ``` 23 | 24 | #### Install Docker and docker compose: 25 | 26 | 1. Install Docker 27 | ```bash 28 | $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 29 | $ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" 30 | $ sudo apt-get update 31 | $ apt-cache policy docker-ce 32 | $ sudo apt-get install -y docker-ce 33 | ``` 34 | After typing the command: 35 | ```bash 36 | $ sudo systemctl status docker 37 | ``` 38 | You should see the service is running. 39 | ```bash 40 | Output 41 | ● docker.service - Docker Application Container Engine 42 | Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled) 43 | Active: active (running) since Sun 2016-05-01 06:53:52 CDT; 1 weeks 3 days ago 44 | Docs: https://docs.docker.com 45 | Main PID: 749 (docker) 46 | ``` 47 | 48 | 2. Install Docker Compose 49 | ```bash 50 | $ sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose 51 | $ sudo chmod +x /usr/local/bin/docker-compose 52 | $ docker-compose -v 53 | ``` 54 | This will print the installed version: 55 | ```bash 56 | Output 57 | docker-compose version 1.21.2, build a133471 58 | ``` 59 | 60 | All dependencies are installed. Now, deployment is a breeze. 61 | 62 | ## Deployment 63 | 64 | ### Configure secrets 65 | 66 | #### 1. `secrets.json` 67 | 68 | The `deploy.sh` script will autogenerate this file. No need to touch it at all. 69 | 70 | #### 2. `secrets.env` 71 | 72 | Add your secret keys and configuration variables here. 73 | ```env 74 | SLS_KEY=XXX 75 | SLS_SECRET=YYY 76 | STAGE=dev 77 | REGION=us-east-1 78 | BUCKET=images.your-domain.com 79 | ``` 80 | 81 | ### Run Docker Compose 82 | ```bash 83 | $ docker-compose up --build 84 | ``` 85 | This will build the image, create a container, run the `deploy.sh` script inside the container and deploy all the resources. 86 | 87 | The command line will log out the service endpoints and all info. What's important to note is the bucket name and URL you need to access your images. Check out [Usage](#usage). 88 | 89 | ## Usage 90 | 91 | After the service has been deployed, you will receive a bucket endpoint. You will add a query parameter to it in order to tell it how to resize the image. The bucket will behave as a public website. 92 | 93 | Let's upload an image so we have something to work with. 94 | ```bash 95 | $ aws s3 cp --acl public-read IMAGE_NAME.jpg s3://BUCKET 96 | ``` 97 | 98 | Example 1: 99 | ``` 100 | http://BUCKET.s3-website.REGION.amazonaws.com/420x360/IMAGE_NAME.jpg 101 | ``` 102 | 103 | Or you can access the lambda function directly. 104 | 105 | Example 2: 106 | ``` 107 | https://LAMBDA_ID.execute-api.REGION.amazonaws.com/dev/resize?key=420x360/IMAGE_NAME.jpg 108 | ``` 109 | 110 | This will resize the image in the fly and send you back the resized image while storing it for further reference. 111 | 112 | ## Credits 113 | The original tutorial for resizing S3 images I followed can be found [here](https://aws.amazon.com/blogs/compute/resize-images-on-the-fly-with-amazon-s3-aws-lambda-and-amazon-api-gateway/)! 114 | -------------------------------------------------------------------------------- /bucket/serverless.yml: -------------------------------------------------------------------------------- 1 | service: image-resize-on-the-fly-bucket 2 | 3 | custom: 4 | secrets: ${file(../secrets/secrets.json)} 5 | 6 | provider: 7 | name: aws 8 | runtime: nodejs12.x 9 | stage: ${env:STAGE, 'dev'} 10 | profile: ${env:PROFILE, 'default'} 11 | region: us-east-1 12 | environment: 13 | BUCKET: ${env:BUCKET} 14 | 15 | resources: 16 | Resources: 17 | ImageResizeOnTheFly: 18 | Type: AWS::S3::Bucket 19 | Properties: 20 | AccessControl: PublicReadWrite 21 | BucketName: ${env:BUCKET} 22 | WebsiteConfiguration: 23 | ErrorDocument: error.html 24 | IndexDocument: index.html 25 | RoutingRules: 26 | - 27 | RedirectRule: 28 | HostName: ${self:custom.secrets.DOMAIN} 29 | HttpRedirectCode: "307" 30 | Protocol: "https" 31 | ReplaceKeyPrefixWith: "${self:provider.stage}/resize?key=" 32 | RoutingRuleCondition: 33 | HttpErrorCodeReturnedEquals: "404" 34 | KeyPrefixEquals: "" 35 | ImageResizeOnTheFlyPolicy: 36 | Type: AWS::S3::BucketPolicy 37 | Properties: 38 | Bucket: 39 | Ref: ImageResizeOnTheFly 40 | PolicyDocument: 41 | Statement: 42 | - 43 | Action: 44 | - "s3:*" 45 | Effect: "Allow" 46 | Resource: 47 | Fn::Join: 48 | - "" 49 | - 50 | - "arn:aws:s3:::" 51 | - 52 | Ref: ImageResizeOnTheFly 53 | - "/*" 54 | Principal: "*" -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | # variables 2 | stage=${STAGE} 3 | region=${REGION} 4 | bucket=${BUCKET} 5 | secrets='/deploy/secrets/secrets.json' 6 | 7 | # Configure your Serverless installation to talk to your AWS account 8 | sls config credentials \ 9 | --provider aws \ 10 | --key ${SLS_KEY} \ 11 | --secret ${SLS_SECRET} \ 12 | --profile serverless-admin 13 | 14 | # cd into functions dir 15 | cd /deploy/functions 16 | 17 | # Deploy functions 18 | echo "------------------" 19 | echo 'Deploying function...' 20 | echo "------------------" 21 | sls deploy 22 | 23 | # find and replace the service endpoint 24 | if [ -z ${stage+dev} ]; then echo "Stage is unset."; else echo "Stage is set to '$stage'."; fi 25 | 26 | sls info -v | grep ServiceEndpoint > domain.txt 27 | sed -i 's@ServiceEndpoint:\ https:\/\/@@g' domain.txt 28 | sed -i "s@/$stage@@g" domain.txt 29 | domain=$(cat domain.txt) 30 | sed "s@.execute-api.$region.amazonaws.com@@g" domain.txt > id.txt 31 | id=$(cat id.txt) 32 | 33 | echo "------------------" 34 | echo "Domain:" 35 | echo " $domain" 36 | echo "------------------" 37 | echo "API ID:" 38 | echo " $id" 39 | 40 | rm domain.txt 41 | rm id.txt 42 | 43 | echo "{\"DOMAIN\":\"$domain\"}" > $secrets 44 | 45 | cd /deploy/bucket 46 | 47 | # Deploy bucket config 48 | echo "------------------" 49 | echo 'Deploying bucket...' 50 | sls deploy 51 | 52 | echo "------------------" 53 | echo 'Bucket endpoint:' 54 | echo " http://$bucket.s3-website.$region.amazonaws.com/" 55 | 56 | echo "------------------" 57 | echo "Service deployed. Press CTRL+C to exit." 58 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | image-resize-on-the-fly: 4 | build: . 5 | volumes: 6 | - ./secrets:/deploy/secrets 7 | env_file: 8 | - ./secrets/secrets.env -------------------------------------------------------------------------------- /functions/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "image-resize", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-regex": { 8 | "version": "2.1.1", 9 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 10 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 11 | }, 12 | "aproba": { 13 | "version": "1.2.0", 14 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 15 | "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 16 | }, 17 | "are-we-there-yet": { 18 | "version": "1.1.5", 19 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", 20 | "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", 21 | "requires": { 22 | "delegates": "^1.0.0", 23 | "readable-stream": "^2.0.6" 24 | } 25 | }, 26 | "array-flatten": { 27 | "version": "3.0.0", 28 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", 29 | "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" 30 | }, 31 | "base64-js": { 32 | "version": "1.5.1", 33 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 34 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 35 | }, 36 | "bl": { 37 | "version": "4.0.3", 38 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", 39 | "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", 40 | "requires": { 41 | "buffer": "^5.5.0", 42 | "inherits": "^2.0.4", 43 | "readable-stream": "^3.4.0" 44 | }, 45 | "dependencies": { 46 | "readable-stream": { 47 | "version": "3.6.0", 48 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 49 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 50 | "requires": { 51 | "inherits": "^2.0.3", 52 | "string_decoder": "^1.1.1", 53 | "util-deprecate": "^1.0.1" 54 | } 55 | } 56 | } 57 | }, 58 | "buffer": { 59 | "version": "5.7.1", 60 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 61 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 62 | "requires": { 63 | "base64-js": "^1.3.1", 64 | "ieee754": "^1.1.13" 65 | } 66 | }, 67 | "chownr": { 68 | "version": "1.1.4", 69 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 70 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 71 | }, 72 | "code-point-at": { 73 | "version": "1.1.0", 74 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 75 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 76 | }, 77 | "color": { 78 | "version": "3.1.3", 79 | "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz", 80 | "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==", 81 | "requires": { 82 | "color-convert": "^1.9.1", 83 | "color-string": "^1.5.4" 84 | } 85 | }, 86 | "color-convert": { 87 | "version": "1.9.3", 88 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 89 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 90 | "requires": { 91 | "color-name": "1.1.3" 92 | } 93 | }, 94 | "color-name": { 95 | "version": "1.1.3", 96 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 97 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 98 | }, 99 | "color-string": { 100 | "version": "1.5.4", 101 | "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", 102 | "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", 103 | "requires": { 104 | "color-name": "^1.0.0", 105 | "simple-swizzle": "^0.2.2" 106 | } 107 | }, 108 | "console-control-strings": { 109 | "version": "1.1.0", 110 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 111 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 112 | }, 113 | "core-util-is": { 114 | "version": "1.0.2", 115 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 116 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 117 | }, 118 | "decompress-response": { 119 | "version": "4.2.1", 120 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", 121 | "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", 122 | "requires": { 123 | "mimic-response": "^2.0.0" 124 | } 125 | }, 126 | "deep-extend": { 127 | "version": "0.6.0", 128 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 129 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" 130 | }, 131 | "delegates": { 132 | "version": "1.0.0", 133 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 134 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 135 | }, 136 | "detect-libc": { 137 | "version": "1.0.3", 138 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 139 | "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" 140 | }, 141 | "end-of-stream": { 142 | "version": "1.4.4", 143 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 144 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 145 | "requires": { 146 | "once": "^1.4.0" 147 | } 148 | }, 149 | "expand-template": { 150 | "version": "2.0.3", 151 | "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", 152 | "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" 153 | }, 154 | "fs-constants": { 155 | "version": "1.0.0", 156 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 157 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" 158 | }, 159 | "gauge": { 160 | "version": "2.7.4", 161 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 162 | "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", 163 | "requires": { 164 | "aproba": "^1.0.3", 165 | "console-control-strings": "^1.0.0", 166 | "has-unicode": "^2.0.0", 167 | "object-assign": "^4.1.0", 168 | "signal-exit": "^3.0.0", 169 | "string-width": "^1.0.1", 170 | "strip-ansi": "^3.0.1", 171 | "wide-align": "^1.1.0" 172 | } 173 | }, 174 | "github-from-package": { 175 | "version": "0.0.0", 176 | "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", 177 | "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" 178 | }, 179 | "has-unicode": { 180 | "version": "2.0.1", 181 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 182 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 183 | }, 184 | "ieee754": { 185 | "version": "1.2.1", 186 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 187 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 188 | }, 189 | "inherits": { 190 | "version": "2.0.4", 191 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 192 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 193 | }, 194 | "ini": { 195 | "version": "1.3.8", 196 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", 197 | "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" 198 | }, 199 | "is-arrayish": { 200 | "version": "0.3.2", 201 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", 202 | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" 203 | }, 204 | "is-fullwidth-code-point": { 205 | "version": "1.0.0", 206 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 207 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 208 | "requires": { 209 | "number-is-nan": "^1.0.0" 210 | } 211 | }, 212 | "isarray": { 213 | "version": "1.0.0", 214 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 215 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 216 | }, 217 | "mimic-response": { 218 | "version": "2.1.0", 219 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", 220 | "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" 221 | }, 222 | "minimist": { 223 | "version": "1.2.5", 224 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 225 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 226 | }, 227 | "mkdirp-classic": { 228 | "version": "0.5.3", 229 | "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", 230 | "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" 231 | }, 232 | "napi-build-utils": { 233 | "version": "1.0.2", 234 | "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", 235 | "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" 236 | }, 237 | "node-abi": { 238 | "version": "2.19.3", 239 | "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.19.3.tgz", 240 | "integrity": "sha512-9xZrlyfvKhWme2EXFKQhZRp1yNWT/uI1luYPr3sFl+H4keYY4xR+1jO7mvTTijIsHf1M+QDe9uWuKeEpLInIlg==", 241 | "requires": { 242 | "semver": "^5.4.1" 243 | }, 244 | "dependencies": { 245 | "semver": { 246 | "version": "5.7.1", 247 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 248 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 249 | } 250 | } 251 | }, 252 | "node-addon-api": { 253 | "version": "3.0.2", 254 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.2.tgz", 255 | "integrity": "sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg==" 256 | }, 257 | "noop-logger": { 258 | "version": "0.1.1", 259 | "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", 260 | "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" 261 | }, 262 | "npmlog": { 263 | "version": "4.1.2", 264 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 265 | "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 266 | "requires": { 267 | "are-we-there-yet": "~1.1.2", 268 | "console-control-strings": "~1.1.0", 269 | "gauge": "~2.7.3", 270 | "set-blocking": "~2.0.0" 271 | } 272 | }, 273 | "number-is-nan": { 274 | "version": "1.0.1", 275 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 276 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 277 | }, 278 | "object-assign": { 279 | "version": "4.1.1", 280 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 281 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 282 | }, 283 | "once": { 284 | "version": "1.4.0", 285 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 286 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 287 | "requires": { 288 | "wrappy": "1" 289 | } 290 | }, 291 | "prebuild-install": { 292 | "version": "6.0.0", 293 | "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.0.0.tgz", 294 | "integrity": "sha512-h2ZJ1PXHKWZpp1caLw0oX9sagVpL2YTk+ZwInQbQ3QqNd4J03O6MpFNmMTJlkfgPENWqe5kP0WjQLqz5OjLfsw==", 295 | "requires": { 296 | "detect-libc": "^1.0.3", 297 | "expand-template": "^2.0.3", 298 | "github-from-package": "0.0.0", 299 | "minimist": "^1.2.3", 300 | "mkdirp-classic": "^0.5.3", 301 | "napi-build-utils": "^1.0.1", 302 | "node-abi": "^2.7.0", 303 | "noop-logger": "^0.1.1", 304 | "npmlog": "^4.0.1", 305 | "pump": "^3.0.0", 306 | "rc": "^1.2.7", 307 | "simple-get": "^3.0.3", 308 | "tar-fs": "^2.0.0", 309 | "tunnel-agent": "^0.6.0", 310 | "which-pm-runs": "^1.0.0" 311 | }, 312 | "dependencies": { 313 | "simple-get": { 314 | "version": "3.1.0", 315 | "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", 316 | "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", 317 | "requires": { 318 | "decompress-response": "^4.2.0", 319 | "once": "^1.3.1", 320 | "simple-concat": "^1.0.0" 321 | } 322 | } 323 | } 324 | }, 325 | "process-nextick-args": { 326 | "version": "2.0.1", 327 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 328 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 329 | }, 330 | "pump": { 331 | "version": "3.0.0", 332 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 333 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 334 | "requires": { 335 | "end-of-stream": "^1.1.0", 336 | "once": "^1.3.1" 337 | } 338 | }, 339 | "rc": { 340 | "version": "1.2.8", 341 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 342 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 343 | "requires": { 344 | "deep-extend": "^0.6.0", 345 | "ini": "~1.3.0", 346 | "minimist": "^1.2.0", 347 | "strip-json-comments": "~2.0.1" 348 | } 349 | }, 350 | "readable-stream": { 351 | "version": "2.3.7", 352 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 353 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 354 | "requires": { 355 | "core-util-is": "~1.0.0", 356 | "inherits": "~2.0.3", 357 | "isarray": "~1.0.0", 358 | "process-nextick-args": "~2.0.0", 359 | "safe-buffer": "~5.1.1", 360 | "string_decoder": "~1.1.1", 361 | "util-deprecate": "~1.0.1" 362 | } 363 | }, 364 | "safe-buffer": { 365 | "version": "5.1.2", 366 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 367 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 368 | }, 369 | "semver": { 370 | "version": "7.3.2", 371 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", 372 | "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" 373 | }, 374 | "serverless-plugin-tracing": { 375 | "version": "2.0.0", 376 | "resolved": "https://registry.npmjs.org/serverless-plugin-tracing/-/serverless-plugin-tracing-2.0.0.tgz", 377 | "integrity": "sha1-32uLMWasm7cKN8f8h1AUsjaRWPY=" 378 | }, 379 | "set-blocking": { 380 | "version": "2.0.0", 381 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 382 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 383 | }, 384 | "sharp": { 385 | "version": "0.26.3", 386 | "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.26.3.tgz", 387 | "integrity": "sha512-NdEJ9S6AMr8Px0zgtFo1TJjMK/ROMU92MkDtYn2BBrDjIx3YfH9TUyGdzPC+I/L619GeYQc690Vbaxc5FPCCWg==", 388 | "requires": { 389 | "array-flatten": "^3.0.0", 390 | "color": "^3.1.3", 391 | "detect-libc": "^1.0.3", 392 | "node-addon-api": "^3.0.2", 393 | "npmlog": "^4.1.2", 394 | "prebuild-install": "^6.0.0", 395 | "semver": "^7.3.2", 396 | "simple-get": "^4.0.0", 397 | "tar-fs": "^2.1.1", 398 | "tunnel-agent": "^0.6.0" 399 | } 400 | }, 401 | "signal-exit": { 402 | "version": "3.0.3", 403 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 404 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" 405 | }, 406 | "simple-concat": { 407 | "version": "1.0.1", 408 | "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", 409 | "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" 410 | }, 411 | "simple-get": { 412 | "version": "4.0.0", 413 | "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.0.tgz", 414 | "integrity": "sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==", 415 | "requires": { 416 | "decompress-response": "^6.0.0", 417 | "once": "^1.3.1", 418 | "simple-concat": "^1.0.0" 419 | }, 420 | "dependencies": { 421 | "decompress-response": { 422 | "version": "6.0.0", 423 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", 424 | "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", 425 | "requires": { 426 | "mimic-response": "^3.1.0" 427 | } 428 | }, 429 | "mimic-response": { 430 | "version": "3.1.0", 431 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", 432 | "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" 433 | } 434 | } 435 | }, 436 | "simple-swizzle": { 437 | "version": "0.2.2", 438 | "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", 439 | "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", 440 | "requires": { 441 | "is-arrayish": "^0.3.1" 442 | } 443 | }, 444 | "string-width": { 445 | "version": "1.0.2", 446 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 447 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 448 | "requires": { 449 | "code-point-at": "^1.0.0", 450 | "is-fullwidth-code-point": "^1.0.0", 451 | "strip-ansi": "^3.0.0" 452 | } 453 | }, 454 | "string_decoder": { 455 | "version": "1.1.1", 456 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 457 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 458 | "requires": { 459 | "safe-buffer": "~5.1.0" 460 | } 461 | }, 462 | "strip-ansi": { 463 | "version": "3.0.1", 464 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 465 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 466 | "requires": { 467 | "ansi-regex": "^2.0.0" 468 | } 469 | }, 470 | "strip-json-comments": { 471 | "version": "2.0.1", 472 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 473 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" 474 | }, 475 | "tar-fs": { 476 | "version": "2.1.1", 477 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", 478 | "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", 479 | "requires": { 480 | "chownr": "^1.1.1", 481 | "mkdirp-classic": "^0.5.2", 482 | "pump": "^3.0.0", 483 | "tar-stream": "^2.1.4" 484 | } 485 | }, 486 | "tar-stream": { 487 | "version": "2.1.4", 488 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", 489 | "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", 490 | "requires": { 491 | "bl": "^4.0.3", 492 | "end-of-stream": "^1.4.1", 493 | "fs-constants": "^1.0.0", 494 | "inherits": "^2.0.3", 495 | "readable-stream": "^3.1.1" 496 | }, 497 | "dependencies": { 498 | "readable-stream": { 499 | "version": "3.6.0", 500 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 501 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 502 | "requires": { 503 | "inherits": "^2.0.3", 504 | "string_decoder": "^1.1.1", 505 | "util-deprecate": "^1.0.1" 506 | } 507 | } 508 | } 509 | }, 510 | "tunnel-agent": { 511 | "version": "0.6.0", 512 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 513 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 514 | "requires": { 515 | "safe-buffer": "^5.0.1" 516 | } 517 | }, 518 | "util-deprecate": { 519 | "version": "1.0.2", 520 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 521 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 522 | }, 523 | "which-pm-runs": { 524 | "version": "1.0.0", 525 | "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", 526 | "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" 527 | }, 528 | "wide-align": { 529 | "version": "1.1.3", 530 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 531 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 532 | "requires": { 533 | "string-width": "^1.0.2 || 2" 534 | } 535 | }, 536 | "wrappy": { 537 | "version": "1.0.2", 538 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 539 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 540 | } 541 | } 542 | } 543 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "image-resize", 3 | "version": "1.0.0", 4 | "description": "Serverless image resizing", 5 | "main": "resize.js", 6 | "dependencies": { 7 | "serverless-plugin-tracing": "^2.0.0", 8 | "sharp": "^0.26.3" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /functions/resize.js: -------------------------------------------------------------------------------- 1 | const stream = require('stream') 2 | const AWS = require('aws-sdk') 3 | const S3 = new AWS.S3({ 4 | signatureVersion: 'v4' 5 | }) 6 | const sharp = require('sharp') 7 | const BUCKET = process.env.BUCKET 8 | const URL = `http://${process.env.BUCKET}.s3-website.${process.env.REGION}.amazonaws.com` 9 | 10 | // create the read stream abstraction for downloading data from S3 11 | const readStreamFromS3 = ({ Bucket, Key }) => { 12 | return S3.getObject({ Bucket, Key }).createReadStream() 13 | } 14 | // create the write stream abstraction for uploading data to S3 15 | const writeStreamToS3 = ({ Bucket, Key }) => { 16 | const pass = new stream.PassThrough() 17 | return { 18 | writeStream: pass, 19 | uploadFinished: S3.upload({ 20 | Body: pass, 21 | Bucket, 22 | ContentType: 'image/png', 23 | Key 24 | }).promise() 25 | } 26 | } 27 | // sharp resize stream 28 | const streamToSharp = ({ width, height }) => { 29 | return sharp() 30 | .resize(width, height) 31 | .toFormat('png') 32 | } 33 | 34 | exports.handler = async (event) => { 35 | const key = event.queryStringParameters.key 36 | const match = key.match(/(\d+)x(\d+)\/(.*)/) 37 | const width = parseInt(match[1], 10) 38 | const height = parseInt(match[2], 10) 39 | const originalKey = match[3] 40 | const newKey = '' + width + 'x' + height + '/' + originalKey 41 | const imageLocation = `${URL}/${newKey}` 42 | 43 | try { 44 | // create the read and write streams from and to S3 and the Sharp resize stream 45 | const readStream = readStreamFromS3({ Bucket: BUCKET, Key: originalKey }) 46 | const resizeStream = streamToSharp({ width, height }) 47 | const { writeStream, uploadFinished } = writeStreamToS3({ Bucket: BUCKET, Key: newKey }) 48 | 49 | // trigger the stream 50 | readStream 51 | .pipe(resizeStream) 52 | .pipe(writeStream) 53 | 54 | // wait for the stream to finish 55 | const uploadedData = await uploadFinished 56 | 57 | // log data to Dashbird 58 | console.log('Data: ', { 59 | ...uploadedData, 60 | BucketEndpoint: URL, 61 | ImageURL: imageLocation 62 | }) 63 | 64 | // return a 301 redirect to the newly created resource in S3 65 | return { 66 | statusCode: '301', 67 | headers: { 'location': imageLocation }, 68 | body: '' 69 | } 70 | } catch (err) { 71 | console.error(err) 72 | return { 73 | statusCode: '500', 74 | body: err.message 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /functions/serverless.yml: -------------------------------------------------------------------------------- 1 | service: image-resize-on-the-fly-functions 2 | 3 | plugins: 4 | - serverless-plugin-tracing 5 | 6 | provider: 7 | name: aws 8 | runtime: nodejs12.x 9 | stage: ${env:STAGE, 'dev'} 10 | profile: ${env:PROFILE, 'default'} 11 | tracing: true 12 | region: ${env:REGION, 'us-east-1'} 13 | environment: 14 | BUCKET: ${env:BUCKET} 15 | REGION: ${env:REGION} 16 | iamRoleStatements: 17 | - Effect: "Allow" 18 | Action: 19 | - "s3:ListBucket" 20 | Resource: "arn:aws:s3:::${env:BUCKET}" 21 | - Effect: "Allow" 22 | Action: 23 | - "s3:PutObject" 24 | Resource: "arn:aws:s3:::${env:BUCKET}" 25 | - Effect: "Allow" 26 | Action: 27 | - "xray:PutTraceSegments" 28 | - "xray:PutTelemetryRecords" 29 | Resource: 30 | - "*" 31 | 32 | functions: 33 | resize: 34 | handler: resize.handler 35 | events: 36 | - http: 37 | path: resize 38 | method: get -------------------------------------------------------------------------------- /secrets/sample.secrets.env: -------------------------------------------------------------------------------- 1 | SLS_KEY=XXX 2 | SLS_SECRET=YYY 3 | STAGE=dev 4 | REGION=us-east-1 5 | BUCKET=images.your-domain.com --------------------------------------------------------------------------------