functions_framework 0.5.2 → 0.6.0

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