gruf 2.15.1 → 2.16.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: 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