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,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
|