fdk 0.0.14 → 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: a50737dfa816cbaa0d9de365617db0b1c393d47984529e6de1ac3fdf6e1f9167
4
- data.tar.gz: 8e843ba19e95fc057ec571ec05dc6e4460761bfc5aa2f3bce81a4f068bfb31ed
2
+ SHA1:
3
+ metadata.gz: 54156518eff40553f0490bcf437fa82daf307ce4
4
+ data.tar.gz: 73e6aed064fa66529f22777f8788e9292828ff90
5
5
  SHA512:
6
- metadata.gz: 67c53e7e572ca2a7f3c894ebe111b07fbf86df79e407ab10e2484e0479a0f67d02f45e504b46af9c5a2c81763994c6986e7f9a7d768ee8c341d175800b2bf013
7
- data.tar.gz: ef3b16817052b5eee6ea90d7c0bbd8158446097a87df2db0d2dfad071beed80044c6bad14a8731064340909b3f9ae5ca6338498874864fae1d2709f6a3559f7e
6
+ metadata.gz: b47e349ed1fcc96d5ab2170ad2bafeddeba810246c4457149fd160cb164e4f1dae32b8172ddff9307a11707010a3657770417e6176fe058ba267ebe21431d1bb
7
+ data.tar.gz: dc5ba5aaadb49d99ee180098700970f36b8bb258086c723c058d4a8f0718077cf6dfc9898e704fd974c4aec649ef94a310ba8fcfb32591f3bd3ef41f902e199e
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Ruby Function Developer Kit (FDK)
2
2
  This provides a Ruby framework for developing functions for use with [Fn](https://fnproject.github.io).
3
3
 
4
+ [![CircleCI](https://circleci.com/gh/fnproject/fdk-ruby.svg?style=svg)](https://circleci.com/gh/fnproject/fdk-ruby)
5
+
4
6
  ## Function Handler
5
7
  To use this FDK, you simply need to require this gem.
6
8
 
data/Rakefile CHANGED
@@ -3,3 +3,9 @@ task default: %w[test]
3
3
  task :test do
4
4
  ruby "tests/test_fdk.rb"
5
5
  end
6
+
7
+ require 'rubocop/rake_task'
8
+ desc "run RuboCop on lib directory"
9
+ RuboCop::RakeTask.new(:rubocop) do |task|
10
+ task.patterns = ["lib/**/*.rb"]
11
+ end
data/lib/fdk/call.rb ADDED
@@ -0,0 +1,61 @@
1
+ module FDK
2
+ # Call represents a call to the target function or lambda
3
+ class Call
4
+ FILTER_HEADERS = ["content-length", "te", "transfer-encoding",
5
+ "upgrade", "trailer"].freeze
6
+
7
+ attr_reader :request, :response
8
+ attr_accessor :error
9
+
10
+ def initialize(request:, response:)
11
+ @request = request
12
+ @response = response
13
+ end
14
+
15
+ def context
16
+ @context ||= FDK::Context.new(headers_in, headers_out)
17
+ end
18
+
19
+ def input
20
+ @input ||= ParsedInput.new(raw_input: request.body.to_s)
21
+ end
22
+
23
+ def headers_out
24
+ @headers_out ||= FDK::OutHeaders.new({}, nil)
25
+ end
26
+
27
+ def headers_in
28
+ @headers_in ||= FDK::InHeaders.new(filtered_request_header, nil)
29
+ end
30
+
31
+ def filtered_request_header
32
+ request.header.reject { |k| FILTER_HEADERS.include? k }
33
+ end
34
+
35
+ def process
36
+ format_response_body(fn_return: yield(context: context, input: input.parsed))
37
+ good_response
38
+ rescue StandardError => e
39
+ error_response(error: e)
40
+ end
41
+
42
+ def format_response_body(fn_return:)
43
+ return response.body = fn_return.to_s unless fn_return.respond_to?(:to_json)
44
+
45
+ response.body = fn_return.to_json
46
+ response["content-type"] = "application/json" unless response["content-type"]
47
+ end
48
+
49
+ def good_response
50
+ response.status = 200
51
+ headers_out.each { |k, v| response[k] = v.join(",") }
52
+ end
53
+
54
+ def error_response(error:)
55
+ response["content-type"] = "application/json"
56
+ response.status = 502
57
+ response.body = { message: "An error occurred in the function",
58
+ detail: error.to_s }.to_json
59
+ end
60
+ end
61
+ end
data/lib/fdk/context.rb CHANGED
@@ -1,7 +1,6 @@
1
- require 'date'
1
+ require "date"
2
2
 
3
3
  module FDK
4
-
5
4
  # Config looks up values in the env vars
6
5
  class Config
7
6
  def [](key)
@@ -9,67 +8,59 @@ module FDK
9
8
  end
10
9
  end
11
10
 
12
-
11
+ # Represents inbound HTTP headers
13
12
  class InHeaders
14
- def initialize (h, key_fn)
15
- @headers = h
13
+ def initialize(headers, key_fn)
14
+ @headers = headers
16
15
  @key_fn = key_fn
17
-
18
16
  end
19
17
 
20
- def headerKey(key)
21
- if @key_fn
22
- key = @key_fn.call(key)
23
- end
18
+ def header_key(key)
19
+ key = @key_fn.call(key) if @key_fn
24
20
  key.downcase
25
21
  end
26
22
 
27
23
  def [](key)
28
- h = @headers[headerKey(key)]
29
- unless h.nil?
30
- return h[0]
31
- end
32
- nil
24
+ h = @headers[header_key(key)]
25
+ return h[0] unless h.nil?
33
26
  end
34
27
 
35
- def each (&block)
36
- @headers.each &block
28
+ def each(&block)
29
+ @headers.each(&block)
37
30
  end
38
31
  end
39
32
 
33
+ # Represents outbound HTTP headers
40
34
  class OutHeaders < InHeaders
41
-
42
- def initialize(h, key_in_fn)
43
- super(h, key_in_fn)
35
+ def initialize(headers, key_in_fn)
36
+ super(headers, key_in_fn)
44
37
  end
45
38
 
46
-
47
39
  def []=(key, value)
48
40
  if value.is_a? Array
49
41
  h = []
50
- value.each {|x| h.push(x.to_s)}
51
- @headers[headerKey(key)] = h
42
+ value.each { |x| h.push(x.to_s) }
43
+ @headers[header_key(key)] = h
52
44
  else
53
- @headers[headerKey(key)] = [value.to_s]
45
+ @headers[header_key(key)] = [value.to_s]
54
46
  end
55
47
  end
56
48
 
57
49
  def delete(key)
58
- @headers.delete headerKey(key)
50
+ @headers.delete header_key(key)
59
51
  end
60
52
  end
61
53
 
62
-
54
+ # Represents the Fn context for a function execution
63
55
  class Context
64
-
56
+ # FN_APP_ID -the ID of the application that this function is a member of.
57
+ # FN_APP_NAME - the name of the application.
65
58
  # FN_CALL_ID - a unique ID for each function execution.
66
- # FN_REQUEST_URL - the full URL for the request (parsing example)
67
- # FN_HEADER_$X - the HTTP headers that were set for this request. Replace $X with the upper cased name of the header and replace dashes in the header with underscores.
68
- # $X - any configuration values you've set for the Application or the Route. Replace X with the upper cased name of the config variable you set. Ex: minio_secret=secret will be exposed via MINIO_SECRET env var.
69
- # FN_APP_NAME - the name of the application that matched this route, eg: myapp
70
- # FN_METHOD - the HTTP method for the request, eg: GET or POST
59
+ # FN_FN_ID - the ID of this function
71
60
  # FN_MEMORY - a number representing the amount of memory available to the call, in MB
72
-
61
+ # $X - any configuration values you've set for the Application.
62
+ # Replace X with the upper cased name of the config variable you set.
63
+ # e.g. minio_secret=secret will be exposed via MINIO_SECRET env var.
73
64
 
74
65
  attr_reader :headers
75
66
  attr_reader :response_headers
@@ -80,31 +71,28 @@ module FDK
80
71
  @config ||= Config.new
81
72
  end
82
73
 
83
-
84
74
  def call_id
85
- @headers['fn-call-id']
75
+ @headers["fn-call-id"]
86
76
  end
87
77
 
88
-
89
78
  def app_id
90
- @config['FN_APP_ID']
79
+ @config["FN_APP_ID"]
91
80
  end
92
81
 
93
-
94
82
  def fn_id
95
- @config['FN_FN_ID']
83
+ @config["FN_FN_ID"]
96
84
  end
97
85
 
98
86
  def deadline
99
- DateTime.iso8601(@headers['fn-deadline'])
87
+ DateTime.iso8601(@headers["fn-deadline"])
100
88
  end
101
89
 
102
90
  def memory
103
- @config['FN_MEMORY'].to_i
91
+ @config["FN_MEMORY"].to_i
104
92
  end
105
93
 
106
94
  def content_type
107
- @headers['content-type']
95
+ @headers["content-type"]
108
96
  end
109
97
 
110
98
  def http_context
@@ -112,43 +100,33 @@ module FDK
112
100
  end
113
101
  end
114
102
 
115
-
103
+ # Represents the context data (inbound && outbound)
104
+ # for the execution passed as HTTP headers
116
105
  class HTTPContext
117
-
118
106
  attr_reader :headers
119
107
  attr_reader :response_headers
120
108
 
121
109
  def initialize(ctx)
122
-
110
+ fn_http_h_ = "fn-http-h-"
123
111
  @ctx = ctx
124
-
125
-
126
112
  http_headers = {}
127
- ctx.headers.each {|k, v|
128
- if k.downcase.start_with?('fn-http-h-')
129
- new_key = k['fn-http-h-'.length..k.length]
130
- http_headers[new_key] = v
131
- end
132
- }
133
-
113
+ ctx.headers.each do |k, v|
114
+ http_headers[k.sub(fn_http_h_, "")] = v if k.downcase.start_with?(fn_http_h_)
115
+ end
134
116
  @headers = InHeaders.new(http_headers, nil)
135
- @response_headers = OutHeaders.new(ctx.response_headers, lambda {|s| 'fn-http-h-' + s})
117
+ @response_headers = OutHeaders.new(ctx.response_headers, ->(s) { fn_http_h_ + s })
136
118
  end
137
119
 
138
-
139
120
  def request_url
140
- @ctx.headers['fn-http-request-url']
121
+ @ctx.headers["fn-http-request-url"]
141
122
  end
142
123
 
143
124
  def method
144
- @ctx.headers['fn-http-method']
125
+ @ctx.headers["fn-http-method"]
145
126
  end
146
127
 
147
-
148
128
  def status_code=(val)
149
- @ctx.response_headers['fn-http-status'] = val.to_i
129
+ @ctx.response_headers["fn-http-status"] = val.to_i
150
130
  end
151
-
152
131
  end
153
132
  end
154
-
@@ -0,0 +1,22 @@
1
+ module FDK
2
+ # Function represents a function function or lambda
3
+ class Function
4
+ attr_reader :format, :function
5
+ def initialize(function:, format:)
6
+ raise "'#{format}' not supported in Ruby FDK." unless format == "http-stream"
7
+
8
+ @format = format
9
+ @function = function
10
+ end
11
+
12
+ def as_proc
13
+ return function if function.respond_to?(:call)
14
+
15
+ ->(context:, input:) { send(function, context: context, input: input) }
16
+ end
17
+
18
+ def call(request:, response:)
19
+ Call.new(request: request, response: response).process(&as_proc)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,68 @@
1
+ module FDK
2
+ # Represents the socket that Fn uses to communicate
3
+ # with the FDK (and thence the function)
4
+ # To avoid Fn trying to connect to the socket before
5
+ # it's ready, the Listener creates a socket on (private_socket_path).
6
+ #
7
+ # When the socket is ready to accept connections,
8
+ # the FDK links the private_socket_path to the socket_path.
9
+ #
10
+ # Fn waits for the socket_path to be created and then connects
11
+ class Listener
12
+ attr_reader :url, :private_socket
13
+
14
+ def initialize(url:)
15
+ if url.nil? || !url.start_with?("unix:/")
16
+ raise "Missing or invalid socket URL in FN_LISTENER."
17
+ end
18
+
19
+ @url = url
20
+ @private_socket = UNIXServer.open(private_socket_path)
21
+ end
22
+
23
+ def socket
24
+ link_socket_file unless @socket
25
+ @socket ||= private_socket
26
+ end
27
+
28
+ def link_socket_file
29
+ File.chmod(0o666, private_socket_path)
30
+ FileUtils.ln_s(File.basename(private_socket_path), socket_path)
31
+ FDK.debug "listening on #{private_socket_path}->#{socket_path}"
32
+ end
33
+
34
+ def listen(&block)
35
+ local_socket = socket.accept
36
+ begin
37
+ raise StandardError("No block given") unless block_given?
38
+
39
+ handle_requests(socket: local_socket, fn_block: block)
40
+ rescue StandardError => e
41
+ STDERR.puts "Error in request handling #{e}"
42
+ STDERR.puts e.backtrace
43
+ end
44
+ local_socket.close
45
+ end
46
+
47
+ def handle_requests(socket:, fn_block:)
48
+ loop do
49
+ req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
50
+ resp = WEBrick::HTTPResponse.new(WEBrick::Config::HTTP)
51
+ req.parse(socket)
52
+ FDK.debug "got request #{req}"
53
+ fn_block.call(req, resp)
54
+ resp.send_response(socket)
55
+ FDK.debug "sending resp #{resp.status}, #{resp.header}"
56
+ break unless req.keep_alive?
57
+ end
58
+ end
59
+
60
+ def socket_path
61
+ @socket_path ||= url[5..url.length]
62
+ end
63
+
64
+ def private_socket_path
65
+ socket_path + ".private"
66
+ end
67
+ end
68
+ end
data/lib/fdk/runner.rb CHANGED
@@ -7,144 +7,16 @@ require "set"
7
7
  # Executes it with input
8
8
  # Responds with output
9
9
  module FDK
10
- @filter_headers = Set["content-length", "te", "transfer-encoding",
11
- "upgrade", "trailer"]
12
-
13
- def self.check_format
14
- f = ENV["FN_FORMAT"]
15
- raise "'#{f}' not supported in Ruby FDK." unless f == "http-stream"
16
-
17
- f
18
- end
19
- private_class_method :check_format
20
-
21
- def self.listener
22
- l = ENV["FN_LISTENER"]
23
- if l.nil? || !l.start_with?("unix:/")
24
- raise "Missing or invalid socket URL in FN_LISTENER."
25
- end
26
-
27
- l
28
- end
29
- private_class_method :listener
30
-
31
10
  @dbg = ENV["FDK_DEBUG"]
32
11
 
33
12
  def self.debug(msg)
34
13
  STDERR.puts(msg) if @dbg
35
14
  end
36
- private_class_method :debug
37
15
 
38
16
  def self.handle(target:)
39
- # To avoid Fn trying to connect to the socket before
40
- # it's ready, the FDK creates a socket on (tmp_file).
41
- #
42
- # When the socket is ready to accept connections,
43
- # the FDK links the tmp_file to the socket_file.
44
- #
45
- # Fn waits for the socket_file to be created and then connects
46
- check_format
47
- l = listener
48
- socket_file = l[5..l.length]
49
- tmp_file = socket_file + ".tmp"
50
-
51
- debug tmp_file
52
- debug socket_file
53
- UNIXServer.open(tmp_file) do |serv|
54
- File.chmod(0o666, tmp_file)
55
- debug "listening on #{tmp_file}->#{socket_file}"
56
- FileUtils.ln_s(File.basename(tmp_file), socket_file)
57
-
58
- loop do
59
- s = serv.accept
60
- begin
61
- loop do
62
- req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
63
- req.parse s
64
- debug "got request #{req}"
65
- resp = WEBrick::HTTPResponse.new(WEBrick::Config::HTTP)
66
- resp.status = 200
67
- handle_call(target, req, resp)
68
- resp.send_response s
69
- debug "sending resp #{resp.status}, #{resp.header}"
70
- break unless req.keep_alive?
71
- end
72
- rescue StandardError => e
73
- STDERR.puts "Error in request handling #{e}"
74
- STDERR.puts e.backtrace
75
- end
76
- s.close
77
- end
78
- end
79
- end
80
-
81
- def self.set_error(resp, error)
82
- STDERR.puts "Error in function: \"#{error}\""
83
- STDERR.puts error.backtrace
84
-
85
- resp["content-type"] = "application/json"
86
- resp.status = 502
87
- resp.body = { message: "An error occurred in the function",
88
- detail: error.to_s }.to_json
89
- end
90
- private_class_method :set_error
91
-
92
- def self.handle_call(target, req, resp)
93
- headers = {}
94
- req.header.map do |k, v|
95
- headers[k] = v unless @filter_headers.include? k
96
- end
97
-
98
- headers_out_hash = {}
99
- headers_out = FDK::OutHeaders.new(headers_out_hash, nil)
100
- headers_in = FDK::InHeaders.new(headers, nil)
101
- context = FDK::Context.new(headers_in, headers_out)
102
- input = ParsedInput.new(raw_input: req.body.to_s)
103
-
104
- begin
105
- rv = if target.respond_to? :call
106
- target.call(context: context, input: input.parsed)
107
- else
108
- send(target, context: context, input: input.parsed)
109
- end
110
- rescue StandardError => e
111
- set_error(resp, e)
112
- return
113
- end
114
-
115
- resp.status = 200
116
- headers_out_hash.map do |k, v|
117
- resp[k] = v.join(",") unless @filter_headers.include? k
118
- end
119
-
120
- # TODO: gimme a bit me flexibility on response handling
121
- # binary, streams etc
122
- if !rv.nil? && rv.respond_to?("to_json")
123
- resp.body = rv.to_json
124
- # don't override content type if already set
125
- resp["content-type"] = "application/json" unless resp["content-type"]
126
- else
127
- resp.body = rv.to_s
128
- end
129
- end
130
-
131
- # Stores raw input and can parse it as
132
- # JSON (add extra formats as required)
133
- class ParsedInput
134
- attr_reader :raw
135
-
136
- def initialize(raw_input:)
137
- @raw = raw_input
138
- end
139
-
140
- def as_json
141
- @json ||= JSON.parse(raw)
142
- rescue JSON::ParserError
143
- @json = false
144
- end
145
-
146
- def parsed
147
- as_json || raw
17
+ func = Function.new(function: target, format: ENV["FN_FORMAT"])
18
+ Listener.new(url: ENV["FN_LISTENER"]).listen do |req, resp|
19
+ func.call(request: req, response: resp)
148
20
  end
149
21
  end
150
22
  end
@@ -0,0 +1,21 @@
1
+ module FDK
2
+ # ParsedInput stores raw input and can parse it as
3
+ # JSON (add extra formats as required)
4
+ class ParsedInput
5
+ attr_reader :raw
6
+
7
+ def initialize(raw_input:)
8
+ @raw = raw_input
9
+ end
10
+
11
+ def as_json
12
+ @json ||= JSON.parse(raw)
13
+ rescue JSON::ParserError
14
+ @json = false
15
+ end
16
+
17
+ def parsed
18
+ as_json || raw
19
+ end
20
+ end
21
+ end
data/lib/fdk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module FDK
2
- VERSION = "0.0.14"
2
+ VERSION = "0.0.15".freeze
3
3
  end
data/lib/fdk.rb CHANGED
@@ -1,3 +1,7 @@
1
- require_relative 'fdk/version'
2
- require_relative 'fdk/runner'
3
- require_relative 'fdk/context'
1
+ require_relative "fdk/version"
2
+ require_relative "fdk/runner"
3
+ require_relative "fdk/context"
4
+ require_relative "fdk/call"
5
+ require_relative "fdk/listener"
6
+ require_relative "fdk/function"
7
+ require_relative "fdk/support_classes"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.14
4
+ version: 0.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Travis Reeder
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2018-10-05 00:00:00.000000000 Z
13
+ date: 2018-10-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: json
@@ -65,8 +65,12 @@ files:
65
65
  - README.md
66
66
  - Rakefile
67
67
  - lib/fdk.rb
68
+ - lib/fdk/call.rb
68
69
  - lib/fdk/context.rb
70
+ - lib/fdk/function.rb
71
+ - lib/fdk/listener.rb
69
72
  - lib/fdk/runner.rb
73
+ - lib/fdk/support_classes.rb
70
74
  - lib/fdk/version.rb
71
75
  homepage: https://github.com/fnproject/fdk-ruby
72
76
  licenses:
@@ -88,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
92
  version: '0'
89
93
  requirements: []
90
94
  rubyforge_project:
91
- rubygems_version: 2.7.6
95
+ rubygems_version: 2.6.14.1
92
96
  signing_key:
93
97
  specification_version: 4
94
98
  summary: Ruby FDK for Fn Project