gruf 2.15.1 → 2.16.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: 5186fbfc554b119075aff88683ee7f1661a884d96d9766e59664d10805bd4ab2
4
- data.tar.gz: 68b995a644b15dc703595df4e35a25539e0edf77d5f5218bffa28253b60eedbf
3
+ metadata.gz: 3d83766bdf6739e3997891fd7b5e5ecd393aa17c8fab0ab839ff0789756726cf
4
+ data.tar.gz: b959a1644c29c5732438a60a079dc318136146eddb96d4060091f0f39fc7e5c6
5
5
  SHA512:
6
- metadata.gz: 8328cccfcc712c21cb5a477939762524d774d3d45a0882217fe3b7d701cfda90e9dcd27af7ab4c4cbc73ca46d420d85e55be3712efc950590212658030217c40
7
- data.tar.gz: 2cf04dce0c4e378edf56ab6b204293ce2b4dc660d2ac103ac55d99e893891ac3a76feb2e05dee4ac3371b47083fd8523441e7e2c87881bacd2511b016a1965b3
6
+ metadata.gz: ef7b011e2d693c2cb05b10697e0b67fcedb1d72ffc8adeac5f207557a30494860878a5b80e94fc8d512e7a4e5e6cb69b2bc8a854e72cd80f2e1507e0c1f379b6
7
+ data.tar.gz: a75845718660f4013015eff266c737315ce7bb00e147c83bc175390c87d890be525ee88059f5bb3ec224bbe30a96fb348143dfccd0c377a2dee8bd3eb674a6ea
data/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@ Changelog for the gruf gem. This includes internal history before the gem was ma
2
2
 
3
3
  ### Pending release
4
4
 
5
+ - 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)
6
+ automatically via `health_check_enabled` configuration option (or `GRUF_HEALTH_CHECK_ENABLED` environment
7
+ variable).
8
+ - Add `health_check_hook` configuration option to implement a custom response for the above gRPC built-in health check
9
+ - [#156] Allow passing a specific list of services to run via the gruf binstub
10
+ - [#163] Add `context` hash attribute to `Gruf::Controllers::Request` to allow interceptors to pass information down
11
+ to a gruf controller
12
+ - Drop Ruby 2.6 support (EOL'ed on March 31st, 2022)
13
+
5
14
  ### 2.15.1
6
15
 
7
16
  - Fix issue where GRPC_SERVER_POOL_KEEP_ALIVE and GRPC_SERVER_POLL_PERIOD when set via ENV are not cast to int
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,85 @@ 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
+ # rubocop:disable Lint/Void
165
+ Gruf::Controllers::HealthController
166
+ # rubocop:enable Lint/Void
167
+ # if we're already bound to the services array (say someone explicitly passes the health check in, skip)
168
+ return services if services.include?(::Grpc::Health::V1::Health::Service)
169
+
170
+ # otherwise, manually add the grpc service
171
+ services << ::Grpc::Health::V1::Health::Service
172
+ services
173
+ end
174
+
175
+ ##
176
+ # Determine how we load services (initializer -> ARGV -> Gruf.services)
177
+ #
178
+ # @return [Array<Class>]
179
+ #
180
+ def determine_services(services = [])
181
+ # first check initializer arguments
182
+ return services if services.any?
183
+
184
+ # next check CLI arguments
185
+ services = @options[:services].to_s.split(',').map(&:strip).uniq
186
+ # finally, if none, use global gruf autoloaded services
187
+ services = (::Gruf.services || []) unless services.any?
188
+
189
+ services
190
+ end
117
191
  end
118
192
  end
119
193
  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,
@@ -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,37 @@
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
+ module Gruf
19
+ module Controllers
20
+ ##
21
+ # Dynamic standard grpc health check controller. Can be used as-is, or can use ::Gruf.health_check_hook to
22
+ # provide custom responses.
23
+ #
24
+ class HealthController < Gruf::Controllers::Base
25
+ bind ::Grpc::Health::V1::Health::Service
26
+
27
+ def check
28
+ health_proc = ::Gruf.health_check_hook
29
+ return health_proc.call(request, error) if !health_proc.nil? && health_proc.respond_to?(:call)
30
+
31
+ ::Grpc::Health::V1::HealthCheckResponse.new(
32
+ status: ::Grpc::Health::V1::HealthCheckResponse::ServingStatus::SERVING
33
+ )
34
+ end
35
+ end
36
+ end
37
+ 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.1'
19
+ VERSION = '2.16.0'
20
20
  end
data/lib/gruf.rb CHANGED
@@ -17,8 +17,10 @@
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'
23
+ require 'grpc/health/v1/health_services_pb'
22
24
  require 'base64'
23
25
 
24
26
  # use Zeitwerk to lazily autoload all the files in the lib directory
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.1
4
+ version: 2.16.0
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-07-08 00:00:00.000000000 Z
11
+ date: 2022-08-14 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