jets 0.0.1 → 0.2.0

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +7 -0
  4. data/Gemfile.lock +63 -0
  5. data/Guardfile +19 -9
  6. data/README.md +9 -16
  7. data/jets.gemspec +11 -7
  8. data/lib/jets.rb +11 -0
  9. data/lib/jets/base_controller.rb +54 -0
  10. data/lib/jets/build.rb +46 -0
  11. data/lib/jets/build/handler_generator.rb +46 -0
  12. data/lib/jets/build/lambda_deducer.rb +23 -0
  13. data/lib/jets/build/templates/handler.js +149 -0
  14. data/lib/jets/build/traveling_ruby.rb +133 -0
  15. data/lib/jets/cfn.rb +5 -0
  16. data/lib/jets/cfn/base.rb +17 -0
  17. data/lib/jets/cfn/builder.rb +53 -0
  18. data/lib/jets/cfn/namer.rb +30 -0
  19. data/lib/jets/cli.rb +10 -6
  20. data/lib/jets/cli/help.rb +8 -2
  21. data/lib/jets/process.rb +18 -0
  22. data/lib/jets/process/base_processor.rb +23 -0
  23. data/lib/jets/process/controller_processor.rb +36 -0
  24. data/lib/jets/process/help.rb +11 -0
  25. data/lib/jets/process/processor_deducer.rb +51 -0
  26. data/lib/jets/project.rb +23 -0
  27. data/lib/jets/util.rb +13 -0
  28. data/lib/jets/version.rb +1 -1
  29. data/notes/design.md +107 -0
  30. data/notes/faq.md +3 -0
  31. data/notes/lambda_ruby_info.md +34 -0
  32. data/notes/traveling-ruby-packaging-jets.md +26 -0
  33. data/notes/traveling-ruby-packaging.md +103 -0
  34. data/notes/traveling-ruby-structure.md +6 -0
  35. data/notes/traveling-ruby.md +82 -0
  36. data/spec/fixtures/classes.rb +5 -0
  37. data/spec/fixtures/project/.gitignore +3 -0
  38. data/spec/fixtures/project/.ruby-version +1 -0
  39. data/spec/fixtures/project/Gemfile +4 -0
  40. data/spec/fixtures/project/Gemfile.lock +23 -0
  41. data/spec/fixtures/project/app/controllers/application_controller.rb +2 -0
  42. data/spec/fixtures/project/app/controllers/posts_controller.rb +12 -0
  43. data/spec/fixtures/project/bin/jets +22 -0
  44. data/spec/fixtures/project/config/routes.rb +6 -0
  45. data/spec/fixtures/project/handlers/controllers/posts.js +212 -0
  46. data/spec/lib/cli_spec.rb +5 -4
  47. data/spec/lib/jets/base_controller_spec.rb +18 -0
  48. data/spec/lib/jets/build/handler_generator_spec.rb +20 -0
  49. data/spec/lib/jets/build/lambda_deducer_spec.rb +15 -0
  50. data/spec/lib/jets/build_spec.rb +34 -0
  51. data/spec/lib/jets/cfn/builder_spec.rb +18 -0
  52. data/spec/lib/jets/cfn/namer_spec.rb +16 -0
  53. data/spec/lib/jets/process/controller_processor_spec.rb +22 -0
  54. data/spec/lib/jets/process/infer_spec.rb +24 -0
  55. data/spec/lib/jets/process_spec.rb +18 -0
  56. data/spec/lib/jets/project_spec.rb +14 -0
  57. data/spec/spec_helper.rb +8 -2
  58. metadata +82 -15
@@ -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/bundled/gems/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
+ # macosx ruby for development
18
+ exec jets "$@"
19
+ else
20
+ # bundled ruby for Lambda
21
+ exec "$PROJECTDIR/bundled/ruby/bin/ruby" -rbundler/setup "$PROJECTDIR/bundled/gems/ruby/2.2.0/bin/jets" "$@"
22
+ fi
@@ -0,0 +1,6 @@
1
+ get "posts/:id", to: "posts#show"
2
+ post "posts", to: "posts#create"
3
+ put "posts", to: "posts#update"
4
+ delete "posts", to: "posts#destroy"
5
+ any "posts/hot", to: "posts#hot"
6
+ resources :posts
@@ -0,0 +1,212 @@
1
+ 'use strict';
2
+
3
+ const spawn = require('child_process').spawn;
4
+
5
+ // Once hooked up to API Gateway can use the curl command to test:
6
+ // curl -s -X POST -d @event.json https://endpoint | jq .
7
+
8
+ // Filters out lines so only the error lines remain.
9
+ // Uses the "RubyError: " marker to find the starting error lines.
10
+ //
11
+ // Input: String
12
+ // random line
13
+ // RubyError: RuntimeError: error in submethod
14
+ // line1
15
+ // line2
16
+ // line3
17
+ //
18
+ // Output: String
19
+ // RubyError: RuntimeError: error in submethod
20
+ // line1
21
+ // line2
22
+ // line3
23
+ function filterErrorLines(text) {
24
+ var lines = text.split("\n")
25
+ var markerIndex = lines.findIndex(line => line.startsWith("RubyError: ") )
26
+ lines = lines.filter((line, index) => index >= markerIndex )
27
+ return lines.join("\n")
28
+ }
29
+
30
+ // Produces an Error object that displays in the AWS Lambda test console nicely.
31
+ // The backtrace are the ruby lines, not the nodejs shim error lines.
32
+ // The json payload in the Lambda console looks something like this:
33
+ //
34
+ // {
35
+ // "errorMessage": "RubyError: RuntimeError: error in submethod",
36
+ // "errorType": "RubyError",
37
+ // "stackTrace": [
38
+ // [
39
+ // "line1",
40
+ // "line2",
41
+ // "line3"
42
+ // ]
43
+ // ]
44
+ // }
45
+ //
46
+ // Input: String
47
+ // RubyError: RuntimeError: error in submethod
48
+ // line1
49
+ // line2
50
+ // line3
51
+ //
52
+ // Output: Error object
53
+ // { RubyError: RuntimeError: error in submethod
54
+ // line1
55
+ // line2
56
+ // line3 name: 'RubyError' }
57
+ function customError(text) {
58
+ text = filterErrorLines(text) // filter for error lines only
59
+ var lines = text.split("\n")
60
+ var message = lines[0]
61
+ var error = new Error(message)
62
+ error.name = message.split(':')[0]
63
+ error.stack = lines.slice(0, lines.length-1) // drop final empty line
64
+ .map(e => e.replace(/^\s+/g,'')) // trim leading whitespaces
65
+ .join("\n")
66
+ return error
67
+ }
68
+
69
+
70
+ module.exports.create = (event, context, callback) => {
71
+ // Command: bin/jets process controller [event] [context] [handler]
72
+ var args = [
73
+ "process",
74
+ "controller", // controller (singular)
75
+ JSON.stringify(event), // event
76
+ JSON.stringify(context), // context
77
+ "handlers/controllers/posts.create" // IE: handlers/controllers/posts.update
78
+ ]
79
+ var ruby = spawn("bin/jets", args);
80
+
81
+ // string concatation in javascript is faster than array concatation
82
+ // http://bit.ly/2gBMDs6
83
+ var stdout_buffer = ""; // stdout buffer
84
+ // In the processor_command we do NOT call puts directly and write to stdout
85
+ // because it will mess up the eventual response that we want API Gateway to
86
+ // process.
87
+ // The Lambda prints out function to whatever the return value the ruby method
88
+ ruby.stdout.on('data', function(data) {
89
+ // Not using console.log because it decorates output with a newline.
90
+ //
91
+ // Uncomment process.stdout.write to see stdout streamed for debugging.
92
+ // process.stdout.write(data)
93
+ stdout_buffer += data;
94
+ });
95
+
96
+ // react to potential errors
97
+ var stderr_buffer = "";
98
+ ruby.stderr.on('data', function(data) {
99
+ // not using console.error because it decorates output with a newline
100
+ stderr_buffer += data
101
+ process.stderr.write(data)
102
+ });
103
+
104
+ //finalize when ruby process is done.
105
+ ruby.on('close', function(exit_code) {
106
+ // http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html#nodejs-prog-model-handler-callback
107
+
108
+ // succcess
109
+ if (exit_code == 0) {
110
+ var result
111
+ try {
112
+ result = JSON.parse(stdout_buffer)
113
+ } catch(e) {
114
+ // if json cannot be parse assume simple text output intended
115
+ process.stderr.write("WARN: error parsing json, assuming plain text is desired.")
116
+ result = stdout_buffer
117
+ }
118
+ callback(null, result);
119
+
120
+ // callback(null, stdout_buffer);
121
+ } else {
122
+
123
+ // TODO: if this works, allow a way to not decorate the error in case
124
+ // it actually errors in javascript land
125
+ // Customize error object with ruby error info
126
+ var error = customError(stderr_buffer)
127
+ callback(error);
128
+ // console.log("error!")
129
+ }
130
+ });
131
+ }
132
+
133
+ module.exports.update = (event, context, callback) => {
134
+ // Command: bin/jets process controller [event] [context] [handler]
135
+ var args = [
136
+ "process",
137
+ "controller", // controller (singular)
138
+ JSON.stringify(event), // event
139
+ JSON.stringify(context), // context
140
+ "handlers/controllers/posts.update" // IE: handlers/controllers/posts.update
141
+ ]
142
+ var ruby = spawn("bin/jets", args);
143
+
144
+ // string concatation in javascript is faster than array concatation
145
+ // http://bit.ly/2gBMDs6
146
+ var stdout_buffer = ""; // stdout buffer
147
+ // In the processor_command we do NOT call puts directly and write to stdout
148
+ // because it will mess up the eventual response that we want API Gateway to
149
+ // process.
150
+ // The Lambda prints out function to whatever the return value the ruby method
151
+ ruby.stdout.on('data', function(data) {
152
+ // Not using console.log because it decorates output with a newline.
153
+ //
154
+ // Uncomment process.stdout.write to see stdout streamed for debugging.
155
+ // process.stdout.write(data)
156
+ stdout_buffer += data;
157
+ });
158
+
159
+ // react to potential errors
160
+ var stderr_buffer = "";
161
+ ruby.stderr.on('data', function(data) {
162
+ // not using console.error because it decorates output with a newline
163
+ stderr_buffer += data
164
+ process.stderr.write(data)
165
+ });
166
+
167
+ //finalize when ruby process is done.
168
+ ruby.on('close', function(exit_code) {
169
+ // http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html#nodejs-prog-model-handler-callback
170
+
171
+ // succcess
172
+ if (exit_code == 0) {
173
+ var result
174
+ try {
175
+ result = JSON.parse(stdout_buffer)
176
+ } catch(e) {
177
+ // if json cannot be parse assume simple text output intended
178
+ process.stderr.write("WARN: error parsing json, assuming plain text is desired.")
179
+ result = stdout_buffer
180
+ }
181
+ callback(null, result);
182
+
183
+ // callback(null, stdout_buffer);
184
+ } else {
185
+
186
+ // TODO: if this works, allow a way to not decorate the error in case
187
+ // it actually errors in javascript land
188
+ // Customize error object with ruby error info
189
+ var error = customError(stderr_buffer)
190
+ callback(error);
191
+ // console.log("error!")
192
+ }
193
+ });
194
+ }
195
+
196
+
197
+ // for local testing
198
+ if (process.platform == "darwin") {
199
+ // fake event and context
200
+ var event = {"hello": "world"}
201
+ // var event = {"body": {"hello": "world"}} // API Gateway wrapper structure
202
+ var context = {"fake": "context"}
203
+ module.exports.create(event, context, (error, message) => {
204
+ console.error("\nLOCAL TESTING OUTPUT")
205
+ if (error) {
206
+ console.error("error message: %o", error)
207
+ } else {
208
+ console.error("success message %o", message)
209
+ // console.log(JSON.stringify(message)) // stringify
210
+ }
211
+ })
212
+ }
@@ -7,13 +7,14 @@ require "spec_helper"
7
7
  # $ rake clean:vcr ; time rake
8
8
  describe Jets::CLI do
9
9
  before(:all) do
10
- @args = "--from Tung"
10
+ @args = "--noop"
11
11
  end
12
12
 
13
13
  describe "jets" do
14
- it "should hello world" do
15
- out = execute("bin/jets hello world #{@args}")
16
- expect(out).to include("from: Tung\nHello world")
14
+ it "build" do
15
+ out = execute("bin/jets build #{@args}")
16
+ # puts out
17
+ expect(out).to include("Building project")
17
18
  end
18
19
  end
19
20
  end
@@ -0,0 +1,18 @@
1
+ require "spec_helper"
2
+
3
+ # For testing lambda_function_names
4
+ class FakeController < Jets::BaseController
5
+ def handler1; end
6
+ def handler2; end
7
+ end
8
+
9
+ describe Jets::BaseController do
10
+ describe "lambda_functions" do
11
+ it "should only list public user defined methods" do
12
+ controller = FakeController.new(nil, nil)
13
+ expect(controller.lambda_functions).to eq(
14
+ [:handler1, :handler2]
15
+ )
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "../../../spec_helper"
2
+
3
+ describe Jets::Build::HandlerGenerator do
4
+ let(:generator) do
5
+ Jets::Build::HandlerGenerator.new(
6
+ "PostsController",
7
+ :create, :update
8
+ )
9
+ end
10
+
11
+ describe "HandlerGenerator" do
12
+ it "generates a node shim for lambda" do
13
+ generator.run
14
+ content = IO.read("#{Jets.root}handlers/controllers/posts.js")
15
+ expect(content).to include("handlers/controllers/posts.create") # handler
16
+ expect(content).to include("exports.create") # function
17
+ expect(content).to include("exports.update") # function
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ require_relative "../../../spec_helper"
2
+
3
+ describe Jets::Build::LambdaDeducer do
4
+ let(:deducer) do
5
+ Jets::Build::LambdaDeducer.new("app/controllers/posts_controller.rb")
6
+ end
7
+
8
+ describe "LambdaDeducer" do
9
+ it "deduces lambda js info" do
10
+ expect(deducer.class_name).to eq("PostsController")
11
+ expect(deducer.functions).to eq([:create, :update])
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+
3
+ describe Jets::Build do
4
+ before(:each) do
5
+ FileUtils.rm_f("spec/fixtures/project/handlers/controllers/posts.js")
6
+ end
7
+ let(:build) do
8
+ Jets::Build.new(noop: true)
9
+ end
10
+
11
+ describe "Build" do
12
+ it "#controller_paths" do
13
+ expect(build.controller_paths).to eq(["app/controllers/posts_controller.rb"])
14
+ end
15
+
16
+ it "builds handlers javascript files" do
17
+ build.build
18
+ file_exist = File.exist?("#{Jets.root}handlers/controllers/posts.js")
19
+ expect(file_exist).to be true
20
+ end
21
+
22
+ # Would be nice to be able to automate testing the shim
23
+ # context "node shim" do
24
+ # it "posts create should return json" do
25
+ # # build.build
26
+ # # Dir.chdir(ENV["PROJECT_ROOT"]) do
27
+ # out = execute("cd #{ENV["PROJECT_ROOT"]} && node handlers/controllers/posts.js")
28
+ # puts out
29
+ # # end
30
+ # end
31
+ # end
32
+ end
33
+ end
34
+
@@ -0,0 +1,18 @@
1
+ require_relative "../../../spec_helper"
2
+
3
+ describe Jets::Cfn::Builder do
4
+ let(:cfn) do
5
+ Jets::Cfn::Builder.new(CommentsController)
6
+ end
7
+
8
+ describe "Cfn::Builder" do
9
+ it "adds functions to resources" do
10
+ cfn.compose!
11
+ expect(cfn.template[:Resources].keys).to eq(
12
+ ["CommentsControllerCreate", "CommentsControllerUpdate"]
13
+ )
14
+ puts cfn.text
15
+ IO.write("tmp/template.yml", cfn.text)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ require_relative "../../../spec_helper"
2
+
3
+ describe Jets::Cfn::Namer do
4
+ let(:namer) do
5
+ Jets::Cfn::Namer.new(CommentsController, :create)
6
+ end
7
+
8
+ describe "Cfn::Namer" do
9
+ it "creates names appropriate for CloudFormation" do
10
+ expect(namer.handler).to eq "handlers/controllers/comments.create"
11
+ expect(namer.logical_id).to eq "CommentsControllerCreate"
12
+ expect(namer.function_name).to eq "proj-dev-comments-controller-create"
13
+ expect(namer.s3_key).to include("jets/cfn-templates/dev/")
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ require_relative "../../../spec_helper"
2
+
3
+ describe Jets::Process::ControllerProcessor do
4
+ before(:all) do
5
+ @event = { "we" => "love", "using" => "Lambda" }
6
+ @context = {"test" => "1"}
7
+ end
8
+ let(:processor) do
9
+ Jets::Process::ControllerProcessor.new(
10
+ JSON.dump(@event),
11
+ JSON.dump(@context),
12
+ 'handlers/controllers/posts.create' # handler
13
+ )
14
+ end
15
+
16
+ describe "ControllerProcessor" do
17
+ it "find public_instance_methods" do
18
+ processor.run
19
+ expect(processor.event).to eq(@event)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ describe Jets::Process::ProcessorDeducer do
4
+ describe "ProcessorDeducer" do
5
+ let(:deducer) { Jets::Process::ProcessorDeducer.new(handle) }
6
+
7
+ context("controller") do
8
+ let(:handle) { "handlers/controllers/posts.create" }
9
+ it "deduces processor info" do
10
+ expect(deducer.controller[:path]).to include "app/controllers/posts_controller.rb"
11
+ expect(deducer.controller[:code]).to eq "PostsController.new(event, context).create"
12
+ end
13
+ end
14
+
15
+ context("function") do
16
+ let(:handle) { "handlers/functions/posts.create" }
17
+ it "deduces processor info" do
18
+ expect(deducer.function[:path]).to include "app/functions/posts.rb"
19
+ expect(deducer.function[:code]).to eq "create(event, context)"
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "../../spec_helper"
2
+
3
+ describe Jets::Process do
4
+ before(:all) do
5
+ # @args = "--noop --project-root spec/fixtures/my_project"
6
+ @args = '\'{ "we" : "love", "using" : "Lambda" }\' \'{"test": "1"}\' "handlers/controllers/posts.create"'
7
+ end
8
+
9
+ describe "jets process" do
10
+ it "controller [event] [context] [handler]" do
11
+ out = execute("bin/jets process controller #{@args}")
12
+ # pp out # uncomment to debug
13
+ data = JSON.parse(out)
14
+ expect(data["statusCode"]).to eq 200
15
+ expect(data["body"]).to eq({"we"=>"love", "using"=>"Lambda","a"=>"create"})
16
+ end
17
+ end
18
+ end