confctl 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,26 +1,114 @@
1
1
  module ConfCtl
2
2
  class Machine
3
- attr_reader :name, :safe_name, :managed, :spin, :opts
3
+ CarriedMachine = Struct.new(
4
+ :carrier,
5
+ :name,
6
+ :alias,
7
+ :attribute,
8
+ keyword_init: true
9
+ )
10
+
11
+ # @return [String]
12
+ attr_reader :name
13
+
14
+ # @return [String]
15
+ attr_reader :safe_name
16
+
17
+ # @return [Boolean]
18
+ attr_reader :managed
19
+
20
+ # @return [String]
21
+ attr_reader :spin
22
+
23
+ # @return [String]
24
+ attr_reader :carrier_name
25
+
26
+ # @return [String]
27
+ attr_reader :cluster_name
28
+
29
+ # @return [String]
30
+ attr_reader :safe_cluster_name
31
+
32
+ # Alias for this machine on the carrier
33
+ # @return [String]
34
+ attr_reader :carried_alias
35
+
36
+ # Alias for this machine on the carrier
37
+ # @return [String]
38
+ attr_reader :safe_carried_alias
39
+
40
+ # @return [Hash] machine metadata
41
+ attr_reader :meta
4
42
 
5
43
  # @param opts [Hash]
6
- def initialize(opts)
7
- @opts = opts
44
+ # @param machine_list [MachineList]
45
+ def initialize(opts, machine_list:)
46
+ @meta = opts['metaConfig']
8
47
  @name = opts['name']
9
- @safe_name = opts['name'].gsub('/', ':')
10
- @managed = opts['managed']
11
- @spin = opts['spin']
48
+ @safe_name = name.gsub('/', ':')
49
+ @managed = meta['managed']
50
+ @spin = meta['spin']
51
+ @is_carrier = meta.fetch('carrier', {}).fetch('enable', false)
52
+ @carrier_name = opts['carrier']
53
+ @cluster_name = opts['clusterName']
54
+ @safe_cluster_name = cluster_name.gsub('/', ':')
55
+ @carried_alias = opts['alias'] || @cluster_name
56
+ @safe_carried_alias = @carried_alias.gsub('/', ':')
57
+ @machine_list = machine_list
58
+ end
59
+
60
+ # True if this machine carries other machines
61
+ def carrier?
62
+ @is_carrier
63
+ end
64
+
65
+ # @return [Array<CarriedMachine>]
66
+ def carried_machines
67
+ meta.fetch('carrier', {}).fetch('machines', []).map do |m|
68
+ CarriedMachine.new(
69
+ carrier: self,
70
+ name: m['machine'],
71
+ alias: m['alias'] || m['machine'],
72
+ attribute: m['attribute']
73
+ )
74
+ end
75
+ end
76
+
77
+ # True if this machine is on a carrier
78
+ def carried?
79
+ !@carrier_name.nil?
80
+ end
81
+
82
+ # @return [Machine] carrier
83
+ def carrier_machine
84
+ carrier = @machine_list[@carrier_name]
85
+
86
+ if carrier.nil?
87
+ raise "Carrier #{@carrier_name} not found in machine list"
88
+ end
89
+
90
+ carrier
12
91
  end
13
92
 
14
93
  def target_host
15
- (opts['host'] && opts['host']['target']) || name
94
+ meta.fetch('host', {}).fetch('target', name)
16
95
  end
17
96
 
18
97
  def localhost?
19
98
  target_host == 'localhost'
20
99
  end
21
100
 
101
+ # @return [String] path to nix-env managed profile
102
+ def profile
103
+ if carried?
104
+ "/nix/var/nix/profiles/confctl-#{safe_carried_alias}"
105
+ else
106
+ '/nix/var/nix/profiles/system'
107
+ end
108
+ end
109
+
22
110
  def nix_paths
23
- opts['nix']['nixPath'].to_h do |v|
111
+ meta['nix']['nixPath'].to_h do |v|
24
112
  eq = v.index('=')
25
113
  raise "'#{v}' is not a valid nix path entry " if eq.nil?
26
114
 
@@ -33,7 +121,7 @@ module ConfCtl
33
121
 
34
122
  @health_checks = []
35
123
 
36
- opts['healthChecks'].each do |type, checks|
124
+ meta['healthChecks'].each do |type, checks|
37
125
  case type
38
126
  when 'systemd'
39
127
  next if !checks['enable'] || spin != 'nixos'
@@ -76,11 +164,13 @@ module ConfCtl
76
164
 
77
165
  def [](key)
78
166
  if key.index('.')
79
- get(opts, key.split('.'))
167
+ get(meta, key.split('.'))
168
+ elsif key == 'name'
169
+ name
80
170
  elsif key == 'checks'
81
171
  health_checks.length
82
172
  else
83
- opts[key]
173
+ meta[key]
84
174
  end
85
175
  end
86
176
 
@@ -108,6 +108,13 @@ module ConfCtl
108
108
  out.strip
109
109
  end
110
110
 
111
+ # @param path [String]
112
+ # @return [String]
113
+ def read_realpath(path)
114
+ out, = run_cmd('realpath', path)
115
+ out.strip
116
+ end
117
+
111
118
  # Execute command, raises exception on error
112
119
  # @yieldparam out [String]
113
120
  # @yieldparam err [String]
@@ -46,6 +46,12 @@ module ConfCtl
46
46
  machines.map(&)
47
47
  end
48
48
 
49
+ # @yieldparam [Machine] machine
50
+ # @return [Hash]
51
+ def transform_values(&)
52
+ machines.transform_values(&)
53
+ end
54
+
49
55
  # @return [MachineList]
50
56
  def managed
51
57
  select { |_host, machine| machine.managed }
@@ -56,6 +62,11 @@ module ConfCtl
56
62
  select { |_host, machine| !machine.managed }
57
63
  end
58
64
 
65
+ # @return [MachineList]
66
+ def runnable
67
+ select { |_host, machine| !machine.carried? && machine.target_host }
68
+ end
69
+
59
70
  # @param host [String]
60
71
  def [](host)
61
72
  @machines[host]
@@ -84,6 +95,8 @@ module ConfCtl
84
95
  checks = []
85
96
 
86
97
  machines.each_value do |machine|
98
+ next if machine.carried?
99
+
87
100
  checks.concat(machine.health_checks)
88
101
  end
89
102
 
@@ -101,7 +114,7 @@ module ConfCtl
101
114
 
102
115
  def parse(data)
103
116
  data.transform_values do |info|
104
- Machine.new(info)
117
+ Machine.new(info, machine_list: self)
105
118
  end
106
119
  end
107
120
  end
@@ -1,3 +1,4 @@
1
+ require 'json'
1
2
  require 'tty-command'
2
3
 
3
4
  module ConfCtl
@@ -81,7 +82,7 @@ module ConfCtl
81
82
  # @param machine [Machine]
82
83
  def initialize(machine)
83
84
  @machine = machine
84
- @mc = MachineControl.new(machine)
85
+ @mc = MachineControl.new(machine.carried? ? machine.carrier_machine : machine)
85
86
  end
86
87
 
87
88
  # Connect to the machine and query its state
@@ -94,7 +95,7 @@ module ConfCtl
94
95
 
95
96
  if toplevel
96
97
  begin
97
- @current_toplevel = mc.read_symlink('/run/current-system')
98
+ @current_toplevel = query_toplevel
98
99
  rescue TTY::Command::ExitError
99
100
  return
100
101
  end
@@ -102,14 +103,14 @@ module ConfCtl
102
103
 
103
104
  if generations
104
105
  begin
105
- @generations = Generation::HostList.fetch(mc)
106
+ @generations = Generation::HostList.fetch(mc, profile: machine.profile)
106
107
  rescue TTY::Command::ExitError
107
108
  return
108
109
  end
109
110
  end
110
111
 
111
112
  begin
112
- @swpins_info = Swpins::DeployedInfo.parse!(mc.read_file('/etc/confctl/swpins-info.json'))
113
+ @swpins_info = query_swpins
113
114
  rescue Error
114
115
  nil
115
116
  end
@@ -131,5 +132,51 @@ module ConfCtl
131
132
  protected
132
133
 
133
134
  attr_reader :mc
135
+
136
+ def query_toplevel
137
+ path =
138
+ if machine.carried?
139
+ machine.profile
140
+ else
141
+ '/run/current-system'
142
+ end
143
+
144
+ mc.read_realpath(path)
145
+ end
146
+
147
+ def query_swpins
148
+ json =
149
+ if machine.carried?
150
+ query_carried_swpins
151
+ else
152
+ mc.read_file('/etc/confctl/swpins-info.json')
153
+ end
154
+
155
+ case json
156
+ when String
157
+ Swpins::DeployedInfo.parse!(json)
158
+ when Hash
159
+ json
160
+ end
161
+ end
162
+
163
+ # @return [Hash, String]
164
+ def query_carried_swpins
165
+ begin
166
+ json = mc.read_file(File.join(machine.profile, 'machine.json'))
167
+ parsed = JSON.parse(json)
168
+ return parsed['swpins-info'] if parsed['swpins-info']
169
+ rescue TTY::Command::ExitError
170
+ # pass
171
+ end
172
+
173
+ begin
174
+ return mc.read_file(File.join(machine.profile, '/etc/confctl/swpins-info.json'))
175
+ rescue TTY::Command::ExitError
176
+ # pass
177
+ end
178
+
179
+ nil
180
+ end
134
181
  end
135
182
  end
data/lib/confctl/nix.rb CHANGED
@@ -15,10 +15,11 @@ module ConfCtl
15
15
 
16
16
  include Utils::File
17
17
 
18
- def initialize(conf_dir: nil, show_trace: false, max_jobs: nil)
18
+ def initialize(conf_dir: nil, show_trace: false, max_jobs: nil, cores: nil)
19
19
  @conf_dir = conf_dir || ConfDir.path
20
20
  @show_trace = show_trace
21
21
  @max_jobs = max_jobs || Settings.instance.max_jobs
22
+ @cores = cores
22
23
  @cmd = SystemCommand.new
23
24
  end
24
25
 
@@ -40,6 +41,7 @@ module ConfCtl
40
41
  '--out-link', out_link,
41
42
  (show_trace ? '--show-trace' : nil),
42
43
  (max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
44
+ (cores ? ['--cores', cores.to_s] : nil),
43
45
  ConfCtl.nix_asset('evaluator.nix')
44
46
  ].flatten.compact
45
47
 
@@ -88,6 +90,7 @@ module ConfCtl
88
90
  '--out-link', out_link,
89
91
  (show_trace ? '--show-trace' : nil),
90
92
  (max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
93
+ (cores ? ['--cores', cores.to_s] : nil),
91
94
  ConfCtl.nix_asset('evaluator.nix')
92
95
  ].flatten.compact
93
96
 
@@ -124,6 +127,7 @@ module ConfCtl
124
127
  '--out-link', out_link,
125
128
  (show_trace ? '--show-trace' : nil),
126
129
  (max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
130
+ (cores ? ['--cores', cores.to_s] : nil),
127
131
  ConfCtl.nix_asset('evaluator.nix')
128
132
  ].flatten.compact
129
133
 
@@ -154,6 +158,7 @@ module ConfCtl
154
158
  '--out-link', out_link,
155
159
  (show_trace ? '--show-trace' : nil),
156
160
  (max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
161
+ (cores ? ['--cores', cores.to_s] : nil),
157
162
  ConfCtl.nix_asset('evaluator.nix')
158
163
  ].flatten.compact
159
164
 
@@ -165,7 +170,7 @@ module ConfCtl
165
170
 
166
171
  # Build config.system.build.toplevel for selected hosts
167
172
  #
168
- # @param hosts [Array<String>]
173
+ # @param hosts [Array<Machine>]
169
174
  # @param swpin_paths [Hash]
170
175
  # @param host_swpin_specs [Hash]
171
176
  # @param time [Time]
@@ -176,7 +181,7 @@ module ConfCtl
176
181
  # @yieldparam path [String]
177
182
  #
178
183
  # @return [Hash<String, Generation::Build>]
179
- def build_toplevels(hosts: [], swpin_paths: {}, host_swpin_specs: {}, time: nil, &block)
184
+ def build_attributes(hosts: [], swpin_paths: {}, host_swpin_specs: {}, time: nil, &block)
180
185
  with_argument({
181
186
  confDir: conf_dir,
182
187
  build: :toplevel,
@@ -191,6 +196,7 @@ module ConfCtl
191
196
  '--out-link', out_link,
192
197
  (show_trace ? '--show-trace' : nil),
193
198
  (max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
199
+ (cores ? ['--cores', cores.to_s] : nil),
194
200
  ConfCtl.nix_asset('evaluator.nix')
195
201
  ].flatten.compact
196
202
 
@@ -236,6 +242,9 @@ module ConfCtl
236
242
  def copy(machine, toplevel, &)
237
243
  if machine.localhost?
238
244
  true
245
+ elsif machine.carried?
246
+ cp = NixCopy.new(machine.carrier_machine.target_host, toplevel)
247
+ cp.run!(&).success?
239
248
  else
240
249
  cp = NixCopy.new(machine.target_host, toplevel)
241
250
  cp.run!(&).success?
@@ -258,13 +267,26 @@ module ConfCtl
258
267
  def set_profile(machine, toplevel)
259
268
  args = [
260
269
  'nix-env',
261
- '-p', '/nix/var/nix/profiles/system',
270
+ '-p', machine.profile,
262
271
  '--set', toplevel
263
272
  ]
264
273
 
265
274
  MachineControl.new(machine).execute!(*args).success?
266
275
  end
267
276
 
277
+ # @param machine [Machine]
278
+ # @param toplevel [String]
279
+ # @return [Boolean]
280
+ def set_carried_profile(machine, toplevel)
281
+ args = [
282
+ 'carrier-env',
283
+ '-p', machine.profile,
284
+ '--set', toplevel
285
+ ]
286
+
287
+ MachineControl.new(machine.carrier_machine).execute!(*args).success?
288
+ end
289
+
268
290
  # @param packages [Array<String>]
269
291
  # @param command [String]
270
292
  # @return [Boolean]
@@ -292,7 +314,7 @@ module ConfCtl
292
314
 
293
315
  protected
294
316
 
295
- attr_reader :conf_dir, :show_trace, :max_jobs, :cmd
317
+ attr_reader :conf_dir, :show_trace, :max_jobs, :cores, :cmd
296
318
 
297
319
  # Execute block only if `out_link` does not exist or conf dir has changed
298
320
  # @param out_link [String] out link path
@@ -357,6 +379,7 @@ module ConfCtl
357
379
  '--arg', 'jsonArg', arg,
358
380
  (show_trace ? '--show-trace' : nil),
359
381
  (max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
382
+ (cores ? ['--cores', cores.to_s] : nil),
360
383
  ConfCtl.nix_asset('evaluator.nix')
361
384
  ].flatten.compact
362
385
 
@@ -103,7 +103,7 @@ module ConfCtl
103
103
 
104
104
  def prefetch_github(ref)
105
105
  mirror = GitRepoMirror.new(nix_opts['url'])
106
- mirror.setup
106
+ mirror.setup(ref:)
107
107
 
108
108
  rev = mirror.revision_parse(ref)
109
109
  url = File.join(nix_opts['url'], 'archive', "#{rev}.tar.gz")
@@ -1,3 +1,3 @@
1
1
  module ConfCtl
2
- VERSION = '1.0.0'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
  end
@@ -1,4 +1,4 @@
1
- .TH confctl\-options.nix 8 2024\-02\-17 master
1
+ .TH confctl\-options.nix 8 2024\-05\-07 master
2
2
  .SH NAME
3
3
  .PP
4
4
  \fB\fCconfctl\-options.nix\fR \- confctl configuration documentation
@@ -599,6 +599,22 @@ Address with prefix as string
599
599
  .PP
600
600
  \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
601
601
  .TP
602
+ \fB\fCcluster.<name>.buildAttribute\fR
603
+ Path to the attribute in machine system config that should be built
604
+ .IP
605
+ For example, \fB\fC[ "system" "build" "toplevel" ]\fR will select attribute
606
+ \fB\fCconfig.system.build.toplevel\fR\&.
607
+ .PP
608
+ \fIType:\fP list of string
609
+ .PP
610
+ \fIDefault:\fP \fB\fC[
611
+ "system"
612
+ "build"
613
+ "toplevel"
614
+ ]\fR
615
+ .PP
616
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
617
+ .TP
602
618
  \fB\fCcluster.<name>.buildGenerations.max\fR
603
619
  The maximum number of build generations to be kept on the build
604
620
  machine.
@@ -632,6 +648,151 @@ machine.
632
648
  .PP
633
649
  \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
634
650
  .TP
651
+ \fB\fCcluster.<name>.carrier.enable\fR
652
+ Whether to enable This machine is a carrier for other machines.
653
+ .PP
654
+ \fIType:\fP boolean
655
+ .PP
656
+ \fIDefault:\fP \fB\fCfalse\fR
657
+ .PP
658
+ \fIExample:\fP \fB\fCtrue\fR
659
+ .PP
660
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
661
+ .TP
662
+ \fB\fCcluster.<name>.carrier.machines\fR
663
+ List of carried machines
664
+ .PP
665
+ \fIType:\fP list of (submodule)
666
+ .PP
667
+ \fIDefault:\fP \fB\fC[ ]\fR
668
+ .PP
669
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
670
+ .TP
671
+ \fB\fCcluster.<name>.carrier.machines.*.alias\fR
672
+ Alias for carried machine name
673
+ .PP
674
+ \fIType:\fP null or string
675
+ .PP
676
+ \fIDefault:\fP \fB\fCnull\fR
677
+ .PP
678
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
679
+ .TP
680
+ \fB\fCcluster.<name>.carrier.machines.*.buildAttribute\fR
681
+ Path to the attribute in machine system config that should be built
682
+ .IP
683
+ For example, \fB\fC[ "system" "build" "toplevel" ]\fR will select attribute
684
+ \fB\fCconfig.system.build.toplevel\fR\&.
685
+ .PP
686
+ \fIType:\fP list of string
687
+ .PP
688
+ \fIDefault:\fP \fB\fC[
689
+ "system"
690
+ "build"
691
+ "toplevel"
692
+ ]\fR
693
+ .PP
694
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
695
+ .TP
696
+ \fB\fCcluster.<name>.carrier.machines.*.buildGenerations.max\fR
697
+ The maximum number of build generations to be kept on the build
698
+ machine.
699
+ .PP
700
+ \fIType:\fP null or signed integer
701
+ .PP
702
+ \fIDefault:\fP \fB\fCnull\fR
703
+ .PP
704
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
705
+ .TP
706
+ \fB\fCcluster.<name>.carrier.machines.*.buildGenerations.maxAge\fR
707
+ Delete build generations older than
708
+ \fB\fCcluster.<name>.carrier.machines.*.buildGenerations.maxAge\fR
709
+ seconds from the build machine. Old generations are deleted even
710
+ if \fB\fCcluster.<name>.carrier.machines.*.buildGenerations.max\fR is
711
+ not reached.
712
+ .PP
713
+ \fIType:\fP null or signed integer
714
+ .PP
715
+ \fIDefault:\fP \fB\fCnull\fR
716
+ .PP
717
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
718
+ .TP
719
+ \fB\fCcluster.<name>.carrier.machines.*.buildGenerations.min\fR
720
+ The minimum number of build generations to be kept on the build
721
+ machine.
722
+ .PP
723
+ \fIType:\fP null or signed integer
724
+ .PP
725
+ \fIDefault:\fP \fB\fCnull\fR
726
+ .PP
727
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
728
+ .TP
729
+ \fB\fCcluster.<name>.carrier.machines.*.extraModules\fR
730
+ A list of additional NixOS modules to be imported for this machine
731
+ .PP
732
+ \fIType:\fP list of path
733
+ .PP
734
+ \fIDefault:\fP \fB\fC[ ]\fR
735
+ .PP
736
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
737
+ .TP
738
+ \fB\fCcluster.<name>.carrier.machines.*.hostGenerations.max\fR
739
+ The maximum number of generations to be kept on the machine.
740
+ .PP
741
+ \fIType:\fP null or signed integer
742
+ .PP
743
+ \fIDefault:\fP \fB\fCnull\fR
744
+ .PP
745
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
746
+ .TP
747
+ \fB\fCcluster.<name>.carrier.machines.*.hostGenerations.maxAge\fR
748
+ Delete generations older than
749
+ \fB\fCcluster.<name>.carrier.machines.*.hostGenerations.maxAge\fR
750
+ seconds from the machine. Old generations are deleted even
751
+ if \fB\fCcluster.<name>.carrier.machines.*.hostGenerations.max\fR is
752
+ not reached.
753
+ .PP
754
+ \fIType:\fP null or signed integer
755
+ .PP
756
+ \fIDefault:\fP \fB\fCnull\fR
757
+ .PP
758
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
759
+ .TP
760
+ \fB\fCcluster.<name>.carrier.machines.*.hostGenerations.min\fR
761
+ The minimum number of generations to be kept on the machine.
762
+ .PP
763
+ \fIType:\fP null or signed integer
764
+ .PP
765
+ \fIDefault:\fP \fB\fCnull\fR
766
+ .PP
767
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
768
+ .TP
769
+ \fB\fCcluster.<name>.carrier.machines.*.labels\fR
770
+ Optional user\-defined labels to classify the machine
771
+ .PP
772
+ \fIType:\fP attribute set
773
+ .PP
774
+ \fIDefault:\fP \fB\fC{ }\fR
775
+ .PP
776
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
777
+ .TP
778
+ \fB\fCcluster.<name>.carrier.machines.*.machine\fR
779
+ Machine name
780
+ .PP
781
+ \fIType:\fP string
782
+ .PP
783
+ \fIDefault:\fP \fB\fCnull\fR
784
+ .PP
785
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
786
+ .TP
787
+ \fB\fCcluster.<name>.carrier.machines.*.tags\fR
788
+ Optional user\-defined tags to classify the machine
789
+ .PP
790
+ \fIType:\fP list of string
791
+ .PP
792
+ \fIDefault:\fP \fB\fC[ ]\fR
793
+ .PP
794
+ \fIDeclared by:\fP \fB\fC<confctl/nix/modules/cluster>\fR
795
+ .TP
635
796
  \fB\fCcluster.<name>.healthChecks.builderCommands\fR
636
797
  Check commands run on the build machine
637
798
  .PP
@@ -1032,6 +1193,9 @@ Host name
1032
1193
  .TP
1033
1194
  \fB\fCcluster.<name>.host.target\fR
1034
1195
  Address/host to which the configuration is deployed to
1196
+ .IP
1197
+ Set to null if the machine is not deployable, e.g. when it is only used
1198
+ as a carried machine.
1035
1199
  .PP
1036
1200
  \fIType:\fP null or string
1037
1201
  .PP