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 +4 -4
- data/.github/workflows/ruby.yml +1 -1
- data/config_skeleton.gemspec +2 -3
- data/lib/config_skeleton.rb +45 -45
- metadata +6 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 114069700875be6dd679f4731609175447186ddcbde862bdfef1d516c0f66601
|
4
|
+
data.tar.gz: 424208817f7b1c407272909e8c13e7e9e308dab0ca18e68ce5fda208c9536464
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1da2b1664644625d452e3aa6d7f10435901dd63c1084f1e6d1f9b0ad87e1ffc977efef0e9d64db11ffb072ab495d2a2064ca5b25f1afcc0b27b0ba9750a2d550
|
7
|
+
data.tar.gz: a288a8a63fba3019181fe44011c21088136e0c89dc72fb7f870dd96ce2fafc2eef599b9eb1a327149ba0ac973d75cbdda3ef3d164345d6984b6e84e14ed550d0
|
data/.github/workflows/ruby.yml
CHANGED
data/config_skeleton.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "config_skeleton"
|
3
3
|
|
4
|
-
s.version = "0.
|
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',
|
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'
|
data/lib/config_skeleton.rb
CHANGED
@@ -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.
|
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
|
-
#
|
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
|
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
|
-
|
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.
|
316
|
-
metrics.
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
+
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:
|
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
|
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
|
55
|
+
version: '1.0'
|
70
56
|
- !ruby/object:Gem::Dependency
|
71
57
|
name: bundler
|
72
58
|
requirement: !ruby/object:Gem::Requirement
|