service_skeleton 0.0.0.1.ENOTAG → 0.0.0.2.g46c1e0e

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +0 -2
  3. data/.rubocop.yml +114 -9
  4. data/.travis.yml +11 -0
  5. data/README.md +153 -279
  6. data/lib/service_skeleton/background_worker.rb +80 -0
  7. data/lib/service_skeleton/config.rb +18 -78
  8. data/lib/service_skeleton/config_variable.rb +8 -29
  9. data/lib/service_skeleton/config_variables.rb +68 -54
  10. data/lib/service_skeleton/error.rb +3 -5
  11. data/lib/service_skeleton/filtering_logger.rb +0 -2
  12. data/lib/service_skeleton/logging_helpers.rb +3 -10
  13. data/lib/service_skeleton/metrics_methods.rb +13 -28
  14. data/lib/service_skeleton/signal_handler.rb +183 -0
  15. data/lib/service_skeleton.rb +145 -22
  16. data/service_skeleton.gemspec +9 -10
  17. metadata +19 -102
  18. data/.editorconfig +0 -7
  19. data/.git-blame-ignore-revs +0 -2
  20. data/.github/workflows/ci.yml +0 -50
  21. data/lib/service_skeleton/config_class.rb +0 -16
  22. data/lib/service_skeleton/config_variable/boolean.rb +0 -21
  23. data/lib/service_skeleton/config_variable/enum.rb +0 -27
  24. data/lib/service_skeleton/config_variable/float.rb +0 -25
  25. data/lib/service_skeleton/config_variable/integer.rb +0 -25
  26. data/lib/service_skeleton/config_variable/kv_list.rb +0 -26
  27. data/lib/service_skeleton/config_variable/path_list.rb +0 -13
  28. data/lib/service_skeleton/config_variable/string.rb +0 -18
  29. data/lib/service_skeleton/config_variable/url.rb +0 -36
  30. data/lib/service_skeleton/config_variable/yaml_file.rb +0 -42
  31. data/lib/service_skeleton/generator.rb +0 -165
  32. data/lib/service_skeleton/metric_method_name.rb +0 -9
  33. data/lib/service_skeleton/runner.rb +0 -46
  34. data/lib/service_skeleton/service_name.rb +0 -20
  35. data/lib/service_skeleton/signal_manager.rb +0 -202
  36. data/lib/service_skeleton/signals_methods.rb +0 -15
  37. data/lib/service_skeleton/ultravisor_children.rb +0 -20
  38. data/lib/service_skeleton/ultravisor_loggerstash.rb +0 -11
  39. data/ultravisor/.yardopts +0 -1
  40. data/ultravisor/Guardfile +0 -9
  41. data/ultravisor/README.md +0 -404
  42. data/ultravisor/lib/ultravisor/child/call.rb +0 -21
  43. data/ultravisor/lib/ultravisor/child/call_receiver.rb +0 -14
  44. data/ultravisor/lib/ultravisor/child/cast.rb +0 -16
  45. data/ultravisor/lib/ultravisor/child/cast_receiver.rb +0 -11
  46. data/ultravisor/lib/ultravisor/child/process_cast_call.rb +0 -39
  47. data/ultravisor/lib/ultravisor/child.rb +0 -481
  48. data/ultravisor/lib/ultravisor/error.rb +0 -25
  49. data/ultravisor/lib/ultravisor/logging_helpers.rb +0 -32
  50. data/ultravisor/lib/ultravisor.rb +0 -216
  51. data/ultravisor/spec/example_group_methods.rb +0 -19
  52. data/ultravisor/spec/example_methods.rb +0 -8
  53. data/ultravisor/spec/spec_helper.rb +0 -52
  54. data/ultravisor/spec/ultravisor/add_child_spec.rb +0 -79
  55. data/ultravisor/spec/ultravisor/child/call_spec.rb +0 -121
  56. data/ultravisor/spec/ultravisor/child/cast_spec.rb +0 -111
  57. data/ultravisor/spec/ultravisor/child/id_spec.rb +0 -21
  58. data/ultravisor/spec/ultravisor/child/new_spec.rb +0 -152
  59. data/ultravisor/spec/ultravisor/child/restart_delay_spec.rb +0 -40
  60. data/ultravisor/spec/ultravisor/child/restart_spec.rb +0 -70
  61. data/ultravisor/spec/ultravisor/child/run_spec.rb +0 -95
  62. data/ultravisor/spec/ultravisor/child/shutdown_spec.rb +0 -124
  63. data/ultravisor/spec/ultravisor/child/spawn_spec.rb +0 -107
  64. data/ultravisor/spec/ultravisor/child/unsafe_instance_spec.rb +0 -55
  65. data/ultravisor/spec/ultravisor/child/wait_spec.rb +0 -32
  66. data/ultravisor/spec/ultravisor/new_spec.rb +0 -71
  67. data/ultravisor/spec/ultravisor/remove_child_spec.rb +0 -49
  68. data/ultravisor/spec/ultravisor/run_spec.rb +0 -334
  69. data/ultravisor/spec/ultravisor/shutdown_spec.rb +0 -106
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ServiceSkeleton
4
- module ConfigClass
5
- Undefined = Module.new
6
- private_constant :Undefined
7
-
8
- def config_class(klass = Undefined)
9
- unless klass == Undefined
10
- @config_class = klass
11
- end
12
-
13
- @config_class
14
- end
15
- end
16
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "service_skeleton/config_variable"
4
-
5
- class ServiceSkeleton::ConfigVariable::Boolean < ServiceSkeleton::ConfigVariable
6
- private
7
-
8
- def pluck_value(env)
9
- maybe_default(env) do
10
- case env[@name.to_s]
11
- when /\A(no|n|off|0|false)\z/i
12
- false
13
- when /\A(yes|y|on|1|true)\z/i
14
- true
15
- else
16
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
17
- "Value #{env[@name.to_s].inspect} for environment variable #{@name} is not a valid boolean value"
18
- end
19
- end
20
- end
21
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "service_skeleton/config_variable"
4
-
5
- class ServiceSkeleton::ConfigVariable::Enum < ServiceSkeleton::ConfigVariable
6
- private
7
-
8
- def pluck_value(env)
9
- maybe_default(env) do
10
- v = env[@name.to_s]
11
-
12
- if @opts[:values].is_a?(Array)
13
- unless @opts[:values].include?(v)
14
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
15
- "Invalid value for #{@name}; must be one of #{@opts[:values].join(", ")}"
16
- end
17
- v
18
- elsif @opts[:values].is_a?(Hash)
19
- unless @opts[:values].keys.include?(v)
20
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
21
- "Invalid value for #{@name}; must be one of #{@opts[:values].keys.join(", ")}"
22
- end
23
- @opts[:values][v]
24
- end
25
- end
26
- end
27
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "service_skeleton/config_variable"
4
-
5
- class ServiceSkeleton::ConfigVariable::Float < ServiceSkeleton::ConfigVariable
6
- private
7
-
8
- def pluck_value(env)
9
- maybe_default(env) do
10
- value = env[@name.to_s]
11
-
12
- if value =~ /\A-?\d+.?\d*\z/
13
- value.to_f.tap do |f|
14
- unless @opts[:range].include?(f)
15
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
16
- "Value #{f} for environment variable #{@name} is out of the valid range (must be between #{@opts[:range].first} and #{@opts[:range].last} inclusive)"
17
- end
18
- end
19
- else
20
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
21
- "Value #{value.inspect} for environment variable #{@name} is not a valid numeric value"
22
- end
23
- end
24
- end
25
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "service_skeleton/config_variable"
4
-
5
- class ServiceSkeleton::ConfigVariable::Integer < ServiceSkeleton::ConfigVariable
6
- private
7
-
8
- def pluck_value(env)
9
- maybe_default(env) do
10
- value = env[@name.to_s]
11
-
12
- if value =~ /\A-?\d+\z/
13
- value.to_i.tap do |i|
14
- unless @opts[:range].include?(i)
15
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
16
- "Value #{i} for environment variable #{@name} is out of the valid range (must be between #{@opts[:range].first} and #{@opts[:range].last} inclusive)"
17
- end
18
- end
19
- else
20
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
21
- "Value #{value.inspect} for environment variable #{@name} is not a valid integer value"
22
- end
23
- end
24
- end
25
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "service_skeleton/config_variable"
4
-
5
- class ServiceSkeleton::ConfigVariable::KVList < ServiceSkeleton::ConfigVariable
6
- def redact!(env)
7
- env.keys.each { |k| env[k] = "*SENSITIVE*" if k =~ @opts[:key_pattern] }
8
- end
9
-
10
- private
11
-
12
- def pluck_value(env)
13
- matches = env.select { |k, _| k.to_s =~ @opts[:key_pattern] }
14
-
15
- if matches.empty?
16
- if @opts.has_key?(:default)
17
- @opts[:default]
18
- else
19
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
20
- "no keys for key-value list #{@name} specified"
21
- end
22
- else
23
- matches.transform_keys { |k| @opts[:key_pattern].match(k.to_s)[1].to_sym }
24
- end
25
- end
26
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "service_skeleton/config_variable"
4
-
5
- class ServiceSkeleton::ConfigVariable::PathList < ServiceSkeleton::ConfigVariable
6
- private
7
-
8
- def pluck_value(env)
9
- maybe_default(env) do
10
- env[@name.to_s].split(":")
11
- end
12
- end
13
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "service_skeleton/config_variable"
4
-
5
- class ServiceSkeleton::ConfigVariable::String < ServiceSkeleton::ConfigVariable
6
- private
7
-
8
- def pluck_value(env)
9
- maybe_default(env) do
10
- env[@name.to_s].tap do |s|
11
- if @opts[:match] && s !~ @opts[:match]
12
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
13
- "Value for #{@name} must match #{@opts[:match]}"
14
- end
15
- end
16
- end
17
- end
18
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "service_skeleton/config_variable"
4
-
5
- class ServiceSkeleton::ConfigVariable::URL < ServiceSkeleton::ConfigVariable
6
- def redact?(env)
7
- !!(env.has_key?(@name.to_s) && (@opts[:sensitive] || URI(env[@name.to_s] || "").password))
8
- end
9
-
10
- def redact!(env)
11
- if env.has_key?(@name.to_s)
12
- super
13
- uri = URI(env[@name.to_s])
14
- if uri.password
15
- uri.password = "*REDACTED*"
16
- env[@name.to_s] = uri.to_s
17
- end
18
- end
19
- end
20
-
21
- private
22
-
23
- def pluck_value(env)
24
- maybe_default(env) do
25
- begin
26
- v = env[@name.to_s]
27
- URI(v)
28
- rescue URI::InvalidURIError
29
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
30
- "Value for #{@name} (#{v}) does not appear to be a valid URL"
31
- end
32
-
33
- v
34
- end
35
- end
36
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "yaml"
4
-
5
- require "service_skeleton/config_variable"
6
-
7
- class ServiceSkeleton::ConfigVariable::YamlFile < ServiceSkeleton::ConfigVariable
8
- def redact!(env)
9
- if env.has_key?(@name.to_s)
10
- if File.world_readable?(env[@name.to_s])
11
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
12
- "Sensitive file #{env[@name.to_s]} is world-readable!"
13
- end
14
-
15
- super
16
- end
17
- end
18
-
19
- private
20
-
21
- def pluck_value(env)
22
- maybe_default(env) do
23
- begin
24
- val = YAML.safe_load(File.read(env[@name.to_s]))
25
- if @opts[:klass]
26
- val = @opts[:klass].new(val)
27
- end
28
-
29
- val
30
- rescue Errno::ENOENT
31
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
32
- "YAML file #{env[@name.to_s]} does not exist"
33
- rescue Errno::EPERM
34
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
35
- "Do not have permission to read YAML file #{env[@name.to_s]}"
36
- rescue Psych::SyntaxError => ex
37
- raise ServiceSkeleton::Error::InvalidEnvironmentError,
38
- "Invalid YAML syntax: #{ex.message}"
39
- end
40
- end
41
- end
42
- end
@@ -1,165 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "config"
4
- require_relative "signal_manager"
5
- require_relative "ultravisor_loggerstash"
6
-
7
- require "frankenstein/ruby_gc_metrics"
8
- require "frankenstein/ruby_vm_metrics"
9
- require "frankenstein/process_metrics"
10
- require "frankenstein/server"
11
- require "prometheus/client/registry"
12
- require "sigdump"
13
- require_relative "../../ultravisor/lib/ultravisor"
14
-
15
- module ServiceSkeleton
16
- module Generator
17
- def generate(config:, metrics_registry:, service_metrics:, service_signal_handlers:)
18
- Ultravisor.new(logger: config.logger).tap do |ultravisor|
19
- initialize_metrics(ultravisor, config, metrics_registry, service_metrics)
20
- initialize_loggerstash(ultravisor, config, metrics_registry)
21
- initialize_signals(ultravisor, config, service_signal_handlers, metrics_registry)
22
- end
23
- end
24
-
25
- private
26
-
27
- def initialize_metrics(ultravisor, config, registry, metrics)
28
- Frankenstein::RubyGCMetrics.register(registry)
29
- Frankenstein::RubyVMMetrics.register(registry)
30
- Frankenstein::ProcessMetrics.register(registry)
31
-
32
- metrics.each do |m|
33
- registry.register(m)
34
-
35
- method_name = m.method_name(config.service_name)
36
-
37
- if registry.singleton_class.method_defined?(method_name)
38
- raise ServiceSkeleton::Error::InvalidMetricNameError,
39
- "Metric method #{method_name} is already defined"
40
- end
41
-
42
- registry.define_singleton_method(method_name) do
43
- m
44
- end
45
- end
46
-
47
- if config.metrics_port
48
- config.pre_run_logger.info(config.service_name) { "Starting metrics server on port #{config.metrics_port}" }
49
- ultravisor.add_child(
50
- id: :metrics_server,
51
- klass: Frankenstein::Server,
52
- method: :run,
53
- args: [
54
- port: config.metrics_port,
55
- logger: config.logger,
56
- metrics_prefix: :"#{config.service_name}_metrics_server",
57
- registry: registry,
58
- ]
59
- )
60
- end
61
- end
62
-
63
- def initialize_loggerstash(ultravisor, config, registry)
64
- if config.logstash_server && !config.logstash_server.empty?
65
- config.pre_run_logger.info(config.service_name) { "Configuring loggerstash to send to #{config.logstash_server}" }
66
-
67
- ultravisor.add_child(
68
- id: :logstash_writer,
69
- klass: LogstashWriter,
70
- method: :run,
71
- args: [
72
- server_name: config.logstash_server,
73
- metrics_registry: registry,
74
- logger: config.logger,
75
- ],
76
- access: :unsafe
77
- )
78
-
79
- config.logger.singleton_class.prepend(Loggerstash::Mixin)
80
-
81
- config.logger.instance_variable_set(:@ultravisor, ultravisor)
82
- config.logger.singleton_class.prepend(ServiceSkeleton::UltravisorLoggerstash)
83
- end
84
- end
85
-
86
- def initialize_signals(ultravisor, config, service_signals, metrics_registry)
87
- counter = metrics_registry.counter(:"#{config.service_name}_signals_handled_total", docstring: "How many of each signal have been handled", labels: %i{signal})
88
-
89
- ultravisor.add_child(
90
- id: :signal_manager,
91
- klass: ServiceSkeleton::SignalManager,
92
- method: :run,
93
- args: [
94
- logger: config.logger,
95
- counter: counter,
96
- signals: global_signals(ultravisor, config.logger) + wrap_service_signals(service_signals, ultravisor),
97
- ],
98
- shutdown: {
99
- method: :shutdown,
100
- timeout: 1,
101
- }
102
- )
103
- end
104
-
105
- def global_signals(ultravisor, logger)
106
- # For mysterious reasons of mystery, simplecov doesn't recognise these
107
- # procs as being called, even though there are definitely tests for
108
- # them. So...
109
- #:nocov:
110
- [
111
- [
112
- "USR1",
113
- ->() {
114
- logger.level -= 1 unless logger.level == Logger::DEBUG
115
- logger.info($0) { "Received SIGUSR1; log level is now #{Logger::SEV_LABEL[logger.level]}." }
116
- }
117
- ],
118
- [
119
- "USR2",
120
- ->() {
121
- logger.level += 1 unless logger.level == Logger::ERROR
122
- logger.info($0) { "Received SIGUSR2; log level is now #{Logger::SEV_LABEL[logger.level]}." }
123
- }
124
- ],
125
- [
126
- "HUP",
127
- ->() {
128
- logger.reopen
129
- logger.info($0) { "Received SIGHUP; log file handle reopened" }
130
- }
131
- ],
132
- [
133
- "QUIT",
134
- ->() { Sigdump.dump("+") }
135
- ],
136
- [
137
- "INT",
138
- ->() {
139
- ultravisor.shutdown(wait: false, force: !!@shutting_down)
140
- @shutting_down = true
141
- }
142
- ],
143
- [
144
- "TERM",
145
- ->() {
146
- ultravisor.shutdown(wait: false, force: !!@shutting_down)
147
- @shutting_down = true
148
- }
149
- ]
150
- ]
151
- #:nocov:
152
- end
153
-
154
- def wrap_service_signals(signals, ultravisor)
155
- [].tap do |signal_list|
156
- signals.each do |service_name, sigs|
157
- sigs.each do |sig, proc|
158
- wrapped_proc = ->() { ultravisor[service_name.to_sym].unsafe_instance.instance_eval(&proc) }
159
- signal_list << [sig, wrapped_proc]
160
- end
161
- end
162
- end
163
- end
164
- end
165
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ServiceSkeleton
4
- module MetricMethodName
5
- def method_name(svc_name)
6
- @name.to_s.gsub(/\A#{Regexp.quote(svc_name)}_/i, '').downcase
7
- end
8
- end
9
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "config"
4
- require_relative "logging_helpers"
5
- require_relative "signal_manager"
6
-
7
- require "frankenstein/ruby_gc_metrics"
8
- require "frankenstein/ruby_vm_metrics"
9
- require "frankenstein/process_metrics"
10
- require "frankenstein/server"
11
- require "prometheus/client/registry"
12
- require "sigdump"
13
- require_relative "../../ultravisor/lib/ultravisor"
14
-
15
- module ServiceSkeleton
16
- class Runner
17
- include ServiceSkeleton::LoggingHelpers
18
-
19
- def initialize(klass, env)
20
- @config = (klass.config_class || ServiceSkeleton::Config).new(env, klass.service_name, klass.registered_variables)
21
- @logger = @config.logger
22
-
23
- @metrics_registry = Prometheus::Client::Registry.new
24
-
25
- @ultravisor = ServiceSkeleton.generate(
26
- config: @config,
27
- metrics_registry: @metrics_registry,
28
- service_metrics: klass.registered_metrics,
29
- service_signal_handlers: { klass.service_name.to_sym => klass.registered_signal_handlers }
30
- )
31
-
32
- klass.register_ultravisor_children(@ultravisor, config: @config, metrics_registry: @metrics_registry)
33
- end
34
-
35
- def run
36
- @config.pre_run_logger.info(logloc) { "Starting service #{@config.service_name}" }
37
- @config.pre_run_logger.info(logloc) { (["Environment:"] + @config.env.map { |k, v| "#{k}=#{v.inspect}" }).join("\n ") }
38
-
39
- @ultravisor.run
40
- end
41
-
42
- private
43
-
44
- attr_reader :logger
45
- end
46
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ServiceSkeleton
4
- module ServiceName
5
- def service_name
6
- service_name_from_class(self)
7
- end
8
-
9
- private
10
-
11
- def service_name_from_class(klass)
12
- klass.to_s
13
- .gsub("::", "_")
14
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
15
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
16
- .downcase
17
- .gsub(/[^a-zA-Z0-9_]/, "_")
18
- end
19
- end
20
- end