gruf 2.15.0 → 2.16.1

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: a807b73598db4fb6caf6e0ed79d164342c1f6f0cfa4b0cf03f83f40cb71baabf
4
- data.tar.gz: c750eac85c6739048498c5579dabbcf268bc796e998e26c14c7b6e2a96ad3d88
3
+ metadata.gz: 1f7ea5bc2b3a0c64008a565c470d19b948814f9c636ccdea72b93703f384ca77
4
+ data.tar.gz: f5a23c82e85208104baec460d93f650e66189dfb3b27dc0c409dc32b21bcda4a
5
5
  SHA512:
6
- metadata.gz: 0ab26cdfa7b95fc92694dc328ac66b32994f35ddfb5bf9f63fb235256853f77f1b4c382bb01870c1afd5d037a2e915cacc0d5c8c307c30d480b90f5d0b373736
7
- data.tar.gz: 06b7a29c4f6bd167178e68d77c4bc68fa704233232b405fa193db8fb4721895391fce58ab70fd06c92ff6a31a4276f20ac8a00a834e0f4c8757ad03f153abeba
6
+ metadata.gz: b227b3fc5253dbace401291e9643e2703244e9e83020d9516358ed78caef24f56668d61f9411f1b2dc25fa5854d711cc5b54346937f71d6835e00f98cd003e46
7
+ data.tar.gz: 61f863b87320654ec2b247953b3817ae854ae4e601bc0c09206803a8cc2e453a8f80c77b0679b2cb03f870f5a0ba6c44ca8be8bc2ea6f1d34d0c03c7527b888b
data/CHANGELOG.md CHANGED
@@ -2,8 +2,28 @@ Changelog for the gruf gem. This includes internal history before the gem was ma
2
2
 
3
3
  ### Pending release
4
4
 
5
+ ### 2.16.1
6
+
7
+ * Fix issue where default gRPC health check was loaded even if unused or not desired; now only loaded when requested
8
+
9
+ ### 2.16.0
10
+
11
+ - Add opt-in ability to serve the official [gRPC health check](https://github.com/grpc/grpc/blob/master/src/ruby/pb/grpc/health/v1/health_services_pb.rb)
12
+ automatically via `health_check_enabled` configuration option (or `GRUF_HEALTH_CHECK_ENABLED` environment
13
+ variable).
14
+ - Add `health_check_hook` configuration option to implement a custom response for the above gRPC built-in health check
15
+ - [#156] Allow passing a specific list of services to run via the gruf binstub
16
+ - [#163] Add `context` hash attribute to `Gruf::Controllers::Request` to allow interceptors to pass information down
17
+ to a gruf controller
18
+ - Drop Ruby 2.6 support (EOL'ed on March 31st, 2022)
19
+
20
+ ### 2.15.1
21
+
22
+ - Fix issue where GRPC_SERVER_POOL_KEEP_ALIVE and GRPC_SERVER_POLL_PERIOD when set via ENV are not cast to int
23
+
5
24
  ### 2.15.0
6
25
 
26
+ - NOTE: This changes the way that gruf controllers are autoloaded. See [UPGRADING.md] for more details.
7
27
  - Autoload Gruf Controllers with zeitwerk, allowing for code reloading in development environments
8
28
  - Add `GRUF_CONTROLLERS_PATH` ENV to allow ENV-based runtime configuration of path to gruf controller files
9
29
  - Move `ServiceBinder` from instance to static class for performance improvements
data/gruf.gemspec CHANGED
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
32
32
  spec.executables << 'gruf'
33
33
  spec.require_paths = ['lib']
34
34
 
35
- spec.required_ruby_version = '>= 2.6', '< 3.2'
35
+ spec.required_ruby_version = '>= 2.7', '< 3.2'
36
36
 
37
37
  spec.metadata = {
38
38
  'bug_tracker_uri' => 'https://github.com/bigcommerce/gruf/issues',
@@ -44,12 +44,7 @@ Gem::Specification.new do |spec|
44
44
  }
45
45
 
46
46
  spec.add_development_dependency 'bundler-audit', '>= 0.6'
47
- # rubocop:disable Gemspec/RubyVersionGlobalsUsage
48
- spec.add_development_dependency(
49
- 'factory_bot',
50
- (Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5') ? '>= 6.1' : '~> 5.2')
51
- )
52
- # rubocop:enable Gemspec/RubyVersionGlobalsUsage
47
+ spec.add_development_dependency 'factory_bot', '>= 6.1'
53
48
  spec.add_development_dependency 'ffaker', '>= 2.15'
54
49
  spec.add_development_dependency 'pry', '~> 0.12'
55
50
  spec.add_development_dependency 'pry-byebug', '>= 3.9'
@@ -52,19 +52,11 @@ module Gruf
52
52
  #
53
53
  def run
54
54
  exception = nil
55
- # wait to load controllers until last possible second to allow late configuration
56
- ::Gruf.autoloaders.load!(controllers_path: Gruf.controllers_path)
57
- # allow lazy registering globally as late as possible, this allows more flexible binstub injections
58
- @services = ::Gruf.services unless @services&.any?
59
55
 
60
- unless @services.any?
61
- raise NoServicesBoundError,
62
- 'No services bound to this gruf process; please bind a service to a Gruf controller ' \
63
- 'to start the server successfully'
64
- end
56
+ # allow lazy registering globally as late as possible, this allows more flexible binstub injections
57
+ register_services!
65
58
 
66
59
  begin
67
- @services.each { |s| @server.add_service(s) }
68
60
  @hook_executor.call(:before_server_start, server: @server)
69
61
  @server.start!
70
62
  rescue StandardError => e
@@ -84,14 +76,15 @@ module Gruf
84
76
  # Setup options for CLI execution and configure Gruf based on inputs
85
77
  #
86
78
  def setup!
87
- opts = parse_options
79
+ @options = parse_options
88
80
 
89
- Gruf.server_binding_url = opts[:host] if opts[:host]
90
- if opts.suppress_default_interceptors?
81
+ Gruf.server_binding_url = @options[:host] if @options[:host]
82
+ if @options.suppress_default_interceptors?
91
83
  Gruf.interceptors.remove(Gruf::Interceptors::ActiveRecord::ConnectionReset)
92
84
  Gruf.interceptors.remove(Gruf::Interceptors::Instrumentation::OutputMetadataTimer)
93
85
  end
94
- Gruf.backtrace_on_error = true if opts.backtrace_on_error?
86
+ Gruf.backtrace_on_error = true if @options.backtrace_on_error?
87
+ Gruf.health_check_enabled = true if @options.health_check?
95
88
  end
96
89
 
97
90
  ##
@@ -106,6 +99,8 @@ module Gruf
106
99
  exit(0)
107
100
  end
108
101
  o.string '--host', 'Specify the binding url for the gRPC service'
102
+ o.string '--services', 'Optional. Run gruf with only the passed gRPC service classes (comma-separated)'
103
+ o.bool '--health-check', 'Serve the default gRPC health check (defaults to false). '
109
104
  o.bool '--suppress-default-interceptors', 'Do not use the default interceptors'
110
105
  o.bool '--backtrace-on-error', 'Push backtraces on exceptions to the error serializer'
111
106
  o.null '-v', '--version', 'print gruf version' do
@@ -114,6 +109,83 @@ module Gruf
114
109
  end
115
110
  end
116
111
  end
112
+
113
+ ##
114
+ # Register services; note that this happens after gruf is initialized, and right before the server is run.
115
+ # This will interpret the services to run in the following precedence:
116
+ # 1. initializer arguments to the executor
117
+ # 2. ARGV options (the --services option)
118
+ # 3. services set to the global gruf configuration (Gruf.services)
119
+ #
120
+ def register_services!
121
+ # wait to load controllers until last possible second to allow late configuration
122
+ ::Gruf.autoloaders.load!(controllers_path: Gruf.controllers_path)
123
+
124
+ services = determine_services(@services)
125
+ services = bind_health_check!(services) if health_check_enabled?
126
+
127
+ services.map! { |s| s.is_a?(Class) ? s : s.constantize }
128
+
129
+ if services.any?
130
+ services.each { |s| @server.add_service(s) }
131
+ return
132
+ end
133
+
134
+ raise NoServicesBoundError
135
+ rescue NoServicesBoundError
136
+ @logger.fatal 'FATAL ERROR: No services bound to this gruf process; please bind a service to a Gruf ' \
137
+ 'controller to start the server successfully'
138
+ exit(1)
139
+ rescue NameError => e
140
+ @logger.fatal 'FATAL ERROR: Could not start server; passed services to run are not loaded or valid ' \
141
+ "constants: #{e.message}"
142
+ exit(1)
143
+ rescue StandardError => e
144
+ @logger.fatal "FATAL ERROR: Could not start server: #{e.message}"
145
+ exit(1)
146
+ end
147
+
148
+ ##
149
+ # @return [Boolean]
150
+ #
151
+ def health_check_enabled?
152
+ ::Gruf.health_check_enabled
153
+ end
154
+
155
+ ##
156
+ # Load the health check if enabled into the services array
157
+ #
158
+ # @param [Array<Class>] services
159
+ # @return [Array<Class>]
160
+ #
161
+ def bind_health_check!(services)
162
+ # do this here to trigger autoloading the controller in zeitwerk, since we don't explicitly load this
163
+ # controller. This binds the service and makes sure the method handlers are setup.
164
+ require 'gruf/controllers/health_controller'
165
+ # if we're already bound to the services array (say someone explicitly passes the health check in, skip)
166
+ return services if services.include?(::Grpc::Health::V1::Health::Service)
167
+
168
+ # otherwise, manually add the grpc service
169
+ services << ::Grpc::Health::V1::Health::Service
170
+ services
171
+ end
172
+
173
+ ##
174
+ # Determine how we load services (initializer -> ARGV -> Gruf.services)
175
+ #
176
+ # @return [Array<Class>]
177
+ #
178
+ def determine_services(services = [])
179
+ # first check initializer arguments
180
+ return services if services.any?
181
+
182
+ # next check CLI arguments
183
+ services = @options[:services].to_s.split(',').map(&:strip).uniq
184
+ # finally, if none, use global gruf autoloaded services
185
+ services = (::Gruf.services || []) unless services.any?
186
+
187
+ services
188
+ end
117
189
  end
118
190
  end
119
191
  end
@@ -77,6 +77,13 @@ module Gruf
77
77
  # @return [Integer] Internal cache expiry period (in seconds) for the SynchronizedClient
78
78
  # @!attribute rpc_server_options
79
79
  # @return [Hash] A hash of RPC options for GRPC server configuration
80
+ # @!attribute health_check_enabled
81
+ # @return [Boolean] If true, will load and register `Gruf::Controllers::HealthController` with the default gRPC
82
+ # health check to the loaded gRPC server
83
+ # @!attribute health_check_hook
84
+ # @return [NilClass]
85
+ # @return [Proc] If set, will call this in the gRPC health check. It is required to return a
86
+ # `::Grpc::Health::V1::HealthCheckResponse` object in this proc to indicate the health of the server.
80
87
  VALID_CONFIG_KEYS = {
81
88
  root_path: '',
82
89
  server_binding_url: '0.0.0.0:9001',
@@ -101,6 +108,8 @@ module Gruf
101
108
  use_exception_message: true,
102
109
  internal_error_message: 'Internal Server Error',
103
110
  event_listener_proc: nil,
111
+ health_check_enabled: false,
112
+ health_check_hook: nil,
104
113
  synchronized_client_internal_cache_expiry: 60,
105
114
  rpc_server_options: {
106
115
  pool_size: GRPC::RpcServer::DEFAULT_POOL_SIZE,
@@ -172,8 +181,8 @@ module Gruf
172
181
  max_waiting_requests: ::ENV.fetch('GRPC_SERVER_MAX_WAITING_REQUESTS',
173
182
  GRPC::RpcServer::DEFAULT_MAX_WAITING_REQUESTS).to_i,
174
183
  pool_size: ::ENV.fetch('GRPC_SERVER_POOL_SIZE', GRPC::RpcServer::DEFAULT_POOL_SIZE).to_i,
175
- pool_keep_alive: ::ENV.fetch('GRPC_SERVER_POOL_KEEP_ALIVE', GRPC::Pool::DEFAULT_KEEP_ALIVE),
176
- poll_period: ::ENV.fetch('GRPC_SERVER_POLL_PERIOD', GRPC::RpcServer::DEFAULT_POLL_PERIOD),
184
+ pool_keep_alive: ::ENV.fetch('GRPC_SERVER_POOL_KEEP_ALIVE', GRPC::Pool::DEFAULT_KEEP_ALIVE).to_i,
185
+ poll_period: ::ENV.fetch('GRPC_SERVER_POLL_PERIOD', GRPC::RpcServer::DEFAULT_POLL_PERIOD).to_i,
177
186
  connect_md_proc: nil,
178
187
  server_args: {}
179
188
  }
@@ -181,6 +190,7 @@ module Gruf
181
190
  interceptors.use(::Gruf::Interceptors::ActiveRecord::ConnectionReset)
182
191
  interceptors.use(::Gruf::Interceptors::Instrumentation::OutputMetadataTimer)
183
192
  end
193
+ self.health_check_enabled = ::ENV.fetch('GRUF_HEALTH_CHECK_ENABLED', 0).to_i.positive?
184
194
  options
185
195
  end
186
196
 
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ require 'grpc/health/v1/health_services_pb'
19
+
20
+ module Gruf
21
+ module Controllers
22
+ ##
23
+ # Dynamic standard grpc health check controller. Can be used as-is, or can use ::Gruf.health_check_hook to
24
+ # provide custom responses.
25
+ #
26
+ class HealthController < Gruf::Controllers::Base
27
+ bind ::Grpc::Health::V1::Health::Service
28
+
29
+ def check
30
+ health_proc = ::Gruf.health_check_hook
31
+ return health_proc.call(request, error) if !health_proc.nil? && health_proc.respond_to?(:call)
32
+
33
+ ::Grpc::Health::V1::HealthCheckResponse.new(
34
+ status: ::Grpc::Health::V1::HealthCheckResponse::ServingStatus::SERVING
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
@@ -36,6 +36,11 @@ module Gruf
36
36
  # @!attribute [r] service
37
37
  # @return [Class] The GRPC service class for this request
38
38
  attr_reader :service
39
+ # @!attribute [r] context
40
+ # @return [::ActiveSupport::HashWithIndifferentAccess] An arbitrary hash of key/value entries that are
41
+ # accessible for interceptors, that can be used to shared information between interceptors and pass down into
42
+ # the controller.
43
+ attr_reader :context
39
44
 
40
45
  delegate :metadata, to: :active_call
41
46
  delegate :messages, :client_streamer?, :server_streamer?, :bidi_streamer?, :request_response?, to: :type
@@ -72,6 +77,7 @@ module Gruf
72
77
  @message = message
73
78
  @rpc_desc = rpc_desc
74
79
  @type = Type.new(rpc_desc)
80
+ @context = ::ActiveSupport::HashWithIndifferentAccess.new
75
81
  end
76
82
 
77
83
  ##
data/lib/gruf/version.rb CHANGED
@@ -16,5 +16,5 @@
16
16
  # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
17
  #
18
18
  module Gruf
19
- VERSION = '2.15.0'
19
+ VERSION = '2.16.1'
20
20
  end
data/lib/gruf.rb CHANGED
@@ -17,6 +17,7 @@
17
17
  #
18
18
  require 'grpc'
19
19
  require 'active_support/core_ext/module/delegation'
20
+ require 'active_support/hash_with_indifferent_access'
20
21
  require 'active_support/concern'
21
22
  require 'active_support/inflector'
22
23
  require 'base64'
@@ -27,6 +28,7 @@ loader = ::Zeitwerk::Loader.new
27
28
  loader.tag = File.basename(__FILE__, '.rb')
28
29
  loader.inflector = ::Zeitwerk::GemInflector.new(__FILE__)
29
30
  loader.ignore("#{__dir__}/gruf/integrations/rails/railtie.rb")
31
+ loader.ignore("#{__dir__}/gruf/controllers/health_controller.rb")
30
32
  loader.push_dir(__dir__)
31
33
  loader.setup
32
34
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gruf
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.15.0
4
+ version: 2.16.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shaun McCormick
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-30 00:00:00.000000000 Z
11
+ date: 2022-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler-audit
@@ -341,6 +341,7 @@ files:
341
341
  - lib/gruf/configuration.rb
342
342
  - lib/gruf/controllers/autoloader.rb
343
343
  - lib/gruf/controllers/base.rb
344
+ - lib/gruf/controllers/health_controller.rb
344
345
  - lib/gruf/controllers/request.rb
345
346
  - lib/gruf/controllers/service_binder.rb
346
347
  - lib/gruf/error.rb
@@ -395,7 +396,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
395
396
  requirements:
396
397
  - - ">="
397
398
  - !ruby/object:Gem::Version
398
- version: '2.6'
399
+ version: '2.7'
399
400
  - - "<"
400
401
  - !ruby/object:Gem::Version
401
402
  version: '3.2'
@@ -405,7 +406,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
405
406
  - !ruby/object:Gem::Version
406
407
  version: '0'
407
408
  requirements: []
408
- rubygems_version: 3.3.12
409
+ rubygems_version: 3.3.3
409
410
  signing_key:
410
411
  specification_version: 4
411
412
  summary: gRPC Ruby Framework