fdk 0.0.14 → 0.0.15

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 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