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.
- checksums.yaml +7 -0
- data/.editorconfig +11 -0
- data/.gitignore +8 -0
- data/.overcommit.yml +6 -0
- data/.rubocop.yml +67 -0
- data/.rubocop_todo.yml +5 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +674 -0
- data/README.md +522 -0
- data/Rakefile +40 -0
- data/bin/confctl +4 -0
- data/confctl.gemspec +33 -0
- data/example/.gitignore +2 -0
- data/example/README.md +38 -0
- data/example/cluster/cluster.nix +7 -0
- data/example/cluster/module-list.nix +3 -0
- data/example/cluster/nixos-machine/config.nix +15 -0
- data/example/cluster/nixos-machine/hardware.nix +4 -0
- data/example/cluster/nixos-machine/module.nix +8 -0
- data/example/cluster/vpsadminos-container/config.nix +22 -0
- data/example/cluster/vpsadminos-container/module.nix +8 -0
- data/example/cluster/vpsadminos-machine/config.nix +22 -0
- data/example/cluster/vpsadminos-machine/hardware.nix +4 -0
- data/example/cluster/vpsadminos-machine/module.nix +8 -0
- data/example/cluster/vpsfreecz-vps/config.nix +25 -0
- data/example/cluster/vpsfreecz-vps/module.nix +8 -0
- data/example/configs/confctl.nix +10 -0
- data/example/configs/swpins.nix +28 -0
- data/example/data/default.nix +5 -0
- data/example/data/ssh-keys.nix +7 -0
- data/example/environments/base.nix +13 -0
- data/example/modules/module-list.nix +13 -0
- data/example/shell.nix +11 -0
- data/example/swpins/channels/nixos-unstable.json +35 -0
- data/example/swpins/channels/vpsadminos-staging.json +35 -0
- data/lib/confctl/cli/app.rb +551 -0
- data/lib/confctl/cli/attr_filters.rb +51 -0
- data/lib/confctl/cli/cluster.rb +1248 -0
- data/lib/confctl/cli/command.rb +206 -0
- data/lib/confctl/cli/configuration.rb +296 -0
- data/lib/confctl/cli/gen_data.rb +97 -0
- data/lib/confctl/cli/generation.rb +335 -0
- data/lib/confctl/cli/log_view.rb +267 -0
- data/lib/confctl/cli/output_formatter.rb +288 -0
- data/lib/confctl/cli/swpins/base.rb +40 -0
- data/lib/confctl/cli/swpins/channel.rb +73 -0
- data/lib/confctl/cli/swpins/cluster.rb +80 -0
- data/lib/confctl/cli/swpins/core.rb +86 -0
- data/lib/confctl/cli/swpins/utils.rb +55 -0
- data/lib/confctl/cli/swpins.rb +5 -0
- data/lib/confctl/cli/tag_filters.rb +30 -0
- data/lib/confctl/cli.rb +5 -0
- data/lib/confctl/conf_cache.rb +105 -0
- data/lib/confctl/conf_dir.rb +88 -0
- data/lib/confctl/erb_template.rb +37 -0
- data/lib/confctl/exceptions.rb +3 -0
- data/lib/confctl/gcroot.rb +30 -0
- data/lib/confctl/generation/build.rb +145 -0
- data/lib/confctl/generation/build_list.rb +106 -0
- data/lib/confctl/generation/host.rb +35 -0
- data/lib/confctl/generation/host_list.rb +81 -0
- data/lib/confctl/generation/unified.rb +117 -0
- data/lib/confctl/generation/unified_list.rb +63 -0
- data/lib/confctl/git_repo_mirror.rb +79 -0
- data/lib/confctl/health_checks/base.rb +66 -0
- data/lib/confctl/health_checks/run_command.rb +179 -0
- data/lib/confctl/health_checks/systemd/properties.rb +84 -0
- data/lib/confctl/health_checks/systemd/property_check.rb +31 -0
- data/lib/confctl/health_checks/systemd/property_list.rb +20 -0
- data/lib/confctl/health_checks.rb +5 -0
- data/lib/confctl/hook.rb +35 -0
- data/lib/confctl/line_buffer.rb +53 -0
- data/lib/confctl/logger.rb +151 -0
- data/lib/confctl/machine.rb +107 -0
- data/lib/confctl/machine_control.rb +172 -0
- data/lib/confctl/machine_list.rb +108 -0
- data/lib/confctl/machine_status.rb +135 -0
- data/lib/confctl/module_options.rb +95 -0
- data/lib/confctl/nix.rb +382 -0
- data/lib/confctl/nix_build.rb +108 -0
- data/lib/confctl/nix_collect_garbage.rb +64 -0
- data/lib/confctl/nix_copy.rb +49 -0
- data/lib/confctl/nix_format.rb +124 -0
- data/lib/confctl/nix_literal_expression.rb +15 -0
- data/lib/confctl/parallel_executor.rb +43 -0
- data/lib/confctl/pattern.rb +9 -0
- data/lib/confctl/settings.rb +50 -0
- data/lib/confctl/std_line_buffer.rb +40 -0
- data/lib/confctl/swpins/change_set.rb +151 -0
- data/lib/confctl/swpins/channel.rb +62 -0
- data/lib/confctl/swpins/channel_list.rb +47 -0
- data/lib/confctl/swpins/cluster_name.rb +94 -0
- data/lib/confctl/swpins/cluster_name_list.rb +15 -0
- data/lib/confctl/swpins/core.rb +137 -0
- data/lib/confctl/swpins/deployed_info.rb +23 -0
- data/lib/confctl/swpins/spec.rb +20 -0
- data/lib/confctl/swpins/specs/base.rb +184 -0
- data/lib/confctl/swpins/specs/directory.rb +51 -0
- data/lib/confctl/swpins/specs/git.rb +135 -0
- data/lib/confctl/swpins/specs/git_rev.rb +24 -0
- data/lib/confctl/swpins.rb +17 -0
- data/lib/confctl/system_command.rb +10 -0
- data/lib/confctl/user_script.rb +13 -0
- data/lib/confctl/user_scripts.rb +41 -0
- data/lib/confctl/utils/file.rb +21 -0
- data/lib/confctl/version.rb +3 -0
- data/lib/confctl.rb +43 -0
- data/man/man8/confctl-options.nix.8 +1334 -0
- data/man/man8/confctl-options.nix.8.md +1340 -0
- data/man/man8/confctl.8 +660 -0
- data/man/man8/confctl.8.md +654 -0
- data/nix/evaluator.nix +160 -0
- data/nix/lib/default.nix +83 -0
- data/nix/lib/machine/default.nix +74 -0
- data/nix/lib/machine/info.nix +5 -0
- data/nix/lib/swpins/eval.nix +71 -0
- data/nix/lib/swpins/options.nix +94 -0
- data/nix/machines.nix +31 -0
- data/nix/modules/cluster/default.nix +459 -0
- data/nix/modules/confctl/cli.nix +21 -0
- data/nix/modules/confctl/generations.nix +84 -0
- data/nix/modules/confctl/nix.nix +28 -0
- data/nix/modules/confctl/swpins.nix +55 -0
- data/nix/modules/module-list.nix +19 -0
- data/shell.nix +42 -0
- data/template/confctl-options.nix/main.erb +45 -0
- data/template/confctl-options.nix/options.erb +15 -0
- 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
|
data/lib/confctl/hook.rb
ADDED
|
@@ -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
|