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/command.rb
CHANGED
|
@@ -139,6 +139,11 @@ module ConfCtl::Cli
|
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
def determine_color
|
|
142
|
+
if gopts[:color] == 'auto' && ConfCtl::Ui.no_color?
|
|
143
|
+
Rainbow.enabled = false
|
|
144
|
+
return false
|
|
145
|
+
end
|
|
146
|
+
|
|
142
147
|
case gopts[:color]
|
|
143
148
|
when 'always'
|
|
144
149
|
Rainbow.enabled = true
|
|
@@ -152,7 +157,7 @@ module ConfCtl::Cli
|
|
|
152
157
|
end
|
|
153
158
|
|
|
154
159
|
def determine_pager
|
|
155
|
-
ENV.fetch('PAGER', nil) && ENV['PAGER'].strip != ''
|
|
160
|
+
ConfCtl::Ui.tty? && ENV.fetch('PAGER', nil) && ENV['PAGER'].strip != ''
|
|
156
161
|
end
|
|
157
162
|
|
|
158
163
|
def select_machines(pattern)
|
|
@@ -161,8 +166,25 @@ module ConfCtl::Cli
|
|
|
161
166
|
attr_filters = AttrFilters.new(opts[:attr])
|
|
162
167
|
tag_filters = TagFilters.new(opts[:tag])
|
|
163
168
|
|
|
169
|
+
if pattern.nil?
|
|
170
|
+
return machines.select do |_host, d|
|
|
171
|
+
attr_filters.pass?(d) && tag_filters.pass?(d)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
if machines[pattern]
|
|
176
|
+
return machines.select do |host, d|
|
|
177
|
+
host == pattern && attr_filters.pass?(d) && tag_filters.pass?(d)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
key_matches = machines.select do |_host, d|
|
|
182
|
+
d['key'] == pattern && attr_filters.pass?(d) && tag_filters.pass?(d)
|
|
183
|
+
end
|
|
184
|
+
return key_matches unless key_matches.empty?
|
|
185
|
+
|
|
164
186
|
machines.select do |host, d|
|
|
165
|
-
|
|
187
|
+
ConfCtl::Pattern.match?(pattern, host) \
|
|
166
188
|
&& attr_filters.pass?(d) \
|
|
167
189
|
&& tag_filters.pass?(d)
|
|
168
190
|
end
|
|
@@ -17,6 +17,106 @@ module ConfCtl::Cli
|
|
|
17
17
|
.gitignore].include?(v)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
init_common
|
|
21
|
+
|
|
22
|
+
if swpins_mode?
|
|
23
|
+
init_swpins
|
|
24
|
+
else
|
|
25
|
+
init_flake
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def add
|
|
30
|
+
require_args!('name')
|
|
31
|
+
|
|
32
|
+
name = args[0]
|
|
33
|
+
dir = File.join('cluster', name)
|
|
34
|
+
depth = name.count('/')
|
|
35
|
+
|
|
36
|
+
raise "#{dir} already exists" if Dir.exist?(dir)
|
|
37
|
+
|
|
38
|
+
mkdir_p(dir)
|
|
39
|
+
|
|
40
|
+
mkfile(File.join(dir, 'module.nix')) do |f|
|
|
41
|
+
channels_attr = flake_config? ? 'inputs.channels' : 'swpins.channels'
|
|
42
|
+
f.puts(<<~END
|
|
43
|
+
{ config, ... }:
|
|
44
|
+
{
|
|
45
|
+
cluster."#{name}" = {
|
|
46
|
+
spin = "nixos";
|
|
47
|
+
#{channels_attr} = [ "nixos-unstable" ];
|
|
48
|
+
host = { name = "machine"; domain = "example.com"; };
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
END
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
mkfile(File.join(dir, 'config.nix')) do |f|
|
|
56
|
+
f.puts(<<~END
|
|
57
|
+
{ config, pkgs, lib, ... }:
|
|
58
|
+
{
|
|
59
|
+
imports = [
|
|
60
|
+
#{'../' * (depth + 2)}environments/base.nix
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
# ... standard NixOS configuration ...
|
|
64
|
+
|
|
65
|
+
networking.hostName = "#{name.gsub('/', '-')}";
|
|
66
|
+
}
|
|
67
|
+
END
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
rediscover
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def rename
|
|
75
|
+
require_args!('old-name', 'new-name')
|
|
76
|
+
|
|
77
|
+
src = args[0]
|
|
78
|
+
dst = args[1]
|
|
79
|
+
|
|
80
|
+
src_path = File.join('cluster', src)
|
|
81
|
+
dst_path = File.join('cluster', dst)
|
|
82
|
+
|
|
83
|
+
if !Dir.exist?(src_path)
|
|
84
|
+
raise "'#{src}' not found"
|
|
85
|
+
elsif Dir.exist?(dst_path)
|
|
86
|
+
raise "'#{dst}' already exists"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
dst_dir = File.dirname(dst_path)
|
|
90
|
+
mkdir_p(dst_dir)
|
|
91
|
+
mv(src_path, dst_path)
|
|
92
|
+
|
|
93
|
+
src_swpins = File.join('swpins/cluster', "#{src.gsub('/', ':')}.json")
|
|
94
|
+
dst_swpins = File.join('swpins/cluster', "#{dst.gsub('/', ':')}.json")
|
|
95
|
+
mv(src_swpins, dst_swpins) if File.exist?(src_swpins)
|
|
96
|
+
|
|
97
|
+
rediscover
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def rediscover
|
|
101
|
+
hosts = discover_dir('cluster').sort
|
|
102
|
+
|
|
103
|
+
replace_file('cluster/cluster.nix') do |f|
|
|
104
|
+
f.puts('# This file is generated by confctl, changes will be lost')
|
|
105
|
+
f.puts('[')
|
|
106
|
+
|
|
107
|
+
hosts.each do |host|
|
|
108
|
+
f.puts(" ./#{host}/module.nix")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
f.puts(']')
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
ConfCtl::Hook.call(:configuration_rediscover)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
protected
|
|
118
|
+
|
|
119
|
+
def init_common
|
|
20
120
|
mkdir('cluster')
|
|
21
121
|
|
|
22
122
|
mkfile('cluster/module-list.nix') do |f|
|
|
@@ -53,40 +153,6 @@ module ConfCtl::Cli
|
|
|
53
153
|
)
|
|
54
154
|
end
|
|
55
155
|
|
|
56
|
-
mkfile('configs/swpins.nix') do |f|
|
|
57
|
-
f.puts(<<~END
|
|
58
|
-
{ config, ... }:
|
|
59
|
-
let
|
|
60
|
-
nixpkgsBranch = branch: {
|
|
61
|
-
type = "git-rev";
|
|
62
|
-
|
|
63
|
-
git-rev = {
|
|
64
|
-
url = "https://github.com/NixOS/nixpkgs";
|
|
65
|
-
update.ref = "refs/heads/${branch}";
|
|
66
|
-
};
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
vpsadminosBranch = branch: {
|
|
70
|
-
type = "git-rev";
|
|
71
|
-
|
|
72
|
-
git-rev = {
|
|
73
|
-
url = "https://github.com/vpsfreecz/vpsadminos";
|
|
74
|
-
update.ref = "refs/heads/${branch}";
|
|
75
|
-
};
|
|
76
|
-
};
|
|
77
|
-
in {
|
|
78
|
-
confctl.swpins.channels = {
|
|
79
|
-
nixos-unstable = { nixpkgs = nixpkgsBranch "nixos-unstable"; };
|
|
80
|
-
|
|
81
|
-
# nixos-stable = { nixpkgs = nixpkgsBranch "nixos-20.09"; };
|
|
82
|
-
|
|
83
|
-
# vpsadminos-staging = { vpsadminos = vpsadminosBranch "staging"; };
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
END
|
|
87
|
-
)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
156
|
mkdir('data')
|
|
91
157
|
|
|
92
158
|
mkfile('data/default.nix') do |f|
|
|
@@ -153,100 +219,99 @@ module ConfCtl::Cli
|
|
|
153
219
|
END
|
|
154
220
|
)
|
|
155
221
|
end
|
|
156
|
-
|
|
157
|
-
mkdir('swpins')
|
|
158
|
-
mkdir('swpins/channels')
|
|
159
222
|
end
|
|
160
223
|
|
|
161
|
-
def
|
|
162
|
-
|
|
224
|
+
def init_swpins
|
|
225
|
+
mkfile('configs/swpins.nix') do |f|
|
|
226
|
+
f.puts(<<~END
|
|
227
|
+
{ config, ... }:
|
|
228
|
+
let
|
|
229
|
+
nixpkgsBranch = branch: {
|
|
230
|
+
type = "git-rev";
|
|
163
231
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
232
|
+
git-rev = {
|
|
233
|
+
url = "https://github.com/NixOS/nixpkgs";
|
|
234
|
+
update.ref = "refs/heads/${branch}";
|
|
235
|
+
};
|
|
236
|
+
};
|
|
167
237
|
|
|
168
|
-
|
|
238
|
+
vpsadminosBranch = branch: {
|
|
239
|
+
type = "git-rev";
|
|
169
240
|
|
|
170
|
-
|
|
241
|
+
git-rev = {
|
|
242
|
+
url = "https://github.com/vpsfreecz/vpsadminos";
|
|
243
|
+
update.ref = "refs/heads/${branch}";
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
in {
|
|
247
|
+
confctl.swpins.channels = {
|
|
248
|
+
nixos-unstable = { nixpkgs = nixpkgsBranch "nixos-unstable"; };
|
|
171
249
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
{
|
|
176
|
-
cluster."#{name}" = {
|
|
177
|
-
spin = "nixos";
|
|
178
|
-
swpins.channels = [ "nixos-unstable" ];
|
|
179
|
-
host = { name = "machine"; domain = "example.com"; };
|
|
250
|
+
# nixos-stable = { nixpkgs = nixpkgsBranch "nixos-20.09"; };
|
|
251
|
+
|
|
252
|
+
# vpsadminos-staging = { vpsadminos = vpsadminosBranch "staging"; };
|
|
180
253
|
};
|
|
181
254
|
}
|
|
182
255
|
END
|
|
183
256
|
)
|
|
184
257
|
end
|
|
185
258
|
|
|
186
|
-
|
|
259
|
+
mkdir('swpins')
|
|
260
|
+
mkdir('swpins/channels')
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def init_flake
|
|
264
|
+
mkfile('flake.nix') do |f|
|
|
187
265
|
f.puts(<<~END
|
|
188
|
-
{ config, pkgs, lib, ... }:
|
|
189
266
|
{
|
|
190
|
-
|
|
191
|
-
#{'../' * (depth + 2)}environments/base.nix
|
|
192
|
-
];
|
|
267
|
+
description = "confctl configuration (flake)";
|
|
193
268
|
|
|
194
|
-
|
|
269
|
+
inputs = {
|
|
270
|
+
confctl.url = "github:vpsfreecz/confctl";
|
|
271
|
+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
195
272
|
|
|
196
|
-
|
|
273
|
+
# vpsadminos.url = "github:vpsfreecz/vpsadminos/staging";
|
|
274
|
+
# vpsadminos.inputs.nixpkgs.follows = "nixpkgs";
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
outputs = inputs@{ self, confctl, ... }:
|
|
278
|
+
let
|
|
279
|
+
channels = {
|
|
280
|
+
nixos-unstable = { nixpkgs = "nixpkgs"; };
|
|
281
|
+
|
|
282
|
+
# vpsadminos = {
|
|
283
|
+
# nixpkgs = "nixpkgs";
|
|
284
|
+
# vpsadminos = "vpsadminos";
|
|
285
|
+
# };
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
confctlOutputs = confctl.lib.mkConfctlOutputs {
|
|
289
|
+
confDir = ./.;
|
|
290
|
+
inherit inputs channels;
|
|
291
|
+
};
|
|
292
|
+
in
|
|
293
|
+
{
|
|
294
|
+
confctl = confctlOutputs;
|
|
295
|
+
|
|
296
|
+
devShells.x86_64-linux.default = confctl.lib.mkConfigDevShell {
|
|
297
|
+
system = "x86_64-linux";
|
|
298
|
+
mode = "minimal";
|
|
299
|
+
};
|
|
300
|
+
};
|
|
197
301
|
}
|
|
198
302
|
END
|
|
199
303
|
)
|
|
200
304
|
end
|
|
201
|
-
|
|
202
|
-
rediscover
|
|
203
305
|
end
|
|
204
306
|
|
|
205
|
-
def
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
src = args[0]
|
|
209
|
-
dst = args[1]
|
|
210
|
-
|
|
211
|
-
src_path = File.join('cluster', src)
|
|
212
|
-
dst_path = File.join('cluster', dst)
|
|
213
|
-
|
|
214
|
-
if !Dir.exist?(src_path)
|
|
215
|
-
raise "'#{src}' not found"
|
|
216
|
-
elsif Dir.exist?(dst_path)
|
|
217
|
-
raise "'#{dst}' already exists"
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
dst_dir = File.dirname(dst_path)
|
|
221
|
-
mkdir_p(dst_dir)
|
|
222
|
-
mv(src_path, dst_path)
|
|
223
|
-
|
|
224
|
-
src_swpins = File.join('swpins/cluster', "#{src.gsub('/', ':')}.json")
|
|
225
|
-
dst_swpins = File.join('swpins/cluster', "#{dst.gsub('/', ':')}.json")
|
|
226
|
-
mv(src_swpins, dst_swpins) if File.exist?(src_swpins)
|
|
227
|
-
|
|
228
|
-
rediscover
|
|
307
|
+
def swpins_mode?
|
|
308
|
+
opts[:swpins] || opts[:legacy]
|
|
229
309
|
end
|
|
230
310
|
|
|
231
|
-
def
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
replace_file('cluster/cluster.nix') do |f|
|
|
235
|
-
f.puts('# This file is generated by confctl, changes will be lost')
|
|
236
|
-
f.puts('[')
|
|
237
|
-
|
|
238
|
-
hosts.each do |host|
|
|
239
|
-
f.puts(" ./#{host}/module.nix")
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
f.puts(']')
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
ConfCtl::Hook.call(:configuration_rediscover)
|
|
311
|
+
def flake_config?
|
|
312
|
+
ConfCtl::ConfigType.flake?(ConfCtl::ConfDir.path)
|
|
246
313
|
end
|
|
247
314
|
|
|
248
|
-
protected
|
|
249
|
-
|
|
250
315
|
def discover_dir(dir_path, rel_path = nil)
|
|
251
316
|
ret = []
|
|
252
317
|
|
|
@@ -277,11 +342,11 @@ module ConfCtl::Cli
|
|
|
277
342
|
FileUtils.mkdir_p(name, mode: DIR_MODE)
|
|
278
343
|
end
|
|
279
344
|
|
|
280
|
-
def mkfile(name)
|
|
345
|
+
def mkfile(name, &block)
|
|
281
346
|
puts "mkfile #{name}"
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
347
|
+
File.open(name, 'w', FILE_MODE) do |f|
|
|
348
|
+
block.call(f)
|
|
349
|
+
end
|
|
285
350
|
end
|
|
286
351
|
|
|
287
352
|
def replace_file(name, &)
|
|
@@ -273,6 +273,30 @@ module ConfCtl::Cli
|
|
|
273
273
|
Rainbow('Collecting garbage on ').bright + Rainbow(machines.first.to_s).yellow
|
|
274
274
|
end
|
|
275
275
|
|
|
276
|
+
unless ConfCtl::Ui.tty?
|
|
277
|
+
puts header
|
|
278
|
+
|
|
279
|
+
executor = ConfCtl::ParallelExecutor.new(opts['max-concurrent-gc'])
|
|
280
|
+
|
|
281
|
+
machines.each do |host, machine|
|
|
282
|
+
executor.add do
|
|
283
|
+
end_stats = nil
|
|
284
|
+
ret = nix.collect_garbage(machine) do |progress|
|
|
285
|
+
puts "#{host}> #{progress}"
|
|
286
|
+
end_stats = progress.line if /^\d+ store paths deleted/ =~ progress.line
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
puts(ret ? "#{host}: #{end_stats || 'done'}" : "#{host}: error occurred")
|
|
290
|
+
ret ? nil : host
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
failed = executor.run.compact
|
|
295
|
+
raise "Gargabe collection failed on: #{failed.join(', ')}" if failed.any?
|
|
296
|
+
|
|
297
|
+
return
|
|
298
|
+
end
|
|
299
|
+
|
|
276
300
|
LogView.open(
|
|
277
301
|
header: "#{header}\n",
|
|
278
302
|
title: Rainbow('Live view').bright,
|
|
@@ -331,11 +355,28 @@ module ConfCtl::Cli
|
|
|
331
355
|
|
|
332
356
|
def list_generations(gens)
|
|
333
357
|
swpin_names = []
|
|
358
|
+
input_roles = []
|
|
334
359
|
|
|
335
360
|
gens.each do |gen|
|
|
336
361
|
gen.swpin_names.each do |name|
|
|
337
362
|
swpin_names << name unless swpin_names.include?(name)
|
|
338
363
|
end
|
|
364
|
+
|
|
365
|
+
next unless flake_generation?(gen)
|
|
366
|
+
|
|
367
|
+
inputs_info = normalized_inputs_info(gen)
|
|
368
|
+
next if inputs_info.nil? || inputs_info.empty?
|
|
369
|
+
|
|
370
|
+
inputs_info.each_key do |role|
|
|
371
|
+
input_roles << role unless input_roles.include?(role)
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
input_column_names = {}
|
|
376
|
+
input_columns = input_roles.map do |role|
|
|
377
|
+
name = swpin_names.include?(role) ? "input:#{role}" : role
|
|
378
|
+
input_column_names[role] = name
|
|
379
|
+
{ name: name, label: role.to_s.upcase }
|
|
339
380
|
end
|
|
340
381
|
|
|
341
382
|
rows = gens.map do |gen|
|
|
@@ -352,15 +393,38 @@ module ConfCtl::Cli
|
|
|
352
393
|
row[name] = spec.version
|
|
353
394
|
end
|
|
354
395
|
|
|
396
|
+
if flake_generation?(gen)
|
|
397
|
+
inputs_info = normalized_inputs_info(gen) || {}
|
|
398
|
+
input_roles.each do |role|
|
|
399
|
+
row[input_column_names[role]] = inputs_short_rev(inputs_info[role])
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
355
403
|
row
|
|
356
404
|
end
|
|
357
405
|
|
|
358
406
|
OutputFormatter.print(
|
|
359
407
|
rows,
|
|
360
|
-
%w[host name id presence current kernel] + swpin_names,
|
|
408
|
+
%w[host name id presence current kernel] + swpin_names + input_columns,
|
|
361
409
|
layout: :columns,
|
|
362
410
|
sort: %w[name host]
|
|
363
411
|
)
|
|
364
412
|
end
|
|
413
|
+
|
|
414
|
+
def normalized_inputs_info(gen)
|
|
415
|
+
return nil unless gen.respond_to?(:inputs_info)
|
|
416
|
+
|
|
417
|
+
ConfCtl::InputsInfo.normalize(gen.inputs_info)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def inputs_short_rev(info)
|
|
421
|
+
return nil unless info.is_a?(Hash)
|
|
422
|
+
|
|
423
|
+
info['shortRev'] || (info['rev'] && info['rev'][0, 8])
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def flake_generation?(gen)
|
|
427
|
+
gen.respond_to?(:flakes_mode?) && gen.flakes_mode?
|
|
428
|
+
end
|
|
365
429
|
end
|
|
366
430
|
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
require 'confctl/cli/command'
|
|
2
|
+
require 'confctl/config_type'
|
|
3
|
+
require 'confctl/flake_lock'
|
|
4
|
+
require 'confctl/nix'
|
|
5
|
+
require 'confctl/pattern'
|
|
6
|
+
require 'confctl/inputs/setter'
|
|
7
|
+
require 'confctl/inputs/updater'
|
|
8
|
+
|
|
9
|
+
module ConfCtl::Cli
|
|
10
|
+
class Inputs::Channels < Command
|
|
11
|
+
def list
|
|
12
|
+
ensure_flake_config!
|
|
13
|
+
|
|
14
|
+
raise GLI::BadCommandLine, 'usage: confctl inputs channel ls [channel-pattern]' if args.length > 1
|
|
15
|
+
|
|
16
|
+
conf_dir = ConfCtl::ConfDir.path
|
|
17
|
+
lock = ConfCtl::FlakeLock.load(File.join(conf_dir, 'flake.lock'))
|
|
18
|
+
channels = eval_channels
|
|
19
|
+
|
|
20
|
+
selected = select_channels(channels.keys, args[0])
|
|
21
|
+
|
|
22
|
+
rows = []
|
|
23
|
+
selected.each do |ch|
|
|
24
|
+
(channels[ch] || {}).each do |role, input|
|
|
25
|
+
info = lock.input_info(input)
|
|
26
|
+
rows << {
|
|
27
|
+
channel: ch,
|
|
28
|
+
role: role,
|
|
29
|
+
input: input,
|
|
30
|
+
rev: info[:short_rev] || '-',
|
|
31
|
+
url: info[:url] || '-'
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
OutputFormatter.print(rows, %i[channel role input rev url], layout: :columns)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def update
|
|
40
|
+
ensure_flake_config!
|
|
41
|
+
require_args!('channels', optional: ['role'])
|
|
42
|
+
|
|
43
|
+
selector = args[0]
|
|
44
|
+
role = args[1]
|
|
45
|
+
role_name = role&.to_s
|
|
46
|
+
|
|
47
|
+
channels = eval_channels
|
|
48
|
+
selected = select_channels(channels.keys, selector)
|
|
49
|
+
raise ConfCtl::Error, "no channels matched '#{selector}'" if selected.empty?
|
|
50
|
+
|
|
51
|
+
targets = selected.flat_map do |ch|
|
|
52
|
+
mapping = channels[ch] || {}
|
|
53
|
+
if role_name
|
|
54
|
+
input = mapping[role] || mapping[role_name]
|
|
55
|
+
input ? [{ channel: ch, role: role_name, input: input }] : []
|
|
56
|
+
else
|
|
57
|
+
mapping.map { |r, input| { channel: ch, role: r.to_s, input: input } }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
inputs = targets.map { |t| t[:input] }.uniq
|
|
62
|
+
|
|
63
|
+
raise ConfCtl::Error, 'no inputs selected (check role name?)' if inputs.empty?
|
|
64
|
+
|
|
65
|
+
ConfCtl::Inputs::Updater.run!(
|
|
66
|
+
conf_dir: ConfCtl::ConfDir.path,
|
|
67
|
+
inputs: inputs,
|
|
68
|
+
commit: opts[:commit],
|
|
69
|
+
changelog: opts[:changelog],
|
|
70
|
+
downgrade: opts[:downgrade],
|
|
71
|
+
editor: opts[:editor]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
lock = ConfCtl::FlakeLock.load(File.join(ConfCtl::ConfDir.path, 'flake.lock'))
|
|
75
|
+
targets.each do |t|
|
|
76
|
+
info = lock.input_info(t[:input])
|
|
77
|
+
rev = info[:short_rev] || info[:rev] || '-'
|
|
78
|
+
puts "Updating #{t[:role]} in #{t[:channel]} -> #{rev}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def set
|
|
83
|
+
ensure_flake_config!
|
|
84
|
+
require_args!('channels', 'role', 'rev')
|
|
85
|
+
|
|
86
|
+
selector = args[0]
|
|
87
|
+
role = args[1]
|
|
88
|
+
rev = args[2]
|
|
89
|
+
|
|
90
|
+
channels = eval_channels
|
|
91
|
+
selected = select_channels(channels.keys, selector)
|
|
92
|
+
raise ConfCtl::Error, "no channels matched '#{selector}'" if selected.empty?
|
|
93
|
+
|
|
94
|
+
role_name = role.to_s
|
|
95
|
+
targets = selected.filter_map do |ch|
|
|
96
|
+
mapping = channels[ch] || {}
|
|
97
|
+
input = mapping[role] || mapping[role_name]
|
|
98
|
+
input ? { channel: ch, role: role_name, input: input } : nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
raise ConfCtl::Error, 'no inputs selected (check role name?)' if targets.empty?
|
|
102
|
+
|
|
103
|
+
inputs = targets.map { |t| t[:input] }.uniq
|
|
104
|
+
|
|
105
|
+
shared = shared_input_uses(channels, selected, role_name, inputs)
|
|
106
|
+
if shared.any?
|
|
107
|
+
msg = build_shared_input_message(shared)
|
|
108
|
+
raise ConfCtl::Error, "#{msg} (use --allow-shared to proceed)" unless opts[:allow_shared]
|
|
109
|
+
|
|
110
|
+
puts "Warning: #{msg}"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
ConfCtl::Inputs::Setter.run!(
|
|
114
|
+
conf_dir: ConfCtl::ConfDir.path,
|
|
115
|
+
inputs: inputs,
|
|
116
|
+
rev: rev,
|
|
117
|
+
commit: opts[:commit],
|
|
118
|
+
changelog: opts[:changelog],
|
|
119
|
+
downgrade: opts[:downgrade],
|
|
120
|
+
editor: opts[:editor]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
lock = ConfCtl::FlakeLock.load(File.join(ConfCtl::ConfDir.path, 'flake.lock'))
|
|
124
|
+
targets.each do |t|
|
|
125
|
+
info = lock.input_info(t[:input])
|
|
126
|
+
resolved_rev = info[:short_rev] || info[:rev] || '-'
|
|
127
|
+
puts "Configuring #{t[:role]} in #{t[:channel]} -> #{resolved_rev}"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
protected
|
|
132
|
+
|
|
133
|
+
def ensure_flake_config!
|
|
134
|
+
return if ConfCtl::ConfigType.flake?(ConfCtl::ConfDir.path)
|
|
135
|
+
|
|
136
|
+
raise ConfCtl::Error, 'confctl inputs channel is available only in flake configs'
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def eval_channels
|
|
140
|
+
json = ConfCtl::Nix.new.eval_json('.#confctl.channels')
|
|
141
|
+
json.is_a?(Hash) ? json : {}
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def select_channels(all, selector)
|
|
145
|
+
return all.sort if selector.nil?
|
|
146
|
+
|
|
147
|
+
if selector.include?('{') || selector.include?(',')
|
|
148
|
+
names = parse_channel_selectors(selector)
|
|
149
|
+
return names.select { |n| all.include?(n) }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
all.select { |n| ConfCtl::Pattern.match?(selector, n) }.sort
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def parse_channel_selectors(str)
|
|
156
|
+
s = str.strip
|
|
157
|
+
s = s[1..-2] if s.start_with?('{') && s.end_with?('}')
|
|
158
|
+
s.split(',').map(&:strip).reject(&:empty?)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def shared_input_uses(channels, selected, role, inputs)
|
|
162
|
+
requested = {}
|
|
163
|
+
selected.each { |ch| requested[[ch, role.to_s]] = true }
|
|
164
|
+
shared = {}
|
|
165
|
+
|
|
166
|
+
channels.each do |ch, mapping|
|
|
167
|
+
(mapping || {}).each do |r, input|
|
|
168
|
+
next unless inputs.include?(input)
|
|
169
|
+
|
|
170
|
+
key = [ch, r.to_s]
|
|
171
|
+
next if requested[key]
|
|
172
|
+
|
|
173
|
+
shared[input] ||= []
|
|
174
|
+
shared[input] << { channel: ch, role: r.to_s }
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
shared
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def build_shared_input_message(shared)
|
|
182
|
+
details = shared.sort.map do |input, uses|
|
|
183
|
+
roles = uses.map { |u| "#{u[:channel]}/#{u[:role]}" }.sort.join(', ')
|
|
184
|
+
"#{input} (also used by #{roles})"
|
|
185
|
+
end.join('; ')
|
|
186
|
+
|
|
187
|
+
"selected input(s) are shared outside the requested channel/role: #{details}"
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|