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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94091ff703a09d316f4bc62973a8995b1815bcc8a591c224fcffa147cba2cd9d
4
- data.tar.gz: 47c9cb4ade0f69e2b5822e27800f70b0465cec2a3780aadfe70027e7d2088795
3
+ metadata.gz: 20798a914f83d1ab8531eb66259f202816e40ade371172102058536cf2fa3af7
4
+ data.tar.gz: 5f742b8f777496e322f845eccc9c641eb7bad0815c8853cd1eea26bbddd2080c
5
5
  SHA512:
6
- metadata.gz: d1b5ed7d48c978e999a76441b8b43c0dee017dc12fc1e54dfb0c418f1d92d4d326457d3e06b5706a644966d607f8bc74ceb85d8d34fac58f72f51eff663b8cff
7
- data.tar.gz: '08c0b0faf4151961eb18536408b576c43000b0409f189d613b9f9738c4ba54ebda2cc8a3925ba12699e7d3832aab651b09dad339ff362de9fff1fb37f8b54fe1'
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
@@ -4,5 +4,6 @@
4
4
  /.yardoc
5
5
  /coverage
6
6
  /.bundle
7
+ /cfg.txt
7
8
 
8
9
  .rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml
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) 2018 Civilized Discourse Construction Kit, Inc.
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
@@ -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 = GVB.version rescue "0.0.0.1.NOGVB"
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
@@ -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? and #sleep_duration).
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
- regenerate_config(force_reload: true)
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([notifier.to_io, @terminate_r], [], [], sleep_duration.tap { |d| logger.debug(logloc) { "Sleeping for #{d} seconds" } })
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
- logger.debug(logloc) { require 'digest/md5'; "Existing config hash: #{Digest::MD5.hexdigest(File.read(config_file))}, new config hash: #{Digest::MD5.hexdigest(File.read(tmpfile.path))}" }
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
- if diff.to_s != ""
423
- logger.info(logloc) { "Config has changed. Diff:\n#{diff.to_s}" }
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
- if force_reload || diff.to_s != ""
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.0
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: 2019-03-01 00:00:00.000000000 Z
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: git-version-bump
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: rake
127
+ name: rubocop
113
128
  requirement: !ruby/object:Gem::Requirement
114
129
  requirements:
115
- - - "~>"
130
+ - - ">="
116
131
  - !ruby/object:Gem::Version
117
- version: '12.0'
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: '12.0'
139
+ version: '0'
125
140
  - !ruby/object:Gem::Dependency
126
- name: redcarpet
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: rubocop
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: yard
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
- rubyforge_project:
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