functions_framework 0.5.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/README.md +6 -6
- data/bin/functions-framework +4 -1
- data/bin/functions-framework-ruby +1 -1
- data/docs/deploying-functions.md +27 -22
- data/docs/overview.md +5 -5
- data/docs/testing-functions.md +50 -0
- data/docs/writing-functions.md +251 -14
- data/lib/functions_framework.rb +31 -4
- data/lib/functions_framework/cli.rb +98 -23
- data/lib/functions_framework/function.rb +190 -48
- data/lib/functions_framework/legacy_event_converter.rb +8 -5
- data/lib/functions_framework/registry.rb +34 -11
- data/lib/functions_framework/server.rb +21 -16
- data/lib/functions_framework/testing.rb +106 -18
- data/lib/functions_framework/version.rb +1 -1
- metadata +10 -7
@@ -49,16 +49,19 @@ module FunctionsFramework
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def normalized_context input
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
service, resource = analyze_resource raw_context&.[]("resource") || input["resource"]
|
52
|
+
id = normalized_context_field input, "eventId"
|
53
|
+
timestamp = normalized_context_field input, "timestamp"
|
54
|
+
type = normalized_context_field input, "eventType"
|
55
|
+
service, resource = analyze_resource normalized_context_field input, "resource"
|
57
56
|
service ||= service_from_type type
|
58
57
|
return nil unless id && timestamp && type && service && resource
|
59
58
|
{ id: id, timestamp: timestamp, type: type, service: service, resource: resource }
|
60
59
|
end
|
61
60
|
|
61
|
+
def normalized_context_field input, field
|
62
|
+
input["context"]&.[](field) || input[field]
|
63
|
+
end
|
64
|
+
|
62
65
|
def analyze_resource raw_resource
|
63
66
|
service = resource = nil
|
64
67
|
case raw_resource
|
@@ -12,21 +12,18 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
require "monitor"
|
16
|
-
|
17
15
|
module FunctionsFramework
|
18
16
|
##
|
19
17
|
# Registry providing lookup of functions by name.
|
20
18
|
#
|
21
19
|
class Registry
|
22
|
-
include ::MonitorMixin
|
23
|
-
|
24
20
|
##
|
25
21
|
# Create a new empty registry.
|
26
22
|
#
|
27
23
|
def initialize
|
28
|
-
|
24
|
+
@mutex = ::Mutex.new
|
29
25
|
@functions = {}
|
26
|
+
@start_tasks = []
|
30
27
|
end
|
31
28
|
|
32
29
|
##
|
@@ -37,7 +34,7 @@ module FunctionsFramework
|
|
37
34
|
# @return [nil] if the function is not found
|
38
35
|
#
|
39
36
|
def [] name
|
40
|
-
@functions[name.to_s]
|
37
|
+
@mutex.synchronize { @functions[name.to_s] }
|
41
38
|
end
|
42
39
|
|
43
40
|
##
|
@@ -46,7 +43,16 @@ module FunctionsFramework
|
|
46
43
|
# @return [Array<String>]
|
47
44
|
#
|
48
45
|
def names
|
49
|
-
@functions.keys.sort
|
46
|
+
@mutex.synchronize { @functions.keys.sort }
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Return an array of startup tasks.
|
51
|
+
#
|
52
|
+
# @return [Array<FunctionsFramework::Function>]
|
53
|
+
#
|
54
|
+
def startup_tasks
|
55
|
+
@mutex.synchronize { @start_tasks.dup }
|
50
56
|
end
|
51
57
|
|
52
58
|
##
|
@@ -68,9 +74,9 @@ module FunctionsFramework
|
|
68
74
|
#
|
69
75
|
def add_http name, &block
|
70
76
|
name = name.to_s
|
71
|
-
synchronize do
|
77
|
+
@mutex.synchronize do
|
72
78
|
raise ::ArgumentError, "Function already defined: #{name}" if @functions.key? name
|
73
|
-
@functions[name] = Function.
|
79
|
+
@functions[name] = Function.http name, &block
|
74
80
|
end
|
75
81
|
self
|
76
82
|
end
|
@@ -89,9 +95,26 @@ module FunctionsFramework
|
|
89
95
|
#
|
90
96
|
def add_cloud_event name, &block
|
91
97
|
name = name.to_s
|
92
|
-
synchronize do
|
98
|
+
@mutex.synchronize do
|
93
99
|
raise ::ArgumentError, "Function already defined: #{name}" if @functions.key? name
|
94
|
-
@functions[name] = Function.
|
100
|
+
@functions[name] = Function.cloud_event name, &block
|
101
|
+
end
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Add a startup task.
|
107
|
+
#
|
108
|
+
# Startup tasks are generally run just before a server starts. They are
|
109
|
+
# passed the {FunctionsFramework::Function} identifying the function to
|
110
|
+
# execute, and have no return value.
|
111
|
+
#
|
112
|
+
# @param block [Proc] The startup task
|
113
|
+
# @return [self]
|
114
|
+
#
|
115
|
+
def add_startup_task &block
|
116
|
+
@mutex.synchronize do
|
117
|
+
@start_tasks << Function.startup_task(&block)
|
95
118
|
end
|
96
119
|
self
|
97
120
|
end
|
@@ -27,17 +27,22 @@ module FunctionsFramework
|
|
27
27
|
include ::MonitorMixin
|
28
28
|
|
29
29
|
##
|
30
|
-
# Create a new web server given a function
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
30
|
+
# Create a new web server given a function definition, a set of application
|
31
|
+
# globals, and server configuration.
|
32
|
+
#
|
33
|
+
# To configure the server, pass a block that takes a
|
34
|
+
# {FunctionsFramework::Server::Config} object as the parameter. This block
|
35
|
+
# is the only opportunity to modify the configuration; once the server is
|
36
|
+
# initialized, configuration is frozen.
|
35
37
|
#
|
36
38
|
# @param function [FunctionsFramework::Function] The function to execute.
|
39
|
+
# @param globals [Hash] Globals to pass to invocations. This hash should
|
40
|
+
# normally be frozen so separate function invocations cannot interfere
|
41
|
+
# with one another's globals.
|
37
42
|
# @yield [FunctionsFramework::Server::Config] A config object that can be
|
38
43
|
# manipulated to configure this server.
|
39
44
|
#
|
40
|
-
def initialize function
|
45
|
+
def initialize function, globals
|
41
46
|
super()
|
42
47
|
@config = Config.new
|
43
48
|
yield @config if block_given?
|
@@ -46,9 +51,9 @@ module FunctionsFramework
|
|
46
51
|
@app =
|
47
52
|
case function.type
|
48
53
|
when :http
|
49
|
-
HttpApp.new function, @config
|
54
|
+
HttpApp.new function, globals, @config
|
50
55
|
when :cloud_event
|
51
|
-
EventApp.new function, @config
|
56
|
+
EventApp.new function, globals, @config
|
52
57
|
else
|
53
58
|
raise "Unrecognized function type: #{function.type}"
|
54
59
|
end
|
@@ -82,9 +87,9 @@ module FunctionsFramework
|
|
82
87
|
@server.max_threads = @config.max_threads
|
83
88
|
@server.leak_stack_on_error = @config.show_error_details?
|
84
89
|
@server.binder.add_tcp_listener @config.bind_addr, @config.port
|
85
|
-
@server.run true
|
86
90
|
@config.logger.info "FunctionsFramework: Serving function #{@function.name.inspect}" \
|
87
91
|
" on port #{@config.port}..."
|
92
|
+
@server.run true
|
88
93
|
end
|
89
94
|
end
|
90
95
|
self
|
@@ -153,7 +158,7 @@ module FunctionsFramework
|
|
153
158
|
::Signal.trap "SIGHUP" do
|
154
159
|
Server.signal_enqueue "SIGHUP", @config.logger, @server
|
155
160
|
end
|
156
|
-
rescue ::ArgumentError
|
161
|
+
rescue ::ArgumentError
|
157
162
|
# Not available on all systems
|
158
163
|
end
|
159
164
|
@signals_installed = true
|
@@ -379,9 +384,10 @@ module FunctionsFramework
|
|
379
384
|
|
380
385
|
## @private
|
381
386
|
class HttpApp < AppBase
|
382
|
-
def initialize function, config
|
387
|
+
def initialize function, globals, config
|
383
388
|
super config
|
384
389
|
@function = function
|
390
|
+
@globals = globals
|
385
391
|
end
|
386
392
|
|
387
393
|
def call env
|
@@ -391,8 +397,7 @@ module FunctionsFramework
|
|
391
397
|
logger = env["rack.logger"] ||= @config.logger
|
392
398
|
request = ::Rack::Request.new env
|
393
399
|
logger.info "FunctionsFramework: Handling HTTP #{request.request_method} request"
|
394
|
-
|
395
|
-
calling_context.call request
|
400
|
+
@function.call request, globals: @globals, logger: logger
|
396
401
|
rescue ::StandardError => e
|
397
402
|
e
|
398
403
|
end
|
@@ -402,9 +407,10 @@ module FunctionsFramework
|
|
402
407
|
|
403
408
|
## @private
|
404
409
|
class EventApp < AppBase
|
405
|
-
def initialize function, config
|
410
|
+
def initialize function, globals, config
|
406
411
|
super config
|
407
412
|
@function = function
|
413
|
+
@globals = globals
|
408
414
|
@cloud_events = ::CloudEvents::HttpBinding.default
|
409
415
|
@legacy_events = LegacyEventConverter.new
|
410
416
|
end
|
@@ -439,8 +445,7 @@ module FunctionsFramework
|
|
439
445
|
|
440
446
|
def handle_cloud_event event, logger
|
441
447
|
logger.info "FunctionsFramework: Handling CloudEvent"
|
442
|
-
|
443
|
-
calling_context.call event
|
448
|
+
@function.call event, globals: @globals, logger: logger
|
444
449
|
"ok"
|
445
450
|
rescue ::StandardError => e
|
446
451
|
e
|
@@ -75,19 +75,71 @@ module FunctionsFramework
|
|
75
75
|
Testing.load_for_testing path, &block
|
76
76
|
end
|
77
77
|
|
78
|
+
##
|
79
|
+
# Run startup tasks for the given function name and return the initialized
|
80
|
+
# globals hash.
|
81
|
+
#
|
82
|
+
# Normally, this will be run automatically prior to the first call to the
|
83
|
+
# function using {call_http} or {call_event}, if it has not already been
|
84
|
+
# run. However, you can call it explicitly to test its behavior. It cannot
|
85
|
+
# be called more than once for any given function.
|
86
|
+
#
|
87
|
+
# By default, the {FunctionsFramework.logger} will be used, but you can
|
88
|
+
# override that by providing your own logger. In particular, to disable
|
89
|
+
# logging, you can pass `Logger.new(nil)`.
|
90
|
+
#
|
91
|
+
# @param name [String] The name of the function to start up.
|
92
|
+
# @param logger [Logger] Use the given logger instead of the Functions
|
93
|
+
# Framework's global logger. Optional.
|
94
|
+
# @param lenient [Boolean] If false (the default), raise an error if the
|
95
|
+
# given function has already had its startup tasks run. If true,
|
96
|
+
# duplicate requests to run startup tasks are ignored.
|
97
|
+
# @return [Hash] The initialized globals.
|
98
|
+
#
|
99
|
+
def run_startup_tasks name, logger: nil, lenient: false
|
100
|
+
function = Testing.current_registry[name]
|
101
|
+
raise "Unknown function name #{name}" unless function
|
102
|
+
globals = Testing.current_globals name
|
103
|
+
if globals
|
104
|
+
raise "Function #{name} has already been started up" unless lenient
|
105
|
+
else
|
106
|
+
globals = function.populate_globals
|
107
|
+
Testing.current_registry.startup_tasks.each do |task|
|
108
|
+
task.call function, globals: globals, logger: logger
|
109
|
+
end
|
110
|
+
Testing.current_globals name, globals
|
111
|
+
end
|
112
|
+
globals.freeze
|
113
|
+
end
|
114
|
+
|
78
115
|
##
|
79
116
|
# Call the given HTTP function for testing. The underlying function must
|
80
|
-
# be of type `:http`.
|
117
|
+
# be of type `:http`. Returns the Rack response.
|
118
|
+
#
|
119
|
+
# By default, the startup tasks will be run for the given function if they
|
120
|
+
# have not already been run. You can, however, disable running startup
|
121
|
+
# tasks by providing an explicit globals hash.
|
122
|
+
#
|
123
|
+
# By default, the {FunctionsFramework.logger} will be used, but you can
|
124
|
+
# override that by providing your own logger. In particular, to disable
|
125
|
+
# logging, you can pass `Logger.new(nil)`.
|
81
126
|
#
|
82
127
|
# @param name [String] The name of the function to call
|
83
128
|
# @param request [Rack::Request] The Rack request to send
|
129
|
+
# @param globals [Hash] Do not run startup tasks, and instead provide the
|
130
|
+
# globals directly. Optional.
|
131
|
+
# @param logger [Logger] Use the given logger instead of the Functions
|
132
|
+
# Framework's global logger. Optional.
|
84
133
|
# @return [Rack::Response]
|
85
134
|
#
|
86
|
-
def call_http name, request
|
87
|
-
|
135
|
+
def call_http name, request, globals: nil, logger: nil
|
136
|
+
globals ||= run_startup_tasks name, logger: logger, lenient: true
|
137
|
+
function = Testing.current_registry[name]
|
88
138
|
case function&.type
|
89
139
|
when :http
|
90
|
-
Testing.interpret_response
|
140
|
+
Testing.interpret_response do
|
141
|
+
function.call request, globals: globals, logger: logger
|
142
|
+
end
|
91
143
|
when nil
|
92
144
|
raise "Unknown function name #{name}"
|
93
145
|
else
|
@@ -99,15 +151,28 @@ module FunctionsFramework
|
|
99
151
|
# Call the given event function for testing. The underlying function must
|
100
152
|
# be of type :cloud_event`.
|
101
153
|
#
|
154
|
+
# By default, the startup tasks will be run for the given function if they
|
155
|
+
# have not already been run. You can, however, disable running startup
|
156
|
+
# tasks by providing an explicit globals hash.
|
157
|
+
#
|
158
|
+
# By default, the {FunctionsFramework.logger} will be used, but you can
|
159
|
+
# override that by providing your own logger. In particular, to disable
|
160
|
+
# logging, you can pass `Logger.new(nil)`.
|
161
|
+
#
|
102
162
|
# @param name [String] The name of the function to call
|
103
163
|
# @param event [::CloudEvents::Event] The event to send
|
164
|
+
# @param globals [Hash] Do not run startup tasks, and instead provide the
|
165
|
+
# globals directly. Optional.
|
166
|
+
# @param logger [Logger] Use the given logger instead of the Functions
|
167
|
+
# Framework's global logger. Optional.
|
104
168
|
# @return [nil]
|
105
169
|
#
|
106
|
-
def call_event name, event
|
107
|
-
|
170
|
+
def call_event name, event, globals: nil, logger: nil
|
171
|
+
globals ||= run_startup_tasks name, logger: logger, lenient: true
|
172
|
+
function = Testing.current_registry[name]
|
108
173
|
case function&.type
|
109
174
|
when :cloud_event
|
110
|
-
function.
|
175
|
+
function.call event, globals: globals, logger: logger
|
111
176
|
nil
|
112
177
|
when nil
|
113
178
|
raise "Unknown function name #{name}"
|
@@ -208,27 +273,49 @@ module FunctionsFramework
|
|
208
273
|
extend self
|
209
274
|
|
210
275
|
@testing_registries = {}
|
276
|
+
@main_globals = {}
|
211
277
|
@mutex = ::Mutex.new
|
212
278
|
|
213
279
|
class << self
|
214
280
|
## @private
|
215
281
|
def load_for_testing path
|
216
282
|
old_registry = ::FunctionsFramework.global_registry
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
283
|
+
::Thread.current[:functions_framework_testing_registry] =
|
284
|
+
@mutex.synchronize do
|
285
|
+
if @testing_registries.key? path
|
286
|
+
::FunctionsFramework.global_registry = @testing_registries[path]
|
287
|
+
else
|
288
|
+
new_registry = ::FunctionsFramework::Registry.new
|
289
|
+
::FunctionsFramework.global_registry = new_registry
|
290
|
+
::Kernel.load path
|
291
|
+
@testing_registries[path] = new_registry
|
292
|
+
end
|
225
293
|
end
|
226
|
-
|
294
|
+
::Thread.current[:functions_framework_testing_globals] = {}
|
227
295
|
yield
|
228
296
|
ensure
|
297
|
+
::Thread.current[:functions_framework_testing_registry] = nil
|
298
|
+
::Thread.current[:functions_framework_testing_globals] = nil
|
229
299
|
::FunctionsFramework.global_registry = old_registry
|
230
300
|
end
|
231
301
|
|
302
|
+
## @private
|
303
|
+
def current_registry
|
304
|
+
::Thread.current[:functions_framework_testing_registry] ||
|
305
|
+
::FunctionsFramework.global_registry
|
306
|
+
end
|
307
|
+
|
308
|
+
## @private
|
309
|
+
def current_globals name, globals = nil
|
310
|
+
name = name.to_s
|
311
|
+
globals_by_name = ::Thread.current[:functions_framework_testing_globals] || @main_globals
|
312
|
+
if globals
|
313
|
+
globals_by_name[name] = globals
|
314
|
+
else
|
315
|
+
globals_by_name[name]
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
232
319
|
## @private
|
233
320
|
def interpret_response
|
234
321
|
response =
|
@@ -279,9 +366,10 @@ module FunctionsFramework
|
|
279
366
|
::Rack::RACK_ERRORS => ::StringIO.new
|
280
367
|
}
|
281
368
|
headers.each do |header|
|
282
|
-
|
369
|
+
case header
|
370
|
+
when String
|
283
371
|
name, value = header.split ":"
|
284
|
-
|
372
|
+
when ::Array
|
285
373
|
name, value = header
|
286
374
|
end
|
287
375
|
next unless name && value
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: functions_framework
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Azuma
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cloud_events
|
@@ -52,7 +52,10 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '2.1'
|
55
|
-
description: The Functions Framework
|
55
|
+
description: The Functions Framework is an open source framework for writing lightweight,
|
56
|
+
portable Ruby functions that run in a serverless environment. Functions written
|
57
|
+
to this Framework will run on Google Cloud Functions, Google Cloud Run, or any other
|
58
|
+
Knative-based environment.
|
56
59
|
email:
|
57
60
|
- dazuma@google.com
|
58
61
|
executables:
|
@@ -84,10 +87,10 @@ homepage: https://github.com/GoogleCloudPlatform/functions-framework-ruby
|
|
84
87
|
licenses:
|
85
88
|
- Apache-2.0
|
86
89
|
metadata:
|
87
|
-
changelog_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.
|
90
|
+
changelog_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.8.0/file.CHANGELOG.html
|
88
91
|
source_code_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby
|
89
92
|
bug_tracker_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby/issues
|
90
|
-
documentation_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.
|
93
|
+
documentation_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.8.0
|
91
94
|
post_install_message:
|
92
95
|
rdoc_options: []
|
93
96
|
require_paths:
|
@@ -96,14 +99,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
99
|
requirements:
|
97
100
|
- - ">="
|
98
101
|
- !ruby/object:Gem::Version
|
99
|
-
version: 2.
|
102
|
+
version: 2.5.0
|
100
103
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
104
|
requirements:
|
102
105
|
- - ">="
|
103
106
|
- !ruby/object:Gem::Version
|
104
107
|
version: '0'
|
105
108
|
requirements: []
|
106
|
-
rubygems_version: 3.
|
109
|
+
rubygems_version: 3.2.11
|
107
110
|
signing_key:
|
108
111
|
specification_version: 4
|
109
112
|
summary: Functions Framework for Ruby
|