functions_framework 0.5.2 → 0.6.0
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/CHANGELOG.md +6 -0
- data/bin/functions-framework +1 -1
- data/bin/functions-framework-ruby +1 -1
- data/docs/writing-functions.md +62 -0
- data/lib/functions_framework.rb +24 -0
- data/lib/functions_framework/cli.rb +97 -22
- data/lib/functions_framework/registry.rb +38 -5
- data/lib/functions_framework/server.rb +1 -1
- data/lib/functions_framework/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67b0d5e61bb58f49adefbff9794c960438c1577b4e3d54ee5bf072b134834b71
|
4
|
+
data.tar.gz: 9cb295fd8ba39ea9ad5e8eab1c3055eaa18dddef5a1838cc58d7dcdb2d12fc5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99e58e1511a97dc9e8643a034bc089f5bb4b71d5f842b507151d8728f058a3763b0f55dcd952ac71f212773e6087cb333fcfebb814a33f57cd2057fde09d6272
|
7
|
+
data.tar.gz: e5afdebdc20c069c247e58196d75787a8c3b3e51924b7714851006ade62dc70bce62cd9e42501528103eb41f3ffc2d6239e9dd8c6650798bdbd1693268fa431a
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### v0.6.0 / 2020-09-17
|
4
|
+
|
5
|
+
* ADDED: You can use the --version flag to print the framework version
|
6
|
+
* ADDED: You can use the --verify flag to verify that a given function is defined
|
7
|
+
* ADDED: You can now define blocks that are executed at server startup
|
8
|
+
|
3
9
|
### v0.5.2 / 2020-09-06
|
4
10
|
|
5
11
|
* FIXED: Use global $stderr rather than STDERR for logger
|
data/bin/functions-framework
CHANGED
data/docs/writing-functions.md
CHANGED
@@ -197,6 +197,68 @@ FunctionsFramework.http "error_reporter" do |request|
|
|
197
197
|
end
|
198
198
|
```
|
199
199
|
|
200
|
+
## Shared resources
|
201
|
+
|
202
|
+
Generally, functions should be self-contained and stateless, and should not use
|
203
|
+
or share any global state in the Ruby VM. This is because serverless runtimes
|
204
|
+
may spin up or terminate instances of your app at any time, and because a
|
205
|
+
single instance may be running multiple functions at a time in separate threads.
|
206
|
+
|
207
|
+
However, it is sometimes useful to share a resource across multiple function
|
208
|
+
invocations that run on the same Ruby instance. For example, you might establish
|
209
|
+
a single connection to a remote database or other service, and share it across
|
210
|
+
function invocations to avoid incurring the overhead of re-establishing it
|
211
|
+
for every function invocation.
|
212
|
+
|
213
|
+
When using a shared resource, it is important to keep three things in mind:
|
214
|
+
|
215
|
+
1. **The shared resource should be thread-safe.** This is because serverless
|
216
|
+
runtimes such as Google Cloud Functions may run multiple functions at a time
|
217
|
+
in separate threads.
|
218
|
+
|
219
|
+
2. **Use `FunctionsFramework.on_startup` to initialize shared resources.**
|
220
|
+
Do not initialize a shared resource at the top level of your app. This is
|
221
|
+
because serverless runtimes may load your files (and thus execute any Ruby
|
222
|
+
code at the top level) in a build/deployment environment that may not be
|
223
|
+
equipped to support the resource. Instead, initialize resources in a
|
224
|
+
`FunctionsFramework.on_startup` block, which the Functions Framework will
|
225
|
+
call only just before starting a server.
|
226
|
+
|
227
|
+
For example:
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
require "functions_framework"
|
231
|
+
|
232
|
+
# This local variable is lexically shared among all blocks.
|
233
|
+
storage_client = nil
|
234
|
+
|
235
|
+
# Do not create the storage client here. This may run during deployment
|
236
|
+
# when, e.g., your storage credentials are not accessible.
|
237
|
+
# require "google/cloud/storage"
|
238
|
+
# storage_client = Google::Cloud::Storage.new # <- may fail
|
239
|
+
|
240
|
+
# Use an on_startup block to initialize the shared client.
|
241
|
+
# This block runs only when the framework is starting an actual server,
|
242
|
+
# and is guaranteed to complete before any functions are executed.
|
243
|
+
FunctionsFramework.on_startup do
|
244
|
+
require "google/cloud/storage"
|
245
|
+
storage_client = Google::Cloud::Storage.new
|
246
|
+
end
|
247
|
+
|
248
|
+
# The storage_client is shared among all function invocations
|
249
|
+
FunctionsFramework.http "storage_example" do |request|
|
250
|
+
bucket = storage_client.bucket "my-bucket"
|
251
|
+
file = bucket.file "path/to/my-file.txt"
|
252
|
+
file.download.to_s
|
253
|
+
end
|
254
|
+
```
|
255
|
+
|
256
|
+
3. **There is no guaranteed cleanup hook.** The Functions Framework does not
|
257
|
+
provide a guaranteed way to register a cleanup task. You can register a
|
258
|
+
`Kernel.at_exit` task, but remember that it is possible for the Ruby VM to
|
259
|
+
terminate without calling it. It is strongly recommended that you use
|
260
|
+
resources that do not require "cleanup".
|
261
|
+
|
200
262
|
## Structuring a project
|
201
263
|
|
202
264
|
A Functions Framework based "project" or "application" is a typical Ruby
|
data/lib/functions_framework.rb
CHANGED
@@ -165,6 +165,29 @@ module FunctionsFramework
|
|
165
165
|
self
|
166
166
|
end
|
167
167
|
|
168
|
+
##
|
169
|
+
# Define a server startup task. This is useful for initializing shared
|
170
|
+
# resources that should be accessible across all function invocations in
|
171
|
+
# this Ruby VM.
|
172
|
+
#
|
173
|
+
# Startup tasks are run just before a server starts. All startup tasks are
|
174
|
+
# guaranteed to complete before any function executes. However, they are
|
175
|
+
# run only when preparing to run functions. They are not run, for example,
|
176
|
+
# if an app is loaded to verify its integrity during deployment.
|
177
|
+
#
|
178
|
+
# Startup tasks are passed two arguments: the {FunctionsFramework::Function}
|
179
|
+
# identifying the function to execute, and the
|
180
|
+
# {FunctionsFramework::Server::Config} specifying the (frozen) server
|
181
|
+
# configuration. Tasks have no return value.
|
182
|
+
#
|
183
|
+
# @param block [Proc] The startup task
|
184
|
+
# @return [self]
|
185
|
+
#
|
186
|
+
def on_startup &block
|
187
|
+
global_registry.add_startup_task(&block)
|
188
|
+
self
|
189
|
+
end
|
190
|
+
|
168
191
|
##
|
169
192
|
# Start the functions framework server in the background. The server will
|
170
193
|
# look up the given target function name in the global registry.
|
@@ -184,6 +207,7 @@ module FunctionsFramework
|
|
184
207
|
raise ::ArgumentError, "Undefined function: #{target.inspect}" if function.nil?
|
185
208
|
end
|
186
209
|
server = Server.new function, &block
|
210
|
+
global_registry.run_startup_tasks server
|
187
211
|
server.respond_to_signals
|
188
212
|
server.start
|
189
213
|
end
|
@@ -42,8 +42,31 @@ module FunctionsFramework
|
|
42
42
|
@detailed_errors = nil
|
43
43
|
@signature_type = ::ENV["FUNCTION_SIGNATURE_TYPE"]
|
44
44
|
@logging_level = init_logging_level
|
45
|
+
@what_to_do = nil
|
46
|
+
@error_message = nil
|
47
|
+
@exit_code = 0
|
45
48
|
end
|
46
49
|
|
50
|
+
##
|
51
|
+
# Determine if an error has occurred
|
52
|
+
#
|
53
|
+
# @return [boolean]
|
54
|
+
#
|
55
|
+
def error?
|
56
|
+
!@error_message.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# @return [Integer] The current exit status.
|
61
|
+
#
|
62
|
+
attr_reader :exit_code
|
63
|
+
|
64
|
+
##
|
65
|
+
# @return [String] The current error message.
|
66
|
+
# @return [nil] if no error has occurred.
|
67
|
+
#
|
68
|
+
attr_reader :error_message
|
69
|
+
|
47
70
|
##
|
48
71
|
# Parse the given command line arguments.
|
49
72
|
# Exits if argument parsing failed.
|
@@ -52,7 +75,7 @@ module FunctionsFramework
|
|
52
75
|
# @return [self]
|
53
76
|
#
|
54
77
|
def parse_args argv # rubocop:disable Metrics/MethodLength
|
55
|
-
option_parser = ::OptionParser.new do |op| # rubocop:disable Metrics/BlockLength
|
78
|
+
@option_parser = ::OptionParser.new do |op| # rubocop:disable Metrics/BlockLength
|
56
79
|
op.on "-t", "--target TARGET",
|
57
80
|
"Set the name of the function to execute (defaults to #{DEFAULT_TARGET})" do |val|
|
58
81
|
@target = val
|
@@ -84,55 +107,92 @@ module FunctionsFramework
|
|
84
107
|
op.on "--[no-]detailed-errors", "Set whether to show error details" do |val|
|
85
108
|
@detailed_errors = val
|
86
109
|
end
|
110
|
+
op.on "--verify", "Verify the app only, but do not run the server." do
|
111
|
+
@what_to_do ||= :verify
|
112
|
+
end
|
87
113
|
op.on "-v", "--verbose", "Increase log verbosity" do
|
88
114
|
@logging_level -= 1
|
89
115
|
end
|
90
116
|
op.on "-q", "--quiet", "Decrease log verbosity" do
|
91
117
|
@logging_level += 1
|
92
118
|
end
|
119
|
+
op.on "--version", "Display the framework version" do
|
120
|
+
@what_to_do ||= :version
|
121
|
+
end
|
93
122
|
op.on "--help", "Display help" do
|
94
|
-
|
95
|
-
exit
|
123
|
+
@what_to_do ||= :help
|
96
124
|
end
|
97
125
|
end
|
98
|
-
|
99
|
-
|
126
|
+
begin
|
127
|
+
@option_parser.parse! argv
|
128
|
+
error! "Unrecognized arguments: #{argv}\n#{@option_parser}", 2 unless argv.empty?
|
129
|
+
rescue ::OptionParser::ParseError => e
|
130
|
+
error! "#{e.message}\n#{@option_parser}", 2
|
131
|
+
end
|
100
132
|
self
|
101
133
|
end
|
102
134
|
|
103
135
|
##
|
104
|
-
#
|
105
|
-
#
|
136
|
+
# Perform the requested function.
|
137
|
+
#
|
138
|
+
# * If the `--version` flag was given, display the version.
|
139
|
+
# * If the `--help` flag was given, display online help.
|
140
|
+
# * If the `--verify` flag was given, load and verify the function,
|
141
|
+
# displaying any errors, then exit without starting a server.
|
142
|
+
# * Otherwise, start the configured server and block until it stops.
|
106
143
|
#
|
107
144
|
# @return [self]
|
108
145
|
#
|
109
146
|
def run
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
147
|
+
return self if error?
|
148
|
+
case @what_to_do
|
149
|
+
when :version
|
150
|
+
puts ::FunctionsFramework::VERSION
|
151
|
+
when :help
|
152
|
+
puts @option_parser
|
153
|
+
when :verify
|
154
|
+
begin
|
155
|
+
load_function
|
156
|
+
puts "OK"
|
157
|
+
rescue ::StandardError => e
|
158
|
+
error! e.message
|
159
|
+
end
|
160
|
+
else
|
161
|
+
begin
|
162
|
+
start_server.wait_until_stopped
|
163
|
+
rescue ::StandardError => e
|
164
|
+
error! e.message
|
165
|
+
end
|
114
166
|
end
|
115
|
-
server.wait_until_stopped
|
116
167
|
self
|
117
168
|
end
|
118
169
|
|
119
170
|
##
|
120
|
-
#
|
171
|
+
# Finish the CLI, displaying any error status and exiting with the current
|
172
|
+
# exit code. Never returns.
|
173
|
+
#
|
174
|
+
def complete
|
175
|
+
warn @error_message if @error_message
|
176
|
+
exit @exit_code
|
177
|
+
end
|
178
|
+
|
179
|
+
##
|
180
|
+
# Load the source and get and verify the requested function.
|
121
181
|
# If a validation error occurs, raise an exception.
|
122
|
-
# This is used for testing the CLI.
|
123
182
|
#
|
124
|
-
# @return [FunctionsFramework::
|
183
|
+
# @return [FunctionsFramework::Function]
|
125
184
|
#
|
126
185
|
# @private
|
127
186
|
#
|
128
|
-
def
|
187
|
+
def load_function
|
129
188
|
::FunctionsFramework.logger.level = @logging_level
|
130
|
-
::FunctionsFramework.logger.info "FunctionsFramework v#{VERSION}
|
189
|
+
::FunctionsFramework.logger.info "FunctionsFramework v#{VERSION}"
|
131
190
|
::ENV["FUNCTION_TARGET"] = @target
|
132
191
|
::ENV["FUNCTION_SOURCE"] = @source
|
133
192
|
::ENV["FUNCTION_SIGNATURE_TYPE"] = @signature_type
|
134
193
|
::FunctionsFramework.logger.info "FunctionsFramework: Loading functions from #{@source.inspect}..."
|
135
194
|
load @source
|
195
|
+
::FunctionsFramework.logger.info "FunctionsFramework: Looking for function name #{@target.inspect}..."
|
136
196
|
function = ::FunctionsFramework.global_registry[@target]
|
137
197
|
raise "Undefined function: #{@target.inspect}" if function.nil?
|
138
198
|
unless @signature_type.nil? ||
|
@@ -140,6 +200,20 @@ module FunctionsFramework
|
|
140
200
|
["cloudevent", "event"].include?(@signature_type) && function.type == :cloud_event
|
141
201
|
raise "Function #{@target.inspect} does not match type #{@signature_type}"
|
142
202
|
end
|
203
|
+
function
|
204
|
+
end
|
205
|
+
|
206
|
+
##
|
207
|
+
# Start the configured server and return the running server object.
|
208
|
+
# If a validation error occurs, raise an exception.
|
209
|
+
#
|
210
|
+
# @return [FunctionsFramework::Server]
|
211
|
+
#
|
212
|
+
# @private
|
213
|
+
#
|
214
|
+
def start_server
|
215
|
+
function = load_function
|
216
|
+
::FunctionsFramework.logger.info "FunctionsFramework: Starting server..."
|
143
217
|
::FunctionsFramework.start function do |config|
|
144
218
|
config.rack_env = @env
|
145
219
|
config.port = @port
|
@@ -160,12 +234,13 @@ module FunctionsFramework
|
|
160
234
|
end
|
161
235
|
|
162
236
|
##
|
163
|
-
#
|
164
|
-
# @param message [String]
|
237
|
+
# Set the error status.
|
238
|
+
# @param message [String] Error message.
|
239
|
+
# @param code [Integer] Exit code, defaults to 1.
|
165
240
|
#
|
166
|
-
def error message
|
167
|
-
|
168
|
-
|
241
|
+
def error! message, code = 1
|
242
|
+
@error_message = message
|
243
|
+
@exit_code = code
|
169
244
|
end
|
170
245
|
end
|
171
246
|
end
|
@@ -25,8 +25,9 @@ module FunctionsFramework
|
|
25
25
|
# Create a new empty registry.
|
26
26
|
#
|
27
27
|
def initialize
|
28
|
-
|
28
|
+
@mutex = ::Monitor.new
|
29
29
|
@functions = {}
|
30
|
+
@start_tasks = []
|
30
31
|
end
|
31
32
|
|
32
33
|
##
|
@@ -37,7 +38,7 @@ module FunctionsFramework
|
|
37
38
|
# @return [nil] if the function is not found
|
38
39
|
#
|
39
40
|
def [] name
|
40
|
-
@functions[name.to_s]
|
41
|
+
@mutex.synchronize { @functions[name.to_s] }
|
41
42
|
end
|
42
43
|
|
43
44
|
##
|
@@ -46,7 +47,21 @@ module FunctionsFramework
|
|
46
47
|
# @return [Array<String>]
|
47
48
|
#
|
48
49
|
def names
|
49
|
-
@functions.keys.sort
|
50
|
+
@mutex.synchronize { @functions.keys.sort }
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Run all startup tasks.
|
55
|
+
#
|
56
|
+
# @param server [FunctionsFramework::Server] The server that is starting.
|
57
|
+
# @return [self]
|
58
|
+
#
|
59
|
+
def run_startup_tasks server
|
60
|
+
tasks = @mutex.synchronize { @start_tasks.dup }
|
61
|
+
tasks.each do |task|
|
62
|
+
task.call server.function, server.config
|
63
|
+
end
|
64
|
+
self
|
50
65
|
end
|
51
66
|
|
52
67
|
##
|
@@ -68,7 +83,7 @@ module FunctionsFramework
|
|
68
83
|
#
|
69
84
|
def add_http name, &block
|
70
85
|
name = name.to_s
|
71
|
-
synchronize do
|
86
|
+
@mutex.synchronize do
|
72
87
|
raise ::ArgumentError, "Function already defined: #{name}" if @functions.key? name
|
73
88
|
@functions[name] = Function.new name, :http, &block
|
74
89
|
end
|
@@ -89,11 +104,29 @@ module FunctionsFramework
|
|
89
104
|
#
|
90
105
|
def add_cloud_event name, &block
|
91
106
|
name = name.to_s
|
92
|
-
synchronize do
|
107
|
+
@mutex.synchronize do
|
93
108
|
raise ::ArgumentError, "Function already defined: #{name}" if @functions.key? name
|
94
109
|
@functions[name] = Function.new name, :cloud_event, &block
|
95
110
|
end
|
96
111
|
self
|
97
112
|
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Add a startup task.
|
116
|
+
#
|
117
|
+
# Startup tasks are generally run just before a server starts. They are
|
118
|
+
# passed two arguments: the {FunctionsFramework::Function} identifying the
|
119
|
+
# function to execute, and the {FunctionsFramework::Server::Config}
|
120
|
+
# specifying the (frozen) server configuration. Tasks have no return value.
|
121
|
+
#
|
122
|
+
# @param block [Proc] The startup task
|
123
|
+
# @return [self]
|
124
|
+
#
|
125
|
+
def add_startup_task &block
|
126
|
+
@mutex.synchronize do
|
127
|
+
@start_tasks << block
|
128
|
+
end
|
129
|
+
self
|
130
|
+
end
|
98
131
|
end
|
99
132
|
end
|
@@ -82,9 +82,9 @@ module FunctionsFramework
|
|
82
82
|
@server.max_threads = @config.max_threads
|
83
83
|
@server.leak_stack_on_error = @config.show_error_details?
|
84
84
|
@server.binder.add_tcp_listener @config.bind_addr, @config.port
|
85
|
-
@server.run true
|
86
85
|
@config.logger.info "FunctionsFramework: Serving function #{@function.name.inspect}" \
|
87
86
|
" on port #{@config.port}..."
|
87
|
+
@server.run true
|
88
88
|
end
|
89
89
|
end
|
90
90
|
self
|
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.6.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: 2020-09-
|
11
|
+
date: 2020-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cloud_events
|
@@ -87,10 +87,10 @@ homepage: https://github.com/GoogleCloudPlatform/functions-framework-ruby
|
|
87
87
|
licenses:
|
88
88
|
- Apache-2.0
|
89
89
|
metadata:
|
90
|
-
changelog_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.
|
90
|
+
changelog_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.6.0/file.CHANGELOG.html
|
91
91
|
source_code_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby
|
92
92
|
bug_tracker_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby/issues
|
93
|
-
documentation_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.
|
93
|
+
documentation_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.6.0
|
94
94
|
post_install_message:
|
95
95
|
rdoc_options: []
|
96
96
|
require_paths:
|