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
         |