lam 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +107 -0
  7. data/Guardfile +22 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +37 -0
  10. data/Rakefile +6 -0
  11. data/bin/lam +14 -0
  12. data/lam.gemspec +32 -0
  13. data/lib/lam.rb +14 -0
  14. data/lib/lam/base_controller.rb +54 -0
  15. data/lib/lam/build.rb +43 -0
  16. data/lib/lam/build/handler_generator.rb +34 -0
  17. data/lib/lam/build/lambda_deducer.rb +47 -0
  18. data/lib/lam/build/templates/handler.js +156 -0
  19. data/lib/lam/build/traveling_ruby.rb +108 -0
  20. data/lib/lam/cli.rb +23 -0
  21. data/lib/lam/cli/help.rb +19 -0
  22. data/lib/lam/command.rb +25 -0
  23. data/lib/lam/process.rb +18 -0
  24. data/lib/lam/process/base_processor.rb +23 -0
  25. data/lib/lam/process/controller_processor.rb +36 -0
  26. data/lib/lam/process/help.rb +11 -0
  27. data/lib/lam/process/processor_deducer.rb +52 -0
  28. data/lib/lam/util.rb +13 -0
  29. data/lib/lam/version.rb +3 -0
  30. data/notes/design.md +43 -0
  31. data/notes/traveling-ruby-packaging-lam.md +26 -0
  32. data/notes/traveling-ruby-packaging.md +103 -0
  33. data/notes/traveling-ruby.md +82 -0
  34. data/spec/fixtures/project/.gitignore +3 -0
  35. data/spec/fixtures/project/.ruby-version +1 -0
  36. data/spec/fixtures/project/Gemfile +4 -0
  37. data/spec/fixtures/project/Gemfile.lock +35 -0
  38. data/spec/fixtures/project/app/controllers/application_controller.rb +2 -0
  39. data/spec/fixtures/project/app/controllers/posts_controller.rb +12 -0
  40. data/spec/fixtures/project/bin/lam +22 -0
  41. data/spec/fixtures/project/handlers/controllers/posts.js +156 -0
  42. data/spec/lib/cli_spec.rb +20 -0
  43. data/spec/lib/lam/base_controller_spec.rb +18 -0
  44. data/spec/lib/lam/build/lambda_deducer_spec.rb +20 -0
  45. data/spec/lib/lam/build_spec.rb +29 -0
  46. data/spec/lib/lam/process/controller_processor_spec.rb +22 -0
  47. data/spec/lib/lam/process/infer_spec.rb +24 -0
  48. data/spec/lib/lam/process_spec.rb +18 -0
  49. data/spec/spec_helper.rb +25 -0
  50. metadata +249 -0
@@ -0,0 +1,13 @@
1
+ module Lam::Util
2
+ # Ensures trailing slash
3
+ # Useful for appending a './' in front of a path or leaving it alone.
4
+ # Returns: '/path/with/trailing/slash/' or './'
5
+ @@root = nil
6
+ def root
7
+ return @@root if @@root
8
+ @@root = ENV['PROJECT_ROOT'].to_s
9
+ @@root = '.' if @@root == ''
10
+ @@root = "#{@@root}/" unless @@root.ends_with?('/')
11
+ @@root
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Lam
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,43 @@
1
+ ## Project Structure
2
+
3
+ TODO: make this a table
4
+
5
+ ```sh
6
+ app/controllers
7
+ app/workers
8
+ app/functions
9
+ config/project.yml
10
+ config/events.yml
11
+ config/routes.rb
12
+ ```
13
+
14
+
15
+ ## Usage
16
+
17
+ ```sh
18
+ lam build
19
+ lam deploy
20
+ ```
21
+
22
+ ## Testing
23
+
24
+ Testing controller processing without node shim.
25
+
26
+ ```
27
+ lam process controller '{ "we" : "love", "using" : "Lambda" }' '{"test": "1"}' "handlers/controllers/posts.create"
28
+ ```
29
+
30
+ Testing the generated node shim handler and the controller processing.
31
+
32
+ ```
33
+ cd spec/fixtures/project
34
+ lam build # generates the handlers
35
+ node handlers/controllers/posts.js
36
+ ```
37
+
38
+ VS
39
+
40
+ ```sh
41
+ processors/controller_processor.rb '{ "we" : "love", "using" : "Lambda" }' '{"test": "1"}' "handlers/controllers/posts.create" | jq '.'
42
+ ```
43
+
@@ -0,0 +1,26 @@
1
+ # start off at project root
2
+
3
+ mkdir -p /tmp/lam_build
4
+ cp Gemfile* /tmp/lam_build/
5
+ cd /tmp/lam_build # cd into there to build TravelingRuby
6
+
7
+ wget http://d6r77u77i8pq3.cloudfront.net/releases/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz .
8
+ mkdir -p bundled/ruby # tmp/lam_build/bundled/ruby
9
+ tar -xvf traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz -C bundled/ruby
10
+ # ls ruby => bin bin.real info lib
11
+ # bundled/ruby/bin/ruby -v # works now :)
12
+
13
+ # had to modify Gemfile and update the local path for linux
14
+ bundle install --path bundled/gems
15
+
16
+ # DONT THINK THAT I NEED TO COPY THE Gemfile into bundled/gems...
17
+ # mv Gemfile* bundled/gems/ # copy Gemfile from the project to bundled/gems
18
+ # IMPORTANT: the Gemfile must be in the same bundled/gems folder
19
+
20
+ # now we have both bundled/gems bundled/ruby :)
21
+ bundled/gems/ruby/2.2.0/bin/print_ruby_info # should work
22
+
23
+
24
+ # Let's move back to the project and test the wrapper, it should work also
25
+ mv bundled ~/lam-test/lam/spec/fixtures/project/
26
+ cd ~/lam-test/lam/spec/fixtures/project # back to project root
@@ -0,0 +1,103 @@
1
+ ### Packaging Gems Info
2
+
3
+ ### Structure
4
+
5
+ This is the structure that TravelingRuby uses in it's tutorial.
6
+
7
+ ```sh
8
+ PROJECT/YOUR_BINARY_WRAPPER (hello)
9
+ PROJECT/lib/ruby/bin/ruby -> PROJECT/lib/ruby/bin.real/ruby
10
+ PROJECT/lib/vendor/ruby/2.2.0/bin/print_ruby_info # the gem binaries are here
11
+ ```
12
+
13
+ * Instead calling the ruby binary `lam process` command directly.
14
+ * Lam will require 'bundler/setup' so the user's gems will be required properly
15
+ * Skip the overhead of having another wrapper
16
+
17
+ ```sh
18
+ PROJECT/vendor/ruby/2.2.0/bin/lam # the gem binaries are here
19
+ ```
20
+
21
+ ### Packaging Gems Commands
22
+
23
+ ```
24
+ mkdir packaging/tmp
25
+ cp Gemfile Gemfile.lock packaging/tmp/ # this are from the user's project
26
+ cd packaging/tmp
27
+
28
+ # here's where the gems are instaleld into packaging/vendor/
29
+ BUNDLE_IGNORE_CONFIG=1 bundle install --path ../vendor --without development
30
+ # IMPORTANT: Think I'll make the user switch to 2.2.0 and error the build proccess.
31
+
32
+ ##############
33
+ I can call ruby bin files directly.
34
+ I was WRONG. Cannot call gem bin files directly because I need to make sure that
35
+ bundler/setup gets loaded before calling the gem bin.
36
+ Tried moving bundler/setup into the lam library itself but get all sorts of warnings.
37
+
38
+ hello-1.0.0-linux-x86_64/lib/vendor/ruby/2.2.0/bin/lam help
39
+ BUT the shabang line has: #!/usr/bin/env ruby2.0 .. but only on linux..
40
+ Simply cannot rename the darn ruby version folder.
41
+ #############
42
+
43
+
44
+ cd ../..
45
+ rm -rf packaging/tmp # remove working space
46
+
47
+ # reduce the zip package size!
48
+ rm -f packaging/vendor/*/*/cache/*
49
+ ```
50
+
51
+ Now we can copy over the generated vendored files
52
+
53
+ ##################################
54
+ # clean
55
+ rm -rf packaging/vendor/
56
+
57
+ bundle update
58
+ rake package:linux:x86_64 DIR_ONLY=1
59
+
60
+ mkdir packaging/tmp
61
+ cp Gemfile Gemfile.lock packaging/tmp/
62
+ cd packaging/tmp
63
+ BUNDLE_IGNORE_CONFIG=1 bundle install --path ../vendor --without development
64
+ cd ../..
65
+
66
+ cp -pR packaging/vendor hello-1.0.0-linux-x86_64/lib/
67
+ cp Gemfile Gemfile.lock hello-1.0.0-linux-x86_64/lib/vendor/
68
+ mkdir hello-1.0.0-linux-x86_64/lib/vendor/.bundle
69
+ cp packaging/bundler-config hello-1.0.0-linux-x86_64/lib/vendor/.bundle/config
70
+
71
+ find . -name print_ruby_info
72
+
73
+
74
+ # Wrapper script `lam`
75
+
76
+ ```bash
77
+ #!/bin/bash
78
+ set -e
79
+
80
+ # Figure out where this script is located.
81
+ SELFDIR="`dirname \"$0\"`"
82
+ SELFDIR="`cd \"$SELFDIR\" && pwd`"
83
+
84
+ # Tell Bundler where the Gemfile and gems are.
85
+ export BUNDLE_GEMFILE="$SELFDIR/lib/vendor/Gemfile"
86
+ unset BUNDLE_IGNORE_CONFIG
87
+
88
+ # Run the actual app using the bundled Ruby interpreter, with Bundler activated.
89
+ exec "$SELFDIR/lib/ruby/bin/ruby" -rbundler/setup "$SELFDIR/lib/app/hello.rb"
90
+ ```
91
+
92
+
93
+ ### Building Ruby
94
+ http://cache.ruby-lang.org/pub/ruby/2.2/
95
+
96
+ ```sh
97
+ wget http://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.2.tar.gz
98
+ tar xvfvz ruby-2.2.2.tar.gz
99
+ cd ruby-2.2.2
100
+ ./configure
101
+ make
102
+ sudo make install
103
+ ```
@@ -0,0 +1,82 @@
1
+ ## Overview
2
+
3
+ There are 2 things to point out:
4
+
5
+ 1. What the user's environment contains - cannot control this, can only suggest
6
+ 2. What the lambda environment contains - can fully control this
7
+
8
+ * User can develop with ruby 2.4 and bundle gems and test and be happy. Hopefully they eventually learn to test with ruby 2.2.0 (when they run into bugs).
9
+ * When `lam build` runs it will bundle it in ruby 2.2.0 though.
10
+ * When lambda runs it will call `lam` with ruby 2.2.0. The gems will be installed in 2.2.0
11
+ * Hope all works
12
+
13
+ ## Download ruby commands
14
+
15
+ ```sh
16
+ # linux 64bit
17
+ wget http://d6r77u77i8pq3.cloudfront.net/releases/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz .
18
+ mkdir ruby-linux
19
+ tar -xvf traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz -C ruby-linux
20
+ # mac
21
+ wget http://d6r77u77i8pq3.cloudfront.net/releases/traveling-ruby-20150715-2.2.2-osx.tar.gz
22
+ mkdir ruby-mac
23
+ tar -xvf traveling-ruby-20150715-2.2.2-osx.tar.gz -C ruby-mac
24
+
25
+ # another version
26
+ mkdir hello-1.0.0-linux-x86_64/lib/ruby && tar -xzf packaging/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz -C hello-1.0.0-linux-x86_64/lib/ruby
27
+ ```
28
+
29
+ ## Launch Instance for Testing
30
+
31
+ ```sh
32
+ aws ec2 run-instances --image-id ami-8c1be5f6 --count 1 --instance-type t2.micro --key-name default --security-groups demo
33
+ ```
34
+
35
+ ## Tree Structure
36
+
37
+ ```sh
38
+ lib/
39
+ ├── app
40
+ │   └── hello.rb
41
+ ├── ruby
42
+ │   ├── bin
43
+ │   │   ├── gem
44
+ │   │   ├── irb
45
+ │   │   ├── rake
46
+ │   │   ├── ruby
47
+ │   │   └── ruby_environment
48
+ │   ├── bin.real
49
+ │   │   ├── bundle
50
+ │   │   ├── bundler
51
+ │   │   ├── gem
52
+ │   │   ├── irb
53
+ │   │   ├── rake
54
+ │   │   └── ruby
55
+ │   └── lib
56
+ │   └── ruby
57
+ │   ├── 2.2.0
58
+ ...
59
+ │   │   └── yaml.rb
60
+ │   ├── gems
61
+ │   │   └── 2.2.0
62
+ │   │   ├── gems
63
+ │   │   │   ├── attr_extras-5.2.0
64
+ │   │   │   │   ├── attr_extras.gemspec
65
+ ...
66
+ │   │   │   ├── bundler-1.9.9
67
+ ...
68
+ │   │   └── rake-12.2.1.gemspec
69
+ ...
70
+ └── vendor
71
+ ├── Gemfile
72
+ ├── Gemfile.lock
73
+ └── ruby
74
+ └── 2.2.0 <= IMPORTANT: bundler copies gems to folder with running ruby version
75
+ ├── gems
76
+ │   ├── concurrent-ruby-1.0.5
77
+ │   ├── faker-1.7.3
78
+ │   └── i18n-0.9.0
79
+ ...
80
+ ```
81
+
82
+ * [Full tree](https://gist.github.com/tongueroo/c42f9d35b15b06eb810802243f4e2f6d)
@@ -0,0 +1,3 @@
1
+ vendor
2
+ bundled
3
+ lam.zip
@@ -0,0 +1 @@
1
+ 2.2.2
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "lam", path: "/Users/tung/src/tongueroo/lam"
4
+ gem "print_ruby_info" # test
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: /Users/tung/src/tongueroo/lam
3
+ specs:
4
+ lam (0.0.1)
5
+ activesupport
6
+ colorize
7
+ hashie
8
+ thor
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ activesupport (5.1.4)
14
+ concurrent-ruby (~> 1.0, >= 1.0.2)
15
+ i18n (~> 0.7)
16
+ minitest (~> 5.1)
17
+ tzinfo (~> 1.1)
18
+ colorize (0.8.1)
19
+ concurrent-ruby (1.0.5)
20
+ hashie (3.5.6)
21
+ i18n (0.9.0)
22
+ concurrent-ruby (~> 1.0)
23
+ minitest (5.10.3)
24
+ print_ruby_info (0.0.3)
25
+ thor (0.20.0)
26
+ thread_safe (0.3.6)
27
+ tzinfo (1.2.3)
28
+ thread_safe (~> 0.1)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ lam!
35
+ print_ruby_info
@@ -0,0 +1,2 @@
1
+ class ApplicationController < Lam::BaseController
2
+ end
@@ -0,0 +1,12 @@
1
+ class PostsController < Lam::BaseController
2
+ def create
3
+ # render text: "test2" # more consistent for web controllers
4
+
5
+ # render returns Lamba Proxy struture for web requests
6
+ render json: event, status: 200
7
+ end
8
+
9
+ # def update
10
+ # render json: event, status: 200
11
+ # end
12
+ end
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ # Figure out where this script is located.
6
+ PROJECTDIR="`dirname \"$0\"`"
7
+ PROJECTDIR="`cd \"$PROJECTDIR/..\" && pwd`"
8
+
9
+ # Tell Bundler where the Gemfile and gems are.
10
+ # IMPORTANT: the Gemfile must be in the same bundled/gems folder
11
+ export BUNDLE_GEMFILE="$PROJECTDIR/Gemfile"
12
+ unset BUNDLE_IGNORE_CONFIG
13
+
14
+ # Run the actual app using the bundled Ruby interpreter, with Bundler activated.
15
+ os=$(uname)
16
+ if [[ "$os" == 'Darwin' ]]; then
17
+ # used macosx ruby for development
18
+ exec lam "$@"
19
+ else
20
+ # used bundled ruby for Lambda
21
+ exec "$PROJECTDIR/bundled/ruby/bin/ruby" -rbundler/setup "$PROJECTDIR/bundled/gems/ruby/2.2.0/bin/lam" "$@"
22
+ fi
@@ -0,0 +1,156 @@
1
+ 'use strict';
2
+
3
+ // handler: handlers/controllers/posts.create
4
+ const spawn = require('child_process').spawn;
5
+
6
+ // Once hooked up to API Gateway can use the curl command to test:
7
+ // curl -s -X POST -d @event.json https://endpoint | jq .
8
+
9
+ // Filters out lines so only the error lines remain.
10
+ // Uses the "RubyError: " marker to find the starting error lines.
11
+ //
12
+ // Input: String
13
+ // random line
14
+ // RubyError: RuntimeError: error in submethod
15
+ // line1
16
+ // line2
17
+ // line3
18
+ //
19
+ // Output: String
20
+ // RubyError: RuntimeError: error in submethod
21
+ // line1
22
+ // line2
23
+ // line3
24
+ function filterErrorLines(text) {
25
+ var lines = text.split("\n")
26
+ var markerIndex = lines.findIndex(line => line.startsWith("RubyError: ") )
27
+ lines = lines.filter((line, index) => index >= markerIndex )
28
+ return lines.join("\n")
29
+ }
30
+
31
+ // Produces an Error object that displays in the AWS Lambda test console nicely.
32
+ // The backtrace are the ruby lines, not the nodejs shim error lines.
33
+ // The json payload in the Lambda console looks something like this:
34
+ //
35
+ // {
36
+ // "errorMessage": "RubyError: RuntimeError: error in submethod",
37
+ // "errorType": "RubyError",
38
+ // "stackTrace": [
39
+ // [
40
+ // "line1",
41
+ // "line2",
42
+ // "line3"
43
+ // ]
44
+ // ]
45
+ // }
46
+ //
47
+ // Input: String
48
+ // RubyError: RuntimeError: error in submethod
49
+ // line1
50
+ // line2
51
+ // line3
52
+ //
53
+ // Output: Error object
54
+ // { RubyError: RuntimeError: error in submethod
55
+ // line1
56
+ // line2
57
+ // line3 name: 'RubyError' }
58
+ function customError(text) {
59
+ text = filterErrorLines(text) // filter for error lines only
60
+ var lines = text.split("\n")
61
+ var message = lines[0]
62
+ var error = new Error(message)
63
+ error.name = message.split(':')[0]
64
+ error.stack = lines.slice(0, lines.length-1) // drop final empty line
65
+ .map(e => e.replace(/^\s+/g,'')) // trim leading whitespaces
66
+ .join("\n")
67
+ return error
68
+ }
69
+
70
+ module.exports.create = (event, context, callback) => {
71
+ // To test on mac, set these environment variables:
72
+ // export RUBY_BIN=$HOME/.rbenv/shims/ruby
73
+ // export PROCESSOR_COMMAND="lam process controller"
74
+
75
+ // Command: lam process controller [event] [context] [handler]
76
+ const processor_command = process.env.PROCESSOR_COMMAND || "lam"
77
+ var args = [
78
+ "process",
79
+ "controller",
80
+ JSON.stringify(event),
81
+ JSON.stringify(context),
82
+ "handlers/controllers/posts.create"
83
+ ]
84
+ // console.log("processor_command %o", processor_command)
85
+ // console.log("args %o", args)
86
+
87
+ var ruby = spawn("bin/lam", args);
88
+
89
+ // string concatation in javascript is faster than array concatation
90
+ // http://bit.ly/2gBMDs6
91
+ var stdout_buffer = ""; // stdout buffer
92
+ // In the processor_command we do NOT call puts directly and write to stdout
93
+ // because it will mess up the eventual response that we want API Gateway to
94
+ // process.
95
+ // The Lambda prints out function to whatever the return value the ruby method
96
+ ruby.stdout.on('data', function(data) {
97
+ // Not using console.log because it decorates output with a newline.
98
+ //
99
+ // Uncomment process.stdout.write to see stdout streamed for debugging.
100
+ // process.stdout.write(data)
101
+ stdout_buffer += data;
102
+ });
103
+
104
+ // react to potential errors
105
+ var stderr_buffer = "";
106
+ ruby.stderr.on('data', function(data) {
107
+ // not using console.error because it decorates output with a newline
108
+ stderr_buffer += data
109
+ process.stderr.write(data)
110
+ });
111
+
112
+ //finalize when ruby process is done.
113
+ ruby.on('close', function(exit_code) {
114
+ // http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html#nodejs-prog-model-handler-callback
115
+
116
+ // succcess
117
+ if (exit_code == 0) {
118
+ var result
119
+ try {
120
+ result = JSON.parse(stdout_buffer)
121
+ } catch(e) {
122
+ // if json cannot be parse assume simple text output intended
123
+ process.stderr.write("WARN: error parsing json, assuming plain text is desired.")
124
+ result = stdout_buffer
125
+ }
126
+ callback(null, result);
127
+
128
+ // callback(null, stdout_buffer);
129
+ } else {
130
+
131
+ // TODO: if this works, allow a way to not decorate the error in case
132
+ // it actually errors in javascript land
133
+ // Customize error object with ruby error info
134
+ var error = customError(stderr_buffer)
135
+ callback(error);
136
+ // console.log("error!")
137
+ }
138
+ });
139
+ }
140
+
141
+ // for local testing
142
+ if (process.platform == "darwin") {
143
+ // fake event and context
144
+ var event = {"hello": "world"}
145
+ // var event = {"body": {"hello": "world"}} // API Gateway wrapper structure
146
+ var context = {"fake": "context"}
147
+ module.exports.create(event, context, (error, message) => {
148
+ console.error("\nLOCAL TESTING OUTPUT")
149
+ if (error) {
150
+ console.error("error message: %o", error)
151
+ } else {
152
+ console.error("success message %o", message)
153
+ // console.log(JSON.stringify(message)) // stringify
154
+ }
155
+ })
156
+ }