config_skeleton 0.4.1 → 1.0.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: 20798a914f83d1ab8531eb66259f202816e40ade371172102058536cf2fa3af7
4
- data.tar.gz: 5f742b8f777496e322f845eccc9c641eb7bad0815c8853cd1eea26bbddd2080c
3
+ metadata.gz: 114069700875be6dd679f4731609175447186ddcbde862bdfef1d516c0f66601
4
+ data.tar.gz: 424208817f7b1c407272909e8c13e7e9e308dab0ca18e68ce5fda208c9536464
5
5
  SHA512:
6
- metadata.gz: 9873af5655630b61a5833a58d6203836a78d4e11218802608fbcde86ada691e6e38ae2a52dd088037e00be1721aed145c6c057cd3c0175ad4279995d3da7d91e
7
- data.tar.gz: a9e1d788d30246ee436a452076fd157d03864123b3b1eca420c26cb5d6c0684752a5028e4930f81195e2e47e6a8253f468564d525eb3bff8738bf61299d8658a
6
+ metadata.gz: 1da2b1664644625d452e3aa6d7f10435901dd63c1084f1e6d1f9b0ad87e1ffc977efef0e9d64db11ffb072ab495d2a2064ca5b25f1afcc0b27b0ba9750a2d550
7
+ data.tar.gz: a288a8a63fba3019181fe44011c21088136e0c89dc72fb7f870dd96ce2fafc2eef599b9eb1a327149ba0ac973d75cbdda3ef3d164345d6984b6e84e14ed550d0
@@ -43,6 +43,6 @@ jobs:
43
43
  - uses: actions/checkout@v2
44
44
 
45
45
  - name: Release Gem
46
- uses: CvX/publish-rubygems-action@master
46
+ uses: discourse/publish-rubygems-action@main
47
47
  env:
48
48
  RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_API_KEY}}
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "config_skeleton"
3
3
 
4
- s.version = "0.4.1"
4
+ s.version = "1.0.0"
5
5
 
6
6
  s.platform = Gem::Platform::RUBY
7
7
 
@@ -16,9 +16,8 @@ Gem::Specification.new do |s|
16
16
  s.required_ruby_version = ">= 2.3.0"
17
17
 
18
18
  s.add_runtime_dependency 'diffy', '~> 3.0'
19
- s.add_runtime_dependency 'frankenstein', '~> 1.0'
20
19
  s.add_runtime_dependency 'rb-inotify', '~> 0.9'
21
- s.add_runtime_dependency 'service_skeleton', '> 0.a'
20
+ s.add_runtime_dependency 'service_skeleton', "~> 1.0"
22
21
 
23
22
  s.add_development_dependency 'bundler'
24
23
  s.add_development_dependency 'github-release'
@@ -2,11 +2,17 @@ require 'diffy'
2
2
  require 'fileutils'
3
3
  require 'frankenstein'
4
4
  require 'logger'
5
- require 'rb-inotify'
6
5
  require 'service_skeleton'
7
6
  require 'tempfile'
8
7
  require 'digest/md5'
9
8
 
9
+ begin
10
+ require 'rb-inotify' unless ENV["DISABLE_INOTIFY"]
11
+ rescue FFI::NotFoundError => e
12
+ STDERR.puts "ERROR: Unable to initialize rb-inotify. To disable, set DISABLE_INOTIFY=1"
13
+ raise
14
+ end
15
+
10
16
  # Framework for creating config generation systems.
11
17
  #
12
18
  # There are many systems which require some sort of configuration file to
@@ -29,14 +35,13 @@ require 'digest/md5'
29
35
  #
30
36
  # 1. Setup any file watchers you want with .watch and #watch.
31
37
  #
32
- # 1. Instantiate your new class, passing in an environment hash, and then call
33
- # #start. Something like this should do the trick:
38
+ # 1. Use the ServiceSkeleton Runner to start the service. Something like this should do the trick:
34
39
  #
35
40
  # class MyConfigGenerator < ConfigSkeleton
36
41
  # # Implement all the necessary methods
37
42
  # end
38
43
  #
39
- # MyConfigGenerator.new(ENV).start if __FILE__ == $0
44
+ # ServiceSkeleton::Runner.new(MyConfigGenerator, ENV).run if __FILE__ == $0
40
45
  #
41
46
  # 1. Sit back and relax.
42
47
  #
@@ -136,7 +141,8 @@ require 'digest/md5'
136
141
  # method, passing one or more strings containing the full path to files or
137
142
  # directories to watch.
138
143
  #
139
- class ConfigSkeleton < ServiceSkeleton
144
+ class ConfigSkeleton
145
+ include ServiceSkeleton
140
146
  # All ConfigSkeleton-related errors will be subclasses of this.
141
147
  class Error < StandardError; end
142
148
 
@@ -155,6 +161,19 @@ class ConfigSkeleton < ServiceSkeleton
155
161
  end
156
162
  end
157
163
 
164
+ def self.inherited(klass)
165
+ klass.gauge :"#{klass.service_name}_last_generation_timestamp", docstring: "When the last config generation run was made"
166
+ klass.gauge :"#{klass.service_name}_last_change_timestamp", docstring: "When the config file was last written to"
167
+ klass.counter :"#{klass.service_name}_reload_total", docstring: "How many times we've asked the server to reload", labels: [:status]
168
+ klass.counter :"#{klass.service_name}_signals_total", docstring: "How many signals have been received (and handled)"
169
+ klass.gauge :"#{klass.service_name}_config_ok", docstring: "Whether the last config change was accepted by the server"
170
+
171
+ klass.hook_signal("HUP") do
172
+ logger.info("SIGHUP") { "received SIGHUP, triggering config regeneration" }
173
+ @trigger_regen_w << "."
174
+ end
175
+ end
176
+
158
177
  # Declare a file watch on all instances of the config generator.
159
178
  #
160
179
  # When you're looking to watch a file whose path is well-known and never-changing, you
@@ -186,20 +205,8 @@ class ConfigSkeleton < ServiceSkeleton
186
205
  @watches || []
187
206
  end
188
207
 
189
- # Create a new config generator.
190
- #
191
- # @param env [Hash<String, String>] the environment in which this config
192
- # generator runs. Typically you'll just pass `ENV` in here, but you can
193
- # pass in any hash you like, for testing purposes.
194
- #
195
- def initialize(env)
208
+ def initialize(*_)
196
209
  super
197
-
198
- hook_signal(:HUP) do
199
- logger.info("SIGHUP") { "received SIGHUP, triggering config regeneration" }
200
- @trigger_regen_w << "."
201
- end
202
-
203
210
  initialize_config_skeleton_metrics
204
211
  @trigger_regen_r, @trigger_regen_w = IO.pipe
205
212
  @terminate_r, @terminate_w = IO.pipe
@@ -294,6 +301,7 @@ class ConfigSkeleton < ServiceSkeleton
294
301
  # @see .watch for watching files and directories whose path never changes.
295
302
  #
296
303
  def watch(*files)
304
+ return if ENV["DISABLE_INOTIFY"]
297
305
  files.each do |f|
298
306
  if File.directory?(f)
299
307
  notifier.watch(f, :recursive, :create, :modify, :delete, :move) { |ev| logger.info("#{logloc} watcher") { "detected #{ev.flags.join(", ")} on #{ev.watcher.path}/#{ev.name}; regenerating config" } }
@@ -305,22 +313,11 @@ class ConfigSkeleton < ServiceSkeleton
305
313
 
306
314
  private
307
315
 
308
- # Register metrics in the ServiceSkeleton metrics registry
309
- #
310
- # @return [void]
311
- #
312
316
  def initialize_config_skeleton_metrics
313
- @config_generation = Frankenstein::Request.new("#{service_name}_generation", outgoing: false, description: "config generation", registry: metrics)
314
-
315
- metrics.gauge(:"#{service_name}_last_generation_timestamp", "When the last config generation run was made")
316
- metrics.gauge(:"#{service_name}_last_change_timestamp", "When the config file was last written to")
317
- metrics.counter(:"#{service_name}_reload_total", "How many times we've asked the server to reload")
318
- metrics.counter(:"#{service_name}_signals_total", "How many signals have been received (and handled)")
319
- metrics.gauge(:"#{service_name}_config_ok", "Whether the last config change was accepted by the server")
320
-
321
- metrics.last_generation_timestamp.set({}, 0)
322
- metrics.last_change_timestamp.set({}, 0)
323
- metrics.config_ok.set({}, 0)
317
+ @config_generation = Frankenstein::Request.new("#{self.class.service_name}_generation", outgoing: false, description: "config generation", registry: metrics)
318
+ metrics.last_generation_timestamp.set(0)
319
+ metrics.last_change_timestamp.set(0)
320
+ metrics.config_ok.set(0)
324
321
  end
325
322
 
326
323
  # Write out a config file if one doesn't exist, or do an initial regen run
@@ -335,7 +332,7 @@ class ConfigSkeleton < ServiceSkeleton
335
332
  else
336
333
  logger.info(logloc) { "No existing config file #{config_file} found; writing one" }
337
334
  File.write(config_file, instrumented_config_data)
338
- metrics.last_change_timestamp.set({}, Time.now.to_f)
335
+ metrics.last_change_timestamp.set(Time.now.to_f)
339
336
  end
340
337
  end
341
338
 
@@ -423,7 +420,7 @@ class ConfigSkeleton < ServiceSkeleton
423
420
  #
424
421
  def instrumented_config_data
425
422
  begin
426
- @config_generation.measure { config_data.tap { metrics.last_generation_timestamp.set({}, Time.now.to_f) } }
423
+ @config_generation.measure { config_data.tap { metrics.last_generation_timestamp.set(Time.now.to_f) } }
427
424
  rescue => ex
428
425
  log_exception(ex, logloc) { "Call to config_data raised exception" }
429
426
  nil
@@ -453,6 +450,9 @@ class ConfigSkeleton < ServiceSkeleton
453
450
  #
454
451
  def notifier
455
452
  @notifier ||= INotify::Notifier.new
453
+ rescue NameError
454
+ raise if !ENV["DISABLE_INOTIFY"]
455
+ @notifier ||= Struct.new(:to_io).new(IO.pipe[1]) # Stub for macOS development
456
456
  end
457
457
 
458
458
  # Do the hard yards of actually regenerating the config and performing the reload.
@@ -474,7 +474,7 @@ class ConfigSkeleton < ServiceSkeleton
474
474
  )
475
475
 
476
476
  logger.debug(logloc) { "force? #{force_reload.inspect}" }
477
- tmpfile = Tempfile.new(service_name, File.dirname(config_file))
477
+ tmpfile = Tempfile.new(self.class.service_name, File.dirname(config_file))
478
478
  logger.debug(logloc) { "Tempfile is #{tmpfile.path}" }
479
479
  unless (new_config = instrumented_config_data).nil?
480
480
  File.write(tmpfile.path, new_config)
@@ -512,7 +512,7 @@ class ConfigSkeleton < ServiceSkeleton
512
512
  new_config_hash: new_config_hash
513
513
  )
514
514
  ensure
515
- metrics.last_change_timestamp.set({}, File.stat(config_file).mtime.to_f)
515
+ metrics.last_change_timestamp.set(File.stat(config_file).mtime.to_f)
516
516
  tmpfile.close rescue nil
517
517
  tmpfile.unlink rescue nil
518
518
  end
@@ -562,7 +562,7 @@ class ConfigSkeleton < ServiceSkeleton
562
562
  logger.debug(logloc) { "Restored previous config file" }
563
563
  File.rename(old_copy, config_file)
564
564
  end
565
- metrics.reload_total.increment(status: "failure")
565
+ metrics.reload_total.increment(labels: { status: "failure" })
566
566
 
567
567
  return
568
568
  end
@@ -570,21 +570,21 @@ class ConfigSkeleton < ServiceSkeleton
570
570
  logger.debug(logloc) { "Server reloaded successfully" }
571
571
 
572
572
  if config_ok?
573
- metrics.config_ok.set({}, 1)
573
+ metrics.config_ok.set(1)
574
574
  logger.debug(logloc) { "Configuration successfully updated." }
575
- metrics.reload_total.increment(status: "success")
576
- metrics.last_change_timestamp.set({}, Time.now.to_f)
575
+ metrics.reload_total.increment(labels: { status: "success" })
576
+ metrics.last_change_timestamp.set(Time.now.to_f)
577
577
  else
578
- metrics.config_ok.set({}, 0)
578
+ metrics.config_ok.set(0)
579
579
  if config_was_ok
580
580
  logger.warn(logloc) { "New config file failed config_ok? test; rolling back to previous known-good config" }
581
581
  File.rename(old_copy, config_file)
582
582
  reload_server
583
- metrics.reload_total.increment(status: "bad-config")
583
+ metrics.reload_total.increment(labels: { status: "bad-config" })
584
584
  else
585
585
  logger.warn(logloc) { "New config file failed config_ok? test; leaving new config in place because old config is broken too" }
586
- metrics.reload_total.increment(status: "everything-is-awful")
587
- metrics.last_change_timestamp.set({}, Time.now.to_f)
586
+ metrics.reload_total.increment(labels: { status: "everything-is-awful" })
587
+ metrics.last_change_timestamp.set(Time.now.to_f)
588
588
  end
589
589
  end
590
590
  ensure
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: config_skeleton
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 1.0.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: 2020-12-01 00:00:00.000000000 Z
12
+ date: 2021-03-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: diffy
@@ -25,20 +25,6 @@ dependencies:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
27
  version: '3.0'
28
- - !ruby/object:Gem::Dependency
29
- name: frankenstein
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - "~>"
33
- - !ruby/object:Gem::Version
34
- version: '1.0'
35
- type: :runtime
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - "~>"
40
- - !ruby/object:Gem::Version
41
- version: '1.0'
42
28
  - !ruby/object:Gem::Dependency
43
29
  name: rb-inotify
44
30
  requirement: !ruby/object:Gem::Requirement
@@ -57,16 +43,16 @@ dependencies:
57
43
  name: service_skeleton
58
44
  requirement: !ruby/object:Gem::Requirement
59
45
  requirements:
60
- - - ">"
46
+ - - "~>"
61
47
  - !ruby/object:Gem::Version
62
- version: 0.a
48
+ version: '1.0'
63
49
  type: :runtime
64
50
  prerelease: false
65
51
  version_requirements: !ruby/object:Gem::Requirement
66
52
  requirements:
67
- - - ">"
53
+ - - "~>"
68
54
  - !ruby/object:Gem::Version
69
- version: 0.a
55
+ version: '1.0'
70
56
  - !ruby/object:Gem::Dependency
71
57
  name: bundler
72
58
  requirement: !ruby/object:Gem::Requirement