confctl 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.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +11 -0
  3. data/.gitignore +8 -0
  4. data/.overcommit.yml +6 -0
  5. data/.rubocop.yml +67 -0
  6. data/.rubocop_todo.yml +5 -0
  7. data/.ruby-version +1 -0
  8. data/CHANGELOG.md +2 -0
  9. data/Gemfile +2 -0
  10. data/LICENSE.txt +674 -0
  11. data/README.md +522 -0
  12. data/Rakefile +40 -0
  13. data/bin/confctl +4 -0
  14. data/confctl.gemspec +33 -0
  15. data/example/.gitignore +2 -0
  16. data/example/README.md +38 -0
  17. data/example/cluster/cluster.nix +7 -0
  18. data/example/cluster/module-list.nix +3 -0
  19. data/example/cluster/nixos-machine/config.nix +15 -0
  20. data/example/cluster/nixos-machine/hardware.nix +4 -0
  21. data/example/cluster/nixos-machine/module.nix +8 -0
  22. data/example/cluster/vpsadminos-container/config.nix +22 -0
  23. data/example/cluster/vpsadminos-container/module.nix +8 -0
  24. data/example/cluster/vpsadminos-machine/config.nix +22 -0
  25. data/example/cluster/vpsadminos-machine/hardware.nix +4 -0
  26. data/example/cluster/vpsadminos-machine/module.nix +8 -0
  27. data/example/cluster/vpsfreecz-vps/config.nix +25 -0
  28. data/example/cluster/vpsfreecz-vps/module.nix +8 -0
  29. data/example/configs/confctl.nix +10 -0
  30. data/example/configs/swpins.nix +28 -0
  31. data/example/data/default.nix +5 -0
  32. data/example/data/ssh-keys.nix +7 -0
  33. data/example/environments/base.nix +13 -0
  34. data/example/modules/module-list.nix +13 -0
  35. data/example/shell.nix +11 -0
  36. data/example/swpins/channels/nixos-unstable.json +35 -0
  37. data/example/swpins/channels/vpsadminos-staging.json +35 -0
  38. data/lib/confctl/cli/app.rb +551 -0
  39. data/lib/confctl/cli/attr_filters.rb +51 -0
  40. data/lib/confctl/cli/cluster.rb +1248 -0
  41. data/lib/confctl/cli/command.rb +206 -0
  42. data/lib/confctl/cli/configuration.rb +296 -0
  43. data/lib/confctl/cli/gen_data.rb +97 -0
  44. data/lib/confctl/cli/generation.rb +335 -0
  45. data/lib/confctl/cli/log_view.rb +267 -0
  46. data/lib/confctl/cli/output_formatter.rb +288 -0
  47. data/lib/confctl/cli/swpins/base.rb +40 -0
  48. data/lib/confctl/cli/swpins/channel.rb +73 -0
  49. data/lib/confctl/cli/swpins/cluster.rb +80 -0
  50. data/lib/confctl/cli/swpins/core.rb +86 -0
  51. data/lib/confctl/cli/swpins/utils.rb +55 -0
  52. data/lib/confctl/cli/swpins.rb +5 -0
  53. data/lib/confctl/cli/tag_filters.rb +30 -0
  54. data/lib/confctl/cli.rb +5 -0
  55. data/lib/confctl/conf_cache.rb +105 -0
  56. data/lib/confctl/conf_dir.rb +88 -0
  57. data/lib/confctl/erb_template.rb +37 -0
  58. data/lib/confctl/exceptions.rb +3 -0
  59. data/lib/confctl/gcroot.rb +30 -0
  60. data/lib/confctl/generation/build.rb +145 -0
  61. data/lib/confctl/generation/build_list.rb +106 -0
  62. data/lib/confctl/generation/host.rb +35 -0
  63. data/lib/confctl/generation/host_list.rb +81 -0
  64. data/lib/confctl/generation/unified.rb +117 -0
  65. data/lib/confctl/generation/unified_list.rb +63 -0
  66. data/lib/confctl/git_repo_mirror.rb +79 -0
  67. data/lib/confctl/health_checks/base.rb +66 -0
  68. data/lib/confctl/health_checks/run_command.rb +179 -0
  69. data/lib/confctl/health_checks/systemd/properties.rb +84 -0
  70. data/lib/confctl/health_checks/systemd/property_check.rb +31 -0
  71. data/lib/confctl/health_checks/systemd/property_list.rb +20 -0
  72. data/lib/confctl/health_checks.rb +5 -0
  73. data/lib/confctl/hook.rb +35 -0
  74. data/lib/confctl/line_buffer.rb +53 -0
  75. data/lib/confctl/logger.rb +151 -0
  76. data/lib/confctl/machine.rb +107 -0
  77. data/lib/confctl/machine_control.rb +172 -0
  78. data/lib/confctl/machine_list.rb +108 -0
  79. data/lib/confctl/machine_status.rb +135 -0
  80. data/lib/confctl/module_options.rb +95 -0
  81. data/lib/confctl/nix.rb +382 -0
  82. data/lib/confctl/nix_build.rb +108 -0
  83. data/lib/confctl/nix_collect_garbage.rb +64 -0
  84. data/lib/confctl/nix_copy.rb +49 -0
  85. data/lib/confctl/nix_format.rb +124 -0
  86. data/lib/confctl/nix_literal_expression.rb +15 -0
  87. data/lib/confctl/parallel_executor.rb +43 -0
  88. data/lib/confctl/pattern.rb +9 -0
  89. data/lib/confctl/settings.rb +50 -0
  90. data/lib/confctl/std_line_buffer.rb +40 -0
  91. data/lib/confctl/swpins/change_set.rb +151 -0
  92. data/lib/confctl/swpins/channel.rb +62 -0
  93. data/lib/confctl/swpins/channel_list.rb +47 -0
  94. data/lib/confctl/swpins/cluster_name.rb +94 -0
  95. data/lib/confctl/swpins/cluster_name_list.rb +15 -0
  96. data/lib/confctl/swpins/core.rb +137 -0
  97. data/lib/confctl/swpins/deployed_info.rb +23 -0
  98. data/lib/confctl/swpins/spec.rb +20 -0
  99. data/lib/confctl/swpins/specs/base.rb +184 -0
  100. data/lib/confctl/swpins/specs/directory.rb +51 -0
  101. data/lib/confctl/swpins/specs/git.rb +135 -0
  102. data/lib/confctl/swpins/specs/git_rev.rb +24 -0
  103. data/lib/confctl/swpins.rb +17 -0
  104. data/lib/confctl/system_command.rb +10 -0
  105. data/lib/confctl/user_script.rb +13 -0
  106. data/lib/confctl/user_scripts.rb +41 -0
  107. data/lib/confctl/utils/file.rb +21 -0
  108. data/lib/confctl/version.rb +3 -0
  109. data/lib/confctl.rb +43 -0
  110. data/man/man8/confctl-options.nix.8 +1334 -0
  111. data/man/man8/confctl-options.nix.8.md +1340 -0
  112. data/man/man8/confctl.8 +660 -0
  113. data/man/man8/confctl.8.md +654 -0
  114. data/nix/evaluator.nix +160 -0
  115. data/nix/lib/default.nix +83 -0
  116. data/nix/lib/machine/default.nix +74 -0
  117. data/nix/lib/machine/info.nix +5 -0
  118. data/nix/lib/swpins/eval.nix +71 -0
  119. data/nix/lib/swpins/options.nix +94 -0
  120. data/nix/machines.nix +31 -0
  121. data/nix/modules/cluster/default.nix +459 -0
  122. data/nix/modules/confctl/cli.nix +21 -0
  123. data/nix/modules/confctl/generations.nix +84 -0
  124. data/nix/modules/confctl/nix.nix +28 -0
  125. data/nix/modules/confctl/swpins.nix +55 -0
  126. data/nix/modules/module-list.nix +19 -0
  127. data/shell.nix +42 -0
  128. data/template/confctl-options.nix/main.erb +45 -0
  129. data/template/confctl-options.nix/options.erb +15 -0
  130. metadata +353 -0
@@ -0,0 +1,117 @@
1
+ module ConfCtl
2
+ class Generation::Unified
3
+ # @return [String]
4
+ attr_reader :host
5
+
6
+ # @return [String]
7
+ attr_reader :name
8
+
9
+ # @return [Integer, nil]
10
+ attr_reader :id
11
+
12
+ # @return [String]
13
+ attr_reader :toplevel
14
+
15
+ # @return [Time]
16
+ attr_reader :date
17
+
18
+ # @return [Boolean]
19
+ attr_reader :current
20
+
21
+ # @return [Generation::Build]
22
+ attr_reader :build_generation
23
+
24
+ # @return [Generation::Host]
25
+ attr_reader :host_generation
26
+
27
+ # @param host [String]
28
+ # @param build_generation [Generation::Build]
29
+ # @param host_generation [Generation::Host]
30
+ def initialize(host, build_generation: nil, host_generation: nil)
31
+ @host = host
32
+ @build_generation = build_generation
33
+ @host_generation = host_generation
34
+ @id = host_generation && host_generation.id
35
+
36
+ if build_generation
37
+ @name = build_generation.name
38
+ @toplevel = build_generation.toplevel
39
+ @date = build_generation.date
40
+ @current ||= build_generation.current
41
+ elsif host_generation
42
+ @name = host_generation.approx_name
43
+ @toplevel = host_generation.toplevel
44
+ @date = host_generation.date
45
+ @current ||= host_generation.current
46
+ else
47
+ raise ArgumentError, 'set build or host'
48
+ end
49
+ end
50
+
51
+ # @param gen [Generation::Build]
52
+ def build_generation=(gen)
53
+ @name = gen.name
54
+ @date = gen.date
55
+ @current ||= gen.current
56
+ @build_generation = gen
57
+ end
58
+
59
+ # @param gen [Generation::Host]
60
+ def host_generation=(gen)
61
+ @id = gen.id
62
+ @current ||= gen.current
63
+ @host_generation = gen
64
+ end
65
+
66
+ # Determines whether `gen` can be wrapped by this object
67
+ # @param gen [Generation::Build, Generation::Host]
68
+ # @return [Boolean]
69
+ def includes?(gen)
70
+ return false if host != gen.host || toplevel != gen.toplevel
71
+
72
+ if build_generation && gen.is_a?(Generation::Build)
73
+ build_generation.name == gen.name \
74
+ && build_generation.swpin_paths == gen.swpin_paths
75
+ else
76
+ true
77
+ end
78
+ end
79
+
80
+ %i[swpin_names swpin_specs].each do |v|
81
+ define_method(v) do
82
+ build_generation ? build_generation.send(v) : []
83
+ end
84
+ end
85
+
86
+ def current_str
87
+ build = build_generation && build_generation.current
88
+ host = host_generation && host_generation.current
89
+
90
+ if build && host
91
+ 'build+host'
92
+ elsif build
93
+ 'build'
94
+ elsif host
95
+ 'host'
96
+ end
97
+ end
98
+
99
+ def presence_str
100
+ if build_generation && host_generation
101
+ 'build+host'
102
+ elsif build_generation
103
+ 'build'
104
+ elsif host_generation
105
+ 'host'
106
+ else
107
+ raise 'programming error'
108
+ end
109
+ end
110
+
111
+ def destroy
112
+ build_generation.destroy if build_generation
113
+ host_generation.destroy if host_generation
114
+ true
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,63 @@
1
+ module ConfCtl
2
+ class Generation::UnifiedList
3
+ def initialize
4
+ @generations = []
5
+ end
6
+
7
+ # @param generation [Generation::Build]
8
+ def add_build_generation(generation)
9
+ unified = generations.detect { |g| g.includes?(generation) }
10
+
11
+ if unified
12
+ unified.build_generation = generation
13
+ else
14
+ generations << Generation::Unified.new(generation.host, build_generation: generation)
15
+ end
16
+
17
+ true
18
+ end
19
+
20
+ # @param generations [Generation::BuildList]
21
+ def add_build_generations(generations)
22
+ generations.each { |v| add_build_generation(v) }
23
+ true
24
+ end
25
+
26
+ # @param generation [Generation::Host]
27
+ def add_host_generation(generation)
28
+ unified = generations.detect { |g| g.includes?(generation) }
29
+
30
+ if unified
31
+ unified.host_generation = generation
32
+ else
33
+ generations << Generation::Unified.new(generation.host, host_generation: generation)
34
+ end
35
+
36
+ true
37
+ end
38
+
39
+ # @param generations [Generation::HostList]
40
+ def add_host_generations(generations)
41
+ generations.each { |v| add_host_generation(v) }
42
+ true
43
+ end
44
+
45
+ def each(&)
46
+ generations.each(&)
47
+ end
48
+
49
+ def delete_if(&)
50
+ generations.delete_if(&)
51
+ end
52
+
53
+ def empty?
54
+ generations.empty?
55
+ end
56
+
57
+ include Enumerable
58
+
59
+ protected
60
+
61
+ attr_reader :generations
62
+ end
63
+ end
@@ -0,0 +1,79 @@
1
+ require 'digest'
2
+ require 'fileutils'
3
+
4
+ module ConfCtl
5
+ class GitRepoMirror
6
+ attr_reader :url, :name
7
+
8
+ # @param url [String]
9
+ # @param quiet [Boolean]
10
+ def initialize(url, quiet: false)
11
+ @url = url
12
+ @quiet = quiet
13
+ @name = Digest::SHA256.hexdigest(url)
14
+ @cmd = SystemCommand.new
15
+ end
16
+
17
+ def setup
18
+ File.stat(mirror_path)
19
+ rescue Errno::ENOENT
20
+ FileUtils.mkdir_p(mirror_path)
21
+ git('clone', args: ['--mirror', url, mirror_path])
22
+ else
23
+ git_repo('fetch')
24
+ end
25
+
26
+ def revision_parse(str)
27
+ git_repo('rev-parse', args: [str])
28
+ end
29
+
30
+ # @param from_ref [String]
31
+ # @param to_ref [String]
32
+ def log(from_ref, to_ref, opts: [])
33
+ ret = "git log for #{from_ref}..#{to_ref}\n"
34
+ ret << git_repo(
35
+ 'log',
36
+ opts: ['--no-decorate', '--left-right', '--cherry-mark'] + opts,
37
+ args: ["#{from_ref}..#{to_ref}"]
38
+ )
39
+ ret
40
+ end
41
+
42
+ # @param from_ref [String]
43
+ # @param to_ref [String]
44
+ def diff(from_ref, to_ref, opts: [])
45
+ ret = "git diff for #{from_ref}..#{to_ref}\n"
46
+ ret << git_repo(
47
+ 'diff',
48
+ opts:,
49
+ args: ["#{from_ref}..#{to_ref}"]
50
+ )
51
+ ret
52
+ end
53
+
54
+ protected
55
+
56
+ attr_reader :cmd, :quiet
57
+
58
+ def git_repo(git_cmd, *args, **kwargs)
59
+ kwargs[:gopts] ||= []
60
+ kwargs[:gopts] << '-C' << mirror_path
61
+ git(git_cmd, *args, **kwargs)
62
+ end
63
+
64
+ def git(git_cmd, args: [], opts: [], gopts: [])
65
+ opts << '--quiet' if quiet && %w[clone fetch].include?(git_cmd)
66
+
67
+ out, = cmd.run('git', *gopts, git_cmd, *opts, *args)
68
+ out.strip
69
+ end
70
+
71
+ def mirror_path
72
+ File.join(mirror_dir, name)
73
+ end
74
+
75
+ def mirror_dir
76
+ File.join(ConfCtl.cache_dir, 'git-mirrors')
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,66 @@
1
+ module ConfCtl
2
+ class HealthChecks::Base
3
+ # @return [Machine]
4
+ attr_reader :machine
5
+
6
+ # @return [Array<String>]
7
+ attr_reader :errors
8
+
9
+ # @param machine [Machine]
10
+ def initialize(machine)
11
+ @machine = machine
12
+ @errors = []
13
+ end
14
+
15
+ # @yieldparam [Integer] number of attempts
16
+ # @yieldparam [Array] errors
17
+ def run
18
+ @started_at = Time.now
19
+ now = @started_at
20
+ attempt = 1
21
+
22
+ until timeout?(now)
23
+ @errors.clear
24
+ run_check
25
+ break if successful?
26
+
27
+ yield(attempt, errors) if block_given?
28
+ sleep(cooldown)
29
+ attempt += 1
30
+ now = Time.now
31
+ end
32
+ end
33
+
34
+ def successful?
35
+ @errors.empty?
36
+ end
37
+
38
+ def description
39
+ raise NotImplementedError
40
+ end
41
+
42
+ def message
43
+ @errors.join('; ')
44
+ end
45
+
46
+ protected
47
+
48
+ attr_reader :started_at
49
+
50
+ def run_check
51
+ raise NotImplementedError
52
+ end
53
+
54
+ def timeout?(_time)
55
+ true
56
+ end
57
+
58
+ def cooldown
59
+ 1
60
+ end
61
+
62
+ def add_error(msg)
63
+ @errors << msg
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,179 @@
1
+ require 'confctl/health_checks/base'
2
+
3
+ module ConfCtl
4
+ class HealthChecks::RunCommand < HealthChecks::Base
5
+ class Output
6
+ # @return [String, nil]
7
+ attr_reader :match
8
+
9
+ # @return [Array<String>]
10
+ attr_reader :include
11
+
12
+ # @return [Array<String>]
13
+ attr_reader :exclude
14
+
15
+ def initialize(opts)
16
+ @match = opts['match']
17
+ @include = opts['include']
18
+ @exclude = opts['exclude']
19
+ end
20
+
21
+ # Returns nil if there is a match
22
+ # @return [String, nil]
23
+ def check_match(str)
24
+ if @match.nil? || str == @match
25
+ nil
26
+ else
27
+ @match
28
+ end
29
+ end
30
+
31
+ # Returns the string that should be and is not included
32
+ # @return [String, nil]
33
+ def check_include(str)
34
+ @include.each do |inc|
35
+ return inc unless str.include?(inc)
36
+ end
37
+
38
+ nil
39
+ end
40
+
41
+ # Returns the string that is and should not be included
42
+ # @return [String, nil]
43
+ def check_exclude(str)
44
+ @exclude.each do |exc|
45
+ return exc if str.include?(exc)
46
+ end
47
+
48
+ nil
49
+ end
50
+ end
51
+
52
+ class Command
53
+ # @return [String]
54
+ attr_reader :description
55
+
56
+ # @return [Array<String>]
57
+ attr_reader :command
58
+
59
+ # @return [Integer]
60
+ attr_reader :exit_status
61
+
62
+ # @return [Output]
63
+ attr_reader :stdout
64
+
65
+ # @return [Output]
66
+ attr_reader :stderr
67
+
68
+ # @return [Integer]
69
+ attr_reader :timeout
70
+
71
+ # @return [Integer]
72
+ attr_reader :cooldown
73
+
74
+ def initialize(machine, opts)
75
+ @description = opts['description']
76
+ @command = make_command(machine, opts['command'])
77
+ @exit_status = opts['exitStatus']
78
+ @stdout = Output.new(opts['standardOutput'])
79
+ @stderr = Output.new(opts['standardError'])
80
+ @timeout = opts['timeout']
81
+ @cooldown = opts['cooldown']
82
+ end
83
+
84
+ def to_s
85
+ @command.join(' ')
86
+ end
87
+
88
+ protected
89
+
90
+ def make_command(machine, args)
91
+ args.map do |arg|
92
+ arg.gsub(/\{([^\}]+)\}/) do
93
+ machine[::Regexp.last_match(1)]
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ # @param machine [Machine]
100
+ # @param command [Command]
101
+ # @param remote [Boolean]
102
+ def initialize(machine, command, remote:)
103
+ super(machine)
104
+ @command = command
105
+ @remote = remote
106
+ end
107
+
108
+ def description
109
+ if @command.description.empty?
110
+ @command.to_s
111
+ else
112
+ @command.description
113
+ end
114
+ end
115
+
116
+ protected
117
+
118
+ def run_check
119
+ if @remote
120
+ run_remote_check
121
+ else
122
+ run_local_check
123
+ end
124
+ end
125
+
126
+ def run_remote_check
127
+ mc = MachineControl.new(machine)
128
+ result = mc.execute!(*@command.command)
129
+
130
+ if result.status != @command.exit_status
131
+ add_error("#{@command} failed with #{result.status} (#{@command.description})")
132
+ end
133
+
134
+ check_output(result)
135
+ end
136
+
137
+ def run_local_check
138
+ cmd = SystemCommand.new
139
+ result = cmd.run!(*@command.command)
140
+
141
+ if result.status != @command.exit_status
142
+ add_error("#{@command} failed with #{result.status} (#{@command.description})")
143
+ end
144
+
145
+ check_output(result)
146
+ end
147
+
148
+ def check_output(result)
149
+ # stdout
150
+ if (fragment = @command.stdout.check_match(result.out))
151
+ add_error("#{@command}: standard output does not match #{fragment.inspect}")
152
+
153
+ elsif (fragment = @command.stdout.check_include(result.out))
154
+ add_error("#{@command}: standard output does not include #{fragment.inspect}")
155
+
156
+ elsif (fragment = @command.stdout.check_exclude(result.out))
157
+ add_error("#{@command}: standard output includes #{fragment.inspect}")
158
+
159
+ # stderr
160
+ elsif (fragment = @command.stderr.check_match(result.err))
161
+ add_error("#{@command}: standard error does not match #{fragment.inspect}")
162
+
163
+ elsif (fragment = @command.stderr.check_include(result.err))
164
+ add_error("#{@command}: standard error does not include #{fragment.inspect}")
165
+
166
+ elsif (fragment = @command.stderr.check_exclude(result.err))
167
+ add_error("#{@command}: standard error includes #{fragment.inspect}")
168
+ end
169
+ end
170
+
171
+ def timeout?(time)
172
+ started_at + @command.timeout < time
173
+ end
174
+
175
+ def cooldown
176
+ @command.cooldown
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,84 @@
1
+ require 'confctl/health_checks/base'
2
+
3
+ module ConfCtl
4
+ class HealthChecks::Systemd::Properties < HealthChecks::Base
5
+ # @param machine [Machine]
6
+ # @param pattern [String, nil]
7
+ # @param property_checks [Array<HealthChecks::Systemd::PropertyCheck>]
8
+ def initialize(machine, property_checks:, pattern: nil)
9
+ super(machine)
10
+ @pattern = pattern
11
+ @property_checks = property_checks
12
+ @shortest_timeout = property_checks.inject(nil) do |acc, check|
13
+ if acc.nil? || check.timeout < acc
14
+ check.timeout
15
+ else
16
+ acc
17
+ end
18
+ end
19
+ @shortest_cooldown = property_checks.inject(nil) do |acc, check|
20
+ if acc.nil? || check.cooldown < acc
21
+ check.cooldown
22
+ else
23
+ acc
24
+ end
25
+ end
26
+ end
27
+
28
+ def description
29
+ ret = ''
30
+
31
+ if @pattern
32
+ ret << @pattern << ': '
33
+ else
34
+ ret << 'systemd: '
35
+ end
36
+
37
+ ret << @property_checks.map(&:to_s).join(', ')
38
+ ret
39
+ end
40
+
41
+ def message
42
+ if @pattern
43
+ "#{@pattern}: #{super}"
44
+ else
45
+ super
46
+ end
47
+ end
48
+
49
+ protected
50
+
51
+ def run_check
52
+ mc = MachineControl.new(machine)
53
+ cmd = %w[systemctl show]
54
+ cmd << @pattern if @pattern
55
+ result = mc.execute!(*cmd)
56
+
57
+ if result.failure?
58
+ add_error("#{cmd.join(' ')} failed with #{result.status}")
59
+ return
60
+ end
61
+
62
+ properties = HealthChecks::Systemd::PropertyList.from_enumerator(result.each)
63
+
64
+ @property_checks.each do |check|
65
+ v = properties[check.property]
66
+
67
+ if v.nil?
68
+ add_error("property #{check.property.inspect} not found")
69
+ next
70
+ end
71
+
72
+ add_error("#{check.property}=#{v}, expected #{check.value}") unless check.check(v)
73
+ end
74
+ end
75
+
76
+ def timeout?(time)
77
+ started_at + @shortest_timeout < time
78
+ end
79
+
80
+ def cooldown
81
+ @shortest_cooldown
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,31 @@
1
+ module ConfCtl
2
+ class HealthChecks::Systemd::PropertyCheck
3
+ # @return [String]
4
+ attr_reader :property
5
+
6
+ # @return [String]
7
+ attr_reader :value
8
+
9
+ # @return [Integer]
10
+ attr_reader :timeout
11
+
12
+ # @return [Integer]
13
+ attr_reader :cooldown
14
+
15
+ def initialize(opts)
16
+ @property = opts['property']
17
+ @value = opts['value']
18
+ @timeout = opts['timeout']
19
+ @cooldown = opts['cooldown']
20
+ end
21
+
22
+ # @return [Boolean]
23
+ def check(v)
24
+ @value == v
25
+ end
26
+
27
+ def to_s
28
+ "#{property}=#{value}"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ module ConfCtl
2
+ class HealthChecks::Systemd::PropertyList < Hash
3
+ def self.from_enumerator(it)
4
+ hash = new
5
+
6
+ it.each do |line|
7
+ stripped = line.strip
8
+ eq = line.index('=')
9
+ next if eq.nil?
10
+
11
+ k = line[0..(eq - 1)]
12
+ v = line[(eq + 1)..]
13
+
14
+ hash[k] = v
15
+ end
16
+
17
+ hash
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ module ConfCtl
2
+ module HealthChecks
3
+ module Systemd; end
4
+ end
5
+ end
@@ -0,0 +1,35 @@
1
+ module ConfCtl
2
+ module Hook
3
+ # @param name [Symbol]
4
+ def self.register(name)
5
+ @hooks ||= {}
6
+ @hooks[name] ||= []
7
+ end
8
+
9
+ # @param name [Symbol]
10
+ def self.subscribe(name, &block)
11
+ subscribers = (@hooks || {})[name]
12
+
13
+ raise "hook #{name.inspect} not registered" if subscribers.nil?
14
+
15
+ subscribers << block
16
+ nil
17
+ end
18
+
19
+ # @param name [Symbol]
20
+ # @param args [Array]
21
+ # @param kwargs [Hash]
22
+ # @return [any]
23
+ def self.call(name, args: [], kwargs: {})
24
+ return if @hooks.nil?
25
+
26
+ subscribers = @hooks[name]
27
+ return if subscribers.empty?
28
+
29
+ subscribers.inject(nil) do |ret, sub|
30
+ hook_kwargs = kwargs.merge(return_value: ret)
31
+ sub.call(*args, **hook_kwargs)
32
+ end
33
+ end
34
+ end
35
+ end