gruf 2.15.0 → 2.16.1

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