confctl 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,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