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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e8f0eda1f51ead6ec3bd53d711d2a7816b5077351f56bfa491311c73461a02d
4
- data.tar.gz: 21921bddd89a66dc95245850cb475967fd5e8de7c678bf4e154d07d7a8264290
3
+ metadata.gz: 67b0d5e61bb58f49adefbff9794c960438c1577b4e3d54ee5bf072b134834b71
4
+ data.tar.gz: 9cb295fd8ba39ea9ad5e8eab1c3055eaa18dddef5a1838cc58d7dcdb2d12fc5e
5
5
  SHA512:
6
- metadata.gz: '08633f2559e4d787f23e2711d40ff17437e74cd9ee4589346bd3c4f567815c422cb6e2827125bad2de5d23f4e52c3a09162db609a4fd62aee4374144ae16686e'
7
- data.tar.gz: 759e2fe6a395fe1ea53ba90bbf5026635429fbdacf161b3f87879d31753532b7ef005fbfce797c6ca599bca441f1919b4d61a508cf43877fee18090a4e8f9fec
6
+ metadata.gz: 99e58e1511a97dc9e8643a034bc089f5bb4b71d5f842b507151d8728f058a3763b0f55dcd952ac71f212773e6087cb333fcfebb814a33f57cd2057fde09d6272
7
+ data.tar.gz: e5afdebdc20c069c247e58196d75787a8c3b3e51924b7714851006ade62dc70bce62cd9e42501528103eb41f3ffc2d6239e9dd8c6650798bdbd1693268fa431a
@@ -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
@@ -16,4 +16,4 @@
16
16
 
17
17
  require "functions_framework/cli"
18
18
 
19
- ::FunctionsFramework::CLI.new.parse_args(::ARGV).run
19
+ ::FunctionsFramework::CLI.new.parse_args(::ARGV).run.complete
@@ -16,4 +16,4 @@
16
16
 
17
17
  require "functions_framework/cli"
18
18
 
19
- ::FunctionsFramework::CLI.new.parse_args(::ARGV).run
19
+ ::FunctionsFramework::CLI.new.parse_args(::ARGV).run.complete
@@ -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
@@ -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
- puts op
95
- exit
123
+ @what_to_do ||= :help
96
124
  end
97
125
  end
98
- option_parser.parse! argv
99
- error "Unrecognized arguments: #{argv}\n#{op}" unless argv.empty?
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
- # Run the configured server, and block until it stops.
105
- # If a validation error occurs, print a message and exit.
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
- begin
111
- server = start_server
112
- rescue ::StandardError => e
113
- error e.message
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
- # Start the configured server and return the running server object.
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::Server]
183
+ # @return [FunctionsFramework::Function]
125
184
  #
126
185
  # @private
127
186
  #
128
- def start_server
187
+ def load_function
129
188
  ::FunctionsFramework.logger.level = @logging_level
130
- ::FunctionsFramework.logger.info "FunctionsFramework v#{VERSION} server starting."
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
- # Print the given error message and exit.
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
- warn message
168
- exit 1
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
- super()
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
@@ -17,5 +17,5 @@ module FunctionsFramework
17
17
  # Version of the Ruby Functions Framework
18
18
  # @return [String]
19
19
  #
20
- VERSION = "0.5.2".freeze
20
+ VERSION = "0.6.0".freeze
21
21
  end
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.5.2
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-07 00:00:00.000000000 Z
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.5.2/file.CHANGELOG.html
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.5.2
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: