confctl 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +11 -0
- data/.gitignore +8 -0
- data/.overcommit.yml +6 -0
- data/.rubocop.yml +67 -0
- data/.rubocop_todo.yml +5 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +674 -0
- data/README.md +522 -0
- data/Rakefile +40 -0
- data/bin/confctl +4 -0
- data/confctl.gemspec +33 -0
- data/example/.gitignore +2 -0
- data/example/README.md +38 -0
- data/example/cluster/cluster.nix +7 -0
- data/example/cluster/module-list.nix +3 -0
- data/example/cluster/nixos-machine/config.nix +15 -0
- data/example/cluster/nixos-machine/hardware.nix +4 -0
- data/example/cluster/nixos-machine/module.nix +8 -0
- data/example/cluster/vpsadminos-container/config.nix +22 -0
- data/example/cluster/vpsadminos-container/module.nix +8 -0
- data/example/cluster/vpsadminos-machine/config.nix +22 -0
- data/example/cluster/vpsadminos-machine/hardware.nix +4 -0
- data/example/cluster/vpsadminos-machine/module.nix +8 -0
- data/example/cluster/vpsfreecz-vps/config.nix +25 -0
- data/example/cluster/vpsfreecz-vps/module.nix +8 -0
- data/example/configs/confctl.nix +10 -0
- data/example/configs/swpins.nix +28 -0
- data/example/data/default.nix +5 -0
- data/example/data/ssh-keys.nix +7 -0
- data/example/environments/base.nix +13 -0
- data/example/modules/module-list.nix +13 -0
- data/example/shell.nix +11 -0
- data/example/swpins/channels/nixos-unstable.json +35 -0
- data/example/swpins/channels/vpsadminos-staging.json +35 -0
- data/lib/confctl/cli/app.rb +551 -0
- data/lib/confctl/cli/attr_filters.rb +51 -0
- data/lib/confctl/cli/cluster.rb +1248 -0
- data/lib/confctl/cli/command.rb +206 -0
- data/lib/confctl/cli/configuration.rb +296 -0
- data/lib/confctl/cli/gen_data.rb +97 -0
- data/lib/confctl/cli/generation.rb +335 -0
- data/lib/confctl/cli/log_view.rb +267 -0
- data/lib/confctl/cli/output_formatter.rb +288 -0
- data/lib/confctl/cli/swpins/base.rb +40 -0
- data/lib/confctl/cli/swpins/channel.rb +73 -0
- data/lib/confctl/cli/swpins/cluster.rb +80 -0
- data/lib/confctl/cli/swpins/core.rb +86 -0
- data/lib/confctl/cli/swpins/utils.rb +55 -0
- data/lib/confctl/cli/swpins.rb +5 -0
- data/lib/confctl/cli/tag_filters.rb +30 -0
- data/lib/confctl/cli.rb +5 -0
- data/lib/confctl/conf_cache.rb +105 -0
- data/lib/confctl/conf_dir.rb +88 -0
- data/lib/confctl/erb_template.rb +37 -0
- data/lib/confctl/exceptions.rb +3 -0
- data/lib/confctl/gcroot.rb +30 -0
- data/lib/confctl/generation/build.rb +145 -0
- data/lib/confctl/generation/build_list.rb +106 -0
- data/lib/confctl/generation/host.rb +35 -0
- data/lib/confctl/generation/host_list.rb +81 -0
- data/lib/confctl/generation/unified.rb +117 -0
- data/lib/confctl/generation/unified_list.rb +63 -0
- data/lib/confctl/git_repo_mirror.rb +79 -0
- data/lib/confctl/health_checks/base.rb +66 -0
- data/lib/confctl/health_checks/run_command.rb +179 -0
- data/lib/confctl/health_checks/systemd/properties.rb +84 -0
- data/lib/confctl/health_checks/systemd/property_check.rb +31 -0
- data/lib/confctl/health_checks/systemd/property_list.rb +20 -0
- data/lib/confctl/health_checks.rb +5 -0
- data/lib/confctl/hook.rb +35 -0
- data/lib/confctl/line_buffer.rb +53 -0
- data/lib/confctl/logger.rb +151 -0
- data/lib/confctl/machine.rb +107 -0
- data/lib/confctl/machine_control.rb +172 -0
- data/lib/confctl/machine_list.rb +108 -0
- data/lib/confctl/machine_status.rb +135 -0
- data/lib/confctl/module_options.rb +95 -0
- data/lib/confctl/nix.rb +382 -0
- data/lib/confctl/nix_build.rb +108 -0
- data/lib/confctl/nix_collect_garbage.rb +64 -0
- data/lib/confctl/nix_copy.rb +49 -0
- data/lib/confctl/nix_format.rb +124 -0
- data/lib/confctl/nix_literal_expression.rb +15 -0
- data/lib/confctl/parallel_executor.rb +43 -0
- data/lib/confctl/pattern.rb +9 -0
- data/lib/confctl/settings.rb +50 -0
- data/lib/confctl/std_line_buffer.rb +40 -0
- data/lib/confctl/swpins/change_set.rb +151 -0
- data/lib/confctl/swpins/channel.rb +62 -0
- data/lib/confctl/swpins/channel_list.rb +47 -0
- data/lib/confctl/swpins/cluster_name.rb +94 -0
- data/lib/confctl/swpins/cluster_name_list.rb +15 -0
- data/lib/confctl/swpins/core.rb +137 -0
- data/lib/confctl/swpins/deployed_info.rb +23 -0
- data/lib/confctl/swpins/spec.rb +20 -0
- data/lib/confctl/swpins/specs/base.rb +184 -0
- data/lib/confctl/swpins/specs/directory.rb +51 -0
- data/lib/confctl/swpins/specs/git.rb +135 -0
- data/lib/confctl/swpins/specs/git_rev.rb +24 -0
- data/lib/confctl/swpins.rb +17 -0
- data/lib/confctl/system_command.rb +10 -0
- data/lib/confctl/user_script.rb +13 -0
- data/lib/confctl/user_scripts.rb +41 -0
- data/lib/confctl/utils/file.rb +21 -0
- data/lib/confctl/version.rb +3 -0
- data/lib/confctl.rb +43 -0
- data/man/man8/confctl-options.nix.8 +1334 -0
- data/man/man8/confctl-options.nix.8.md +1340 -0
- data/man/man8/confctl.8 +660 -0
- data/man/man8/confctl.8.md +654 -0
- data/nix/evaluator.nix +160 -0
- data/nix/lib/default.nix +83 -0
- data/nix/lib/machine/default.nix +74 -0
- data/nix/lib/machine/info.nix +5 -0
- data/nix/lib/swpins/eval.nix +71 -0
- data/nix/lib/swpins/options.nix +94 -0
- data/nix/machines.nix +31 -0
- data/nix/modules/cluster/default.nix +459 -0
- data/nix/modules/confctl/cli.nix +21 -0
- data/nix/modules/confctl/generations.nix +84 -0
- data/nix/modules/confctl/nix.nix +28 -0
- data/nix/modules/confctl/swpins.nix +55 -0
- data/nix/modules/module-list.nix +19 -0
- data/shell.nix +42 -0
- data/template/confctl-options.nix/main.erb +45 -0
- data/template/confctl-options.nix/options.erb +15 -0
- metadata +353 -0
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module ConfCtl
|
4
|
+
# Format Ruby values in Nix
|
5
|
+
class NixFormat
|
6
|
+
# @param value [any]
|
7
|
+
# @param sort [Boolean] sort hash keys
|
8
|
+
# @return [String]
|
9
|
+
def self.to_nix(value, sort: true)
|
10
|
+
new(sort:).to_nix(value)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param sort [Boolean] sort hash keys
|
14
|
+
def initialize(sort: true)
|
15
|
+
@sort = sort
|
16
|
+
@output = ''
|
17
|
+
@level = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param value [any]
|
21
|
+
# @return [String]
|
22
|
+
def to_nix(value)
|
23
|
+
format_value(value).strip
|
24
|
+
end
|
25
|
+
|
26
|
+
def pad(str, indent: true)
|
27
|
+
if indent
|
28
|
+
"#{' ' * @level}#{str}"
|
29
|
+
else
|
30
|
+
str
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def indent
|
35
|
+
@level += 2
|
36
|
+
yield
|
37
|
+
@level -= 2
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def format_value(value, indent: true, semicolon: false, nl: false)
|
43
|
+
if value.respond_to?(:to_nix)
|
44
|
+
return value.to_nix(nix_format: self, indent:, semicolon:, nl:)
|
45
|
+
end
|
46
|
+
|
47
|
+
case value
|
48
|
+
when Hash
|
49
|
+
format_hash(value, indent:, semicolon:, nl:)
|
50
|
+
|
51
|
+
when Array
|
52
|
+
format_array(value, indent:, semicolon:, nl:)
|
53
|
+
|
54
|
+
else
|
55
|
+
simple =
|
56
|
+
case value
|
57
|
+
when String
|
58
|
+
value.dump
|
59
|
+
|
60
|
+
when Symbol
|
61
|
+
value.to_s.dump
|
62
|
+
|
63
|
+
when Numeric, true, false
|
64
|
+
value.to_s
|
65
|
+
|
66
|
+
when Pathname
|
67
|
+
str = value.to_s
|
68
|
+
|
69
|
+
if str.start_with?('/')
|
70
|
+
str
|
71
|
+
else
|
72
|
+
"./#{str}"
|
73
|
+
end
|
74
|
+
|
75
|
+
when nil
|
76
|
+
'null'
|
77
|
+
|
78
|
+
else
|
79
|
+
raise "Unable to format #{value.inspect} (#{value.class}) to nix: " \
|
80
|
+
'implement #to_nix()?'
|
81
|
+
end
|
82
|
+
|
83
|
+
pad("#{simple}#{semicolon ? ';' : ''}#{nl ? "\n" : ''}", indent:)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def format_hash(hash, indent: true, semicolon: false, nl: true)
|
88
|
+
ret = pad("{\n", indent:)
|
89
|
+
|
90
|
+
quote_keys = !hash.each_key.all? { |k| /^[a-zA-Z_][a-zA-Z_\-']*$/ =~ k }
|
91
|
+
|
92
|
+
sorted =
|
93
|
+
if @sort
|
94
|
+
hash.sort do |a, b|
|
95
|
+
a[0] <=> b[0]
|
96
|
+
end
|
97
|
+
else
|
98
|
+
hash
|
99
|
+
end
|
100
|
+
|
101
|
+
indent do
|
102
|
+
sorted.each do |k, v|
|
103
|
+
ret << (quote_keys ? pad("\"#{k}\"") : pad(k.to_s))
|
104
|
+
ret << " = #{format_value(v, indent: false, semicolon: true, nl: false)}\n"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
ret << pad("}#{semicolon ? ';' : ''}#{nl ? "\n" : ''}")
|
109
|
+
ret
|
110
|
+
end
|
111
|
+
|
112
|
+
def format_array(array, indent: true, semicolon: false, nl: true)
|
113
|
+
ret = pad("[\n", indent:)
|
114
|
+
|
115
|
+
indent do
|
116
|
+
array.each do |v|
|
117
|
+
ret << "#{pad(format_value(v, indent: false, nl: false))}\n"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
ret << pad("]#{semicolon ? ';' : ''}#{nl ? "\n" : ''}")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module ConfCtl
|
2
|
+
class ParallelExecutor
|
3
|
+
attr_reader :thread_count
|
4
|
+
|
5
|
+
def initialize(threads)
|
6
|
+
@thread_count = threads
|
7
|
+
@threads = []
|
8
|
+
@queue = Queue.new
|
9
|
+
@retvals = []
|
10
|
+
@mutex = Mutex.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(&block)
|
14
|
+
queue << block
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
thread_count.times do
|
19
|
+
threads << Thread.new { worker }
|
20
|
+
end
|
21
|
+
|
22
|
+
threads.each(&:join)
|
23
|
+
retvals
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
attr_reader :threads, :queue, :mutex, :retvals
|
29
|
+
|
30
|
+
def worker
|
31
|
+
loop do
|
32
|
+
begin
|
33
|
+
block = queue.pop(true)
|
34
|
+
rescue ThreadError
|
35
|
+
return
|
36
|
+
end
|
37
|
+
|
38
|
+
v = block.call
|
39
|
+
mutex.synchronize { retvals << v }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module ConfCtl
|
4
|
+
class Settings
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@settings = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def list_columns
|
12
|
+
read_settings { |s| s['list']['columns'] }
|
13
|
+
end
|
14
|
+
|
15
|
+
def max_jobs
|
16
|
+
read_settings { |s| s['nix']['maxJobs'] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def nix_paths
|
20
|
+
read_settings { |s| s['nix']['nixPath'] }
|
21
|
+
end
|
22
|
+
|
23
|
+
def core_swpin_channels
|
24
|
+
read_settings { |s| s['swpins']['core']['channels'] }
|
25
|
+
end
|
26
|
+
|
27
|
+
def core_swpin_pins
|
28
|
+
read_settings { |s| s['swpins']['core']['pins'] }
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_generations
|
32
|
+
read_settings { |s| s['buildGenerations'] }
|
33
|
+
end
|
34
|
+
|
35
|
+
def host_generations
|
36
|
+
read_settings { |s| s['hostGenerations'] }
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def read_settings
|
42
|
+
if @settings.nil?
|
43
|
+
nix = Nix.stateless
|
44
|
+
@settings = nix.confctl_settings
|
45
|
+
end
|
46
|
+
|
47
|
+
yield(@settings)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ConfCtl
|
2
|
+
# Pair of line buffers for standard output/error
|
3
|
+
class StdLineBuffer
|
4
|
+
# @yieldparam out [String, nil]
|
5
|
+
# @yieldparam err [String, nil]
|
6
|
+
def initialize(&block)
|
7
|
+
@out_buffer = LineBuffer.new
|
8
|
+
@err_buffer = LineBuffer.new
|
9
|
+
@block = block
|
10
|
+
@mutex = Mutex.new
|
11
|
+
end
|
12
|
+
|
13
|
+
# Get a block which can be called to feed data to the buffer
|
14
|
+
# @return [Proc]
|
15
|
+
def feed_block
|
16
|
+
proc do |stdout, stderr|
|
17
|
+
@mutex.synchronize do
|
18
|
+
out_buffer << stdout if stdout
|
19
|
+
err_buffer << stderr if stderr
|
20
|
+
|
21
|
+
loop do
|
22
|
+
out_line = out_buffer.read_line
|
23
|
+
err_line = err_buffer.read_line
|
24
|
+
break if out_line.nil? && err_line.nil?
|
25
|
+
|
26
|
+
block.call(out_line, err_line)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def flush
|
33
|
+
block.call(out_buffer.flush, err_buffer.flush)
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
attr_reader :out_buffer, :err_buffer, :block
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module ConfCtl
|
2
|
+
class Swpins::ChangeSet
|
3
|
+
class SpecOwner
|
4
|
+
attr_reader :name, :path
|
5
|
+
end
|
6
|
+
|
7
|
+
SpecSet = Struct.new(:spec, :original_version, :original_info, keyword_init: true) do
|
8
|
+
def name
|
9
|
+
spec.name
|
10
|
+
end
|
11
|
+
|
12
|
+
def new_version
|
13
|
+
spec.version
|
14
|
+
end
|
15
|
+
|
16
|
+
def new_info
|
17
|
+
spec.info
|
18
|
+
end
|
19
|
+
|
20
|
+
def changed?
|
21
|
+
original_version != new_version
|
22
|
+
end
|
23
|
+
|
24
|
+
def string_changelog(type)
|
25
|
+
if original_info
|
26
|
+
spec.string_changelog_info(type, original_info, color: false)
|
27
|
+
else
|
28
|
+
"Initial version\n"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@owners = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param owner [SpecOwner]
|
38
|
+
# @param spec [Swpins::Spec]
|
39
|
+
def add(owner, spec)
|
40
|
+
@owners[owner] ||= []
|
41
|
+
@owners[owner] << SpecSet.new(
|
42
|
+
spec:,
|
43
|
+
original_version: spec.valid? ? spec.version : nil,
|
44
|
+
original_info: spec.valid? ? spec.info : nil
|
45
|
+
)
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# @param type [:upgrade, :downgrade]
|
50
|
+
# @param changelog [Boolean]
|
51
|
+
def commit(type: :upgrade, changelog: false)
|
52
|
+
return if @owners.empty?
|
53
|
+
|
54
|
+
ret = Kernel.system(
|
55
|
+
'git',
|
56
|
+
'commit',
|
57
|
+
'-e',
|
58
|
+
'-m', build_message(type, changelog),
|
59
|
+
*changed_files
|
60
|
+
)
|
61
|
+
|
62
|
+
return if ret
|
63
|
+
|
64
|
+
raise 'git commit exited with non-zero status code'
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def build_message(type, changelog)
|
70
|
+
[
|
71
|
+
build_title(type, changelog),
|
72
|
+
build_description(type, changelog)
|
73
|
+
].join("\n\n")
|
74
|
+
end
|
75
|
+
|
76
|
+
def build_title(type, changelog)
|
77
|
+
msg = 'swpins: '
|
78
|
+
|
79
|
+
if same_changes?
|
80
|
+
spec_sets = @owners.first[1]
|
81
|
+
msg << "#{@owners.each_key.map(&:name).sort.join(', ')}: update "
|
82
|
+
|
83
|
+
msg << spec_sets.map do |spec_set|
|
84
|
+
"#{spec_set.name} to #{spec_set.new_version}"
|
85
|
+
end.join(', ')
|
86
|
+
else
|
87
|
+
all_spec_names = []
|
88
|
+
|
89
|
+
@owners.each_value do |spec_sets|
|
90
|
+
spec_sets.each do |spec_set|
|
91
|
+
all_spec_names << spec_set.name unless all_spec_names.include?(spec_set.name)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
msg << "#{@owners.each_key.map(&:name).join(', ')}: update #{all_spec_names.sort.join(', ')}"
|
96
|
+
end
|
97
|
+
|
98
|
+
msg
|
99
|
+
end
|
100
|
+
|
101
|
+
def build_description(type, changelog)
|
102
|
+
msg = ''
|
103
|
+
|
104
|
+
@owners.each do |owner, spec_sets|
|
105
|
+
spec_sets.each do |spec_set|
|
106
|
+
if spec_set.changed?
|
107
|
+
msg << "#{owner.name} #{spec_set.name}: "
|
108
|
+
|
109
|
+
msg << if spec_set.original_version
|
110
|
+
"#{spec_set.original_version} -> #{spec_set.new_version}\n"
|
111
|
+
else
|
112
|
+
"set to #{spec_set.new_version}\n"
|
113
|
+
end
|
114
|
+
|
115
|
+
if changelog
|
116
|
+
msg << spec_set.string_changelog(type)
|
117
|
+
msg << "\n"
|
118
|
+
end
|
119
|
+
else
|
120
|
+
msg << " #{spec_set.name}: unchanged\n"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
msg << "\n" if changelog
|
125
|
+
end
|
126
|
+
|
127
|
+
msg
|
128
|
+
end
|
129
|
+
|
130
|
+
def changed_files
|
131
|
+
@owners.each_key.map(&:path)
|
132
|
+
end
|
133
|
+
|
134
|
+
def same_changes?
|
135
|
+
return true if @owners.length == 1
|
136
|
+
|
137
|
+
expected_spec_sets = @owners.first[1]
|
138
|
+
|
139
|
+
@owners.each_value do |spec_sets|
|
140
|
+
return false if expected_spec_sets.length != spec_sets.length
|
141
|
+
|
142
|
+
spec_sets.each do |spec_set|
|
143
|
+
expected = expected_spec_sets.detect { |v| v.name == spec_set.name }
|
144
|
+
return false if expected.nil? || expected.new_version != spec_set.new_version
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
true
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module ConfCtl
|
5
|
+
class Swpins::Channel
|
6
|
+
# @return [String]
|
7
|
+
attr_reader :path
|
8
|
+
|
9
|
+
# @return [String]
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
# @return [Hash<String, Swpins::Specs::Base>]
|
13
|
+
attr_reader :specs
|
14
|
+
|
15
|
+
# @param name [String]
|
16
|
+
# @param nix_specs [Hash]
|
17
|
+
def initialize(name, nix_specs)
|
18
|
+
@name = name
|
19
|
+
@path = File.join(ConfCtl::Swpins.channel_dir, "#{name}.json")
|
20
|
+
@nix_specs = nix_specs
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse
|
24
|
+
@json_specs = if File.exist?(path)
|
25
|
+
JSON.parse(File.read(path))
|
26
|
+
else
|
27
|
+
{}
|
28
|
+
end
|
29
|
+
|
30
|
+
@specs = nix_specs.to_h do |name, nix_opts|
|
31
|
+
[
|
32
|
+
name,
|
33
|
+
Swpins::Spec.for(nix_opts['type'].to_sym).new(
|
34
|
+
name,
|
35
|
+
nix_opts[nix_opts['type']],
|
36
|
+
json_specs[name]
|
37
|
+
)
|
38
|
+
]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def valid?
|
43
|
+
specs.values.all?(&:valid?)
|
44
|
+
end
|
45
|
+
|
46
|
+
def save
|
47
|
+
tmp = "#{path}.new"
|
48
|
+
|
49
|
+
FileUtils.mkdir_p(ConfCtl::Swpins.channel_dir)
|
50
|
+
|
51
|
+
File.open(tmp, 'w') do |f|
|
52
|
+
f.puts(JSON.pretty_generate(specs))
|
53
|
+
end
|
54
|
+
|
55
|
+
File.rename(tmp, path)
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
attr_reader :nix_specs, :json_specs
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module ConfCtl
|
2
|
+
class Swpins::ChannelList < Array
|
3
|
+
# @return [Swpins::ChannelList]
|
4
|
+
def self.get
|
5
|
+
@get ||= new
|
6
|
+
end
|
7
|
+
|
8
|
+
# @param pattern [String]
|
9
|
+
def self.pattern(pattern)
|
10
|
+
get.pattern(pattern)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.refresh
|
14
|
+
get.refresh
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param pattern [String]
|
18
|
+
def initialize(pattern: '*')
|
19
|
+
super()
|
20
|
+
parse(pattern:)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param pattern [String]
|
24
|
+
# @return [Array<Swpins::Channel>]
|
25
|
+
def pattern(pattern)
|
26
|
+
select { |c| Pattern.match?(pattern, c.name) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def refresh
|
30
|
+
clear
|
31
|
+
parse
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def parse(pattern: '*')
|
37
|
+
nix = Nix.new
|
38
|
+
nix.list_swpins_channels.each do |name, nix_specs|
|
39
|
+
next unless Pattern.match?(pattern, name)
|
40
|
+
|
41
|
+
c = Swpins::Channel.new(name, nix_specs)
|
42
|
+
c.parse
|
43
|
+
self << c
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module ConfCtl
|
5
|
+
class Swpins::ClusterName
|
6
|
+
# @return [Machine]
|
7
|
+
attr_reader :machine
|
8
|
+
|
9
|
+
# @return [String]
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
# @return [String]
|
13
|
+
attr_reader :path
|
14
|
+
|
15
|
+
# @return [Hash<String, Swpins::Specs::Base>]
|
16
|
+
attr_reader :specs
|
17
|
+
|
18
|
+
# @return [Array<Swpins::Channel>]
|
19
|
+
attr_reader :channels
|
20
|
+
|
21
|
+
# @param machine [Machine]
|
22
|
+
# @param channels [Swpins::ChannelList]
|
23
|
+
def initialize(machine, channels)
|
24
|
+
@machine = machine
|
25
|
+
@name = machine.name
|
26
|
+
@path = File.join(ConfCtl::Swpins.cluster_dir, "#{name.gsub('/', ':')}.json")
|
27
|
+
@channels = channels.select do |c|
|
28
|
+
machine['swpins']['channels'].include?(c.name)
|
29
|
+
end
|
30
|
+
@nix_specs = machine['swpins']['pins']
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse
|
34
|
+
@specs = {}
|
35
|
+
|
36
|
+
# Add specs from channels
|
37
|
+
channels.each do |chan|
|
38
|
+
chan.specs.each do |name, chan_spec|
|
39
|
+
s = chan_spec.clone
|
40
|
+
s.channel = chan.name
|
41
|
+
specs[name] = s
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Add machine-specific specs
|
46
|
+
@json_specs = if File.exist?(path)
|
47
|
+
JSON.parse(File.read(path))
|
48
|
+
else
|
49
|
+
{}
|
50
|
+
end
|
51
|
+
|
52
|
+
nix_specs.each do |name, nix_opts|
|
53
|
+
specs[name] = Swpins::Spec.for(nix_opts['type'].to_sym).new(
|
54
|
+
name,
|
55
|
+
nix_opts[nix_opts['type']],
|
56
|
+
json_specs[name]
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def valid?
|
62
|
+
specs.values.all?(&:valid?)
|
63
|
+
end
|
64
|
+
|
65
|
+
def save
|
66
|
+
custom = {}
|
67
|
+
specs.each do |name, s|
|
68
|
+
custom[name] = s unless s.from_channel?
|
69
|
+
end
|
70
|
+
|
71
|
+
if custom.empty?
|
72
|
+
begin
|
73
|
+
File.unlink(path)
|
74
|
+
rescue Errno::ENOENT
|
75
|
+
# ignore
|
76
|
+
end
|
77
|
+
else
|
78
|
+
tmp = "#{path}.new"
|
79
|
+
|
80
|
+
FileUtils.mkdir_p(ConfCtl::Swpins.cluster_dir)
|
81
|
+
|
82
|
+
File.open(tmp, 'w') do |f|
|
83
|
+
f.puts(JSON.pretty_generate(custom))
|
84
|
+
end
|
85
|
+
|
86
|
+
File.rename(tmp, path)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
attr_reader :nix_specs, :json_specs
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ConfCtl
|
2
|
+
class Swpins::ClusterNameList < Array
|
3
|
+
# @param channels [Swpins::ChannelList]
|
4
|
+
# @param pattern [String]
|
5
|
+
# @param machines [MachineList]
|
6
|
+
def initialize(channels: nil, pattern: '*', machines: nil)
|
7
|
+
super()
|
8
|
+
channels ||= ConfCtl::Swpins::ChannelList.get
|
9
|
+
|
10
|
+
(machines || MachineList.new).each_value do |machine|
|
11
|
+
self << Swpins::ClusterName.new(machine, channels) if Pattern.match?(pattern, machine.name)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|