confctl 2.2.2 → 3.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 +4 -4
- data/.git-hooks/pre_commit/nixfmt.rb +13 -0
- data/.github/workflows/rspec.yml +64 -0
- data/.github/workflows/rubocop.yml +27 -0
- data/.github/workflows/tests.yml +139 -0
- data/.gitignore +12 -7
- data/.overcommit.yml +2 -0
- data/.rspec +1 -0
- data/.rubocop.yml +8 -1
- data/AGENTS.md +42 -0
- data/CHANGELOG.md +57 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +198 -0
- data/README.md +166 -58
- data/Rakefile +5 -0
- data/confctl.gemspec +20 -2
- data/docs/carrier.md +3 -3
- data/docs/flake-inputs.md +159 -0
- data/docs/swpins-to-flakes.md +325 -0
- data/example/cluster/module-list.nix +2 -1
- data/example/cluster/nixos-machine/config.nix +12 -2
- data/example/cluster/nixos-machine/hardware.nix +6 -1
- data/example/cluster/vpsadminos-container/config.nix +6 -1
- data/example/cluster/vpsadminos-container/module.nix +4 -1
- data/example/cluster/vpsadminos-machine/config.nix +6 -1
- data/example/cluster/vpsadminos-machine/hardware.nix +6 -1
- data/example/cluster/vpsadminos-machine/module.nix +5 -2
- data/example/cluster/vpsfreecz-vps/config.nix +6 -1
- data/example/cluster/vpsfreecz-vps/module.nix +4 -1
- data/example/configs/swpins.nix +8 -3
- data/example/environments/base.nix +6 -1
- data/example/swpins/core.json +35 -0
- data/example-flake/.gitignore +2 -0
- data/example-flake/README.md +38 -0
- data/example-flake/cluster/cluster.nix +5 -0
- data/example-flake/cluster/module-list.nix +4 -0
- data/example-flake/cluster/nested/nixos-machine/config.nix +25 -0
- data/example-flake/cluster/nested/nixos-machine/hardware.nix +9 -0
- data/example-flake/cluster/nested/nixos-machine/module.nix +8 -0
- data/example-flake/cluster/nixos-machine/config.nix +25 -0
- data/example-flake/cluster/nixos-machine/hardware.nix +9 -0
- data/example-flake/cluster/nixos-machine/module.nix +8 -0
- data/example-flake/cluster/vpsadminos-container/config.nix +28 -0
- data/example-flake/cluster/vpsadminos-container/module.nix +8 -0
- data/example-flake/cluster/vpsadminos-machine/config.nix +27 -0
- data/example-flake/cluster/vpsadminos-machine/hardware.nix +9 -0
- data/example-flake/cluster/vpsadminos-machine/module.nix +8 -0
- data/example-flake/cluster/vpsfreecz-vps/config.nix +31 -0
- data/example-flake/cluster/vpsfreecz-vps/module.nix +8 -0
- data/example-flake/configs/confctl.nix +10 -0
- data/example-flake/data/default.nix +5 -0
- data/example-flake/data/ssh-keys.nix +7 -0
- data/example-flake/environments/base.nix +18 -0
- data/example-flake/flake.lock +75 -0
- data/example-flake/flake.nix +36 -0
- data/example-flake/modules/module-list.nix +13 -0
- data/example-flake/shell.nix +11 -0
- data/flake.lock +159 -0
- data/flake.nix +189 -0
- data/gemset.nix +1022 -0
- data/lib/confctl/cli/app.rb +145 -0
- data/lib/confctl/cli/attr_filters.rb +1 -1
- data/lib/confctl/cli/cluster.rb +681 -108
- data/lib/confctl/cli/command.rb +24 -2
- data/lib/confctl/cli/configuration.rb +171 -106
- data/lib/confctl/cli/generation.rb +65 -1
- data/lib/confctl/cli/inputs/channels.rb +190 -0
- data/lib/confctl/cli/inputs/machines.rb +83 -0
- data/lib/confctl/cli/inputs/root.rb +101 -0
- data/lib/confctl/cli/inputs.rb +5 -0
- data/lib/confctl/cli/log_view.rb +21 -10
- data/lib/confctl/cli/migrate/swpins_to_flakes.rb +866 -0
- data/lib/confctl/cli/migrate.rb +5 -0
- data/lib/confctl/cli/output_formatter.rb +5 -7
- data/lib/confctl/cli/swpins/base.rb +9 -0
- data/lib/confctl/cli/swpins/channel.rb +2 -5
- data/lib/confctl/cli/swpins/cluster.rb +2 -5
- data/lib/confctl/cli/swpins/core.rb +2 -5
- data/lib/confctl/config_type.rb +7 -0
- data/lib/confctl/flake_lock.rb +78 -0
- data/lib/confctl/flake_lock_diff.rb +36 -0
- data/lib/confctl/generation/build.rb +131 -24
- data/lib/confctl/generation/build_list.rb +4 -3
- data/lib/confctl/generation/unified.rb +14 -1
- data/lib/confctl/git_repo_mirror.rb +2 -2
- data/lib/confctl/health_checks/run_command.rb +3 -2
- data/lib/confctl/health_checks/systemd/properties.rb +1 -1
- data/lib/confctl/health_checks/systemd/property_list.rb +2 -2
- data/lib/confctl/inputs/commit_message.rb +125 -0
- data/lib/confctl/inputs/git_commit.rb +17 -0
- data/lib/confctl/inputs/nix_output_guard.rb +37 -0
- data/lib/confctl/inputs/setter.rb +179 -0
- data/lib/confctl/inputs/updater.rb +76 -0
- data/lib/confctl/inputs.rb +5 -0
- data/lib/confctl/inputs_info.rb +50 -0
- data/lib/confctl/line_buffer.rb +1 -1
- data/lib/confctl/machine.rb +18 -3
- data/lib/confctl/machine_control.rb +8 -2
- data/lib/confctl/machine_list.rb +2 -2
- data/lib/confctl/machine_status.rb +63 -20
- data/lib/confctl/nix/args.rb +48 -0
- data/lib/confctl/nix.rb +30 -437
- data/lib/confctl/nix_build_flake.rb +95 -0
- data/lib/confctl/nix_copy.rb +14 -2
- data/lib/confctl/nix_flake.rb +449 -0
- data/lib/confctl/nix_format.rb +3 -3
- data/lib/confctl/nix_legacy.rb +467 -0
- data/lib/confctl/swpins/change_set.rb +29 -3
- data/lib/confctl/swpins/specs/base.rb +2 -2
- data/lib/confctl/ui.rb +19 -0
- data/lib/confctl/version.rb +1 -1
- data/man/index.html +11 -0
- data/man/man8/confctl-options.nix.8.html +113 -0
- data/man/man8/confctl.8 +152 -21
- data/man/man8/confctl.8.html +355 -0
- data/man/man8/confctl.8.md +148 -17
- data/man/style.css +301 -0
- data/nix/evaluator.nix +94 -69
- data/nix/flake/mk-confctl-devshell.nix +85 -0
- data/nix/flake/mk-confctl-outputs.nix +522 -0
- data/nix/flake/mk-config-devshell.nix +168 -0
- data/nix/lib/default.nix +118 -65
- data/nix/lib/machine/default.nix +65 -45
- data/nix/lib/machine/info.nix +16 -5
- data/nix/lib/swpins/eval.nix +42 -29
- data/nix/lib/swpins/options.nix +6 -2
- data/nix/machines.nix +23 -15
- data/nix/modules/cluster/default.nix +104 -41
- data/nix/modules/confctl/carrier/base.nix +11 -4
- data/nix/modules/confctl/carrier/carrier-env.rb +2 -2
- data/nix/modules/confctl/carrier/netboot/build-netboot-server.rb +50 -16
- data/nix/modules/confctl/carrier/netboot/nixos.nix +56 -24
- data/nix/modules/confctl/configuration-info.nix +17 -0
- data/nix/modules/confctl/generations.nix +2 -2
- data/nix/modules/confctl/host.nix +13 -0
- data/nix/modules/confctl/inputs-info.nix +21 -0
- data/nix/modules/confctl/kexec-netboot/default.nix +13 -6
- data/nix/modules/confctl/kexec-netboot/kexec-netboot.8.adoc +3 -0
- data/nix/modules/confctl/kexec-netboot/kexec-netboot.rb +25 -16
- data/nix/modules/confctl/nix.nix +31 -1
- data/nix/modules/confctl/swpins.nix +13 -6
- data/nix/modules/module-list.nix +4 -2
- data/nix/modules/system-list.nix +4 -1
- data/nix/package.nix +37 -0
- data/shell.nix +39 -8
- data/skills/confctl-configuration-update/SKILL.md +228 -0
- data/skills/confctl-configuration-update/agents/openai.yaml +4 -0
- data/skills/confctl-release/SKILL.md +102 -0
- data/skills/confctl-release/agents/openai.yaml +4 -0
- data/spec/confctl/cli/cluster_health_check_spec.rb +43 -0
- data/spec/confctl/cli/cluster_skip_current_deploy_spec.rb +157 -0
- data/spec/confctl/cli/cluster_status_flake_spec.rb +83 -0
- data/spec/confctl/cli/inputs_set_output_spec.rb +70 -0
- data/spec/confctl/configuration_spec.rb +45 -0
- data/spec/confctl/inputs_spec.rb +104 -0
- data/spec/confctl/machine_control_spec.rb +112 -0
- data/spec/confctl/machine_list_spec.rb +47 -0
- data/spec/confctl/nix_flake_spec.rb +41 -0
- data/spec/confctl/swpins_spec.rb +106 -0
- data/spec/generation/build_modes_spec.rb +132 -0
- data/spec/inputs/commit_message_spec.rb +205 -0
- data/spec/inputs/nix_cached_fallback_spec.rb +50 -0
- data/spec/inputs/nix_output_guard_spec.rb +45 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/cli_helper.rb +57 -0
- data/test-runner.sh +7 -0
- data/tests/all-tests.nix +30 -0
- data/tests/make-test.nix +15 -0
- data/tests/runner/extensions/confctl_helpers.rb +238 -0
- data/tests/runner/extensions/hostfwd_ports.rb +41 -0
- data/tests/suite/auto_rollback.nix +344 -0
- data/tests/suite/carrier/deploy.nix +751 -0
- data/tests/suite/carrier/netboot.nix +849 -0
- data/tests/suite/deploy/base.nix +806 -0
- data/tests/suite/deploy/flakes.nix +1 -0
- data/tests/suite/deploy/swpins.nix +1 -0
- metadata +104 -4
- data/nix/modules/confctl/overlays.nix +0 -15
data/lib/confctl/cli/cluster.rb
CHANGED
|
@@ -2,6 +2,7 @@ require_relative 'command'
|
|
|
2
2
|
require_relative '../hook'
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'rainbow'
|
|
5
|
+
require 'shellwords'
|
|
5
6
|
require 'tty-pager'
|
|
6
7
|
require 'tty-progressbar'
|
|
7
8
|
require 'tty-spinner'
|
|
@@ -61,7 +62,7 @@ module ConfCtl::Cli
|
|
|
61
62
|
list_machines(machines)
|
|
62
63
|
puts
|
|
63
64
|
puts "Generation: #{opts[:generation] || 'new build'}"
|
|
64
|
-
puts "Target action: #{action}#{
|
|
65
|
+
puts "Target action: #{action}#{' + reboot' if opts[:reboot]}"
|
|
65
66
|
end
|
|
66
67
|
|
|
67
68
|
ConfCtl::Hook.call(:cluster_deploy, kwargs: {
|
|
@@ -79,6 +80,9 @@ module ConfCtl::Cli
|
|
|
79
80
|
end
|
|
80
81
|
|
|
81
82
|
nix = ConfCtl::Nix.new(show_trace: opts['show-trace'])
|
|
83
|
+
machines, host_generations = skip_current_deploy_targets(machines, host_generations, action)
|
|
84
|
+
|
|
85
|
+
return if host_generations.empty?
|
|
82
86
|
|
|
83
87
|
if opts['one-by-one']
|
|
84
88
|
deploy_one_by_one(machines, host_generations, nix, action)
|
|
@@ -88,10 +92,10 @@ module ConfCtl::Cli
|
|
|
88
92
|
end
|
|
89
93
|
|
|
90
94
|
def health_check
|
|
91
|
-
machines = select_machines(args[0]).managed.select do |_host, machine|
|
|
95
|
+
machines = select_machines(args[0]).managed.runnable.select do |_host, machine|
|
|
92
96
|
machine.health_checks.any?
|
|
93
97
|
end
|
|
94
|
-
raise 'No machines to check or no health checks configured' if machines.empty?
|
|
98
|
+
raise 'No runnable machines to check or no health checks configured' if machines.empty?
|
|
95
99
|
|
|
96
100
|
run_checks = machines.health_checks
|
|
97
101
|
|
|
@@ -110,6 +114,8 @@ module ConfCtl::Cli
|
|
|
110
114
|
end
|
|
111
115
|
|
|
112
116
|
def status
|
|
117
|
+
return status_flake if flake_config?
|
|
118
|
+
|
|
113
119
|
machines = select_machines(args[0]).managed.select do |_host, machine|
|
|
114
120
|
machine.target_host || machine.carried?
|
|
115
121
|
end
|
|
@@ -216,7 +222,100 @@ module ConfCtl::Cli
|
|
|
216
222
|
OutputFormatter.print(rows, cols, layout: :columns, color: use_color?)
|
|
217
223
|
end
|
|
218
224
|
|
|
225
|
+
def status_flake
|
|
226
|
+
machines = select_machines(args[0]).managed.select do |_host, machine|
|
|
227
|
+
machine.target_host || machine.carried?
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
raise 'No machines to check' if machines.empty?
|
|
231
|
+
|
|
232
|
+
ask_confirmation! do
|
|
233
|
+
if opts[:generation]
|
|
234
|
+
puts 'The following machines will be checked:'
|
|
235
|
+
else
|
|
236
|
+
puts 'The following machines will be built and then checked:'
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
list_machines(machines)
|
|
240
|
+
puts
|
|
241
|
+
puts "Generation: #{opts[:generation] || 'new build'}"
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
statuses = machines.transform_values do |machine|
|
|
245
|
+
ConfCtl::MachineStatus.new(machine)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
if opts[:generation] == 'none'
|
|
249
|
+
host_generations = nil
|
|
250
|
+
elsif opts[:generation]
|
|
251
|
+
host_generations = find_generations(machines, opts[:generation])
|
|
252
|
+
|
|
253
|
+
# Ignore statuses when no generation was found
|
|
254
|
+
statuses.delete_if do |host, _st|
|
|
255
|
+
!host_generations.has_key?(host)
|
|
256
|
+
end
|
|
257
|
+
else
|
|
258
|
+
host_generations = do_build(machines)
|
|
259
|
+
puts
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
if host_generations
|
|
263
|
+
host_generations.each do |host, gen|
|
|
264
|
+
statuses[host].target_toplevel = gen.toplevel
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
assign_target_inputs_info(
|
|
269
|
+
statuses,
|
|
270
|
+
host_generations: opts[:generation] && opts[:generation] != 'none' ? host_generations : nil
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
tw = ConfCtl::ParallelExecutor.new(machines.length)
|
|
274
|
+
|
|
275
|
+
statuses.each_value do |st|
|
|
276
|
+
tw.add do
|
|
277
|
+
st.query(toplevel: opts[:generation] != 'none', swpins: false, inputs: true)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
tw.run
|
|
282
|
+
|
|
283
|
+
roles = inputs_roles_for_status(statuses)
|
|
284
|
+
rows = []
|
|
285
|
+
cols = %w[host online uptime status generations] + roles
|
|
286
|
+
|
|
287
|
+
statuses.each do |host, st|
|
|
288
|
+
build_generations = ConfCtl::Generation::BuildList.new(host)
|
|
289
|
+
online = st.uptime ? true : false
|
|
290
|
+
|
|
291
|
+
inputs_ok = inputs_ok?(st)
|
|
292
|
+
status_ok = online && inputs_ok
|
|
293
|
+
status_ok &&= st.target_toplevel == st.current_toplevel if st.target_toplevel
|
|
294
|
+
|
|
295
|
+
row = {
|
|
296
|
+
'host' => host,
|
|
297
|
+
'online' => online && Rainbow('yes').green,
|
|
298
|
+
'uptime' => st.uptime && format_duration(st.uptime),
|
|
299
|
+
'status' => status_ok ? Rainbow('ok').green : Rainbow('outdated').red,
|
|
300
|
+
'generations' => "#{build_generations.count}:#{st.generations && st.generations.count}"
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
roles.each do |role|
|
|
304
|
+
t_info = st.target_inputs_info && st.target_inputs_info[role]
|
|
305
|
+
d_info = st.inputs_info && st.inputs_info[role]
|
|
306
|
+
state = inputs_info_state(t_info, d_info)
|
|
307
|
+
row[role] = format_inputs_state(state, d_info)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
rows << row
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
OutputFormatter.print(rows, cols, layout: :columns, color: use_color?)
|
|
314
|
+
end
|
|
315
|
+
|
|
219
316
|
def changelog
|
|
317
|
+
return changelog_flake if flake_config?
|
|
318
|
+
|
|
220
319
|
compare_swpins do |io, _host, status, sw_name, spec|
|
|
221
320
|
s = spec.string_changelog_info(
|
|
222
321
|
opts[:downgrade] ? :downgrade : :upgrade,
|
|
@@ -233,6 +332,8 @@ module ConfCtl::Cli
|
|
|
233
332
|
end
|
|
234
333
|
|
|
235
334
|
def diff
|
|
335
|
+
return diff_flake if flake_config?
|
|
336
|
+
|
|
236
337
|
compare_swpins do |io, _host, status, sw_name, spec|
|
|
237
338
|
s = spec.string_diff_info(
|
|
238
339
|
opts[:downgrade] ? :downgrade : :upgrade,
|
|
@@ -246,6 +347,96 @@ module ConfCtl::Cli
|
|
|
246
347
|
end
|
|
247
348
|
end
|
|
248
349
|
|
|
350
|
+
def changelog_flake
|
|
351
|
+
compare_inputs_info do |io, host, target_info, deployed_info|
|
|
352
|
+
roles = inputs_roles(target_info, deployed_info)
|
|
353
|
+
if roles.empty?
|
|
354
|
+
io.puts "#{host}: no inputs info"
|
|
355
|
+
io.puts ''
|
|
356
|
+
next
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
entries = inputs_info_entries(target_info, deployed_info, pattern: args[1])
|
|
360
|
+
next if entries.empty?
|
|
361
|
+
|
|
362
|
+
any_changed = false
|
|
363
|
+
any_unknown = false
|
|
364
|
+
|
|
365
|
+
entries.each do |entry|
|
|
366
|
+
role = entry[:role]
|
|
367
|
+
t_info = entry[:target]
|
|
368
|
+
d_info = entry[:deployed]
|
|
369
|
+
|
|
370
|
+
case entry[:status]
|
|
371
|
+
when :unknown
|
|
372
|
+
any_unknown = true
|
|
373
|
+
io.puts "#{host} @ #{role} in unknown state"
|
|
374
|
+
when :changed
|
|
375
|
+
any_changed = true
|
|
376
|
+
io.puts "#{host} @ #{role}:"
|
|
377
|
+
|
|
378
|
+
begin
|
|
379
|
+
log = inputs_git_log(t_info, d_info)
|
|
380
|
+
io.puts(log || '(changelog unavailable)')
|
|
381
|
+
rescue StandardError => e
|
|
382
|
+
io.puts e.message
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
io.puts ''
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
unless any_changed || any_unknown
|
|
390
|
+
io.puts "#{host}: no changes"
|
|
391
|
+
io.puts ''
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
io.puts '' if any_unknown && !any_changed
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def diff_flake
|
|
399
|
+
compare_inputs_info do |io, host, target_info, deployed_info|
|
|
400
|
+
roles = inputs_roles(target_info, deployed_info)
|
|
401
|
+
if roles.empty?
|
|
402
|
+
io.puts "#{host}: no inputs info"
|
|
403
|
+
io.puts ''
|
|
404
|
+
next
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
entries = inputs_info_entries(target_info, deployed_info, pattern: args[1])
|
|
408
|
+
next if entries.empty?
|
|
409
|
+
|
|
410
|
+
any_output = false
|
|
411
|
+
|
|
412
|
+
entries.each do |entry|
|
|
413
|
+
role = entry[:role]
|
|
414
|
+
t_info = entry[:target]
|
|
415
|
+
d_info = entry[:deployed]
|
|
416
|
+
|
|
417
|
+
case entry[:status]
|
|
418
|
+
when :unknown
|
|
419
|
+
any_output = true
|
|
420
|
+
io.puts "#{host} @ #{role} in unknown state"
|
|
421
|
+
when :changed
|
|
422
|
+
any_output = true
|
|
423
|
+
from = inputs_short_rev(d_info) || 'unknown'
|
|
424
|
+
to = inputs_short_rev(t_info) || 'unknown'
|
|
425
|
+
from, to = to, from if opts[:downgrade]
|
|
426
|
+
url = inputs_url(t_info, d_info)
|
|
427
|
+
label = url ? "#{role} (#{url})" : role
|
|
428
|
+
io.puts "#{host} @ #{label}: #{from} -> #{to}"
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
unless any_output
|
|
433
|
+
io.puts "#{host}: no changes"
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
io.puts ''
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
249
440
|
def test_connection
|
|
250
441
|
machines = select_machines_with_managed(args[0]).runnable
|
|
251
442
|
raise 'No machines to test' if machines.empty?
|
|
@@ -327,6 +518,64 @@ module ConfCtl::Cli
|
|
|
327
518
|
|
|
328
519
|
attr_reader :wait_online
|
|
329
520
|
|
|
521
|
+
def skip_current_deploy_targets(machines, host_generations, action)
|
|
522
|
+
return [machines, host_generations] if opts['copy-only']
|
|
523
|
+
|
|
524
|
+
statuses = deploy_target_statuses(machines, host_generations)
|
|
525
|
+
|
|
526
|
+
filtered_generations = host_generations.reject do |host, generation|
|
|
527
|
+
machine = machines[host]
|
|
528
|
+
status = statuses[host]
|
|
529
|
+
|
|
530
|
+
if already_using_target_generation?(machine, status, generation, action)
|
|
531
|
+
puts Rainbow("Skipping #{host}: already using target generation #{generation.name}").yellow
|
|
532
|
+
true
|
|
533
|
+
else
|
|
534
|
+
false
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
filtered_machines = machines.select do |host, _machine|
|
|
539
|
+
filtered_generations.has_key?(host)
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
[filtered_machines, filtered_generations]
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def deploy_target_statuses(machines, host_generations)
|
|
546
|
+
statuses = {}
|
|
547
|
+
executor = ConfCtl::ParallelExecutor.new([host_generations.length, 5].min)
|
|
548
|
+
|
|
549
|
+
host_generations.each_key do |host|
|
|
550
|
+
statuses[host] = ConfCtl::MachineStatus.new(machines[host])
|
|
551
|
+
|
|
552
|
+
executor.add do
|
|
553
|
+
statuses[host].query(toplevel: true, generations: true, swpins: false)
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
executor.run
|
|
558
|
+
statuses
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def already_using_target_generation?(machine, status, generation, action)
|
|
562
|
+
return false if status.nil? || status.current_toplevel.nil?
|
|
563
|
+
|
|
564
|
+
target_current = status.current_toplevel == generation.toplevel
|
|
565
|
+
|
|
566
|
+
return target_current if machine.carried? || %w[test dry-activate].include?(action)
|
|
567
|
+
return target_current && current_profile_toplevel(status) == generation.toplevel if %w[boot switch].include?(action)
|
|
568
|
+
|
|
569
|
+
false
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def current_profile_toplevel(status)
|
|
573
|
+
return if status.generations.nil?
|
|
574
|
+
|
|
575
|
+
current = status.generations.current
|
|
576
|
+
current && current.toplevel
|
|
577
|
+
end
|
|
578
|
+
|
|
330
579
|
def deploy_in_bulk(machines, host_generations, nix, action)
|
|
331
580
|
skipped_copy = []
|
|
332
581
|
skipped_activation = []
|
|
@@ -426,6 +675,16 @@ module ConfCtl::Cli
|
|
|
426
675
|
|
|
427
676
|
return :skip if opts[:interactive] && !ask_confirmation(always: true)
|
|
428
677
|
|
|
678
|
+
unless ui_enabled?
|
|
679
|
+
ret = nix_copy(nix, machine, build_generation) do |i, n, path|
|
|
680
|
+
puts "[#{host}] [#{i}/#{n}] #{path}"
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
raise "Error while copying system to #{host}" unless ret
|
|
684
|
+
|
|
685
|
+
return true
|
|
686
|
+
end
|
|
687
|
+
|
|
429
688
|
LogView.open(
|
|
430
689
|
header: "#{Rainbow('Copying to').bright} #{host}\n",
|
|
431
690
|
title: Rainbow('Live view').bright
|
|
@@ -453,6 +712,26 @@ module ConfCtl::Cli
|
|
|
453
712
|
def concurrent_copy(machines, host_generations, nix)
|
|
454
713
|
sorted_generations = sort_generations_for_copy(machines, host_generations)
|
|
455
714
|
|
|
715
|
+
unless ui_enabled?
|
|
716
|
+
executor = ConfCtl::ParallelExecutor.new(opts['max-concurrent-copy'])
|
|
717
|
+
|
|
718
|
+
sorted_generations.each do |host, gen|
|
|
719
|
+
executor.add do
|
|
720
|
+
ret = nix_copy(nix, machines[host], gen) do |i, n, path|
|
|
721
|
+
puts "[#{host}] [#{i}/#{n}] #{path}"
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
puts("[#{host}] copy failed") unless ret
|
|
725
|
+
ret ? nil : host
|
|
726
|
+
end
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
failed = executor.run.compact
|
|
730
|
+
raise "Copy failed to: #{failed.join(', ')}" if failed.any?
|
|
731
|
+
|
|
732
|
+
return
|
|
733
|
+
end
|
|
734
|
+
|
|
456
735
|
LogView.open(
|
|
457
736
|
header: "#{Rainbow("Copying to #{sorted_generations.length} machines").bright}\n",
|
|
458
737
|
title: Rainbow('Live view').bright
|
|
@@ -521,13 +800,13 @@ module ConfCtl::Cli
|
|
|
521
800
|
end
|
|
522
801
|
|
|
523
802
|
until carried_groups.empty?
|
|
524
|
-
carried_groups.delete_if do |_,
|
|
525
|
-
m =
|
|
803
|
+
carried_groups.delete_if do |_, group_machines|
|
|
804
|
+
m = group_machines.shift
|
|
526
805
|
next(true) if m.nil?
|
|
527
806
|
|
|
528
807
|
sorted_generations << [m.name, host_generations[m.name]]
|
|
529
808
|
|
|
530
|
-
|
|
809
|
+
group_machines.empty?
|
|
531
810
|
end
|
|
532
811
|
end
|
|
533
812
|
|
|
@@ -655,26 +934,43 @@ module ConfCtl::Cli
|
|
|
655
934
|
m.reboot
|
|
656
935
|
else
|
|
657
936
|
since = Time.now
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
937
|
+
|
|
938
|
+
if ui_enabled?
|
|
939
|
+
spinner = nil
|
|
940
|
+
|
|
941
|
+
secs = m.reboot_and_wait(
|
|
942
|
+
timeout: wait_online == :wait ? nil : wait_online
|
|
943
|
+
) do |state, timeleft|
|
|
944
|
+
if state == :reboot
|
|
945
|
+
spinner = TTY::Spinner.new(
|
|
946
|
+
":spinner Waiting for #{host} (:seconds s)",
|
|
947
|
+
format: :classic
|
|
948
|
+
)
|
|
949
|
+
spinner.auto_spin
|
|
950
|
+
elsif state == :is_up
|
|
951
|
+
spinner.success('up')
|
|
952
|
+
next
|
|
953
|
+
end
|
|
954
|
+
|
|
955
|
+
if wait_online == :wait
|
|
956
|
+
spinner.update(seconds: (Time.now - since).round)
|
|
957
|
+
else
|
|
958
|
+
spinner.update(seconds: timeleft.round)
|
|
959
|
+
end
|
|
672
960
|
end
|
|
961
|
+
else
|
|
962
|
+
puts "Waiting for #{host} to reboot..."
|
|
673
963
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
964
|
+
secs = m.reboot_and_wait(
|
|
965
|
+
timeout: wait_online == :wait ? nil : wait_online
|
|
966
|
+
) do |state, timeleft|
|
|
967
|
+
next unless %i[went_down is_down].include?(state)
|
|
968
|
+
|
|
969
|
+
if wait_online == :wait
|
|
970
|
+
puts " #{host}: waiting #{(Time.now - since).round}s"
|
|
971
|
+
else
|
|
972
|
+
puts " #{host}: #{timeleft.round}s left"
|
|
973
|
+
end
|
|
678
974
|
end
|
|
679
975
|
end
|
|
680
976
|
|
|
@@ -753,42 +1049,68 @@ module ConfCtl::Cli
|
|
|
753
1049
|
|
|
754
1050
|
header << "\n" << Rainbow('Full log: ').bright << ConfCtl::Logger.relative_path << "\n"
|
|
755
1051
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1052
|
+
if ui_enabled?
|
|
1053
|
+
LogView.open(
|
|
1054
|
+
header:,
|
|
1055
|
+
title: Rainbow('Live view').bright,
|
|
1056
|
+
size: :auto,
|
|
1057
|
+
reserved_lines: 10
|
|
1058
|
+
) do |lw|
|
|
1059
|
+
pb = TTY::ProgressBar.new(
|
|
1060
|
+
'Checks [:bar] :current/:total (:percent)',
|
|
1061
|
+
width: 80,
|
|
1062
|
+
total: run_checks.length
|
|
1063
|
+
)
|
|
1064
|
+
|
|
1065
|
+
run_checks.each_with_index do |check, i|
|
|
1066
|
+
tw.add do
|
|
1067
|
+
prefix = "[#{i + 1}/#{run_checks.length}] #{check.machine}> "
|
|
1068
|
+
lw << "#{prefix}#{check.description}"
|
|
1069
|
+
|
|
1070
|
+
check.run do |attempt, _errors|
|
|
1071
|
+
lw << "#{prefix}failed ##{attempt}: #{check.message}"
|
|
1072
|
+
ConfCtl::Logger.keep
|
|
1073
|
+
end
|
|
1074
|
+
|
|
1075
|
+
lw << if check.successful?
|
|
1076
|
+
"#{prefix}succeeded"
|
|
1077
|
+
else
|
|
1078
|
+
"#{prefix}error: #{check.message}"
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
lw.sync_console do
|
|
1082
|
+
pb.advance
|
|
1083
|
+
end
|
|
1084
|
+
end
|
|
1085
|
+
end
|
|
1086
|
+
|
|
1087
|
+
tw.run
|
|
1088
|
+
lw.flush
|
|
1089
|
+
end
|
|
1090
|
+
else
|
|
1091
|
+
puts header
|
|
767
1092
|
|
|
768
1093
|
run_checks.each_with_index do |check, i|
|
|
769
1094
|
tw.add do
|
|
770
1095
|
prefix = "[#{i + 1}/#{run_checks.length}] #{check.machine}> "
|
|
771
|
-
|
|
1096
|
+
puts "#{prefix}#{check.description}"
|
|
772
1097
|
|
|
773
1098
|
check.run do |attempt, _errors|
|
|
774
|
-
|
|
1099
|
+
puts "#{prefix}failed ##{attempt}: #{check.message}"
|
|
775
1100
|
ConfCtl::Logger.keep
|
|
776
1101
|
end
|
|
777
1102
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
pb.advance
|
|
786
|
-
end
|
|
1103
|
+
puts(
|
|
1104
|
+
if check.successful?
|
|
1105
|
+
"#{prefix}succeeded"
|
|
1106
|
+
else
|
|
1107
|
+
"#{prefix}error: #{check.message}"
|
|
1108
|
+
end
|
|
1109
|
+
)
|
|
787
1110
|
end
|
|
788
1111
|
end
|
|
789
1112
|
|
|
790
1113
|
tw.run
|
|
791
|
-
lw.flush
|
|
792
1114
|
end
|
|
793
1115
|
|
|
794
1116
|
successful = []
|
|
@@ -847,7 +1169,7 @@ module ConfCtl::Cli
|
|
|
847
1169
|
puts 'Run command over SSH on the following machines:'
|
|
848
1170
|
list_machines(machines)
|
|
849
1171
|
puts
|
|
850
|
-
puts "Command: #{
|
|
1172
|
+
puts "Command: #{Shellwords.join(cmd)}"
|
|
851
1173
|
end
|
|
852
1174
|
|
|
853
1175
|
if opts[:parallel]
|
|
@@ -889,31 +1211,44 @@ module ConfCtl::Cli
|
|
|
889
1211
|
results = {}
|
|
890
1212
|
tw = ConfCtl::ParallelExecutor.new(machines.length)
|
|
891
1213
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1214
|
+
if ui_enabled?
|
|
1215
|
+
LogView.open_with_logger(
|
|
1216
|
+
header: Rainbow('Executing').bright + " #{Shellwords.join(cmd)}\n",
|
|
1217
|
+
title: Rainbow('Live view').bright,
|
|
1218
|
+
size: :auto,
|
|
1219
|
+
reserved_lines: 10
|
|
1220
|
+
) do |lw|
|
|
1221
|
+
pb = TTY::ProgressBar.new(
|
|
1222
|
+
'Command [:bar] :current/:total (:percent)',
|
|
1223
|
+
width: 80,
|
|
1224
|
+
total: machines.length
|
|
1225
|
+
)
|
|
1226
|
+
|
|
1227
|
+
machines.each do |host, machine|
|
|
1228
|
+
tw.add do
|
|
1229
|
+
mc = ConfCtl::MachineControl.new(machine)
|
|
1230
|
+
|
|
1231
|
+
result = run_ssh_command_on_machine(mc, cmd)
|
|
1232
|
+
results[host] = result
|
|
1233
|
+
|
|
1234
|
+
lw.sync_console { pb.advance }
|
|
1235
|
+
end
|
|
1236
|
+
end
|
|
1237
|
+
|
|
1238
|
+
tw.run
|
|
1239
|
+
lw.flush
|
|
1240
|
+
end
|
|
1241
|
+
else
|
|
1242
|
+
puts "Executing #{Shellwords.join(cmd)} on #{machines.length} machines"
|
|
903
1243
|
|
|
904
1244
|
machines.each do |host, machine|
|
|
905
1245
|
tw.add do
|
|
906
1246
|
mc = ConfCtl::MachineControl.new(machine)
|
|
907
|
-
|
|
908
|
-
result = run_ssh_command_on_machine(mc, cmd)
|
|
909
|
-
results[host] = result
|
|
910
|
-
|
|
911
|
-
lw.sync_console { pb.advance }
|
|
1247
|
+
results[host] = run_ssh_command_on_machine(mc, cmd)
|
|
912
1248
|
end
|
|
913
1249
|
end
|
|
914
1250
|
|
|
915
1251
|
tw.run
|
|
916
|
-
lw.flush
|
|
917
1252
|
end
|
|
918
1253
|
|
|
919
1254
|
if aggregate
|
|
@@ -1013,11 +1348,28 @@ module ConfCtl::Cli
|
|
|
1013
1348
|
|
|
1014
1349
|
def list_generations(host_generations, missing_hosts:)
|
|
1015
1350
|
swpin_names = []
|
|
1351
|
+
input_roles = []
|
|
1016
1352
|
|
|
1017
1353
|
host_generations.each_value do |gen|
|
|
1018
1354
|
gen.swpin_names.each do |name|
|
|
1019
1355
|
swpin_names << name unless swpin_names.include?(name)
|
|
1020
1356
|
end
|
|
1357
|
+
|
|
1358
|
+
next unless flake_generation?(gen)
|
|
1359
|
+
|
|
1360
|
+
inputs_info = normalized_inputs_info(gen)
|
|
1361
|
+
next if inputs_info.nil? || inputs_info.empty?
|
|
1362
|
+
|
|
1363
|
+
inputs_info.each_key do |role|
|
|
1364
|
+
input_roles << role unless input_roles.include?(role)
|
|
1365
|
+
end
|
|
1366
|
+
end
|
|
1367
|
+
|
|
1368
|
+
input_column_names = {}
|
|
1369
|
+
input_columns = input_roles.map do |role|
|
|
1370
|
+
name = swpin_names.include?(role) ? "input:#{role}" : role
|
|
1371
|
+
input_column_names[role] = name
|
|
1372
|
+
{ name: name, label: role.to_s.upcase }
|
|
1021
1373
|
end
|
|
1022
1374
|
|
|
1023
1375
|
rows = host_generations.map do |host, gen|
|
|
@@ -1031,6 +1383,13 @@ module ConfCtl::Cli
|
|
|
1031
1383
|
row[name] = spec.version
|
|
1032
1384
|
end
|
|
1033
1385
|
|
|
1386
|
+
if flake_generation?(gen)
|
|
1387
|
+
inputs_info = normalized_inputs_info(gen) || {}
|
|
1388
|
+
input_roles.each do |role|
|
|
1389
|
+
row[input_column_names[role]] = inputs_short_rev(inputs_info[role])
|
|
1390
|
+
end
|
|
1391
|
+
end
|
|
1392
|
+
|
|
1034
1393
|
row
|
|
1035
1394
|
end
|
|
1036
1395
|
|
|
@@ -1040,7 +1399,7 @@ module ConfCtl::Cli
|
|
|
1040
1399
|
|
|
1041
1400
|
OutputFormatter.print(
|
|
1042
1401
|
rows,
|
|
1043
|
-
%w[name generation kernel] + swpin_names,
|
|
1402
|
+
%w[name generation kernel] + swpin_names + input_columns,
|
|
1044
1403
|
layout: :columns,
|
|
1045
1404
|
sort: %w[name generation]
|
|
1046
1405
|
)
|
|
@@ -1058,7 +1417,11 @@ module ConfCtl::Cli
|
|
|
1058
1417
|
|
|
1059
1418
|
raise 'one or more swpins need to be updated' unless host_swpin_specs
|
|
1060
1419
|
|
|
1061
|
-
|
|
1420
|
+
if ConfCtl::ConfigType.flake?(ConfCtl::ConfDir.path)
|
|
1421
|
+
puts Rainbow("Evaluating inputs for #{machines.length} machines...").bright
|
|
1422
|
+
else
|
|
1423
|
+
puts Rainbow("Evaluating swpins for #{machines.length} machines...").bright
|
|
1424
|
+
end
|
|
1062
1425
|
|
|
1063
1426
|
hosts_swpin_paths = nix.eval_host_swpins(machines.map { |host, _| host })
|
|
1064
1427
|
|
|
@@ -1066,7 +1429,7 @@ module ConfCtl::Cli
|
|
|
1066
1429
|
hosts_swpin_paths[host].update(m.nix_paths)
|
|
1067
1430
|
end
|
|
1068
1431
|
|
|
1069
|
-
grps = swpin_build_groups(hosts_swpin_paths)
|
|
1432
|
+
grps = swpin_build_groups(hosts_swpin_paths, nix)
|
|
1070
1433
|
puts
|
|
1071
1434
|
puts "Machines will be built in #{grps.length} groups"
|
|
1072
1435
|
puts
|
|
@@ -1112,7 +1475,7 @@ module ConfCtl::Cli
|
|
|
1112
1475
|
def do_build_group(group_index, group_count, hosts, swpin_paths, host_swpin_specs, nix, time)
|
|
1113
1476
|
puts Rainbow('Building machines').bright
|
|
1114
1477
|
hosts.each { |h| puts " #{h}" }
|
|
1115
|
-
puts 'with swpins'
|
|
1478
|
+
puts ConfCtl::ConfigType.flake?(ConfCtl::ConfDir.path) ? 'with inputs' : 'with swpins'
|
|
1116
1479
|
swpin_paths.each { |k, v| puts " #{k}=#{v}" }
|
|
1117
1480
|
|
|
1118
1481
|
header = '' \
|
|
@@ -1126,55 +1489,72 @@ module ConfCtl::Cli
|
|
|
1126
1489
|
<< " #{ConfCtl::Logger.relative_path}" \
|
|
1127
1490
|
<< "\n\n"
|
|
1128
1491
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1492
|
+
if ui_enabled?
|
|
1493
|
+
LogView.open_with_logger(
|
|
1494
|
+
header:,
|
|
1495
|
+
title: Rainbow('Live view').bright,
|
|
1496
|
+
size: :auto,
|
|
1497
|
+
reserved_lines: 10
|
|
1498
|
+
) do |lw|
|
|
1499
|
+
multibar = TTY::ProgressBar::Multi.new(
|
|
1500
|
+
'nix-build [:bar] :current/:total (:percent)',
|
|
1501
|
+
width: 80
|
|
1502
|
+
)
|
|
1139
1503
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1504
|
+
build_pb = multibar.register(
|
|
1505
|
+
'Building [:bar] :current/:total (:percent)'
|
|
1506
|
+
)
|
|
1143
1507
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1508
|
+
fetch_pb = multibar.register(
|
|
1509
|
+
'Fetching [:bar] :current/:total (:percent)'
|
|
1510
|
+
)
|
|
1147
1511
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1512
|
+
built_generations = nix.build_attributes(
|
|
1513
|
+
hosts:,
|
|
1514
|
+
swpin_paths:,
|
|
1515
|
+
time:,
|
|
1516
|
+
host_swpin_specs:
|
|
1517
|
+
) do |type, _progress, total, _path|
|
|
1518
|
+
if type == :build
|
|
1519
|
+
lw.sync_console do
|
|
1520
|
+
build_pb.update(total:) if total > 0 && build_pb.total.nil?
|
|
1521
|
+
build_pb.advance
|
|
1522
|
+
end
|
|
1523
|
+
elsif type == :fetch
|
|
1524
|
+
lw.sync_console do
|
|
1525
|
+
fetch_pb.update(total:) if total > 0 && fetch_pb.total.nil?
|
|
1526
|
+
fetch_pb.advance
|
|
1527
|
+
end
|
|
1163
1528
|
end
|
|
1164
|
-
end
|
|
1165
1529
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1530
|
+
if build_pb.total && fetch_pb.total && multibar.top_bar.total.nil?
|
|
1531
|
+
lw.sync_console do
|
|
1532
|
+
multibar.top_bar.update(total: multibar.total)
|
|
1533
|
+
end
|
|
1169
1534
|
end
|
|
1170
1535
|
end
|
|
1171
|
-
end
|
|
1172
1536
|
|
|
1173
|
-
|
|
1537
|
+
built_generations
|
|
1538
|
+
end
|
|
1539
|
+
else
|
|
1540
|
+
puts header
|
|
1541
|
+
nix.build_attributes(
|
|
1542
|
+
hosts:,
|
|
1543
|
+
swpin_paths:,
|
|
1544
|
+
time:,
|
|
1545
|
+
host_swpin_specs:
|
|
1546
|
+
) do |type, progress, total, path|
|
|
1547
|
+
puts "[#{type}] #{progress}/#{total} #{path}"
|
|
1548
|
+
end
|
|
1174
1549
|
end
|
|
1175
1550
|
end
|
|
1176
1551
|
|
|
1177
1552
|
def autoupdate_swpins(machines)
|
|
1553
|
+
if ConfCtl::ConfigType.flake?(ConfCtl::ConfDir.path)
|
|
1554
|
+
puts Rainbow('Skipping swpins auto updates for flake config').bright
|
|
1555
|
+
return
|
|
1556
|
+
end
|
|
1557
|
+
|
|
1178
1558
|
puts Rainbow('Running swpins auto updates...').bright
|
|
1179
1559
|
channels_update = []
|
|
1180
1560
|
any_updated = false
|
|
@@ -1250,6 +1630,13 @@ module ConfCtl::Cli
|
|
|
1250
1630
|
end
|
|
1251
1631
|
|
|
1252
1632
|
def check_swpins(machines)
|
|
1633
|
+
if ConfCtl::ConfigType.flake?(ConfCtl::ConfDir.path)
|
|
1634
|
+
puts Rainbow('Skipping swpins checks for flake config').bright
|
|
1635
|
+
ret = {}
|
|
1636
|
+
machines.each_key { |host| ret[host] = {} }
|
|
1637
|
+
return ret
|
|
1638
|
+
end
|
|
1639
|
+
|
|
1253
1640
|
ret = {}
|
|
1254
1641
|
valid = true
|
|
1255
1642
|
|
|
@@ -1278,7 +1665,15 @@ module ConfCtl::Cli
|
|
|
1278
1665
|
valid ? ret : false
|
|
1279
1666
|
end
|
|
1280
1667
|
|
|
1281
|
-
def swpin_build_groups(hosts_swpins)
|
|
1668
|
+
def swpin_build_groups(hosts_swpins, nix = nil)
|
|
1669
|
+
if ConfCtl::ConfigType.flake?(ConfCtl::ConfDir.path)
|
|
1670
|
+
legacy_nix_path = false
|
|
1671
|
+
if nix
|
|
1672
|
+
legacy_nix_path = nix.confctl_settings.dig('nix', 'legacyNixPath') == true
|
|
1673
|
+
end
|
|
1674
|
+
return [[hosts_swpins.keys, {}]] unless legacy_nix_path
|
|
1675
|
+
end
|
|
1676
|
+
|
|
1282
1677
|
ret = []
|
|
1283
1678
|
all_swpins = hosts_swpins.values.uniq
|
|
1284
1679
|
|
|
@@ -1295,6 +1690,50 @@ module ConfCtl::Cli
|
|
|
1295
1690
|
ret
|
|
1296
1691
|
end
|
|
1297
1692
|
|
|
1693
|
+
def compare_inputs_info
|
|
1694
|
+
machines = select_machines(args[0]).managed
|
|
1695
|
+
|
|
1696
|
+
ask_confirmation! do
|
|
1697
|
+
puts 'Compare inputs on the following machines:'
|
|
1698
|
+
list_machines(machines)
|
|
1699
|
+
puts
|
|
1700
|
+
puts "Generation: #{opts[:generation] || 'current configuration'}"
|
|
1701
|
+
end
|
|
1702
|
+
|
|
1703
|
+
statuses = machines.transform_values do |machine|
|
|
1704
|
+
ConfCtl::MachineStatus.new(machine)
|
|
1705
|
+
end
|
|
1706
|
+
|
|
1707
|
+
if opts[:generation]
|
|
1708
|
+
host_generations = find_generations(machines, opts[:generation])
|
|
1709
|
+
|
|
1710
|
+
# Ignore statuses when no generation was found
|
|
1711
|
+
statuses.delete_if do |host, _st|
|
|
1712
|
+
!host_generations.has_key?(host)
|
|
1713
|
+
end
|
|
1714
|
+
|
|
1715
|
+
assign_target_inputs_info(statuses, host_generations:)
|
|
1716
|
+
else
|
|
1717
|
+
assign_target_inputs_info(statuses)
|
|
1718
|
+
end
|
|
1719
|
+
|
|
1720
|
+
TTY::Pager.page(enabled: use_pager?) do |io|
|
|
1721
|
+
statuses.each do |host, st|
|
|
1722
|
+
st.query(toplevel: false, generations: false, swpins: false, inputs: true)
|
|
1723
|
+
|
|
1724
|
+
if st.uptime.nil?
|
|
1725
|
+
io.puts "#{host} is offline"
|
|
1726
|
+
next
|
|
1727
|
+
end
|
|
1728
|
+
|
|
1729
|
+
target_info = st.target_inputs_info || {}
|
|
1730
|
+
deployed_info = st.inputs_info || {}
|
|
1731
|
+
|
|
1732
|
+
yield(io, host, target_info, deployed_info)
|
|
1733
|
+
end
|
|
1734
|
+
end
|
|
1735
|
+
end
|
|
1736
|
+
|
|
1298
1737
|
def compare_swpins
|
|
1299
1738
|
machines = select_machines(args[0]).managed
|
|
1300
1739
|
|
|
@@ -1355,6 +1794,132 @@ module ConfCtl::Cli
|
|
|
1355
1794
|
end
|
|
1356
1795
|
end
|
|
1357
1796
|
|
|
1797
|
+
def assign_target_inputs_info(statuses, host_generations: nil)
|
|
1798
|
+
if host_generations
|
|
1799
|
+
host_generations.each do |host, gen|
|
|
1800
|
+
next unless statuses[host]
|
|
1801
|
+
|
|
1802
|
+
statuses[host].target_inputs_info = ConfCtl::InputsInfo.normalize(gen.inputs_info)
|
|
1803
|
+
end
|
|
1804
|
+
else
|
|
1805
|
+
nix = ConfCtl::Nix.new(show_trace: opts['show-trace'])
|
|
1806
|
+
|
|
1807
|
+
statuses.each do |host, st|
|
|
1808
|
+
st.target_inputs_info = ConfCtl::InputsInfo.normalize(nix.eval_inputs_info(host))
|
|
1809
|
+
rescue StandardError
|
|
1810
|
+
st.target_inputs_info = nil
|
|
1811
|
+
end
|
|
1812
|
+
end
|
|
1813
|
+
end
|
|
1814
|
+
|
|
1815
|
+
def inputs_roles_for_status(statuses)
|
|
1816
|
+
roles = []
|
|
1817
|
+
|
|
1818
|
+
statuses.each_value do |st|
|
|
1819
|
+
roles.concat((st.target_inputs_info || {}).keys)
|
|
1820
|
+
roles.concat((st.inputs_info || {}).keys)
|
|
1821
|
+
end
|
|
1822
|
+
|
|
1823
|
+
roles.uniq.sort
|
|
1824
|
+
end
|
|
1825
|
+
|
|
1826
|
+
def inputs_ok?(status)
|
|
1827
|
+
target_info = status.target_inputs_info
|
|
1828
|
+
return false if target_info.nil? || target_info.empty?
|
|
1829
|
+
|
|
1830
|
+
target_info.all? do |role, info|
|
|
1831
|
+
inputs_info_state(info, status.inputs_info && status.inputs_info[role]) == :same
|
|
1832
|
+
end
|
|
1833
|
+
end
|
|
1834
|
+
|
|
1835
|
+
def inputs_info_entries(target_info, deployed_info, pattern: nil)
|
|
1836
|
+
inputs_roles(target_info, deployed_info).filter_map do |role|
|
|
1837
|
+
next if pattern && !ConfCtl::Pattern.match?(pattern, role)
|
|
1838
|
+
|
|
1839
|
+
t_info = target_info[role]
|
|
1840
|
+
d_info = deployed_info[role]
|
|
1841
|
+
{ role:, target: t_info, deployed: d_info, status: inputs_info_state(t_info, d_info) }
|
|
1842
|
+
end
|
|
1843
|
+
end
|
|
1844
|
+
|
|
1845
|
+
def inputs_roles(target_info, deployed_info)
|
|
1846
|
+
roles = []
|
|
1847
|
+
roles.concat(target_info.keys) if target_info
|
|
1848
|
+
roles.concat(deployed_info.keys) if deployed_info
|
|
1849
|
+
roles.uniq.sort
|
|
1850
|
+
end
|
|
1851
|
+
|
|
1852
|
+
def inputs_info_state(target_info, deployed_info)
|
|
1853
|
+
return :unknown if target_info.nil? || deployed_info.nil?
|
|
1854
|
+
|
|
1855
|
+
target_rev = inputs_rev(target_info)
|
|
1856
|
+
deployed_rev = inputs_rev(deployed_info)
|
|
1857
|
+
return :unknown if target_rev.nil? || deployed_rev.nil?
|
|
1858
|
+
|
|
1859
|
+
target_rev == deployed_rev ? :same : :changed
|
|
1860
|
+
end
|
|
1861
|
+
|
|
1862
|
+
def inputs_rev(info)
|
|
1863
|
+
return nil unless info.is_a?(Hash)
|
|
1864
|
+
|
|
1865
|
+
info['rev']
|
|
1866
|
+
end
|
|
1867
|
+
|
|
1868
|
+
def inputs_short_rev(info)
|
|
1869
|
+
return nil unless info.is_a?(Hash)
|
|
1870
|
+
|
|
1871
|
+
info['shortRev'] || (info['rev'] && info['rev'][0, 8])
|
|
1872
|
+
end
|
|
1873
|
+
|
|
1874
|
+
def normalized_inputs_info(gen)
|
|
1875
|
+
return nil unless gen.respond_to?(:inputs_info)
|
|
1876
|
+
|
|
1877
|
+
ConfCtl::InputsInfo.normalize(gen.inputs_info)
|
|
1878
|
+
end
|
|
1879
|
+
|
|
1880
|
+
def flake_generation?(gen)
|
|
1881
|
+
gen.respond_to?(:flakes_mode?) && gen.flakes_mode?
|
|
1882
|
+
end
|
|
1883
|
+
|
|
1884
|
+
def inputs_url(target_info, deployed_info)
|
|
1885
|
+
(target_info && target_info['url']) || (deployed_info && deployed_info['url'])
|
|
1886
|
+
end
|
|
1887
|
+
|
|
1888
|
+
def inputs_git_log(target_info, deployed_info)
|
|
1889
|
+
url = inputs_url(target_info, deployed_info)
|
|
1890
|
+
old_rev = inputs_rev(deployed_info)
|
|
1891
|
+
new_rev = inputs_rev(target_info)
|
|
1892
|
+
return nil if url.nil? || old_rev.nil? || new_rev.nil?
|
|
1893
|
+
|
|
1894
|
+
old_rev, new_rev = new_rev, old_rev if opts[:downgrade]
|
|
1895
|
+
|
|
1896
|
+
log_opts = []
|
|
1897
|
+
log_opts << '--oneline' unless opts[:verbose]
|
|
1898
|
+
log_opts << '-p' if opts[:patch]
|
|
1899
|
+
log_opts << '--max-count=50' unless opts[:verbose] || opts[:patch]
|
|
1900
|
+
|
|
1901
|
+
mirror = ConfCtl::GitRepoMirror.new(url, quiet: true)
|
|
1902
|
+
mirror.setup
|
|
1903
|
+
mirror.log(old_rev, new_rev, opts: log_opts)
|
|
1904
|
+
end
|
|
1905
|
+
|
|
1906
|
+
def format_inputs_state(state, deployed_info)
|
|
1907
|
+
rev = inputs_short_rev(deployed_info) || 'unknown'
|
|
1908
|
+
|
|
1909
|
+
case state
|
|
1910
|
+
when :same
|
|
1911
|
+
Rainbow(rev).green
|
|
1912
|
+
when :changed
|
|
1913
|
+
Rainbow(rev).red
|
|
1914
|
+
else
|
|
1915
|
+
Rainbow(rev).yellow
|
|
1916
|
+
end
|
|
1917
|
+
end
|
|
1918
|
+
|
|
1919
|
+
def flake_config?
|
|
1920
|
+
ConfCtl::ConfigType.flake?(ConfCtl::ConfDir.path)
|
|
1921
|
+
end
|
|
1922
|
+
|
|
1358
1923
|
def parse_wait_online
|
|
1359
1924
|
@wait_online =
|
|
1360
1925
|
case opts['wait-online']
|
|
@@ -1371,9 +1936,13 @@ module ConfCtl::Cli
|
|
|
1371
1936
|
|
|
1372
1937
|
def format_command(reserved_cols = 0)
|
|
1373
1938
|
cmd = "#{$0.split('/').last} #{ARGV.join(' ')}"
|
|
1374
|
-
|
|
1939
|
+
cols = IO.console&.winsize&.last
|
|
1940
|
+
cols = cols.to_i
|
|
1941
|
+
cols = 80 if cols <= 0
|
|
1375
1942
|
max_length = cols - reserved_cols
|
|
1376
1943
|
|
|
1944
|
+
return cmd if max_length <= 4
|
|
1945
|
+
|
|
1377
1946
|
if cmd.length > max_length
|
|
1378
1947
|
"#{cmd[0..(max_length - 4)]}..."
|
|
1379
1948
|
else
|
|
@@ -1404,6 +1973,10 @@ module ConfCtl::Cli
|
|
|
1404
1973
|
nix.copy(machine, paths, &)
|
|
1405
1974
|
end
|
|
1406
1975
|
|
|
1976
|
+
def ui_enabled?
|
|
1977
|
+
ConfCtl::Ui.tty?
|
|
1978
|
+
end
|
|
1979
|
+
|
|
1407
1980
|
def enable_auto_rollback?(machine, generation)
|
|
1408
1981
|
!opts['disable-auto-rollback'] \
|
|
1409
1982
|
&& (opts['enable-auto-rollback'] || machine.auto_rollback?) \
|