config_skeleton 0.1.0 → 0.4.1
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 +48 -0
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/README.md +1 -1
- data/config_skeleton.gemspec +6 -11
- data/lib/config_skeleton.rb +90 -10
- metadata +44 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20798a914f83d1ab8531eb66259f202816e40ade371172102058536cf2fa3af7
|
4
|
+
data.tar.gz: 5f742b8f777496e322f845eccc9c641eb7bad0815c8853cd1eea26bbddd2080c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9873af5655630b61a5833a58d6203836a78d4e11218802608fbcde86ada691e6e38ae2a52dd088037e00be1721aed145c6c057cd3c0175ad4279995d3da7d91e
|
7
|
+
data.tar.gz: a9e1d788d30246ee436a452076fd157d03864123b3b1eca420c26cb5d6c0684752a5028e4930f81195e2e47e6a8253f468564d525eb3bff8738bf61299d8658a
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: Ruby
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches:
|
13
|
+
- master
|
14
|
+
tags:
|
15
|
+
- v*
|
16
|
+
pull_request:
|
17
|
+
branches:
|
18
|
+
- master
|
19
|
+
workflow_dispatch:
|
20
|
+
|
21
|
+
jobs:
|
22
|
+
build:
|
23
|
+
runs-on: ubuntu-latest
|
24
|
+
|
25
|
+
steps:
|
26
|
+
- uses: actions/checkout@v2
|
27
|
+
|
28
|
+
- name: Set up Ruby
|
29
|
+
uses: ruby/setup-ruby@v1
|
30
|
+
with:
|
31
|
+
ruby-version: 2.6
|
32
|
+
- name: Install dependencies
|
33
|
+
run: bundle install
|
34
|
+
- name: Run specs
|
35
|
+
run: bundle exec rspec
|
36
|
+
|
37
|
+
publish:
|
38
|
+
if: contains(github.ref, 'refs/tags/v')
|
39
|
+
needs: build
|
40
|
+
runs-on: ubuntu-latest
|
41
|
+
|
42
|
+
steps:
|
43
|
+
- uses: actions/checkout@v2
|
44
|
+
|
45
|
+
- name: Release Gem
|
46
|
+
uses: CvX/publish-rubygems-action@master
|
47
|
+
env:
|
48
|
+
RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_API_KEY}}
|
data/.gitignore
CHANGED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/README.md
CHANGED
@@ -41,7 +41,7 @@ conduct](CODE_OF_CONDUCT.md).
|
|
41
41
|
Unless otherwise stated, everything in this repo is covered by the following
|
42
42
|
copyright notice:
|
43
43
|
|
44
|
-
Copyright (C)
|
44
|
+
Copyright (C) 2020 Civilized Discourse Construction Kit, Inc.
|
45
45
|
|
46
46
|
This program is free software: you can redistribute it and/or modify it
|
47
47
|
under the terms of the GNU General Public License version 3, as
|
data/config_skeleton.gemspec
CHANGED
@@ -1,21 +1,14 @@
|
|
1
|
-
begin
|
2
|
-
require 'git-version-bump'
|
3
|
-
rescue LoadError
|
4
|
-
nil
|
5
|
-
end
|
6
|
-
|
7
1
|
Gem::Specification.new do |s|
|
8
2
|
s.name = "config_skeleton"
|
9
3
|
|
10
|
-
s.version =
|
11
|
-
s.date = GVB.date rescue Time.now.strftime("%Y-%m-%d")
|
4
|
+
s.version = "0.4.1"
|
12
5
|
|
13
6
|
s.platform = Gem::Platform::RUBY
|
14
7
|
|
15
8
|
s.summary = "Dynamically generate configs and reload servers"
|
16
9
|
|
17
|
-
s.authors = ["Matt Palmer"]
|
18
|
-
s.email = ["matt.palmer@discourse.org"]
|
10
|
+
s.authors = ["Matt Palmer", "Discourse Team"]
|
11
|
+
s.email = ["matt.palmer@discourse.org", "team@discourse.org"]
|
19
12
|
s.homepage = "https://github.com/discourse/config_skeleton"
|
20
13
|
|
21
14
|
s.files = `git ls-files -z`.split("\0").reject { |f| f =~ /^(G|spec|Rakefile)/ }
|
@@ -29,9 +22,11 @@ Gem::Specification.new do |s|
|
|
29
22
|
|
30
23
|
s.add_development_dependency 'bundler'
|
31
24
|
s.add_development_dependency 'github-release'
|
32
|
-
s.add_development_dependency 'git-version-bump'
|
33
25
|
s.add_development_dependency 'rake', "~> 12.0"
|
34
26
|
s.add_development_dependency 'redcarpet'
|
35
27
|
s.add_development_dependency 'rubocop'
|
36
28
|
s.add_development_dependency 'yard'
|
29
|
+
s.add_development_dependency 'rspec'
|
30
|
+
s.add_development_dependency 'pry'
|
31
|
+
s.add_development_dependency 'pry-byebug'
|
37
32
|
end
|
data/lib/config_skeleton.rb
CHANGED
@@ -5,6 +5,7 @@ require 'logger'
|
|
5
5
|
require 'rb-inotify'
|
6
6
|
require 'service_skeleton'
|
7
7
|
require 'tempfile'
|
8
|
+
require 'digest/md5'
|
8
9
|
|
9
10
|
# Framework for creating config generation systems.
|
10
11
|
#
|
@@ -23,7 +24,7 @@ require 'tempfile'
|
|
23
24
|
#
|
24
25
|
# 1. Implement service-specific config generation and reloading code, by
|
25
26
|
# overriding the private methods #config_file, #config_data, and #reload_server
|
26
|
-
# (and also potentially #config_ok
|
27
|
+
# (and also potentially #config_ok?, #sleep_duration, #before_regenerate_config, and #after_regenerate_config).
|
27
28
|
# See the documentation for those methods for what they need to do.
|
28
29
|
#
|
29
30
|
# 1. Setup any file watchers you want with .watch and #watch.
|
@@ -142,6 +143,18 @@ class ConfigSkeleton < ServiceSkeleton
|
|
142
143
|
# If you get this, someone didn't read the documentation.
|
143
144
|
class NotImplementedError < Error; end
|
144
145
|
|
146
|
+
# It is useful for consumers to manually request a config regen. An instance
|
147
|
+
# of this class is made via the regen_notifier method.
|
148
|
+
class ConfigRegenNotifier
|
149
|
+
def initialize(io_write)
|
150
|
+
@io_write = io_write
|
151
|
+
end
|
152
|
+
|
153
|
+
def trigger_regen
|
154
|
+
@io_write << "."
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
145
158
|
# Declare a file watch on all instances of the config generator.
|
146
159
|
#
|
147
160
|
# When you're looking to watch a file whose path is well-known and never-changing, you
|
@@ -184,10 +197,23 @@ class ConfigSkeleton < ServiceSkeleton
|
|
184
197
|
|
185
198
|
hook_signal(:HUP) do
|
186
199
|
logger.info("SIGHUP") { "received SIGHUP, triggering config regeneration" }
|
187
|
-
|
200
|
+
@trigger_regen_w << "."
|
188
201
|
end
|
189
202
|
|
190
203
|
initialize_config_skeleton_metrics
|
204
|
+
@trigger_regen_r, @trigger_regen_w = IO.pipe
|
205
|
+
@terminate_r, @terminate_w = IO.pipe
|
206
|
+
end
|
207
|
+
|
208
|
+
# Expose the write pipe which can be written to to trigger a config
|
209
|
+
# regeneration with a forced reload; a similar mechanism is used for
|
210
|
+
# shutdown but in that case writes are managed internally.
|
211
|
+
#
|
212
|
+
# Usage: config.regen_notifier.trigger_regen
|
213
|
+
#
|
214
|
+
# @return [ConfigRegenNotifier]
|
215
|
+
def regen_notifier
|
216
|
+
@regen_notifier ||= ConfigRegenNotifier.new(@trigger_regen_w)
|
191
217
|
end
|
192
218
|
|
193
219
|
# Set the config generator running.
|
@@ -206,10 +232,12 @@ class ConfigSkeleton < ServiceSkeleton
|
|
206
232
|
|
207
233
|
logger.debug(logloc) { "notifier fd is #{notifier.to_io.inspect}" }
|
208
234
|
|
209
|
-
@terminate_r, @terminate_w = IO.pipe
|
210
|
-
|
211
235
|
loop do
|
212
|
-
if ios = IO.select(
|
236
|
+
if ios = IO.select(
|
237
|
+
[notifier.to_io, @terminate_r, @trigger_regen_r],
|
238
|
+
[], [],
|
239
|
+
sleep_duration.tap { |d| logger.debug(logloc) { "Sleeping for #{d} seconds" } }
|
240
|
+
)
|
213
241
|
if ios.first.include?(notifier.to_io)
|
214
242
|
logger.debug(logloc) { "inotify triggered" }
|
215
243
|
notifier.process
|
@@ -217,6 +245,14 @@ class ConfigSkeleton < ServiceSkeleton
|
|
217
245
|
elsif ios.first.include?(@terminate_r)
|
218
246
|
logger.debug(logloc) { "triggered by termination pipe" }
|
219
247
|
break
|
248
|
+
elsif ios.first.include?(@trigger_regen_r)
|
249
|
+
# we want to wait until everything in the backlog is read
|
250
|
+
# before proceeding so we don't run out of buffer memory
|
251
|
+
# for the pipe
|
252
|
+
while @trigger_regen_r.read_nonblock(20, nil, exception: false) != :wait_readable; end
|
253
|
+
|
254
|
+
logger.debug(logloc) { "triggered by regen pipe" }
|
255
|
+
regenerate_config(force_reload: true)
|
220
256
|
else
|
221
257
|
logger.error(logloc) { "Mysterious return from select: #{ios.inspect}" }
|
222
258
|
end
|
@@ -327,6 +363,27 @@ class ConfigSkeleton < ServiceSkeleton
|
|
327
363
|
raise NotImplementedError, "config_data must be implemented in subclass."
|
328
364
|
end
|
329
365
|
|
366
|
+
# Run code before the config is regenerated and the config_file
|
367
|
+
# is written.
|
368
|
+
#
|
369
|
+
# @param force_reload [Boolean] Whether the regenerate_config was called with force_reload
|
370
|
+
# @param existing_config_hash [String] MD5 hash of the config file before regeneration.
|
371
|
+
#
|
372
|
+
# @note this can optionally be implemented by subclasses.
|
373
|
+
#
|
374
|
+
def before_regenerate_config(force_reload:, existing_config_hash:, existing_config_data:); end
|
375
|
+
|
376
|
+
# Run code after the config is regenerated and potentially a new file is written.
|
377
|
+
#
|
378
|
+
# @param force_reload [Boolean] Whether the regenerate_config was called with force_reload
|
379
|
+
# @param config_was_different [Boolean] Whether the diff of the old and new config was different.
|
380
|
+
# @param config_was_cycled [Boolean] Whether a new config file was cycled in.
|
381
|
+
# @param new_config_hash [String] MD5 hash of the new config file after write.
|
382
|
+
#
|
383
|
+
# @note this can optionally be implemented by subclasses.
|
384
|
+
#
|
385
|
+
def after_regenerate_config(force_reload:, config_was_different:, config_was_cycled:, new_config_hash:); end
|
386
|
+
|
330
387
|
# Verify that the currently running config is acceptable.
|
331
388
|
#
|
332
389
|
# In the event that a generated config is "bad", it may be possible to detect
|
@@ -408,29 +465,52 @@ class ConfigSkeleton < ServiceSkeleton
|
|
408
465
|
# @return [void]
|
409
466
|
#
|
410
467
|
def regenerate_config(force_reload: false)
|
468
|
+
data = File.read(config_file)
|
469
|
+
existing_config_hash = Digest::MD5.hexdigest(data)
|
470
|
+
before_regenerate_config(
|
471
|
+
force_reload: force_reload,
|
472
|
+
existing_config_hash: existing_config_hash,
|
473
|
+
existing_config_data: data
|
474
|
+
)
|
475
|
+
|
411
476
|
logger.debug(logloc) { "force? #{force_reload.inspect}" }
|
412
477
|
tmpfile = Tempfile.new(service_name, File.dirname(config_file))
|
413
478
|
logger.debug(logloc) { "Tempfile is #{tmpfile.path}" }
|
414
479
|
unless (new_config = instrumented_config_data).nil?
|
415
480
|
File.write(tmpfile.path, new_config)
|
416
481
|
tmpfile.close
|
417
|
-
|
482
|
+
|
483
|
+
new_config_hash = Digest::MD5.hexdigest(File.read(tmpfile.path))
|
484
|
+
logger.debug(logloc) do
|
485
|
+
"Existing config hash: #{existing_config_hash}, new config hash: #{new_config_hash}"
|
486
|
+
end
|
418
487
|
|
419
488
|
match_perms(config_file, tmpfile.path)
|
420
489
|
|
421
|
-
diff = Diffy::Diff.new(config_file, tmpfile.path, source: 'files', context: 3, include_diff_info: true)
|
422
|
-
|
423
|
-
|
490
|
+
diff = Diffy::Diff.new(config_file, tmpfile.path, source: 'files', context: 3, include_diff_info: true).to_s
|
491
|
+
config_was_different = diff != ""
|
492
|
+
|
493
|
+
if config_was_different
|
494
|
+
logger.info(logloc) { "Config has changed. Diff:\n#{diff}" }
|
424
495
|
end
|
425
496
|
|
426
497
|
if force_reload
|
427
498
|
logger.debug(logloc) { "Forcing config reload because force_reload == true" }
|
428
499
|
end
|
429
500
|
|
430
|
-
|
501
|
+
config_was_cycled = false
|
502
|
+
if force_reload || config_was_different
|
431
503
|
cycle_config(tmpfile.path)
|
504
|
+
config_was_cycled = true
|
432
505
|
end
|
433
506
|
end
|
507
|
+
|
508
|
+
after_regenerate_config(
|
509
|
+
force_reload: force_reload,
|
510
|
+
config_was_different: config_was_different,
|
511
|
+
config_was_cycled: config_was_cycled,
|
512
|
+
new_config_hash: new_config_hash
|
513
|
+
)
|
434
514
|
ensure
|
435
515
|
metrics.last_change_timestamp.set({}, File.stat(config_file).mtime.to_f)
|
436
516
|
tmpfile.close rescue nil
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: config_skeleton
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Palmer
|
8
|
+
- Discourse Team
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2020-12-01 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: diffy
|
@@ -95,7 +96,21 @@ dependencies:
|
|
95
96
|
- !ruby/object:Gem::Version
|
96
97
|
version: '0'
|
97
98
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
99
|
+
name: rake
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '12.0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '12.0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: redcarpet
|
99
114
|
requirement: !ruby/object:Gem::Requirement
|
100
115
|
requirements:
|
101
116
|
- - ">="
|
@@ -109,21 +124,21 @@ dependencies:
|
|
109
124
|
- !ruby/object:Gem::Version
|
110
125
|
version: '0'
|
111
126
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
127
|
+
name: rubocop
|
113
128
|
requirement: !ruby/object:Gem::Requirement
|
114
129
|
requirements:
|
115
|
-
- - "
|
130
|
+
- - ">="
|
116
131
|
- !ruby/object:Gem::Version
|
117
|
-
version: '
|
132
|
+
version: '0'
|
118
133
|
type: :development
|
119
134
|
prerelease: false
|
120
135
|
version_requirements: !ruby/object:Gem::Requirement
|
121
136
|
requirements:
|
122
|
-
- - "
|
137
|
+
- - ">="
|
123
138
|
- !ruby/object:Gem::Version
|
124
|
-
version: '
|
139
|
+
version: '0'
|
125
140
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
141
|
+
name: yard
|
127
142
|
requirement: !ruby/object:Gem::Requirement
|
128
143
|
requirements:
|
129
144
|
- - ">="
|
@@ -137,7 +152,7 @@ dependencies:
|
|
137
152
|
- !ruby/object:Gem::Version
|
138
153
|
version: '0'
|
139
154
|
- !ruby/object:Gem::Dependency
|
140
|
-
name:
|
155
|
+
name: rspec
|
141
156
|
requirement: !ruby/object:Gem::Requirement
|
142
157
|
requirements:
|
143
158
|
- - ">="
|
@@ -151,7 +166,21 @@ dependencies:
|
|
151
166
|
- !ruby/object:Gem::Version
|
152
167
|
version: '0'
|
153
168
|
- !ruby/object:Gem::Dependency
|
154
|
-
name:
|
169
|
+
name: pry
|
170
|
+
requirement: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - ">="
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
type: :development
|
176
|
+
prerelease: false
|
177
|
+
version_requirements: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - ">="
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
- !ruby/object:Gem::Dependency
|
183
|
+
name: pry-byebug
|
155
184
|
requirement: !ruby/object:Gem::Requirement
|
156
185
|
requirements:
|
157
186
|
- - ">="
|
@@ -167,11 +196,14 @@ dependencies:
|
|
167
196
|
description:
|
168
197
|
email:
|
169
198
|
- matt.palmer@discourse.org
|
199
|
+
- team@discourse.org
|
170
200
|
executables: []
|
171
201
|
extensions: []
|
172
202
|
extra_rdoc_files: []
|
173
203
|
files:
|
204
|
+
- ".github/workflows/ruby.yml"
|
174
205
|
- ".gitignore"
|
206
|
+
- ".rspec"
|
175
207
|
- ".rubocop.yml"
|
176
208
|
- ".yardopts"
|
177
209
|
- CODE_OF_CONDUCT.md
|
@@ -198,8 +230,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
198
230
|
- !ruby/object:Gem::Version
|
199
231
|
version: '0'
|
200
232
|
requirements: []
|
201
|
-
|
202
|
-
rubygems_version: 2.7.7
|
233
|
+
rubygems_version: 3.0.3
|
203
234
|
signing_key:
|
204
235
|
specification_version: 4
|
205
236
|
summary: Dynamically generate configs and reload servers
|