confctl 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,53 @@
|
|
1
|
+
module ConfCtl
|
2
|
+
# Feed string data and get output as lines
|
3
|
+
class LineBuffer
|
4
|
+
# If instantiated with a block, the block is invoked for each read line
|
5
|
+
# @yieldparam line [String]
|
6
|
+
def initialize(&block)
|
7
|
+
@buffer = ''
|
8
|
+
@block = block
|
9
|
+
end
|
10
|
+
|
11
|
+
# Feed string
|
12
|
+
# @param str [String]
|
13
|
+
def <<(str)
|
14
|
+
buffer << str
|
15
|
+
return if block.nil?
|
16
|
+
|
17
|
+
loop do
|
18
|
+
out_line = read_line
|
19
|
+
break if out_line.nil?
|
20
|
+
|
21
|
+
block.call(out_line)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Read one line if there is one
|
26
|
+
# @return [String, nil]
|
27
|
+
def read_line
|
28
|
+
nl = buffer.index("\n")
|
29
|
+
return if nl.nil?
|
30
|
+
|
31
|
+
line = buffer[0..nl]
|
32
|
+
@buffer = buffer[nl + 1..]
|
33
|
+
line
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return the buffer's contents and flush it
|
37
|
+
#
|
38
|
+
# If block was given to {LineBuffer}, it will be invoked with the buffer
|
39
|
+
# contents.
|
40
|
+
#
|
41
|
+
# @return [String]
|
42
|
+
def flush
|
43
|
+
ret = buffer.clone
|
44
|
+
buffer.clear
|
45
|
+
block.call(ret) if block
|
46
|
+
ret
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
attr_reader :buffer, :block
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'pathname'
|
3
|
+
require 'pp'
|
4
|
+
require 'singleton'
|
5
|
+
|
6
|
+
module ConfCtl
|
7
|
+
class Logger
|
8
|
+
class << self
|
9
|
+
%i[
|
10
|
+
open
|
11
|
+
close
|
12
|
+
unlink
|
13
|
+
close_and_unlink
|
14
|
+
keep?
|
15
|
+
keep
|
16
|
+
io
|
17
|
+
path
|
18
|
+
relative_path
|
19
|
+
].each do |v|
|
20
|
+
define_method(v) { |*args, **kwargs| instance.send(v, *args, **kwargs) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
include Singleton
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@mutex = Mutex.new
|
28
|
+
@readers = []
|
29
|
+
@keep = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def open(name, output: nil)
|
33
|
+
if output
|
34
|
+
@io = output
|
35
|
+
else
|
36
|
+
dir = ConfDir.log_dir
|
37
|
+
FileUtils.mkdir_p(dir)
|
38
|
+
|
39
|
+
@io = File.new(File.join(dir, file_name(name)), 'w')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def open?
|
44
|
+
!@io.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
def close
|
48
|
+
raise 'log file not open' if @io.nil?
|
49
|
+
|
50
|
+
@io.close
|
51
|
+
end
|
52
|
+
|
53
|
+
def io
|
54
|
+
raise 'log file not open' if @io.nil?
|
55
|
+
|
56
|
+
@io
|
57
|
+
end
|
58
|
+
|
59
|
+
def path
|
60
|
+
raise 'log file not open' if @io.nil?
|
61
|
+
|
62
|
+
@io.path
|
63
|
+
end
|
64
|
+
|
65
|
+
def relative_path
|
66
|
+
return @relative_path if @relative_path
|
67
|
+
|
68
|
+
abs = Pathname.new(path)
|
69
|
+
dir = Pathname.new(ConfDir.path)
|
70
|
+
abs.relative_path_from(dir).to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def keep?
|
74
|
+
@keep
|
75
|
+
end
|
76
|
+
|
77
|
+
def keep
|
78
|
+
@keep = true
|
79
|
+
end
|
80
|
+
|
81
|
+
def unlink
|
82
|
+
raise 'log file not open' if @io.nil?
|
83
|
+
|
84
|
+
File.unlink(@io.path)
|
85
|
+
end
|
86
|
+
|
87
|
+
def close_and_unlink
|
88
|
+
close
|
89
|
+
unlink
|
90
|
+
end
|
91
|
+
|
92
|
+
def write(str)
|
93
|
+
sync do
|
94
|
+
@io << str
|
95
|
+
@io.flush
|
96
|
+
end
|
97
|
+
|
98
|
+
readers.each { |r| r << str }
|
99
|
+
end
|
100
|
+
|
101
|
+
def <<(str)
|
102
|
+
write(str)
|
103
|
+
end
|
104
|
+
|
105
|
+
def cli(cmd, gopts, opts, args)
|
106
|
+
sync do
|
107
|
+
PP.pp({
|
108
|
+
command: cmd,
|
109
|
+
global_options: prune_opts(gopts.clone),
|
110
|
+
command_options: prune_opts(opts.clone),
|
111
|
+
arguments: args
|
112
|
+
}, @io)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# @param obj [#<<]
|
117
|
+
def add_reader(obj)
|
118
|
+
@readers << obj
|
119
|
+
end
|
120
|
+
|
121
|
+
# @param obj [#<<]
|
122
|
+
def remove_reader(obj)
|
123
|
+
@readers.delete(obj)
|
124
|
+
end
|
125
|
+
|
126
|
+
protected
|
127
|
+
|
128
|
+
attr_reader :mutex, :readers
|
129
|
+
|
130
|
+
def sync(&)
|
131
|
+
if mutex.owned?
|
132
|
+
yield
|
133
|
+
else
|
134
|
+
mutex.synchronize(&)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def file_name(name)
|
139
|
+
n = [
|
140
|
+
Time.now.strftime('%Y-%m-%d--%H-%M-%S'),
|
141
|
+
'confctl',
|
142
|
+
name
|
143
|
+
].compact.join('-')
|
144
|
+
"#{n}.log"
|
145
|
+
end
|
146
|
+
|
147
|
+
def prune_opts(hash)
|
148
|
+
hash.delete_if { |k, _v| k.is_a?(::Symbol) }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module ConfCtl
|
2
|
+
class Machine
|
3
|
+
attr_reader :name, :safe_name, :managed, :spin, :opts
|
4
|
+
|
5
|
+
# @param opts [Hash]
|
6
|
+
def initialize(opts)
|
7
|
+
@opts = opts
|
8
|
+
@name = opts['name']
|
9
|
+
@safe_name = opts['name'].gsub('/', ':')
|
10
|
+
@managed = opts['managed']
|
11
|
+
@spin = opts['spin']
|
12
|
+
end
|
13
|
+
|
14
|
+
def target_host
|
15
|
+
(opts['host'] && opts['host']['target']) || name
|
16
|
+
end
|
17
|
+
|
18
|
+
def localhost?
|
19
|
+
target_host == 'localhost'
|
20
|
+
end
|
21
|
+
|
22
|
+
def nix_paths
|
23
|
+
opts['nix']['nixPath'].to_h do |v|
|
24
|
+
eq = v.index('=')
|
25
|
+
raise "'#{v}' is not a valid nix path entry " if eq.nil?
|
26
|
+
|
27
|
+
[v[0..eq - 1], v[eq + 1..]]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def health_checks
|
32
|
+
return @health_checks if @health_checks
|
33
|
+
|
34
|
+
@health_checks = []
|
35
|
+
|
36
|
+
opts['healthChecks'].each do |type, checks|
|
37
|
+
case type
|
38
|
+
when 'systemd'
|
39
|
+
next if !checks['enable'] || spin != 'nixos'
|
40
|
+
|
41
|
+
if checks['systemProperties'].any?
|
42
|
+
@health_checks << HealthChecks::Systemd::Properties.new(
|
43
|
+
self,
|
44
|
+
property_checks: checks['systemProperties'].map do |v|
|
45
|
+
HealthChecks::Systemd::PropertyCheck.new(v)
|
46
|
+
end
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
checks['unitProperties'].each do |unit_name, prop_checks|
|
51
|
+
health_checks << HealthChecks::Systemd::Properties.new(
|
52
|
+
self,
|
53
|
+
pattern: unit_name,
|
54
|
+
property_checks: prop_checks.map do |v|
|
55
|
+
HealthChecks::Systemd::PropertyCheck.new(v)
|
56
|
+
end
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
when 'builderCommands', 'machineCommands'
|
61
|
+
checks.each do |cmd|
|
62
|
+
health_checks << HealthChecks::RunCommand.new(
|
63
|
+
self,
|
64
|
+
HealthChecks::RunCommand::Command.new(self, cmd),
|
65
|
+
remote: type == 'machineCommands'
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
else
|
70
|
+
raise "Unsupported health-check type #{type.inspect}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
@health_checks
|
75
|
+
end
|
76
|
+
|
77
|
+
def [](key)
|
78
|
+
if key.index('.')
|
79
|
+
get(opts, key.split('.'))
|
80
|
+
elsif key == 'checks'
|
81
|
+
health_checks.length
|
82
|
+
else
|
83
|
+
opts[key]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_s
|
88
|
+
name
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def get(hash, keys)
|
94
|
+
k = keys.shift
|
95
|
+
|
96
|
+
return unless hash.has_key?(k)
|
97
|
+
|
98
|
+
if keys.empty?
|
99
|
+
hash[k]
|
100
|
+
elsif hash[k].nil?
|
101
|
+
nil
|
102
|
+
else
|
103
|
+
get(hash[k], keys)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'etc'
|
2
|
+
|
3
|
+
module ConfCtl
|
4
|
+
class MachineControl
|
5
|
+
# @return [Machine]
|
6
|
+
attr_reader :machine
|
7
|
+
|
8
|
+
# @param machine [Machine]
|
9
|
+
def initialize(machine)
|
10
|
+
@machine = machine
|
11
|
+
@extra_ssh_opts = []
|
12
|
+
@cmd = SystemCommand.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# Try to open SSH connection
|
16
|
+
# @raise [TTY::Command::ExitError]
|
17
|
+
def test_connection
|
18
|
+
with_ssh_opts(
|
19
|
+
'-o', 'ConnectTimeout=3',
|
20
|
+
'-o', 'ServerAliveInterval=3',
|
21
|
+
'-o', 'ServerAliveCountMax=1'
|
22
|
+
) { uptime }
|
23
|
+
end
|
24
|
+
|
25
|
+
def interactive_shell
|
26
|
+
if machine.localhost?
|
27
|
+
Kernel.system(Etc.getpwuid(0).shell)
|
28
|
+
else
|
29
|
+
Kernel.system(*ssh_args)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Reboot the machine
|
34
|
+
def reboot
|
35
|
+
execute!('reboot')
|
36
|
+
end
|
37
|
+
|
38
|
+
# Reboot the machine and wait for it to come back online
|
39
|
+
# @param timeout [Integer, nil]
|
40
|
+
# @yieldparam [:reboot, :went_down, :is_down, :is_up, :timeout] state
|
41
|
+
# @return [Integer] seconds it took to reboot the machine
|
42
|
+
def reboot_and_wait(timeout: nil)
|
43
|
+
initial_uptime = uptime
|
44
|
+
t = Time.now
|
45
|
+
went_down = false
|
46
|
+
reboot
|
47
|
+
yield :reboot, timeout if block_given?
|
48
|
+
|
49
|
+
loop do
|
50
|
+
state = nil
|
51
|
+
|
52
|
+
begin
|
53
|
+
current_uptime = with_ssh_opts(
|
54
|
+
'-o', 'ConnectTimeout=3',
|
55
|
+
'-o', 'ServerAliveInterval=3',
|
56
|
+
'-o', 'ServerAliveCountMax=1'
|
57
|
+
) { uptime }
|
58
|
+
|
59
|
+
if current_uptime < initial_uptime
|
60
|
+
yield :is_up, nil
|
61
|
+
return Time.now - t
|
62
|
+
end
|
63
|
+
rescue TTY::Command::ExitError
|
64
|
+
if went_down
|
65
|
+
state = :is_down
|
66
|
+
else
|
67
|
+
state = :went_down
|
68
|
+
went_down = true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if timeout
|
73
|
+
timeleft = (t + timeout) - Time.now
|
74
|
+
|
75
|
+
raise 'timeout' if timeleft <= 0
|
76
|
+
|
77
|
+
yield state, timeleft if block_given?
|
78
|
+
elsif block_given?
|
79
|
+
yield state, nil
|
80
|
+
end
|
81
|
+
|
82
|
+
sleep(0.3)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @return [Integer] uptime in seconds
|
87
|
+
def uptime
|
88
|
+
read_file('/proc/uptime').strip.split[0].to_f
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [Array<String>]
|
92
|
+
def timezone
|
93
|
+
out, = run_cmd('date', '+%Z;%z')
|
94
|
+
out.strip.split(';')
|
95
|
+
end
|
96
|
+
|
97
|
+
# @param path [String]
|
98
|
+
# @return [String]
|
99
|
+
def read_file(path)
|
100
|
+
out, = run_cmd('cat', path)
|
101
|
+
out
|
102
|
+
end
|
103
|
+
|
104
|
+
# @param path [String]
|
105
|
+
# @return [String]
|
106
|
+
def read_symlink(path)
|
107
|
+
out, = run_cmd('readlink', path)
|
108
|
+
out.strip
|
109
|
+
end
|
110
|
+
|
111
|
+
# Execute command, raises exception on error
|
112
|
+
# @yieldparam out [String]
|
113
|
+
# @yieldparam err [String]
|
114
|
+
# @raise [TTY::Command::ExitError]
|
115
|
+
# @return [TTY::Command::Result]
|
116
|
+
def execute(...)
|
117
|
+
run_cmd(...)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Execute command, no exception raised on error
|
121
|
+
# @yieldparam out [String]
|
122
|
+
# @yieldparam err [String]
|
123
|
+
# @return [TTY::Command::Result]
|
124
|
+
def execute!(...)
|
125
|
+
run_cmd!(...)
|
126
|
+
end
|
127
|
+
|
128
|
+
# @param script [String]
|
129
|
+
# @return [TTY::Command::Result]
|
130
|
+
def bash_script(script)
|
131
|
+
run_cmd('bash', '--norc', input: script)
|
132
|
+
end
|
133
|
+
|
134
|
+
protected
|
135
|
+
|
136
|
+
attr_reader :cmd, :extra_ssh_opts
|
137
|
+
|
138
|
+
def run_cmd(...)
|
139
|
+
do_run_cmd(:run, ...)
|
140
|
+
end
|
141
|
+
|
142
|
+
def run_cmd!(...)
|
143
|
+
do_run_cmd(:run!, ...)
|
144
|
+
end
|
145
|
+
|
146
|
+
# rubocop:disable Style/ArgumentsForwarding
|
147
|
+
|
148
|
+
def do_run_cmd(method, *command, **kwargs, &)
|
149
|
+
args =
|
150
|
+
if machine.localhost?
|
151
|
+
command
|
152
|
+
else
|
153
|
+
ssh_args + command
|
154
|
+
end
|
155
|
+
|
156
|
+
cmd.method(method).call(*args, **kwargs, &)
|
157
|
+
end
|
158
|
+
|
159
|
+
# rubocop:enable Style/ArgumentsForwarding
|
160
|
+
|
161
|
+
def with_ssh_opts(*opts)
|
162
|
+
@extra_ssh_opts = opts
|
163
|
+
ret = yield
|
164
|
+
@extra_ssh_opts = []
|
165
|
+
ret
|
166
|
+
end
|
167
|
+
|
168
|
+
def ssh_args
|
169
|
+
['ssh', '-l', 'root'] + extra_ssh_opts + [machine.target_host]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module ConfCtl
|
4
|
+
class MachineList
|
5
|
+
# @param machine [Machine]
|
6
|
+
# @return [MachineList]
|
7
|
+
def self.from_machine(machine)
|
8
|
+
new(machines: { machine.name => machine })
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param opts [Hash]
|
12
|
+
# @option opts [Boolean] :show_trace
|
13
|
+
# @option opts [MachineList] :machines
|
14
|
+
def initialize(opts = {})
|
15
|
+
@opts = opts
|
16
|
+
@machines = opts[:machines] || parse(extract)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @yieldparam [String] host
|
20
|
+
# @yieldparam [Machine] machine
|
21
|
+
def each(&)
|
22
|
+
machines.each(&)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @yieldparam [String] host
|
26
|
+
def each_key(&)
|
27
|
+
machines.each_key(&)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @yieldparam [Machine] machine
|
31
|
+
def each_value(&)
|
32
|
+
machines.each_value(&)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @yieldparam [String] host
|
36
|
+
# @yieldparam [Machine] machine
|
37
|
+
# @return [MachineList]
|
38
|
+
def select(&)
|
39
|
+
self.class.new(machines: machines.select(&))
|
40
|
+
end
|
41
|
+
|
42
|
+
# @yieldparam [String] host
|
43
|
+
# @yieldparam [Machine] machine
|
44
|
+
# @return [Array]
|
45
|
+
def map(&)
|
46
|
+
machines.map(&)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [MachineList]
|
50
|
+
def managed
|
51
|
+
select { |_host, machine| machine.managed }
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [MachineList]
|
55
|
+
def unmanaged
|
56
|
+
select { |_host, machine| !machine.managed }
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param host [String]
|
60
|
+
def [](host)
|
61
|
+
@machines[host]
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Machine, nil]
|
65
|
+
def first
|
66
|
+
@machines.each_value.first
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [Integer]
|
70
|
+
def length
|
71
|
+
@machines.length
|
72
|
+
end
|
73
|
+
|
74
|
+
def empty?
|
75
|
+
@machines.empty?
|
76
|
+
end
|
77
|
+
|
78
|
+
def any?
|
79
|
+
!empty?
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Array<HealthChecks::Base>]
|
83
|
+
def health_checks
|
84
|
+
checks = []
|
85
|
+
|
86
|
+
machines.each_value do |machine|
|
87
|
+
checks.concat(machine.health_checks)
|
88
|
+
end
|
89
|
+
|
90
|
+
checks
|
91
|
+
end
|
92
|
+
|
93
|
+
protected
|
94
|
+
|
95
|
+
attr_reader :opts, :machines
|
96
|
+
|
97
|
+
def extract
|
98
|
+
nix = Nix.new
|
99
|
+
nix.list_machines
|
100
|
+
end
|
101
|
+
|
102
|
+
def parse(data)
|
103
|
+
data.transform_values do |info|
|
104
|
+
Machine.new(info)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'tty-command'
|
2
|
+
|
3
|
+
module ConfCtl
|
4
|
+
class MachineStatus
|
5
|
+
class SwpinState
|
6
|
+
# @return [Swpins::Specs::Base]
|
7
|
+
attr_reader :target_spec
|
8
|
+
|
9
|
+
# @return [Hash, nil]
|
10
|
+
attr_reader :current_info
|
11
|
+
|
12
|
+
# @param target_spec [Swpins::Specs::Base]
|
13
|
+
# @param current_info [Hash, nil]
|
14
|
+
def initialize(target_spec, current_info)
|
15
|
+
@target_spec = target_spec
|
16
|
+
@current_info = current_info
|
17
|
+
@uptodate =
|
18
|
+
if current_info
|
19
|
+
target_spec.check_info(current_info)
|
20
|
+
else
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def uptodate?
|
26
|
+
@uptodate
|
27
|
+
end
|
28
|
+
|
29
|
+
def outdated?
|
30
|
+
!uptodate?
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [String, nil]
|
34
|
+
def target_version
|
35
|
+
target_spec.version
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [String, nil]
|
39
|
+
def current_version
|
40
|
+
target_spec.version_info(current_info) || 'unknown'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Machine]
|
45
|
+
attr_reader :machine
|
46
|
+
|
47
|
+
# @return [Boolean]
|
48
|
+
attr_reader :status
|
49
|
+
|
50
|
+
# @return [Boolean]
|
51
|
+
attr_reader :online
|
52
|
+
alias online? online
|
53
|
+
|
54
|
+
# @return [Float]
|
55
|
+
attr_reader :uptime
|
56
|
+
|
57
|
+
# @return [String]
|
58
|
+
attr_accessor :target_toplevel
|
59
|
+
|
60
|
+
# @return [String]
|
61
|
+
attr_reader :current_toplevel
|
62
|
+
|
63
|
+
# @return [String]
|
64
|
+
attr_reader :timezone_name
|
65
|
+
|
66
|
+
# @return [String]
|
67
|
+
attr_reader :timezone_offset
|
68
|
+
|
69
|
+
# @return [Generation::HostList]
|
70
|
+
attr_reader :generations
|
71
|
+
|
72
|
+
# @return [Hash]
|
73
|
+
attr_accessor :target_swpin_specs
|
74
|
+
|
75
|
+
# @return [Hash]
|
76
|
+
attr_reader :swpins_info
|
77
|
+
|
78
|
+
# @return [Hash]
|
79
|
+
attr_reader :swpins_state
|
80
|
+
|
81
|
+
# @param machine [Machine]
|
82
|
+
def initialize(machine)
|
83
|
+
@machine = machine
|
84
|
+
@mc = MachineControl.new(machine)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Connect to the machine and query its state
|
88
|
+
def query(toplevel: true, generations: true)
|
89
|
+
begin
|
90
|
+
@uptime = mc.uptime
|
91
|
+
rescue TTY::Command::ExitError
|
92
|
+
return
|
93
|
+
end
|
94
|
+
|
95
|
+
if toplevel
|
96
|
+
begin
|
97
|
+
@current_toplevel = mc.read_symlink('/run/current-system')
|
98
|
+
rescue TTY::Command::ExitError
|
99
|
+
return
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
if generations
|
104
|
+
begin
|
105
|
+
@generations = Generation::HostList.fetch(mc)
|
106
|
+
rescue TTY::Command::ExitError
|
107
|
+
return
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
begin
|
112
|
+
@swpins_info = Swpins::DeployedInfo.parse!(mc.read_file('/etc/confctl/swpins-info.json'))
|
113
|
+
rescue Error
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def evaluate
|
119
|
+
@swpins_state = {}
|
120
|
+
|
121
|
+
target_swpin_specs.each do |name, spec|
|
122
|
+
swpins_state[name] = SwpinState.new(spec, swpins_info && swpins_info[name])
|
123
|
+
end
|
124
|
+
|
125
|
+
outdated_swpins = swpins_state.detect { |_k, v| v.outdated? }
|
126
|
+
@online = uptime ? true : false
|
127
|
+
@status = online? && !outdated_swpins
|
128
|
+
@status = false if target_toplevel && target_toplevel != current_toplevel
|
129
|
+
end
|
130
|
+
|
131
|
+
protected
|
132
|
+
|
133
|
+
attr_reader :mc
|
134
|
+
end
|
135
|
+
end
|