confctl 2.0.0 → 2.2.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/CHANGELOG.md +19 -1
- data/Gemfile +6 -0
- data/README.md +2 -1
- data/docs/carrier.md +12 -0
- data/lib/confctl/cli/app.rb +19 -0
- data/lib/confctl/cli/cluster.rb +183 -47
- data/lib/confctl/cli/generation.rb +44 -15
- data/lib/confctl/cli/swpins/channel.rb +6 -4
- data/lib/confctl/cli/swpins/cluster.rb +6 -4
- data/lib/confctl/cli/swpins/core.rb +6 -4
- data/lib/confctl/generation/build.rb +42 -1
- data/lib/confctl/generation/build_list.rb +10 -0
- data/lib/confctl/generation/host.rb +9 -5
- data/lib/confctl/generation/host_list.rb +20 -5
- data/lib/confctl/generation/unified.rb +5 -0
- data/lib/confctl/generation/unified_list.rb +10 -0
- data/lib/confctl/machine.rb +4 -0
- data/lib/confctl/machine_control.rb +3 -2
- data/lib/confctl/machine_list.rb +4 -0
- data/lib/confctl/machine_status.rb +1 -1
- data/lib/confctl/nix.rb +63 -18
- data/lib/confctl/nix_copy.rb +5 -5
- data/lib/confctl/null_logger.rb +7 -0
- data/lib/confctl/swpins/change_set.rb +11 -4
- data/lib/confctl/swpins/specs/git.rb +23 -16
- data/lib/confctl/swpins/specs/git_rev.rb +1 -1
- data/lib/confctl/system_command.rb +3 -2
- data/lib/confctl/version.rb +1 -1
- data/libexec/auto-rollback.rb +106 -0
- data/man/man8/confctl.8 +109 -72
- data/man/man8/confctl.8.md +91 -54
- data/nix/evaluator.nix +26 -1
- data/nix/modules/cluster/default.nix +20 -0
- data/nix/modules/confctl/carrier/base.nix +8 -6
- data/nix/modules/confctl/carrier/netboot/build-netboot-server.rb +209 -50
- data/nix/modules/confctl/carrier/netboot/nixos.nix +5 -3
- data/nix/modules/confctl/kexec-netboot/default.nix +38 -0
- data/nix/modules/confctl/kexec-netboot/kexec-netboot.8.adoc +62 -0
- data/nix/modules/confctl/kexec-netboot/kexec-netboot.rb +455 -0
- data/nix/modules/confctl/overlays.nix +15 -0
- data/nix/modules/module-list.nix +1 -0
- data/nix/modules/system-list.nix +3 -1
- data/shell.nix +9 -2
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd99990731daa837f1243feb4a3b098320180badbbb9cc3846c105e7b747b794
|
4
|
+
data.tar.gz: 2ab400c25ea0bfc35f53713826b0e69aeff4667314048aaf6f8f56c942dda4e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b2f85762c2c9c8173ffc38fd1fd6c37e2866ae8a6d5a4d1cbfc6d4cbbb8e8037dc9e9d40d0d8f9f12f7a0eae7140c4c52f435ea6e952327803024a6cd08720b
|
7
|
+
data.tar.gz: a3f87cfe12b26ef6c0a520030256a798418b85cee9dbedc2cd63e35b7625aa7e763c4f3d07f9e194826c6d3cffd5f12bc1a55d04201c6963067412a5010c4bad
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
# Fri Jun 06 2025 -- version 2.2.0
|
2
|
+
- Handle `pkgs.substituteAll` / `pkgs.replaceVarsWith` compatibility on NixOS unstable
|
3
|
+
and 25.05
|
4
|
+
- Change swpin files only when revisions are updated, skip commit when no changes were made
|
5
|
+
- Add option `--[no-]editor` to `confctl swpins core/cluster/channel set/update` commands
|
6
|
+
|
7
|
+
# Sun May 11 2025 -- version 2.1.0
|
8
|
+
- Support for referring to generations by their offset
|
9
|
+
- Resolved generations are printed on build/deploy/etc.
|
10
|
+
- Automatically rollback faulty configurations
|
11
|
+
- Interleave copying of carried machine generations
|
12
|
+
- Add `confctl.programs.kexec-netboot`
|
13
|
+
- Let the user retry dry activation in interactive mode
|
14
|
+
- Fix listing, deletion and garbage collection of carried machines' generations
|
15
|
+
- Read and display kernel version for each generation
|
16
|
+
- Option to disable the garbage collection in `confctl generation rotate`
|
17
|
+
- Shorten titles and entries in netboot menus
|
18
|
+
|
1
19
|
# Sun Nov 17 2024 -- version 2.0.0
|
2
20
|
- Distinguish machine `config` and `metaConfig` (breaking change)
|
3
21
|
- Support for machine carriers and netboot servers
|
@@ -13,7 +31,7 @@ or it could mean machine metadata from `module.nix`. Machine metadata
|
|
13
31
|
is now accessible as `metaConfig`.
|
14
32
|
|
15
33
|
- `confLib.findConfig` has been renamed to `confLib.findMetaConfig`
|
16
|
-
- `confLib.
|
34
|
+
- `confLib.getClusterMachines` returns a list of machines with `metaConfig` attribute
|
17
35
|
|
18
36
|
# Sat Feb 17 2024 -- version 1.0.0
|
19
37
|
- Initial release
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -13,7 +13,8 @@ machines.
|
|
13
13
|
configurations)
|
14
14
|
* Query machine state, view changelogs and diffs
|
15
15
|
* Run health checks
|
16
|
-
*
|
16
|
+
* Automatically roll back faulty configurations
|
17
|
+
* Support for creating netboot servers with option to kexec, see [docs/carrier.md](docs/carrier.md)
|
17
18
|
|
18
19
|
## Requirements
|
19
20
|
|
data/docs/carrier.md
CHANGED
@@ -136,3 +136,15 @@ images are deployed to it:
|
|
136
136
|
'';
|
137
137
|
}
|
138
138
|
```
|
139
|
+
|
140
|
+
## kexec from the netboot server
|
141
|
+
Set `confctl.programs.kexec-netboot.enable = true;` within your netbooted machine
|
142
|
+
configurations. This will add program `kexec-netboot` to your system path.
|
143
|
+
|
144
|
+
This program can be used to load kernel and initrd from the netboot server for kexec.
|
145
|
+
It automatically discovers the netboot server it was booted from, by default it uses
|
146
|
+
the latest generation found on the netboot server at the moment of execution. Use
|
147
|
+
`kexec-netboot --interactive` to select which machine/generation/variant should be
|
148
|
+
loaded. See `kexec-netboot --help` for all available options.
|
149
|
+
|
150
|
+
The loaded kernel can be run either by `kexec-netboot -e` or `kexec -e` directly.
|
data/lib/confctl/cli/app.rb
CHANGED
@@ -11,6 +11,13 @@ module ConfCtl::Cli
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.run
|
14
|
+
# Workaround for nix-build error:
|
15
|
+
# error: creating directory '/tmp/nix-shell-5100-0/nix-build-1986594-0': No such file or directory
|
16
|
+
if ENV['IN_NIX_SHELL']
|
17
|
+
ENV['TMP'] = '/tmp'
|
18
|
+
ENV['TMPDIR'] = '/tmp'
|
19
|
+
end
|
20
|
+
|
14
21
|
cli = get
|
15
22
|
exit(cli.run(ARGV))
|
16
23
|
end
|
@@ -213,6 +220,12 @@ module ConfCtl::Cli
|
|
213
220
|
c.desc 'Do not activate copied closures'
|
214
221
|
c.switch 'copy-only', negatable: false
|
215
222
|
|
223
|
+
c.desc 'Enable auto-rollback'
|
224
|
+
c.switch 'enable-auto-rollback', default_value: false, negatable: false
|
225
|
+
|
226
|
+
c.desc 'Disable auto-rollback'
|
227
|
+
c.switch 'disable-auto-rollback', default_value: false, negatable: false
|
228
|
+
|
216
229
|
c.desc 'Reboot target systems after deployment'
|
217
230
|
c.switch :reboot
|
218
231
|
|
@@ -447,6 +460,9 @@ module ConfCtl::Cli
|
|
447
460
|
c.desc 'List remote machine generations'
|
448
461
|
c.switch %i[r remote]
|
449
462
|
|
463
|
+
c.desc 'Do not run the garbage collector if enabled in configuration'
|
464
|
+
c.switch %i[gc collect-garbage], default_value: true
|
465
|
+
|
450
466
|
c.desc 'Max number of concurrent nix-collect-garbage processes'
|
451
467
|
c.flag 'max-concurrent-gc', arg_name: 'n', type: Integer,
|
452
468
|
default_value: 5
|
@@ -521,6 +537,9 @@ module ConfCtl::Cli
|
|
521
537
|
c.desc 'Include changelog in the commit message'
|
522
538
|
c.switch :changelog, default_value: true
|
523
539
|
|
540
|
+
c.desc 'Open $EDITOR with commit message'
|
541
|
+
c.switch :editor, default_value: true
|
542
|
+
|
524
543
|
c.desc 'Generate changelog for downgrade'
|
525
544
|
c.switch %i[d downgrade], default_value: false
|
526
545
|
|
data/lib/confctl/cli/cluster.rb
CHANGED
@@ -333,7 +333,7 @@ module ConfCtl::Cli
|
|
333
333
|
|
334
334
|
if opts[:interactive]
|
335
335
|
host_generations.each do |host, gen|
|
336
|
-
if copy_to_host(nix, host, machines[host], gen
|
336
|
+
if copy_to_host(nix, host, machines[host], gen) == :skip
|
337
337
|
puts Rainbow("Skipping #{host}").yellow
|
338
338
|
skipped_copy << host
|
339
339
|
end
|
@@ -351,7 +351,7 @@ module ConfCtl::Cli
|
|
351
351
|
next
|
352
352
|
end
|
353
353
|
|
354
|
-
if deploy_to_host(nix, host, machines[host], gen
|
354
|
+
if deploy_to_host(nix, host, machines[host], gen, action) == :skip
|
355
355
|
puts Rainbow("Skipping #{host}").yellow
|
356
356
|
skipped_activation << host
|
357
357
|
next
|
@@ -396,14 +396,14 @@ module ConfCtl::Cli
|
|
396
396
|
host_generations.each do |host, gen|
|
397
397
|
machine = machines[host]
|
398
398
|
|
399
|
-
if copy_to_host(nix, host, machine, gen
|
399
|
+
if copy_to_host(nix, host, machine, gen) == :skip
|
400
400
|
puts Rainbow("Skipping #{host}").yellow
|
401
401
|
next
|
402
402
|
end
|
403
403
|
|
404
404
|
next if opts['copy-only']
|
405
405
|
|
406
|
-
if deploy_to_host(nix, host, machine, gen
|
406
|
+
if deploy_to_host(nix, host, machine, gen, action) == :skip
|
407
407
|
puts Rainbow("Skipping #{host}").yellow
|
408
408
|
next
|
409
409
|
end
|
@@ -421,7 +421,7 @@ module ConfCtl::Cli
|
|
421
421
|
end
|
422
422
|
end
|
423
423
|
|
424
|
-
def copy_to_host(nix, host, machine,
|
424
|
+
def copy_to_host(nix, host, machine, build_generation)
|
425
425
|
puts Rainbow("Copying configuration to #{host} (#{machine.target_host})").yellow
|
426
426
|
|
427
427
|
return :skip if opts[:interactive] && !ask_confirmation(always: true)
|
@@ -435,7 +435,7 @@ module ConfCtl::Cli
|
|
435
435
|
width: 80
|
436
436
|
)
|
437
437
|
|
438
|
-
ret = nix
|
438
|
+
ret = nix_copy(nix, machine, build_generation) do |i, n, path|
|
439
439
|
lw << "[#{i}/#{n}] #{path}"
|
440
440
|
|
441
441
|
lw.sync_console do
|
@@ -451,8 +451,10 @@ module ConfCtl::Cli
|
|
451
451
|
end
|
452
452
|
|
453
453
|
def concurrent_copy(machines, host_generations, nix)
|
454
|
+
sorted_generations = sort_generations_for_copy(machines, host_generations)
|
455
|
+
|
454
456
|
LogView.open(
|
455
|
-
header: "#{Rainbow("Copying to #{
|
457
|
+
header: "#{Rainbow("Copying to #{sorted_generations.length} machines").bright}\n",
|
456
458
|
title: Rainbow('Live view').bright
|
457
459
|
) do |lw|
|
458
460
|
multibar = TTY::ProgressBar::Multi.new(
|
@@ -461,13 +463,13 @@ module ConfCtl::Cli
|
|
461
463
|
)
|
462
464
|
executor = ConfCtl::ParallelExecutor.new(opts['max-concurrent-copy'])
|
463
465
|
|
464
|
-
|
466
|
+
sorted_generations.each do |host, gen|
|
465
467
|
pb = multibar.register(
|
466
468
|
"#{host} [:bar] :current/:total (:percent)"
|
467
469
|
)
|
468
470
|
|
469
471
|
executor.add do
|
470
|
-
ret = nix
|
472
|
+
ret = nix_copy(nix, machines[host], gen) do |i, n, path|
|
471
473
|
lw << "#{host}> [#{i}/#{n}] #{path}"
|
472
474
|
|
473
475
|
lw.sync_console do
|
@@ -504,67 +506,136 @@ module ConfCtl::Cli
|
|
504
506
|
end
|
505
507
|
end
|
506
508
|
|
507
|
-
|
509
|
+
# Sort generations for copy to target machines
|
510
|
+
#
|
511
|
+
# This algorithm interleaves carried machines from different carriers,
|
512
|
+
# so that we copy to multiple carriers at the same time, and not many carried
|
513
|
+
# machines to one carrier.
|
514
|
+
def sort_generations_for_copy(machines, host_generations)
|
515
|
+
carried, standalone = machines.to_a.partition(&:carried?)
|
516
|
+
|
517
|
+
carried_groups = carried.group_by { |m| m.carrier_machine.name }
|
518
|
+
|
519
|
+
sorted_generations = standalone.map do |m|
|
520
|
+
[m.name, host_generations[m.name]]
|
521
|
+
end
|
522
|
+
|
523
|
+
until carried_groups.empty?
|
524
|
+
carried_groups.delete_if do |_, machines|
|
525
|
+
m = machines.shift
|
526
|
+
next(true) if m.nil?
|
527
|
+
|
528
|
+
sorted_generations << [m.name, host_generations[m.name]]
|
529
|
+
|
530
|
+
machines.empty?
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
if sorted_generations.length != host_generations.length
|
535
|
+
raise 'programming error: sorted generations length != original generations'
|
536
|
+
end
|
537
|
+
|
538
|
+
sorted_generations
|
539
|
+
end
|
540
|
+
|
541
|
+
def deploy_to_host(nix, host, machine, generation, action)
|
542
|
+
ret = nil
|
543
|
+
|
508
544
|
LogView.open_with_logger(
|
509
545
|
header: "#{Rainbow('Deploying to').bright} #{Rainbow(host).yellow}\n",
|
510
546
|
title: Rainbow('Live view').bright,
|
511
547
|
size: :auto,
|
512
548
|
reserved_lines: 10
|
513
549
|
) do |lw|
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
550
|
+
ret =
|
551
|
+
if machine.carried?
|
552
|
+
deploy_carried_to_host(lw, nix, host, machine, generation, action)
|
553
|
+
else
|
554
|
+
deploy_standalone_to_host(lw, nix, host, machine, generation, action)
|
555
|
+
end
|
519
556
|
|
520
557
|
lw.flush
|
521
558
|
end
|
522
|
-
end
|
523
559
|
|
524
|
-
|
525
|
-
|
526
|
-
lw.sync_console do
|
527
|
-
puts Rainbow(
|
528
|
-
"Trying to activate configuration on #{host} " \
|
529
|
-
"(#{machine.target_host})"
|
530
|
-
).yellow
|
531
|
-
end
|
532
|
-
|
533
|
-
raise "Error while activating configuration on #{host}" unless nix.activate(machine, toplevel, 'dry-activate')
|
534
|
-
end
|
535
|
-
|
536
|
-
lw.sync_console do
|
537
|
-
puts Rainbow(
|
538
|
-
"Activating configuration on #{host} (#{machine.target_host}): " \
|
539
|
-
"#{action}"
|
540
|
-
).yellow
|
541
|
-
end
|
560
|
+
ret
|
561
|
+
end
|
542
562
|
|
543
|
-
|
563
|
+
def deploy_standalone_to_host(lw, nix, host, machine, generation, action)
|
564
|
+
return :skip if pre_host_activate(lw, nix, host, machine, generation, action) == :skip
|
544
565
|
|
545
566
|
# rubocop:disable Style/GuardClause
|
546
567
|
|
547
|
-
|
548
|
-
|
549
|
-
|
568
|
+
result =
|
569
|
+
if %w[test switch].include?(action) && enable_auto_rollback?(machine, generation)
|
570
|
+
nix.activate_with_rollback(machine, generation, action)
|
571
|
+
else
|
572
|
+
nix.activate(machine, generation, action)
|
573
|
+
end
|
574
|
+
|
575
|
+
raise "Error while activating configuration on #{host}" unless result
|
550
576
|
|
551
|
-
if %w[boot switch].include?(action) && !nix.set_profile(machine, toplevel)
|
577
|
+
if %w[boot switch].include?(action) && !nix.set_profile(machine, generation.toplevel)
|
552
578
|
raise "Error while setting profile on #{host}"
|
553
579
|
end
|
554
580
|
|
555
581
|
# rubocop:enable Style/GuardClause
|
556
582
|
end
|
557
583
|
|
558
|
-
def deploy_carried_to_host(lw, nix, host, machine,
|
584
|
+
def deploy_carried_to_host(lw, nix, host, machine, generation, action)
|
559
585
|
return if action != 'switch'
|
560
586
|
|
561
587
|
# rubocop:disable Style/GuardClause
|
562
|
-
unless nix.set_carried_profile(machine, toplevel)
|
588
|
+
unless nix.set_carried_profile(machine, generation.toplevel)
|
563
589
|
raise "Error while setting carried profile for #{host} on #{machine.carrier_machine}"
|
564
590
|
end
|
565
591
|
# rubocop:enable Style/GuardClause
|
566
592
|
end
|
567
593
|
|
594
|
+
def pre_host_activate(lw, nix, host, machine, generation, action)
|
595
|
+
loop do
|
596
|
+
if opts['dry-activate-first']
|
597
|
+
lw.sync_console do
|
598
|
+
puts Rainbow(
|
599
|
+
"Trying to activate configuration on #{host} " \
|
600
|
+
"(#{machine.target_host})"
|
601
|
+
).yellow
|
602
|
+
end
|
603
|
+
|
604
|
+
unless nix.activate(machine, generation, 'dry-activate')
|
605
|
+
raise "Error while activating configuration on #{host}"
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
lw.sync_console do
|
610
|
+
puts Rainbow(
|
611
|
+
"Activating configuration on #{host} (#{machine.target_host}): " \
|
612
|
+
"#{action}"
|
613
|
+
).yellow
|
614
|
+
end
|
615
|
+
|
616
|
+
return unless opts[:interactive]
|
617
|
+
|
618
|
+
options = {}
|
619
|
+
options['y'] = 'Continue'
|
620
|
+
options['r'] = 'Retry' if opts['dry-activate-first']
|
621
|
+
options['s'] = 'Skip'
|
622
|
+
options['a'] = 'Abort'
|
623
|
+
|
624
|
+
answer = ask_action(options:, default: nil)
|
625
|
+
|
626
|
+
case answer
|
627
|
+
when 'y'
|
628
|
+
return
|
629
|
+
when 'r'
|
630
|
+
next
|
631
|
+
when 's'
|
632
|
+
return :skip
|
633
|
+
when 'a'
|
634
|
+
raise 'Aborting'
|
635
|
+
end
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
568
639
|
def reboot_host(host, machine)
|
569
640
|
if machine.localhost?
|
570
641
|
puts Rainbow("Skipping reboot of #{host} as it is localhost").yellow
|
@@ -896,12 +967,15 @@ module ConfCtl::Cli
|
|
896
967
|
def find_generations(machines, generation_name)
|
897
968
|
host_generations = {}
|
898
969
|
missing_hosts = []
|
970
|
+
generation_offset = generation_name.to_i if /\A-?\d+\z/ =~ generation_name
|
899
971
|
|
900
972
|
machines.each_key do |host|
|
901
973
|
list = ConfCtl::Generation::BuildList.new(host)
|
902
974
|
|
903
975
|
gen =
|
904
|
-
if
|
976
|
+
if generation_offset
|
977
|
+
list.at_offset(generation_offset)
|
978
|
+
elsif generation_name == 'current'
|
905
979
|
list.current
|
906
980
|
else
|
907
981
|
list[generation_name]
|
@@ -916,8 +990,17 @@ module ConfCtl::Cli
|
|
916
990
|
|
917
991
|
raise 'No generation found' if host_generations.empty?
|
918
992
|
|
993
|
+
puts 'Resolved host generations:'
|
994
|
+
list_generations(host_generations, missing_hosts:)
|
995
|
+
|
996
|
+
if opts[:interactive]
|
997
|
+
ask_confirmation!
|
998
|
+
else
|
999
|
+
puts
|
1000
|
+
end
|
1001
|
+
|
919
1002
|
if missing_hosts.any?
|
920
|
-
ask_confirmation! do
|
1003
|
+
ask_confirmation!(always: opts[:interactive]) do
|
921
1004
|
puts "Generation '#{generation_name}' was not found on the following hosts:"
|
922
1005
|
missing_hosts.each { |host| puts " #{host}" }
|
923
1006
|
puts
|
@@ -928,22 +1011,59 @@ module ConfCtl::Cli
|
|
928
1011
|
host_generations
|
929
1012
|
end
|
930
1013
|
|
1014
|
+
def list_generations(host_generations, missing_hosts:)
|
1015
|
+
swpin_names = []
|
1016
|
+
|
1017
|
+
host_generations.each_value do |gen|
|
1018
|
+
gen.swpin_names.each do |name|
|
1019
|
+
swpin_names << name unless swpin_names.include?(name)
|
1020
|
+
end
|
1021
|
+
end
|
1022
|
+
|
1023
|
+
rows = host_generations.map do |host, gen|
|
1024
|
+
row = {
|
1025
|
+
'name' => host,
|
1026
|
+
'generation' => gen.name,
|
1027
|
+
'kernel' => gen.kernel_version
|
1028
|
+
}
|
1029
|
+
|
1030
|
+
gen.swpin_specs.each do |name, spec|
|
1031
|
+
row[name] = spec.version
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
row
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
missing_hosts.each do |host|
|
1038
|
+
rows << { 'name' => host, 'generation' => 'not found' }
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
OutputFormatter.print(
|
1042
|
+
rows,
|
1043
|
+
%w[name generation kernel] + swpin_names,
|
1044
|
+
layout: :columns,
|
1045
|
+
sort: %w[name generation]
|
1046
|
+
)
|
1047
|
+
end
|
1048
|
+
|
931
1049
|
def do_build(machines)
|
932
1050
|
nix = ConfCtl::Nix.new(
|
933
1051
|
show_trace: opts['show-trace'],
|
934
1052
|
max_jobs: opts['max-jobs'],
|
935
1053
|
cores: opts['cores']
|
936
1054
|
)
|
937
|
-
hosts_swpin_paths = {}
|
938
1055
|
|
939
1056
|
autoupdate_swpins(machines)
|
940
1057
|
host_swpin_specs = check_swpins(machines)
|
941
1058
|
|
942
1059
|
raise 'one or more swpins need to be updated' unless host_swpin_specs
|
943
1060
|
|
944
|
-
machines.
|
945
|
-
|
946
|
-
|
1061
|
+
puts Rainbow("Evaluating swpins for #{machines.length} machines...").bright
|
1062
|
+
|
1063
|
+
hosts_swpin_paths = nix.eval_host_swpins(machines.map { |host, _| host })
|
1064
|
+
|
1065
|
+
machines.each do |host, m|
|
1066
|
+
hosts_swpin_paths[host].update(m.nix_paths)
|
947
1067
|
end
|
948
1068
|
|
949
1069
|
grps = swpin_build_groups(hosts_swpin_paths)
|
@@ -1273,5 +1393,21 @@ module ConfCtl::Cli
|
|
1273
1393
|
|
1274
1394
|
raise ArgumentError, "invalid time duration '#{interval}'"
|
1275
1395
|
end
|
1396
|
+
|
1397
|
+
# @param nix [Nix]
|
1398
|
+
# @param machine [Machine]
|
1399
|
+
# @param generation [Generation::Build]
|
1400
|
+
def nix_copy(nix, machine, generation, &)
|
1401
|
+
paths = [generation.toplevel]
|
1402
|
+
paths << generation.auto_rollback if enable_auto_rollback?(machine, generation)
|
1403
|
+
|
1404
|
+
nix.copy(machine, paths, &)
|
1405
|
+
end
|
1406
|
+
|
1407
|
+
def enable_auto_rollback?(machine, generation)
|
1408
|
+
!opts['disable-auto-rollback'] \
|
1409
|
+
&& (opts['enable-auto-rollback'] || machine.auto_rollback?) \
|
1410
|
+
&& generation.auto_rollback
|
1411
|
+
end
|
1276
1412
|
end
|
1277
1413
|
end
|
@@ -11,6 +11,7 @@ module ConfCtl::Cli
|
|
11
11
|
def remove
|
12
12
|
machines = select_machines(args[0])
|
13
13
|
gens = select_generations(machines, args[1])
|
14
|
+
changed_hosts = []
|
14
15
|
|
15
16
|
if gens.empty?
|
16
17
|
puts 'No generations found'
|
@@ -27,21 +28,36 @@ module ConfCtl::Cli
|
|
27
28
|
gens.each do |gen|
|
28
29
|
puts "Removing #{gen.presence_str} generation #{gen.host}@#{gen.name}"
|
29
30
|
gen.destroy
|
31
|
+
|
32
|
+
changed_hosts << gen.host unless changed_hosts.include?(gen.host)
|
30
33
|
end
|
31
34
|
|
32
35
|
return unless opts[:remote] && opts[:gc]
|
33
36
|
|
34
|
-
machines_gc =
|
35
|
-
|
37
|
+
machines_gc = {}
|
38
|
+
|
39
|
+
machines.each do |host, machine|
|
40
|
+
next unless changed_hosts.include?(host)
|
41
|
+
|
42
|
+
m =
|
43
|
+
if machine.carried?
|
44
|
+
machine.carrier_machine
|
45
|
+
else
|
46
|
+
machine
|
47
|
+
end
|
48
|
+
|
49
|
+
machines_gc[m.name] = m if m.target_host
|
36
50
|
end
|
37
51
|
|
38
|
-
run_gc(machines_gc)
|
52
|
+
run_gc(ConfCtl::MachineList.new(machines: machines_gc))
|
39
53
|
end
|
40
54
|
|
41
55
|
def rotate
|
42
56
|
machines = select_machines(args[0])
|
43
57
|
|
44
58
|
to_delete = []
|
59
|
+
changed_hosts = []
|
60
|
+
enable_gc = opts[:remote] && opts[:gc]
|
45
61
|
|
46
62
|
to_delete.concat(host_generations_rotate(machines)) if opts[:remote]
|
47
63
|
|
@@ -56,29 +72,37 @@ module ConfCtl::Cli
|
|
56
72
|
puts 'The following generations will be removed:'
|
57
73
|
OutputFormatter.print(to_delete, %i[host name type id], layout: :columns)
|
58
74
|
puts
|
59
|
-
puts "Garbage collection: #{
|
75
|
+
puts "Garbage collection: #{enable_gc ? 'when enabled in configuration' : 'no'}"
|
60
76
|
end
|
61
77
|
|
62
78
|
to_delete.each do |gen|
|
63
79
|
puts "Removing #{gen[:type]} generation #{gen[:host]}@#{gen[:name]}"
|
64
80
|
gen[:generation].destroy
|
81
|
+
|
82
|
+
changed_hosts << gen[:host] unless changed_hosts.include?(gen[:host])
|
65
83
|
end
|
66
84
|
|
67
|
-
return unless
|
85
|
+
return unless enable_gc
|
68
86
|
|
69
87
|
global = ConfCtl::Settings.instance.host_generations
|
88
|
+
machines_gc = {}
|
70
89
|
|
71
|
-
|
72
|
-
|
90
|
+
machines.each do |host, machine|
|
91
|
+
next unless changed_hosts.include?(host)
|
73
92
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
93
|
+
m =
|
94
|
+
if machine.carried?
|
95
|
+
machine.carrier_machine
|
96
|
+
else
|
97
|
+
machine
|
98
|
+
end
|
99
|
+
|
100
|
+
next if !m.target_host || (!m['buildGenerations']['collectGarbage'] && !global['collectGarbage'])
|
101
|
+
|
102
|
+
machines_gc[m.name] = m
|
79
103
|
end
|
80
104
|
|
81
|
-
run_gc(machines_gc) if machines_gc.any?
|
105
|
+
run_gc(ConfCtl::MachineList.new(machines: machines_gc)) if machines_gc.any?
|
82
106
|
end
|
83
107
|
|
84
108
|
def collect_garbage
|
@@ -131,12 +155,16 @@ module ConfCtl::Cli
|
|
131
155
|
select_older_than =
|
132
156
|
(Time.now - (::Regexp.last_match(1).to_i * 24 * 60 * 60) if !select_old && /\A(\d+)d\Z/ =~ pattern)
|
133
157
|
|
158
|
+
gen_at = gens.at_offset(pattern.to_i) if /\A-?\d+\z/ =~ pattern
|
159
|
+
|
134
160
|
if pattern
|
135
161
|
gens.delete_if do |gen|
|
136
162
|
if select_old
|
137
163
|
gen.current
|
138
164
|
elsif select_older_than
|
139
165
|
gen.date >= select_older_than
|
166
|
+
elsif gen_at
|
167
|
+
gen != gen_at
|
140
168
|
else
|
141
169
|
!ConfCtl::Pattern.match?(pattern, gen.name)
|
142
170
|
end
|
@@ -316,7 +344,8 @@ module ConfCtl::Cli
|
|
316
344
|
'name' => gen.name,
|
317
345
|
'id' => gen.id,
|
318
346
|
'presence' => gen.presence_str,
|
319
|
-
'current' => gen.current_str
|
347
|
+
'current' => gen.current_str,
|
348
|
+
'kernel' => gen.kernel_version
|
320
349
|
}
|
321
350
|
|
322
351
|
gen.swpin_specs.each do |name, spec|
|
@@ -328,7 +357,7 @@ module ConfCtl::Cli
|
|
328
357
|
|
329
358
|
OutputFormatter.print(
|
330
359
|
rows,
|
331
|
-
%w[host name id presence current] + swpin_names,
|
360
|
+
%w[host name id presence current kernel] + swpin_names,
|
332
361
|
layout: :columns,
|
333
362
|
sort: %w[name host]
|
334
363
|
)
|
@@ -37,11 +37,12 @@ module ConfCtl::Cli
|
|
37
37
|
|
38
38
|
channels.each(&:save)
|
39
39
|
|
40
|
-
return
|
40
|
+
return if !opts[:commit] || !change_set.any_changes?
|
41
41
|
|
42
42
|
change_set.commit(
|
43
43
|
type: opts[:downgrade] ? :downgrade : :upgrade,
|
44
|
-
changelog: opts[:changelog]
|
44
|
+
changelog: opts[:changelog],
|
45
|
+
editor: opts[:editor]
|
45
46
|
)
|
46
47
|
end
|
47
48
|
|
@@ -62,11 +63,12 @@ module ConfCtl::Cli
|
|
62
63
|
|
63
64
|
channels.each(&:save)
|
64
65
|
|
65
|
-
return
|
66
|
+
return if !opts[:commit] || !change_set.any_changes?
|
66
67
|
|
67
68
|
change_set.commit(
|
68
69
|
type: opts[:downgrade] ? :downgrade : :upgrade,
|
69
|
-
changelog: opts[:changelog]
|
70
|
+
changelog: opts[:changelog],
|
71
|
+
editor: opts[:editor]
|
70
72
|
)
|
71
73
|
end
|
72
74
|
end
|
@@ -42,11 +42,12 @@ module ConfCtl::Cli
|
|
42
42
|
|
43
43
|
cluster_names.each(&:save)
|
44
44
|
|
45
|
-
return
|
45
|
+
return if !opts[:commit] || !change_set.any_changes?
|
46
46
|
|
47
47
|
change_set.commit(
|
48
48
|
type: opts[:downgrade] ? :downgrade : :upgrade,
|
49
|
-
changelog: opts[:changelog]
|
49
|
+
changelog: opts[:changelog],
|
50
|
+
editor: opts[:editor]
|
50
51
|
)
|
51
52
|
end
|
52
53
|
|
@@ -69,11 +70,12 @@ module ConfCtl::Cli
|
|
69
70
|
|
70
71
|
cluster_names.each(&:save)
|
71
72
|
|
72
|
-
return
|
73
|
+
return if !opts[:commit] || !change_set.any_changes?
|
73
74
|
|
74
75
|
change_set.commit(
|
75
76
|
type: opts[:downgrade] ? :downgrade : :upgrade,
|
76
|
-
changelog: opts[:changelog]
|
77
|
+
changelog: opts[:changelog],
|
78
|
+
editor: opts[:editor]
|
77
79
|
)
|
78
80
|
end
|
79
81
|
end
|