├── .gitignore ├── README.md ├── awsm.json ├── awsm └── thumbnail │ ├── awsm.json │ ├── event.json │ ├── handler.js │ └── index.js ├── index.js ├── lib ├── resize.js └── utils.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | dist 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 29 | node_modules 30 | 31 | #IDE Stuff 32 | **/.idea 33 | 34 | #OS STUFF 35 | .DS_Store 36 | .tmp 37 | 38 | #JAWS STUFF 39 | admin.env 40 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # awsm-images 2 | 3 | This awsm does image modifications (resizing, etc.) and showcases many of the features and integration points of JAWS: 4 | 5 | * browserify optimization 6 | * including own version of the `aws-sdk` 7 | * ENV var integration into code and exposing via the `awsm.json:lambda.envVars` extension point 8 | * Leverage awsm CloudFormation extension points (creates s3 bucket AND sets up fine grain perms) 9 | 10 | ## Quickstart 11 | 12 | Goal: Given image url, creates a thumbnail and saves to s3 bucket with the `JAWS_DATA_MODEL_STAGE` prefix (default jaws env var) and an env var defined by this awsm (`IMAGE_RESIZE_BUCKET`) 13 | 14 | 1. Create a new jaws project. Commit your files to version control so we can do diff later (trust me this is worth it) 15 | 1. Run `npm install --save awsm-org/awsm-images` 16 | 1. This downloads the module, installs its dependencies (via npm), replicates the AWSM module to the `aws_modules` directory, merges in CF resources to project `resources-cf.json`, merges in CF lambda IAM rules to project `resources-cf.json`. Cool right? 17 | 1. Run `jaws env list all` You will notice how `IMAGE_RESIZE_BUCKET` is not set. 18 | 1. Run `jaws env set all IMAGE_RESIZE_BUCKET ` 19 | 1. Cd into `aws_modules/awsm-images/thumbnail` and run `jaws deploy lambda`. Note how small the lambda code size is ;). Most of the size is the aws-sdk that [can't currently be browserified](https://github.com/aws/aws-sdk-js/issues/696). 20 | 1. Find an image that is > 100x100 and test the lambda sending json `{"url":"url here"}` 21 | -------------------------------------------------------------------------------- /awsm.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awsm-images", 3 | "version": "0.0.1", 4 | "location": "https://github.com/awsm-org/awsm-images", 5 | "author": "Ryan Pendergast ", 6 | "description": "Given image url, resize and save to s3", 7 | "resources": { 8 | "cloudFormation": { 9 | "LambdaIamPolicyDocumentStatements": [ 10 | { 11 | "Effect": "Allow", 12 | "Action": [ 13 | "s3:PutObject", 14 | "s3:PutObjectAcl" 15 | ], 16 | "Resource": [ 17 | { 18 | "Fn::Join": [ 19 | "", 20 | [ 21 | "arn:aws:s3:::", 22 | "imgresize.", 23 | { 24 | "Ref": "aaProjectDomain" 25 | }, 26 | "/", 27 | { 28 | "Ref": "aaDataModelStage" 29 | }, 30 | "*" 31 | ] 32 | ] 33 | } 34 | ] 35 | } 36 | ], 37 | "ApiGatewayIamPolicyDocumentStatements": [], 38 | "Resources": { 39 | "ImgS3Bucket": { 40 | "Type": "AWS::S3::Bucket", 41 | "DeletionPolicy": "Delete", 42 | "Properties": { 43 | "AccessControl": "PublicRead", 44 | "BucketName": { 45 | "Fn::Join": [ 46 | ".", 47 | [ 48 | "imgresize", 49 | { 50 | "Ref": "aaProjectDomain" 51 | } 52 | ] 53 | ] 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /awsm/thumbnail/awsm.json: -------------------------------------------------------------------------------- 1 | { 2 | "lambda": { 3 | "envVars": [ 4 | "IMAGE_RESIZE_BUCKET", 5 | "JAWS_DATA_MODEL_STAGE" 6 | ], 7 | "deploy": true, 8 | "package": { 9 | "optimize": { 10 | "builder": "browserify", 11 | "minify": true, 12 | "ignore": [], 13 | "exclude": [ 14 | "aws-sdk", 15 | "gm" 16 | ], 17 | "includePaths": [ 18 | "node_modules/awsm-images/node_modules/gm", 19 | "node_modules/awsm-images/node_modules/aws-sdk" 20 | ] 21 | }, 22 | "excludePatterns": [] 23 | }, 24 | "cloudFormation": { 25 | "Description": "", 26 | "Handler": "aws_modules/awsm-images/thumbnail/handler.handler", 27 | "MemorySize": 1024, 28 | "Runtime": "nodejs", 29 | "Timeout": 6 30 | } 31 | }, 32 | "apiGateway": { 33 | "cloudFormation": { 34 | "Type": "AWS", 35 | "Path": "images/thumbnail", 36 | "Method": "GET", 37 | "AuthorizationType": "none", 38 | "ApiKeyRequired": false, 39 | "RequestTemplates": { 40 | "application/json": "{\"url\":\"$util.urlDecode($input.params('url'))\"}" 41 | }, 42 | "RequestParameters": { 43 | "integration.request.querystring.integrationQueryParam": "method.request.querystring.url" 44 | }, 45 | "CacheNamespace": "String", 46 | "CacheKeyParameters": [], 47 | "Responses": { 48 | "default": { 49 | "statusCode": "200", 50 | "responseParameters": { 51 | }, 52 | "responseTemplates": { 53 | "application/json": "" 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /awsm/thumbnail/event.json: -------------------------------------------------------------------------------- 1 | {"url":"http://talkincloud.com/site-files/talkincloud.com/files/uploads/2014/08/jeff-barr-amazon-web-services.jpg"} 2 | -------------------------------------------------------------------------------- /awsm/thumbnail/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * AWS Module: Action: Lambda Handler 5 | * "Your lambda functions should be a thin wrapper around your own separate 6 | * modules, to keep your code testable, reusable and AWS independent" 7 | */ 8 | 9 | require('jaws-core-js/env'); 10 | 11 | // Modularized Code 12 | var action = require('./index.js'); 13 | 14 | // Lambda Handler 15 | module.exports.handler = function(event, context) { 16 | action.run(event, context, function(error, result) { 17 | return context.done(error, result); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /awsm/thumbnail/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * !!!!!!!!!!!!!IMPORTANT!!!!!!!!!!! 5 | * 6 | * Code in awsm dir of an aws module is just skeletion/example code and CAN be modified by user who installed awsm. 7 | * 8 | * Code inside an aws module's 'awsm' dir is copied to project's aws_modules/ folder (during npm postinstall) 9 | * 10 | * Therefore, npm 'dependencies' defined in the module you are creating CAN NOT be used here as node require loader wont 11 | * be able to find it. 12 | * 13 | * For example if awsm-images had a 'dependency' of bluebird defined in its package.json, bluebird could NOT be used 14 | * in this file. This file will live in /aws_modules/awsm-images/thumbnail/index.js and the bluebird mod will 15 | * be included in /node_modules/awsm-images/node_modules/bluebird. 16 | * 17 | * In npm v3 dependencies were flattened (https://github.com/npm/npm/releases/tag/v3.0.0) HOWEVER you can still NOT 18 | * rely on skeletion/example having access to a package the module you are creating depends on because there could be 19 | * conflicting dependencies (and then your dependency wont be on the flattened path). 20 | * 21 | * Keep in mind awsm nodejs mods are just npm modules and should be treated as such. 22 | */ 23 | 24 | var ir = require('awsm-images').resize; 25 | 26 | /** 27 | * Export for lambda handler 28 | * @param event 29 | * @param context 30 | * @param cb 31 | */ 32 | exports.run = function(event, context, cb) { 33 | if (!event.url) { 34 | return cb(new Error('Missing url parameter')); 35 | } 36 | 37 | if (!process.env.IMAGE_RESIZE_BUCKET) { 38 | return cb(new Error('IMAGE_RESIZE_BUCKET env var not set')); 39 | } 40 | 41 | ir.thumbnail(event.url) 42 | .then(function(s3HttpsUrl) { 43 | return cb(null, {thumbUrl: s3HttpsUrl}); 44 | }) 45 | .catch(function(e) { 46 | return cb(e); 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var r = require('./lib/resize.js'); 4 | 5 | exports.resize = r; 6 | -------------------------------------------------------------------------------- /lib/resize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Promise = require('bluebird'), 4 | rp = require('request-promise'), 5 | gm = require('gm').subClass({imageMagick: true}), 6 | path = require('path'), 7 | tmpFile = path.join(require('os').tmpdir(), 'overwrite'), 8 | fs = require('fs'), 9 | utils = require('./utils.js'); 10 | 11 | Promise.promisifyAll(gm.prototype); 12 | 13 | /** 14 | * Creates a thumbnail from URL to image 15 | * 16 | * @param url 17 | * @returns {Promose} node Buffer of thumbnail 18 | */ 19 | exports.thumbnail = Promise.method(function(url) { 20 | return rp({ 21 | uri: url, 22 | encoding: null, 23 | }) 24 | .then(function(body) { 25 | //gm.thumb forces u to output file :( 26 | var i = gm(body); 27 | return Promise.all([i, i.thumbAsync(100, 100, tmpFile, 85, 'center')]); 28 | }) 29 | .spread(function(img) { 30 | return fs.readFileSync(tmpFile); 31 | }) 32 | .then(function(imgBuffer) { 33 | return utils.uploadToS3(imgBuffer); 34 | }); 35 | }); 36 | 37 | exports.scale = Promise.method(function(url, targetW, targetH) { 38 | //TODO: implement 39 | }); 40 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var AWS = require('aws-sdk'), 4 | Promise = require('bluebird'), 5 | s3 = Promise.promisifyAll(new AWS.S3()), 6 | shortid = require('shortid'); 7 | 8 | var imgBucket = process.env.IMAGE_RESIZE_BUCKET; 9 | 10 | console.log('Using AWS SDK ver: ' + require('aws-sdk/package.json').version); 11 | 12 | /** 13 | * Take image buffer and upload it to the s3 image bucket 14 | * 15 | * @param imgBuffer 16 | * @returns {Promise} full https path to uploaded image 17 | */ 18 | exports.uploadToS3 = function(imgBuffer) { 19 | var key = [process.env.JAWS_DATA_MODEL_STAGE, shortid.generate() + '.jpg'].join('/'), 20 | params = { 21 | Bucket: imgBucket, 22 | Key: key, 23 | ACL: 'public-read', 24 | Body: imgBuffer, 25 | ContentType: 'image/jpeg' 26 | }; 27 | 28 | return s3.putObjectAsync(params) 29 | .then(function() { 30 | var s3HttpsUrl = 'https://' + s3.endpoint.hostname + '/' + imgBucket + '/' + key; 31 | 32 | return s3HttpsUrl; 33 | }) 34 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awsm-images", 3 | "version": "0.0.1", 4 | "description": "Given image url, resize and save to s3", 5 | "author": "Ryan Pendergast ", 6 | "license": "", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/awsm-org/awsm-images" 10 | }, 11 | "keywords": [], 12 | "scripts": { 13 | "postinstall" : "jaws postinstall awsm-images npm" 14 | }, 15 | "devDependencies": {}, 16 | "dependencies": { 17 | "aws-sdk": "^2.2.4", 18 | "bluebird": "^2.10.1", 19 | "gm": "^1.20.0", 20 | "request-promise": "^0.4.3", 21 | "shortid": "^2.2.2" 22 | } 23 | } 24 | --------------------------------------------------------------------------------