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
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module ConfCtl
|
|
2
|
+
class NixBuildFlake
|
|
3
|
+
# @param args [Array<String>]
|
|
4
|
+
# @param chdir [String]
|
|
5
|
+
def initialize(args, chdir:)
|
|
6
|
+
@args = args
|
|
7
|
+
@chdir = chdir
|
|
8
|
+
@build_progress = 0
|
|
9
|
+
@build_total = 0
|
|
10
|
+
@fetch_progress = 0
|
|
11
|
+
@fetch_total = 0
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @yieldparam type [:build, :fetch]
|
|
15
|
+
# @yieldparam progress [Integer]
|
|
16
|
+
# @yieldparam total [Integer]
|
|
17
|
+
# @yieldparam path [String]
|
|
18
|
+
def run(&block)
|
|
19
|
+
cmd = SystemCommand.new
|
|
20
|
+
|
|
21
|
+
line_buf = StdLineBuffer.new do |_out, err|
|
|
22
|
+
parse_line(err, &block) if err && block
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
ret = cmd.run('nix', 'build', *args, chdir: chdir, &line_buf.feed_block)
|
|
26
|
+
|
|
27
|
+
line_buf.flush
|
|
28
|
+
ret
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
protected
|
|
32
|
+
|
|
33
|
+
attr_reader :args, :chdir
|
|
34
|
+
|
|
35
|
+
def parse_line(line)
|
|
36
|
+
# Beware that nix can fetch/build stuff even before those two
|
|
37
|
+
# summary lines are printed. Therefore we report progress with total=0
|
|
38
|
+
# (indeterminate) until the total becomes known.
|
|
39
|
+
|
|
40
|
+
# Nix >= around 2.11
|
|
41
|
+
case line
|
|
42
|
+
when /^this derivation will be built:/
|
|
43
|
+
@build_total = 1
|
|
44
|
+
return
|
|
45
|
+
when /^these (\d+) derivations will be built:/
|
|
46
|
+
@build_total = ::Regexp.last_match(1).to_i
|
|
47
|
+
return
|
|
48
|
+
when /^this path will be fetched /
|
|
49
|
+
@fetch_total = 1
|
|
50
|
+
return
|
|
51
|
+
when /^these (\d+) paths will be fetched /
|
|
52
|
+
@fetch_total = ::Regexp.last_match(1).to_i
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Nix < around 2.11, we can drop this in the future
|
|
57
|
+
if /^these derivations will be built:/ =~ line
|
|
58
|
+
@in_derivation_list = true
|
|
59
|
+
@in_fetch_list = false
|
|
60
|
+
return
|
|
61
|
+
elsif /^these paths will be fetched / =~ line
|
|
62
|
+
@in_derivation_list = false
|
|
63
|
+
@in_fetch_list = true
|
|
64
|
+
return
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if @in_derivation_list
|
|
68
|
+
if %r{^\s+/nix/store/} =~ line
|
|
69
|
+
@build_total += 1
|
|
70
|
+
return
|
|
71
|
+
else
|
|
72
|
+
@in_derivation_list = false
|
|
73
|
+
@build_total += @build_progress
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
elsif @in_fetch_list
|
|
77
|
+
if %r{^\s+/nix/store/} =~ line
|
|
78
|
+
@fetch_total += 1
|
|
79
|
+
return
|
|
80
|
+
else
|
|
81
|
+
@in_fetch_list = false
|
|
82
|
+
@fetch_total += @fetch_progress
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if /^building '([^']+)/ =~ line
|
|
87
|
+
@build_progress += 1
|
|
88
|
+
yield(:build, @build_progress, @build_total, ::Regexp.last_match(1))
|
|
89
|
+
elsif /^copying path '([^']+)/ =~ line
|
|
90
|
+
@fetch_progress += 1
|
|
91
|
+
yield(:fetch, @fetch_progress, @fetch_total, ::Regexp.last_match(1))
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/confctl/nix_copy.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
module ConfCtl
|
|
2
2
|
class NixCopy
|
|
3
3
|
# @param target [String]
|
|
4
|
+
# @param port [Integer]
|
|
4
5
|
# @param store_paths [Array<String>]
|
|
5
|
-
def initialize(target, store_paths)
|
|
6
|
+
def initialize(target, store_paths, port: 22)
|
|
6
7
|
@target = target
|
|
7
8
|
@store_paths = store_paths
|
|
9
|
+
@port = port.to_i
|
|
8
10
|
@total = nil
|
|
9
11
|
@progress = 0
|
|
10
12
|
end
|
|
@@ -23,6 +25,7 @@ module ConfCtl
|
|
|
23
25
|
'nix-copy-closure',
|
|
24
26
|
'--to', "root@#{target}",
|
|
25
27
|
*store_paths,
|
|
28
|
+
env: nix_ssh_env,
|
|
26
29
|
&line_buf.feed_block
|
|
27
30
|
)
|
|
28
31
|
|
|
@@ -32,7 +35,16 @@ module ConfCtl
|
|
|
32
35
|
|
|
33
36
|
protected
|
|
34
37
|
|
|
35
|
-
attr_reader :target, :store_paths
|
|
38
|
+
attr_reader :target, :store_paths, :port
|
|
39
|
+
|
|
40
|
+
def nix_ssh_env
|
|
41
|
+
return {} if port == 22
|
|
42
|
+
|
|
43
|
+
ssh_opts = [ENV.fetch('NIX_SSHOPTS', nil), "-p #{port}"].compact.join(' ').strip
|
|
44
|
+
{
|
|
45
|
+
'NIX_SSHOPTS' => ssh_opts
|
|
46
|
+
}
|
|
47
|
+
end
|
|
36
48
|
|
|
37
49
|
def parse_line(line)
|
|
38
50
|
if @total.nil? && /^copying (\d+) paths/ =~ line
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require_relative 'nix_build_flake'
|
|
3
|
+
require_relative 'nix/args'
|
|
4
|
+
|
|
5
|
+
module ConfCtl
|
|
6
|
+
class NixFlake < NixLegacy
|
|
7
|
+
def confctl_settings
|
|
8
|
+
@confctl_settings ||= begin
|
|
9
|
+
settings = nix_eval_json('.#confctl.settings', impure: false, settings: {})
|
|
10
|
+
demodulify(settings)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns an array with options from all confctl modules
|
|
15
|
+
# @return [Array]
|
|
16
|
+
def module_options
|
|
17
|
+
[]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns an array with machine fqdns
|
|
21
|
+
# @return [Array<String>]
|
|
22
|
+
def list_machine_fqdns
|
|
23
|
+
nix_eval_json('.#confctl.machineNames')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Return machines and their config in a hash
|
|
27
|
+
# @return [Hash]
|
|
28
|
+
def list_machines
|
|
29
|
+
machines_json = nix_build_output_path('.#confctl.machinesJson')
|
|
30
|
+
machines = JSON.parse(File.read(machines_json))
|
|
31
|
+
machines = demodulify(machines)
|
|
32
|
+
refresh_machine_key_maps(machines)
|
|
33
|
+
machines
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Evaluate flake installable and parse JSON
|
|
37
|
+
# @param installable [String]
|
|
38
|
+
# @return [Object]
|
|
39
|
+
def eval_json(installable)
|
|
40
|
+
nix_eval_json(installable)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Return list of swpins channels
|
|
44
|
+
# @return [Hash]
|
|
45
|
+
def list_swpins_channels
|
|
46
|
+
confctl_settings.fetch('swpins', {}).fetch('channels', {})
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Evaluate core swpins for host
|
|
50
|
+
# @return [Hash]
|
|
51
|
+
def eval_core_swpins
|
|
52
|
+
{}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Evaluate swpins for hosts
|
|
56
|
+
# @param hosts [Array<String>]
|
|
57
|
+
# @return [Hash] host => swpins
|
|
58
|
+
def eval_host_swpins(hosts)
|
|
59
|
+
plan = build_plan
|
|
60
|
+
|
|
61
|
+
hosts.to_h do |host|
|
|
62
|
+
host_plan = plan.fetch(host)
|
|
63
|
+
[host, host_plan['inputs'] || {}]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Evaluate inputs info for host
|
|
68
|
+
# @param host [String]
|
|
69
|
+
# @return [Hash]
|
|
70
|
+
def eval_inputs_info(host)
|
|
71
|
+
nix_eval_json(inputs_info_installable(host))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Evaluate inputs for host
|
|
75
|
+
# @param host [String]
|
|
76
|
+
# @return [Hash]
|
|
77
|
+
def eval_inputs(host)
|
|
78
|
+
nix_eval_json(inputs_installable(host))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Build config.system.build.toplevel for selected hosts
|
|
82
|
+
#
|
|
83
|
+
# @param hosts [Array<String>]
|
|
84
|
+
# @param swpin_paths [Hash]
|
|
85
|
+
# @param host_swpin_specs [Hash]
|
|
86
|
+
# @param time [Time]
|
|
87
|
+
#
|
|
88
|
+
# @yieldparam type [:build, :fetch]
|
|
89
|
+
# @yieldparam progress [Integer]
|
|
90
|
+
# @yieldparam total [Integer]
|
|
91
|
+
# @yieldparam path [String]
|
|
92
|
+
#
|
|
93
|
+
# @return [Hash<String, Generation::Build>]
|
|
94
|
+
def build_attributes(hosts: [], swpin_paths: {}, host_swpin_specs: {}, time: nil, &)
|
|
95
|
+
plan = build_plan
|
|
96
|
+
time ||= Time.now
|
|
97
|
+
ret_generations = {}
|
|
98
|
+
host_plans = {}
|
|
99
|
+
installables = []
|
|
100
|
+
machine_keys = []
|
|
101
|
+
|
|
102
|
+
hosts.each do |host|
|
|
103
|
+
host_plan = plan.fetch(host)
|
|
104
|
+
machine_key = host_plan['key'] || host_plan['machineKey'] || host_plan['flakeKey'] || machine_key_for(host)
|
|
105
|
+
host_plans[host] = host_plan
|
|
106
|
+
installables << ".#confctl.build.#{machine_key}.toplevel"
|
|
107
|
+
installables << ".#confctl.build.#{machine_key}.autoRollback"
|
|
108
|
+
machine_keys << machine_key
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
legacy_args = legacy_nix_path_args(hosts)
|
|
112
|
+
nix_build_json(installables, legacy_nix_path_args: legacy_args, &)
|
|
113
|
+
build_outputs = build_outputs_for_keys(machine_keys)
|
|
114
|
+
pending_generations = {}
|
|
115
|
+
|
|
116
|
+
hosts.each do |host|
|
|
117
|
+
host_plan = host_plans[host]
|
|
118
|
+
machine_key = host_plan['key'] || host_plan['machineKey'] || host_plan['flakeKey'] || machine_key_for(host)
|
|
119
|
+
host_input_paths = host_plan['inputs'] || {}
|
|
120
|
+
|
|
121
|
+
outputs = build_outputs[machine_key]
|
|
122
|
+
if outputs.nil?
|
|
123
|
+
raise ConfCtl::Error, "missing build outputs for #{machine_key.inspect}"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
toplevel_path = outputs['toplevel']
|
|
127
|
+
auto_rollback_path = outputs['autoRollback']
|
|
128
|
+
|
|
129
|
+
if toplevel_path.nil? || auto_rollback_path.nil?
|
|
130
|
+
raise ConfCtl::Error, "invalid build outputs for #{machine_key.inspect}"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
host_generations = Generation::BuildList.new(host)
|
|
134
|
+
generation = host_generations.find(toplevel_path, host_input_paths, mode: 'flakes')
|
|
135
|
+
|
|
136
|
+
if generation.nil?
|
|
137
|
+
pending_generations[host] = {
|
|
138
|
+
host_generations: host_generations,
|
|
139
|
+
machine_key: machine_key,
|
|
140
|
+
toplevel_path: toplevel_path,
|
|
141
|
+
auto_rollback_path: auto_rollback_path,
|
|
142
|
+
host_input_paths: host_input_paths
|
|
143
|
+
}
|
|
144
|
+
next
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
host_generations.current = generation
|
|
148
|
+
ret_generations[host] = generation
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
if pending_generations.any?
|
|
152
|
+
inputs_infos = inputs_info_for_keys(pending_generations.values.map { |v| v[:machine_key] })
|
|
153
|
+
|
|
154
|
+
pending_generations.each do |host, data|
|
|
155
|
+
inputs_info = inputs_infos[data[:machine_key]] || eval_inputs_info(host)
|
|
156
|
+
generation = Generation::Build.new(host)
|
|
157
|
+
generation.create_flake(
|
|
158
|
+
data[:toplevel_path],
|
|
159
|
+
data[:auto_rollback_path],
|
|
160
|
+
inputs: data[:host_input_paths],
|
|
161
|
+
inputs_info: inputs_info,
|
|
162
|
+
date: time
|
|
163
|
+
)
|
|
164
|
+
generation.save
|
|
165
|
+
|
|
166
|
+
data[:host_generations].current = generation
|
|
167
|
+
ret_generations[host] = generation
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
ret_generations
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
protected
|
|
175
|
+
|
|
176
|
+
def build_plan
|
|
177
|
+
@build_plan ||= nix_eval_json('.#confctl.buildPlan')
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def inputs_info_installable(host)
|
|
181
|
+
".#confctl.inputsInfo.#{machine_key_for(host)}"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def inputs_installable(host)
|
|
185
|
+
".#confctl.inputs.#{machine_key_for(host)}"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def nix_eval_json(installable, impure: nil, settings: nil, apply: nil)
|
|
189
|
+
settings_for_args = settings || confctl_settings
|
|
190
|
+
|
|
191
|
+
result = run_nix_with_fallback do |extra_experimental, no_update_lock_file|
|
|
192
|
+
args_builder = nix_args(
|
|
193
|
+
settings: settings_for_args,
|
|
194
|
+
impure: impure,
|
|
195
|
+
no_update_lock_file: no_update_lock_file
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
nix_eval_args(
|
|
199
|
+
installable,
|
|
200
|
+
args_builder: args_builder,
|
|
201
|
+
extra_experimental: extra_experimental,
|
|
202
|
+
apply: apply
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
out, = result.stdout
|
|
207
|
+
JSON.parse(out)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def nix_build_json(installables, legacy_nix_path_args: [], &block)
|
|
211
|
+
extra_experimental = false
|
|
212
|
+
no_update_lock_file = true
|
|
213
|
+
|
|
214
|
+
loop do
|
|
215
|
+
args_builder = nix_args(
|
|
216
|
+
settings: confctl_settings,
|
|
217
|
+
no_update_lock_file: no_update_lock_file
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
args = nix_build_args(
|
|
221
|
+
installables,
|
|
222
|
+
args_builder: args_builder,
|
|
223
|
+
extra_experimental: extra_experimental,
|
|
224
|
+
legacy_nix_path_args: legacy_nix_path_args
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
nb = NixBuildFlake.new(args, chdir: conf_dir)
|
|
228
|
+
result = nb.run(&block)
|
|
229
|
+
out, = result.stdout
|
|
230
|
+
return JSON.parse(out)
|
|
231
|
+
rescue TTY::Command::ExitError => e
|
|
232
|
+
if no_update_lock_file && no_update_lock_file_error?(e.message)
|
|
233
|
+
no_update_lock_file = false
|
|
234
|
+
retry
|
|
235
|
+
elsif !extra_experimental && experimental_error?(e.message)
|
|
236
|
+
extra_experimental = true
|
|
237
|
+
retry
|
|
238
|
+
else
|
|
239
|
+
raise
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def nix_build_output_path(installable)
|
|
245
|
+
outputs = nix_build_json([installable])
|
|
246
|
+
output_path = outputs.dig(0, 'outputs', 'out')
|
|
247
|
+
|
|
248
|
+
unless output_path
|
|
249
|
+
raise ConfCtl::Error, "missing output path for #{installable.inspect}"
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
output_path
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def run_nix_with_fallback
|
|
256
|
+
extra_experimental = false
|
|
257
|
+
no_update_lock_file = true
|
|
258
|
+
|
|
259
|
+
loop do
|
|
260
|
+
args = yield(extra_experimental, no_update_lock_file)
|
|
261
|
+
|
|
262
|
+
begin
|
|
263
|
+
return cmd.run(*args, chdir: conf_dir)
|
|
264
|
+
rescue TTY::Command::ExitError => e
|
|
265
|
+
if no_update_lock_file && no_update_lock_file_error?(e.message)
|
|
266
|
+
no_update_lock_file = false
|
|
267
|
+
next
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
if !extra_experimental && experimental_error?(e.message)
|
|
271
|
+
extra_experimental = true
|
|
272
|
+
next
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
raise
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def nix_args(settings:, impure: nil, no_update_lock_file: true)
|
|
281
|
+
ConfCtl::Nix::Args.new(
|
|
282
|
+
settings: settings,
|
|
283
|
+
impure: impure,
|
|
284
|
+
no_update_lock_file: no_update_lock_file
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def nix_eval_args(installable, args_builder:, extra_experimental:, apply: nil)
|
|
289
|
+
args = ['nix', 'eval', '--json']
|
|
290
|
+
args.concat(
|
|
291
|
+
nix_common_args(
|
|
292
|
+
args_builder.eval_args,
|
|
293
|
+
extra_experimental: extra_experimental
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
if apply
|
|
297
|
+
args << '--apply' << apply
|
|
298
|
+
end
|
|
299
|
+
args << installable
|
|
300
|
+
args
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def nix_build_args(installables, args_builder:, extra_experimental:, legacy_nix_path_args: [])
|
|
304
|
+
args = ['--json', '--no-link']
|
|
305
|
+
args.concat(
|
|
306
|
+
nix_common_args(
|
|
307
|
+
args_builder.build_args,
|
|
308
|
+
extra_experimental: extra_experimental
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
args.concat(legacy_nix_path_args)
|
|
312
|
+
args.concat(installables)
|
|
313
|
+
args
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def nix_common_args(base_args, extra_experimental:)
|
|
317
|
+
args = []
|
|
318
|
+
if extra_experimental
|
|
319
|
+
args << '--extra-experimental-features' << 'nix-command'
|
|
320
|
+
args << '--extra-experimental-features' << 'flakes'
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
args.concat(base_args)
|
|
324
|
+
|
|
325
|
+
args << '--show-trace' if show_trace
|
|
326
|
+
|
|
327
|
+
if max_jobs
|
|
328
|
+
args << '--option' << 'max-jobs' << max_jobs.to_s
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
if cores
|
|
332
|
+
args << '--option' << 'cores' << cores.to_s
|
|
333
|
+
end
|
|
334
|
+
args
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def nix_attr_string(value)
|
|
338
|
+
escaped = value.to_s.gsub('\\', '\\\\').gsub('"', '\\"')
|
|
339
|
+
"\"#{escaped}\""
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def build_outputs_for_keys(machine_keys)
|
|
343
|
+
return {} if machine_keys.empty?
|
|
344
|
+
|
|
345
|
+
keys = machine_keys.uniq
|
|
346
|
+
apply = list_to_attrs_apply_expr(keys)
|
|
347
|
+
nix_eval_json('.#confctl.build', apply: apply)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def inputs_info_for_keys(machine_keys)
|
|
351
|
+
return {} if machine_keys.empty?
|
|
352
|
+
|
|
353
|
+
keys = machine_keys.uniq
|
|
354
|
+
apply = list_to_attrs_apply_expr(keys)
|
|
355
|
+
nix_eval_json('.#confctl.inputsInfo', apply: apply)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def inputs_for_keys(machine_keys)
|
|
359
|
+
return {} if machine_keys.empty?
|
|
360
|
+
|
|
361
|
+
keys = machine_keys.uniq
|
|
362
|
+
apply = list_to_attrs_apply_expr(keys)
|
|
363
|
+
nix_eval_json('.#confctl.inputs', apply: apply)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def list_to_attrs_apply_expr(keys)
|
|
367
|
+
list = keys.map { |k| nix_attr_string(k) }.join(' ')
|
|
368
|
+
"x: builtins.listToAttrs (map (k: { name = k; value = x.${k}; }) [ #{list} ])"
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def legacy_nix_path_args(hosts)
|
|
372
|
+
return [] if hosts.empty?
|
|
373
|
+
|
|
374
|
+
args_builder = nix_args(settings: confctl_settings)
|
|
375
|
+
return [] unless args_builder.legacy_nix_path?
|
|
376
|
+
|
|
377
|
+
unless args_builder.impure?
|
|
378
|
+
raise ConfCtl::Error, 'legacyNixPath requires impureEval'
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
merged_inputs = {}
|
|
382
|
+
host_keys = {}
|
|
383
|
+
hosts.each { |host| host_keys[host] = machine_key_for(host) }
|
|
384
|
+
inputs_by_key = inputs_for_keys(host_keys.values)
|
|
385
|
+
|
|
386
|
+
hosts.each do |host|
|
|
387
|
+
machine_key = host_keys[host]
|
|
388
|
+
inputs = inputs_by_key[machine_key] || nix_eval_json(inputs_installable(host))
|
|
389
|
+
|
|
390
|
+
inputs.each do |name, path|
|
|
391
|
+
next unless path.is_a?(String) && !path.empty?
|
|
392
|
+
|
|
393
|
+
key = name.to_s
|
|
394
|
+
if merged_inputs.has_key?(key) && merged_inputs[key] != path
|
|
395
|
+
raise ConfCtl::Error,
|
|
396
|
+
"legacyNixPath requires consistent input #{key} across hosts; build hosts separately"
|
|
397
|
+
end
|
|
398
|
+
merged_inputs[key] = path
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
args_builder.legacy_names.each_with_object([]) do |name, acc|
|
|
403
|
+
path = merged_inputs[name.to_s]
|
|
404
|
+
next unless path.is_a?(String) && !path.empty?
|
|
405
|
+
|
|
406
|
+
acc << '-I' << "#{name}=#{path}"
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def refresh_machine_key_maps(machines)
|
|
411
|
+
@machine_name_to_key = {}
|
|
412
|
+
@machine_key_to_name = {}
|
|
413
|
+
|
|
414
|
+
machines.each do |name, info|
|
|
415
|
+
key = info['key'] || info['machineKey'] || info['flakeKey'] || name
|
|
416
|
+
@machine_name_to_key[name] = key
|
|
417
|
+
@machine_key_to_name[key] = name
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def ensure_machine_key_maps
|
|
422
|
+
return if @machine_name_to_key && @machine_key_to_name
|
|
423
|
+
|
|
424
|
+
mapping = nix_eval_json('.#confctl.machineKeys')
|
|
425
|
+
@machine_name_to_key = mapping
|
|
426
|
+
@machine_key_to_name = mapping.to_h { |name, key| [key, name] }
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def machine_key_for(host)
|
|
430
|
+
ensure_machine_key_maps
|
|
431
|
+
|
|
432
|
+
if @machine_name_to_key.has_key?(host)
|
|
433
|
+
@machine_name_to_key[host]
|
|
434
|
+
elsif @machine_key_to_name.has_key?(host)
|
|
435
|
+
host
|
|
436
|
+
else
|
|
437
|
+
raise ConfCtl::Error, "Unknown machine #{host.inspect}"
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def no_update_lock_file_error?(message)
|
|
442
|
+
message.match?(/--no-update-lock-file/) && message.match?(/unknown|unrecognized|invalid|unsupported/i)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def experimental_error?(message)
|
|
446
|
+
message.match?(/experimental/i) && message.match?(/nix-command|flakes/i)
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
end
|
data/lib/confctl/nix_format.rb
CHANGED
|
@@ -80,7 +80,7 @@ module ConfCtl
|
|
|
80
80
|
'implement #to_nix()?'
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
pad("#{simple}#{
|
|
83
|
+
pad("#{simple}#{';' if semicolon}#{"\n" if nl}", indent:)
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
@@ -105,7 +105,7 @@ module ConfCtl
|
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
ret << pad("}#{
|
|
108
|
+
ret << pad("}#{';' if semicolon}#{"\n" if nl}")
|
|
109
109
|
ret
|
|
110
110
|
end
|
|
111
111
|
|
|
@@ -118,7 +118,7 @@ module ConfCtl
|
|
|
118
118
|
end
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
-
ret << pad("]#{
|
|
121
|
+
ret << pad("]#{';' if semicolon}#{"\n" if nl}")
|
|
122
122
|
end
|
|
123
123
|
end
|
|
124
124
|
end
|