faastruby 0.4.12 → 0.4.14

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +15 -4
  4. data/Gemfile.lock +2 -2
  5. data/README.md +2 -0
  6. data/exe/faastruby-server +4 -2
  7. data/lib/faastruby/cli/commands/function/build.rb +20 -1
  8. data/lib/faastruby/cli/commands/function/deploy_to.rb +20 -1
  9. data/lib/faastruby/cli/commands/function/new.rb +6 -4
  10. data/lib/faastruby/cli/commands/function/update_context.rb +2 -2
  11. data/lib/faastruby/server.rb +10 -233
  12. data/lib/faastruby/server/concurrency_controller.rb +51 -0
  13. data/lib/faastruby/server/errors.rb +3 -0
  14. data/lib/faastruby/server/event.rb +19 -0
  15. data/lib/faastruby/server/event_channel.rb +19 -0
  16. data/lib/faastruby/server/event_hub.rb +50 -0
  17. data/lib/faastruby/server/function_object.rb +9 -0
  18. data/lib/faastruby/server/response.rb +25 -0
  19. data/lib/faastruby/server/runner.rb +43 -0
  20. data/lib/faastruby/server/runner_methods.rb +106 -0
  21. data/lib/faastruby/server/subscriber.rb +16 -0
  22. data/lib/faastruby/spec_helper.rb +36 -0
  23. data/lib/faastruby/version.rb +1 -1
  24. data/templates/crystal/example-blank/spec/spec_helper.cr +1 -1
  25. data/templates/crystal/example/spec/spec_helper.cr +1 -1
  26. data/templates/ruby/example-blank/Gemfile +1 -0
  27. data/templates/ruby/example-blank/spec/handler_spec.rb +6 -1
  28. data/templates/ruby/example-blank/spec/spec_helper.rb +2 -2
  29. data/templates/ruby/example/Gemfile +1 -0
  30. data/templates/ruby/example/spec/handler_spec.rb +8 -3
  31. data/templates/ruby/example/spec/spec_helper.rb +2 -2
  32. metadata +13 -6
  33. data/templates/crystal/example-blank/spec/helpers/faastruby.cr +0 -77
  34. data/templates/crystal/example/spec/helpers/faastruby.cr +0 -77
  35. data/templates/ruby/example-blank/spec/helpers/faastruby.rb +0 -66
  36. data/templates/ruby/example/spec/helpers/faastruby.rb +0 -65
@@ -0,0 +1,51 @@
1
+ module FaaStRuby
2
+ class ConcurrencyController
3
+ def self.store
4
+ @@store ||= {}
5
+ end
6
+ attr_accessor :params, :name, :max, :type
7
+ def initialize(name, max: 1, type:)
8
+ @type = type
9
+ @name = name
10
+ @max = max
11
+ @running = 0
12
+ # @mutex = Mutex.new
13
+ self.class.store[name] = self
14
+ puts "[ConcurrencyController] Started controller for '#{name}' with max_concurrency = #{@max}".yellow
15
+ end
16
+
17
+ def running
18
+ # puts "[ConcurrencyController] [#{name}] Reading runners".red
19
+ # wait
20
+ # puts "[ConcurrencyController] [#{name}] Locking mutex".red
21
+ # @mutex.lock
22
+ @running
23
+ # ensure
24
+ # puts "[ConcurrencyController] [#{name}] Unlocking mutex".red
25
+ # @mutex.unlock
26
+ end
27
+
28
+ def decr(amount = 1)
29
+ incr(0 - amount)
30
+ end
31
+
32
+ def incr(amount = 1)
33
+ # puts "[ConcurrencyController] [#{name}] Incr #{amount}".red
34
+ # wait
35
+ # puts "[ConcurrencyController] [#{name}] Locking mutex".red
36
+ # @mutex.lock
37
+ current = @running + amount
38
+ return nil if max < current
39
+ @running += amount
40
+ # ensure
41
+ # puts "[ConcurrencyController] [#{name}] Unlocking mutex".red
42
+ # @mutex.unlock
43
+ end
44
+
45
+ # def wait
46
+ # puts "[ConcurrencyController] [#{name}] Waiting for mutex lock to release".red
47
+ # while @mutex.locked? do;end
48
+ # puts "[ConcurrencyController] [#{name}] Mutex released".red
49
+ # end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module FaaStRuby
2
+ class DoubleRenderError < StandardError; end
3
+ end
@@ -0,0 +1,19 @@
1
+ module FaaStRuby
2
+ class Event
3
+ attr_accessor :body, :query_params, :headers, :context
4
+ def initialize(body:, query_params:, headers:, context:)
5
+ @body = body
6
+ @query_params = query_params
7
+ @headers = headers
8
+ @context = context
9
+ end
10
+ def to_h
11
+ {
12
+ "body" => @body,
13
+ "query_params" => @query_params,
14
+ "headers" => @headers,
15
+ "context" => @context
16
+ }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module FaaStRuby
2
+ class EventChannel
3
+ @@channels = {}
4
+ def self.channels
5
+ @@channels
6
+ end
7
+ attr_accessor :name
8
+ def initialize(channel)
9
+ @name = channel
10
+ @@channels[channel] ||= []
11
+ end
12
+ def subscribe(function_path)
13
+ @@channels[@name] << function_path
14
+ end
15
+ def subscribers
16
+ @@channels[@name] || []
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,50 @@
1
+ module FaaStRuby
2
+ class EventHub
3
+ @@queue = Queue.new
4
+ def self.queue
5
+ @@queue
6
+ end
7
+
8
+ def self.push(payload)
9
+ @@queue << payload
10
+ end
11
+
12
+ def self.thread
13
+ @@thread
14
+ end
15
+
16
+ def self.load_subscribers
17
+ Dir.glob('*/*/faastruby.yml').each do |file|
18
+ workspace_name, function_name, _ = file.split('/')
19
+ path = "#{workspace_name}/#{function_name}"
20
+ config = YAML.load(File.read(file))
21
+ next unless config['channels'].is_a?(Array)
22
+ config['channels'].compact!
23
+ config['channels'].each do |c|
24
+ channel = EventChannel.new(c)
25
+ channel.subscribe(path)
26
+ end
27
+ end
28
+ puts "[EventHub] Channel subscriptions: #{EventChannel.channels}".yellow
29
+ puts "[EventHub] If you modify 'faastruby.yml' in any function, you will need to restart the server to apply the changes.".yellow
30
+ end
31
+
32
+ def self.listen_for_events!
33
+ load_subscribers
34
+ @@thread = Thread.new do
35
+ loop do
36
+ encoded_channel, encoded_data = @@queue.pop.split(',')
37
+ channel = EventChannel.new(Base64.urlsafe_decode64(encoded_channel))
38
+ puts "[EventHub] Event channel=#{channel.name.inspect}".yellow
39
+ channel.subscribers.each do |s|
40
+ subscriber = Subscriber.new(s)
41
+ puts "[EventHub] Trigger function=#{subscriber.path.inspect} base64_payload=#{encoded_data.inspect}".yellow
42
+ response = subscriber.call(encoded_data)
43
+ puts "[#{subscriber.path}] #=> status=#{response.status} body=#{response.body.inspect} headers=#{Oj.dump response.headers}".light_blue
44
+ end
45
+ end
46
+ end
47
+ puts "[EventHub] Events thread started.".yellow
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,9 @@
1
+ module FaaStRuby
2
+ class FunctionObject
3
+ include RunnerMethods
4
+ attr_reader :path
5
+ def initialize(path)
6
+ @path = path
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ module FaaStRuby
2
+ class Response
3
+ def self.request_limit_reached(workspace: nil, function: nil)
4
+ body = {'error' => "Concurrent requests limit reached. Please add more runners to the workspace #{workspace}."} if workspace
5
+ # body = {'error' => "Concurrent requests limit reached for function '#{workspace}/#{function}'. Please associate more runners."} if function
6
+ body = Oj.dump(body)
7
+ new(
8
+ body: body,
9
+ status: 422,
10
+ headers: {'Content-Type' => 'application/json'}
11
+ )
12
+ end
13
+ attr_accessor :body, :status, :headers, :binary
14
+ def initialize(body:, status: 200, headers: {}, binary: false)
15
+ @body = body
16
+ @status = status
17
+ @headers = headers
18
+ @binary = binary
19
+ end
20
+
21
+ def binary?
22
+ @binary
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ require 'base64'
2
+
3
+ module FaaStRuby
4
+ class Runner
5
+ include RunnerMethods
6
+ def initialize
7
+ @rendered = false
8
+ end
9
+
10
+ def path
11
+ @path
12
+ end
13
+
14
+ def load_function(path)
15
+ eval "Module.new do; #{File.read(path)};end"
16
+ end
17
+
18
+ def call(workspace_name, function_name, event, args)
19
+ @short_path = "#{workspace_name}/#{function_name}"
20
+ @path = "#{FaaStRuby::PROJECT_ROOT}/#{workspace_name}/#{function_name}"
21
+ short_path = "#{workspace_name}/#{function_name}"
22
+ begin
23
+ Dir.chdir(@path)
24
+ function = load_function("#{@path}/handler.rb")
25
+ runner = FunctionObject.new(short_path)
26
+ runner.extend(function)
27
+ response = runner.handler(event, *args)
28
+ # response = handler(event, args)
29
+ return response if response.is_a?(FaaStRuby::Response)
30
+ body = {
31
+ 'error' => "Please use the helpers 'render' or 'respond_with' as your function return value."
32
+ }
33
+ FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'})
34
+ rescue Exception => e
35
+ body = {
36
+ 'error' => e.message,
37
+ 'location' => e.backtrace&.first,
38
+ }
39
+ FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'})
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,106 @@
1
+ module FaaStRuby
2
+ module RunnerMethods
3
+ def rendered!
4
+ @rendered = true
5
+ end
6
+ def rendered?
7
+ @rendered
8
+ end
9
+
10
+ def respond_with(body, status: 200, headers: {}, binary: false)
11
+ raise FaaStRuby::DoubleRenderError.new("You called 'render/respond_with/redirect_to' twice in your handler method") if rendered?
12
+ response = FaaStRuby::Response.new(body: body, status: status, headers: headers, binary: binary)
13
+ rendered!
14
+ response
15
+ end
16
+
17
+ def render(
18
+ js: nil,
19
+ css: nil,
20
+ body: nil,
21
+ inline: nil,
22
+ html: nil,
23
+ json: nil,
24
+ yaml: nil,
25
+ text: nil,
26
+ data: nil,
27
+ png: nil,
28
+ svg: nil,
29
+ jpeg: nil,
30
+ gif: nil,
31
+ icon: nil,
32
+ status: 200, headers: {}, content_type: nil, binary: false
33
+ )
34
+ headers["Content-Type"] = content_type if content_type
35
+ bin = false
36
+ case
37
+ when json
38
+ headers["Content-Type"] ||= "application/json"
39
+ resp_body = json.is_a?(String) ? json : Oj.dump(json)
40
+ when html, inline
41
+ headers["Content-Type"] ||= "text/html"
42
+ resp_body = html
43
+ when text
44
+ headers["Content-Type"] ||= "text/plain"
45
+ resp_body = text
46
+ when yaml
47
+ headers["Content-Type"] ||= "application/yaml"
48
+ resp_body = yaml.is_a?(String) ? yaml : YAML.load(yaml)
49
+ when body
50
+ headers["Content-Type"] ||= "application/octet-stream"
51
+ bin = binary
52
+ resp_body = bin ? Base64.urlsafe_encode64(body) : body
53
+ when data
54
+ headers["Content-Type"] ||= "application/octet-stream"
55
+ resp_body = Base64.urlsafe_encode64(data)
56
+ bin = true
57
+ when js
58
+ headers["Content-Type"] ||= "text/javascript"
59
+ resp_body = js
60
+ when css
61
+ headers["Content-Type"] ||= "text/css"
62
+ resp_body = css
63
+ when png
64
+ headers["Content-Type"] ||= "image/png"
65
+ resp_body = Base64.urlsafe_encode64(png)
66
+ bin = true
67
+ when svg
68
+ headers["Content-Type"] ||= "image/svg+xml"
69
+ resp_body = svg
70
+ when jpeg
71
+ headers["Content-Type"] ||= "image/jpeg"
72
+ resp_body = Base64.urlsafe_encode64(jpeg)
73
+ bin = true
74
+ when gif
75
+ headers["Content-Type"] ||= "image/gif"
76
+ resp_body = Base64.urlsafe_encode64(gif)
77
+ bin = true
78
+ when icon
79
+ headers["Content-Type"] ||= "image/x-icon"
80
+ resp_body = Base64.urlsafe_encode64(icon)
81
+ bin = true
82
+ end
83
+ respond_with(resp_body, status: status, headers: headers, binary: bin)
84
+ end
85
+
86
+ def redirect_to(function: nil, url: nil, status: 303)
87
+ headers = {"Location" => function || url}
88
+ respond_with(nil, status: status, headers: headers, binary: false)
89
+ end
90
+
91
+ def puts(msg)
92
+ super "[#{@short_path}] #{msg}".green
93
+ end
94
+
95
+ def publish(channel, data: nil)
96
+ begin
97
+ encoded_data = data ? Base64.urlsafe_encode64(data, padding: false) : ""
98
+ payload = %(#{Base64.urlsafe_encode64(channel, padding: false)},#{encoded_data})
99
+ EventHub.queue.push payload
100
+ true
101
+ rescue
102
+ false
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,16 @@
1
+ module FaaStRuby
2
+ class Subscriber
3
+ attr_accessor :path
4
+ def initialize(path)
5
+ @path = path
6
+ @workspace_name, @function_name = @path.split("/")
7
+ end
8
+
9
+ def call(encoded_data)
10
+ data = Base64.urlsafe_decode64(encoded_data)
11
+ headers = {'X-Origin' => 'event_hub', 'Content-Transfer-Encoding' => 'base64'}
12
+ event = Event.new(body: data, query_params: {}, headers: headers, context: nil)
13
+ Runner.new.call(@workspace_name, @function_name, event, [])
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,36 @@
1
+ $LOAD_PATH << Dir.pwd
2
+ module FaaStRuby
3
+ require 'faastruby/server/errors'
4
+ require 'faastruby/server/runner_methods'
5
+ require 'faastruby/server/function_object'
6
+ require 'faastruby/server/event'
7
+ require 'faastruby/server/response'
8
+ ##########
9
+ # Add call method to the Response class
10
+ # for backwards compatibility.
11
+ # This will be removed on v0.6
12
+ class Response
13
+ def call
14
+ self
15
+ end
16
+ end
17
+ # Add default initialize values to the
18
+ # Event class for backwards compatibility.
19
+ # This will be removed on v0.6
20
+ class Event
21
+ def initialize(body: nil, query_params: {}, headers: {}, context: nil)
22
+ @body = body
23
+ @query_params = query_params
24
+ @headers = headers
25
+ @context = context
26
+ end
27
+ end
28
+ #########
29
+ module SpecHelper
30
+ include FaaStRuby
31
+ include RunnerMethods
32
+ def publish(channel, data: nil)
33
+ true
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,3 @@
1
1
  module FaaStRuby
2
- VERSION = '0.4.12'
2
+ VERSION = '0.4.14'
3
3
  end
@@ -1,4 +1,4 @@
1
1
  require "spec"
2
- require "./helpers/faastruby"
2
+ require "faastruby-spec-helper"
3
3
  require "../src/handler"
4
4
  include FaaStRuby
@@ -1,4 +1,4 @@
1
1
  require "spec"
2
- require "./helpers/faastruby"
2
+ require "faastruby-spec-helper"
3
3
  require "../src/handler"
4
4
  include FaaStRuby
@@ -3,4 +3,5 @@ source 'https://rubygems.org'
3
3
  group :test do
4
4
  gem 'rspec'
5
5
  gem 'faastruby-rpc'
6
+ gem 'faastruby'
6
7
  end
@@ -2,7 +2,12 @@ require 'spec_helper'
2
2
  require 'handler'
3
3
 
4
4
  describe 'handler(event)' do
5
- # let(:event) {SpecHelper::Event.new(body: 'Hello', context: 'secret_data', headers: {'MyHeader' => true}, query_params: {'foo' => 'bar'})}
5
+ let(:event) {Event.new(
6
+ body: nil,
7
+ query_params: {},
8
+ headers: {},
9
+ context: nil
10
+ )}
6
11
 
7
12
  xit 'write some tests here' do
8
13
  # function_return = handler(event).call
@@ -1,3 +1,3 @@
1
1
  require 'faastruby-rpc/test_helper'
2
- require 'helpers/faastruby'
3
- include FaaStRuby
2
+ require 'faastruby/spec_helper'
3
+ include FaaStRuby::SpecHelper