ConfigLMM 0.1.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.
- 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