confctl 1.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.
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