service_skeleton 1.0.5 → 2.1.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: 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