confctl 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +11 -0
  3. data/.gitignore +8 -0
  4. data/.overcommit.yml +6 -0
  5. data/.rubocop.yml +67 -0
  6. data/.rubocop_todo.yml +5 -0
  7. data/.ruby-version +1 -0
  8. data/CHANGELOG.md +2 -0
  9. data/Gemfile +2 -0
  10. data/LICENSE.txt +674 -0
  11. data/README.md +522 -0
  12. data/Rakefile +40 -0
  13. data/bin/confctl +4 -0
  14. data/confctl.gemspec +33 -0
  15. data/example/.gitignore +2 -0
  16. data/example/README.md +38 -0
  17. data/example/cluster/cluster.nix +7 -0
  18. data/example/cluster/module-list.nix +3 -0
  19. data/example/cluster/nixos-machine/config.nix +15 -0
  20. data/example/cluster/nixos-machine/hardware.nix +4 -0
  21. data/example/cluster/nixos-machine/module.nix +8 -0
  22. data/example/cluster/vpsadminos-container/config.nix +22 -0
  23. data/example/cluster/vpsadminos-container/module.nix +8 -0
  24. data/example/cluster/vpsadminos-machine/config.nix +22 -0
  25. data/example/cluster/vpsadminos-machine/hardware.nix +4 -0
  26. data/example/cluster/vpsadminos-machine/module.nix +8 -0
  27. data/example/cluster/vpsfreecz-vps/config.nix +25 -0
  28. data/example/cluster/vpsfreecz-vps/module.nix +8 -0
  29. data/example/configs/confctl.nix +10 -0
  30. data/example/configs/swpins.nix +28 -0
  31. data/example/data/default.nix +5 -0
  32. data/example/data/ssh-keys.nix +7 -0
  33. data/example/environments/base.nix +13 -0
  34. data/example/modules/module-list.nix +13 -0
  35. data/example/shell.nix +11 -0
  36. data/example/swpins/channels/nixos-unstable.json +35 -0
  37. data/example/swpins/channels/vpsadminos-staging.json +35 -0
  38. data/lib/confctl/cli/app.rb +551 -0
  39. data/lib/confctl/cli/attr_filters.rb +51 -0
  40. data/lib/confctl/cli/cluster.rb +1248 -0
  41. data/lib/confctl/cli/command.rb +206 -0
  42. data/lib/confctl/cli/configuration.rb +296 -0
  43. data/lib/confctl/cli/gen_data.rb +97 -0
  44. data/lib/confctl/cli/generation.rb +335 -0
  45. data/lib/confctl/cli/log_view.rb +267 -0
  46. data/lib/confctl/cli/output_formatter.rb +288 -0
  47. data/lib/confctl/cli/swpins/base.rb +40 -0
  48. data/lib/confctl/cli/swpins/channel.rb +73 -0
  49. data/lib/confctl/cli/swpins/cluster.rb +80 -0
  50. data/lib/confctl/cli/swpins/core.rb +86 -0
  51. data/lib/confctl/cli/swpins/utils.rb +55 -0
  52. data/lib/confctl/cli/swpins.rb +5 -0
  53. data/lib/confctl/cli/tag_filters.rb +30 -0
  54. data/lib/confctl/cli.rb +5 -0
  55. data/lib/confctl/conf_cache.rb +105 -0
  56. data/lib/confctl/conf_dir.rb +88 -0
  57. data/lib/confctl/erb_template.rb +37 -0
  58. data/lib/confctl/exceptions.rb +3 -0
  59. data/lib/confctl/gcroot.rb +30 -0
  60. data/lib/confctl/generation/build.rb +145 -0
  61. data/lib/confctl/generation/build_list.rb +106 -0
  62. data/lib/confctl/generation/host.rb +35 -0
  63. data/lib/confctl/generation/host_list.rb +81 -0
  64. data/lib/confctl/generation/unified.rb +117 -0
  65. data/lib/confctl/generation/unified_list.rb +63 -0
  66. data/lib/confctl/git_repo_mirror.rb +79 -0
  67. data/lib/confctl/health_checks/base.rb +66 -0
  68. data/lib/confctl/health_checks/run_command.rb +179 -0
  69. data/lib/confctl/health_checks/systemd/properties.rb +84 -0
  70. data/lib/confctl/health_checks/systemd/property_check.rb +31 -0
  71. data/lib/confctl/health_checks/systemd/property_list.rb +20 -0
  72. data/lib/confctl/health_checks.rb +5 -0
  73. data/lib/confctl/hook.rb +35 -0
  74. data/lib/confctl/line_buffer.rb +53 -0
  75. data/lib/confctl/logger.rb +151 -0
  76. data/lib/confctl/machine.rb +107 -0
  77. data/lib/confctl/machine_control.rb +172 -0
  78. data/lib/confctl/machine_list.rb +108 -0
  79. data/lib/confctl/machine_status.rb +135 -0
  80. data/lib/confctl/module_options.rb +95 -0
  81. data/lib/confctl/nix.rb +382 -0
  82. data/lib/confctl/nix_build.rb +108 -0
  83. data/lib/confctl/nix_collect_garbage.rb +64 -0
  84. data/lib/confctl/nix_copy.rb +49 -0
  85. data/lib/confctl/nix_format.rb +124 -0
  86. data/lib/confctl/nix_literal_expression.rb +15 -0
  87. data/lib/confctl/parallel_executor.rb +43 -0
  88. data/lib/confctl/pattern.rb +9 -0
  89. data/lib/confctl/settings.rb +50 -0
  90. data/lib/confctl/std_line_buffer.rb +40 -0
  91. data/lib/confctl/swpins/change_set.rb +151 -0
  92. data/lib/confctl/swpins/channel.rb +62 -0
  93. data/lib/confctl/swpins/channel_list.rb +47 -0
  94. data/lib/confctl/swpins/cluster_name.rb +94 -0
  95. data/lib/confctl/swpins/cluster_name_list.rb +15 -0
  96. data/lib/confctl/swpins/core.rb +137 -0
  97. data/lib/confctl/swpins/deployed_info.rb +23 -0
  98. data/lib/confctl/swpins/spec.rb +20 -0
  99. data/lib/confctl/swpins/specs/base.rb +184 -0
  100. data/lib/confctl/swpins/specs/directory.rb +51 -0
  101. data/lib/confctl/swpins/specs/git.rb +135 -0
  102. data/lib/confctl/swpins/specs/git_rev.rb +24 -0
  103. data/lib/confctl/swpins.rb +17 -0
  104. data/lib/confctl/system_command.rb +10 -0
  105. data/lib/confctl/user_script.rb +13 -0
  106. data/lib/confctl/user_scripts.rb +41 -0
  107. data/lib/confctl/utils/file.rb +21 -0
  108. data/lib/confctl/version.rb +3 -0
  109. data/lib/confctl.rb +43 -0
  110. data/man/man8/confctl-options.nix.8 +1334 -0
  111. data/man/man8/confctl-options.nix.8.md +1340 -0
  112. data/man/man8/confctl.8 +660 -0
  113. data/man/man8/confctl.8.md +654 -0
  114. data/nix/evaluator.nix +160 -0
  115. data/nix/lib/default.nix +83 -0
  116. data/nix/lib/machine/default.nix +74 -0
  117. data/nix/lib/machine/info.nix +5 -0
  118. data/nix/lib/swpins/eval.nix +71 -0
  119. data/nix/lib/swpins/options.nix +94 -0
  120. data/nix/machines.nix +31 -0
  121. data/nix/modules/cluster/default.nix +459 -0
  122. data/nix/modules/confctl/cli.nix +21 -0
  123. data/nix/modules/confctl/generations.nix +84 -0
  124. data/nix/modules/confctl/nix.nix +28 -0
  125. data/nix/modules/confctl/swpins.nix +55 -0
  126. data/nix/modules/module-list.nix +19 -0
  127. data/shell.nix +42 -0
  128. data/template/confctl-options.nix/main.erb +45 -0
  129. data/template/confctl-options.nix/options.erb +15 -0
  130. metadata +353 -0
@@ -0,0 +1,137 @@
1
+ require 'fileutils'
2
+ require 'json'
3
+
4
+ module ConfCtl
5
+ class Swpins::Core
6
+ # @return [Swpins::Core]
7
+ def self.get
8
+ return @instance if @instance
9
+
10
+ @instance = new
11
+ @instance.parse
12
+ @instance
13
+ end
14
+
15
+ include Utils::File
16
+
17
+ # @return [String]
18
+ attr_reader :name
19
+
20
+ # @return [String]
21
+ attr_reader :path
22
+
23
+ # @return [Hash<String, Swpins::Specs::Base>]
24
+ attr_reader :specs
25
+
26
+ # @return [Array<Swpins::Channel>]
27
+ attr_reader :channels
28
+
29
+ def initialize
30
+ @name = 'core'
31
+ @path = File.join(ConfCtl::Swpins.core_dir, 'core.json')
32
+
33
+ settings = ConfCtl::Settings.instance
34
+
35
+ @channels = Swpins::ChannelList.get.select do |c|
36
+ settings.core_swpin_channels.include?(c.name)
37
+ end
38
+
39
+ @nix_specs = settings.core_swpin_pins
40
+ end
41
+
42
+ def parse
43
+ @specs = {}
44
+
45
+ # Add specs from channels
46
+ channels.each do |chan|
47
+ chan.specs.each do |name, chan_spec|
48
+ s = chan_spec.clone
49
+ s.channel = chan.name
50
+ specs[name] = s
51
+ end
52
+ end
53
+
54
+ # Add core-specific specs
55
+ @json_specs = if File.exist?(path)
56
+ JSON.parse(File.read(path))
57
+ else
58
+ {}
59
+ end
60
+
61
+ nix_specs.each do |name, nix_opts|
62
+ specs[name] = Swpins::Spec.for(nix_opts['type'].to_sym).new(
63
+ name,
64
+ nix_opts[nix_opts['type']],
65
+ json_specs[name]
66
+ )
67
+ end
68
+ end
69
+
70
+ def valid?
71
+ specs.values.all?(&:valid?)
72
+ end
73
+
74
+ def save
75
+ custom = {}
76
+
77
+ specs.each do |name, s|
78
+ custom[name] = s unless s.from_channel?
79
+ end
80
+
81
+ if custom.empty?
82
+ begin
83
+ File.unlink(path)
84
+ rescue Errno::ENOENT
85
+ # ignore
86
+ end
87
+ else
88
+ tmp = "#{path}.new"
89
+
90
+ FileUtils.mkdir_p(ConfCtl::Swpins.core_dir)
91
+
92
+ File.open(tmp, 'w') do |f|
93
+ f.puts(JSON.pretty_generate(custom))
94
+ end
95
+
96
+ File.rename(tmp, path)
97
+ end
98
+ end
99
+
100
+ def pre_evaluate
101
+ nix = ConfCtl::Nix.new
102
+ paths = nix.eval_core_swpins
103
+
104
+ FileUtils.mkdir_p(cache_dir)
105
+
106
+ paths.each do |pin, path|
107
+ name = "core-swpin.#{pin}"
108
+ link = File.join(cache_dir, name)
109
+ replace_symlink(link, path)
110
+ ConfCtl::GCRoot.add(name, link) unless ConfCtl::GCRoot.exist?(name)
111
+ end
112
+ end
113
+
114
+ def pre_evaluated_store_paths
115
+ swpins_path = File.join(cache_dir, 'core.swpins')
116
+ return unless File.exist?(swpins_path)
117
+
118
+ swpins = JSON.parse(File.read(swpins_path))
119
+
120
+ missing_path = swpins.each_value.detect do |path|
121
+ !Dir.exist?(path)
122
+ end
123
+
124
+ return if missing_path
125
+
126
+ swpins
127
+ end
128
+
129
+ protected
130
+
131
+ attr_reader :nix_specs, :json_specs
132
+
133
+ def cache_dir
134
+ File.join(ConfDir.cache_dir, 'build')
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,23 @@
1
+ require 'json'
2
+
3
+ module ConfCtl
4
+ class Swpins::DeployedInfo
5
+ # @param json [String]
6
+ def self.parse!(json)
7
+ new(JSON.parse(json))
8
+ rescue JSON::ParserError => e
9
+ raise Error, "unable to parse swpins info: #{e.message}"
10
+ end
11
+
12
+ # @return [Hash]
13
+ attr_reader :swpins
14
+
15
+ def initialize(hash)
16
+ @swpins = hash
17
+ end
18
+
19
+ def [](swpin)
20
+ swpins[swpin]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ module ConfCtl
2
+ module Swpins::Spec
3
+ # @param name [Symbol]
4
+ # @param klass [Class]
5
+ def self.register(name, klass)
6
+ @specs ||= {}
7
+ @specs[name] = klass
8
+ end
9
+
10
+ # @param name [Symbol]
11
+ # @return [Class]
12
+ def self.for(name)
13
+ @specs[name]
14
+ end
15
+ end
16
+
17
+ module Swpins::Specs; end
18
+ end
19
+
20
+ require_rel 'specs'
@@ -0,0 +1,184 @@
1
+ module ConfCtl
2
+ class Swpins::Specs::Base
3
+ class << self
4
+ attr_reader :type
5
+
6
+ # @param name [Symbol]
7
+ def handle(name)
8
+ @type = name
9
+ Swpins::Spec.register(name, self)
10
+ end
11
+ end
12
+
13
+ # @return [String]
14
+ attr_reader :name
15
+
16
+ # @param [String]
17
+ # @return [String, nil]
18
+ attr_accessor :channel
19
+
20
+ # @return [Array]
21
+ attr_reader :errors
22
+
23
+ # @return [Hash]
24
+ attr_reader :info
25
+
26
+ # @param name [String]
27
+ # @param nix_opts [Hash] options from the user's nix configuration
28
+ # @param json_opts [Hash] options from generated json file
29
+ def initialize(name, nix_opts, json_opts)
30
+ @name = name
31
+ @nix_opts = nix_opts
32
+ @json_opts = json_opts
33
+ @state = nil
34
+ @info = nil
35
+ @fetcher = nil
36
+ @fetcher_opts = nil
37
+ @errors = []
38
+ parse
39
+ end
40
+
41
+ def type
42
+ self.class.type
43
+ end
44
+
45
+ # Check that current nix options match those that generated json options
46
+ # @return [Boolean]
47
+ def check_opts
48
+ raise NotImplementedError
49
+ end
50
+
51
+ def valid?
52
+ errors.empty?
53
+ end
54
+
55
+ def can_update?
56
+ nix_opts['update']['ref'] ? true : false
57
+ end
58
+
59
+ def auto_update?
60
+ can_update? && nix_opts['update']['auto']
61
+ end
62
+
63
+ def from_channel?
64
+ !channel.nil?
65
+ end
66
+
67
+ def status
68
+ valid? ? version : 'update'
69
+ end
70
+
71
+ def version
72
+ raise NotImplementedError
73
+ end
74
+
75
+ def prefetch_set(args)
76
+ raise NotImplementedError
77
+ end
78
+
79
+ def prefetch_update
80
+ raise NotImplementedError
81
+ end
82
+
83
+ # Check that `other_info` corresponds to this spec
84
+ # @param other_info [Hash]
85
+ # @return [Boolean]
86
+ def check_info(other_info)
87
+ raise NotImplementedError
88
+ end
89
+
90
+ # Return version based on the info hash
91
+ # @param other_info [Hash]
92
+ # @return [String]
93
+ def version_info(other_info)
94
+ raise NotImplementedError
95
+ end
96
+
97
+ # Generate changelog between revisions
98
+ # @param type [:upgrade, :downgrade]
99
+ # @param other_info [any] version reference
100
+ # @param opts [Hash] options
101
+ # @option opts [Boolean] :color
102
+ # @option opts [Boolean] :verbose
103
+ # @option opts [Boolean] :patch
104
+ # @return [String, nil]
105
+ def string_changelog_info(type, other_info, **opts)
106
+ raise NotImplementedError
107
+ end
108
+
109
+ # Generate diff between revisions
110
+ # @param type [:upgrade, :downgrade]
111
+ # @param other_info [any] version reference
112
+ # @param opts [Hash] options
113
+ # @option opts [Boolean] :color
114
+ # @return [String, nil]
115
+ def string_diff_info(type, other_info, **opts)
116
+ raise NotImplementedError
117
+ end
118
+
119
+ def as_json
120
+ ret = {}
121
+ ret['type'] = type.to_s
122
+ ret['nix_options'] = nix_opts
123
+ ret['state'] = state
124
+ ret['info'] = info
125
+ ret['fetcher'] = fetcher && {
126
+ 'type' => fetcher,
127
+ 'options' => fetcher_opts
128
+ }
129
+ ret
130
+ end
131
+
132
+ def to_json(*args)
133
+ as_json.to_json(*args)
134
+ end
135
+
136
+ protected
137
+
138
+ # @return [Hash]
139
+ attr_reader :nix_opts
140
+
141
+ # @return [Hash]
142
+ attr_reader :json_opts
143
+
144
+ # @return [Hash]
145
+ attr_accessor :state
146
+
147
+ attr_writer :info
148
+
149
+ # @return [String]
150
+ attr_reader :fetcher
151
+
152
+ # @return [Hash]
153
+ attr_reader :fetcher_opts
154
+
155
+ def parse
156
+ errors.clear
157
+
158
+ if json_opts.nil?
159
+ errors << 'needs prefetch'
160
+ elsif type.to_s != json_opts['type']
161
+ errors << 'invalid type, needs update'
162
+ elsif !json_opts['nix_options'] || !check_opts
163
+ errors << 'outdated options, needs update'
164
+ elsif json_opts['fetcher'].nil?
165
+ errors << 'no fetcher, needs update'
166
+ end
167
+
168
+ return if errors.any?
169
+
170
+ @state = json_opts['state']
171
+ @info = json_opts['info']
172
+
173
+ return unless json_opts['fetcher']
174
+
175
+ @fetcher = json_opts['fetcher']['type']
176
+ @fetcher_opts = json_opts['fetcher']['options']
177
+ end
178
+
179
+ def set_fetcher(name, opts)
180
+ @fetcher = name
181
+ @fetcher_opts = opts
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,51 @@
1
+ require_relative 'base'
2
+ require 'json'
3
+ require 'time'
4
+
5
+ module ConfCtl
6
+ class Swpins::Specs::Directory < Swpins::Specs::Base
7
+ handle :directory
8
+
9
+ def check_opts
10
+ nix_opts['path'] === json_opts['nix_options']['path']
11
+ end
12
+
13
+ def version
14
+ 'directory'
15
+ end
16
+
17
+ def can_update?
18
+ true
19
+ end
20
+
21
+ def auto_update?
22
+ true
23
+ end
24
+
25
+ def prefetch_set(args)
26
+ raise "spec #{name} does not accept any arguments" if args.any?
27
+
28
+ set_fetcher(:directory, { path: nix_opts['path'] })
29
+ end
30
+
31
+ def prefetch_update
32
+ set_fetcher(:directory, { path: nix_opts['path'] })
33
+ end
34
+
35
+ def check_info(_other_info)
36
+ false
37
+ end
38
+
39
+ def version_info(_other_info)
40
+ 'directory'
41
+ end
42
+
43
+ def string_changelog_info(_type, _other_info, verbose: false, patch: false, color: false)
44
+ nil
45
+ end
46
+
47
+ def string_diff_info(_type, _other_info, _opts = {})
48
+ nil
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,135 @@
1
+ require_relative 'base'
2
+ require 'json'
3
+ require 'time'
4
+
5
+ module ConfCtl
6
+ class Swpins::Specs::Git < Swpins::Specs::Base
7
+ handle :git
8
+
9
+ def check_opts
10
+ nix_opts['url'] === json_opts['nix_options']['url'] \
11
+ && nix_opts['fetchSubmodules'] === json_opts['nix_options']['fetchSubmodules']
12
+ end
13
+
14
+ def version
15
+ state['rev'][0..8]
16
+ end
17
+
18
+ def auto_update?
19
+ super && (
20
+ state['date'].nil? \
21
+ || (Time.iso8601(state['date']) + nix_opts['update']['interval'] < Time.now)
22
+ )
23
+ end
24
+
25
+ def prefetch_set(args)
26
+ ref = args[0]
27
+
28
+ if %r{^https://github\.com/} =~ nix_opts['url'] && !nix_opts['fetchSubmodules']
29
+ set_fetcher('zip', prefetch_github(ref))
30
+ else
31
+ set_fetcher('git', prefetch_git(ref))
32
+ end
33
+ end
34
+
35
+ def prefetch_update
36
+ prefetch_set([nix_opts['update']['ref']])
37
+ end
38
+
39
+ def check_info(other_info)
40
+ return false if !other_info.is_a?(Hash) || !info.is_a?(Hash)
41
+
42
+ other_info['rev'] == info['rev'] && other_info['sha256'] == info['sha256']
43
+ end
44
+
45
+ def version_info(other_info)
46
+ return false if !other_info.is_a?(Hash) || !info.is_a?(Hash)
47
+
48
+ other_info['rev'] && other_info['rev'][0..8]
49
+ end
50
+
51
+ def string_changelog_info(type, other_info, verbose: false, patch: false, color: false)
52
+ opts = []
53
+ opts << '--color=always' if color
54
+ opts << '--oneline' unless verbose
55
+ opts << '-p' if patch
56
+
57
+ args =
58
+ if type == :upgrade
59
+ [other_info['rev'], state['rev']]
60
+ else
61
+ [state['rev'], other_info['rev']]
62
+ end
63
+
64
+ git_mirror_with_info(other_info) do |mirror|
65
+ mirror.log(*args, opts:)
66
+ end
67
+ end
68
+
69
+ def string_diff_info(type, other_info, color: false, **_opts)
70
+ gitopts = []
71
+ gitopts << '--color=always' if color
72
+
73
+ args =
74
+ if type == :upgrade
75
+ [other_info['rev'], state['rev']]
76
+ else
77
+ [state['rev'], other_info['rev']]
78
+ end
79
+
80
+ git_mirror_with_info(other_info) do |mirror|
81
+ mirror.diff(*args, opts: gitopts)
82
+ end
83
+ end
84
+
85
+ protected
86
+
87
+ def prefetch_git(ref)
88
+ json = `nix-prefetch-git --quiet #{nix_opts['url']} #{ref}`
89
+
90
+ raise "nix-prefetch-git failed with status #{$?.exitstatus}" if $?.exitstatus != 0
91
+
92
+ ret = JSON.parse(json.strip)
93
+ self.state = {
94
+ 'rev' => ret['rev'],
95
+ 'date' => Time.now.iso8601
96
+ }
97
+ self.info = {
98
+ 'rev' => ret['rev'],
99
+ 'sha256' => ret['sha256']
100
+ }
101
+ ret
102
+ end
103
+
104
+ def prefetch_github(ref)
105
+ mirror = GitRepoMirror.new(nix_opts['url'])
106
+ mirror.setup
107
+
108
+ rev = mirror.revision_parse(ref)
109
+ url = File.join(nix_opts['url'], 'archive', "#{rev}.tar.gz")
110
+ hash = `nix-prefetch-url --unpack "#{url}" 2> /dev/null`.strip
111
+
112
+ raise "nix-prefetch-url failed with status #{$?.exitstatus}" if $?.exitstatus != 0
113
+
114
+ self.state = {
115
+ 'rev' => rev,
116
+ 'date' => Time.now.iso8601
117
+ }
118
+ self.info = {
119
+ 'rev' => rev,
120
+ 'sha256' => hash
121
+ }
122
+ { 'url' => url, 'sha256' => hash }
123
+ end
124
+
125
+ def git_mirror_with_info(other_info)
126
+ raise ConfCtl::Error, 'swpin not configured' if info.nil?
127
+
128
+ return if state['rev'] == other_info['rev']
129
+
130
+ mirror = GitRepoMirror.new(nix_opts['url'], quiet: true)
131
+ mirror.setup
132
+ yield(mirror)
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,24 @@
1
+ require_relative 'git'
2
+
3
+ module ConfCtl
4
+ class Swpins::Specs::GitRev < Swpins::Specs::Git
5
+ handle :'git-rev'
6
+
7
+ def prefetch_set(args)
8
+ super(args)
9
+ wrap_fetcher
10
+ end
11
+
12
+ protected
13
+
14
+ def wrap_fetcher
15
+ set_fetcher('git-rev', {
16
+ 'rev' => state['rev'],
17
+ 'wrapped_fetcher' => {
18
+ 'type' => fetcher,
19
+ 'options' => fetcher_opts
20
+ }
21
+ })
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module ConfCtl
2
+ module Swpins
3
+ def self.core_dir
4
+ File.join(ConfDir.path, 'swpins')
5
+ end
6
+
7
+ def self.channel_dir
8
+ File.join(ConfDir.path, 'swpins/channels')
9
+ end
10
+
11
+ def self.cluster_dir
12
+ File.join(ConfDir.path, 'swpins/cluster')
13
+ end
14
+ end
15
+ end
16
+
17
+ require_rel 'swpins'
@@ -0,0 +1,10 @@
1
+ require 'tty-command'
2
+
3
+ module ConfCtl
4
+ module SystemCommand
5
+ # @return [TTY::Command]
6
+ def self.new
7
+ TTY::Command.new(output: Logger.instance, color: false)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module ConfCtl
2
+ class UserScript
3
+ def self.register
4
+ UserScripts.register(self)
5
+ end
6
+
7
+ # @param hooks [Hook]
8
+ def setup_hooks(hooks); end
9
+
10
+ # @param app [GLI::App]
11
+ def setup_cli(app); end
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ module ConfCtl
2
+ module UserScripts
3
+ def self.load_scripts
4
+ dir = ConfDir.user_script_dir
5
+
6
+ begin
7
+ files = Dir.entries(dir)
8
+ rescue Errno::ENOENT
9
+ return
10
+ end
11
+
12
+ files.each do |f|
13
+ abs_path = File.join(dir, f)
14
+ next unless File.file?(abs_path)
15
+
16
+ load(abs_path)
17
+ end
18
+ end
19
+
20
+ def self.register(klass)
21
+ @scripts ||= []
22
+ @scripts << klass.new
23
+ end
24
+
25
+ def self.setup_all
26
+ each do |script|
27
+ script.setup_hooks(Hook)
28
+ end
29
+ end
30
+
31
+ # @return [Array<UserScript>]
32
+ def self.get
33
+ @scripts || []
34
+ end
35
+
36
+ # @yieldparam [UserScript]
37
+ def self.each(&)
38
+ get.each(&)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ module ConfCtl
2
+ module Utils::File
3
+ # Atomically replace or create symlink
4
+ # @param path [String] symlink path
5
+ # @param dst [String] destination
6
+ def replace_symlink(path, dst)
7
+ replacement = "#{path}.new-#{SecureRandom.hex(3)}"
8
+ File.symlink(dst, replacement)
9
+ File.rename(replacement, path)
10
+ end
11
+
12
+ # Unlink file if it exists
13
+ # @param path [String]
14
+ def unlink_if_exists(path)
15
+ File.unlink(path)
16
+ true
17
+ rescue Errno::ENOENT
18
+ false
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module ConfCtl
2
+ VERSION = '1.0.0'.freeze
3
+ end