├── .gitignore ├── LICENSE ├── README.md ├── app ├── Gemfile ├── Gemfile.lock └── app.rb ├── deploy.sh ├── index.js └── packaging ├── bundler-config └── wrapper.sh /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | packaging/vendor/* 3 | packaging/*.tar.gz 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Loren Norman 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruby on AWS Lambda 2 | 3 | ## Why? 4 | 5 | Because: 6 | 7 | - AWS Lambda has immense potential and interesting implications 8 | - AWS Lambda doesn't officially support Ruby, as yet 9 | - Ruby has immense potential and interesting implications as well! 10 | 11 | ## How? 12 | 13 | By: 14 | 15 | - packaging a Ruby binary _with our app_ 16 | - thank you, [Traveling Ruby!](http://phusion.github.io/traveling-ruby/) 17 | - Lambda executes a simple NodeJS program that shells out to our code 18 | - see `index.js` 19 | - Ruby executes your code 20 | - yes, Bundler works! *(within reason)* 21 | - Hello World is about 8MB, leaving you 42MB of room to get crazy 22 | - (no native gems) 23 | 24 | ## Usage 25 | 26 | 1. Sign up for Amazon AWS 27 | 2. Get access to Amazon S3 28 | 3. Get access to Amazon Lambda 29 | 4. Create an IAM account with access to S3 and Lambda 30 | 5. Install the aws cli tools 31 | 6. Set up an AWS profile in `~/.aws/credentials` for the account created above 32 | 7. Change the variables in `deploy.sh` to match your app and AWS settings 33 | 8. Ensure you're using ruby 2.1.x in this directory 34 | 9. Ensure you're using Bundler version 1.9.9 35 | 10. Add any gems you need to app/Gemfile 36 | 11. `cd app` and run `bundle install` from there (you need a Gemfile.lock) 37 | 12. edit `app/app.rb` with your application code 38 | 13. run `./deploy.sh linux-x86_64` 39 | 14. test your Lambda Function! 40 | - i've been using the web console for testing 41 | -------------------------------------------------------------------------------- /app/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | -------------------------------------------------------------------------------- /app/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | 5 | PLATFORMS 6 | ruby 7 | 8 | DEPENDENCIES 9 | -------------------------------------------------------------------------------- /app/app.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | puts "event: #{ARGV[0]}" 3 | puts "context: #{ARGV[1]}" 4 | 5 | puts "To: Lambda," 6 | puts "From: Ruby!" 7 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # Your Ruby app's name 6 | APP_NAME="hello-world" 7 | # Your app's version, increment before deploys! 8 | APP_VERSION="1.0.0" 9 | TRAVELING_RUBY_VERSION="20150210-2.1.5" 10 | 11 | # Set this up in your ~/.aws/credentials 12 | # Use a profile that has the ability to put 13 | # objects on S3 and update Lambda functions 14 | AWS_PROFILE="user-with-s3-and-lambda-privileges" 15 | # S3 Bucket where Lambda will find the Deployment Package 16 | AWS_BUCKET="your.bucket.net" 17 | # The S3 Key of the Deployment Package zip file 18 | AWS_KEY="lambda-functions/$APP_NAME-$APP_VERSION.zip" 19 | # The Lambda Function name, created already 20 | AWS_LAMBDA_FUNCTION="yourLambdaFunctionName" 21 | 22 | ######################## 23 | ### Helper Functions ### 24 | ######################## 25 | 26 | banner() 27 | { 28 | echo "\n*****************" 29 | echo "*** $1 ***" 30 | echo "*****************\n" 31 | } 32 | 33 | validate_os() 34 | { 35 | target_os=$1 36 | echo "Validating OS \"$target_os\"..." 37 | 38 | if [ "$target_os" = "osx" -o "$target_os" = "linux-x86" -o "$target_os" = "linux-x86_64" ]; then 39 | echo "OS valid." 40 | else 41 | echo "No such target os!" 42 | echo "Must be one of 'osx', 'linux-x86', 'linux-x86_64'." 43 | exit 1 44 | fi 45 | } 46 | 47 | traveling_ruby_filename() 48 | { 49 | target_os=$1 50 | echo "traveling-ruby-$TRAVELING_RUBY_VERSION-$target_os.tar.gz" 51 | } 52 | 53 | download_runtime() 54 | { 55 | target_os=$1 56 | filename=$( traveling_ruby_filename $target_os ) 57 | 58 | pushd packaging 59 | echo "Checking for file:\n $filename" 60 | 61 | if [ -f $filename ] ; then 62 | echo "...already have it." 63 | 64 | else 65 | echo "...doesn't exist, downloading." 66 | 67 | traveling_ruby_url="http://d6r77u77i8pq3.cloudfront.net/releases/$filename" 68 | curl_command="curl -L -O --fail $traveling_ruby_url" 69 | 70 | if ! $curl_command ; then 71 | echo "Failure attempting to download url:\n $traveling_ruby_url" 72 | exit 1 73 | fi 74 | fi 75 | popd 76 | } 77 | 78 | 79 | ##################### 80 | ### Deploy Script ### 81 | ##################### 82 | 83 | banner "Gathering Info" 84 | 85 | target_os=$1 86 | validate_os $target_os 87 | 88 | 89 | banner "Creating Build" 90 | 91 | # Clean build dir 92 | rm -rf build 93 | mkdir build 94 | 95 | # Create package dir skeleton 96 | package_dir="build/$APP_NAME-$APP_VERSION-$target_os" 97 | lib_dir="$package_dir/lib" 98 | mkdir -p $lib_dir 99 | 100 | # Copy in the app 101 | cp -R app $lib_dir 102 | 103 | # Uncompress the appropriate Ruby into it 104 | download_runtime $target_os 105 | traveling_ruby_path=$( traveling_ruby_filename $target_os ) 106 | mkdir "$package_dir/lib/ruby" 107 | tar -xzf "packaging/$traveling_ruby_path" -C "$package_dir/lib/ruby" 108 | 109 | 110 | banner "Bundling" 111 | 112 | # Copy in Bundler and gems 113 | mkdir -p packaging/tmp 114 | cp app/Gemfile* packaging/tmp/ 115 | pushd packaging/tmp 116 | BUNDLE_IGNORE_CONFIG=1 bundle install --path ../vendor --without development 117 | popd 118 | rm -rf packaging/tmp 119 | rm -rf packaging/vendor/*/*/cache/* 120 | cp -pR packaging/vendor $lib_dir 121 | cp app/Gemfile* $lib_dir/vendor/ 122 | 123 | mkdir $lib_dir/vendor/.bundle 124 | cp packaging/bundler-config $lib_dir/vendor/.bundle/config 125 | cp packaging/wrapper.sh $package_dir/app 126 | 127 | 128 | banner "Zipping for Lambda" 129 | 130 | # Add Lambda wrapper and zip it all up for deploy 131 | cp index.js $package_dir 132 | pushd $package_dir 133 | find . | zip "../$APP_NAME-$APP_VERSION-$target_os.zip" -@ 134 | package_zip="$package_dir.zip" 135 | popd 136 | 137 | # Clean up files 138 | rm -rf $package_dir 139 | 140 | 141 | banner "Copying to S3" 142 | aws s3api put-object --bucket $AWS_BUCKET --key $AWS_KEY --body $package_zip --profile $AWS_PROFILE 143 | 144 | 145 | banner "Updating Lambda" 146 | aws lambda update-function-code --function-name $AWS_LAMBDA_FUNCTION --s3-bucket $AWS_BUCKET --s3-key $AWS_KEY --profile $AWS_PROFILE 147 | 148 | 149 | banner "Done!" 150 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var spawn = require('child_process').spawn; 2 | 3 | var invokeRubyApp = "./app"; 4 | 5 | exports.handler = function(event, context) { 6 | console.log("Starting process: " + invokeRubyApp); 7 | var child = spawn(invokeRubyApp, [JSON.stringify(event, null, 2), JSON.stringify(context, null, 2)]); 8 | 9 | child.stdout.on('data', function (data) { console.log("stdout:\n"+data); }); 10 | child.stderr.on('data', function (data) { console.log("stderr:\n"+data); }); 11 | 12 | child.on('close', function (code) { 13 | if(code === 0) { 14 | context.succeed("Process completed: " + invokeRubyApp); 15 | } else { 16 | context.fail("Process \"" + invokeRubyApp + "\" exited with code: " + code); 17 | } 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /packaging/bundler-config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: . 2 | BUNDLE_WITHOUT: development 3 | BUNDLE_DISABLE_SHARED_GEMS: '1' 4 | -------------------------------------------------------------------------------- /packaging/wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Figure out where this script is located. 5 | SELFDIR="`dirname \"$0\"`" 6 | SELFDIR="`cd \"$SELFDIR\" && pwd`" 7 | 8 | # Tell Bundler where the Gemfile and gems are. 9 | export BUNDLE_GEMFILE="$SELFDIR/lib/vendor/Gemfile" 10 | unset BUNDLE_IGNORE_CONFIG 11 | 12 | # Run the actual app using the bundled Ruby interpreter, with Bundler activated. 13 | exec "$SELFDIR/lib/ruby/bin/ruby" -rbundler/setup "$SELFDIR/lib/app/app.rb" "$@" 14 | --------------------------------------------------------------------------------