ConfigLMM 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +5 -0
- data/Examples/Android.mm.yaml +8 -0
- data/Examples/Apps/Blog.mm.yaml +7 -0
- data/Examples/Apps/Jellyfin.mm.yaml +3 -0
- data/Examples/Implemented.mm.yaml +155 -0
- data/Examples/Keys.ini +7 -0
- data/Examples/Linux.mm.yaml +16 -0
- data/Examples/Windows.mm.yaml +11 -0
- data/Examples/configlmmAuth.sh +26 -0
- data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.conf.erb +38 -0
- data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.lmm.rb +19 -0
- data/Plugins/Apps/IPFS/IPFS.conf.erb +44 -0
- data/Plugins/Apps/IPFS/IPFS.lmm.rb +23 -0
- data/Plugins/Apps/InfluxDB/InfluxDB.conf.erb +34 -0
- data/Plugins/Apps/InfluxDB/InfluxDB.lmm.rb +19 -0
- data/Plugins/Apps/Jackett/Jackett.conf.erb +38 -0
- data/Plugins/Apps/Jackett/Jackett.lmm.rb +19 -0
- data/Plugins/Apps/Jellyfin/Jellyfin.conf.erb +59 -0
- data/Plugins/Apps/Jellyfin/Jellyfin.lmm.rb +23 -0
- data/Plugins/Apps/Mastodon/Mastodon.conf.erb +81 -0
- data/Plugins/Apps/Mastodon/Mastodon.lmm.rb +23 -0
- data/Plugins/Apps/Matrix/Matrix.conf.erb +36 -0
- data/Plugins/Apps/Matrix/Matrix.lmm.rb +23 -0
- data/Plugins/Apps/Netdata/Netdata.conf.erb +37 -0
- data/Plugins/Apps/Netdata/Netdata.lmm.rb +23 -0
- data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +165 -0
- data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +23 -0
- data/Plugins/Apps/Nginx/config-lmm/errors.conf +31 -0
- data/Plugins/Apps/Nginx/config-lmm/private.conf +6 -0
- data/Plugins/Apps/Nginx/config-lmm/proxy.conf +15 -0
- data/Plugins/Apps/Nginx/config-lmm/public.conf +3 -0
- data/Plugins/Apps/Nginx/config-lmm/ssl.conf +18 -0
- data/Plugins/Apps/Nginx/main.conf +30 -0
- data/Plugins/Apps/Nginx/nginx.conf +90 -0
- data/Plugins/Apps/Nginx/nginx.lmm.rb +62 -0
- data/Plugins/Apps/Nginx/proxy.conf.erb +31 -0
- data/Plugins/Apps/Odoo/Odoo.conf.erb +44 -0
- data/Plugins/Apps/Odoo/Odoo.lmm.rb +23 -0
- data/Plugins/Apps/Pterodactyl/Pterodactyl.conf.erb +50 -0
- data/Plugins/Apps/Pterodactyl/Pterodactyl.lmm.rb +30 -0
- data/Plugins/Apps/Pterodactyl/Wings.conf.erb +38 -0
- data/Plugins/Apps/Sunshine/Sunshine.conf.erb +31 -0
- data/Plugins/Apps/Sunshine/Sunshine.lmm.rb +21 -0
- data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +48 -0
- data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +25 -0
- data/Plugins/Apps/bitmagnet/bitmagnet.conf.erb +35 -0
- data/Plugins/Apps/bitmagnet/bitmagnet.lmm.rb +19 -0
- data/Plugins/Apps/gollum/config.ru +11 -0
- data/Plugins/Apps/gollum/gollum.conf.erb +41 -0
- data/Plugins/Apps/gollum/gollum.lmm.rb +52 -0
- data/Plugins/OS/Linux.lmm.rb +64 -0
- data/Plugins/OS/Routers/Aruba/ArubaInstant.lmm.rb +144 -0
- data/Plugins/Platforms/GitHub.lmm.rb +57 -0
- data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +83 -0
- data/Plugins/Platforms/GoDaddy/zone.txt.erb +13 -0
- data/Plugins/Platforms/porkbun.lmm.rb +129 -0
- data/Plugins/Platforms/porkbun_spec.rb +110 -0
- data/Plugins/Services/DNS/AmberBit.lmm.rb +14 -0
- data/Plugins/Services/DNS/ArubaItDNS.lmm.rb +14 -0
- data/Plugins/Services/DNS/NICLV.lmm.rb +18 -0
- data/Plugins/Services/DNS/PowerDNS.lmm.rb +261 -0
- data/Plugins/Services/DNS/tonic.lmm.rb +126 -0
- data/README.md +337 -0
- data/Rakefile +15 -0
- data/UNLICENSE +24 -0
- data/bin/configlmm +7 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/lib/ConfigLMM/Framework/plugins/dns.rb +63 -0
- data/lib/ConfigLMM/Framework/plugins/errors.rb +23 -0
- data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +55 -0
- data/lib/ConfigLMM/Framework/plugins/plugin.rb +167 -0
- data/lib/ConfigLMM/Framework/plugins/ssh.rb +37 -0
- data/lib/ConfigLMM/Framework/plugins/store.rb +57 -0
- data/lib/ConfigLMM/Framework/plugins.rb +5 -0
- data/lib/ConfigLMM/Framework/registrator.rb +32 -0
- data/lib/ConfigLMM/Framework.rb +9 -0
- data/lib/ConfigLMM/LMM/plugins.rb +5 -0
- data/lib/ConfigLMM/LMM.rb +8 -0
- data/lib/ConfigLMM/cli.rb +161 -0
- data/lib/ConfigLMM/command.rb +53 -0
- data/lib/ConfigLMM/commands/build.rb +41 -0
- data/lib/ConfigLMM/commands/cleanup.rb +30 -0
- data/lib/ConfigLMM/commands/configsCommand.rb +167 -0
- data/lib/ConfigLMM/commands/deploy.rb +39 -0
- data/lib/ConfigLMM/commands/diff.rb +45 -0
- data/lib/ConfigLMM/commands/list.rb +15 -0
- data/lib/ConfigLMM/commands/refresh.rb +46 -0
- data/lib/ConfigLMM/commands/types.rb +35 -0
- data/lib/ConfigLMM/commands/validate.rb +49 -0
- data/lib/ConfigLMM/context.rb +52 -0
- data/lib/ConfigLMM/io/configList.rb +98 -0
- data/lib/ConfigLMM/io/path.rb +48 -0
- data/lib/ConfigLMM/io/source.rb +47 -0
- data/lib/ConfigLMM/io.rb +2 -0
- data/lib/ConfigLMM/state.rb +78 -0
- data/lib/ConfigLMM/utils/filters.rb +126 -0
- data/lib/ConfigLMM/version.rb +5 -0
- data/lib/ConfigLMM.rb +6 -0
- data/sig/ConfigLMM.rbs +4 -0
- metadata +485 -0
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
require_relative '../utils/filters'
|
5
|
+
require_relative '../io/configList'
|
6
|
+
require_relative '../context'
|
7
|
+
require_relative '../state'
|
8
|
+
require_relative '../Framework'
|
9
|
+
require_relative '../LMM'
|
10
|
+
require 'xdg'
|
11
|
+
require 'tmpdir'
|
12
|
+
|
13
|
+
module ConfigLMM
|
14
|
+
module Commands
|
15
|
+
class ConfigsCommand < ConfigLMM::Command
|
16
|
+
|
17
|
+
def initialize(configPaths, options)
|
18
|
+
@ConfigPaths = configPaths
|
19
|
+
@Options = options
|
20
|
+
@Plugins = {}
|
21
|
+
|
22
|
+
logger do |config|
|
23
|
+
config.level = @Options[:level]
|
24
|
+
end
|
25
|
+
|
26
|
+
@Context = Context.new(logger, prompt, ::XDG.new, @Options[:context])
|
27
|
+
@State = State.new(logger, prompt)
|
28
|
+
@Diff = {}
|
29
|
+
|
30
|
+
# Load all Plugin files
|
31
|
+
Framework::Registrator.registerAll(logger)
|
32
|
+
|
33
|
+
# Create Plugin instances
|
34
|
+
Framework::Store.boot(logger, prompt, @Plugins)
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute
|
38
|
+
raise ConfigLMM::CLI::MissingArgument.new("ERROR: No configs specified!\n\n") if @ConfigPaths.empty?
|
39
|
+
|
40
|
+
options = @Options.dup
|
41
|
+
options.delete(:locations)
|
42
|
+
options.delete(:things)
|
43
|
+
#options[:locationFilter] = Utils::Filters.parseLocationsOption(@Options[:locations], logger)
|
44
|
+
#options[:thingFilter] = Utils::Filters.parseThingsOption(@Options[:things], logger)
|
45
|
+
|
46
|
+
configList = IO::ConfigList.create(@ConfigPaths, logger)
|
47
|
+
configList.expand!(options[:locationFilter])
|
48
|
+
|
49
|
+
@State.load!(configList, options)
|
50
|
+
|
51
|
+
self.processConfig(configList.toConfig(@Context), options)
|
52
|
+
end
|
53
|
+
|
54
|
+
def plugins
|
55
|
+
@Plugins
|
56
|
+
end
|
57
|
+
|
58
|
+
def state
|
59
|
+
@State
|
60
|
+
end
|
61
|
+
|
62
|
+
def context
|
63
|
+
@Context
|
64
|
+
end
|
65
|
+
|
66
|
+
def findBestProvider(plugins)
|
67
|
+
raise 'No providers!' if plugins.empty?
|
68
|
+
# TODO FIXME
|
69
|
+
# In case of multiple providers that match
|
70
|
+
# We should chose best one and save it in state
|
71
|
+
plugins.first
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def invokeValidateAction(id, plugin, singleTarget, options)
|
77
|
+
actionMethod = plugin.class.actionMethod(singleTarget['Type'], 'Validate')
|
78
|
+
plugin.send(actionMethod, id, singleTarget, state, context, options)
|
79
|
+
end
|
80
|
+
|
81
|
+
def invokeRefreshAction(id, plugin, singleTarget, options)
|
82
|
+
state.create!
|
83
|
+
activeState = state.item(id)
|
84
|
+
if activeState[:Type].nil?
|
85
|
+
activeState[:Type] = singleTarget['Type'].to_s
|
86
|
+
elsif activeState[:Type] != singleTarget['Type'].to_s
|
87
|
+
raise Framework::PluginError.new("Unexpected Type #{activeState[:Type].inspect}! Wanted #{singleTarget['Type']}")
|
88
|
+
end
|
89
|
+
actionMethod = plugin.class.actionMethod(singleTarget['Type'], 'Refresh')
|
90
|
+
if plugin.methods.include?(:authenticate)
|
91
|
+
result = plugin.authenticate(actionMethod, singleTarget, state, context, options)
|
92
|
+
raise Framework::PluginAuthError.new('Failed to authenticate!') unless result
|
93
|
+
end
|
94
|
+
plugin.send(actionMethod, id, singleTarget, activeState, context, options)
|
95
|
+
state.save
|
96
|
+
end
|
97
|
+
|
98
|
+
def invokeDiffAction(id, plugin, singleTarget, options)
|
99
|
+
state.create!
|
100
|
+
activeState = state.item(id)
|
101
|
+
if activeState[:Type].nil?
|
102
|
+
activeState[:Type] = singleTarget['Type'].to_s
|
103
|
+
elsif activeState[:Type] != singleTarget['Type'].to_s
|
104
|
+
raise Framework::PluginError.new("Unexpected Type #{activeState[:Type].inspect}! Wanted #{singleTarget['Type']}")
|
105
|
+
end
|
106
|
+
actionMethod = plugin.class.actionMethod(singleTarget['Type'], 'Diff')
|
107
|
+
plugin.send(actionMethod, id, singleTarget, activeState, context, options)
|
108
|
+
end
|
109
|
+
|
110
|
+
def invokeBuildAction(id, plugin, singleTarget, options)
|
111
|
+
actionMethod = plugin.class.actionMethod(singleTarget['Type'], 'Build')
|
112
|
+
plugin.send(actionMethod, id, singleTarget, state, context, options)
|
113
|
+
end
|
114
|
+
|
115
|
+
def invokeDeployAction(id, plugin, singleTarget, options)
|
116
|
+
prompt.warn("Deploying #{singleTarget['ID']}: #{singleTarget['Type'].to_s}")
|
117
|
+
state.create!
|
118
|
+
activeState = state.item(id)
|
119
|
+
if activeState[:Type].nil?
|
120
|
+
activeState[:Type] = singleTarget['Type'].to_s
|
121
|
+
elsif activeState[:Type] != singleTarget['Type'].to_s
|
122
|
+
raise Framework::PluginError.new("Unexpected Type #{activeState[:Type].inspect}! Wanted #{singleTarget['Type']}")
|
123
|
+
end
|
124
|
+
actionMethod = plugin.class.actionMethod(singleTarget['Type'], 'Deploy')
|
125
|
+
if plugin.methods.include?(:authenticate)
|
126
|
+
result = plugin.authenticate(actionMethod, singleTarget, state, context, options)
|
127
|
+
raise Framework::PluginAuthError.new('Failed to authenticate!') unless result
|
128
|
+
end
|
129
|
+
|
130
|
+
Dir.mktmpdir do |outputDir|
|
131
|
+
|
132
|
+
if plugin.class.persistBuildDir?
|
133
|
+
if options['output'] == '/tmp or ./build'
|
134
|
+
options = options.dup
|
135
|
+
options['output'] = './build'
|
136
|
+
if !options['dry']
|
137
|
+
FileUtils.mkdir_p(options['output'])
|
138
|
+
end
|
139
|
+
end
|
140
|
+
else
|
141
|
+
options = options.dup
|
142
|
+
options['output'] = outputDir
|
143
|
+
end
|
144
|
+
|
145
|
+
if !options['dry']
|
146
|
+
# Prevent others accessing it
|
147
|
+
FileUtils.chmod(0750, options['output'])
|
148
|
+
end
|
149
|
+
|
150
|
+
if plugin.hasAction?(singleTarget['Type'], :build)
|
151
|
+
invokeBuildAction(id, plugin, singleTarget, options)
|
152
|
+
end
|
153
|
+
|
154
|
+
plugin.send(actionMethod, id, singleTarget, activeState, context, options)
|
155
|
+
end
|
156
|
+
state.save
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
def checkDiff(id, targetName, stateName, target, activeState)
|
161
|
+
if target[targetName] != activeState[stateName]
|
162
|
+
@Diff[id] = target
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'configsCommand'
|
4
|
+
|
5
|
+
module ConfigLMM
|
6
|
+
module Commands
|
7
|
+
class Deploy < ConfigsCommand
|
8
|
+
def processConfig(config, options)
|
9
|
+
config.each do |id, target|
|
10
|
+
|
11
|
+
target['Resources'].to_h.each do |id, config|
|
12
|
+
IO::ConfigList.processConfig(id, config, target[:Parent])
|
13
|
+
processDeploy(IO::ConfigList.normalizeId(id), config, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
processDeploy(id, target, options)
|
17
|
+
end
|
18
|
+
prompt.ok('Deploy successful!')
|
19
|
+
end
|
20
|
+
|
21
|
+
def processDeploy(id, target, options)
|
22
|
+
providers = []
|
23
|
+
self.plugins.each do |pluginId, plugin|
|
24
|
+
if plugin.hasAction?(target['Type'], :deploy)
|
25
|
+
providers << plugin
|
26
|
+
end
|
27
|
+
end
|
28
|
+
message = "Couldn't find action Build for #{target['Type']}"
|
29
|
+
if providers.empty?
|
30
|
+
logger.debug(message)
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
bestProvider = self.findBestProvider(providers)
|
35
|
+
invokeDeployAction(id, bestProvider, target, options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'configsCommand'
|
4
|
+
|
5
|
+
module ConfigLMM
|
6
|
+
module Commands
|
7
|
+
class Diff < ConfigsCommand
|
8
|
+
def processConfig(config, options)
|
9
|
+
configDiffs = {}
|
10
|
+
config.each do |id, data|
|
11
|
+
found = false
|
12
|
+
plugins.each do |pluginId, plugin|
|
13
|
+
if plugin.hasAction?(data['Type'], :diff)
|
14
|
+
invokeDiffAction(id, plugin, data, options)
|
15
|
+
configDiffs[id] = plugin.diff unless plugin.diff.empty?
|
16
|
+
found = true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
logger.debug("Couldn't find action Diff for type #{data['Type']}") unless found
|
20
|
+
end
|
21
|
+
showDiff(configDiffs)
|
22
|
+
end
|
23
|
+
|
24
|
+
def showDiff(configDiffs)
|
25
|
+
configDiffs.each do |id, diffs|
|
26
|
+
prompt.say(' ' + id + ':')
|
27
|
+
diffs.each do |name, diff|
|
28
|
+
if diff.first.is_a?(Hash)
|
29
|
+
prompt.say(' ' + name + ':')
|
30
|
+
diff.first.each do |name, value|
|
31
|
+
prompt.say('- ' + name + ': ' + value, :color => :red)
|
32
|
+
end
|
33
|
+
diff.last.each do |name, value|
|
34
|
+
prompt.say('+ ' + name + ': ' + value, :color => :green)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
prompt.say('- ' + name + ': ' + diff.first, :color => :red)
|
38
|
+
prompt.say('+ ' + name + ': ' + diff.last, :color => :green)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'configsCommand'
|
4
|
+
|
5
|
+
module ConfigLMM
|
6
|
+
module Commands
|
7
|
+
class List < ConfigsCommand
|
8
|
+
def processConfig(config, options)
|
9
|
+
config.each do |id, data|
|
10
|
+
prompt.say("#{data['Name']}: #{data['Type']}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'configsCommand'
|
4
|
+
|
5
|
+
module ConfigLMM
|
6
|
+
module Commands
|
7
|
+
class Refresh < ConfigsCommand
|
8
|
+
def processConfig(config, options)
|
9
|
+
errors = 0
|
10
|
+
config.each do |id, target|
|
11
|
+
|
12
|
+
target['Resources'].to_h.each do |id, target|
|
13
|
+
IO::ConfigList.processConfig(id, target, target[:Parent])
|
14
|
+
processRefresh(IO::ConfigList.normalizeId(id), target, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
errors += processRefresh(id, target, options)
|
18
|
+
end
|
19
|
+
if errors.zero?
|
20
|
+
prompt.ok('Refresh successful!')
|
21
|
+
else
|
22
|
+
prompt.error('Encountered issue while refreshing state!')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def processRefresh(id, target, options)
|
27
|
+
errors = 0
|
28
|
+
found = false
|
29
|
+
self.plugins.each do |pluginId, plugin|
|
30
|
+
if plugin.hasAction?(target['Type'], :refresh)
|
31
|
+
begin
|
32
|
+
invokeRefreshAction(id, plugin, target, options)
|
33
|
+
rescue Framework::PluginError => e
|
34
|
+
logger.error(e)
|
35
|
+
errors += 1
|
36
|
+
end
|
37
|
+
found = true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
# We allow plugins without refresh action
|
41
|
+
logger.debug("Couldn't find action Refresh for type #{target['Type']}") unless found
|
42
|
+
errors
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
require_relative '../Framework'
|
5
|
+
require 'set'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
module ConfigLMM
|
9
|
+
module Commands
|
10
|
+
class Types < ConfigLMM::Command
|
11
|
+
def initialize(options)
|
12
|
+
logger do |config|
|
13
|
+
config.level = options[:level]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Load all Plugin files
|
17
|
+
Framework::Registrator.registerAll(logger)
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute
|
21
|
+
types = {}
|
22
|
+
Framework::Store.plugins.each do |plugin|
|
23
|
+
plugin.instance_methods.each do |method|
|
24
|
+
if match = method.match('^action(\w+)(Validate|Build|Refresh|Diff|Deploy)$')
|
25
|
+
type = match[1]
|
26
|
+
types[type] ||= []
|
27
|
+
types[type] << match[2]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
prompt.say(YAML.dump({ 'Types' => types.sort.to_h }))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'configsCommand'
|
4
|
+
|
5
|
+
module ConfigLMM
|
6
|
+
module Commands
|
7
|
+
class Validate < ConfigsCommand
|
8
|
+
def processConfig(config, options)
|
9
|
+
|
10
|
+
errors = 0
|
11
|
+
config.each do |id, target|
|
12
|
+
target['Resources'].to_h.each do |id, target|
|
13
|
+
IO::ConfigList.processConfig(id, target, target[:Parent])
|
14
|
+
processValidate(IO::ConfigList.normalizeId(id), target, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
errors += 1 unless processValidate(id, target, options)
|
18
|
+
end
|
19
|
+
if errors.zero?
|
20
|
+
prompt.ok('Validation successful, no issues were found!')
|
21
|
+
else
|
22
|
+
prompt.error('Validation failed, we found some issues!')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def processValidate(id, target, options)
|
27
|
+
providers = []
|
28
|
+
self.plugins.each do |pluginId, plugin|
|
29
|
+
if plugin.hasAction?(target['Type'], :validate)
|
30
|
+
providers << plugin
|
31
|
+
end
|
32
|
+
end
|
33
|
+
if providers.empty?
|
34
|
+
# We allow for validation function to not exist
|
35
|
+
true
|
36
|
+
else
|
37
|
+
errors = []
|
38
|
+
providers.each do |provider|
|
39
|
+
errors += invokeValidateAction(id, provider, target, options)
|
40
|
+
end
|
41
|
+
errors.each do |error|
|
42
|
+
prompt.error(error)
|
43
|
+
end
|
44
|
+
errors.empty?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
# encoding: UTF-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module ConfigLMM
|
8
|
+
class Context
|
9
|
+
CONTEXT_FILE = 'configlmm/context.yaml'
|
10
|
+
|
11
|
+
def initialize(logger, prompt, xdg, contextFile)
|
12
|
+
@Logger = logger
|
13
|
+
@Prompt = prompt
|
14
|
+
load!(xdg.config_home, contextFile)
|
15
|
+
end
|
16
|
+
|
17
|
+
def likes?(name)
|
18
|
+
@Context['Likes'].include?(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def dislikes?(name)
|
22
|
+
@Context['Dislikes'].include?(name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(context)
|
26
|
+
return unless context
|
27
|
+
context['Likes'] ||= []
|
28
|
+
context['Dislikes'] ||= []
|
29
|
+
@Context['Likes'] += context['Likes']
|
30
|
+
@Context['Dislikes'] += context['Dislikes']
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def load!(configHome, contextFile)
|
36
|
+
@Context = {}
|
37
|
+
if (contextFile && !File.exist?(contextFile))
|
38
|
+
@Logger.error("Provided Context file doesn't exist: #{contextFile}")
|
39
|
+
raise 'Missing Context!'
|
40
|
+
end
|
41
|
+
if !contextFile
|
42
|
+
contextFile = configHome / CONTEXT_FILE
|
43
|
+
end
|
44
|
+
if (File.exist?(contextFile))
|
45
|
+
@Context = YAML.safe_load_file(contextFile, permitted_classes: [Symbol])
|
46
|
+
end
|
47
|
+
@Context['Likes'] ||= []
|
48
|
+
@Context['Dislikes'] ||= []
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'path'
|
4
|
+
require 'find'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module ConfigLMM
|
8
|
+
module IO
|
9
|
+
class ConfigList
|
10
|
+
ConfigError = Class.new(RuntimeError)
|
11
|
+
|
12
|
+
def self.create(targets, logger)
|
13
|
+
targets = targets.uniq.select do |target|
|
14
|
+
exist = File.exist?(target)
|
15
|
+
logger.warn("'#{path}' doesn't exist, ignoring!") unless exist
|
16
|
+
exist
|
17
|
+
end
|
18
|
+
self.new(targets)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(targets)
|
22
|
+
@Sources = targets.map do |target|
|
23
|
+
Path.new(target)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def expand!(locationFilter)
|
28
|
+
sources = []
|
29
|
+
@Sources.each do |source|
|
30
|
+
basePath = source.to_s
|
31
|
+
if File.file?(basePath)
|
32
|
+
parent = Path.new(File.dirname(File.expand_path(basePath)))
|
33
|
+
sources << Path.new(basePath, parent)
|
34
|
+
else
|
35
|
+
parent = source
|
36
|
+
::Find.find(basePath) do |path|
|
37
|
+
next unless Path.isConfig?(path)
|
38
|
+
parent = parent.lookupParent(path)
|
39
|
+
if File.directory?(path)
|
40
|
+
parent = Path.new(path, parent)
|
41
|
+
next
|
42
|
+
end
|
43
|
+
path = Path.new(path, parent)
|
44
|
+
sources << path if Utils::Filters.includePath?(path, locationFilter)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
@Sources = sources
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.normalizeId(id)
|
52
|
+
# Remove all non-letters but allow Unicode
|
53
|
+
id.gsub(/[[:space:]]/, '').upcase
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.processConfig(id, data, parent)
|
57
|
+
data['ID'] = id
|
58
|
+
if data['Type'].nil?
|
59
|
+
raise ConfigError.new("Missing 'Type' field: #{id}!")
|
60
|
+
end
|
61
|
+
data['Name'] = id unless data.has_key?('Name')
|
62
|
+
data['Type'] = data['Type'].to_sym
|
63
|
+
data[:Parent] = parent
|
64
|
+
data
|
65
|
+
end
|
66
|
+
|
67
|
+
def toConfig(context)
|
68
|
+
config = {}
|
69
|
+
@Sources.each do |source|
|
70
|
+
YAML.safe_load_file(source.to_s, permitted_classes: [Symbol]).each do |id, data|
|
71
|
+
normalizedId = self.class.normalizeId(id)
|
72
|
+
if id == '_CONTEXT_'
|
73
|
+
context.add(data)
|
74
|
+
next
|
75
|
+
end
|
76
|
+
|
77
|
+
self.class.processConfig(id, data, source.parent)
|
78
|
+
|
79
|
+
# TODO FIXME we should deep merge them instead
|
80
|
+
raise ConfigError.new("Duplicate ID: #{id} (#{normalizedId}) - #{source}") if config.has_key?(normalizedId)
|
81
|
+
config[normalizedId] = data
|
82
|
+
end
|
83
|
+
#rescue YAML::SyntaxError => error
|
84
|
+
# raise ConfigError.new(error)
|
85
|
+
end
|
86
|
+
config
|
87
|
+
end
|
88
|
+
|
89
|
+
def count
|
90
|
+
@Sources.length
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_a
|
94
|
+
@Sources
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require_relative 'source'
|
5
|
+
|
6
|
+
module ConfigLMM
|
7
|
+
module IO
|
8
|
+
class Path < Source
|
9
|
+
def initialize(path, parent = nil)
|
10
|
+
@Path = path.is_a?(Pathname) ? path : Pathname.new(path)
|
11
|
+
super(parent)
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
@Path.basename.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def basename
|
19
|
+
@Path.basename(@Path.extname).to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
def dirname
|
23
|
+
parent.basename unless parent.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.isConfig?(path)
|
27
|
+
return false unless File.extname(path) == '.yaml'
|
28
|
+
File.basename(path, '.yaml').end_with?('.mm')
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
@Path.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(other)
|
36
|
+
self.to_s == other.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def lookupParent(path)
|
40
|
+
if path.start_with?(self.to_s)
|
41
|
+
self
|
42
|
+
else
|
43
|
+
parent&.lookupParent(path) or raise "Didn't find parent for #{path}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ConfigLMM
|
4
|
+
module IO
|
5
|
+
class Source
|
6
|
+
|
7
|
+
def initialize(parent = nil)
|
8
|
+
unless parent.nil? || parent.is_a?(Source)
|
9
|
+
raise "Invalid parent! Must be instance of Source! Got #{parent.inspect}"
|
10
|
+
end
|
11
|
+
@ID = false
|
12
|
+
@Parent = parent
|
13
|
+
@Childs = {}
|
14
|
+
@Parent.addChild(self) if @Parent.is_a?(Source)
|
15
|
+
end
|
16
|
+
|
17
|
+
def id
|
18
|
+
return @ID unless @ID == false
|
19
|
+
if @Parent.is_a?(Source)
|
20
|
+
@ID = self.name
|
21
|
+
@ID = @Parent.id + '/' + @ID if @Parent.id
|
22
|
+
else
|
23
|
+
@ID = nil
|
24
|
+
end
|
25
|
+
@ID
|
26
|
+
end
|
27
|
+
|
28
|
+
def parent
|
29
|
+
@Parent
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](name)
|
33
|
+
raise "Didn't find #{name} in #{self.inspect}" unless @Childs.key?(name)
|
34
|
+
@Childs[name]
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def addChild(child)
|
40
|
+
raise "Invalid child! Must be instance of Source! Got #{child.inspect}" unless child.is_a?(Source)
|
41
|
+
return if @Childs.key?(child.name)
|
42
|
+
@Childs[child.name] = child
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/ConfigLMM/io.rb
ADDED