service_skeleton 1.0.5 → 2.1.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: 020137b90b172033166a44e4e8f94094a2cb35f8886a185dbfaa8a47a29cff71
4
- data.tar.gz: 85da8294b7d0c158404ef42ffb4969a7f02560ad35b4a5b9713bacbfb50ab9f4
3
+ metadata.gz: 2eb043ee79208c4b1b457b1b9deb2b5901119339c9789d8a6d511870bc038e46
4
+ data.tar.gz: 6848edcb6fe369e5a6ce0dcf823780ee8047f8953b40ea261e03aa0f47314d27
5
5
  SHA512:
6
- metadata.gz: 39d9073b15a24f9b13ffc6bcaa7c60756e425301749fdf6b90e08b132bd7eadeb1ac90c75e0398e2704d57e1bc849edb9ecdfd78270d98b033a51dd7277addc0
7
- data.tar.gz: 7cc439b11c6f36df6b2b0282e6f7661ef3c78caa496396ce1260a7d6ab66ccc7aef9a7d5debdd5e38ae2ff74d9833c7bf465f0167d74e7e649b952e882c73858
6
+ metadata.gz: 43fe6a4e0ad9adb50cd78c09c41063df614a0116c194283710660b24f2877994038bb818e35ad78eb7b72da62c3ec7e75646fc2bfae5c6f85ef5fdd7a5289dba
7
+ data.tar.gz: 9aacd383f2d867a51b19adc8c1d501ad383497d13a72de0b8fa9992301b029cd23e60aaa3c72511e98f240ed060dd1dab6b8b38a89a9de3cce19a40c8c696f39
@@ -4,7 +4,8 @@ on:
4
4
  pull_request:
5
5
  push:
6
6
  branches:
7
- - "v1_0"
7
+ - master
8
+ - main
8
9
 
9
10
  jobs:
10
11
  build:
@@ -14,6 +15,9 @@ jobs:
14
15
  matrix:
15
16
  ruby:
16
17
  - 2.5
18
+ - 2.6
19
+ - 2.7
20
+ - 3.0
17
21
 
18
22
  steps:
19
23
  - uses: actions/checkout@v2
@@ -31,7 +35,7 @@ jobs:
31
35
  run: bundle exec rake test
32
36
 
33
37
  publish:
34
- if: github.event_name == 'push' && (github.ref == 'refs/heads/v1_0')
38
+ if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
35
39
  needs: build
36
40
  runs-on: ubuntu-latest
37
41
 
data/README.md CHANGED
@@ -268,7 +268,7 @@ default for a config variable, like so:
268
268
  class GenericHelloService
269
269
  include ServiceSkeleton
270
270
 
271
- string :RECIPIENT, match: /\a\w+\z/, default: "Anonymous Coward"
271
+ string :RECIPIENT, match: /\A\w+\z/, default: "Anonymous Coward"
272
272
 
273
273
  # ...
274
274
 
@@ -448,8 +448,7 @@ portion is the all-uppercase [service name](#the-service-name).
448
448
 
449
449
  INFO,buggy=DEBUG,/noisy/i=ERROR
450
450
 
451
- Logging levels can be changed at runtime, via [signals](#default-signals) or
452
- [the HTTP admin interface](#http-admin-interface).
451
+ Logging levels can be changed at runtime via [signals](#default-signals).
453
452
 
454
453
  * **`<SERVICENAME>_LOGSTASH_SERVER`** (string; default `""`) -- if set to a
455
454
  non-empty string, we will engage the services of the [loggerstash
@@ -686,57 +685,6 @@ When the service is shutdown, all signal handlers will be automatically
686
685
  unhooked, which saves you having to do it yourself.
687
686
 
688
687
 
689
- ## HTTP Admin Interface
690
-
691
- In these modern times we live in, it seems everything from nuclear reactors to
692
- toasters can be controlled from a browser. Why should your services be any
693
- different?
694
-
695
-
696
- ### HTTP Admin Configuration
697
-
698
- In the spirit of "secure by default", you must explicitly enable the HTTP admin
699
- interface, and configure an authentication method. To do that, use the
700
- following environment variables, where `<SERVICENAME>_` is the all-uppercase
701
- version of [the service name](#the-service-name).
702
-
703
- * **`<SERVICENAME>_HTTP_ADMIN_PORT`** (integer; range 1..65535; default: `""`)
704
- -- if set to a valid port number (`1` to `65535` inclusive), the HTTP admin
705
- interface will listen on that port, if also enabled by configuring
706
- authentication.
707
-
708
- * **`<SERVICENAME>_HTTP_ADMIN_BASIC_AUTH`** (string; default: `""`) -- if set
709
- to a string containing a username and password separated by a colon, then
710
- authentication via [HTTP Basic auth](https://tools.ietf.org/html/rfc7617)
711
- will be supported. Note that in addition to this setting, an admin port must
712
- also be specified in order for the admin interface to be enabled.
713
-
714
- * **`<SERVICENAME>_HTTP_ADMIN_PROXY_USERNAME_HEADER`** (string; default: `""`)
715
- -- if set to a non-empty string, then incoming requests will be examined for
716
- a HTTP header with the specified name. If such a header exists and has a
717
- non-empty value, then the request will be deemed to have been authenticated
718
- by an upstream authenticating proxy (such as
719
- [`discourse-auth-proxy`](https://github.com/discourse/discourse-auth-proxy))
720
- as the user given in the header value. Note that in addition to this
721
- setting, an admin port must also be specified in order for the admin
722
- interface to be enabled.
723
-
724
-
725
- ### HTTP Admin Usage
726
-
727
- The HTTP admin interface provides both an interactive, browser-based mode,
728
- as well as a RESTful interface, which should, in general, provide equivalent
729
- functionality.
730
-
731
- * Visiting the service's `IP address:port` in a web browser will bring up an HTML
732
- interface showing all the features that are available. Usage should
733
- (hopefully) be self-explanatory.
734
-
735
- * Visiting the service's `IP address:port` whilst accepting `application/json`
736
- responses will provide a directory of links to available endpoints which you
737
- can use to interact with the HTTP admin interface programmatically.
738
-
739
-
740
688
  # Contributing
741
689
 
742
690
  Patches can be sent as [a Github pull
@@ -16,8 +16,8 @@ module ServiceSkeleton
16
16
  module Generator
17
17
  def generate(config:, metrics_registry:, service_metrics:, service_signal_handlers:)
18
18
  Ultravisor.new(logger: config.logger).tap do |ultravisor|
19
- initialize_metrics(ultravisor, config, metrics_registry, service_metrics)
20
19
  initialize_loggerstash(ultravisor, config, metrics_registry)
20
+ initialize_metrics(ultravisor, config, metrics_registry, service_metrics)
21
21
  initialize_signals(ultravisor, config, service_signal_handlers, metrics_registry)
22
22
  end
23
23
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A mechanism for waiting until a timer expires or until another thread signals
4
+ # readiness.
5
+ class ServiceSkeleton::HurriableTimer
6
+ def initialize(timeout)
7
+ @mutex = Mutex.new
8
+ @condition = ConditionVariable.new
9
+ @end_time = now + timeout
10
+ @hurried = false
11
+ end
12
+
13
+ # Wait for the timer to elapse
14
+ #
15
+ # Any number of threads can wait on the same HurriableTimer
16
+ def wait(t = nil)
17
+ end_time =
18
+ if t
19
+ [@end_time, now + t].min
20
+ else
21
+ @end_time
22
+ end
23
+
24
+ @mutex.synchronize {
25
+ while true
26
+ remaining = end_time - now
27
+
28
+ if remaining < 0 || @hurried
29
+ break
30
+ else
31
+ @condition.wait(@mutex, remaining)
32
+ end
33
+ end
34
+ }
35
+
36
+ nil
37
+ end
38
+
39
+ # Cause the timer to trigger early if it hasn't already expired
40
+ #
41
+ # This method is idempotent
42
+ def hurry!
43
+ @mutex.synchronize {
44
+ @hurried = true
45
+ @condition.broadcast
46
+ }
47
+
48
+ nil
49
+ end
50
+
51
+ def expired?
52
+ @hurried || @end_time - now < 0
53
+ end
54
+
55
+ private
56
+
57
+ def now
58
+ # Using this instead of Time.now, because it isn't affected by NTP updates
59
+ Process.clock_gettime(Process::CLOCK_MONOTONIC_RAW)
60
+ end
61
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # HurribleTimerSequence is a resettable version of HurriableTimer, designed for
4
+ # cases where some action needs to happen at at least some frequency, but may
5
+ # happen more often when other threads trigger the process early.
6
+ #
7
+ # It would have been possible to implement this without requiring allocation on
8
+ # reset, by reusing the mutex and condition variable in the normal timer, but
9
+ # this version is more obviously correct.
10
+ class ServiceSkeleton::HurriableTimerSequence
11
+ def initialize(timeout)
12
+ @mutex = Mutex.new
13
+ @timeout = timeout
14
+ @latest = ServiceSkeleton::HurriableTimer.new(@timeout)
15
+ end
16
+
17
+ def reset!
18
+ @mutex.synchronize {
19
+ @latest.hurry!
20
+ @latest = ServiceSkeleton::HurriableTimer.new(@timeout)
21
+ }
22
+ end
23
+
24
+ def wait(t = nil)
25
+ @mutex.synchronize { @latest }.wait(t)
26
+ end
27
+
28
+ def hurry!
29
+ @mutex.synchronize { @latest }.hurry!
30
+ end
31
+
32
+ def expired?
33
+ @mutex.synchronize { @latest }.expired?
34
+ end
35
+ end
@@ -81,7 +81,7 @@ module ServiceSkeleton
81
81
  logger.error(logloc) { "Mysterious return from select: #{ios.inspect}" }
82
82
  end
83
83
  end
84
- rescue IOError
84
+ rescue IOError, Errno::EBADF
85
85
  # Something has gone terribly wrong here... bail
86
86
  break
87
87
  rescue StandardError => ex
@@ -3,6 +3,8 @@
3
3
  require_relative "service_skeleton/config_class"
4
4
  require_relative "service_skeleton/config_variables"
5
5
  require_relative "service_skeleton/generator"
6
+ require_relative "service_skeleton/hurriable_timer"
7
+ require_relative "service_skeleton/hurriable_timer_sequence"
6
8
  require_relative "service_skeleton/logging_helpers"
7
9
  require_relative "service_skeleton/metrics_methods"
8
10
  require_relative "service_skeleton/service_name"
@@ -1,15 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
4
- require 'git-version-bump'
5
- rescue LoadError
6
- nil
7
- end
8
-
9
3
  Gem::Specification.new do |s|
10
4
  s.name = "service_skeleton"
11
5
 
12
- s.version = '1.0.5'
6
+ s.version = '2.1.0'
13
7
 
14
8
  s.platform = Gem::Platform::RUBY
15
9
 
@@ -32,22 +26,20 @@ Gem::Specification.new do |s|
32
26
  s.required_ruby_version = ">= 2.5.0"
33
27
 
34
28
  s.add_runtime_dependency "frankenstein", "~> 2.0"
35
- s.add_runtime_dependency "loggerstash", ">= 0.0.9", "< 1"
29
+ s.add_runtime_dependency "loggerstash", "~> 1"
36
30
  s.add_runtime_dependency "prometheus-client", "~> 2.0"
37
31
  s.add_runtime_dependency "sigdump", "~> 0.2"
38
32
  s.add_runtime_dependency "to_regexp", "~> 0.2"
33
+ s.add_runtime_dependency "webrick"
39
34
 
40
35
  s.add_development_dependency 'bundler'
41
- s.add_development_dependency 'github-release'
42
- s.add_development_dependency 'git-version-bump'
43
36
  s.add_development_dependency 'guard-rspec'
44
37
  s.add_development_dependency 'guard-rubocop'
45
38
  s.add_development_dependency 'rack-test'
46
39
  s.add_development_dependency 'rake'
47
40
  s.add_development_dependency 'redcarpet'
48
41
  s.add_development_dependency 'rspec'
49
- s.add_development_dependency 'rubocop'
50
- s.add_development_dependency 'rubocop-discourse'
42
+ s.add_development_dependency 'rubocop-discourse', '~> 2.4.1'
51
43
  s.add_development_dependency 'simplecov'
52
44
  s.add_development_dependency 'yard'
53
45
  end
@@ -381,6 +381,10 @@ class Ultravisor
381
381
  # urgh.
382
382
  if @klass.instance_method(:initialize).arity == 0
383
383
  @klass.new()
384
+ elsif @args.is_a?(Hash)
385
+ @klass.new(**@args)
386
+ elsif @args.last.is_a?(Hash)
387
+ @klass.new(*@args[0...-1], **@args.last)
384
388
  else
385
389
  @klass.new(*@args)
386
390
  end.tap do |i|
@@ -7,8 +7,10 @@ require 'rspec/mocks'
7
7
  Thread.report_on_exception = false
8
8
 
9
9
  require 'simplecov'
10
- SimpleCov.start do
11
- add_filter('spec')
10
+ unless SimpleCov.running
11
+ SimpleCov.start do
12
+ add_filter('spec')
13
+ end
12
14
  end
13
15
 
14
16
  class ListIncompletelyCoveredFiles
@@ -93,14 +93,123 @@ describe Ultravisor::Child do
93
93
  end
94
94
  end
95
95
 
96
- context "with a worker class that takes args" do
96
+ context "with a worker class that takes only non-keyword args" do
97
+ let(:args) { { id: :testy, klass: mock_class, args: ["foo", "bar"], method: :run } }
98
+ let(:mock_class) do
99
+ Class.new do
100
+ def initialize(foo, bar)
101
+ end
102
+
103
+ def run
104
+ end
105
+ end
106
+ end
107
+
108
+ it "creates the class instance with args" do
109
+ allow(mock_class).to receive(:new).and_call_original
110
+
111
+ child.spawn(term_queue).wait
112
+ expect(mock_class).to have_received(:new).with("foo", "bar")
113
+ end
114
+ end
115
+
116
+ context "with a worker class that takes non-keyword and optional args" do
97
117
  let(:args) { { id: :testy, klass: mock_class, args: ["foo", "bar", baz: "wombat"], method: :run } }
98
- let(:mock_class) { Class.new.tap { |k| k.class_eval { def initialize(*x); end; def run; end } } }
118
+ let(:mock_class) do
119
+ Class.new do
120
+ def initialize(foo, bar, baz = {})
121
+ end
122
+
123
+ def run
124
+ end
125
+ end
126
+ end
99
127
 
100
128
  it "creates the class instance with args" do
101
- expect(mock_class).to receive(:new).with("foo", "bar", baz: "wombat").and_call_original
129
+ allow(mock_class).to receive(:new).and_call_original
102
130
 
103
131
  child.spawn(term_queue).wait
132
+ expect(mock_class).to have_received(:new).with("foo", "bar", { baz: "wombat" })
133
+ end
134
+ end
135
+
136
+ context "with a worker class that takes mixed args" do
137
+ let(:args) { { id: :testy, klass: mock_class, args: ["foo", "bar", baz: "wombat"], method: :run } }
138
+ let(:mock_class) do
139
+ Class.new do
140
+ def initialize(foo, bar, baz:)
141
+ end
142
+
143
+ def run
144
+ end
145
+ end
146
+ end
147
+
148
+ it "creates the class instance with args" do
149
+ allow(mock_class).to receive(:new).and_call_original
150
+
151
+ child.spawn(term_queue).wait
152
+ expect(mock_class).to have_received(:new).with("foo", "bar", baz: "wombat")
153
+ end
154
+ end
155
+
156
+ context "with a worker class that takes keyword args" do
157
+ let(:args) { { id: :testy, klass: mock_class, args: [foo: "bar", baz: "wombat"], method: :run } }
158
+ let(:mock_class) do
159
+ Class.new do
160
+ def initialize(foo:, baz:)
161
+ end
162
+
163
+ def run
164
+ end
165
+ end
166
+ end
167
+
168
+ it "creates the class instance with args" do
169
+ allow(mock_class).to receive(:new).and_call_original
170
+
171
+ child.spawn(term_queue).wait
172
+ expect(mock_class).to have_received(:new).with(foo: "bar", baz: "wombat")
173
+ end
174
+ end
175
+
176
+ context "with a worker class that takes optional args" do
177
+ let(:args) { { id: :testy, klass: mock_class, args: [foo: "bar", baz: "wombat", fib: "wib", woop: "woob"], method: :run } }
178
+ let(:mock_class) do
179
+ Class.new do
180
+ def initialize(foo:, baz:, fib: "none", woop: nil)
181
+ end
182
+
183
+ def run
184
+ end
185
+ end
186
+ end
187
+
188
+ it "creates the class instance with args" do
189
+ allow(mock_class).to receive(:new).and_call_original
190
+ child.spawn(term_queue).wait
191
+
192
+ expect(mock_class).to have_received(:new).with(foo: "bar", baz: "wombat", fib: "wib", woop: "woob")
193
+ end
194
+ end
195
+
196
+ context "with a worker class that takes keyword args in form of a hash" do
197
+ let(:args) { { id: :testy, klass: mock_class, args: { foo: "bar", baz: "wombat" }, method: :run } }
198
+ let(:mock_class) do
199
+ Class.new do
200
+ def initialize(foo:, baz:)
201
+ end
202
+
203
+ def run
204
+ end
205
+ end
206
+ end
207
+
208
+ it "creates the class instance with args" do
209
+ allow(mock_class).to receive(:new).and_call_original
210
+ child.spawn(term_queue).wait
211
+
212
+ expect(mock_class).to have_received(:new).with(foo: "bar", baz: "wombat")
104
213
  end
105
214
  end
106
215
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: service_skeleton
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Palmer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-03-04 00:00:00.000000000 Z
12
+ date: 2022-01-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: frankenstein
@@ -29,20 +29,14 @@ dependencies:
29
29
  name: loggerstash
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - ">="
33
- - !ruby/object:Gem::Version
34
- version: 0.0.9
35
- - - "<"
32
+ - - "~>"
36
33
  - !ruby/object:Gem::Version
37
34
  version: '1'
38
35
  type: :runtime
39
36
  prerelease: false
40
37
  version_requirements: !ruby/object:Gem::Requirement
41
38
  requirements:
42
- - - ">="
43
- - !ruby/object:Gem::Version
44
- version: 0.0.9
45
- - - "<"
39
+ - - "~>"
46
40
  - !ruby/object:Gem::Version
47
41
  version: '1'
48
42
  - !ruby/object:Gem::Dependency
@@ -88,27 +82,13 @@ dependencies:
88
82
  - !ruby/object:Gem::Version
89
83
  version: '0.2'
90
84
  - !ruby/object:Gem::Dependency
91
- name: bundler
85
+ name: webrick
92
86
  requirement: !ruby/object:Gem::Requirement
93
87
  requirements:
94
88
  - - ">="
95
89
  - !ruby/object:Gem::Version
96
90
  version: '0'
97
- type: :development
98
- prerelease: false
99
- version_requirements: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- - !ruby/object:Gem::Dependency
105
- name: github-release
106
- requirement: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- type: :development
91
+ type: :runtime
112
92
  prerelease: false
113
93
  version_requirements: !ruby/object:Gem::Requirement
114
94
  requirements:
@@ -116,7 +96,7 @@ dependencies:
116
96
  - !ruby/object:Gem::Version
117
97
  version: '0'
118
98
  - !ruby/object:Gem::Dependency
119
- name: git-version-bump
99
+ name: bundler
120
100
  requirement: !ruby/object:Gem::Requirement
121
101
  requirements:
122
102
  - - ">="
@@ -213,34 +193,20 @@ dependencies:
213
193
  - - ">="
214
194
  - !ruby/object:Gem::Version
215
195
  version: '0'
216
- - !ruby/object:Gem::Dependency
217
- name: rubocop
218
- requirement: !ruby/object:Gem::Requirement
219
- requirements:
220
- - - ">="
221
- - !ruby/object:Gem::Version
222
- version: '0'
223
- type: :development
224
- prerelease: false
225
- version_requirements: !ruby/object:Gem::Requirement
226
- requirements:
227
- - - ">="
228
- - !ruby/object:Gem::Version
229
- version: '0'
230
196
  - !ruby/object:Gem::Dependency
231
197
  name: rubocop-discourse
232
198
  requirement: !ruby/object:Gem::Requirement
233
199
  requirements:
234
- - - ">="
200
+ - - "~>"
235
201
  - !ruby/object:Gem::Version
236
- version: '0'
202
+ version: 2.4.1
237
203
  type: :development
238
204
  prerelease: false
239
205
  version_requirements: !ruby/object:Gem::Requirement
240
206
  requirements:
241
- - - ">="
207
+ - - "~>"
242
208
  - !ruby/object:Gem::Version
243
- version: '0'
209
+ version: 2.4.1
244
210
  - !ruby/object:Gem::Dependency
245
211
  name: simplecov
246
212
  requirement: !ruby/object:Gem::Requirement
@@ -309,6 +275,8 @@ files:
309
275
  - lib/service_skeleton/error.rb
310
276
  - lib/service_skeleton/filtering_logger.rb
311
277
  - lib/service_skeleton/generator.rb
278
+ - lib/service_skeleton/hurriable_timer.rb
279
+ - lib/service_skeleton/hurriable_timer_sequence.rb
312
280
  - lib/service_skeleton/logging_helpers.rb
313
281
  - lib/service_skeleton/metric_method_name.rb
314
282
  - lib/service_skeleton/metrics_methods.rb
@@ -368,7 +336,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
368
336
  - !ruby/object:Gem::Version
369
337
  version: '0'
370
338
  requirements: []
371
- rubygems_version: 3.1.4
339
+ rubygems_version: 3.1.6
372
340
  signing_key:
373
341
  specification_version: 4
374
342
  summary: The bare bones of a service