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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +15 -4
- data/Gemfile.lock +2 -2
- data/README.md +2 -0
- data/exe/faastruby-server +4 -2
- data/lib/faastruby/cli/commands/function/build.rb +20 -1
- data/lib/faastruby/cli/commands/function/deploy_to.rb +20 -1
- data/lib/faastruby/cli/commands/function/new.rb +6 -4
- data/lib/faastruby/cli/commands/function/update_context.rb +2 -2
- data/lib/faastruby/server.rb +10 -233
- data/lib/faastruby/server/concurrency_controller.rb +51 -0
- data/lib/faastruby/server/errors.rb +3 -0
- data/lib/faastruby/server/event.rb +19 -0
- data/lib/faastruby/server/event_channel.rb +19 -0
- data/lib/faastruby/server/event_hub.rb +50 -0
- data/lib/faastruby/server/function_object.rb +9 -0
- data/lib/faastruby/server/response.rb +25 -0
- data/lib/faastruby/server/runner.rb +43 -0
- data/lib/faastruby/server/runner_methods.rb +106 -0
- data/lib/faastruby/server/subscriber.rb +16 -0
- data/lib/faastruby/spec_helper.rb +36 -0
- data/lib/faastruby/version.rb +1 -1
- data/templates/crystal/example-blank/spec/spec_helper.cr +1 -1
- data/templates/crystal/example/spec/spec_helper.cr +1 -1
- data/templates/ruby/example-blank/Gemfile +1 -0
- data/templates/ruby/example-blank/spec/handler_spec.rb +6 -1
- data/templates/ruby/example-blank/spec/spec_helper.rb +2 -2
- data/templates/ruby/example/Gemfile +1 -0
- data/templates/ruby/example/spec/handler_spec.rb +8 -3
- data/templates/ruby/example/spec/spec_helper.rb +2 -2
- metadata +13 -6
- data/templates/crystal/example-blank/spec/helpers/faastruby.cr +0 -77
- data/templates/crystal/example/spec/helpers/faastruby.cr +0 -77
- data/templates/ruby/example-blank/spec/helpers/faastruby.rb +0 -66
- 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,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,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
|
data/lib/faastruby/version.rb
CHANGED
|
@@ -2,7 +2,12 @@ require 'spec_helper'
|
|
|
2
2
|
require 'handler'
|
|
3
3
|
|
|
4
4
|
describe 'handler(event)' do
|
|
5
|
-
|
|
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 '
|
|
3
|
-
include FaaStRuby
|
|
2
|
+
require 'faastruby/spec_helper'
|
|
3
|
+
include FaaStRuby::SpecHelper
|